mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-12 13:59:29 +02:00
adds NFC-Based transient accounts.
Refactors the login screen
This commit is contained in:
parent
f086c0fe10
commit
b2c13089ce
@ -72,6 +72,7 @@ data class AccountInfo(
|
||||
val npub: String,
|
||||
val hasPrivKey: Boolean,
|
||||
val loggedInWithExternalSigner: Boolean,
|
||||
val isTransient: Boolean = false,
|
||||
)
|
||||
|
||||
private object PrefKeys {
|
||||
@ -135,16 +136,18 @@ object LocalPreferences {
|
||||
return currentAccount
|
||||
}
|
||||
|
||||
private suspend fun updateCurrentAccount(npub: String?) {
|
||||
if (npub == null) {
|
||||
private suspend fun updateCurrentAccount(info: AccountInfo?) {
|
||||
if (info == null) {
|
||||
currentAccount = null
|
||||
withContext(Dispatchers.IO) {
|
||||
encryptedPreferences().edit().clear().apply()
|
||||
}
|
||||
} else if (currentAccount != npub) {
|
||||
currentAccount = npub
|
||||
withContext(Dispatchers.IO) {
|
||||
encryptedPreferences().edit().apply { putString(PrefKeys.CURRENT_ACCOUNT, npub) }.apply()
|
||||
} else if (currentAccount != info.npub) {
|
||||
currentAccount = info.npub
|
||||
if (!info.isTransient) {
|
||||
withContext(Dispatchers.IO) {
|
||||
encryptedPreferences().edit().apply { putString(PrefKeys.CURRENT_ACCOUNT, info.npub) }.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,8 +195,12 @@ object LocalPreferences {
|
||||
|
||||
encryptedPreferences()
|
||||
.edit()
|
||||
.apply { putString(PrefKeys.ALL_ACCOUNT_INFO, Event.mapper.writeValueAsString(accounts)) }
|
||||
.apply()
|
||||
.apply {
|
||||
putString(
|
||||
PrefKeys.ALL_ACCOUNT_INFO,
|
||||
Event.mapper.writeValueAsString(accounts.filter { !it.isTransient }),
|
||||
)
|
||||
}.apply()
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,12 +219,13 @@ object LocalPreferences {
|
||||
npub,
|
||||
accountSettings.isWriteable(),
|
||||
accountSettings.externalSignerPackageName != null,
|
||||
accountSettings.transientAccount,
|
||||
)
|
||||
updateCurrentAccount(npub)
|
||||
updateCurrentAccount(accInfo)
|
||||
addAccount(accInfo)
|
||||
}
|
||||
|
||||
suspend fun switchToAccount(accountInfo: AccountInfo) = updateCurrentAccount(accountInfo.npub)
|
||||
suspend fun switchToAccount(accountInfo: AccountInfo) = updateCurrentAccount(accountInfo)
|
||||
|
||||
/** Removes the account from the app level shared preferences */
|
||||
private suspend fun removeAccount(accountInfo: AccountInfo) {
|
||||
@ -267,7 +275,7 @@ object LocalPreferences {
|
||||
if (savedAccounts().isEmpty()) {
|
||||
updateCurrentAccount(null)
|
||||
} else if (currentAccount() == accountInfo.npub) {
|
||||
updateCurrentAccount(savedAccounts().elementAt(0).npub)
|
||||
updateCurrentAccount(savedAccounts().elementAt(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -281,145 +289,147 @@ object LocalPreferences {
|
||||
|
||||
suspend fun saveToEncryptedStorage(settings: AccountSettings) {
|
||||
Log.d("LocalPreferences", "Saving to encrypted storage")
|
||||
withContext(Dispatchers.IO) {
|
||||
val prefs = encryptedPreferences(settings.keyPair.pubKey.toNpub())
|
||||
prefs
|
||||
.edit()
|
||||
.apply {
|
||||
putBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, settings.externalSignerPackageName != null)
|
||||
if (settings.externalSignerPackageName != null) {
|
||||
remove(PrefKeys.NOSTR_PRIVKEY)
|
||||
putString(PrefKeys.SIGNER_PACKAGE_NAME, settings.externalSignerPackageName)
|
||||
} else {
|
||||
remove(PrefKeys.SIGNER_PACKAGE_NAME)
|
||||
settings.keyPair.privKey?.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHexKey()) }
|
||||
}
|
||||
settings.keyPair.pubKey.let { putString(PrefKeys.NOSTR_PUBKEY, it.toHexKey()) }
|
||||
putString(PrefKeys.RELAYS, Event.mapper.writeValueAsString(settings.localRelays))
|
||||
putStringSet(PrefKeys.DONT_TRANSLATE_FROM, settings.dontTranslateFrom)
|
||||
putStringSet(PrefKeys.LOCAL_RELAY_SERVERS, settings.localRelayServers)
|
||||
putString(
|
||||
PrefKeys.LANGUAGE_PREFS,
|
||||
Event.mapper.writeValueAsString(settings.languagePreferences),
|
||||
)
|
||||
putString(PrefKeys.TRANSLATE_TO, settings.translateTo)
|
||||
putString(PrefKeys.ZAP_AMOUNTS, Event.mapper.writeValueAsString(settings.zapAmountChoices.value))
|
||||
putString(
|
||||
PrefKeys.REACTION_CHOICES,
|
||||
Event.mapper.writeValueAsString(settings.reactionChoices.value),
|
||||
)
|
||||
putString(PrefKeys.DEFAULT_ZAPTYPE, settings.defaultZapType.value.name)
|
||||
putString(
|
||||
PrefKeys.DEFAULT_FILE_SERVER,
|
||||
Event.mapper.writeValueAsString(settings.defaultFileServer),
|
||||
)
|
||||
putString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, settings.defaultHomeFollowList.value)
|
||||
putString(PrefKeys.DEFAULT_STORIES_FOLLOW_LIST, settings.defaultStoriesFollowList.value)
|
||||
putString(
|
||||
PrefKeys.DEFAULT_NOTIFICATION_FOLLOW_LIST,
|
||||
settings.defaultNotificationFollowList.value,
|
||||
)
|
||||
putString(
|
||||
PrefKeys.DEFAULT_DISCOVERY_FOLLOW_LIST,
|
||||
settings.defaultDiscoveryFollowList.value,
|
||||
)
|
||||
putString(
|
||||
PrefKeys.ZAP_PAYMENT_REQUEST_SERVER,
|
||||
Event.mapper.writeValueAsString(settings.zapPaymentRequest),
|
||||
)
|
||||
if (settings.backupContactList != null) {
|
||||
if (!settings.transientAccount) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val prefs = encryptedPreferences(settings.keyPair.pubKey.toNpub())
|
||||
prefs
|
||||
.edit()
|
||||
.apply {
|
||||
putBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, settings.externalSignerPackageName != null)
|
||||
if (settings.externalSignerPackageName != null) {
|
||||
remove(PrefKeys.NOSTR_PRIVKEY)
|
||||
putString(PrefKeys.SIGNER_PACKAGE_NAME, settings.externalSignerPackageName)
|
||||
} else {
|
||||
remove(PrefKeys.SIGNER_PACKAGE_NAME)
|
||||
settings.keyPair.privKey?.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHexKey()) }
|
||||
}
|
||||
settings.keyPair.pubKey.let { putString(PrefKeys.NOSTR_PUBKEY, it.toHexKey()) }
|
||||
putString(PrefKeys.RELAYS, Event.mapper.writeValueAsString(settings.localRelays))
|
||||
putStringSet(PrefKeys.DONT_TRANSLATE_FROM, settings.dontTranslateFrom)
|
||||
putStringSet(PrefKeys.LOCAL_RELAY_SERVERS, settings.localRelayServers)
|
||||
putString(
|
||||
PrefKeys.LATEST_CONTACT_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupContactList),
|
||||
PrefKeys.LANGUAGE_PREFS,
|
||||
Event.mapper.writeValueAsString(settings.languagePreferences),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_CONTACT_LIST)
|
||||
}
|
||||
|
||||
if (settings.backupUserMetadata != null) {
|
||||
putString(PrefKeys.TRANSLATE_TO, settings.translateTo)
|
||||
putString(PrefKeys.ZAP_AMOUNTS, Event.mapper.writeValueAsString(settings.zapAmountChoices.value))
|
||||
putString(
|
||||
PrefKeys.LATEST_USER_METADATA,
|
||||
Event.mapper.writeValueAsString(settings.backupUserMetadata),
|
||||
PrefKeys.REACTION_CHOICES,
|
||||
Event.mapper.writeValueAsString(settings.reactionChoices.value),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_USER_METADATA)
|
||||
}
|
||||
|
||||
if (settings.backupDMRelayList != null) {
|
||||
putString(PrefKeys.DEFAULT_ZAPTYPE, settings.defaultZapType.value.name)
|
||||
putString(
|
||||
PrefKeys.LATEST_DM_RELAY_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupDMRelayList),
|
||||
PrefKeys.DEFAULT_FILE_SERVER,
|
||||
Event.mapper.writeValueAsString(settings.defaultFileServer),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_DM_RELAY_LIST)
|
||||
}
|
||||
|
||||
if (settings.backupNIP65RelayList != null) {
|
||||
putString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, settings.defaultHomeFollowList.value)
|
||||
putString(PrefKeys.DEFAULT_STORIES_FOLLOW_LIST, settings.defaultStoriesFollowList.value)
|
||||
putString(
|
||||
PrefKeys.LATEST_NIP65_RELAY_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupNIP65RelayList),
|
||||
PrefKeys.DEFAULT_NOTIFICATION_FOLLOW_LIST,
|
||||
settings.defaultNotificationFollowList.value,
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_NIP65_RELAY_LIST)
|
||||
}
|
||||
|
||||
if (settings.backupSearchRelayList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_SEARCH_RELAY_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupSearchRelayList),
|
||||
PrefKeys.DEFAULT_DISCOVERY_FOLLOW_LIST,
|
||||
settings.defaultDiscoveryFollowList.value,
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_SEARCH_RELAY_LIST)
|
||||
}
|
||||
|
||||
if (settings.backupMuteList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_MUTE_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupMuteList),
|
||||
PrefKeys.ZAP_PAYMENT_REQUEST_SERVER,
|
||||
Event.mapper.writeValueAsString(settings.zapPaymentRequest),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_MUTE_LIST)
|
||||
}
|
||||
|
||||
if (settings.backupPrivateHomeRelayList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupPrivateHomeRelayList),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST)
|
||||
}
|
||||
|
||||
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, settings.hideDeleteRequestDialog)
|
||||
putBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, settings.hideNIP17WarningDialog)
|
||||
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, settings.hideBlockAlertDialog)
|
||||
putBoolean(PrefKeys.USE_PROXY, settings.proxy != null)
|
||||
putInt(PrefKeys.PROXY_PORT, settings.proxyPort)
|
||||
putBoolean(PrefKeys.WARN_ABOUT_REPORTS, settings.warnAboutPostsWithReports)
|
||||
putBoolean(PrefKeys.FILTER_SPAM_FROM_STRANGERS, settings.filterSpamFromStrangers)
|
||||
|
||||
val regularMap =
|
||||
settings.lastReadPerRoute.value.mapValues {
|
||||
it.value.value
|
||||
if (settings.backupContactList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_CONTACT_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupContactList),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_CONTACT_LIST)
|
||||
}
|
||||
|
||||
putString(
|
||||
PrefKeys.LAST_READ_PER_ROUTE,
|
||||
Event.mapper.writeValueAsString(regularMap),
|
||||
)
|
||||
putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, settings.hasDonatedInVersion.value)
|
||||
if (settings.backupUserMetadata != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_USER_METADATA,
|
||||
Event.mapper.writeValueAsString(settings.backupUserMetadata),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_USER_METADATA)
|
||||
}
|
||||
|
||||
if (settings.showSensitiveContent.value == null) {
|
||||
remove(PrefKeys.SHOW_SENSITIVE_CONTENT)
|
||||
} else {
|
||||
putBoolean(PrefKeys.SHOW_SENSITIVE_CONTENT, settings.showSensitiveContent.value!!)
|
||||
}
|
||||
if (settings.backupDMRelayList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_DM_RELAY_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupDMRelayList),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_DM_RELAY_LIST)
|
||||
}
|
||||
|
||||
putString(
|
||||
PrefKeys.PENDING_ATTESTATIONS,
|
||||
Event.mapper.writeValueAsString(settings.pendingAttestations.value),
|
||||
)
|
||||
}.apply()
|
||||
if (settings.backupNIP65RelayList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_NIP65_RELAY_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupNIP65RelayList),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_NIP65_RELAY_LIST)
|
||||
}
|
||||
|
||||
if (settings.backupSearchRelayList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_SEARCH_RELAY_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupSearchRelayList),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_SEARCH_RELAY_LIST)
|
||||
}
|
||||
|
||||
if (settings.backupMuteList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_MUTE_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupMuteList),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_MUTE_LIST)
|
||||
}
|
||||
|
||||
if (settings.backupPrivateHomeRelayList != null) {
|
||||
putString(
|
||||
PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST,
|
||||
Event.mapper.writeValueAsString(settings.backupPrivateHomeRelayList),
|
||||
)
|
||||
} else {
|
||||
remove(PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST)
|
||||
}
|
||||
|
||||
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, settings.hideDeleteRequestDialog)
|
||||
putBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, settings.hideNIP17WarningDialog)
|
||||
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, settings.hideBlockAlertDialog)
|
||||
putBoolean(PrefKeys.USE_PROXY, settings.proxy != null)
|
||||
putInt(PrefKeys.PROXY_PORT, settings.proxyPort)
|
||||
putBoolean(PrefKeys.WARN_ABOUT_REPORTS, settings.warnAboutPostsWithReports)
|
||||
putBoolean(PrefKeys.FILTER_SPAM_FROM_STRANGERS, settings.filterSpamFromStrangers)
|
||||
|
||||
val regularMap =
|
||||
settings.lastReadPerRoute.value.mapValues {
|
||||
it.value.value
|
||||
}
|
||||
|
||||
putString(
|
||||
PrefKeys.LAST_READ_PER_ROUTE,
|
||||
Event.mapper.writeValueAsString(regularMap),
|
||||
)
|
||||
putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, settings.hasDonatedInVersion.value)
|
||||
|
||||
if (settings.showSensitiveContent.value == null) {
|
||||
remove(PrefKeys.SHOW_SENSITIVE_CONTENT)
|
||||
} else {
|
||||
putBoolean(PrefKeys.SHOW_SENSITIVE_CONTENT, settings.showSensitiveContent.value!!)
|
||||
}
|
||||
|
||||
putString(
|
||||
PrefKeys.PENDING_ATTESTATIONS,
|
||||
Event.mapper.writeValueAsString(settings.pendingAttestations.value),
|
||||
)
|
||||
}.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -550,6 +560,7 @@ object LocalPreferences {
|
||||
|
||||
return@with AccountSettings(
|
||||
keyPair = keyPair,
|
||||
transientAccount = false,
|
||||
externalSignerPackageName = externalSignerPackageName,
|
||||
localRelays = localRelays,
|
||||
localRelayServers = localRelayServers,
|
||||
|
@ -113,6 +113,7 @@ val KIND3_FOLLOWS = " All Follows "
|
||||
@Stable
|
||||
class AccountSettings(
|
||||
val keyPair: KeyPair,
|
||||
val transientAccount: Boolean = false,
|
||||
var externalSignerPackageName: String? = null,
|
||||
var localRelays: Set<RelaySetupInfo> = Constants.defaultRelays.toSet(),
|
||||
var localRelayServers: Set<String> = setOf(),
|
||||
@ -246,14 +247,17 @@ class AccountSettings(
|
||||
|
||||
fun isProxyEnabled() = proxy != null
|
||||
|
||||
fun updateProxy(
|
||||
enabled: Boolean,
|
||||
portNumber: String,
|
||||
) {
|
||||
val port = portNumber.toIntOrNull() ?: return
|
||||
if (proxyPort != port || isProxyEnabled() != enabled) {
|
||||
proxyPort = portNumber.toInt()
|
||||
proxy = HttpClientManager.initProxy(enabled, "127.0.0.1", proxyPort)
|
||||
fun disableProxy() {
|
||||
if (isProxyEnabled()) {
|
||||
proxy = HttpClientManager.initProxy(false, "127.0.0.1", proxyPort)
|
||||
saveAccountSettings()
|
||||
}
|
||||
}
|
||||
|
||||
fun enableProxy(portNumber: Int) {
|
||||
if (proxyPort != portNumber || !isProxyEnabled()) {
|
||||
proxyPort = portNumber
|
||||
proxy = HttpClientManager.initProxy(true, "127.0.0.1", proxyPort)
|
||||
saveAccountSettings()
|
||||
}
|
||||
}
|
||||
|
@ -445,13 +445,6 @@ fun ListContent(
|
||||
var checked by remember { mutableStateOf(accountViewModel.account.settings.proxy != null) }
|
||||
var disconnectTorDialog by remember { mutableStateOf(false) }
|
||||
var conectOrbotDialogOpen by remember { mutableStateOf(false) }
|
||||
val proxyPort =
|
||||
remember {
|
||||
mutableStateOf(
|
||||
accountViewModel.account.settings.proxyPort
|
||||
.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
@ -575,7 +568,7 @@ fun ListContent(
|
||||
conectOrbotDialogOpen = false
|
||||
disconnectTorDialog = false
|
||||
checked = true
|
||||
accountViewModel.enableTor(true, proxyPort)
|
||||
accountViewModel.enableTor(it)
|
||||
},
|
||||
onError = {
|
||||
accountViewModel.toast(
|
||||
@ -583,7 +576,7 @@ fun ListContent(
|
||||
it,
|
||||
)
|
||||
},
|
||||
proxyPort,
|
||||
currentPortNumber = accountViewModel.account.settings.proxyPort,
|
||||
)
|
||||
}
|
||||
|
||||
@ -597,7 +590,7 @@ fun ListContent(
|
||||
onClick = {
|
||||
disconnectTorDialog = false
|
||||
checked = false
|
||||
accountViewModel.enableTor(false, proxyPort)
|
||||
accountViewModel.disableTor()
|
||||
},
|
||||
) {
|
||||
Text(text = stringRes(R.string.yes))
|
||||
|
@ -94,6 +94,7 @@ class AccountStateViewModel : ViewModel() {
|
||||
key: String,
|
||||
useProxy: Boolean,
|
||||
proxyPort: Int,
|
||||
transientAccount: Boolean,
|
||||
loginWithExternalSigner: Boolean = false,
|
||||
packageName: String = "",
|
||||
) = withContext(Dispatchers.IO) {
|
||||
@ -121,6 +122,7 @@ class AccountStateViewModel : ViewModel() {
|
||||
if (loginWithExternalSigner) {
|
||||
AccountSettings(
|
||||
keyPair = KeyPair(pubKey = pubKeyParsed),
|
||||
transientAccount = transientAccount,
|
||||
externalSignerPackageName = packageName.ifBlank { "com.greenart7c3.nostrsigner" },
|
||||
proxy = proxy,
|
||||
proxyPort = proxyPort,
|
||||
@ -128,24 +130,28 @@ class AccountStateViewModel : ViewModel() {
|
||||
} else if (key.startsWith("nsec")) {
|
||||
AccountSettings(
|
||||
keyPair = KeyPair(privKey = key.bechToBytes()),
|
||||
transientAccount = transientAccount,
|
||||
proxy = proxy,
|
||||
proxyPort = proxyPort,
|
||||
)
|
||||
} else if (key.contains(" ") && CryptoUtils.isValidMnemonic(key)) {
|
||||
AccountSettings(
|
||||
keyPair = KeyPair(privKey = CryptoUtils.privateKeyFromMnemonic(key)),
|
||||
transientAccount = transientAccount,
|
||||
proxy = proxy,
|
||||
proxyPort = proxyPort,
|
||||
)
|
||||
} else if (pubKeyParsed != null) {
|
||||
AccountSettings(
|
||||
keyPair = KeyPair(pubKey = pubKeyParsed),
|
||||
transientAccount = transientAccount,
|
||||
proxy = proxy,
|
||||
proxyPort = proxyPort,
|
||||
)
|
||||
} else {
|
||||
AccountSettings(
|
||||
keyPair = KeyPair(Hex.decode(key)),
|
||||
transientAccount = transientAccount,
|
||||
proxy = proxy,
|
||||
proxyPort = proxyPort,
|
||||
)
|
||||
@ -194,6 +200,7 @@ class AccountStateViewModel : ViewModel() {
|
||||
password: String,
|
||||
useProxy: Boolean,
|
||||
proxyPort: Int,
|
||||
transientAccount: Boolean,
|
||||
loginWithExternalSigner: Boolean = false,
|
||||
packageName: String = "",
|
||||
onError: (String?) -> Unit,
|
||||
@ -213,26 +220,20 @@ class AccountStateViewModel : ViewModel() {
|
||||
onError("Could not decrypt key with provided password")
|
||||
Log.e("Login", "Could not decrypt ncryptsec")
|
||||
} else {
|
||||
loginSync(newKey, useProxy, proxyPort, loginWithExternalSigner, packageName) {
|
||||
onError(null)
|
||||
}
|
||||
loginSync(newKey, useProxy, proxyPort, transientAccount, loginWithExternalSigner, packageName, onError)
|
||||
}
|
||||
} else if (EMAIL_PATTERN.matcher(key).matches()) {
|
||||
Nip05NostrAddressVerifier().verifyNip05(
|
||||
key,
|
||||
onSuccess = { publicKey ->
|
||||
loginSync(Hex.decode(publicKey).toNpub(), useProxy, proxyPort, loginWithExternalSigner, packageName) {
|
||||
onError(null)
|
||||
}
|
||||
loginSync(Hex.decode(publicKey).toNpub(), useProxy, proxyPort, transientAccount, loginWithExternalSigner, packageName, onError)
|
||||
},
|
||||
onError = {
|
||||
onError(it)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
loginSync(key, useProxy, proxyPort, loginWithExternalSigner, packageName) {
|
||||
onError(null)
|
||||
}
|
||||
loginSync(key, useProxy, proxyPort, transientAccount, loginWithExternalSigner, packageName, onError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -241,12 +242,13 @@ class AccountStateViewModel : ViewModel() {
|
||||
key: String,
|
||||
useProxy: Boolean,
|
||||
proxyPort: Int,
|
||||
transientAccount: Boolean,
|
||||
loginWithExternalSigner: Boolean = false,
|
||||
packageName: String = "",
|
||||
onError: () -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
loginSync(key, useProxy, proxyPort, loginWithExternalSigner, packageName, onError)
|
||||
loginSync(key, useProxy, proxyPort, transientAccount, loginWithExternalSigner, packageName, onError)
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,16 +256,17 @@ class AccountStateViewModel : ViewModel() {
|
||||
key: String,
|
||||
useProxy: Boolean,
|
||||
proxyPort: Int,
|
||||
transientAccount: Boolean,
|
||||
loginWithExternalSigner: Boolean = false,
|
||||
packageName: String = "",
|
||||
onError: () -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
) {
|
||||
try {
|
||||
loginAndStartUI(key, useProxy, proxyPort, loginWithExternalSigner, packageName)
|
||||
loginAndStartUI(key, useProxy, proxyPort, transientAccount, loginWithExternalSigner, packageName)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("Login", "Could not sign in", e)
|
||||
onError()
|
||||
onError("Could not sign in: " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,12 +276,15 @@ class AccountStateViewModel : ViewModel() {
|
||||
name: String? = null,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_accountContent.update { AccountState.Loading }
|
||||
|
||||
val keyPair = KeyPair()
|
||||
val tempSigner = NostrSignerSync(keyPair)
|
||||
|
||||
val accountSettings =
|
||||
AccountSettings(
|
||||
keyPair = keyPair,
|
||||
transientAccount = false,
|
||||
backupUserMetadata = MetadataEvent.newUser(name, tempSigner),
|
||||
backupContactList =
|
||||
ContactListEvent.createFromScratch(
|
||||
|
@ -26,7 +26,6 @@ import android.util.Log
|
||||
import android.util.LruCache
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
@ -1222,12 +1221,16 @@ class AccountViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun enableTor(
|
||||
checked: Boolean,
|
||||
portNumber: MutableState<String>,
|
||||
) {
|
||||
fun disableTor() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
account.settings.updateProxy(checked, portNumber.value)
|
||||
account.settings.disableProxy()
|
||||
Amethyst.instance.serviceManager.forceRestart()
|
||||
}
|
||||
}
|
||||
|
||||
fun enableTor(portNumber: Int) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
account.settings.enableProxy(portNumber)
|
||||
Amethyst.instance.serviceManager.forceRestart()
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -62,9 +62,9 @@ import kotlinx.coroutines.CancellationException
|
||||
@Composable
|
||||
fun ConnectOrbotDialog(
|
||||
onClose: () -> Unit,
|
||||
onPost: () -> Unit,
|
||||
onPost: (port: Int) -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
portNumber: MutableState<String>,
|
||||
currentPortNumber: Int?,
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = onClose,
|
||||
@ -74,6 +74,13 @@ fun ConnectOrbotDialog(
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
) {
|
||||
val proxyPort =
|
||||
remember {
|
||||
mutableStateOf(
|
||||
currentPortNumber?.toString() ?: "",
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@ -85,15 +92,16 @@ fun ConnectOrbotDialog(
|
||||
|
||||
UseOrbotButton(
|
||||
onPost = {
|
||||
try {
|
||||
Integer.parseInt(portNumber.value)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
onError(toastMessage)
|
||||
return@UseOrbotButton
|
||||
}
|
||||
val port =
|
||||
try {
|
||||
Integer.parseInt(proxyPort.value)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
onError(toastMessage)
|
||||
return@UseOrbotButton
|
||||
}
|
||||
|
||||
onPost()
|
||||
onPost(port)
|
||||
},
|
||||
isActive = true,
|
||||
)
|
||||
@ -137,8 +145,8 @@ fun ConnectOrbotDialog(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = portNumber.value,
|
||||
onValueChange = { portNumber.value = it },
|
||||
value = proxyPort.value,
|
||||
onValueChange = { proxyPort.value = it },
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
|
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedOff
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableText
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@Composable
|
||||
fun AcceptTerms(
|
||||
checked: Boolean,
|
||||
onCheckedChange: ((Boolean) -> Unit)?,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
)
|
||||
|
||||
val regularText = SpanStyle(color = MaterialTheme.colorScheme.onBackground)
|
||||
val clickableTextStyle = SpanStyle(color = MaterialTheme.colorScheme.primary)
|
||||
|
||||
val annotatedTermsString =
|
||||
buildAnnotatedString {
|
||||
withStyle(regularText) { append(stringRes(R.string.i_accept_the)) }
|
||||
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("openTerms", "")
|
||||
append(stringRes(R.string.terms_of_use))
|
||||
pop()
|
||||
}
|
||||
}
|
||||
|
||||
val uri = LocalUriHandler.current
|
||||
|
||||
ClickableText(
|
||||
annotatedTermsString,
|
||||
) { spanOffset ->
|
||||
annotatedTermsString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also { span ->
|
||||
if (span.tag == "openTerms") {
|
||||
runCatching {
|
||||
uri.openUri("https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedOff
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@Composable
|
||||
fun OrbotCheckBox(
|
||||
currentPort: Int?,
|
||||
useProxy: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
) {
|
||||
var connectOrbotDialogOpen by remember { mutableStateOf(false) }
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = useProxy,
|
||||
onCheckedChange = {
|
||||
if (it) {
|
||||
connectOrbotDialogOpen = true
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Text(stringRes(R.string.connect_via_tor))
|
||||
}
|
||||
|
||||
if (connectOrbotDialogOpen) {
|
||||
ConnectOrbotDialog(
|
||||
onClose = { connectOrbotDialogOpen = false },
|
||||
onPost = {
|
||||
connectOrbotDialogOpen = false
|
||||
onCheckedChange(true)
|
||||
},
|
||||
onError = onError,
|
||||
currentPort,
|
||||
)
|
||||
}
|
||||
}
|
@ -25,20 +25,18 @@ import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
@ -53,13 +51,9 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@ -68,7 +62,6 @@ import com.vitorpamplona.amethyst.commons.hashtags.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
|
||||
import com.vitorpamplona.amethyst.service.PackageUtils
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||
@ -100,7 +93,6 @@ fun SignUpPage(
|
||||
val acceptedTerms = remember { mutableStateOf(false) }
|
||||
var termsAcceptanceIsRequired by remember { mutableStateOf("") }
|
||||
|
||||
val uri = LocalUriHandler.current
|
||||
val context = LocalContext.current
|
||||
val useProxy = remember { mutableStateOf(false) }
|
||||
val proxyPort = remember { mutableStateOf("9050") }
|
||||
@ -111,6 +103,7 @@ fun SignUpPage(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(Size20dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
@ -176,39 +169,10 @@ fun SignUpPage(
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = acceptedTerms.value,
|
||||
onCheckedChange = { acceptedTerms.value = it },
|
||||
)
|
||||
|
||||
val regularText = SpanStyle(color = MaterialTheme.colorScheme.onBackground)
|
||||
|
||||
val clickableTextStyle = SpanStyle(color = MaterialTheme.colorScheme.primary)
|
||||
|
||||
val annotatedTermsString =
|
||||
buildAnnotatedString {
|
||||
withStyle(regularText) { append(stringRes(R.string.i_accept_the)) }
|
||||
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("openTerms", "")
|
||||
append(stringRes(R.string.terms_of_use))
|
||||
pop()
|
||||
}
|
||||
}
|
||||
|
||||
ClickableText(
|
||||
text = annotatedTermsString,
|
||||
) { spanOffset ->
|
||||
annotatedTermsString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also { span ->
|
||||
if (span.tag == "openTerms") {
|
||||
runCatching {
|
||||
uri.openUri("https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AcceptTerms(
|
||||
checked = acceptedTerms.value,
|
||||
onCheckedChange = { acceptedTerms.value = it },
|
||||
)
|
||||
|
||||
if (termsAcceptanceIsRequired.isNotBlank()) {
|
||||
Text(
|
||||
@ -219,45 +183,29 @@ fun SignUpPage(
|
||||
}
|
||||
|
||||
if (PackageUtils.isOrbotInstalled(context)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = useProxy.value,
|
||||
onCheckedChange = {
|
||||
if (it) {
|
||||
connectOrbotDialogOpen = true
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Text(stringRes(R.string.connect_via_tor))
|
||||
}
|
||||
|
||||
if (connectOrbotDialogOpen) {
|
||||
ConnectOrbotDialog(
|
||||
onClose = { connectOrbotDialogOpen = false },
|
||||
onPost = {
|
||||
connectOrbotDialogOpen = false
|
||||
useProxy.value = true
|
||||
},
|
||||
onError = {
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
it,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
},
|
||||
proxyPort,
|
||||
)
|
||||
}
|
||||
OrbotCheckBox(
|
||||
currentPort = proxyPort.value.toIntOrNull(),
|
||||
useProxy = useProxy.value,
|
||||
onCheckedChange = {
|
||||
useProxy.value = it
|
||||
},
|
||||
onError = {
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
it,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(Size10dp))
|
||||
|
||||
Box(modifier = Modifier.padding(Size40dp, 0.dp, Size40dp, 0.dp)) {
|
||||
Button(
|
||||
SignUpButton(
|
||||
enabled = acceptedTerms.value,
|
||||
onClick = {
|
||||
if (!acceptedTerms.value) {
|
||||
@ -272,14 +220,7 @@ fun SignUpPage(
|
||||
accountStateViewModel.newKey(useProxy.value, proxyPort.value.toInt(), displayName.value.text)
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(Size35dp),
|
||||
modifier = Modifier.height(50.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.create_account),
|
||||
modifier = Modifier.padding(horizontal = Size40dp),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(Size40dp))
|
||||
@ -289,16 +230,39 @@ fun SignUpPage(
|
||||
Spacer(modifier = Modifier.height(Size20dp))
|
||||
|
||||
Box(modifier = Modifier.padding(Size40dp, 0.dp, Size40dp, 0.dp)) {
|
||||
OutlinedButton(
|
||||
onClick = onWantsToLogin,
|
||||
shape = RoundedCornerShape(Size35dp),
|
||||
modifier = Modifier.height(50.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.login),
|
||||
modifier = Modifier.padding(horizontal = Size40dp),
|
||||
)
|
||||
}
|
||||
LoginButton(onWantsToLogin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoginButton(onWantsToLogin: () -> Unit) {
|
||||
OutlinedButton(
|
||||
onClick = onWantsToLogin,
|
||||
shape = RoundedCornerShape(Size35dp),
|
||||
modifier = Modifier.height(50.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.login),
|
||||
modifier = Modifier.padding(horizontal = Size40dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SignUpButton(
|
||||
enabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Button(
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
shape = RoundedCornerShape(Size35dp),
|
||||
modifier = Modifier.height(50.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.create_account),
|
||||
modifier = Modifier.padding(horizontal = Size40dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -979,4 +979,6 @@
|
||||
<string name="torrent_no_apps">No torrent apps installed to open and download the file.</string>
|
||||
|
||||
<string name="select_list_to_filter">Select a list to filter the feed</string>
|
||||
|
||||
<string name="temporary_account">Log off on device lock</string>
|
||||
</resources>
|
||||
|
@ -48,6 +48,12 @@ object Nip19Bech32 {
|
||||
KIND(3),
|
||||
}
|
||||
|
||||
val nip19PlusNip46regex =
|
||||
Pattern.compile(
|
||||
"(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1|nembed1|ncryptsec1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)([\\S]*)",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
)
|
||||
|
||||
val nip19regex =
|
||||
Pattern.compile(
|
||||
"(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1|nembed1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)([\\S]*)",
|
||||
@ -110,6 +116,26 @@ object Nip19Bech32 {
|
||||
val event: Event,
|
||||
) : Entity
|
||||
|
||||
fun tryParseAndClean(uri: String?): String? {
|
||||
if (uri == null) return null
|
||||
|
||||
try {
|
||||
val matcher = nip19PlusNip46regex.matcher(uri)
|
||||
if (!matcher.find()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val type = matcher.group(2) // npub1
|
||||
val key = matcher.group(3) // bech32
|
||||
|
||||
return type + key
|
||||
} catch (e: Throwable) {
|
||||
Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun uriToRoute(uri: String?): ParseReturn? {
|
||||
if (uri == null) return null
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user