Merge pull request #357 from greenart7c3/main

Add option to setup connection through Orbot
This commit is contained in:
Vitor Pamplona
2023-05-11 08:33:33 -04:00
committed by GitHub
19 changed files with 363 additions and 33 deletions
+3 -1
View File
@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<queries>
<package android:name="org.torproject.android"/>
</queries>
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-permission android:name="android.permission.INTERNET"/>
@@ -10,6 +10,7 @@ import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
import com.vitorpamplona.amethyst.model.RelaySetupInfo
import com.vitorpamplona.amethyst.model.hexToByteArray
import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.service.model.ContactListEvent
import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent
@@ -55,6 +56,8 @@ private object PrefKeys {
const val LATEST_CONTACT_LIST = "latestContactList"
const val HIDE_DELETE_REQUEST_DIALOG = "hide_delete_request_dialog"
const val HIDE_BLOCK_ALERT_DIALOG = "hide_block_alert_dialog"
const val USE_PROXY = "use_proxy"
const val PROXY_PORT = "proxy_port"
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
}
@@ -209,6 +212,8 @@ object LocalPreferences {
putString(PrefKeys.LATEST_CONTACT_LIST, Event.gson.toJson(account.backupContactList))
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, account.hideDeleteRequestDialog)
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog)
putBoolean(PrefKeys.USE_PROXY, account.proxy != null)
putInt(PrefKeys.PROXY_PORT, account.proxyPort)
}.apply()
}
@@ -283,6 +288,9 @@ object LocalPreferences {
val hideDeleteRequestDialog = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, false)
val hideBlockAlertDialog = getBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, false)
val useProxy = getBoolean(PrefKeys.USE_PROXY, false)
val proxyPort = getInt(PrefKeys.PROXY_PORT, 9050)
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
val a = Account(
Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
@@ -301,7 +309,9 @@ object LocalPreferences {
zapPaymentRequestServer,
hideDeleteRequestDialog,
hideBlockAlertDialog,
latestContactList
latestContactList,
proxy,
proxyPort
)
return a
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
@@ -26,7 +27,7 @@ object ServiceManager {
fun start() {
val myAccount = account
HttpClient.start(account)
if (myAccount != null) {
Client.connect(myAccount.activeRelays() ?: myAccount.convertLocalRelays())
@@ -22,6 +22,7 @@ import kotlinx.coroutines.launch
import nostr.postr.Persona
import nostr.postr.Utils
import java.math.BigDecimal
import java.net.Proxy
import java.util.Locale
val DefaultChannels = setOf(
@@ -59,7 +60,9 @@ class Account(
var zapPaymentRequest: Nip47URI? = null,
var hideDeleteRequestDialog: Boolean = false,
var hideBlockAlertDialog: Boolean = false,
var backupContactList: ContactListEvent? = null
var backupContactList: ContactListEvent? = null,
var proxy: Proxy?,
var proxyPort: Int
) {
var transientHiddenUsers: Set<String> = setOf()
@@ -936,7 +939,7 @@ class Account(
fun activeRelays(): Array<Relay>? {
var usersRelayList = userProfile().latestContactList?.relays()?.map {
val localFeedTypes = localRelays.firstOrNull() { localRelay -> localRelay.url == it.key }?.feedTypes ?: FeedType.values().toSet()
Relay(it.key, it.value.read, it.value.write, localFeedTypes)
Relay(it.key, it.value.read, it.value.write, localFeedTypes, proxy)
} ?: return null
// Ugly, but forces nostr.band as the only search-supporting relay today.
@@ -946,7 +949,8 @@ class Account(
Constants.forcedRelayForSearch.url,
Constants.forcedRelayForSearch.read,
Constants.forcedRelayForSearch.write,
Constants.forcedRelayForSearch.feedTypes
Constants.forcedRelayForSearch.feedTypes,
proxy
)
}
@@ -955,7 +959,7 @@ class Account(
fun convertLocalRelays(): Array<Relay> {
return localRelays.map {
Relay(it.url, it.read, it.write, it.feedTypes)
Relay(it.url, it.read, it.write, it.feedTypes, proxy)
}.toTypedArray()
}
@@ -0,0 +1,34 @@
package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.Account
import okhttp3.OkHttpClient
import java.net.InetSocketAddress
import java.net.Proxy
import java.time.Duration
object HttpClient {
private var proxy: Proxy? = null
fun start(account: Account?) {
this.proxy = account?.proxy
}
fun getHttpClient(): OkHttpClient {
val seconds = if (proxy != null) 20L else 10L
val duration = Duration.ofSeconds(seconds)
return OkHttpClient.Builder()
.proxy(proxy)
.readTimeout(duration)
.connectTimeout(duration)
.writeTimeout(duration)
.build()
}
fun getProxy(): Proxy? {
return proxy
}
fun initProxy(useProxy: Boolean, hostname: String, port: Int): Proxy? {
return if (useProxy) Proxy(Proxy.Type.SOCKS, InetSocketAddress(hostname, port)) else null
}
}
@@ -9,12 +9,11 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
class Nip05Verifier {
val client = OkHttpClient.Builder().build()
class Nip05Verifier() {
val client = HttpClient.getHttpClient()
fun assembleUrl(nip05address: String): String? {
val parts = nip05address.trim().split("@")
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.service.lnurl
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.service.HttpClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -10,14 +11,13 @@ import kotlinx.coroutines.withContext
import nostr.postr.Bech32
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.math.BigDecimal
import java.net.URLEncoder
class LightningAddressResolver {
val client = OkHttpClient.Builder().build()
class LightningAddressResolver() {
val client = HttpClient.getHttpClient()
fun assembleUrl(lnaddress: String): String? {
val parts = lnaddress.split("@")
@@ -4,13 +4,13 @@ import android.util.Log
import com.vitorpamplona.amethyst.AccountInfo
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.service.model.RelayAuthEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
@@ -54,7 +54,7 @@ class RegisterAccounts(
.post(body)
.build()
val client = OkHttpClient.Builder().build()
val client = HttpClient.getHttpClient()
client.newCall(request).execute()
} catch (e: java.lang.Exception) {
@@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.service.relays
import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.EventInterface
import kotlinx.coroutines.DelicateCoroutinesApi
@@ -91,7 +92,7 @@ object Client : RelayPool.Listener {
@OptIn(DelicateCoroutinesApi::class)
fun newSporadicRelay(url: String, feedTypes: Set<FeedType>?, onConnected: (Relay) -> Unit, onDone: (() -> Unit)?) {
val relay = Relay(url, true, true, feedTypes ?: emptySet())
val relay = Relay(url, true, true, feedTypes ?: emptySet(), HttpClient.getProxy())
RelayPool.addRelay(relay)
relay.requestAndWatch {
@@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.service.relays
import com.vitorpamplona.amethyst.model.RelaySetupInfo
import com.vitorpamplona.amethyst.service.HttpClient
object Constants {
val activeTypes = setOf(FeedType.FOLLOWS, FeedType.PRIVATE_DMS)
@@ -10,7 +11,7 @@ object Constants {
fun convertDefaultRelays(): Array<Relay> {
return defaultRelays.map {
Relay(it.url, it.read, it.write, it.feedTypes)
Relay(it.url, it.read, it.write, it.feedTypes, HttpClient.getProxy())
}.toTypedArray()
}
@@ -11,6 +11,8 @@ import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import java.net.Proxy
import java.time.Duration
import java.util.Date
enum class FeedType {
@@ -23,9 +25,16 @@ class Relay(
var url: String,
var read: Boolean = true,
var write: Boolean = true,
var activeTypes: Set<FeedType> = FeedType.values().toSet()
var activeTypes: Set<FeedType> = FeedType.values().toSet(),
proxy: Proxy?
) {
val seconds = if (proxy != null) 20L else 10L
val duration = Duration.ofSeconds(seconds)
private val httpClient = OkHttpClient.Builder()
.proxy(proxy)
.readTimeout(duration)
.connectTimeout(duration)
.writeTimeout(duration)
.followRedirects(true)
.followSslRedirects(true)
.build()
@@ -10,6 +10,7 @@ import android.provider.MediaStore
import android.webkit.MimeTypeMap
import androidx.annotation.RequiresApi
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.service.HttpClient
import okhttp3.*
import okio.BufferedSource
import okio.IOException
@@ -32,7 +33,7 @@ object ImageSaver {
onSuccess: () -> Any?,
onError: (Throwable) -> Any?
) {
val client = OkHttpClient.Builder().build()
val client = HttpClient.getHttpClient()
val request = Request.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
@@ -6,6 +6,7 @@ import android.provider.OpenableColumns
import android.webkit.MimeTypeMap
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.service.HttpClient
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okio.BufferedSink
@@ -70,7 +71,7 @@ object ImageUploader {
val fileName = randomChars()
val extension = contentType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: ""
val client = OkHttpClient.Builder().build()
val client = HttpClient.getHttpClient()
val requestBody: RequestBody
val requestBuilder = Request.Builder()
@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.AlertDialog
import androidx.compose.material.Divider
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
@@ -24,7 +25,9 @@ import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.ScaffoldState
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
@@ -45,13 +48,17 @@ import androidx.navigation.NavController
import androidx.navigation.NavHostController
import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ServiceManager
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.ui.components.ResizeImage
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@@ -100,7 +107,12 @@ fun DrawerContent(
}
@Composable
fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffoldState: ScaffoldState, navController: NavController) {
fun ProfileContent(
baseAccountUser: User,
modifier: Modifier = Modifier,
scaffoldState: ScaffoldState,
navController: NavController
) {
val coroutineScope = rememberCoroutineScope()
val accountUserState by baseAccountUser.live().metadata.observeAsState()
@@ -197,11 +209,17 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
})
) {
Row() {
Text("${accountUserFollows.cachedFollowCount() ?: "--"}", fontWeight = FontWeight.Bold)
Text(
"${accountUserFollows.cachedFollowCount() ?: "--"}",
fontWeight = FontWeight.Bold
)
Text(stringResource(R.string.following))
}
Row(modifier = Modifier.padding(start = 10.dp)) {
Text("${accountUserFollows.cachedFollowerCount() ?: "--"}", fontWeight = FontWeight.Bold)
Text(
"${accountUserFollows.cachedFollowerCount() ?: "--"}",
fontWeight = FontWeight.Bold
)
Text(stringResource(R.string.followers))
}
}
@@ -221,6 +239,10 @@ fun ListContent(
) {
val coroutineScope = rememberCoroutineScope()
var backupDialogOpen by remember { mutableStateOf(false) }
var checked by remember { mutableStateOf(account.proxy != null) }
val openDialog = remember { mutableStateOf(false) }
var conectOrbotDialogOpen by remember { mutableStateOf(false) }
var proxyPort = remember { mutableStateOf(account.proxyPort.toString()) }
Column(modifier = modifier.fillMaxHeight()) {
if (accountUser != null) {
@@ -259,6 +281,21 @@ fun ListContent(
onClick = { backupDialogOpen = true }
)
val textTorProxy = if (checked) stringResource(R.string.disconnect_from_your_orbot_setup) else stringResource(R.string.connect_via_tor)
IconRow(
title = textTorProxy,
icon = R.drawable.ic_tor,
tint = MaterialTheme.colors.onBackground,
onClick = {
if (checked) {
openDialog.value = true
} else {
conectOrbotDialogOpen = true
}
}
)
Spacer(modifier = Modifier.weight(1f))
IconRow(
@@ -272,6 +309,58 @@ fun ListContent(
if (backupDialogOpen) {
AccountBackupDialog(account, onClose = { backupDialogOpen = false })
}
if (conectOrbotDialogOpen) {
ConnectOrbotDialog(
onClose = { conectOrbotDialogOpen = false },
onPost = {
conectOrbotDialogOpen = false
openDialog.value = false
checked = true
enableTor(account, true, proxyPort)
},
proxyPort
)
}
if (openDialog.value) {
AlertDialog(
text = { Text(text = stringResource(R.string.do_you_really_want_to_disable_tor)) },
onDismissRequest = { },
confirmButton = {
TextButton(
onClick = {
openDialog.value = false
checked = false
enableTor(account, false, proxyPort)
}
) {
Text(text = stringResource(R.string.yes))
}
},
dismissButton = {
TextButton(
onClick = {
openDialog.value = false
}
) {
Text(text = stringResource(R.string.no))
}
}
)
}
}
private fun enableTor(
account: Account,
checked: Boolean,
portNumber: MutableState<String>
) {
account.proxyPort = portNumber.value.toInt()
account.proxy = HttpClient.initProxy(checked, "127.0.0.1", account.proxyPort)
LocalPreferences.saveToEncryptedStorage(account)
ServiceManager.pause()
ServiceManager.start()
}
@Composable
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.ServiceManager
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.hexToByteArray
import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.service.nip19.Nip19
import fr.acinq.secp256k1.Hex
import kotlinx.coroutines.CoroutineScope
@@ -39,21 +40,22 @@ class AccountStateViewModel() : ViewModel() {
}
}
fun startUI(key: String) {
fun startUI(key: String, useProxy: Boolean, proxyPort: Int) {
val pattern = Pattern.compile(".+@.+\\.[a-z]+")
val parsed = Nip19.uriToRoute(key)
val pubKeyParsed = parsed?.hex?.hexToByteArray()
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
val account =
if (key.startsWith("nsec")) {
Account(Persona(privKey = key.bechToBytes()))
Account(Persona(privKey = key.bechToBytes()), proxy = proxy, proxyPort = proxyPort)
} else if (pubKeyParsed != null) {
Account(Persona(pubKey = pubKeyParsed))
Account(Persona(pubKey = pubKeyParsed), proxy = proxy, proxyPort = proxyPort)
} else if (pattern.matcher(key).matches()) {
// Evaluate NIP-5
Account(Persona())
Account(Persona(), proxy = proxy, proxyPort = proxyPort)
} else {
Account(Persona(Hex.decode(key)))
Account(Persona(Hex.decode(key)), proxy = proxy, proxyPort = proxyPort)
}
LocalPreferences.updatePrefsForLogin(account)
@@ -66,8 +68,9 @@ class AccountStateViewModel() : ViewModel() {
tryLoginExistingAccount()
}
fun newKey() {
val account = Account(Persona())
fun newKey(useProxy: Boolean, proxyPort: Int) {
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
val account = Account(Persona(), proxy = proxy, proxyPort = proxyPort)
// saves to local preferences
LocalPreferences.updatePrefsForLogin(account)
startUI(account)
@@ -0,0 +1,120 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
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.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material.MaterialRichText
import com.halilibo.richtext.ui.resolveDefaults
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.CloseButton
import com.vitorpamplona.amethyst.ui.actions.PostButton
import kotlinx.coroutines.launch
@Composable
fun ConnectOrbotDialog(onClose: () -> Unit, onPost: () -> Unit, portNumber: MutableState<String>) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
Dialog(
onDismissRequest = onClose,
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Surface(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.padding(10.dp)
.background(MaterialTheme.colors.background)
.fillMaxSize()
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
CloseButton(onCancel = {
onClose()
})
val toastMessage = stringResource(R.string.invalid_port_number)
PostButton(
onPost = {
try {
Integer.parseInt(portNumber.value)
} catch (_: Exception) {
scope.launch {
Toast.makeText(
context,
toastMessage,
Toast.LENGTH_LONG
).show()
}
return@PostButton
}
onPost()
},
isActive = true
)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 30.dp)
) {
MaterialRichText(
style = RichTextStyle().resolveDefaults()
) {
Markdown(
content = stringResource(R.string.connect_through_your_orbot_setup_markdown)
)
}
Spacer(modifier = Modifier.height(10.dp))
OutlinedTextField(
value = portNumber.value,
onValueChange = { portNumber.value = it },
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.None,
keyboardType = KeyboardType.Number
),
label = { Text(text = stringResource(R.string.orbot_socks_port)) },
placeholder = {
Text(
text = "9050",
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
}
)
}
}
}
}
}
@@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.screen.loggedOff
import android.content.Context
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
@@ -38,6 +39,7 @@ import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
import java.util.*
@OptIn(ExperimentalComposeUiApi::class)
@@ -55,6 +57,9 @@ fun LoginPage(
var dialogOpen by remember {
mutableStateOf(false)
}
val useProxy = remember { mutableStateOf(false) }
val proxyPort = remember { mutableStateOf("9050") }
var connectOrbotDialogOpen by remember { mutableStateOf(false) }
Column(
modifier = Modifier
@@ -158,7 +163,7 @@ fun LoginPage(
keyboardActions = KeyboardActions(
onGo = {
try {
accountViewModel.startUI(key.value.text)
accountViewModel.startUI(key.value.text, useProxy.value, proxyPort.value.toInt())
} catch (e: Exception) {
errorMessage = context.getString(R.string.invalid_key)
}
@@ -221,6 +226,32 @@ fun LoginPage(
}
}
if (isPackageInstalled(context, "org.torproject.android")) {
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = useProxy.value,
onCheckedChange = {
if (it) {
connectOrbotDialogOpen = true
}
}
)
Text(stringResource(R.string.connect_via_tor))
}
if (connectOrbotDialogOpen) {
ConnectOrbotDialog(
onClose = { connectOrbotDialogOpen = false },
onPost = {
connectOrbotDialogOpen = false
useProxy.value = true
},
proxyPort
)
}
}
Spacer(modifier = Modifier.height(20.dp))
Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
@@ -237,7 +268,7 @@ fun LoginPage(
if (acceptedTerms.value && key.value.text.isNotBlank()) {
try {
accountViewModel.startUI(key.value.text)
accountViewModel.startUI(key.value.text, useProxy.value, proxyPort.value.toInt())
} catch (e: Exception) {
errorMessage = context.getString(R.string.invalid_key)
}
@@ -265,7 +296,7 @@ fun LoginPage(
.fillMaxWidth(),
onClick = {
if (acceptedTerms.value) {
accountViewModel.newKey()
accountViewModel.newKey(useProxy.value, proxyPort.value.toInt())
} else {
termsAcceptanceIsRequired =
context.getString(R.string.acceptance_of_terms_is_required)
@@ -280,3 +311,7 @@ fun LoginPage(
)
}
}
fun isPackageInstalled(context: Context, target: String): Boolean {
return context.packageManager.getInstalledApplications(0).find { info -> info.packageName == target } != null
}
+4
View File
@@ -0,0 +1,4 @@
<vector android:height="512dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="512dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M17.578,12.201c-0.76,-0.692 -1.721,-1.251 -2.704,-1.81 -0.446,-0.246 -1.81,-1.318 -1.34,-2.838l-0.851,-0.358c1.342,-2.078 3.085,-4.134 5.229,-6.056 -1.721,0.581 -3.24,1.476 -4.379,3.062 0.67,-1.407 1.765,-2.793 2.972,-4.201 -1.654,1.185 -3.084,2.525 -3.979,4.313l0.627,-2.503c-0.894,1.608 -1.52,3.24 -1.766,4.871l-1.317,-0.535 -0.223,0.178c1.162,2.078 0.559,3.174 -0.022,3.553 -1.162,0.783 -2.838,1.788 -3.688,2.659 -1.609,1.654 -2.078,3.218 -1.921,5.296 0.157,2.66 2.101,4.873 4.67,5.744 1.14,0.38 2.19,0.424 3.352,0.424 1.877,0 3.799,-0.491 5.207,-1.676a6.551,6.551 0,0 0,2.369 -5.027,6.875 6.875,0 0,0 -2.236,-5.096zM14.025,21.073c-0.09,0.402 -0.38,0.894 -0.737,1.341 0.134,-0.246 0.246,-0.492 0.313,-0.76 0.559,-1.989 0.805,-2.904 0.537,-5.095 -0.045,-0.224 -0.135,-0.938 -0.471,-1.721 -0.468,-1.185 -1.184,-2.303 -1.272,-2.548 -0.157,-0.38 -0.38,-1.989 -0.403,-3.084 0.023,0.938 0.089,2.659 0.335,3.329 0.067,0.225 0.715,1.229 1.185,2.459 0.312,0.849 0.38,1.632 0.446,1.854 0.224,1.007 -0.045,2.705 -0.401,4.313 -0.111,0.581 -0.426,1.252 -0.828,1.766 0.225,-0.313 0.402,-0.715 0.537,-1.185 0.269,-0.938 0.38,-2.145 0.356,-2.905 -0.021,-0.446 -0.222,-1.407 -0.558,-2.278 -0.201,-0.47 -0.492,-0.961 -0.692,-1.297 -0.224,-0.335 -0.224,-1.072 -0.313,-1.921 0.021,0.916 -0.068,1.385 0.156,2.033 0.134,0.379 0.625,0.916 0.759,1.43 0.201,0.693 0.402,1.453 0.381,1.922 0,0.536 -0.022,1.52 -0.269,2.593 -0.157,0.804 -0.515,1.497 -1.095,1.943 0.246,-0.312 0.38,-0.625 0.447,-0.938 0.089,-0.469 0.111,-0.916 0.156,-1.475a5.96,5.96 0,0 0,-0.111 -1.721c-0.179,-0.805 -0.469,-1.608 -0.604,-2.168 0.022,0.626 0.269,1.408 0.381,2.235 0.089,0.604 0.044,1.206 0.021,1.742 -0.021,0.627 -0.223,1.722 -0.492,2.258 -0.268,-0.112 -0.357,-0.269 -0.537,-0.491 -0.223,-0.291 -0.357,-0.604 -0.491,-0.962a5.043,5.043 0,0 1,-0.291 -0.915,3.071 3.071,0 0,1 0.559,-2.213c0.469,-0.671 0.559,-0.716 0.715,-1.497 -0.223,0.692 -0.379,0.759 -0.871,1.341 -0.559,0.647 -0.648,1.586 -0.648,2.346 0,0.313 0.134,0.671 0.246,1.007 0.134,0.356 0.268,0.714 0.447,0.982 0.134,0.223 0.313,0.379 0.469,0.491 -0.581,-0.156 -1.184,-0.379 -1.564,-0.692 -0.938,-0.805 -1.765,-2.167 -1.877,-3.375 -0.089,-0.982 0.804,-2.413 2.078,-3.128 1.073,-0.626 1.318,-1.319 1.542,-2.459 -0.313,0.983 -0.626,1.833 -1.654,2.348 -1.475,0.804 -2.235,2.1 -2.167,3.352 0.112,1.586 0.737,2.682 2.011,3.554 0.291,0.2 0.693,0.401 1.118,0.559 -1.587,-0.381 -1.788,-0.604 -2.324,-1.229 0,-0.045 -0.134,-0.135 -0.134,-0.156 -0.715,-0.805 -1.609,-2.19 -1.922,-3.464 -0.112,-0.447 -0.224,-0.916 -0.089,-1.363 0.581,-2.101 1.854,-2.905 3.128,-3.775 0.313,-0.225 0.626,-0.426 0.916,-0.649 0.715,-0.559 0.894,-2.012 1.05,-2.838 -0.29,1.006 -0.603,2.258 -1.162,2.659 -0.29,0.224 -0.648,0.402 -0.938,0.604 -1.318,0.894 -2.637,1.743 -3.24,3.91 -0.134,0.56 -0.044,0.962 0.089,1.498 0.335,1.317 1.229,2.748 1.989,3.597l0.134,0.135c0.335,0.381 0.76,0.67 1.274,0.871a5.945,5.945 0,0 1,-1.296 -0.469c-2.078,-1.005 -3.463,-3.173 -3.553,-4.939 -0.179,-3.597 1.542,-4.647 3.151,-5.966 0.894,-0.737 2.145,-1.095 2.86,-2.413 0.134,-0.291 0.224,-0.916 0.045,-1.587 -0.067,-0.224 -0.402,-1.028 -0.537,-1.207l1.989,0.872c-0.044,0.938 -0.067,1.698 0.112,2.391 0.2,0.76 1.184,1.854 1.586,3.129 0.783,2.41 0.583,5.561 0.023,8.019z"/>
</vector>
+16
View File
@@ -296,6 +296,11 @@
<string name="poll_author_no_vote">Poll authors can\'t vote in their own polls.</string>
<string name="poll_hashtag" translatable="false">#zappoll</string>
<string name="connect_via_tor">Connect through your Orbot setup</string>
<string name="do_you_really_want_to_disable_tor">Do you really want to disconnect from your Orbot setup?</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="disconnect_from_your_orbot_setup">Disconnect from your Orbot setup</string>
<string name="hash_verification_passed">Image is the same since the post</string>
<string name="hash_verification_failed">Image has changed. The author might not have seen the change</string>
@@ -360,6 +365,17 @@
<string name="follow_list_selection">Follow List</string>
<string name="follow_list_kind3follows">All Follows</string>
<string name="follow_list_global">Global</string>
<string name="connect_through_your_orbot_setup_markdown">
## Connect through your Orbot setup
\n\n1. Install [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)
\n2. Start Orbot
\n3. In Orbot check the Socks port. By default it uses the port 9050
\n4. If necessary change the port
\n5. Configure the Socks port in this screen
\n6. Press the post button to use Orbot as a proxy
</string>
<string name="orbot_socks_port">Orbot Socks Port</string>
<string name="invalid_port_number">Invalid port number</string>
<string name="app_notification_channel_id" translatable="false">DefaultChannelID</string>
<string name="app_notification_private_message" translatable="false">New notification arrived</string>