Merge pull request #357 from greenart7c3/main
Add option to setup connection through Orbot
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
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-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<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.KIND3_FOLLOWS
|
||||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||||
import com.vitorpamplona.amethyst.model.hexToByteArray
|
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.ContactListEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent
|
import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent
|
||||||
@@ -55,6 +56,8 @@ private object PrefKeys {
|
|||||||
const val LATEST_CONTACT_LIST = "latestContactList"
|
const val LATEST_CONTACT_LIST = "latestContactList"
|
||||||
const val HIDE_DELETE_REQUEST_DIALOG = "hide_delete_request_dialog"
|
const val HIDE_DELETE_REQUEST_DIALOG = "hide_delete_request_dialog"
|
||||||
const val HIDE_BLOCK_ALERT_DIALOG = "hide_block_alert_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" }
|
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))
|
putString(PrefKeys.LATEST_CONTACT_LIST, Event.gson.toJson(account.backupContactList))
|
||||||
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, account.hideDeleteRequestDialog)
|
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, account.hideDeleteRequestDialog)
|
||||||
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog)
|
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog)
|
||||||
|
putBoolean(PrefKeys.USE_PROXY, account.proxy != null)
|
||||||
|
putInt(PrefKeys.PROXY_PORT, account.proxyPort)
|
||||||
}.apply()
|
}.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +288,9 @@ object LocalPreferences {
|
|||||||
|
|
||||||
val hideDeleteRequestDialog = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, false)
|
val hideDeleteRequestDialog = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, false)
|
||||||
val hideBlockAlertDialog = getBoolean(PrefKeys.HIDE_BLOCK_ALERT_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(
|
val a = Account(
|
||||||
Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
||||||
@@ -301,7 +309,9 @@ object LocalPreferences {
|
|||||||
zapPaymentRequestServer,
|
zapPaymentRequestServer,
|
||||||
hideDeleteRequestDialog,
|
hideDeleteRequestDialog,
|
||||||
hideBlockAlertDialog,
|
hideBlockAlertDialog,
|
||||||
latestContactList
|
latestContactList,
|
||||||
|
proxy,
|
||||||
|
proxyPort
|
||||||
)
|
)
|
||||||
|
|
||||||
return a
|
return a
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
|
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
||||||
@@ -26,7 +27,7 @@ object ServiceManager {
|
|||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
val myAccount = account
|
val myAccount = account
|
||||||
|
HttpClient.start(account)
|
||||||
if (myAccount != null) {
|
if (myAccount != null) {
|
||||||
Client.connect(myAccount.activeRelays() ?: myAccount.convertLocalRelays())
|
Client.connect(myAccount.activeRelays() ?: myAccount.convertLocalRelays())
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import kotlinx.coroutines.launch
|
|||||||
import nostr.postr.Persona
|
import nostr.postr.Persona
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import java.net.Proxy
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
val DefaultChannels = setOf(
|
val DefaultChannels = setOf(
|
||||||
@@ -59,7 +60,9 @@ class Account(
|
|||||||
var zapPaymentRequest: Nip47URI? = null,
|
var zapPaymentRequest: Nip47URI? = null,
|
||||||
var hideDeleteRequestDialog: Boolean = false,
|
var hideDeleteRequestDialog: Boolean = false,
|
||||||
var hideBlockAlertDialog: 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()
|
var transientHiddenUsers: Set<String> = setOf()
|
||||||
|
|
||||||
@@ -936,7 +939,7 @@ class Account(
|
|||||||
fun activeRelays(): Array<Relay>? {
|
fun activeRelays(): Array<Relay>? {
|
||||||
var usersRelayList = userProfile().latestContactList?.relays()?.map {
|
var usersRelayList = userProfile().latestContactList?.relays()?.map {
|
||||||
val localFeedTypes = localRelays.firstOrNull() { localRelay -> localRelay.url == it.key }?.feedTypes ?: FeedType.values().toSet()
|
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
|
} ?: return null
|
||||||
|
|
||||||
// Ugly, but forces nostr.band as the only search-supporting relay today.
|
// Ugly, but forces nostr.band as the only search-supporting relay today.
|
||||||
@@ -946,7 +949,8 @@ class Account(
|
|||||||
Constants.forcedRelayForSearch.url,
|
Constants.forcedRelayForSearch.url,
|
||||||
Constants.forcedRelayForSearch.read,
|
Constants.forcedRelayForSearch.read,
|
||||||
Constants.forcedRelayForSearch.write,
|
Constants.forcedRelayForSearch.write,
|
||||||
Constants.forcedRelayForSearch.feedTypes
|
Constants.forcedRelayForSearch.feedTypes,
|
||||||
|
proxy
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,7 +959,7 @@ class Account(
|
|||||||
|
|
||||||
fun convertLocalRelays(): Array<Relay> {
|
fun convertLocalRelays(): Array<Relay> {
|
||||||
return localRelays.map {
|
return localRelays.map {
|
||||||
Relay(it.url, it.read, it.write, it.feedTypes)
|
Relay(it.url, it.read, it.write, it.feedTypes, proxy)
|
||||||
}.toTypedArray()
|
}.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 kotlinx.coroutines.withContext
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Callback
|
import okhttp3.Callback
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
|
||||||
class Nip05Verifier {
|
class Nip05Verifier() {
|
||||||
val client = OkHttpClient.Builder().build()
|
val client = HttpClient.getHttpClient()
|
||||||
|
|
||||||
fun assembleUrl(nip05address: String): String? {
|
fun assembleUrl(nip05address: String): String? {
|
||||||
val parts = nip05address.trim().split("@")
|
val parts = nip05address.trim().split("@")
|
||||||
|
|||||||
+3
-3
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.service.lnurl
|
|||||||
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -10,14 +11,13 @@ import kotlinx.coroutines.withContext
|
|||||||
import nostr.postr.Bech32
|
import nostr.postr.Bech32
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Callback
|
import okhttp3.Callback
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
|
||||||
class LightningAddressResolver {
|
class LightningAddressResolver() {
|
||||||
val client = OkHttpClient.Builder().build()
|
val client = HttpClient.getHttpClient()
|
||||||
|
|
||||||
fun assembleUrl(lnaddress: String): String? {
|
fun assembleUrl(lnaddress: String): String? {
|
||||||
val parts = lnaddress.split("@")
|
val parts = lnaddress.split("@")
|
||||||
|
|||||||
+2
-2
@@ -4,13 +4,13 @@ import android.util.Log
|
|||||||
import com.vitorpamplona.amethyst.AccountInfo
|
import com.vitorpamplona.amethyst.AccountInfo
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
import com.vitorpamplona.amethyst.service.model.RelayAuthEvent
|
import com.vitorpamplona.amethyst.service.model.RelayAuthEvent
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class RegisterAccounts(
|
|||||||
.post(body)
|
.post(body)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val client = OkHttpClient.Builder().build()
|
val client = HttpClient.getHttpClient()
|
||||||
|
|
||||||
client.newCall(request).execute()
|
client.newCall(request).execute()
|
||||||
} catch (e: java.lang.Exception) {
|
} catch (e: java.lang.Exception) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.service.relays
|
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.Event
|
||||||
import com.vitorpamplona.amethyst.service.model.EventInterface
|
import com.vitorpamplona.amethyst.service.model.EventInterface
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
@@ -91,7 +92,7 @@ object Client : RelayPool.Listener {
|
|||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun newSporadicRelay(url: String, feedTypes: Set<FeedType>?, onConnected: (Relay) -> Unit, onDone: (() -> Unit)?) {
|
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)
|
RelayPool.addRelay(relay)
|
||||||
|
|
||||||
relay.requestAndWatch {
|
relay.requestAndWatch {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.vitorpamplona.amethyst.service.relays
|
package com.vitorpamplona.amethyst.service.relays
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||||
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
|
|
||||||
object Constants {
|
object Constants {
|
||||||
val activeTypes = setOf(FeedType.FOLLOWS, FeedType.PRIVATE_DMS)
|
val activeTypes = setOf(FeedType.FOLLOWS, FeedType.PRIVATE_DMS)
|
||||||
@@ -10,7 +11,7 @@ object Constants {
|
|||||||
|
|
||||||
fun convertDefaultRelays(): Array<Relay> {
|
fun convertDefaultRelays(): Array<Relay> {
|
||||||
return defaultRelays.map {
|
return defaultRelays.map {
|
||||||
Relay(it.url, it.read, it.write, it.feedTypes)
|
Relay(it.url, it.read, it.write, it.feedTypes, HttpClient.getProxy())
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import okhttp3.Request
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.WebSocket
|
import okhttp3.WebSocket
|
||||||
import okhttp3.WebSocketListener
|
import okhttp3.WebSocketListener
|
||||||
|
import java.net.Proxy
|
||||||
|
import java.time.Duration
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
enum class FeedType {
|
enum class FeedType {
|
||||||
@@ -23,9 +25,16 @@ class Relay(
|
|||||||
var url: String,
|
var url: String,
|
||||||
var read: Boolean = true,
|
var read: Boolean = true,
|
||||||
var write: 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()
|
private val httpClient = OkHttpClient.Builder()
|
||||||
|
.proxy(proxy)
|
||||||
|
.readTimeout(duration)
|
||||||
|
.connectTimeout(duration)
|
||||||
|
.writeTimeout(duration)
|
||||||
.followRedirects(true)
|
.followRedirects(true)
|
||||||
.followSslRedirects(true)
|
.followSslRedirects(true)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.provider.MediaStore
|
|||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okio.BufferedSource
|
import okio.BufferedSource
|
||||||
import okio.IOException
|
import okio.IOException
|
||||||
@@ -32,7 +33,7 @@ object ImageSaver {
|
|||||||
onSuccess: () -> Any?,
|
onSuccess: () -> Any?,
|
||||||
onError: (Throwable) -> Any?
|
onError: (Throwable) -> Any?
|
||||||
) {
|
) {
|
||||||
val client = OkHttpClient.Builder().build()
|
val client = HttpClient.getHttpClient()
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.provider.OpenableColumns
|
|||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okio.BufferedSink
|
import okio.BufferedSink
|
||||||
@@ -70,7 +71,7 @@ object ImageUploader {
|
|||||||
val fileName = randomChars()
|
val fileName = randomChars()
|
||||||
val extension = contentType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: ""
|
val extension = contentType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: ""
|
||||||
|
|
||||||
val client = OkHttpClient.Builder().build()
|
val client = HttpClient.getHttpClient()
|
||||||
val requestBody: RequestBody
|
val requestBody: RequestBody
|
||||||
val requestBuilder = Request.Builder()
|
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.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.AlertDialog
|
||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
@@ -24,7 +25,9 @@ import androidx.compose.material.ModalBottomSheetState
|
|||||||
import androidx.compose.material.ScaffoldState
|
import androidx.compose.material.ScaffoldState
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -45,13 +48,17 @@ import androidx.navigation.NavController
|
|||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ServiceManager
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.User
|
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.ResizeImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@@ -100,7 +107,12 @@ fun DrawerContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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 coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val accountUserState by baseAccountUser.live().metadata.observeAsState()
|
val accountUserState by baseAccountUser.live().metadata.observeAsState()
|
||||||
@@ -197,11 +209,17 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
|
|||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
Row() {
|
Row() {
|
||||||
Text("${accountUserFollows.cachedFollowCount() ?: "--"}", fontWeight = FontWeight.Bold)
|
Text(
|
||||||
|
"${accountUserFollows.cachedFollowCount() ?: "--"}",
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
Text(stringResource(R.string.following))
|
Text(stringResource(R.string.following))
|
||||||
}
|
}
|
||||||
Row(modifier = Modifier.padding(start = 10.dp)) {
|
Row(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
Text("${accountUserFollows.cachedFollowerCount() ?: "--"}", fontWeight = FontWeight.Bold)
|
Text(
|
||||||
|
"${accountUserFollows.cachedFollowerCount() ?: "--"}",
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
Text(stringResource(R.string.followers))
|
Text(stringResource(R.string.followers))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,6 +239,10 @@ fun ListContent(
|
|||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var backupDialogOpen by remember { mutableStateOf(false) }
|
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()) {
|
Column(modifier = modifier.fillMaxHeight()) {
|
||||||
if (accountUser != null) {
|
if (accountUser != null) {
|
||||||
@@ -259,6 +281,21 @@ fun ListContent(
|
|||||||
onClick = { backupDialogOpen = true }
|
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))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
IconRow(
|
IconRow(
|
||||||
@@ -272,6 +309,58 @@ fun ListContent(
|
|||||||
if (backupDialogOpen) {
|
if (backupDialogOpen) {
|
||||||
AccountBackupDialog(account, onClose = { backupDialogOpen = false })
|
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
|
@Composable
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.LocalPreferences
|
|||||||
import com.vitorpamplona.amethyst.ServiceManager
|
import com.vitorpamplona.amethyst.ServiceManager
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.hexToByteArray
|
import com.vitorpamplona.amethyst.model.hexToByteArray
|
||||||
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
||||||
import fr.acinq.secp256k1.Hex
|
import fr.acinq.secp256k1.Hex
|
||||||
import kotlinx.coroutines.CoroutineScope
|
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 pattern = Pattern.compile(".+@.+\\.[a-z]+")
|
||||||
val parsed = Nip19.uriToRoute(key)
|
val parsed = Nip19.uriToRoute(key)
|
||||||
val pubKeyParsed = parsed?.hex?.hexToByteArray()
|
val pubKeyParsed = parsed?.hex?.hexToByteArray()
|
||||||
|
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
|
||||||
|
|
||||||
val account =
|
val account =
|
||||||
if (key.startsWith("nsec")) {
|
if (key.startsWith("nsec")) {
|
||||||
Account(Persona(privKey = key.bechToBytes()))
|
Account(Persona(privKey = key.bechToBytes()), proxy = proxy, proxyPort = proxyPort)
|
||||||
} else if (pubKeyParsed != null) {
|
} else if (pubKeyParsed != null) {
|
||||||
Account(Persona(pubKey = pubKeyParsed))
|
Account(Persona(pubKey = pubKeyParsed), proxy = proxy, proxyPort = proxyPort)
|
||||||
} else if (pattern.matcher(key).matches()) {
|
} else if (pattern.matcher(key).matches()) {
|
||||||
// Evaluate NIP-5
|
// Evaluate NIP-5
|
||||||
Account(Persona())
|
Account(Persona(), proxy = proxy, proxyPort = proxyPort)
|
||||||
} else {
|
} else {
|
||||||
Account(Persona(Hex.decode(key)))
|
Account(Persona(Hex.decode(key)), proxy = proxy, proxyPort = proxyPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalPreferences.updatePrefsForLogin(account)
|
LocalPreferences.updatePrefsForLogin(account)
|
||||||
@@ -66,8 +68,9 @@ class AccountStateViewModel() : ViewModel() {
|
|||||||
tryLoginExistingAccount()
|
tryLoginExistingAccount()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun newKey() {
|
fun newKey(useProxy: Boolean, proxyPort: Int) {
|
||||||
val account = Account(Persona())
|
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
|
||||||
|
val account = Account(Persona(), proxy = proxy, proxyPort = proxyPort)
|
||||||
// saves to local preferences
|
// saves to local preferences
|
||||||
LocalPreferences.updatePrefsForLogin(account)
|
LocalPreferences.updatePrefsForLogin(account)
|
||||||
startUI(account)
|
startUI(account)
|
||||||
|
|||||||
+120
@@ -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
|
package com.vitorpamplona.amethyst.ui.screen.loggedOff
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@@ -38,6 +39,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner
|
import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@@ -55,6 +57,9 @@ fun LoginPage(
|
|||||||
var dialogOpen by remember {
|
var dialogOpen by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
val useProxy = remember { mutableStateOf(false) }
|
||||||
|
val proxyPort = remember { mutableStateOf("9050") }
|
||||||
|
var connectOrbotDialogOpen by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -158,7 +163,7 @@ fun LoginPage(
|
|||||||
keyboardActions = KeyboardActions(
|
keyboardActions = KeyboardActions(
|
||||||
onGo = {
|
onGo = {
|
||||||
try {
|
try {
|
||||||
accountViewModel.startUI(key.value.text)
|
accountViewModel.startUI(key.value.text, useProxy.value, proxyPort.value.toInt())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
errorMessage = context.getString(R.string.invalid_key)
|
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))
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.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()) {
|
if (acceptedTerms.value && key.value.text.isNotBlank()) {
|
||||||
try {
|
try {
|
||||||
accountViewModel.startUI(key.value.text)
|
accountViewModel.startUI(key.value.text, useProxy.value, proxyPort.value.toInt())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
errorMessage = context.getString(R.string.invalid_key)
|
errorMessage = context.getString(R.string.invalid_key)
|
||||||
}
|
}
|
||||||
@@ -265,7 +296,7 @@ fun LoginPage(
|
|||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (acceptedTerms.value) {
|
if (acceptedTerms.value) {
|
||||||
accountViewModel.newKey()
|
accountViewModel.newKey(useProxy.value, proxyPort.value.toInt())
|
||||||
} else {
|
} else {
|
||||||
termsAcceptanceIsRequired =
|
termsAcceptanceIsRequired =
|
||||||
context.getString(R.string.acceptance_of_terms_is_required)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -296,6 +296,11 @@
|
|||||||
<string name="poll_author_no_vote">Poll authors can\'t vote in their own polls.</string>
|
<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="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_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>
|
<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_selection">Follow List</string>
|
||||||
<string name="follow_list_kind3follows">All Follows</string>
|
<string name="follow_list_kind3follows">All Follows</string>
|
||||||
<string name="follow_list_global">Global</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_channel_id" translatable="false">DefaultChannelID</string>
|
||||||
<string name="app_notification_private_message" translatable="false">New notification arrived</string>
|
<string name="app_notification_private_message" translatable="false">New notification arrived</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user