From fd2e1615ead6d77973cd5162013bfedd826090cc Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 26 Nov 2025 11:09:35 -0300 Subject: [PATCH] - Add support for importing accounts from backup - Switch export format to JSONL - Update ktlint to 14.0.1 and apply formatting changes --- .../java/com/greenart7c3/nostrsigner/Amber.kt | 48 +- .../nostrsigner/DataStoreAccess.kt | 12 +- .../nostrsigner/EncryptedStorage.kt | 4 +- .../nostrsigner/LocalPreferences.kt | 60 +-- .../greenart7c3/nostrsigner/MainActivity.kt | 3 +- .../nostrsigner/SecureCryptoHelper.kt | 5 +- .../greenart7c3/nostrsigner/SignerProvider.kt | 23 +- .../nostrsigner/database/AppDatabase.kt | 60 ++- .../nostrsigner/database/ApplicationEntity.kt | 10 +- .../nostrsigner/database/HistoryDatabase.kt | 30 +- .../nostrsigner/database/LogDatabase.kt | 30 +- .../greenart7c3/nostrsigner/models/Account.kt | 4 +- ...{AccountExport.kt => AccountExportData.kt} | 31 +- .../nostrsigner/models/BunkerMetadata.kt | 18 +- .../nostrsigner/models/Permission.kt | 491 +++++++++--------- .../okhttp/EncryptedBlobInterceptor.kt | 13 +- .../nostrsigner/okhttp/HttpClientManager.kt | 21 +- .../service/AccountExportService.kt | 93 +++- .../nostrsigner/service/AmberUtils.kt | 68 ++- .../nostrsigner/service/BunkerRequestUtils.kt | 50 +- .../service/BunkerResponseWorker.kt | 3 +- .../nostrsigner/service/ClearLogsWorker.kt | 3 +- .../service/ConnectivityService.kt | 4 +- .../service/EventNotificationConsumer.kt | 4 +- .../nostrsigner/service/NostrConnectUtils.kt | 4 +- .../service/crashreports/CrashReportCache.kt | 30 +- .../service/crashreports/ReportAssembler.kt | 115 ++-- .../nostrsigner/service/model/AmberEvent.kt | 51 +- .../nostrsigner/ui/AccountStateViewModel.kt | 2 +- .../nostrsigner/ui/ApplicationsScreen.kt | 12 +- .../greenart7c3/nostrsigner/ui/LoginScreen.kt | 261 +++++++--- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 1 + .../nostrsigner/ui/QrCodeDrawer.kt | 9 +- .../nostrsigner/ui/ScrollableModifier.kt | 54 +- .../nostrsigner/ui/SettingsScreen.kt | 30 +- .../ui/actions/ExportAllAccountsScreen.kt | 2 +- .../ui/components/AmberNavigationBar.kt | 8 +- .../ui/components/SeedWordsPage.kt | 6 +- app/src/main/res/values/strings.xml | 2 + gradle/libs.versions.toml | 2 +- 40 files changed, 882 insertions(+), 795 deletions(-) rename app/src/main/java/com/greenart7c3/nostrsigner/models/{AccountExport.kt => AccountExportData.kt} (66%) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/Amber.kt b/app/src/main/java/com/greenart7c3/nostrsigner/Amber.kt index 7b737b8e..5a42d421 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/Amber.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/Amber.kt @@ -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? = null val crashReportCache: CrashReportCache by lazy { CrashReportCache(this.applicationContext) } @@ -68,9 +70,7 @@ class Amber : Application(), LifecycleObserver { mainActivityRef = WeakReference(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() diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/DataStoreAccess.kt b/app/src/main/java/com/greenart7c3/nostrsigner/DataStoreAccess.kt index 84959dde..ef6ed44d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/DataStoreAccess.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/DataStoreAccess.kt @@ -17,13 +17,11 @@ import kotlinx.coroutines.flow.first object DataStoreAccess { private val storeCache = ConcurrentHashMap>() - private fun getDataStore(context: Context, npub: String): DataStore { - 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 = 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") diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/EncryptedStorage.kt b/app/src/main/java/com/greenart7c3/nostrsigner/EncryptedStorage.kt index fe6bb01d..9ba9cde7 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/EncryptedStorage.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/EncryptedStorage.kt @@ -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 = diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt index 9bb95984..89ec4438 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt @@ -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(10) + private var accountCache = LargeCache() - fun allLegacySavedAccounts(context: Context): List { - return legacySavedAccounts(context).map { npub -> - AccountInfo( - npub, - true, - ) - }.toSet().toList() - } + fun allLegacySavedAccounts(context: Context): List = legacySavedAccounts(context).map { npub -> + AccountInfo( + npub, + true, + ) + }.toSet().toList() - fun allSavedAccounts(context: Context): List { - return savedAccounts(context).map { npub -> - AccountInfo( - npub, - true, - ) - }.toSet().toList() - } + fun allSavedAccounts(context: Context): List = 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? = null @Suppress("DEPRECATION") - private fun legacySavedAccounts(context: Context): List { - return encryptedPreferences(context) - .getString(SettingsKeys.SAVED_ACCOUNTS.key, null)?.split(COMMA) ?: listOf() - } + private fun legacySavedAccounts(context: Context): List = encryptedPreferences(context) + .getString(SettingsKeys.SAVED_ACCOUNTS.key, null)?.split(COMMA) ?: listOf() private fun savedAccounts(context: Context): List { 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( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt index 893b3440..efbc9154 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt @@ -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 diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/SecureCryptoHelper.kt b/app/src/main/java/com/greenart7c3/nostrsigner/SecureCryptoHelper.kt index 636c75fa..f133b963 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/SecureCryptoHelper.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/SecureCryptoHelper.kt @@ -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) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt index ee0edbe5..9614923d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt @@ -30,24 +30,16 @@ class SignerProvider : ContentProvider() { uri: Uri, selection: String?, selectionArgs: Array?, - ): 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?, - ): Int { - return 0 - } + ): Int = 0 } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/database/AppDatabase.kt b/app/src/main/java/com/greenart7c3/nostrsigner/database/AppDatabase.kt index 1a864e69..c2ba046e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/AppDatabase.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/AppDatabase.kt @@ -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 } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationEntity.kt b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationEntity.kt index fabee693..eb2ddc81 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationEntity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationEntity.kt @@ -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): String { - return relays.joinToString(separator = ",") { - it.url - } + fun toString(relays: List): String = relays.joinToString(separator = ",") { + it.url } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/database/HistoryDatabase.kt b/app/src/main/java/com/greenart7c3/nostrsigner/database/HistoryDatabase.kt index 42cc83c7..7d5d400f 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/HistoryDatabase.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/HistoryDatabase.kt @@ -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 } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/database/LogDatabase.kt b/app/src/main/java/com/greenart7c3/nostrsigner/database/LogDatabase.kt index 50a07f2b..442ce78d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/LogDatabase.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/LogDatabase.kt @@ -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 } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt index 3e6aeddb..737e9297 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt @@ -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(account)) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/AccountExport.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/AccountExportData.kt similarity index 66% rename from app/src/main/java/com/greenart7c3/nostrsigner/models/AccountExport.kt rename to app/src/main/java/com/greenart7c3/nostrsigner/models/AccountExportData.kt index 206059f6..7006e70a 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/AccountExport.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/AccountExportData.kt @@ -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, ) { 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) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/BunkerMetadata.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/BunkerMetadata.kt index 0c4d8d51..a3733058 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/BunkerMetadata.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/BunkerMetadata.kt @@ -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::class.java) { override fun deserialize( jp: JsonParser, ctxt: DeserializationContext, - ): BunkerMetadata { - return fromJson(jp.codec.readTree(jp)) - } + ): BunkerMetadata = fromJson(jp.codec.readTree(jp)) } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/Permission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/Permission.kt index 12c1281f..2ae59ea2 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/Permission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/Permission.kt @@ -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()) } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/okhttp/EncryptedBlobInterceptor.kt b/app/src/main/java/com/greenart7c3/nostrsigner/okhttp/EncryptedBlobInterceptor.kt index 9653ff48..b1ae4fa8 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/okhttp/EncryptedBlobInterceptor.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/okhttp/EncryptedBlobInterceptor.kt @@ -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) { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/okhttp/HttpClientManager.kt b/app/src/main/java/com/greenart7c3/nostrsigner/okhttp/HttpClientManager.kt index 2cb54b6e..beb24417 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/okhttp/HttpClientManager.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/okhttp/HttpClientManager.kt @@ -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))) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/AccountExportService.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/AccountExportService.kt index 4c0fb6b8..3e89b141 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/AccountExportService.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/AccountExportService.kt @@ -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() - + 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, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt index 96b395da..43fc226a 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt @@ -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>, -): 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" } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt index 9a285c16..799d0bb3 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt @@ -49,9 +49,7 @@ object BunkerRequestUtils { state.tryEmit(state.value.filter { it.request.id != id }) } - fun getBunkerRequests(): List { - return state.value - } + fun getBunkerRequests(): List = 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( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerResponseWorker.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerResponseWorker.kt index 48e00cd7..e1457679 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerResponseWorker.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerResponseWorker.kt @@ -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") diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/ClearLogsWorker.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/ClearLogsWorker.kt index e517690f..5e53c5d7 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/ClearLogsWorker.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/ClearLogsWorker.kt @@ -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 { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/ConnectivityService.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/ConnectivityService.kt index 45488705..e28e00df 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/ConnectivityService.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/ConnectivityService.kt @@ -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") diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/EventNotificationConsumer.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/EventNotificationConsumer.kt index ad427609..551b689c 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/EventNotificationConsumer.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/EventNotificationConsumer.kt @@ -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 } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/NostrConnectUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/NostrConnectUtils.kt index 87f475f3..182c68ae 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/NostrConnectUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/NostrConnectUtils.kt @@ -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, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/CrashReportCache.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/CrashReportCache.kt index 37a6ef39..2ecdf8bd 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/CrashReportCache.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/CrashReportCache.kt @@ -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 + } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/ReportAssembler.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/ReportAssembler.kt index c684b162..74e5ff5f 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/ReportAssembler.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/ReportAssembler.kt @@ -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("```") + } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/AmberEvent.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/AmberEvent.kt index f8382939..1f732b4e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/AmberEvent.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/AmberEvent.kt @@ -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 = 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() } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt index 0ddef117..8899c975 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt @@ -242,7 +242,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() { startUI(account, null) } - private fun startUI( + fun startUI( account: Account, route: String?, ) { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/ApplicationsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/ApplicationsScreen.kt index c23cfe03..bc51c9f9 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/ApplicationsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/ApplicationsScreen.kt @@ -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 } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt index 94026b95..f41e1686 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt @@ -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, ) }, ) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt index 635060d7..545f723d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -298,6 +298,7 @@ fun MainScreen( MainPage( scope = scope, navController = navController, + accountViewModel = accountStateViewModel, ) }, ) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/QrCodeDrawer.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/QrCodeDrawer.kt index fd25960b..2626c9bf 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/QrCodeDrawer.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/QrCodeDrawer.kt @@ -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, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/ScrollableModifier.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/ScrollableModifier.kt index aeaca74c..f0482244 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/ScrollableModifier.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/ScrollableModifier.kt @@ -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), + ) + } + }, +) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt index d8b6f610..025d1c2d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt @@ -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 } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ExportAllAccountsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ExportAllAccountsScreen.kt index 62fc9313..7149ef42 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ExportAllAccountsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ExportAllAccountsScreen.kt @@ -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)) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberNavigationBar.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberNavigationBar.kt index 6186b848..80ff191b 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberNavigationBar.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberNavigationBar.kt @@ -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 + }, ) } }, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SeedWordsPage.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SeedWordsPage.kt index 7e643e94..8f106a89 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SeedWordsPage.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SeedWordsPage.kt @@ -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 { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e39e3ff5..3a205bce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -601,4 +601,6 @@ Read this text from %1$s encrypted content Ping Response + Recover from backup + Recover from backup diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d26900f4..4af6c92e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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"