mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 10:58:23 +00:00
- Add support for importing accounts from backup
- Switch export format to JSONL - Update ktlint to 14.0.1 and apply formatting changes
This commit is contained in:
@@ -59,7 +59,9 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class Amber : Application(), LifecycleObserver {
|
||||
class Amber :
|
||||
Application(),
|
||||
LifecycleObserver {
|
||||
private var mainActivityRef: WeakReference<MainActivity?>? = null
|
||||
val crashReportCache: CrashReportCache by lazy { CrashReportCache(this.applicationContext) }
|
||||
|
||||
@@ -68,9 +70,7 @@ class Amber : Application(), LifecycleObserver {
|
||||
mainActivityRef = WeakReference<MainActivity?>(activity)
|
||||
}
|
||||
|
||||
fun getMainActivity(): MainActivity? {
|
||||
return if (mainActivityRef != null) mainActivityRef!!.get() else null
|
||||
}
|
||||
fun getMainActivity(): MainActivity? = if (mainActivityRef != null) mainActivityRef!!.get() else null
|
||||
|
||||
// Exists to avoid exceptions stopping the coroutine
|
||||
val exceptionHandler =
|
||||
@@ -437,27 +437,25 @@ class Amber : Application(), LifecycleObserver {
|
||||
}
|
||||
}
|
||||
|
||||
fun isPrivateIp(url: String): Boolean {
|
||||
return url.contains("127.0.0.1") ||
|
||||
url.contains("localhost") ||
|
||||
url.contains("192.168.") ||
|
||||
url.contains("172.16.") ||
|
||||
url.contains("172.17.") ||
|
||||
url.contains("172.18.") ||
|
||||
url.contains("172.19.") ||
|
||||
url.contains("172.20.") ||
|
||||
url.contains("172.21.") ||
|
||||
url.contains("172.22.") ||
|
||||
url.contains("172.23.") ||
|
||||
url.contains("172.24.") ||
|
||||
url.contains("172.25.") ||
|
||||
url.contains("172.26.") ||
|
||||
url.contains("172.27.") ||
|
||||
url.contains("172.28.") ||
|
||||
url.contains("172.29.") ||
|
||||
url.contains("172.30.") ||
|
||||
url.contains("172.31.")
|
||||
}
|
||||
fun isPrivateIp(url: String): Boolean = url.contains("127.0.0.1") ||
|
||||
url.contains("localhost") ||
|
||||
url.contains("192.168.") ||
|
||||
url.contains("172.16.") ||
|
||||
url.contains("172.17.") ||
|
||||
url.contains("172.18.") ||
|
||||
url.contains("172.19.") ||
|
||||
url.contains("172.20.") ||
|
||||
url.contains("172.21.") ||
|
||||
url.contains("172.22.") ||
|
||||
url.contains("172.23.") ||
|
||||
url.contains("172.24.") ||
|
||||
url.contains("172.25.") ||
|
||||
url.contains("172.26.") ||
|
||||
url.contains("172.27.") ||
|
||||
url.contains("172.28.") ||
|
||||
url.contains("172.29.") ||
|
||||
url.contains("172.30.") ||
|
||||
url.contains("172.31.")
|
||||
|
||||
override fun onTerminate() {
|
||||
super.onTerminate()
|
||||
|
||||
@@ -17,13 +17,11 @@ import kotlinx.coroutines.flow.first
|
||||
object DataStoreAccess {
|
||||
private val storeCache = ConcurrentHashMap<String, DataStore<Preferences>>()
|
||||
|
||||
private fun getDataStore(context: Context, npub: String): DataStore<Preferences> {
|
||||
return storeCache.computeIfAbsent(npub) {
|
||||
Log.d(Amber.TAG, "Creating new DataStore for $npub")
|
||||
PreferenceDataStoreFactory.create(
|
||||
produceFile = { context.applicationContext.preferencesDataStoreFile("secure_datastore_$npub") },
|
||||
)
|
||||
}
|
||||
private fun getDataStore(context: Context, npub: String): DataStore<Preferences> = storeCache.computeIfAbsent(npub) {
|
||||
Log.d(Amber.TAG, "Creating new DataStore for $npub")
|
||||
PreferenceDataStoreFactory.create(
|
||||
produceFile = { context.applicationContext.preferencesDataStoreFile("secure_datastore_$npub") },
|
||||
)
|
||||
}
|
||||
|
||||
val NOSTR_PRIVKEY = stringPreferencesKey("nostr_privkey")
|
||||
|
||||
@@ -9,9 +9,7 @@ import androidx.security.crypto.MasterKey
|
||||
object EncryptedStorage {
|
||||
private const val PREFERENCES_NAME = "secret_keeper"
|
||||
|
||||
private fun prefsFileName(npub: String? = null): String {
|
||||
return if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub"
|
||||
}
|
||||
private fun prefsFileName(npub: String? = null): String = if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub"
|
||||
|
||||
fun preferences(npub: String? = null, context: Context): EncryptedSharedPreferences {
|
||||
val masterKey: MasterKey =
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import android.util.LruCache
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.core.content.edit
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
@@ -18,6 +17,7 @@ import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.utils.cache.LargeCache
|
||||
import java.io.File
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
@@ -67,25 +67,21 @@ data class AccountInfo(
|
||||
object LocalPreferences {
|
||||
private const val COMMA = ","
|
||||
private var currentAccount: String? = null
|
||||
private var accountCache = LruCache<String, Account>(10)
|
||||
private var accountCache = LargeCache<String, Account>()
|
||||
|
||||
fun allLegacySavedAccounts(context: Context): List<AccountInfo> {
|
||||
return legacySavedAccounts(context).map { npub ->
|
||||
AccountInfo(
|
||||
npub,
|
||||
true,
|
||||
)
|
||||
}.toSet().toList()
|
||||
}
|
||||
fun allLegacySavedAccounts(context: Context): List<AccountInfo> = legacySavedAccounts(context).map { npub ->
|
||||
AccountInfo(
|
||||
npub,
|
||||
true,
|
||||
)
|
||||
}.toSet().toList()
|
||||
|
||||
fun allSavedAccounts(context: Context): List<AccountInfo> {
|
||||
return savedAccounts(context).map { npub ->
|
||||
AccountInfo(
|
||||
npub,
|
||||
true,
|
||||
)
|
||||
}.toSet().toList()
|
||||
}
|
||||
fun allSavedAccounts(context: Context): List<AccountInfo> = savedAccounts(context).map { npub ->
|
||||
AccountInfo(
|
||||
npub,
|
||||
true,
|
||||
)
|
||||
}.toSet().toList()
|
||||
|
||||
fun currentAccount(context: Context): String? {
|
||||
if (currentAccount == null) {
|
||||
@@ -144,9 +140,7 @@ object LocalPreferences {
|
||||
}
|
||||
}
|
||||
|
||||
fun getBatteryOptimization(context: Context): Boolean {
|
||||
return sharedPrefs(context).getBoolean(SettingsKeys.BATERRY_OPTIMIZATION.key, false)
|
||||
}
|
||||
fun getBatteryOptimization(context: Context): Boolean = sharedPrefs(context).getBoolean(SettingsKeys.BATERRY_OPTIMIZATION.key, false)
|
||||
|
||||
fun updateBatteryOptimization(context: Context, value: Boolean) {
|
||||
sharedPrefs(context).edit {
|
||||
@@ -194,7 +188,7 @@ object LocalPreferences {
|
||||
val context = Amber.instance
|
||||
currentAccount = null
|
||||
savedAccounts = null
|
||||
accountCache.evictAll()
|
||||
accountCache.clear()
|
||||
allSavedAccounts(context).forEach {
|
||||
loadFromEncryptedStorage(context, it.npub)
|
||||
}
|
||||
@@ -229,10 +223,8 @@ object LocalPreferences {
|
||||
private var savedAccounts: List<String>? = null
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun legacySavedAccounts(context: Context): List<String> {
|
||||
return encryptedPreferences(context)
|
||||
.getString(SettingsKeys.SAVED_ACCOUNTS.key, null)?.split(COMMA) ?: listOf()
|
||||
}
|
||||
private fun legacySavedAccounts(context: Context): List<String> = encryptedPreferences(context)
|
||||
.getString(SettingsKeys.SAVED_ACCOUNTS.key, null)?.split(COMMA) ?: listOf()
|
||||
|
||||
private fun savedAccounts(context: Context): List<String> {
|
||||
if (savedAccounts == null) {
|
||||
@@ -254,9 +246,7 @@ object LocalPreferences {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDirPath(context: Context): String {
|
||||
return "${context.filesDir.parent}/shared_prefs/"
|
||||
}
|
||||
private fun getDirPath(context: Context): String = "${context.filesDir.parent}/shared_prefs/"
|
||||
|
||||
private fun addAccount(context: Context, npub: String) {
|
||||
val accounts = savedAccounts(context).toMutableList()
|
||||
@@ -275,9 +265,7 @@ object LocalPreferences {
|
||||
updateCurrentAccount(context, npub)
|
||||
}
|
||||
|
||||
fun containsAccount(context: Context, npub: String): Boolean {
|
||||
return savedAccounts(context).contains(npub)
|
||||
}
|
||||
fun containsAccount(context: Context, npub: String): Boolean = savedAccounts(context).contains(npub)
|
||||
|
||||
/**
|
||||
* Removes the account from the app level shared preferences
|
||||
@@ -349,9 +337,7 @@ object LocalPreferences {
|
||||
private fun encryptedPreferences(
|
||||
context: Context,
|
||||
npub: String? = null,
|
||||
): SharedPreferences {
|
||||
return EncryptedStorage.preferences(npub, context)
|
||||
}
|
||||
): SharedPreferences = EncryptedStorage.preferences(npub, context)
|
||||
|
||||
private fun sharedPrefs(
|
||||
context: Context,
|
||||
@@ -451,9 +437,9 @@ object LocalPreferences {
|
||||
}
|
||||
if (npub == null) {
|
||||
val currentAccount = currentAccount(context) ?: return null
|
||||
return accountCache[currentAccount]
|
||||
return accountCache.get(currentAccount)
|
||||
}
|
||||
return accountCache[npub]
|
||||
return accountCache.get(npub)
|
||||
}
|
||||
|
||||
fun setAccountName(
|
||||
|
||||
@@ -52,8 +52,7 @@ import java.time.Instant
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
fun Intent.isLaunchFromHistory(): Boolean =
|
||||
this.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
||||
fun Intent.isLaunchFromHistory(): Boolean = this.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var mainViewModel: MainViewModel
|
||||
|
||||
@@ -84,6 +84,5 @@ object SecureCryptoHelper {
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.hasStrongBox(): Boolean =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
|
||||
fun Context.hasStrongBox(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
|
||||
|
||||
@@ -30,24 +30,16 @@ class SignerProvider : ContentProvider() {
|
||||
uri: Uri,
|
||||
selection: String?,
|
||||
selectionArgs: Array<String>?,
|
||||
): Int {
|
||||
return 0
|
||||
}
|
||||
): Int = 0
|
||||
|
||||
override fun getType(uri: Uri): String? {
|
||||
return null
|
||||
}
|
||||
override fun getType(uri: Uri): String? = null
|
||||
|
||||
override fun insert(
|
||||
uri: Uri,
|
||||
values: ContentValues?,
|
||||
): Uri? {
|
||||
return null
|
||||
}
|
||||
): Uri? = null
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
return true
|
||||
}
|
||||
override fun onCreate(): Boolean = true
|
||||
|
||||
override fun query(
|
||||
uri: Uri,
|
||||
@@ -251,8 +243,7 @@ class SignerProvider : ContentProvider() {
|
||||
MatrixCursor(arrayOf("signature", "event", "result")).also {
|
||||
val signature =
|
||||
if (event.kind == LnZapRequestEvent.KIND &&
|
||||
event.tags.any {
|
||||
tag ->
|
||||
event.tags.any { tag ->
|
||||
tag.any { t -> t == "anon" }
|
||||
}
|
||||
) {
|
||||
@@ -553,7 +544,5 @@ class SignerProvider : ContentProvider() {
|
||||
values: ContentValues?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<String>?,
|
||||
): Int {
|
||||
return 0
|
||||
}
|
||||
): Int = 0
|
||||
}
|
||||
|
||||
@@ -163,39 +163,37 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
fun getDatabase(
|
||||
context: Context,
|
||||
npub: String,
|
||||
): AppDatabase {
|
||||
return synchronized(this) {
|
||||
val executor = Executors.newCachedThreadPool()
|
||||
val transactionExecutor = Executors.newCachedThreadPool()
|
||||
): AppDatabase = synchronized(this) {
|
||||
val executor = Executors.newCachedThreadPool()
|
||||
val transactionExecutor = Executors.newCachedThreadPool()
|
||||
|
||||
val instance =
|
||||
Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java,
|
||||
"amber_db_$npub",
|
||||
)
|
||||
.setQueryExecutor(executor)
|
||||
.setTransactionExecutor(transactionExecutor)
|
||||
.addMigrations(MIGRATION_1_2)
|
||||
.addMigrations(MIGRATION_2_3)
|
||||
.addMigrations(MIGRATION_3_4)
|
||||
.addMigrations(MIGRATION_4_5)
|
||||
.addMigrations(MIGRATION_5_6)
|
||||
.addMigrations(MIGRATION_6_7)
|
||||
.addMigrations(MIGRATION_7_8)
|
||||
.addMigrations(MIGRATION_8_9)
|
||||
.addMigrations(MIGRATION_9_10)
|
||||
.addMigrations(MIGRATION_10_11)
|
||||
.addMigrations(MIGRATION_11_12)
|
||||
.addMigrations(MIGRATION_12_13)
|
||||
.addMigrations(MIGRATION_13_14)
|
||||
.addMigrations(MIGRATION_14_15)
|
||||
.addMigrations(MIGRATION_15_16)
|
||||
.build()
|
||||
instance.openHelper.writableDatabase.execSQL("VACUUM")
|
||||
val instance =
|
||||
Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java,
|
||||
"amber_db_$npub",
|
||||
)
|
||||
.setQueryExecutor(executor)
|
||||
.setTransactionExecutor(transactionExecutor)
|
||||
.addMigrations(MIGRATION_1_2)
|
||||
.addMigrations(MIGRATION_2_3)
|
||||
.addMigrations(MIGRATION_3_4)
|
||||
.addMigrations(MIGRATION_4_5)
|
||||
.addMigrations(MIGRATION_5_6)
|
||||
.addMigrations(MIGRATION_6_7)
|
||||
.addMigrations(MIGRATION_7_8)
|
||||
.addMigrations(MIGRATION_8_9)
|
||||
.addMigrations(MIGRATION_9_10)
|
||||
.addMigrations(MIGRATION_10_11)
|
||||
.addMigrations(MIGRATION_11_12)
|
||||
.addMigrations(MIGRATION_12_13)
|
||||
.addMigrations(MIGRATION_13_14)
|
||||
.addMigrations(MIGRATION_14_15)
|
||||
.addMigrations(MIGRATION_15_16)
|
||||
.build()
|
||||
instance.openHelper.writableDatabase.execSQL("VACUUM")
|
||||
|
||||
instance
|
||||
}
|
||||
instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +61,7 @@ data class ApplicationEntity(
|
||||
)
|
||||
}
|
||||
|
||||
fun shouldShowRelays(): Boolean {
|
||||
return (secret.isNotEmpty() || relays.isNotEmpty()) && !isConnected
|
||||
}
|
||||
fun shouldShowRelays(): Boolean = (secret.isNotEmpty() || relays.isNotEmpty()) && !isConnected
|
||||
}
|
||||
|
||||
data class ApplicationWithPermissions(
|
||||
@@ -87,9 +85,7 @@ class Converters {
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun toString(relays: List<NormalizedRelayUrl>): String {
|
||||
return relays.joinToString(separator = ",") {
|
||||
it.url
|
||||
}
|
||||
fun toString(relays: List<NormalizedRelayUrl>): String = relays.joinToString(separator = ",") {
|
||||
it.url
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,24 +21,22 @@ abstract class HistoryDatabase : RoomDatabase() {
|
||||
fun getDatabase(
|
||||
context: Context,
|
||||
npub: String,
|
||||
): HistoryDatabase {
|
||||
return synchronized(this) {
|
||||
val executor = Executors.newCachedThreadPool()
|
||||
val transactionExecutor = Executors.newCachedThreadPool()
|
||||
): HistoryDatabase = synchronized(this) {
|
||||
val executor = Executors.newCachedThreadPool()
|
||||
val transactionExecutor = Executors.newCachedThreadPool()
|
||||
|
||||
val instance =
|
||||
Room.databaseBuilder(
|
||||
context,
|
||||
HistoryDatabase::class.java,
|
||||
"history_db_$npub",
|
||||
)
|
||||
.setQueryExecutor(executor)
|
||||
.setTransactionExecutor(transactionExecutor)
|
||||
.build()
|
||||
instance.openHelper.writableDatabase.execSQL("VACUUM")
|
||||
val instance =
|
||||
Room.databaseBuilder(
|
||||
context,
|
||||
HistoryDatabase::class.java,
|
||||
"history_db_$npub",
|
||||
)
|
||||
.setQueryExecutor(executor)
|
||||
.setTransactionExecutor(transactionExecutor)
|
||||
.build()
|
||||
instance.openHelper.writableDatabase.execSQL("VACUUM")
|
||||
|
||||
instance
|
||||
}
|
||||
instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,24 +21,22 @@ abstract class LogDatabase : RoomDatabase() {
|
||||
fun getDatabase(
|
||||
context: Context,
|
||||
npub: String,
|
||||
): LogDatabase {
|
||||
return synchronized(this) {
|
||||
val executor = Executors.newCachedThreadPool()
|
||||
val transactionExecutor = Executors.newCachedThreadPool()
|
||||
): LogDatabase = synchronized(this) {
|
||||
val executor = Executors.newCachedThreadPool()
|
||||
val transactionExecutor = Executors.newCachedThreadPool()
|
||||
|
||||
val instance =
|
||||
Room.databaseBuilder(
|
||||
context,
|
||||
LogDatabase::class.java,
|
||||
"log_db_$npub",
|
||||
)
|
||||
.setQueryExecutor(executor)
|
||||
.setTransactionExecutor(transactionExecutor)
|
||||
.build()
|
||||
instance.openHelper.writableDatabase.execSQL("VACUUM")
|
||||
val instance =
|
||||
Room.databaseBuilder(
|
||||
context,
|
||||
LogDatabase::class.java,
|
||||
"log_db_$npub",
|
||||
)
|
||||
.setQueryExecutor(executor)
|
||||
.setTransactionExecutor(transactionExecutor)
|
||||
.build()
|
||||
instance.openHelper.writableDatabase.execSQL("VACUUM")
|
||||
|
||||
instance
|
||||
}
|
||||
instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,7 @@ class Account(
|
||||
suspend fun createAuthEvent(
|
||||
relayUrl: NormalizedRelayUrl,
|
||||
challenge: String,
|
||||
): RelayAuthEvent {
|
||||
return RelayAuthEvent.create(relayUrl, challenge, signer)
|
||||
}
|
||||
): RelayAuthEvent = RelayAuthEvent.create(relayUrl, challenge, signer)
|
||||
}
|
||||
|
||||
class AccountLiveData(account: Account) : LiveData<AccountState>(AccountState(account))
|
||||
|
||||
+9
-22
@@ -2,6 +2,7 @@ package com.greenart7c3.nostrsigner.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
|
||||
@@ -10,12 +11,16 @@ import com.fasterxml.jackson.module.kotlin.readValue
|
||||
*/
|
||||
@Immutable
|
||||
data class AccountExportData(
|
||||
@param:JsonProperty("version")
|
||||
val version: String = "1.0",
|
||||
@param:JsonProperty("exportDate")
|
||||
val exportDate: Long = System.currentTimeMillis() / 1000,
|
||||
@param:JsonProperty("npub")
|
||||
val npub: String,
|
||||
@param:JsonProperty("name")
|
||||
val name: String,
|
||||
@param:JsonProperty("nsec")
|
||||
val nsec: String?,
|
||||
val nsec: String,
|
||||
@param:JsonProperty("seedWords")
|
||||
val seedWords: String,
|
||||
@param:JsonProperty("signPolicy")
|
||||
@@ -24,31 +29,13 @@ data class AccountExportData(
|
||||
val picture: String?,
|
||||
@param:JsonProperty("didBackup")
|
||||
val didBackup: Boolean,
|
||||
)
|
||||
|
||||
/**
|
||||
* Container for bulk account export with versioning for future compatibility
|
||||
*/
|
||||
@Immutable
|
||||
data class BulkAccountExport(
|
||||
@param:JsonProperty("version")
|
||||
val version: String = "1.0",
|
||||
@param:JsonProperty("exportDate")
|
||||
val exportDate: Long = System.currentTimeMillis() / 1000,
|
||||
@param:JsonProperty("accountCount")
|
||||
val accountCount: Int,
|
||||
@param:JsonProperty("accounts")
|
||||
val accounts: List<AccountExportData>,
|
||||
) {
|
||||
companion object {
|
||||
private val mapper = jacksonObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
|
||||
fun toJson(export: BulkAccountExport): String {
|
||||
return mapper.writeValueAsString(export)
|
||||
}
|
||||
fun toJson(export: AccountExportData): String = mapper.writeValueAsString(export)
|
||||
|
||||
fun fromJson(json: String): BulkAccountExport {
|
||||
return mapper.readValue(json)
|
||||
}
|
||||
fun fromJson(json: String): AccountExportData = mapper.readValue(json)
|
||||
}
|
||||
}
|
||||
@@ -24,22 +24,18 @@ data class BunkerMetadata(
|
||||
.addDeserializer(BunkerMetadata::class.java, BunkerMetadataDeserializer()),
|
||||
)
|
||||
|
||||
fun fromJson(jsonObject: JsonNode): BunkerMetadata {
|
||||
return BunkerMetadata(
|
||||
name = jsonObject.get("name")?.asText()?.intern() ?: "",
|
||||
url = jsonObject.get("url")?.asText()?.intern() ?: "",
|
||||
description = jsonObject.get("description")?.asText()?.intern() ?: "",
|
||||
perms = jsonObject.get("perms")?.asText()?.intern() ?: "",
|
||||
)
|
||||
}
|
||||
fun fromJson(jsonObject: JsonNode): BunkerMetadata = BunkerMetadata(
|
||||
name = jsonObject.get("name")?.asText()?.intern() ?: "",
|
||||
url = jsonObject.get("url")?.asText()?.intern() ?: "",
|
||||
description = jsonObject.get("description")?.asText()?.intern() ?: "",
|
||||
perms = jsonObject.get("perms")?.asText()?.intern() ?: "",
|
||||
)
|
||||
|
||||
private class BunkerMetadataDeserializer : StdDeserializer<BunkerMetadata>(BunkerMetadata::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): BunkerMetadata {
|
||||
return fromJson(jp.codec.readTree(jp))
|
||||
}
|
||||
): BunkerMetadata = fromJson(jp.codec.readTree(jp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,11 +45,10 @@ data class Permission(
|
||||
|
||||
private class PermissionManualDeserializer {
|
||||
companion object {
|
||||
fun fromJson(jsonObject: JsonNode): Permission =
|
||||
Permission(
|
||||
type = jsonObject.get("type").asText().intern(),
|
||||
kind = jsonObject.get("kind").asText()?.toIntOrNull(),
|
||||
)
|
||||
fun fromJson(jsonObject: JsonNode): Permission = Permission(
|
||||
type = jsonObject.get("type").asText().intern(),
|
||||
kind = jsonObject.get("kind").asText()?.toIntOrNull(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,256 +66,252 @@ data class Permission(
|
||||
}
|
||||
|
||||
// LAST_COMMIT_CHECKED = "14ec14dac92d891715239bfb83ed1c9bdf2ac382"
|
||||
fun toLocalizedString(context: Context, shouldTranslateConnect: Boolean = false): String {
|
||||
return when (type.trim()) {
|
||||
"sign_message" -> context.getString(R.string.sign_message)
|
||||
"get_public_key" -> context.getString(R.string.read_your_public_key)
|
||||
"nip04_encrypt" -> {
|
||||
context.getString(R.string.encrypt_data_using_nip_4)
|
||||
}
|
||||
"nip04_decrypt" -> {
|
||||
context.getString(R.string.decrypt_data_using_nip_4)
|
||||
}
|
||||
"nip44_decrypt" -> {
|
||||
context.getString(R.string.decrypt_data_using_nip_44)
|
||||
}
|
||||
"nip44_encrypt" -> {
|
||||
context.getString(R.string.encrypt_data_using_nip_44)
|
||||
}
|
||||
"decrypt_zap_event" -> {
|
||||
context.getString(R.string.decrypt_private_zaps)
|
||||
}
|
||||
"sign_event" -> {
|
||||
when (kind) {
|
||||
0 -> context.getString(R.string.event_kind_0)
|
||||
1 -> context.getString(R.string.event_kind_1)
|
||||
3 -> context.getString(R.string.event_kind_3)
|
||||
4 -> context.getString(R.string.event_kind_4)
|
||||
5 -> context.getString(R.string.event_kind_5)
|
||||
6 -> context.getString(R.string.event_kind_6)
|
||||
7 -> context.getString(R.string.event_kind_7)
|
||||
8 -> context.getString(R.string.event_kind_8)
|
||||
9 -> context.getString(R.string.event_kind_9)
|
||||
10 -> context.getString(R.string.event_kind_10)
|
||||
11 -> context.getString(R.string.event_kind_11)
|
||||
12 -> context.getString(R.string.event_kind_12)
|
||||
13 -> context.getString(R.string.event_kind_13)
|
||||
14 -> context.getString(R.string.event_kind_14)
|
||||
16 -> context.getString(R.string.event_kind_16)
|
||||
17 -> context.getString(R.string.event_kind_17)
|
||||
20 -> context.getString(R.string.event_kind_20)
|
||||
40 -> context.getString(R.string.event_kind_40)
|
||||
41 -> context.getString(R.string.event_kind_41)
|
||||
42 -> context.getString(R.string.event_kind_42)
|
||||
43 -> context.getString(R.string.event_kind_43)
|
||||
44 -> context.getString(R.string.event_kind_44)
|
||||
64 -> context.getString(R.string.event_kind_64)
|
||||
443 -> context.getString(R.string.event_kind_443)
|
||||
444 -> context.getString(R.string.event_kind_444)
|
||||
445 -> context.getString(R.string.event_kind_445)
|
||||
818 -> context.getString(R.string.event_kind_818)
|
||||
1018 -> context.getString(R.string.event_kind_1018)
|
||||
1021 -> context.getString(R.string.event_kind_1021)
|
||||
1022 -> context.getString(R.string.event_kind_1022)
|
||||
1040 -> context.getString(R.string.event_kind_1040)
|
||||
1059 -> context.getString(R.string.event_kind_1059)
|
||||
1063 -> context.getString(R.string.event_kind_1063)
|
||||
1068 -> context.getString(R.string.event_kind_1068)
|
||||
1111 -> context.getString(R.string.event_kind_1111)
|
||||
1311 -> context.getString(R.string.event_kind_1311)
|
||||
1617 -> context.getString(R.string.event_kind_1617)
|
||||
1621 -> context.getString(R.string.event_kind_1621)
|
||||
1622 -> context.getString(R.string.event_kind_1622)
|
||||
in 1630..1633 -> context.getString(R.string.event_kind_1630_1633)
|
||||
1971 -> context.getString(R.string.event_kind_1971)
|
||||
1984 -> context.getString(R.string.event_kind_1984)
|
||||
1985 -> context.getString(R.string.event_kind_1985)
|
||||
1986 -> context.getString(R.string.event_kind_1986)
|
||||
1987 -> context.getString(R.string.event_kind_1987)
|
||||
2003 -> context.getString(R.string.event_kind_2003)
|
||||
2004 -> context.getString(R.string.event_kind_2004)
|
||||
2022 -> context.getString(R.string.event_kind_2022)
|
||||
4550 -> context.getString(R.string.event_kind_4550)
|
||||
in 5000..5999 -> context.getString(R.string.event_kind_5000_5999)
|
||||
in 6000..6999 -> context.getString(R.string.event_kind_6000_6999)
|
||||
7000 -> context.getString(R.string.event_kind_7000)
|
||||
7374 -> context.getString(R.string.event_kind_7374)
|
||||
7375 -> context.getString(R.string.event_kind_7375)
|
||||
7376 -> context.getString(R.string.event_kind_7376)
|
||||
9000 -> context.getString(R.string.event_kind_9000)
|
||||
9001 -> context.getString(R.string.event_kind_9001)
|
||||
9002 -> context.getString(R.string.event_kind_9002)
|
||||
9003 -> context.getString(R.string.event_kind_9003)
|
||||
9004 -> context.getString(R.string.event_kind_9004)
|
||||
9005 -> context.getString(R.string.event_kind_9005)
|
||||
9006 -> context.getString(R.string.event_kind_9006)
|
||||
9007 -> context.getString(R.string.event_kind_9007)
|
||||
9021 -> context.getString(R.string.event_kind_9021)
|
||||
in 9000..9030 -> context.getString(R.string.event_kind_9000_9030)
|
||||
9041 -> context.getString(R.string.event_kind_9041)
|
||||
9321 -> context.getString(R.string.event_kind_9321)
|
||||
9467 -> context.getString(R.string.event_kind_9467)
|
||||
9734 -> context.getString(R.string.event_kind_9734)
|
||||
9735 -> context.getString(R.string.event_kind_9735)
|
||||
9802 -> context.getString(R.string.event_kind_9802)
|
||||
10000 -> context.getString(R.string.event_kind_10000)
|
||||
10001 -> context.getString(R.string.event_kind_10001)
|
||||
10002 -> context.getString(R.string.event_kind_10002)
|
||||
10003 -> context.getString(R.string.event_kind_10003)
|
||||
10004 -> context.getString(R.string.event_kind_10004)
|
||||
10005 -> context.getString(R.string.event_kind_10005)
|
||||
10006 -> context.getString(R.string.event_kind_10006)
|
||||
10007 -> context.getString(R.string.event_kind_10007)
|
||||
10009 -> context.getString(R.string.event_kind_10009)
|
||||
10013 -> context.getString(R.string.event_kind_10013)
|
||||
10015 -> context.getString(R.string.event_kind_10015)
|
||||
10020 -> context.getString(R.string.event_kind_10020)
|
||||
10030 -> context.getString(R.string.event_kind_10030)
|
||||
10050 -> context.getString(R.string.event_kind_10050)
|
||||
10051 -> context.getString(R.string.event_kind_10051)
|
||||
10063 -> context.getString(R.string.event_kind_10063)
|
||||
10096 -> context.getString(R.string.event_kind_10096)
|
||||
13194 -> context.getString(R.string.event_kind_13194)
|
||||
21000 -> context.getString(R.string.event_kind_21000)
|
||||
22242 -> context.getString(R.string.event_kind_22242)
|
||||
23194 -> context.getString(R.string.event_kind_23194)
|
||||
23195 -> context.getString(R.string.event_kind_23195)
|
||||
24133 -> context.getString(R.string.event_kind_24133)
|
||||
24242 -> context.getString(R.string.event_kind_24242)
|
||||
27235 -> context.getString(R.string.event_kind_27235)
|
||||
22456 -> context.getString(R.string.event_kind_22456)
|
||||
30000 -> context.getString(R.string.event_kind_30000)
|
||||
30001 -> context.getString(R.string.event_kind_30001)
|
||||
30002 -> context.getString(R.string.event_kind_30002)
|
||||
30003 -> context.getString(R.string.event_kind_30003)
|
||||
30004 -> context.getString(R.string.event_kind_30004)
|
||||
30008 -> context.getString(R.string.event_kind_30008)
|
||||
30009 -> context.getString(R.string.event_kind_30009)
|
||||
30015 -> context.getString(R.string.event_kind_30015)
|
||||
30017 -> context.getString(R.string.event_kind_30017)
|
||||
30018 -> context.getString(R.string.event_kind_30018)
|
||||
30019 -> context.getString(R.string.event_kind_30019)
|
||||
30020 -> context.getString(R.string.event_kind_30020)
|
||||
30023 -> context.getString(R.string.event_kind_30023)
|
||||
30024 -> context.getString(R.string.event_kind_30024)
|
||||
30030 -> context.getString(R.string.event_kind_30030)
|
||||
30040 -> context.getString(R.string.event_kind_30040)
|
||||
30041 -> context.getString(R.string.event_kind_30041)
|
||||
30063 -> context.getString(R.string.event_kind_30063)
|
||||
30078 -> context.getString(R.string.event_kind_30078)
|
||||
30311 -> context.getString(R.string.event_kind_30311)
|
||||
30315 -> context.getString(R.string.event_kind_30315)
|
||||
30402 -> context.getString(R.string.event_kind_30402)
|
||||
30403 -> context.getString(R.string.event_kind_30403)
|
||||
31234 -> context.getString(R.string.event_kind_31234)
|
||||
31922 -> context.getString(R.string.event_kind_31922)
|
||||
31923 -> context.getString(R.string.event_kind_31923)
|
||||
31924 -> context.getString(R.string.event_kind_31924)
|
||||
31925 -> context.getString(R.string.event_kind_31925)
|
||||
31989 -> context.getString(R.string.event_kind_31989)
|
||||
31990 -> context.getString(R.string.event_kind_31990)
|
||||
34235 -> context.getString(R.string.event_kind_34235)
|
||||
34236 -> context.getString(R.string.event_kind_34236)
|
||||
34550 -> context.getString(R.string.event_kind_34550)
|
||||
37375 -> context.getString(R.string.event_kind_37375)
|
||||
38383 -> context.getString(R.string.event_kind_38383)
|
||||
39000 -> context.getString(R.string.event_kind_39000)
|
||||
39001 -> context.getString(R.string.event_kind_39001)
|
||||
39002 -> context.getString(R.string.event_kind_39002)
|
||||
in 39003..39009 -> context.getString(R.string.event_kind_39000_39009)
|
||||
39089 -> context.getString(R.string.event_kind_39089)
|
||||
39092 -> context.getString(R.string.event_kind_39092)
|
||||
10000300 -> context.getString(R.string.event_kind_10000300)
|
||||
else -> {
|
||||
val nipDescription = nipToLocalizedString(context, this, true)
|
||||
nipDescription.ifEmpty {
|
||||
context.getString(R.string.event_kind, kind.toString())
|
||||
}
|
||||
fun toLocalizedString(context: Context, shouldTranslateConnect: Boolean = false): String = when (type.trim()) {
|
||||
"sign_message" -> context.getString(R.string.sign_message)
|
||||
"get_public_key" -> context.getString(R.string.read_your_public_key)
|
||||
"nip04_encrypt" -> {
|
||||
context.getString(R.string.encrypt_data_using_nip_4)
|
||||
}
|
||||
"nip04_decrypt" -> {
|
||||
context.getString(R.string.decrypt_data_using_nip_4)
|
||||
}
|
||||
"nip44_decrypt" -> {
|
||||
context.getString(R.string.decrypt_data_using_nip_44)
|
||||
}
|
||||
"nip44_encrypt" -> {
|
||||
context.getString(R.string.encrypt_data_using_nip_44)
|
||||
}
|
||||
"decrypt_zap_event" -> {
|
||||
context.getString(R.string.decrypt_private_zaps)
|
||||
}
|
||||
"sign_event" -> {
|
||||
when (kind) {
|
||||
0 -> context.getString(R.string.event_kind_0)
|
||||
1 -> context.getString(R.string.event_kind_1)
|
||||
3 -> context.getString(R.string.event_kind_3)
|
||||
4 -> context.getString(R.string.event_kind_4)
|
||||
5 -> context.getString(R.string.event_kind_5)
|
||||
6 -> context.getString(R.string.event_kind_6)
|
||||
7 -> context.getString(R.string.event_kind_7)
|
||||
8 -> context.getString(R.string.event_kind_8)
|
||||
9 -> context.getString(R.string.event_kind_9)
|
||||
10 -> context.getString(R.string.event_kind_10)
|
||||
11 -> context.getString(R.string.event_kind_11)
|
||||
12 -> context.getString(R.string.event_kind_12)
|
||||
13 -> context.getString(R.string.event_kind_13)
|
||||
14 -> context.getString(R.string.event_kind_14)
|
||||
16 -> context.getString(R.string.event_kind_16)
|
||||
17 -> context.getString(R.string.event_kind_17)
|
||||
20 -> context.getString(R.string.event_kind_20)
|
||||
40 -> context.getString(R.string.event_kind_40)
|
||||
41 -> context.getString(R.string.event_kind_41)
|
||||
42 -> context.getString(R.string.event_kind_42)
|
||||
43 -> context.getString(R.string.event_kind_43)
|
||||
44 -> context.getString(R.string.event_kind_44)
|
||||
64 -> context.getString(R.string.event_kind_64)
|
||||
443 -> context.getString(R.string.event_kind_443)
|
||||
444 -> context.getString(R.string.event_kind_444)
|
||||
445 -> context.getString(R.string.event_kind_445)
|
||||
818 -> context.getString(R.string.event_kind_818)
|
||||
1018 -> context.getString(R.string.event_kind_1018)
|
||||
1021 -> context.getString(R.string.event_kind_1021)
|
||||
1022 -> context.getString(R.string.event_kind_1022)
|
||||
1040 -> context.getString(R.string.event_kind_1040)
|
||||
1059 -> context.getString(R.string.event_kind_1059)
|
||||
1063 -> context.getString(R.string.event_kind_1063)
|
||||
1068 -> context.getString(R.string.event_kind_1068)
|
||||
1111 -> context.getString(R.string.event_kind_1111)
|
||||
1311 -> context.getString(R.string.event_kind_1311)
|
||||
1617 -> context.getString(R.string.event_kind_1617)
|
||||
1621 -> context.getString(R.string.event_kind_1621)
|
||||
1622 -> context.getString(R.string.event_kind_1622)
|
||||
in 1630..1633 -> context.getString(R.string.event_kind_1630_1633)
|
||||
1971 -> context.getString(R.string.event_kind_1971)
|
||||
1984 -> context.getString(R.string.event_kind_1984)
|
||||
1985 -> context.getString(R.string.event_kind_1985)
|
||||
1986 -> context.getString(R.string.event_kind_1986)
|
||||
1987 -> context.getString(R.string.event_kind_1987)
|
||||
2003 -> context.getString(R.string.event_kind_2003)
|
||||
2004 -> context.getString(R.string.event_kind_2004)
|
||||
2022 -> context.getString(R.string.event_kind_2022)
|
||||
4550 -> context.getString(R.string.event_kind_4550)
|
||||
in 5000..5999 -> context.getString(R.string.event_kind_5000_5999)
|
||||
in 6000..6999 -> context.getString(R.string.event_kind_6000_6999)
|
||||
7000 -> context.getString(R.string.event_kind_7000)
|
||||
7374 -> context.getString(R.string.event_kind_7374)
|
||||
7375 -> context.getString(R.string.event_kind_7375)
|
||||
7376 -> context.getString(R.string.event_kind_7376)
|
||||
9000 -> context.getString(R.string.event_kind_9000)
|
||||
9001 -> context.getString(R.string.event_kind_9001)
|
||||
9002 -> context.getString(R.string.event_kind_9002)
|
||||
9003 -> context.getString(R.string.event_kind_9003)
|
||||
9004 -> context.getString(R.string.event_kind_9004)
|
||||
9005 -> context.getString(R.string.event_kind_9005)
|
||||
9006 -> context.getString(R.string.event_kind_9006)
|
||||
9007 -> context.getString(R.string.event_kind_9007)
|
||||
9021 -> context.getString(R.string.event_kind_9021)
|
||||
in 9000..9030 -> context.getString(R.string.event_kind_9000_9030)
|
||||
9041 -> context.getString(R.string.event_kind_9041)
|
||||
9321 -> context.getString(R.string.event_kind_9321)
|
||||
9467 -> context.getString(R.string.event_kind_9467)
|
||||
9734 -> context.getString(R.string.event_kind_9734)
|
||||
9735 -> context.getString(R.string.event_kind_9735)
|
||||
9802 -> context.getString(R.string.event_kind_9802)
|
||||
10000 -> context.getString(R.string.event_kind_10000)
|
||||
10001 -> context.getString(R.string.event_kind_10001)
|
||||
10002 -> context.getString(R.string.event_kind_10002)
|
||||
10003 -> context.getString(R.string.event_kind_10003)
|
||||
10004 -> context.getString(R.string.event_kind_10004)
|
||||
10005 -> context.getString(R.string.event_kind_10005)
|
||||
10006 -> context.getString(R.string.event_kind_10006)
|
||||
10007 -> context.getString(R.string.event_kind_10007)
|
||||
10009 -> context.getString(R.string.event_kind_10009)
|
||||
10013 -> context.getString(R.string.event_kind_10013)
|
||||
10015 -> context.getString(R.string.event_kind_10015)
|
||||
10020 -> context.getString(R.string.event_kind_10020)
|
||||
10030 -> context.getString(R.string.event_kind_10030)
|
||||
10050 -> context.getString(R.string.event_kind_10050)
|
||||
10051 -> context.getString(R.string.event_kind_10051)
|
||||
10063 -> context.getString(R.string.event_kind_10063)
|
||||
10096 -> context.getString(R.string.event_kind_10096)
|
||||
13194 -> context.getString(R.string.event_kind_13194)
|
||||
21000 -> context.getString(R.string.event_kind_21000)
|
||||
22242 -> context.getString(R.string.event_kind_22242)
|
||||
23194 -> context.getString(R.string.event_kind_23194)
|
||||
23195 -> context.getString(R.string.event_kind_23195)
|
||||
24133 -> context.getString(R.string.event_kind_24133)
|
||||
24242 -> context.getString(R.string.event_kind_24242)
|
||||
27235 -> context.getString(R.string.event_kind_27235)
|
||||
22456 -> context.getString(R.string.event_kind_22456)
|
||||
30000 -> context.getString(R.string.event_kind_30000)
|
||||
30001 -> context.getString(R.string.event_kind_30001)
|
||||
30002 -> context.getString(R.string.event_kind_30002)
|
||||
30003 -> context.getString(R.string.event_kind_30003)
|
||||
30004 -> context.getString(R.string.event_kind_30004)
|
||||
30008 -> context.getString(R.string.event_kind_30008)
|
||||
30009 -> context.getString(R.string.event_kind_30009)
|
||||
30015 -> context.getString(R.string.event_kind_30015)
|
||||
30017 -> context.getString(R.string.event_kind_30017)
|
||||
30018 -> context.getString(R.string.event_kind_30018)
|
||||
30019 -> context.getString(R.string.event_kind_30019)
|
||||
30020 -> context.getString(R.string.event_kind_30020)
|
||||
30023 -> context.getString(R.string.event_kind_30023)
|
||||
30024 -> context.getString(R.string.event_kind_30024)
|
||||
30030 -> context.getString(R.string.event_kind_30030)
|
||||
30040 -> context.getString(R.string.event_kind_30040)
|
||||
30041 -> context.getString(R.string.event_kind_30041)
|
||||
30063 -> context.getString(R.string.event_kind_30063)
|
||||
30078 -> context.getString(R.string.event_kind_30078)
|
||||
30311 -> context.getString(R.string.event_kind_30311)
|
||||
30315 -> context.getString(R.string.event_kind_30315)
|
||||
30402 -> context.getString(R.string.event_kind_30402)
|
||||
30403 -> context.getString(R.string.event_kind_30403)
|
||||
31234 -> context.getString(R.string.event_kind_31234)
|
||||
31922 -> context.getString(R.string.event_kind_31922)
|
||||
31923 -> context.getString(R.string.event_kind_31923)
|
||||
31924 -> context.getString(R.string.event_kind_31924)
|
||||
31925 -> context.getString(R.string.event_kind_31925)
|
||||
31989 -> context.getString(R.string.event_kind_31989)
|
||||
31990 -> context.getString(R.string.event_kind_31990)
|
||||
34235 -> context.getString(R.string.event_kind_34235)
|
||||
34236 -> context.getString(R.string.event_kind_34236)
|
||||
34550 -> context.getString(R.string.event_kind_34550)
|
||||
37375 -> context.getString(R.string.event_kind_37375)
|
||||
38383 -> context.getString(R.string.event_kind_38383)
|
||||
39000 -> context.getString(R.string.event_kind_39000)
|
||||
39001 -> context.getString(R.string.event_kind_39001)
|
||||
39002 -> context.getString(R.string.event_kind_39002)
|
||||
in 39003..39009 -> context.getString(R.string.event_kind_39000_39009)
|
||||
39089 -> context.getString(R.string.event_kind_39089)
|
||||
39092 -> context.getString(R.string.event_kind_39092)
|
||||
10000300 -> context.getString(R.string.event_kind_10000300)
|
||||
else -> {
|
||||
val nipDescription = nipToLocalizedString(context, this, true)
|
||||
nipDescription.ifEmpty {
|
||||
context.getString(R.string.event_kind, kind.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
"connect" -> if (shouldTranslateConnect) context.getString(R.string.connect) else context.getString(R.string.would_like_your_permission_to_read_your_public_key_and_sign_events_on_your_behalf)
|
||||
"nip" -> {
|
||||
nipToLocalizedString(context, this)
|
||||
}
|
||||
else -> type
|
||||
}
|
||||
"connect" -> if (shouldTranslateConnect) context.getString(R.string.connect) else context.getString(R.string.would_like_your_permission_to_read_your_public_key_and_sign_events_on_your_behalf)
|
||||
"nip" -> {
|
||||
nipToLocalizedString(context, this)
|
||||
}
|
||||
else -> type
|
||||
}
|
||||
}
|
||||
|
||||
fun nipToLocalizedString(context: Context, permission: Permission, shouldReturnEmpty: Boolean = false): String {
|
||||
return when (permission.kind) {
|
||||
1 -> context.getString(R.string.nip_01)
|
||||
2 -> context.getString(R.string.nip_02)
|
||||
3 -> context.getString(R.string.nip_03)
|
||||
4 -> context.getString(R.string.nip_04)
|
||||
5 -> context.getString(R.string.nip_05)
|
||||
6 -> context.getString(R.string.nip_06)
|
||||
7 -> context.getString(R.string.nip_07)
|
||||
8 -> context.getString(R.string.nip_08)
|
||||
9 -> context.getString(R.string.nip_09)
|
||||
10 -> context.getString(R.string.nip_10)
|
||||
11 -> context.getString(R.string.nip_11)
|
||||
13 -> context.getString(R.string.nip_13)
|
||||
14 -> context.getString(R.string.nip_14)
|
||||
15 -> context.getString(R.string.nip_15)
|
||||
17 -> context.getString(R.string.nip_17)
|
||||
18 -> context.getString(R.string.nip_18)
|
||||
19 -> context.getString(R.string.nip_19)
|
||||
21 -> context.getString(R.string.nip_21)
|
||||
23 -> context.getString(R.string.nip_23)
|
||||
24 -> context.getString(R.string.nip_24)
|
||||
25 -> context.getString(R.string.nip_25)
|
||||
26 -> context.getString(R.string.nip_26)
|
||||
27 -> context.getString(R.string.nip_27)
|
||||
28 -> context.getString(R.string.nip_28)
|
||||
29 -> context.getString(R.string.nip_29)
|
||||
30 -> context.getString(R.string.nip_30)
|
||||
31 -> context.getString(R.string.nip_31)
|
||||
32 -> context.getString(R.string.nip_32)
|
||||
34 -> context.getString(R.string.nip_34)
|
||||
35 -> context.getString(R.string.nip_35)
|
||||
36 -> context.getString(R.string.nip_36)
|
||||
38 -> context.getString(R.string.nip_38)
|
||||
39 -> context.getString(R.string.nip_39)
|
||||
40 -> context.getString(R.string.nip_40)
|
||||
42 -> context.getString(R.string.nip_42)
|
||||
44 -> context.getString(R.string.nip_44)
|
||||
45 -> context.getString(R.string.nip_45)
|
||||
46 -> context.getString(R.string.nip_46)
|
||||
47 -> context.getString(R.string.nip_47)
|
||||
48 -> context.getString(R.string.nip_48)
|
||||
49 -> context.getString(R.string.nip_49)
|
||||
50 -> context.getString(R.string.nip_50)
|
||||
51 -> context.getString(R.string.nip_51)
|
||||
52 -> context.getString(R.string.nip_52)
|
||||
53 -> context.getString(R.string.nip_53)
|
||||
54 -> context.getString(R.string.nip_54)
|
||||
55 -> context.getString(R.string.nip_55)
|
||||
56 -> context.getString(R.string.nip_56)
|
||||
57 -> context.getString(R.string.nip_57)
|
||||
58 -> context.getString(R.string.nip_58)
|
||||
59 -> context.getString(R.string.nip_59)
|
||||
65 -> context.getString(R.string.nip_65)
|
||||
70 -> context.getString(R.string.nip_70)
|
||||
71 -> context.getString(R.string.nip_71)
|
||||
72 -> context.getString(R.string.nip_72)
|
||||
75 -> context.getString(R.string.nip_75)
|
||||
78 -> context.getString(R.string.nip_78)
|
||||
84 -> context.getString(R.string.nip_84)
|
||||
89 -> context.getString(R.string.nip_89)
|
||||
90 -> context.getString(R.string.nip_90)
|
||||
92 -> context.getString(R.string.nip_92)
|
||||
94 -> context.getString(R.string.nip_94)
|
||||
96 -> context.getString(R.string.nip_96)
|
||||
98 -> context.getString(R.string.nip_98)
|
||||
99 -> context.getString(R.string.nip_99)
|
||||
else -> {
|
||||
if (shouldReturnEmpty) {
|
||||
""
|
||||
} else {
|
||||
context.getString(R.string.nip_kind, permission.kind.toString())
|
||||
}
|
||||
fun nipToLocalizedString(context: Context, permission: Permission, shouldReturnEmpty: Boolean = false): String = when (permission.kind) {
|
||||
1 -> context.getString(R.string.nip_01)
|
||||
2 -> context.getString(R.string.nip_02)
|
||||
3 -> context.getString(R.string.nip_03)
|
||||
4 -> context.getString(R.string.nip_04)
|
||||
5 -> context.getString(R.string.nip_05)
|
||||
6 -> context.getString(R.string.nip_06)
|
||||
7 -> context.getString(R.string.nip_07)
|
||||
8 -> context.getString(R.string.nip_08)
|
||||
9 -> context.getString(R.string.nip_09)
|
||||
10 -> context.getString(R.string.nip_10)
|
||||
11 -> context.getString(R.string.nip_11)
|
||||
13 -> context.getString(R.string.nip_13)
|
||||
14 -> context.getString(R.string.nip_14)
|
||||
15 -> context.getString(R.string.nip_15)
|
||||
17 -> context.getString(R.string.nip_17)
|
||||
18 -> context.getString(R.string.nip_18)
|
||||
19 -> context.getString(R.string.nip_19)
|
||||
21 -> context.getString(R.string.nip_21)
|
||||
23 -> context.getString(R.string.nip_23)
|
||||
24 -> context.getString(R.string.nip_24)
|
||||
25 -> context.getString(R.string.nip_25)
|
||||
26 -> context.getString(R.string.nip_26)
|
||||
27 -> context.getString(R.string.nip_27)
|
||||
28 -> context.getString(R.string.nip_28)
|
||||
29 -> context.getString(R.string.nip_29)
|
||||
30 -> context.getString(R.string.nip_30)
|
||||
31 -> context.getString(R.string.nip_31)
|
||||
32 -> context.getString(R.string.nip_32)
|
||||
34 -> context.getString(R.string.nip_34)
|
||||
35 -> context.getString(R.string.nip_35)
|
||||
36 -> context.getString(R.string.nip_36)
|
||||
38 -> context.getString(R.string.nip_38)
|
||||
39 -> context.getString(R.string.nip_39)
|
||||
40 -> context.getString(R.string.nip_40)
|
||||
42 -> context.getString(R.string.nip_42)
|
||||
44 -> context.getString(R.string.nip_44)
|
||||
45 -> context.getString(R.string.nip_45)
|
||||
46 -> context.getString(R.string.nip_46)
|
||||
47 -> context.getString(R.string.nip_47)
|
||||
48 -> context.getString(R.string.nip_48)
|
||||
49 -> context.getString(R.string.nip_49)
|
||||
50 -> context.getString(R.string.nip_50)
|
||||
51 -> context.getString(R.string.nip_51)
|
||||
52 -> context.getString(R.string.nip_52)
|
||||
53 -> context.getString(R.string.nip_53)
|
||||
54 -> context.getString(R.string.nip_54)
|
||||
55 -> context.getString(R.string.nip_55)
|
||||
56 -> context.getString(R.string.nip_56)
|
||||
57 -> context.getString(R.string.nip_57)
|
||||
58 -> context.getString(R.string.nip_58)
|
||||
59 -> context.getString(R.string.nip_59)
|
||||
65 -> context.getString(R.string.nip_65)
|
||||
70 -> context.getString(R.string.nip_70)
|
||||
71 -> context.getString(R.string.nip_71)
|
||||
72 -> context.getString(R.string.nip_72)
|
||||
75 -> context.getString(R.string.nip_75)
|
||||
78 -> context.getString(R.string.nip_78)
|
||||
84 -> context.getString(R.string.nip_84)
|
||||
89 -> context.getString(R.string.nip_89)
|
||||
90 -> context.getString(R.string.nip_90)
|
||||
92 -> context.getString(R.string.nip_92)
|
||||
94 -> context.getString(R.string.nip_94)
|
||||
96 -> context.getString(R.string.nip_96)
|
||||
98 -> context.getString(R.string.nip_98)
|
||||
99 -> context.getString(R.string.nip_99)
|
||||
else -> {
|
||||
if (shouldReturnEmpty) {
|
||||
""
|
||||
} else {
|
||||
context.getString(R.string.nip_kind, permission.kind.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,13 +37,12 @@ class EncryptedBlobInterceptor(
|
||||
return newBuilder().body(newBody).build()
|
||||
}
|
||||
|
||||
private fun Response.decryptOrNull(cipher: NostrCipher): Response? =
|
||||
try {
|
||||
decrypt(cipher)
|
||||
} catch (e: Exception) {
|
||||
Log.w("EncryptedBlobInterceptor", "Failed to decrypt", e)
|
||||
null
|
||||
}
|
||||
private fun Response.decryptOrNull(cipher: NostrCipher): Response? = try {
|
||||
decrypt(cipher)
|
||||
} catch (e: Exception) {
|
||||
Log.w("EncryptedBlobInterceptor", "Failed to decrypt", e)
|
||||
null
|
||||
}
|
||||
|
||||
private fun Response.decryptOrNullWithErrorCorrection(cipher: NostrCipher): Response? {
|
||||
return decryptOrNull(cipher) ?: return if (cipher is AESGCM) {
|
||||
|
||||
@@ -95,18 +95,17 @@ object HttpClientManager {
|
||||
.build()
|
||||
}
|
||||
|
||||
fun getHttpClient(useProxy: Boolean): OkHttpClient =
|
||||
if (useProxy) {
|
||||
if (defaultHttpClient == null) {
|
||||
defaultHttpClient = buildHttpClient(currentProxy, defaultTimeout)
|
||||
}
|
||||
defaultHttpClient!!
|
||||
} else {
|
||||
if (defaultHttpClientWithoutProxy == null) {
|
||||
defaultHttpClientWithoutProxy = buildHttpClient(null, defaultTimeout)
|
||||
}
|
||||
defaultHttpClientWithoutProxy!!
|
||||
fun getHttpClient(useProxy: Boolean): OkHttpClient = if (useProxy) {
|
||||
if (defaultHttpClient == null) {
|
||||
defaultHttpClient = buildHttpClient(currentProxy, defaultTimeout)
|
||||
}
|
||||
defaultHttpClient!!
|
||||
} else {
|
||||
if (defaultHttpClientWithoutProxy == null) {
|
||||
defaultHttpClientWithoutProxy = buildHttpClient(null, defaultTimeout)
|
||||
}
|
||||
defaultHttpClientWithoutProxy!!
|
||||
}
|
||||
|
||||
fun setDefaultProxyOnPort(port: Int) {
|
||||
setDefaultProxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", port)))
|
||||
|
||||
@@ -1,17 +1,81 @@
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.anggrayudi.storage.extension.toDocumentFile
|
||||
import com.anggrayudi.storage.file.openInputStream
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.AccountExportData
|
||||
import com.greenart7c3.nostrsigner.models.BulkAccountExport
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNsec
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip49PrivKeyEnc.Nip49
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object AccountExportService {
|
||||
suspend fun importAccounts(
|
||||
uri: Uri,
|
||||
password: String,
|
||||
onLoading: (isLoading: Boolean) -> Unit,
|
||||
onText: (text: String) -> Unit,
|
||||
onFinish: () -> Unit,
|
||||
) {
|
||||
onLoading(true)
|
||||
try {
|
||||
val file = uri.toDocumentFile(Amber.instance) ?: return
|
||||
val name = file.name
|
||||
if (name?.endsWith(".jsonl") == false) {
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
Toast.makeText(Amber.instance, "File is not a Amber backup", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (file.isFile) {
|
||||
file.openInputStream(Amber.instance).use { oi ->
|
||||
oi?.bufferedReader().use { reader ->
|
||||
reader?.readLines()?.forEachIndexed { index, line ->
|
||||
try {
|
||||
onText("Importing account ${index + 1}")
|
||||
|
||||
val accountData = AccountExportData.fromJson(line)
|
||||
val nsec = Nip49().decrypt(accountData.nsec, password)
|
||||
val signer = NostrSignerInternal(KeyPair(privKey = Hex.decode(nsec)))
|
||||
val account = Account(
|
||||
signer = signer,
|
||||
name = MutableStateFlow(accountData.name),
|
||||
picture = MutableStateFlow(accountData.picture ?: ""),
|
||||
signPolicy = accountData.signPolicy,
|
||||
seedWords = signer.signerSync.decrypt(accountData.seedWords, signer.keyPair.pubKey.toHexKey()).split(" ").toSet(),
|
||||
didBackup = accountData.didBackup,
|
||||
)
|
||||
LocalPreferences.switchToAccount(Amber.instance, accountData.npub)
|
||||
LocalPreferences.updatePrefsForLogin(Amber.instance, account)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
|
||||
Log.e("Amber", "Error importing account", e)
|
||||
}
|
||||
}
|
||||
onFinish()
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
onText("")
|
||||
onLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all accounts as an encrypted JSON file
|
||||
*
|
||||
@@ -33,27 +97,19 @@ object AccountExportService {
|
||||
return@withContext Result.failure(Exception("No accounts to export"))
|
||||
}
|
||||
|
||||
val exportedAccounts = mutableListOf<AccountExportData>()
|
||||
|
||||
var data = ""
|
||||
accountInfos.forEachIndexed { index, accountInfo ->
|
||||
onProgress(index + 1, totalAccounts)
|
||||
|
||||
val account = LocalPreferences.loadFromEncryptedStorage(context, accountInfo.npub)
|
||||
account?.let {
|
||||
val exportData = accountToExportData(it, password)
|
||||
exportedAccounts.add(exportData)
|
||||
data += "${AccountExportData.toJson(exportData)}\n"
|
||||
}
|
||||
}
|
||||
|
||||
val bulkExport = BulkAccountExport(
|
||||
accountCount = exportedAccounts.size,
|
||||
accounts = exportedAccounts,
|
||||
)
|
||||
|
||||
val json = BulkAccountExport.toJson(bulkExport)
|
||||
|
||||
// Each private key is already encrypted with NIP-49 inside the JSON
|
||||
Result.success(json)
|
||||
Result.success(data)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
@@ -66,16 +122,7 @@ object AccountExportService {
|
||||
val privKey = account.signer.keyPair.privKey
|
||||
val encrypedSeedWords = if (account.seedWords.isNotEmpty()) account.signer.signerSync.nip44Encrypt(account.seedWords.joinToString(" "), account.hexKey) else ""
|
||||
|
||||
// Encrypt private key with password (NIP-49) or use nsec if no password
|
||||
val encryptedNsec = if (privKey != null) {
|
||||
if (password.isNotBlank()) {
|
||||
Nip49().encrypt(privKey.toHexKey(), password)
|
||||
} else {
|
||||
privKey.toNsec()
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val encryptedNsec = Nip49().encrypt(privKey!!.toHexKey(), password)
|
||||
|
||||
return AccountExportData(
|
||||
npub = account.npub,
|
||||
|
||||
@@ -31,39 +31,37 @@ object AmberUtils {
|
||||
type: SignerType,
|
||||
account: Account,
|
||||
pubKey: HexKey,
|
||||
): String? {
|
||||
return when (type) {
|
||||
SignerType.DECRYPT_ZAP_EVENT -> {
|
||||
decryptZapEvent(data, account)
|
||||
}
|
||||
SignerType.NIP04_DECRYPT -> {
|
||||
Nip04.decrypt(
|
||||
data,
|
||||
account.signer.keyPair.privKey!!,
|
||||
Hex.decode(pubKey),
|
||||
)
|
||||
}
|
||||
SignerType.NIP04_ENCRYPT -> {
|
||||
Nip04.encrypt(
|
||||
data,
|
||||
account.signer.keyPair.privKey!!,
|
||||
Hex.decode(pubKey),
|
||||
)
|
||||
}
|
||||
SignerType.NIP44_ENCRYPT -> {
|
||||
Nip44.encrypt(
|
||||
data,
|
||||
account.signer.keyPair.privKey!!,
|
||||
pubKey.hexToByteArray(),
|
||||
).encodePayload()
|
||||
}
|
||||
else -> {
|
||||
Nip44.decrypt(
|
||||
data,
|
||||
account.signer.keyPair.privKey!!,
|
||||
pubKey.hexToByteArray(),
|
||||
)
|
||||
}
|
||||
): String? = when (type) {
|
||||
SignerType.DECRYPT_ZAP_EVENT -> {
|
||||
decryptZapEvent(data, account)
|
||||
}
|
||||
SignerType.NIP04_DECRYPT -> {
|
||||
Nip04.decrypt(
|
||||
data,
|
||||
account.signer.keyPair.privKey!!,
|
||||
Hex.decode(pubKey),
|
||||
)
|
||||
}
|
||||
SignerType.NIP04_ENCRYPT -> {
|
||||
Nip04.encrypt(
|
||||
data,
|
||||
account.signer.keyPair.privKey!!,
|
||||
Hex.decode(pubKey),
|
||||
)
|
||||
}
|
||||
SignerType.NIP44_ENCRYPT -> {
|
||||
Nip44.encrypt(
|
||||
data,
|
||||
account.signer.keyPair.privKey!!,
|
||||
pubKey.hexToByteArray(),
|
||||
).encodePayload()
|
||||
}
|
||||
else -> {
|
||||
Nip44.decrypt(
|
||||
data,
|
||||
account.signer.keyPair.privKey!!,
|
||||
pubKey.hexToByteArray(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,6 +250,4 @@ fun String.toShortenHex(): String {
|
||||
fun isPrivateEvent(
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
): Boolean {
|
||||
return kind == LnZapRequestEvent.KIND && tags.any { t -> t.size > 1 && t[0] == "anon" }
|
||||
}
|
||||
): Boolean = kind == LnZapRequestEvent.KIND && tags.any { t -> t.size > 1 && t[0] == "anon" }
|
||||
|
||||
@@ -49,9 +49,7 @@ object BunkerRequestUtils {
|
||||
state.tryEmit(state.value.filter { it.request.id != id })
|
||||
}
|
||||
|
||||
fun getBunkerRequests(): List<AmberBunkerRequest> {
|
||||
return state.value
|
||||
}
|
||||
fun getBunkerRequests(): List<AmberBunkerRequest> = state.value
|
||||
|
||||
suspend fun sendBunkerResponse(
|
||||
context: Context,
|
||||
@@ -192,34 +190,30 @@ object BunkerRequestUtils {
|
||||
onLoading(false)
|
||||
}
|
||||
|
||||
fun getTypeFromBunker(bunkerRequest: BunkerRequest): SignerType {
|
||||
return when (bunkerRequest.method) {
|
||||
"sign_message" -> SignerType.SIGN_MESSAGE
|
||||
"connect" -> SignerType.CONNECT
|
||||
"sign_event" -> SignerType.SIGN_EVENT
|
||||
"get_public_key" -> SignerType.GET_PUBLIC_KEY
|
||||
"nip04_encrypt" -> SignerType.NIP04_ENCRYPT
|
||||
"nip04_decrypt" -> SignerType.NIP04_DECRYPT
|
||||
"nip44_encrypt" -> SignerType.NIP44_ENCRYPT
|
||||
"nip44_decrypt" -> SignerType.NIP44_DECRYPT
|
||||
"decrypt_zap_event" -> SignerType.DECRYPT_ZAP_EVENT
|
||||
"ping" -> SignerType.PING
|
||||
else -> SignerType.INVALID
|
||||
}
|
||||
fun getTypeFromBunker(bunkerRequest: BunkerRequest): SignerType = when (bunkerRequest.method) {
|
||||
"sign_message" -> SignerType.SIGN_MESSAGE
|
||||
"connect" -> SignerType.CONNECT
|
||||
"sign_event" -> SignerType.SIGN_EVENT
|
||||
"get_public_key" -> SignerType.GET_PUBLIC_KEY
|
||||
"nip04_encrypt" -> SignerType.NIP04_ENCRYPT
|
||||
"nip04_decrypt" -> SignerType.NIP04_DECRYPT
|
||||
"nip44_encrypt" -> SignerType.NIP44_ENCRYPT
|
||||
"nip44_decrypt" -> SignerType.NIP44_DECRYPT
|
||||
"decrypt_zap_event" -> SignerType.DECRYPT_ZAP_EVENT
|
||||
"ping" -> SignerType.PING
|
||||
else -> SignerType.INVALID
|
||||
}
|
||||
|
||||
fun getDataFromBunker(bunkerRequest: BunkerRequest): String {
|
||||
return when (bunkerRequest.method) {
|
||||
"sign_message" -> bunkerRequest.params.first()
|
||||
"connect" -> "ack"
|
||||
"sign_event" -> {
|
||||
val amberEvent = AmberEvent.fromJson(bunkerRequest.params.first())
|
||||
AmberEvent.toEvent(amberEvent).toJson()
|
||||
}
|
||||
"nip04_encrypt", "nip04_decrypt", "nip44_encrypt", "nip44_decrypt", "decrypt_zap_event" -> bunkerRequest.params.getOrElse(1) { "" }
|
||||
"ping" -> "pong"
|
||||
else -> ""
|
||||
fun getDataFromBunker(bunkerRequest: BunkerRequest): String = when (bunkerRequest.method) {
|
||||
"sign_message" -> bunkerRequest.params.first()
|
||||
"connect" -> "ack"
|
||||
"sign_event" -> {
|
||||
val amberEvent = AmberEvent.fromJson(bunkerRequest.params.first())
|
||||
AmberEvent.toEvent(amberEvent).toJson()
|
||||
}
|
||||
"nip04_encrypt", "nip04_decrypt", "nip44_encrypt", "nip44_decrypt", "decrypt_zap_event" -> bunkerRequest.params.getOrElse(1) { "" }
|
||||
"ping" -> "pong"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
fun sendResult(
|
||||
|
||||
@@ -4,8 +4,7 @@ import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
|
||||
class BunkerResponseWorker(appContext: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(appContext, workerParams) {
|
||||
class BunkerResponseWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
@@ -10,8 +10,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils.ONE_WEEK
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
class ClearLogsWorker(appContext: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(appContext, workerParams) {
|
||||
class ClearLogsWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
LocalPreferences.allSavedAccounts(Amber.instance).forEach {
|
||||
|
||||
@@ -83,9 +83,7 @@ class ConnectivityService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
override fun onBind(intent: Intent): IBinder? = null
|
||||
|
||||
override fun onCreate() {
|
||||
Log.d(Amber.TAG, "onCreate ConnectivityService isStarted: $isStarted")
|
||||
|
||||
@@ -513,7 +513,5 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun notificationManager(): NotificationManager {
|
||||
return ContextCompat.getSystemService(applicationContext, NotificationManager::class.java) as NotificationManager
|
||||
}
|
||||
fun notificationManager(): NotificationManager = ContextCompat.getSystemService(applicationContext, NotificationManager::class.java) as NotificationManager
|
||||
}
|
||||
|
||||
@@ -16,9 +16,7 @@ import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestConnect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object NostrConnectUtils {
|
||||
private fun metaDataFromJson(json: String): BunkerMetadata {
|
||||
return BunkerMetadata.mapper.readValue(json, BunkerMetadata::class.java)
|
||||
}
|
||||
private fun metaDataFromJson(json: String): BunkerMetadata = BunkerMetadata.mapper.readValue(json, BunkerMetadata::class.java)
|
||||
|
||||
fun getIntentFromNostrConnect(
|
||||
intent: Intent,
|
||||
|
||||
+14
-16
@@ -36,12 +36,11 @@ class CrashReportCache(
|
||||
|
||||
private fun deleteReport() = appContext.deleteFile(STACK_TRACE_FILENAME)
|
||||
|
||||
private fun inputStreamOrNull(): FileInputStream? =
|
||||
try {
|
||||
appContext.openFileInput(STACK_TRACE_FILENAME)
|
||||
} catch (_: FileNotFoundException) {
|
||||
null
|
||||
}
|
||||
private fun inputStreamOrNull(): FileInputStream? = try {
|
||||
appContext.openFileInput(STACK_TRACE_FILENAME)
|
||||
} catch (_: FileNotFoundException) {
|
||||
null
|
||||
}
|
||||
|
||||
fun writeReport(report: String) {
|
||||
val trace = outputStream()
|
||||
@@ -49,15 +48,14 @@ class CrashReportCache(
|
||||
trace.close()
|
||||
}
|
||||
|
||||
suspend fun loadAndDelete(): String? =
|
||||
withContext(Dispatchers.IO) {
|
||||
val stack =
|
||||
inputStreamOrNull()?.use { inStream ->
|
||||
InputStreamReader(inStream).use { reader ->
|
||||
reader.readText()
|
||||
}
|
||||
suspend fun loadAndDelete(): String? = withContext(Dispatchers.IO) {
|
||||
val stack =
|
||||
inputStreamOrNull()?.use { inStream ->
|
||||
InputStreamReader(inStream).use { reader ->
|
||||
reader.readText()
|
||||
}
|
||||
deleteReport()
|
||||
stack
|
||||
}
|
||||
}
|
||||
deleteReport()
|
||||
stack
|
||||
}
|
||||
}
|
||||
|
||||
+57
-58
@@ -24,70 +24,69 @@ import android.os.Build
|
||||
import com.greenart7c3.nostrsigner.BuildConfig
|
||||
|
||||
class ReportAssembler {
|
||||
fun buildReport(e: Throwable): String =
|
||||
buildString {
|
||||
append(e.javaClass.simpleName)
|
||||
append(": ")
|
||||
appendLine(BuildConfig.VERSION_NAME + "-" + BuildConfig.FLAVOR.uppercase())
|
||||
appendLine()
|
||||
fun buildReport(e: Throwable): String = buildString {
|
||||
append(e.javaClass.simpleName)
|
||||
append(": ")
|
||||
appendLine(BuildConfig.VERSION_NAME + "-" + BuildConfig.FLAVOR.uppercase())
|
||||
appendLine()
|
||||
|
||||
// Device and Product Information
|
||||
appendLine("| Prop | Value |")
|
||||
appendLine("|------|-------|")
|
||||
append("| Manuf |")
|
||||
append(Build.MANUFACTURER)
|
||||
appendLine(" |")
|
||||
append("| Model |")
|
||||
append(Build.MODEL)
|
||||
appendLine(" |")
|
||||
append("| Prod |")
|
||||
append(Build.PRODUCT)
|
||||
appendLine(" |")
|
||||
// Device and Product Information
|
||||
appendLine("| Prop | Value |")
|
||||
appendLine("|------|-------|")
|
||||
append("| Manuf |")
|
||||
append(Build.MANUFACTURER)
|
||||
appendLine(" |")
|
||||
append("| Model |")
|
||||
append(Build.MODEL)
|
||||
appendLine(" |")
|
||||
append("| Prod |")
|
||||
append(Build.PRODUCT)
|
||||
appendLine(" |")
|
||||
|
||||
// OS Information
|
||||
append("| Android |")
|
||||
append(Build.VERSION.RELEASE)
|
||||
appendLine(" |")
|
||||
append("| SDK Int |")
|
||||
append(Build.VERSION.SDK_INT.toString())
|
||||
appendLine(" |")
|
||||
// OS Information
|
||||
append("| Android |")
|
||||
append(Build.VERSION.RELEASE)
|
||||
appendLine(" |")
|
||||
append("| SDK Int |")
|
||||
append(Build.VERSION.SDK_INT.toString())
|
||||
appendLine(" |")
|
||||
|
||||
// Hardware Information
|
||||
append("| Brand |")
|
||||
append(Build.BRAND)
|
||||
appendLine(" |")
|
||||
append("| Hardware |")
|
||||
append(Build.HARDWARE)
|
||||
appendLine(" |")
|
||||
// Hardware Information
|
||||
append("| Brand |")
|
||||
append(Build.BRAND)
|
||||
appendLine(" |")
|
||||
append("| Hardware |")
|
||||
append(Build.HARDWARE)
|
||||
appendLine(" |")
|
||||
|
||||
// Other Useful Information
|
||||
append("| Device | ")
|
||||
append(Build.DEVICE)
|
||||
appendLine(" |")
|
||||
append("| Host | ")
|
||||
append(Build.HOST)
|
||||
appendLine(" |")
|
||||
append("| User | ")
|
||||
append(Build.USER)
|
||||
appendLine(" |")
|
||||
appendLine()
|
||||
// Other Useful Information
|
||||
append("| Device | ")
|
||||
append(Build.DEVICE)
|
||||
appendLine(" |")
|
||||
append("| Host | ")
|
||||
append(Build.HOST)
|
||||
appendLine(" |")
|
||||
append("| User | ")
|
||||
append(Build.USER)
|
||||
appendLine(" |")
|
||||
appendLine()
|
||||
|
||||
appendLine("```")
|
||||
appendLine(e.toString())
|
||||
e.stackTrace.forEach {
|
||||
append(" ")
|
||||
appendLine("```")
|
||||
appendLine(e.toString())
|
||||
e.stackTrace.forEach {
|
||||
append(" ")
|
||||
appendLine(it.toString())
|
||||
}
|
||||
val cause = e.cause
|
||||
if (cause != null) {
|
||||
appendLine("\n\nCause:")
|
||||
append(" ")
|
||||
appendLine(cause.toString())
|
||||
cause.stackTrace.forEach {
|
||||
append(" ")
|
||||
appendLine(it.toString())
|
||||
}
|
||||
val cause = e.cause
|
||||
if (cause != null) {
|
||||
appendLine("\n\nCause:")
|
||||
append(" ")
|
||||
appendLine(cause.toString())
|
||||
cause.stackTrace.forEach {
|
||||
append(" ")
|
||||
appendLine(it.toString())
|
||||
}
|
||||
}
|
||||
appendLine("```")
|
||||
}
|
||||
appendLine("```")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,24 +59,21 @@ open class AmberEvent(
|
||||
|
||||
private class EventManualDeserializer {
|
||||
companion object {
|
||||
fun fromJson(jsonObject: JsonNode): AmberEvent =
|
||||
AmberEvent(
|
||||
id = jsonObject.get("id")?.asText() ?: "",
|
||||
pubKey = jsonObject.get("pubkey")?.asText()?.intern() ?: "",
|
||||
createdAt = jsonObject.get("created_at")?.asLong() ?: TimeUtils.now(),
|
||||
kind = jsonObject.get("kind").asInt(),
|
||||
tags = jsonObject.get("tags")?.toTypedArray {
|
||||
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
|
||||
} ?: emptyArray(),
|
||||
content = jsonObject.get("content").asText(),
|
||||
sig = jsonObject.get("sig")?.asText() ?: "",
|
||||
)
|
||||
fun fromJson(jsonObject: JsonNode): AmberEvent = AmberEvent(
|
||||
id = jsonObject.get("id")?.asText() ?: "",
|
||||
pubKey = jsonObject.get("pubkey")?.asText()?.intern() ?: "",
|
||||
createdAt = jsonObject.get("created_at")?.asLong() ?: TimeUtils.now(),
|
||||
kind = jsonObject.get("kind").asInt(),
|
||||
tags = jsonObject.get("tags")?.toTypedArray {
|
||||
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
|
||||
} ?: emptyArray(),
|
||||
content = jsonObject.get("content").asText(),
|
||||
sig = jsonObject.get("sig")?.asText() ?: "",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun relay(): String {
|
||||
return tags.filter { it.size > 1 && it[0] == "relay" }.map { it[1] }.first()
|
||||
}
|
||||
fun relay(): String = tags.filter { it.size > 1 && it[0] == "relay" }.map { it[1] }.first()
|
||||
|
||||
fun verifiedFollowKeySet(): Set<HexKey> = tags.mapNotNullTo(HashSet(), ContactTag::parseValidKey)
|
||||
|
||||
@@ -94,20 +91,16 @@ open class AmberEvent(
|
||||
|
||||
fun fromJson(json: String): AmberEvent = mapper.readValue(json, AmberEvent::class.java)
|
||||
|
||||
fun toEvent(amberEvent: AmberEvent): Event {
|
||||
return Event(
|
||||
amberEvent.id,
|
||||
amberEvent.pubKey,
|
||||
amberEvent.createdAt,
|
||||
amberEvent.kind,
|
||||
amberEvent.tags,
|
||||
amberEvent.content,
|
||||
amberEvent.sig,
|
||||
)
|
||||
}
|
||||
fun toEvent(amberEvent: AmberEvent): Event = Event(
|
||||
amberEvent.id,
|
||||
amberEvent.pubKey,
|
||||
amberEvent.createdAt,
|
||||
amberEvent.kind,
|
||||
amberEvent.tags,
|
||||
amberEvent.content,
|
||||
amberEvent.sig,
|
||||
)
|
||||
|
||||
fun relay(event: Event): String {
|
||||
return event.tags.filter { it.size > 1 && it[0] == "relay" }.map { it[1] }.first()
|
||||
}
|
||||
fun relay(event: Event): String = event.tags.filter { it.size > 1 && it[0] == "relay" }.map { it[1] }.first()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() {
|
||||
startUI(account, null)
|
||||
}
|
||||
|
||||
private fun startUI(
|
||||
fun startUI(
|
||||
account: Account,
|
||||
route: String?,
|
||||
) {
|
||||
|
||||
@@ -199,11 +199,9 @@ fun ApplicationsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.isZapstoreInstalled(): Boolean {
|
||||
return try {
|
||||
packageManager.getPackageInfo("dev.zapstore.app", 0)
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
fun Context.isZapstoreInstalled(): Boolean = try {
|
||||
packageManager.getPackageInfo("dev.zapstore.app", 0)
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
@@ -34,13 +36,17 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -58,6 +64,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -83,6 +90,7 @@ import androidx.compose.ui.text.toLowerCase
|
||||
import androidx.compose.ui.text.toUpperCase
|
||||
import androidx.compose.ui.text.withLink
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
@@ -92,10 +100,12 @@ import androidx.navigation.compose.composable
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.service.AccountExportService
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberElevatedButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.IconRow
|
||||
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
import com.greenart7c3.nostrsigner.ui.theme.RichTextDefaults
|
||||
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
|
||||
import com.halilibo.richtext.commonmark.MarkdownParseOptions
|
||||
@@ -110,17 +120,131 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@SuppressLint("ConfigurationScreenWidthHeight")
|
||||
@Composable
|
||||
fun MainPage(
|
||||
scope: CoroutineScope,
|
||||
navController: NavController,
|
||||
accountViewModel: AccountStateViewModel,
|
||||
) {
|
||||
var isLoading by remember { mutableStateOf(false) }
|
||||
var text by remember { mutableStateOf("") }
|
||||
var password by remember { mutableStateOf(TextFieldValue()) }
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidthDp = configuration.screenWidthDp.dp
|
||||
val percentage = (screenWidthDp * 0.93f)
|
||||
val verticalPadding = (screenWidthDp - percentage)
|
||||
val context = LocalContext.current
|
||||
var shouldShowBottomSheet by remember { mutableStateOf(false) }
|
||||
val selectFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
shouldShowBottomSheet = false
|
||||
Amber.instance.applicationIOScope.launch {
|
||||
uri?.let {
|
||||
val hasAccounts = LocalPreferences.allAccounts(Amber.instance).isNotEmpty()
|
||||
AccountExportService.importAccounts(
|
||||
uri = uri,
|
||||
password = password.text,
|
||||
onText = {
|
||||
text = it
|
||||
},
|
||||
onLoading = {
|
||||
isLoading = it
|
||||
},
|
||||
onFinish = {
|
||||
Amber.instance.applicationIOScope.launch {
|
||||
if (hasAccounts) {
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
navController.navigate(Route.Applications.route) {
|
||||
popUpTo(0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val accounts = LocalPreferences.allAccounts(Amber.instance)
|
||||
if (accounts.isNotEmpty()) {
|
||||
val account = LocalPreferences.loadFromEncryptedStorage(Amber.instance, accounts.first().npub)
|
||||
account?.let {
|
||||
accountViewModel.startUI(account, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
Amber.instance.notificationSubscription.updateFilter()
|
||||
Amber.instance.profileSubscription.updateFilter()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val sheetState =
|
||||
rememberModalBottomSheetState(
|
||||
confirmValueChange = { it != SheetValue.PartiallyExpanded },
|
||||
skipPartiallyExpanded = true,
|
||||
)
|
||||
|
||||
if (shouldShowBottomSheet) {
|
||||
var showCharsPassword by remember { mutableStateOf(false) }
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = {
|
||||
shouldShowBottomSheet = false
|
||||
},
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalDensity provides Density(
|
||||
LocalDensity.current.density,
|
||||
1f,
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
Modifier.padding(8.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.semantics {
|
||||
contentType = ContentType.Password
|
||||
},
|
||||
value = password,
|
||||
onValueChange = {
|
||||
password = it
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Next,
|
||||
),
|
||||
label = {
|
||||
Text(text = stringResource(R.string.encryption_password))
|
||||
},
|
||||
placeholder = {
|
||||
Text(text = stringResource(R.string.enter_strong_password))
|
||||
},
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { showCharsPassword = !showCharsPassword }) {
|
||||
Icon(
|
||||
imageVector = if (showCharsPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
|
||||
contentDescription = if (showCharsPassword) {
|
||||
stringResource(R.string.hide_password)
|
||||
} else {
|
||||
stringResource(R.string.show_password)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
visualTransformation = if (showCharsPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
)
|
||||
AmberButton(
|
||||
onClick = {
|
||||
selectFileLauncher.launch("*/*")
|
||||
},
|
||||
text = stringResource(R.string.recover_from_backup),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold {
|
||||
Column(
|
||||
@@ -130,79 +254,93 @@ fun MainPage(
|
||||
.padding(horizontal = verticalPadding)
|
||||
.padding(top = verticalPadding * 1.5f),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.app_name_release).toUpperCase(Locale.current),
|
||||
fontSize = 36.sp,
|
||||
if (isLoading) {
|
||||
CenterCircularProgressIndicator(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
text = text,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.a_nostr_secure_signer),
|
||||
fontSize = 16.sp,
|
||||
color = Color(0xFFC98500),
|
||||
)
|
||||
|
||||
Image(
|
||||
modifier = Modifier.padding(top = 20.dp, bottom = 20.dp),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.frame),
|
||||
contentDescription = "Logo",
|
||||
)
|
||||
|
||||
} else {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
AmberElevatedButton(
|
||||
contentColor = Color(0xFF4C4C4C),
|
||||
textColor = MaterialTheme.colorScheme.primary,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
navController.navigate("loginPage")
|
||||
}
|
||||
},
|
||||
text = stringResource(R.string.add_a_key),
|
||||
Text(
|
||||
text = stringResource(R.string.app_name_release).toUpperCase(Locale.current),
|
||||
fontSize = 36.sp,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.a_nostr_secure_signer),
|
||||
fontSize = 16.sp,
|
||||
color = Color(0xFFC98500),
|
||||
)
|
||||
|
||||
AmberButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
navController.navigate("create")
|
||||
}
|
||||
},
|
||||
text = stringResource(R.string.generate_a_new_key),
|
||||
Image(
|
||||
modifier = Modifier.padding(top = 20.dp, bottom = 20.dp),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.frame),
|
||||
contentDescription = "Logo",
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
AmberElevatedButton(
|
||||
contentColor = Color(0xFF4C4C4C),
|
||||
textColor = MaterialTheme.colorScheme.primary,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
navController.navigate("loginPage")
|
||||
}
|
||||
},
|
||||
text = stringResource(R.string.add_a_key),
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 20.dp),
|
||||
color = Color(0xFF8C8C8C),
|
||||
text = buildAnnotatedString {
|
||||
withStyle(
|
||||
style = ParagraphStyle(
|
||||
textAlign = TextAlign.Center,
|
||||
),
|
||||
) {
|
||||
append("Amber is a free and open source project\n")
|
||||
withLink(
|
||||
LinkAnnotation.Url(
|
||||
context.getString(R.string.amber_github_uri),
|
||||
styles = TextLinkStyles(
|
||||
style = SpanStyle(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
),
|
||||
),
|
||||
AmberButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
navController.navigate("create")
|
||||
}
|
||||
},
|
||||
text = stringResource(R.string.generate_a_new_key),
|
||||
)
|
||||
|
||||
AmberButton(
|
||||
onClick = {
|
||||
shouldShowBottomSheet = true
|
||||
},
|
||||
text = stringResource(R.string.recover_from_backup),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 20.dp),
|
||||
color = Color(0xFF8C8C8C),
|
||||
text = buildAnnotatedString {
|
||||
withStyle(
|
||||
style = ParagraphStyle(
|
||||
textAlign = TextAlign.Center,
|
||||
),
|
||||
) {
|
||||
append(context.getString(R.string.check_the_code))
|
||||
append("Amber is a free and open source project\n")
|
||||
withLink(
|
||||
LinkAnnotation.Url(
|
||||
context.getString(R.string.amber_github_uri),
|
||||
styles = TextLinkStyles(
|
||||
style = SpanStyle(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
) {
|
||||
append(context.getString(R.string.check_the_code))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,6 +363,7 @@ fun MainLoginPage(
|
||||
MainPage(
|
||||
scope = scope,
|
||||
navController = navController,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -298,6 +298,7 @@ fun MainScreen(
|
||||
MainPage(
|
||||
scope = scope,
|
||||
navController = navController,
|
||||
accountViewModel = accountStateViewModel,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -119,11 +119,10 @@ private fun createQrCode(contents: String): QRCode {
|
||||
)
|
||||
}
|
||||
|
||||
fun newPath(withPath: Path.() -> Unit) =
|
||||
Path().apply {
|
||||
fillType = PathFillType.EvenOdd
|
||||
withPath(this)
|
||||
}
|
||||
fun newPath(withPath: Path.() -> Unit) = Path().apply {
|
||||
fillType = PathFillType.EvenOdd
|
||||
withPath(this)
|
||||
}
|
||||
|
||||
fun DrawScope.drawAllQrCodeDataBits(
|
||||
bytes: ByteMatrix,
|
||||
|
||||
@@ -14,35 +14,33 @@ fun Modifier.verticalScrollbar(
|
||||
state: ScrollState,
|
||||
scrollbarWidth: Dp = 6.dp,
|
||||
color: Color = Color.LightGray,
|
||||
): Modifier {
|
||||
return this.then(
|
||||
Modifier.drawWithContent {
|
||||
drawContent()
|
||||
): Modifier = this.then(
|
||||
Modifier.drawWithContent {
|
||||
drawContent()
|
||||
|
||||
if (state.maxValue > 0) {
|
||||
val width = size.width
|
||||
val height = size.height
|
||||
if (state.maxValue > 0) {
|
||||
val width = size.width
|
||||
val height = size.height
|
||||
|
||||
val scrollFraction = state.value.toFloat() / state.maxValue.toFloat()
|
||||
val scrollbarHeight = (height * height / (state.maxValue + height))
|
||||
.coerceIn(10.dp.toPx()..height)
|
||||
val scrollFraction = state.value.toFloat() / state.maxValue.toFloat()
|
||||
val scrollbarHeight = (height * height / (state.maxValue + height))
|
||||
.coerceIn(10.dp.toPx()..height)
|
||||
|
||||
val scrollableHeight = height - scrollbarHeight
|
||||
val scrollbarY = scrollableHeight * scrollFraction
|
||||
val scrollableHeight = height - scrollbarHeight
|
||||
val scrollbarY = scrollableHeight * scrollFraction
|
||||
|
||||
drawRoundRect(
|
||||
color = color,
|
||||
topLeft = Offset(
|
||||
x = width - scrollbarWidth.toPx(),
|
||||
y = scrollbarY,
|
||||
),
|
||||
size = Size(
|
||||
width = scrollbarWidth.toPx(),
|
||||
height = scrollbarHeight,
|
||||
),
|
||||
cornerRadius = CornerRadius(scrollbarWidth.toPx() / 2),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
drawRoundRect(
|
||||
color = color,
|
||||
topLeft = Offset(
|
||||
x = width - scrollbarWidth.toPx(),
|
||||
y = scrollbarY,
|
||||
),
|
||||
size = Size(
|
||||
width = scrollbarWidth.toPx(),
|
||||
height = scrollbarHeight,
|
||||
),
|
||||
cornerRadius = CornerRadius(scrollbarWidth.toPx() / 2),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -465,15 +465,13 @@ enum class DeleteAfterType(val screenCode: Int, val resourceId: Int) {
|
||||
ONE_WEEK(5, R.string.one_week),
|
||||
}
|
||||
|
||||
fun parseDeleteAfterType(screenCode: Int): DeleteAfterType {
|
||||
return when (screenCode) {
|
||||
DeleteAfterType.FIVE_MINUTES.screenCode -> DeleteAfterType.FIVE_MINUTES
|
||||
DeleteAfterType.TEN_MINUTES.screenCode -> DeleteAfterType.TEN_MINUTES
|
||||
DeleteAfterType.ONE_HOUR.screenCode -> DeleteAfterType.ONE_HOUR
|
||||
DeleteAfterType.ONE_DAY.screenCode -> DeleteAfterType.ONE_DAY
|
||||
DeleteAfterType.ONE_WEEK.screenCode -> DeleteAfterType.ONE_WEEK
|
||||
else -> DeleteAfterType.NEVER
|
||||
}
|
||||
fun parseDeleteAfterType(screenCode: Int): DeleteAfterType = when (screenCode) {
|
||||
DeleteAfterType.FIVE_MINUTES.screenCode -> DeleteAfterType.FIVE_MINUTES
|
||||
DeleteAfterType.TEN_MINUTES.screenCode -> DeleteAfterType.TEN_MINUTES
|
||||
DeleteAfterType.ONE_HOUR.screenCode -> DeleteAfterType.ONE_HOUR
|
||||
DeleteAfterType.ONE_DAY.screenCode -> DeleteAfterType.ONE_DAY
|
||||
DeleteAfterType.ONE_WEEK.screenCode -> DeleteAfterType.ONE_WEEK
|
||||
else -> DeleteAfterType.NEVER
|
||||
}
|
||||
|
||||
fun deleteAfterToSeconds(deleteAfterType: DeleteAfterType): Long {
|
||||
@@ -495,13 +493,11 @@ enum class BiometricsTimeType(val screenCode: Int, val resourceId: Int) {
|
||||
TEN_MINUTES(3, R.string.ten_minutes),
|
||||
}
|
||||
|
||||
fun parseBiometricsTimeType(screenCode: Int): BiometricsTimeType {
|
||||
return when (screenCode) {
|
||||
BiometricsTimeType.ONE_MINUTE.screenCode -> BiometricsTimeType.ONE_MINUTE
|
||||
BiometricsTimeType.FIVE_MINUTES.screenCode -> BiometricsTimeType.FIVE_MINUTES
|
||||
BiometricsTimeType.TEN_MINUTES.screenCode -> BiometricsTimeType.TEN_MINUTES
|
||||
else -> {
|
||||
BiometricsTimeType.EVERY_TIME
|
||||
}
|
||||
fun parseBiometricsTimeType(screenCode: Int): BiometricsTimeType = when (screenCode) {
|
||||
BiometricsTimeType.ONE_MINUTE.screenCode -> BiometricsTimeType.ONE_MINUTE
|
||||
BiometricsTimeType.FIVE_MINUTES.screenCode -> BiometricsTimeType.FIVE_MINUTES
|
||||
BiometricsTimeType.TEN_MINUTES.screenCode -> BiometricsTimeType.TEN_MINUTES
|
||||
else -> {
|
||||
BiometricsTimeType.EVERY_TIME
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ fun ExportAllAccountsScreen(
|
||||
onClick = {
|
||||
showConfirmationDialog = false
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||
exportLauncher.launch("amber_backup_$timestamp.dat")
|
||||
exportLauncher.launch("amber_backup_$timestamp.jsonl")
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.export))
|
||||
|
||||
@@ -95,7 +95,13 @@ fun AmberNavigationBar(
|
||||
Icon(
|
||||
painterResource(it.icon),
|
||||
it.route,
|
||||
tint = if (selected) Color.Black else if (isSystemInDarkTheme()) Color.White else Color.Black,
|
||||
tint = if (selected) {
|
||||
Color.Black
|
||||
} else if (isSystemInDarkTheme()) {
|
||||
Color.White
|
||||
} else {
|
||||
Color.Black
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -91,16 +91,14 @@ fun SeedWordsPage(
|
||||
val size = seedWords.size
|
||||
val count = size / 2
|
||||
|
||||
val firstColumnWords = seedWords.mapIndexedNotNull {
|
||||
index, word ->
|
||||
val firstColumnWords = seedWords.mapIndexedNotNull { index, word ->
|
||||
if (index < count) {
|
||||
word
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
val secondColumnWords = seedWords.mapIndexedNotNull {
|
||||
index, word ->
|
||||
val secondColumnWords = seedWords.mapIndexedNotNull { index, word ->
|
||||
if (index >= count) {
|
||||
word
|
||||
} else {
|
||||
|
||||
@@ -601,4 +601,6 @@
|
||||
<string name="read_this_text_from_encrypted_content">Read this text from %1$s encrypted content</string>
|
||||
<string name="ping">Ping</string>
|
||||
<string name="response">Response</string>
|
||||
<string name="recover_from_bac">Recover from backup</string>
|
||||
<string name="recover_from_backup">Recover from backup</string>
|
||||
</resources>
|
||||
|
||||
@@ -22,7 +22,7 @@ okhttp = "5.3.1"
|
||||
kotlin = "2.2.21"
|
||||
workRuntimeKtx = "2.11.0"
|
||||
agp = "8.13.1"
|
||||
ktlint = "12.3.0"
|
||||
ktlint = "14.0.1"
|
||||
ksp = "2.3.2"
|
||||
coil = "3.3.0"
|
||||
storage = "2.2.0"
|
||||
|
||||
Reference in New Issue
Block a user