mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 10:58:23 +00:00
Add built-in Tor support via kmptor library
Introduces embedded Tor functionality as an alternative to the existing Orbot proxy integration, so users no longer need an external app to route traffic through the Tor network. Key changes: - Add TorMode enum (DISABLED, ORBOT, BUILTIN) replacing the useProxy boolean - Create TorManager (free-flavor only) using io.matthewnelson.kmp-tor:runtime and resource-exec-tor to manage the Tor daemon lifecycle - Offline flavor gets a no-op TorManager stub to maintain build compatibility - AmberSettings: replace useProxy field with torMode enum; useProxy becomes a computed property (torMode != DISABLED) for backward compatibility - LocalPreferences: migrate old use_proxy boolean to TorMode on first load, save/load torMode, update proxy config per mode - Amber: start built-in Tor on app launch when torMode == BUILTIN; isSocksProxyAlive returns TorManager.isRunning for built-in mode - ConnectOrbotDialog: add Built-in Tor section above the Orbot section - SettingsScreen: show mode-specific disconnect label (built-in vs orbot) - HttpClientManager: add clearProxy() public method - AndroidManifest: add INTERNET permission explicitly - gradle: add kmptor 2.6.0 + resource-exec-tor 408.19.0 dependencies, enable jniLibs.useLegacyPackaging for native binary extraction https://claude.ai/code/session_01JwcwxdYrDnr3QyHqTBz33a
This commit is contained in:
@@ -164,6 +164,7 @@ android {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
excludes +='META-INF/versions/9/OSGI-INF/MANIFEST.MF'
|
||||
}
|
||||
jniLibs.useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +234,8 @@ dependencies {
|
||||
|
||||
freeImplementation libs.okhttp
|
||||
freeImplementation libs.okhttpCoroutines
|
||||
freeImplementation libs.kmptor.runtime
|
||||
freeImplementation libs.kmptor.resource.exec
|
||||
|
||||
// Load images from the web.
|
||||
implementation libs.coil.compose
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.greenart7c3.nostrsigner.okhttp.HttpClientManager
|
||||
import io.matthewnelson.kmp.tor.resource.exec.tor.ResourceLoaderTorExec
|
||||
import io.matthewnelson.kmp.tor.runtime.RuntimeEvent
|
||||
import io.matthewnelson.kmp.tor.runtime.TorRuntime
|
||||
import io.matthewnelson.kmp.tor.runtime.core.OnEvent
|
||||
import io.matthewnelson.kmp.tor.runtime.core.config.TorOption
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object TorManager {
|
||||
private const val TAG = "TorManager"
|
||||
|
||||
@Volatile
|
||||
private var torRuntime: TorRuntime? = null
|
||||
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
val isRunning: StateFlow<Boolean> = _isRunning.asStateFlow()
|
||||
|
||||
fun start(context: Context, scope: CoroutineScope) {
|
||||
if (torRuntime != null) return
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val workDir = context.filesDir.resolve("kmptor")
|
||||
val cacheDir = context.cacheDir.resolve("kmptor")
|
||||
|
||||
val env = TorRuntime.Environment.Builder(workDir, cacheDir, ResourceLoaderTorExec::getOrCreate)
|
||||
|
||||
val runtime = TorRuntime.Builder(env) {
|
||||
// Log all runtime events for debugging
|
||||
RuntimeEvent.entries().forEach { event ->
|
||||
observerStatic(event, OnEvent.Executor.Immediate) { data ->
|
||||
Log.d(TAG, data.toString())
|
||||
}
|
||||
}
|
||||
|
||||
// Observe SOCKS listeners to get the assigned port
|
||||
observerStatic(RuntimeEvent.LISTENERS, OnEvent.Executor.Immediate) { listeners ->
|
||||
val addr = listeners.socks.firstOrNull()
|
||||
if (addr != null) {
|
||||
try {
|
||||
val port = addr.port.value
|
||||
Log.i(TAG, "Built-in Tor SOCKS proxy on port $port")
|
||||
HttpClientManager.setDefaultProxyOnPort(port)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to read Tor SOCKS port", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config {
|
||||
TorOption.__SocksPort.configure { auto() }
|
||||
}
|
||||
}
|
||||
|
||||
torRuntime = runtime
|
||||
runtime.startDaemonSync()
|
||||
_isRunning.value = true
|
||||
Log.i(TAG, "Built-in Tor started")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to start built-in Tor", e)
|
||||
torRuntime = null
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
try {
|
||||
torRuntime?.stopDaemonSync()
|
||||
Log.i(TAG, "Built-in Tor stopped")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error stopping built-in Tor", e)
|
||||
} finally {
|
||||
torRuntime = null
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
<package android:name="dev.zapstore.app" />
|
||||
</queries>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
@@ -37,10 +37,12 @@ import com.greenart7c3.nostrsigner.okhttp.HttpClientManager
|
||||
import com.greenart7c3.nostrsigner.okhttp.OkHttpWebSocket
|
||||
import com.greenart7c3.nostrsigner.relays.AmberRelayStats
|
||||
import com.greenart7c3.nostrsigner.relays.NostrClientLoggerListener
|
||||
import com.greenart7c3.nostrsigner.models.TorMode
|
||||
import com.greenart7c3.nostrsigner.service.ClearLogsWorker
|
||||
import com.greenart7c3.nostrsigner.service.ConnectivityService
|
||||
import com.greenart7c3.nostrsigner.service.NotificationSubscription
|
||||
import com.greenart7c3.nostrsigner.service.ProfileSubscription
|
||||
import com.greenart7c3.nostrsigner.service.TorManager
|
||||
import com.greenart7c3.nostrsigner.service.crashreports.CrashReportCache
|
||||
import com.greenart7c3.nostrsigner.service.crashreports.UnexpectedCrashSaver
|
||||
import com.greenart7c3.nostrsigner.ui.ToastManager
|
||||
@@ -138,6 +140,9 @@ class Amber :
|
||||
val notificationCache = LruCache<String, Long>(10)
|
||||
|
||||
fun isSocksProxyAlive(proxyHost: String, proxyPort: Int): Boolean {
|
||||
if (settings.torMode == TorMode.BUILTIN) {
|
||||
return TorManager.isRunning.value
|
||||
}
|
||||
try {
|
||||
val socket = Socket()
|
||||
socket.connect(InetSocketAddress(proxyHost, proxyPort), 5000) // 3-second timeout
|
||||
@@ -245,6 +250,9 @@ class Amber :
|
||||
LocalPreferences.switchToAccount(this@Amber, LocalPreferences.allSavedAccounts(this@Amber).first().npub)
|
||||
}
|
||||
LocalPreferences.reloadApp()
|
||||
if (settings.torMode == TorMode.BUILTIN) {
|
||||
TorManager.start(this@Amber, applicationIOScope)
|
||||
}
|
||||
checkForNewRelaysAndUpdateAllFilters(true)
|
||||
if (settings.killSwitch.value) {
|
||||
disconnectIntentionally()
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.core.content.edit
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.AmberSettings
|
||||
import com.greenart7c3.nostrsigner.models.TorMode
|
||||
import com.greenart7c3.nostrsigner.models.defaultAppRelays
|
||||
import com.greenart7c3.nostrsigner.models.defaultIndexerRelays
|
||||
import com.greenart7c3.nostrsigner.okhttp.HttpClientManager
|
||||
@@ -47,6 +48,7 @@ private enum class SettingsKeys(val key: String) {
|
||||
SAVED_ACCOUNTS("all_saved_accounts"),
|
||||
USE_PROXY("use_proxy"),
|
||||
PROXY_PORT("proxy_port"),
|
||||
TOR_MODE("tor_mode"),
|
||||
BATERRY_OPTIMIZATION("battery_optimization"),
|
||||
KILL_SWITCH("kill_switch"),
|
||||
LANGUAGE_PREFS("languagePreferences"),
|
||||
@@ -119,7 +121,7 @@ object LocalPreferences {
|
||||
putBoolean(SettingsKeys.USE_AUTH.key, settings.useAuth)
|
||||
putInt(SettingsKeys.BIOMETRICS_TYPE.key, settings.biometricsTimeType.screenCode)
|
||||
putBoolean(SettingsKeys.USE_PIN.key, settings.usePin)
|
||||
putBoolean(SettingsKeys.USE_PROXY.key, settings.useProxy)
|
||||
putString(SettingsKeys.TOR_MODE.key, settings.torMode.name)
|
||||
putInt(SettingsKeys.PROXY_PORT.key, settings.proxyPort)
|
||||
putBoolean(SettingsKeys.KILL_SWITCH.key, settings.killSwitch.value)
|
||||
putString(SettingsKeys.LANGUAGE_PREFS.key, settings.language)
|
||||
@@ -192,8 +194,23 @@ object LocalPreferences {
|
||||
fun loadSettingsFromEncryptedStorage(context: Context = Amber.instance): AmberSettings {
|
||||
checkNotInMainThread()
|
||||
sharedPrefs(context).apply {
|
||||
// Migrate old use_proxy boolean to TorMode enum
|
||||
val torMode = if (contains(SettingsKeys.TOR_MODE.key)) {
|
||||
try {
|
||||
TorMode.valueOf(getString(SettingsKeys.TOR_MODE.key, TorMode.DISABLED.name)!!)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
TorMode.DISABLED
|
||||
}
|
||||
} else if (getBoolean(SettingsKeys.USE_PROXY.key, false)) {
|
||||
TorMode.ORBOT
|
||||
} else {
|
||||
TorMode.DISABLED
|
||||
}
|
||||
|
||||
val proxyPort = getInt(SettingsKeys.PROXY_PORT.key, 9050)
|
||||
HttpClientManager.setDefaultProxyOnPort(proxyPort)
|
||||
if (torMode == TorMode.ORBOT) {
|
||||
HttpClientManager.setDefaultProxyOnPort(proxyPort)
|
||||
}
|
||||
|
||||
return AmberSettings(
|
||||
defaultRelays = getStringSet(SettingsKeys.DEFAULT_RELAYS.key, null)?.mapNotNull {
|
||||
@@ -206,8 +223,8 @@ object LocalPreferences {
|
||||
useAuth = getBoolean(SettingsKeys.USE_AUTH.key, false),
|
||||
biometricsTimeType = parseBiometricsTimeType(getInt(SettingsKeys.BIOMETRICS_TYPE.key, 0)),
|
||||
usePin = getBoolean(SettingsKeys.USE_PIN.key, false),
|
||||
useProxy = getBoolean(SettingsKeys.USE_PROXY.key, false),
|
||||
proxyPort = getInt(SettingsKeys.PROXY_PORT.key, 9050),
|
||||
torMode = torMode,
|
||||
proxyPort = proxyPort,
|
||||
killSwitch = MutableStateFlow(getBoolean(SettingsKeys.KILL_SWITCH.key, false)),
|
||||
language = getString(SettingsKeys.LANGUAGE_PREFS.key, null),
|
||||
)
|
||||
@@ -425,15 +442,28 @@ object LocalPreferences {
|
||||
context: Context,
|
||||
useProxy: Boolean,
|
||||
port: Int,
|
||||
) {
|
||||
val torMode = if (useProxy) TorMode.ORBOT else TorMode.DISABLED
|
||||
updateTorMode(context, torMode, port)
|
||||
}
|
||||
|
||||
fun updateTorMode(
|
||||
context: Context,
|
||||
torMode: TorMode,
|
||||
port: Int = Amber.instance.settings.proxyPort,
|
||||
) {
|
||||
sharedPrefs(context).edit {
|
||||
apply {
|
||||
putBoolean(SettingsKeys.USE_PROXY.key, useProxy)
|
||||
putString(SettingsKeys.TOR_MODE.key, torMode.name)
|
||||
putInt(SettingsKeys.PROXY_PORT.key, port)
|
||||
}
|
||||
}
|
||||
Amber.instance.settings = loadSettingsFromEncryptedStorage()
|
||||
HttpClientManager.setDefaultProxyOnPort(port)
|
||||
when (torMode) {
|
||||
TorMode.ORBOT -> HttpClientManager.setDefaultProxyOnPort(port)
|
||||
TorMode.DISABLED -> HttpClientManager.clearProxy()
|
||||
TorMode.BUILTIN -> { /* Proxy port set by TorManager once Tor starts */ }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadFromEncryptedStorage(context: Context, npub: String): Account? {
|
||||
|
||||
@@ -25,8 +25,10 @@ data class AmberSettings(
|
||||
val useAuth: Boolean = false,
|
||||
val biometricsTimeType: BiometricsTimeType = BiometricsTimeType.EVERY_TIME,
|
||||
val usePin: Boolean = false,
|
||||
val useProxy: Boolean = false,
|
||||
val torMode: TorMode = TorMode.DISABLED,
|
||||
val proxyPort: Int = 9050,
|
||||
val killSwitch: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
var language: String? = null,
|
||||
)
|
||||
) {
|
||||
val useProxy: Boolean get() = torMode != TorMode.DISABLED
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.greenart7c3.nostrsigner.models
|
||||
|
||||
enum class TorMode {
|
||||
DISABLED,
|
||||
ORBOT,
|
||||
BUILTIN,
|
||||
}
|
||||
@@ -110,4 +110,8 @@ object HttpClientManager {
|
||||
fun setDefaultProxyOnPort(port: Int) {
|
||||
setDefaultProxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", port)))
|
||||
}
|
||||
|
||||
fun clearProxy() {
|
||||
setDefaultProxy(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.TorMode
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
@@ -111,7 +112,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() {
|
||||
|
||||
if (LocalPreferences.allSavedAccounts(Amber.instance).isEmpty()) {
|
||||
Amber.instance.settings = Amber.instance.settings.copy(
|
||||
useProxy = useProxy,
|
||||
torMode = if (useProxy) TorMode.ORBOT else TorMode.DISABLED,
|
||||
proxyPort = proxyPort,
|
||||
)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(
|
||||
@@ -145,7 +146,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() {
|
||||
)
|
||||
if (LocalPreferences.allSavedAccounts(Amber.instance).isEmpty()) {
|
||||
Amber.instance.settings = Amber.instance.settings.copy(
|
||||
useProxy = useProxy,
|
||||
torMode = if (useProxy) TorMode.ORBOT else TorMode.DISABLED,
|
||||
proxyPort = proxyPort,
|
||||
)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(
|
||||
|
||||
@@ -58,10 +58,13 @@ import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.BuildFlavorChecker
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.okhttp.HttpClientManager
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
|
||||
import com.greenart7c3.nostrsigner.models.IntentData
|
||||
import com.greenart7c3.nostrsigner.models.IntentResultType
|
||||
import com.greenart7c3.nostrsigner.models.TorMode
|
||||
import com.greenart7c3.nostrsigner.service.TorManager
|
||||
import com.greenart7c3.nostrsigner.service.crashreports.DisplayCrashMessages
|
||||
import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen
|
||||
import com.greenart7c3.nostrsigner.ui.actions.AccountsBottomSheet
|
||||
@@ -848,6 +851,9 @@ fun MainScreen(
|
||||
.padding(top = verticalPadding * 1.5f),
|
||||
onPost = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
if (Amber.instance.settings.torMode == TorMode.BUILTIN) {
|
||||
TorManager.stop()
|
||||
}
|
||||
LocalPreferences.updateProxy(context, true, it)
|
||||
Amber.instance.checkForNewRelaysAndUpdateAllFilters()
|
||||
scope.launch {
|
||||
@@ -857,6 +863,21 @@ fun MainScreen(
|
||||
}
|
||||
}
|
||||
},
|
||||
onBuiltinTor = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
if (Amber.instance.settings.torMode == TorMode.ORBOT) {
|
||||
HttpClientManager.clearProxy()
|
||||
}
|
||||
LocalPreferences.updateTorMode(context, TorMode.BUILTIN)
|
||||
TorManager.start(context, Amber.instance.applicationIOScope)
|
||||
Amber.instance.checkForNewRelaysAndUpdateAllFilters()
|
||||
scope.launch {
|
||||
navController.navigate(Route.Settings.route) {
|
||||
popUpTo(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onError = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
|
||||
@@ -55,6 +55,7 @@ import com.greenart7c3.nostrsigner.BuildFlavorChecker
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.TorMode
|
||||
import com.greenart7c3.nostrsigner.ui.actions.LogoutDialog
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.IconRow
|
||||
@@ -78,7 +79,7 @@ fun SettingsScreen(
|
||||
) {
|
||||
var logoutDialog by remember { mutableStateOf(false) }
|
||||
val context = LocalContext.current
|
||||
var checked by remember { mutableStateOf(Amber.instance.settings.useProxy) }
|
||||
var torMode by remember { mutableStateOf(Amber.instance.settings.torMode) }
|
||||
var disconnectTorDialog by remember { mutableStateOf(false) }
|
||||
val proxyPort = remember { mutableStateOf(Amber.instance.settings.proxyPort.toString()) }
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -178,10 +179,10 @@ fun SettingsScreen(
|
||||
.padding(vertical = 8.dp),
|
||||
) {
|
||||
IconRow(
|
||||
title = if (checked) {
|
||||
stringResource(R.string.disconnect_from_your_orbot_setup)
|
||||
} else {
|
||||
stringResource(R.string.connect_via_tor_short)
|
||||
title = when (torMode) {
|
||||
TorMode.BUILTIN -> stringResource(R.string.disconnect_builtin_tor)
|
||||
TorMode.ORBOT -> stringResource(R.string.disconnect_from_your_orbot_setup)
|
||||
TorMode.DISABLED -> stringResource(R.string.connect_via_tor_short)
|
||||
},
|
||||
icon = R.drawable.ic_tor,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
@@ -189,7 +190,7 @@ fun SettingsScreen(
|
||||
navController.navigate(Route.TorSettings.route)
|
||||
},
|
||||
onClick = {
|
||||
if (checked) {
|
||||
if (torMode != TorMode.DISABLED) {
|
||||
disconnectTorDialog = true
|
||||
} else {
|
||||
navController.navigate(Route.TorSettings.route)
|
||||
@@ -369,9 +370,12 @@ fun SettingsScreen(
|
||||
TextButton(
|
||||
onClick = {
|
||||
disconnectTorDialog = false
|
||||
checked = false
|
||||
torMode = TorMode.DISABLED
|
||||
scope.launch(Dispatchers.IO) {
|
||||
LocalPreferences.updateProxy(context, false, proxyPort.value.toInt())
|
||||
if (Amber.instance.settings.torMode == TorMode.BUILTIN) {
|
||||
com.greenart7c3.nostrsigner.service.TorManager.stop()
|
||||
}
|
||||
LocalPreferences.updateTorMode(context, TorMode.DISABLED)
|
||||
Amber.instance.checkForNewRelaysAndUpdateAllFilters()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
@@ -39,12 +40,14 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.TorMode
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.greenart7c3.nostrsigner.ui.theme.RichTextDefaults
|
||||
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
|
||||
@@ -57,6 +60,7 @@ import kotlinx.coroutines.CancellationException
|
||||
fun ConnectOrbotScreen(
|
||||
modifier: Modifier,
|
||||
onPost: (Int) -> Unit,
|
||||
onBuiltinTor: () -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
) {
|
||||
val portNumber = remember { mutableStateOf(Amber.instance.settings.proxyPort.toString()) }
|
||||
@@ -64,6 +68,46 @@ fun ConnectOrbotScreen(
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.builtin_tor_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.builtin_tor_description),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
AmberButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onBuiltinTor,
|
||||
text = if (Amber.instance.settings.torMode == TorMode.BUILTIN) {
|
||||
stringResource(R.string.builtin_tor_active)
|
||||
} else {
|
||||
stringResource(R.string.use_builtin_tor)
|
||||
},
|
||||
enabled = Amber.instance.settings.torMode != TorMode.BUILTIN,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.orbot_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
val myMarkDownStyle =
|
||||
RichTextDefaults.copy(
|
||||
stringStyle = RichTextDefaults.stringStyle?.copy(
|
||||
|
||||
@@ -109,9 +109,15 @@
|
||||
<string name="invalid_port_number">Invalid port number</string>
|
||||
<string name="use_orbot">Use Orbot</string>
|
||||
<string name="disconnect_from_your_orbot_setup">Disconnect Tor/Orbot</string>
|
||||
<string name="connect_via_tor_short">Tor/Orbot setup</string>
|
||||
<string name="do_you_really_want_to_disable_tor_title">Disconnect from your Orbot/Tor?</string>
|
||||
<string name="connect_via_tor_short">Tor setup</string>
|
||||
<string name="do_you_really_want_to_disable_tor_title">Disconnect from Tor?</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">Your data will be immediately transferred in the regular network</string>
|
||||
<string name="builtin_tor_title">Built-in Tor</string>
|
||||
<string name="builtin_tor_description">Run Tor directly inside the app. No external app required.</string>
|
||||
<string name="use_builtin_tor">Use Built-in Tor</string>
|
||||
<string name="builtin_tor_active">Built-in Tor Active</string>
|
||||
<string name="disconnect_builtin_tor">Disconnect Built-in Tor</string>
|
||||
<string name="orbot_title">Orbot (External App)</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="could_not_connect_to_tor">Could not connect to Tor</string>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
object TorManager {
|
||||
private val _socksPort = MutableStateFlow(0)
|
||||
val socksPort: StateFlow<Int> = _socksPort.asStateFlow()
|
||||
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
val isRunning: StateFlow<Boolean> = _isRunning.asStateFlow()
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun start(context: Context, scope: CoroutineScope) {
|
||||
// No-op in offline flavor
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
// No-op in offline flavor
|
||||
}
|
||||
}
|
||||
@@ -23,3 +23,4 @@ kotlin.code.style=official
|
||||
android.nonTransitiveRClass=true
|
||||
android.enableR8.fullMode=true
|
||||
org.gradle.configuration-cache=true
|
||||
android.bundle.enableUncompressedNativeLibs=false
|
||||
|
||||
@@ -31,6 +31,8 @@ collections = "0.4.0"
|
||||
pagingCommon = "3.4.1"
|
||||
mockk = "1.14.9"
|
||||
coroutinesTest = "1.10.2"
|
||||
kmpTor = "2.6.0"
|
||||
kmpTorResource = "408.19.0"
|
||||
|
||||
[libraries]
|
||||
datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||
@@ -81,6 +83,8 @@ paging-compose = { group = "androidx.paging", name = "paging-compose", version.r
|
||||
paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "pagingCommon" }
|
||||
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
|
||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutinesTest" }
|
||||
kmptor-runtime = { module = "io.matthewnelson.kmp-tor:runtime", version.ref = "kmpTor" }
|
||||
kmptor-resource-exec = { module = "io.matthewnelson.kmp-tor:resource-exec-tor", version.ref = "kmpTorResource" }
|
||||
|
||||
[plugins]
|
||||
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
||||
|
||||
Reference in New Issue
Block a user