From d40f45c2b1892fecd305b680c36c40491a3ee0f5 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Sat, 19 Oct 2024 15:36:03 -0300 Subject: [PATCH 01/30] update quartz --- gradle/libs.versions.toml | 2 +- gradle/libs.versions.toml.save | 82 ++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 gradle/libs.versions.toml.save diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 138d6f63..c2e079fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ lifecycle_version = "2.8.6" material3 = "1.3.0" mockk = "1.13.12" nav_version = "2.8.2" -quartz = "0.92.2" +quartz = "0.92.7" compose_ui = "1.7.3" richtextUi = "077a2cde64" roomKtx = "2.6.1" diff --git a/gradle/libs.versions.toml.save b/gradle/libs.versions.toml.save new file mode 100644 index 00000000..c2e079fc --- /dev/null +++ b/gradle/libs.versions.toml.save @@ -0,0 +1,82 @@ +[versions] +activityCompose = "1.9.2" +unfiedpush = "2.4.0" +appcompat = "1.7.0" +biometricKtx = "1.2.0-alpha05" +converterGson = "2.11.0" +core = "3.5.3" +coreKtx = "1.13.1" +coreSplashscreen = "1.0.1" +espressoCore = "3.6.1" +firebaseBom = "33.4.0" +firebaseMessaging = "24.0.2" +jna = "5.14.0" +junit = "4.13.2" +junitVersion = "1.2.1" +lifecycle_version = "2.8.6" +material3 = "1.3.0" +mockk = "1.13.12" +nav_version = "2.8.2" +quartz = "0.92.7" +compose_ui = "1.7.3" +richtextUi = "077a2cde64" +roomKtx = "2.6.1" +securityCryptoKtx = "1.1.0-alpha06" +zxingAndroidEmbedded = "4.3.0" +okhttp = "5.0.0-alpha.14" +kotlin = "2.0.20" +workRuntimeKtx = "2.9.1" +agp = "8.7.0" +ktlint = "12.1.1" +gms = "4.4.2" +ksp = "2.0.20-1.0.24" + +[libraries] +activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" } +unfiedpush = { module = "com.github.UnifiedPush:android-connector", version.ref = "unfiedpush" } +appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } +biometric-ktx = { module = "androidx.biometric:biometric-ktx", version.ref = "biometricKtx" } +converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" } +core = { module = "com.google.zxing:core", version.ref = "core" } +core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } +core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" } +espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } +ext-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" } +firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } +firebase-messaging = { module = "com.google.firebase:firebase-messaging", version.ref = "firebaseMessaging" } +jna = { module = "net.java.dev.jna:jna", version.ref = "jna" } +junit = { module = "junit:junit", version.ref = "junit" } +lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle_version" } +lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_version" } +lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle_version" } +material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose_ui" } +material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "nav_version" } +quartz = { module = "com.github.vitorpamplona.amethyst:quartz", version.ref = "quartz" } +ammolite = { module = "com.github.vitorpamplona.amethyst:ammolite", version.ref = "quartz" } +richtext-commonmark = { module = "com.github.vitorpamplona.compose-richtext:richtext-commonmark", version.ref = "richtextUi" } +richtext-ui = { module = "com.github.vitorpamplona.compose-richtext:richtext-ui", version.ref = "richtextUi" } +richtext-ui-material3 = { module = "com.github.vitorpamplona.compose-richtext:richtext-ui-material3", version.ref = "richtextUi" } +room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomKtx" } +room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" } +runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "compose_ui" } +security-crypto-ktx = { module = "androidx.security:security-crypto-ktx", version.ref = "securityCryptoKtx" } +ui = { module = "androidx.compose.ui:ui", version.ref = "compose_ui" } +ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose_ui" } +ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose_ui" } +ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose_ui" } +ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose_ui" } +zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroidEmbedded" } +okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } +work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" } + +[plugins] +jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +androidLibrary = { id = "com.android.library", version.ref = "agp" } +androidApplication = { id = "com.android.application", version.ref = "agp" } +gradle_ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "ktlint" } +google_gms = { id = "com.google.gms.google-services", version = "gms" } +serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kotlin_ksp = { id = "com.google.devtools.ksp", version = "ksp" } +jetbrainsComposeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } From a85836a7f0bfae20eaf75c020bfcc62d81baa3a1 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Sat, 19 Oct 2024 15:48:02 -0300 Subject: [PATCH 02/30] 2.0.7 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3c6abe94..917017e2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "com.greenart7c3.nostrsigner" minSdk 26 targetSdk 34 - versionCode 98 - versionName "2.0.6" + versionCode 99 + versionName "2.0.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From d61b59a7ed78d53e81915d1333d9de78ca0c078c Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Sun, 27 Oct 2024 15:22:12 -0300 Subject: [PATCH 03/30] Fix private zaps --- .../greenart7c3/nostrsigner/SignerProvider.kt | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt index 6dd05a9f..658c8e08 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt @@ -12,7 +12,6 @@ import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.models.kindToNip import com.greenart7c3.nostrsigner.service.AmberUtils import com.greenart7c3.nostrsigner.service.IntentUtils -import com.greenart7c3.nostrsigner.service.model.AmberEvent import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.encoders.toNpub @@ -112,27 +111,12 @@ class SignerProvider : ContentProvider() { if (!LocalPreferences.containsAccount(context!!, npub)) return null val account = LocalPreferences.loadFromEncryptedStorage(context!!, npub) ?: return null val event = try { - AmberEvent.toEvent(AmberEvent.fromJson(json)) + IntentUtils.getUnsignedEvent(json, account) } catch (e: Exception) { Log.d("SignerProvider", "Failed to parse event from $packageName", e) return null } - if (event.pubKey.isBlank()) { - Log.d("SignerProvider", "Event from $packageName has no pubkey") - return null - } - - if (event.pubKey != account.keyPair.pubKey.toHexKey()) { - Log.d("SignerProvider", "Event from $packageName has incorrect pubkey") - return null - } - - if (!event.hasCorrectIDHash()) { - Log.d("SignerProvider", "Event from $packageName has incorrect ID hash") - return null - } - val database = NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()) var permission = database @@ -211,7 +195,7 @@ class SignerProvider : ContentProvider() { val cursor = MatrixCursor(arrayOf("signature", "event", "result")).also { val signature = - if (event is LnZapRequestEvent && + if (event.kind == LnZapRequestEvent.KIND && event.tags.any { tag -> tag.any { t -> t == "anon" } From a929316cc6e974200741137f726160569fb5f469 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Sun, 27 Oct 2024 15:25:36 -0300 Subject: [PATCH 04/30] 2.0.8 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 917017e2..1020dc53 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "com.greenart7c3.nostrsigner" minSdk 26 targetSdk 34 - versionCode 99 - versionName "2.0.7" + versionCode 100 + versionName "2.0.8" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From f90a9da504170428bd5f70346c59ddad97fcca1a Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 14 Oct 2024 16:11:53 -0300 Subject: [PATCH 05/30] Move relays to the bottom nav bar --- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 7 ++++- .../nostrsigner/ui/components/MainScaffold.kt | 31 +++---------------- .../nostrsigner/ui/navigation/Route.kt | 8 +++-- 3 files changed, 16 insertions(+), 30 deletions(-) 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 16226eab..c20ee797 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -876,8 +876,13 @@ fun MainScreen( composable( Route.ActiveRelays.route, content = { - BackButtonScaffold( + MainScaffold( + accountStateViewModel, + account, + database, navController = navController, + destinationRoute = destinationRoute, + false, ) { padding -> ActiveRelaysScreen( modifier = diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt index 17dfadb0..3fec271a 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt @@ -3,14 +3,12 @@ package com.greenart7c3.nostrsigner.ui.components import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ExpandMore -import androidx.compose.material.icons.filled.Hub import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api @@ -21,7 +19,6 @@ import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold import androidx.compose.material3.SheetValue import androidx.compose.material3.Text -import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -67,7 +64,11 @@ fun MainScaffold( ) { val scope = rememberCoroutineScope() val clipboardManager = LocalClipboardManager.current - val items = listOf(Route.Home, Route.Permissions, Route.Settings) + val items = mutableListOf(Route.Home, Route.Permissions, Route.ActiveRelays, Route.Settings) + @Suppress("KotlinConstantConditions") + if (BuildConfig.FLAVOR == "offline") { + items.remove(Route.ActiveRelays) + } var shouldShowBottomSheet by remember { mutableStateOf(false) } val sheetState = rememberModalBottomSheetState( @@ -131,28 +132,6 @@ fun MainScaffold( } CenterAlignedTopAppBar( - actions = { - @Suppress("KotlinConstantConditions") - if (BuildConfig.FLAVOR != "offline") { - Box( - modifier = Modifier - .minimumInteractiveComponentSize() - .size(40.dp) - .clickable( - interactionSource = interactionSource, - indication = null, - ) { - navController.navigate(Route.ActiveRelays.route) - }, - contentAlignment = Alignment.Center, - ) { - Icon( - Icons.Default.Hub, - contentDescription = "Relays", - ) - } - } - }, title = { val name = LocalPreferences.getAccountName(context, account.keyPair.pubKey.toNpub()) Row( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt index 8d72052a..fabe7b19 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt @@ -4,8 +4,10 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ViewList import androidx.compose.material.icons.automirrored.outlined.ViewList import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Hub import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Hub import androidx.compose.material.icons.outlined.Settings import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.vector.ImageVector @@ -62,10 +64,10 @@ sealed class Route( ) data object ActiveRelays : Route( - title = "", + title = NostrSigner.getInstance().getString(R.string.relays), route = "ActiveRelays", - icon = Icons.Outlined.Settings, - selectedIcon = Icons.Default.Settings, + icon = Icons.Outlined.Hub, + selectedIcon = Icons.Default.Hub, ) data object Language : Route( From 8e6d3006eaf3f9857ff74f36c8e23fa2dead163b Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 14 Oct 2024 17:08:32 -0300 Subject: [PATCH 06/30] Move accounts to the bottom --- .../nostrsigner/ui/components/MainScaffold.kt | 87 ++++++------------- .../nostrsigner/ui/navigation/Route.kt | 9 ++ app/src/main/res/values/strings.xml | 1 + 3 files changed, 36 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt index 3fec271a..b2dadb2d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt @@ -1,19 +1,11 @@ package com.greenart7c3.nostrsigner.ui.components -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ExpandMore -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold @@ -26,27 +18,21 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.greenart7c3.nostrsigner.BuildConfig -import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.NostrSigner import com.greenart7c3.nostrsigner.database.AppDatabase import com.greenart7c3.nostrsigner.database.ApplicationEntity import com.greenart7c3.nostrsigner.models.Account -import com.greenart7c3.nostrsigner.service.toShortenHex import com.greenart7c3.nostrsigner.ui.AccountStateViewModel import com.greenart7c3.nostrsigner.ui.PermissionsFloatingActionButton import com.greenart7c3.nostrsigner.ui.actions.AccountsBottomSheet import com.greenart7c3.nostrsigner.ui.navigation.Route -import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder import com.vitorpamplona.quartz.encoders.toHexKey -import com.vitorpamplona.quartz.encoders.toNpub import java.util.UUID import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -64,7 +50,7 @@ fun MainScaffold( ) { val scope = rememberCoroutineScope() val clipboardManager = LocalClipboardManager.current - val items = mutableListOf(Route.Home, Route.Permissions, Route.ActiveRelays, Route.Settings) + val items = mutableListOf(Route.Home, Route.Permissions, Route.ActiveRelays, Route.Settings, Route.Accounts) @Suppress("KotlinConstantConditions") if (BuildConfig.FLAVOR == "offline") { items.remove(Route.ActiveRelays) @@ -78,6 +64,20 @@ fun MainScaffold( val interactionSource = remember { MutableInteractionSource() } val context = LocalContext.current + if (shouldShowBottomSheet) { + AccountsBottomSheet( + sheetState = sheetState, + account = account, + accountStateViewModel = accountStateViewModel, + onClose = { + scope.launch { + shouldShowBottomSheet = false + sheetState.hide() + } + }, + ) + } + Scaffold( floatingActionButton = { @Suppress("KotlinConstantConditions") @@ -117,49 +117,10 @@ fun MainScaffold( } }, topBar = { - if (shouldShowBottomSheet) { - AccountsBottomSheet( - sheetState = sheetState, - account = account, - accountStateViewModel = accountStateViewModel, - onClose = { - scope.launch { - shouldShowBottomSheet = false - sheetState.hide() - } - }, - ) - } - CenterAlignedTopAppBar( title = { - val name = LocalPreferences.getAccountName(context, account.keyPair.pubKey.toNpub()) - Row( - Modifier - .border( - border = ButtonDefaults.outlinedButtonBorder(), - shape = ButtonBorder, - ) - .padding(8.dp) - .clickable( - interactionSource = interactionSource, - indication = null, - ) { - scope.launch { - sheetState.show() - shouldShowBottomSheet = true - } - }, - verticalAlignment = Alignment.CenterVertically, - ) { - Text(name.ifBlank { account.keyPair.pubKey.toNpub().toShortenHex() }) - Icon( - imageVector = Icons.Default.ExpandMore, - null, - modifier = Modifier.size(20.dp), - tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.32f), - ) - } + val title = items.find { it.route == destinationRoute }?.title ?: "" + Text(title) }, ) }, @@ -170,8 +131,15 @@ fun MainScaffold( NavigationBarItem( selected = selected, onClick = { - navController.navigate(it.route) { - popUpTo(0) + if (it.route == Route.Accounts.route) { + scope.launch { + sheetState.show() + shouldShowBottomSheet = true + } + } else { + navController.navigate(it.route) { + popUpTo(0) + } } }, icon = { @@ -180,9 +148,6 @@ fun MainScaffold( it.route, ) }, - label = { - Text(it.title) - }, ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt index fabe7b19..973a1b96 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt @@ -5,9 +5,11 @@ import androidx.compose.material.icons.automirrored.filled.ViewList import androidx.compose.material.icons.automirrored.outlined.ViewList import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Hub +import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Hub +import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Settings import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.vector.ImageVector @@ -104,4 +106,11 @@ sealed class Route( icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) + + data object Accounts : Route( + title = NostrSigner.getInstance().getString(R.string.accounts), + route = "Accounts", + icon = Icons.Outlined.Person, + selectedIcon = Icons.Default.Person, + ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec454fd0..a4919602 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -381,4 +381,5 @@ %1$s failed with code %2$s Download relay document unavailable Failed to send event to relay + Accounts From 6e1d3a997e5794a09f01966e47e53aeb3e1e16a4 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 16 Oct 2024 10:11:11 -0300 Subject: [PATCH 07/30] App list as main screen, changed home to incoming requests --- .../greenart7c3/nostrsigner/MainViewModel.kt | 2 +- .../nostrsigner/service/NotificationUtils.kt | 6 ++--- .../nostrsigner/ui/EditPermission.kt | 4 +-- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 16 +++++++----- .../nostrsigner/ui/PermissionsScreen.kt | 2 +- .../nostrsigner/ui/components/MainScaffold.kt | 7 +---- .../nostrsigner/ui/navigation/Route.kt | 26 ++++++++++--------- app/src/main/res/values/strings.xml | 2 ++ 8 files changed, 33 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt index f43d47e9..8cf920a0 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt @@ -46,7 +46,7 @@ class MainViewModel(val context: Context) : ViewModel() { } contentIntent.putExtra("bunker", it.toJson()) contentIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) - IntentUtils.getIntentData(context, contentIntent, callingPackage, Route.Home.route, acc) { intentData -> + IntentUtils.getIntentData(context, contentIntent, callingPackage, Route.IncomingRequest.route, acc) { intentData -> if (intentData != null) { if (intents.value.none { item -> item.id == intentData.id }) { intents.value += listOf(intentData) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt index 441ba617..9d58f95c 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt @@ -101,7 +101,7 @@ object NotificationUtils { val contentIntent = Intent(applicationContext, MainActivity::class.java).apply { data = Uri.parse(uri) } contentIntent.putExtra("bunker", bunkerRequest.toJson()) - contentIntent.putExtra("route", Route.Home.route) + contentIntent.putExtra("route", Route.IncomingRequest.route) contentIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) val contentPendingIntent = PendingIntent.getActivity( @@ -132,7 +132,7 @@ object NotificationUtils { .setAutoCancel(true) .setExtras( Bundle().apply { - putString("route", Route.Home.route) + putString("route", Route.IncomingRequest.route) }, ) @@ -156,7 +156,7 @@ object NotificationUtils { .setAutoCancel(true) .setExtras( Bundle().apply { - putString("route", Route.Home.route) + putString("route", Route.IncomingRequest.route) }, ) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt index 38e45159..24028283 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -166,7 +166,7 @@ fun EditPermission( scope.launch(Dispatchers.Main) { navController.navigateUp() - accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Permissions.route) + accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Applications.route) } } } @@ -499,7 +499,7 @@ fun EditPermission( scope.launch(Dispatchers.Main) { navController.navigateUp() - accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Permissions.route) + accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Applications.route) } } }, 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 c20ee797..fbc82ce3 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -368,7 +368,7 @@ fun PermissionsFloatingActionButton( intent.data = Uri.parse(it) intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) context.getAppCompatActivity()?.startActivity(intent) - accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.Home.route) + accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.IncomingRequest.route) } } } @@ -406,7 +406,7 @@ fun PermissionsFloatingActionButton( intent.data = Uri.parse(it.text) intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) context.getAppCompatActivity()?.startActivity(intent) - accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.Home.route) + accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.IncomingRequest.route) } expanded = false }, @@ -461,7 +461,7 @@ fun PermissionsFloatingActionButton( intent.data = Uri.parse(it.text) intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) context.getAppCompatActivity()?.startActivity(intent) - accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.Home.route) + accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.IncomingRequest.route) } expanded = false }, @@ -726,15 +726,17 @@ fun MainScreen( ) } - var localRoute by remember { mutableStateOf(route.value ?: Route.Home.route) } + var localRoute by remember { mutableStateOf(route.value ?: if (intents.isEmpty()) Route.Applications.route else Route.IncomingRequest.route) } @Suppress("KotlinConstantConditions") if (BuildConfig.FLAVOR != "offline") { - LaunchedEffect(Unit, route.value) { + LaunchedEffect(Unit, route.value, intents) { launch(Dispatchers.Main) { if (route.value != null) { localRoute = route.value!! route.value = null + } else { + localRoute = if (intents.isEmpty()) Route.Applications.route else Route.IncomingRequest.route } } } @@ -747,7 +749,7 @@ fun MainScreen( exitTransition = { fadeOut(animationSpec = tween(200)) }, ) { composable( - Route.Home.route, + Route.IncomingRequest.route, content = { MainScaffold( accountStateViewModel, @@ -772,7 +774,7 @@ fun MainScreen( ) composable( - Route.Permissions.route, + Route.Applications.route, content = { MainScaffold( accountStateViewModel, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt index b0e80ace..495d2edd 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt @@ -84,7 +84,7 @@ fun PermissionsScreen( resetPermissions = false scope.launch { LocalPreferences.deleteSavedApps(applications, database) - accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Permissions.route) + accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Applications.route) } }, ) { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt index b2dadb2d..2e67fdad 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt @@ -1,8 +1,6 @@ package com.greenart7c3.nostrsigner.ui.components -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.padding import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -19,7 +17,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import androidx.navigation.NavController @@ -50,7 +47,7 @@ fun MainScaffold( ) { val scope = rememberCoroutineScope() val clipboardManager = LocalClipboardManager.current - val items = mutableListOf(Route.Home, Route.Permissions, Route.ActiveRelays, Route.Settings, Route.Accounts) + val items = mutableListOf(Route.Applications, Route.IncomingRequest, Route.ActiveRelays, Route.Settings, Route.Accounts) @Suppress("KotlinConstantConditions") if (BuildConfig.FLAVOR == "offline") { items.remove(Route.ActiveRelays) @@ -61,8 +58,6 @@ fun MainScaffold( confirmValueChange = { it != SheetValue.PartiallyExpanded }, skipPartiallyExpanded = true, ) - val interactionSource = remember { MutableInteractionSource() } - val context = LocalContext.current if (shouldShowBottomSheet) { AccountsBottomSheet( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt index 973a1b96..f18f0e98 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt @@ -1,13 +1,15 @@ package com.greenart7c3.nostrsigner.ui.navigation import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.automirrored.filled.ViewList +import androidx.compose.material.icons.automirrored.outlined.List import androidx.compose.material.icons.automirrored.outlined.ViewList -import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Apps import androidx.compose.material.icons.filled.Hub import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Settings -import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Apps import androidx.compose.material.icons.outlined.Hub import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Settings @@ -23,18 +25,18 @@ sealed class Route( val icon: ImageVector, val selectedIcon: ImageVector, ) { - data object Home : Route( - title = NostrSigner.getInstance().getString(R.string.home), - route = "Home", - icon = Icons.Outlined.Home, - selectedIcon = Icons.Default.Home, + data object IncomingRequest : Route( + title = NostrSigner.getInstance().getString(R.string.incoming_request), + route = "IncomingRequest", + icon = Icons.AutoMirrored.Outlined.List, + selectedIcon = Icons.AutoMirrored.Filled.List, ) - data object Permissions : Route( - route = "Permissions", - title = NostrSigner.getInstance().getString(R.string.permissions), - icon = Icons.AutoMirrored.Outlined.ViewList, - selectedIcon = Icons.AutoMirrored.Default.ViewList, + data object Applications : Route( + route = "Applications", + title = NostrSigner.getInstance().getString(R.string.applications), + icon = Icons.Outlined.Apps, + selectedIcon = Icons.Default.Apps, ) data object Settings : Route( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a4919602..d88a7fe8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -382,4 +382,6 @@ Download relay document unavailable Failed to send event to relay Accounts + Applications + Incoming Request From a14f5377acdf71cd03d1e3568e592fb33823b1b0 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 16 Oct 2024 13:24:07 -0300 Subject: [PATCH 08/30] Change theme, redesign the applications screen --- .../nostrsigner/database/ApplicationDao.kt | 2 +- .../nostrsigner/ui/PermissionsScreen.kt | 144 ++++++++---------- .../greenart7c3/nostrsigner/ui/theme/Theme.kt | 6 +- app/src/main/res/values/colors.xml | 2 +- 4 files changed, 67 insertions(+), 87 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt index 3f49964a..83cba61c 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt @@ -23,7 +23,7 @@ interface ApplicationDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertNotification(notificationEntity: NotificationEntity): Long? - @Query("SELECT * FROM application where pubKey = :pubKey") + @Query("SELECT * FROM application where pubKey = :pubKey order by name") fun getAll(pubKey: String): List @Query("SELECT * FROM application WHERE `key` = :key") diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt index 495d2edd..a52242fe 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt @@ -1,32 +1,25 @@ package com.greenart7c3.nostrsigner.ui +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowForward -import androidx.compose.material.icons.filled.ClearAll -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.R @@ -34,8 +27,6 @@ import com.greenart7c3.nostrsigner.database.AppDatabase import com.greenart7c3.nostrsigner.database.ApplicationEntity import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.service.toShortenHex -import com.greenart7c3.nostrsigner.ui.components.IconRow -import com.greenart7c3.nostrsigner.ui.navigation.Route import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.encoders.toNpub import kotlinx.coroutines.Dispatchers @@ -56,9 +47,8 @@ fun PermissionsScreen( remember { mutableListOf() } - var resetPermissions by remember { mutableStateOf(false) } - val scope = rememberCoroutineScope() + // val sortBy by remember { mutableStateOf("name") } LaunchedEffect(lifecycleEvent) { scope.launch(Dispatchers.IO) { @@ -67,61 +57,9 @@ fun PermissionsScreen( } } - if (resetPermissions) { - AlertDialog( - title = { - Text(text = stringResource(R.string.reset_permissions)) - }, - text = { - Text(text = stringResource(R.string.do_you_want_to_reset_all_permissions)) - }, - onDismissRequest = { - resetPermissions = false - }, - confirmButton = { - TextButton( - onClick = { - resetPermissions = false - scope.launch { - LocalPreferences.deleteSavedApps(applications, database) - accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Applications.route) - } - }, - ) { - Text(text = stringResource(R.string.confirm)) - } - }, - dismissButton = { - TextButton( - onClick = { - resetPermissions = false - }, - ) { - Text(text = stringResource(R.string.cancel)) - } - }, - ) - } - Column( modifier, ) { - Box( - Modifier - .padding(16.dp), - ) { - IconRow( - title = stringResource(id = R.string.reset_permissions), - icon = Icons.Default.ClearAll, - tint = MaterialTheme.colorScheme.onBackground, - onClick = { - resetPermissions = true - }, - ) - } - - HorizontalDivider() - if (applications.isEmpty()) { Column( Modifier.fillMaxSize(), @@ -133,26 +71,66 @@ fun PermissionsScreen( } else { LazyColumn( Modifier - .fillMaxSize(), + .fillMaxSize() + .padding(8.dp), ) { +// item { +// Row( +// Modifier +// .fillMaxWidth() +// .padding(8.dp) +// .clickable { +// +// }, +// horizontalArrangement = Arrangement.End, +// ) { +// Icon( +// Icons.AutoMirrored.Filled.Sort, +// null, +// modifier = Modifier.size(22.dp), +// tint = MaterialTheme.colorScheme.onBackground, +// ) +// Text( +// modifier = Modifier.padding(start = 16.dp), +// text = "Sort by $sortBy", +// maxLines = 1, +// overflow = TextOverflow.Ellipsis, +// ) +// } +// } items(applications.size) { - Row( - verticalAlignment = Alignment.CenterVertically, + ElevatedCard( + modifier = Modifier + .padding(4.dp) + .fillMaxSize(), ) { - Box( - Modifier - .padding(16.dp) - .weight(0.9f), + Row( + modifier = Modifier.clickable { + navController.navigate("Permission/${applications.elementAt(it).key}") + }, + verticalAlignment = Alignment.CenterVertically, ) { - val localPermission = applications.elementAt(it) - IconRow( - title = localPermission.name.ifBlank { localPermission.key.toShortenHex() }, - icon = Icons.AutoMirrored.Default.ArrowForward, - tint = MaterialTheme.colorScheme.onBackground, - onClick = { - navController.navigate("Permission/${applications.elementAt(it).key}") - }, - ) + Column( + Modifier + .padding(16.dp) + .weight(0.9f), + ) { + val localPermission = applications.elementAt(it) + Text( + modifier = Modifier.padding(start = 16.dp), + text = localPermission.name.ifBlank { localPermission.key.toShortenHex() }, + fontSize = 24.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + modifier = Modifier.padding(start = 16.dp, top = 4.dp), + text = localPermission.key, + fontSize = 18.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/theme/Theme.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/theme/Theme.kt index 2a9e4434..0ae10bc5 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/theme/Theme.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/theme/Theme.kt @@ -54,6 +54,8 @@ private val LightColorPalette = tertiary = secondaryColor, primaryContainer = secondaryColor, secondaryContainer = secondaryColor, + surface = Color(0xFFFFDE9E), + surfaceContainer = Color(0xFFFFDE9E), ) @Composable @@ -78,9 +80,9 @@ fun NostrSignerTheme( if (darkTheme) { window.statusBarColor = colors.background.toArgb() } else { - window.statusBarColor = colors.primary.toArgb() + window.statusBarColor = colors.surface.toArgb() } - window.navigationBarColor = colors.background.toArgb() + window.navigationBarColor = colors.surface.toArgb() insetsController.isAppearanceLightNavigationBars = !darkTheme } } diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f68b8be6..0f741075 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,4 +3,4 @@ #FF3700B3 #FF000000 #FFBF00 - \ No newline at end of file + From 707a989363a7665e949f58a3960e99b35d2c74c2 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 16 Oct 2024 14:41:16 -0300 Subject: [PATCH 09/30] Add new application screen --- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 23 ++- .../nostrsigner/ui/NewApplicationScreen.kt | 144 ++++++++++++++++++ .../ui/components/BackButtonScaffold.kt | 42 ++--- .../nostrsigner/ui/components/MainScaffold.kt | 58 +++---- .../nostrsigner/ui/navigation/Route.kt | 7 + app/src/main/res/values/strings.xml | 8 + 6 files changed, 235 insertions(+), 47 deletions(-) create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt 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 fbc82ce3..e846f90e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -354,6 +354,7 @@ fun PermissionsFloatingActionButton( accountStateViewModel: AccountStateViewModel, account: Account, goToTop: () -> Unit, + onClick: () -> Unit, ) { val clipboardManager = LocalClipboardManager.current var expanded by remember { mutableStateOf(false) } @@ -594,9 +595,7 @@ fun PermissionsFloatingActionButton( ) FloatingActionButton( - onClick = { - expanded = !expanded - }, + onClick = onClick, shape = CircleShape, ) { Icon( @@ -985,5 +984,23 @@ fun MainScreen( } }, ) + + composable( + Route.NewApplication.route, + content = { + BackButtonScaffold( + navController = navController, + ) { padding -> + NewApplicationScreen( + account = account, + accountStateViewModel = accountStateViewModel, + modifier = + Modifier + .fillMaxSize() + .padding(padding), + ) + } + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt new file mode 100644 index 00000000..44c5cc40 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt @@ -0,0 +1,144 @@ +package com.greenart7c3.nostrsigner.ui + +import android.content.Intent +import android.net.Uri +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.greenart7c3.nostrsigner.R +import com.greenart7c3.nostrsigner.models.Account +import com.greenart7c3.nostrsigner.service.getAppCompatActivity +import com.greenart7c3.nostrsigner.ui.navigation.Route +import com.vitorpamplona.quartz.encoders.toNpub + +@Composable +fun NewApplicationScreen( + modifier: Modifier, + accountStateViewModel: AccountStateViewModel, + account: Account, +) { + var dialogOpen by remember { mutableStateOf(false) } + val clipboardManager = LocalClipboardManager.current + val context = LocalContext.current + + if (dialogOpen) { + SimpleQrCodeScanner { + dialogOpen = false + if (!it.isNullOrEmpty()) { + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Uri.parse(it) + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + context.getAppCompatActivity()?.startActivity(intent) + accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.IncomingRequest.route) + } + } + } + + Column( + modifier = + modifier + .fillMaxSize() + .padding(horizontal = 16.dp, vertical = 24.dp), + ) { + Text( + text = stringResource(R.string.new_app_description), + ) + + Row( + Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.Center, + ) { + Button( + onClick = { + clipboardManager.getText()?.let { + if (it.text.isBlank()) { + Toast.makeText( + context, + context.getString(R.string.invalid_nostr_connect_uri), + Toast.LENGTH_SHORT, + ).show() + return@let + } + if (!it.text.startsWith("nostrconnect://")) { + Toast.makeText( + context, + context.getString(R.string.invalid_nostr_connect_uri), + Toast.LENGTH_SHORT, + ).show() + return@let + } + + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Uri.parse(it.text) + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + context.getAppCompatActivity()?.startActivity(intent) + accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.IncomingRequest.route) + } + }, + content = { + Text(text = stringResource(R.string.paste_from_clipboard)) + }, + ) + } + + Text( + stringResource(R.string.nostr_connect_description), + ) + + Row( + Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.Center, + ) { + Button( + onClick = { + dialogOpen = true + }, + content = { + Text(text = stringResource(R.string.scan_qr_code)) + }, + ) + } + + Text( + stringResource(R.string.nostr_connect_qr_description), + ) + + Row( + Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.Center, + ) { + Button( + onClick = { }, + content = { + Text(text = stringResource(R.string.add_a_nsecbunker)) + }, + ) + } + + Text( + stringResource(R.string.nsecbunker_description), + ) + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt index 32f86a83..d39ef01f 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt @@ -3,14 +3,18 @@ package com.greenart7c3.nostrsigner.ui.components import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.navigation.NavController +import com.greenart7c3.nostrsigner.R @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -18,23 +22,27 @@ fun BackButtonScaffold( navController: NavController, content: @Composable (paddingValues: PaddingValues) -> Unit, ) { + val context = LocalContext.current + val title = remember { + navController.previousBackStackEntry?.destination?.route?.let { context.getString(R.string.back_to, it) } ?: "" + } Scaffold( + bottomBar = { + BottomAppBar { + IconRow( + title = title, + icon = Icons.AutoMirrored.Filled.ArrowBack, + onClick = { + navController.navigateUp() + }, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + }, topBar = { - TopAppBar( + CenterAlignedTopAppBar( title = { - Text("") - }, - navigationIcon = { - IconButton( - onClick = { - navController.navigateUp() - }, - ) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = null, - ) - } + Text(stringResource(R.string.add_a_new_application)) }, ) }, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt index 2e67fdad..38a53e71 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt @@ -80,35 +80,39 @@ fun MainScaffold( PermissionsFloatingActionButton( accountStateViewModel, account, - ) { - val secret = UUID.randomUUID().toString().substring(0, 6) - scope.launch(Dispatchers.IO) { - val application = - ApplicationEntity( - secret, - "", - NostrSigner.getInstance().settings.defaultRelays, - "", - "", - "", - account.keyPair.pubKey.toHexKey(), - false, - secret, - false, - account.signPolicy, - ) + goToTop = { + val secret = UUID.randomUUID().toString().substring(0, 6) + scope.launch(Dispatchers.IO) { + val application = + ApplicationEntity( + secret, + "", + NostrSigner.getInstance().settings.defaultRelays, + "", + "", + "", + account.keyPair.pubKey.toHexKey(), + false, + secret, + false, + account.signPolicy, + ) - database.applicationDao().insertApplication( - application, - ) - val relayString = NostrSigner.getInstance().settings.defaultRelays.joinToString(separator = "&") { "relay=${it.url}" } - val bunkerUrl = "bunker://${account.keyPair.pubKey.toHexKey()}?$relayString" - clipboardManager.setText(AnnotatedString(bunkerUrl)) - scope.launch(Dispatchers.Main) { - navController.navigate("Permission/$secret") + database.applicationDao().insertApplication( + application, + ) + val relayString = NostrSigner.getInstance().settings.defaultRelays.joinToString(separator = "&") { "relay=${it.url}" } + val bunkerUrl = "bunker://${account.keyPair.pubKey.toHexKey()}?$relayString" + clipboardManager.setText(AnnotatedString(bunkerUrl)) + scope.launch(Dispatchers.Main) { + navController.navigate("Permission/$secret") + } } - } - } + }, + onClick = { + navController.navigate(Route.NewApplication.route) + }, + ) } }, topBar = { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt index f18f0e98..df25702f 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt @@ -115,4 +115,11 @@ sealed class Route( icon = Icons.Outlined.Person, selectedIcon = Icons.Default.Person, ) + + data object NewApplication : Route( + title = "", + route = "NewApplication", + icon = Icons.Outlined.Settings, + selectedIcon = Icons.Default.Settings, + ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d88a7fe8..1a828d8d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -384,4 +384,12 @@ Accounts Applications Incoming Request + Back to %1$s + Scan a QR Code + Add a nsecbunker + This option allow to add a nsecbunker manually, and use with whichever app supports Nostr Connect (NIP-46) + If your app offers you a Nostr Connect QR Code you can scan it from here. Usually this login mode it is offered by web apps. + If your app offeres you a Nostr Connect URL, you can paste it here. Usually this login mode it is offered by web apps. + Usually Nostr applications have a “Login with Amber” option that permits to use Amber as external signer; when you press that button Amber is launched and you are asked to approve some permissions.\n\nBut you have other options to use Amber: + Add a new application From 0b8501c563c96196a69826abaaf73f4a25676172 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 16 Oct 2024 16:45:20 -0300 Subject: [PATCH 10/30] Finish add a new bunker screen --- .../nostrsigner/database/ApplicationEntity.kt | 18 +- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 61 ++++ .../nostrsigner/ui/NewApplicationScreen.kt | 6 +- .../nostrsigner/ui/NewNsecBunkerScreen.kt | 333 ++++++++++++++++++ .../ui/actions/EditRelaysDialog.kt | 2 +- .../ui/components/BackButtonScaffold.kt | 22 +- .../nostrsigner/ui/navigation/Route.kt | 34 +- app/src/main/res/values/strings.xml | 9 + 8 files changed, 471 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/NewNsecBunkerScreen.kt 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 e89cef8b..f749deda 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationEntity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationEntity.kt @@ -37,7 +37,23 @@ data class ApplicationEntity( val secret: String, val useSecret: Boolean, var signPolicy: Int, -) +) { + companion object { + fun empty() = ApplicationEntity( + key = "", + name = "", + relays = emptyList(), + url = "", + icon = "", + description = "", + pubKey = "", + isConnected = false, + secret = "", + useSecret = false, + signPolicy = 0, + ) + } +} data class ApplicationWithPermissions( @Embedded val application: ApplicationEntity, 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 e846f90e..ccc75117 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -823,6 +823,7 @@ fun MainScreen( Route.AccountBackup.route, content = { BackButtonScaffold( + title = stringResource(R.string.account_backup), navController = navController, ) { padding -> AccountBackupScreen( @@ -840,6 +841,7 @@ fun MainScreen( arguments = listOf(navArgument("packageName") { type = NavType.StringType }), content = { BackButtonScaffold( + title = stringResource(R.string.permissions), navController = navController, ) { padding -> EditPermission( @@ -861,6 +863,7 @@ fun MainScreen( Route.Logs.route, content = { BackButtonScaffold( + title = stringResource(R.string.logs), navController = navController, ) { padding -> LogsScreen( @@ -899,6 +902,7 @@ fun MainScreen( Route.Language.route, content = { BackButtonScaffold( + title = stringResource(R.string.language), navController = navController, ) { padding -> LanguageScreen( @@ -916,6 +920,7 @@ fun MainScreen( Route.NotificationType.route, content = { BackButtonScaffold( + title = stringResource(R.string.notification_type), navController = navController, ) { padding -> NotificationTypeScreen( @@ -935,6 +940,7 @@ fun MainScreen( Route.DefaultRelays.route, content = { BackButtonScaffold( + title = stringResource(R.string.default_relays), navController = navController, ) { padding -> DefaultRelaysScreen( @@ -954,6 +960,7 @@ fun MainScreen( Route.SignPolicy.route, content = { BackButtonScaffold( + title = stringResource(R.string.sign_policy), navController = navController, ) { padding -> SignPolicySettingsScreen( @@ -972,6 +979,7 @@ fun MainScreen( Route.Security.route, content = { BackButtonScaffold( + title = stringResource(R.string.security), navController = navController, ) { padding -> SecurityScreen( @@ -989,11 +997,13 @@ fun MainScreen( Route.NewApplication.route, content = { BackButtonScaffold( + title = stringResource(R.string.add_a_new_application), navController = navController, ) { padding -> NewApplicationScreen( account = account, accountStateViewModel = accountStateViewModel, + navController = navController, modifier = Modifier .fillMaxSize() @@ -1002,5 +1012,56 @@ fun MainScreen( } }, ) + + composable( + Route.NewNsecBunker.route, + content = { + BackButtonScaffold( + title = stringResource(R.string.add_a_nsecbunker), + navController = navController, + ) { padding -> + NewNsecBunkerScreen( + database = database, + account = account, + accountStateViewModel = accountStateViewModel, + navController = navController, + modifier = + Modifier + .fillMaxSize() + .padding(padding), + ) + } + }, + ) + + composable( + Route.NSecBunkerCreated.route, + arguments = listOf(navArgument("key") { type = NavType.StringType }), + content = { + it.arguments?.getString("key")?.let { key -> + BackButtonScaffold( + title = stringResource(R.string.add_a_nsecbunker), + navController = navController, + backButtonTitle = stringResource(R.string.add_a_new_application), + onNav = { + navController.navigate(Route.Applications.route) { + popUpTo(0) + } + }, + ) { padding -> + NewNsecBunkerCreatedScreen( + database = database, + account = account, + accountStateViewModel = accountStateViewModel, + key = key, + modifier = + Modifier + .fillMaxSize() + .padding(padding), + ) + } + } + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt index 44c5cc40..7d7279e3 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.navigation.NavController import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.service.getAppCompatActivity @@ -32,6 +33,7 @@ fun NewApplicationScreen( modifier: Modifier, accountStateViewModel: AccountStateViewModel, account: Account, + navController: NavController, ) { var dialogOpen by remember { mutableStateOf(false) } val clipboardManager = LocalClipboardManager.current @@ -130,7 +132,9 @@ fun NewApplicationScreen( horizontalArrangement = Arrangement.Center, ) { Button( - onClick = { }, + onClick = { + navController.navigate(Route.NewNsecBunker.route) + }, content = { Text(text = stringResource(R.string.add_a_nsecbunker)) }, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewNsecBunkerScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewNsecBunkerScreen.kt new file mode 100644 index 00000000..6aa24ed2 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewNsecBunkerScreen.kt @@ -0,0 +1,333 @@ +package com.greenart7c3.nostrsigner.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import com.greenart7c3.nostrsigner.NostrSigner +import com.greenart7c3.nostrsigner.R +import com.greenart7c3.nostrsigner.database.AppDatabase +import com.greenart7c3.nostrsigner.database.ApplicationEntity +import com.greenart7c3.nostrsigner.models.Account +import com.greenart7c3.nostrsigner.ui.actions.onAddRelay +import com.vitorpamplona.ammolite.relays.RelaySetupInfo +import com.vitorpamplona.quartz.encoders.toHexKey +import java.util.UUID +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +fun NewNsecBunkerScreen( + modifier: Modifier = Modifier, + account: Account, + accountStateViewModel: AccountStateViewModel, + navController: NavController, + database: AppDatabase, +) { + val secret = remember { mutableStateOf(UUID.randomUUID().toString().substring(0, 6)) } + var name by remember { mutableStateOf(TextFieldValue(AnnotatedString(""))) } + val context = LocalContext.current + + val relays = + remember { + val localRelays = mutableStateListOf() + NostrSigner.getInstance().settings.defaultRelays.forEach { + localRelays.add( + it.copy(), + ) + } + localRelays + } + + val textFieldRelay = remember { + mutableStateOf(TextFieldValue("")) + } + val scope = rememberCoroutineScope() + val isLoading = remember { mutableStateOf(false) } + + if (isLoading.value) { + CenterCircularProgressIndicator( + modifier = Modifier.fillMaxSize(), + ) + } else { + Column( + modifier = modifier + .fillMaxSize() + .padding(16.dp), + ) { + Text(stringResource(R.string.create_nsecbunker_description)) + + OutlinedTextField( + value = name, + onValueChange = { name = it }, + label = { Text(stringResource(R.string.name)) }, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth(0.9f) + .padding(end = 16.dp), + value = textFieldRelay.value.text, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, + ), + onValueChange = { + textFieldRelay.value = TextFieldValue(it) + }, + keyboardActions = KeyboardActions( + onDone = { + scope.launch(Dispatchers.IO) { + onAddRelay( + textFieldRelay, + isLoading, + relays, + scope, + accountStateViewModel, + account, + context, + ) + } + }, + ), + label = { + Text(stringResource(R.string.wss)) + }, + ) + IconButton( + onClick = { + scope.launch(Dispatchers.IO) { + onAddRelay( + textFieldRelay, + isLoading, + relays, + scope, + accountStateViewModel, + account, + context, + ) + } + }, + ) { + Icon( + Icons.Default.Add, + null, + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + LazyColumn { + items(relays.size) { + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + Text( + relays[it].url, + Modifier + .weight(0.9f) + .padding(8.dp), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + IconButton( + onClick = { + relays.removeAt(it) + }, + ) { + Icon( + Icons.Default.Delete, + stringResource(R.string.delete), + ) + } + } + } + } + + Row( + Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.Center, + ) { + Button( + onClick = { + if (relays.isEmpty()) { + accountStateViewModel.toast( + context.getString(R.string.relays), + context.getString(R.string.no_relays_added), + ) + + return@Button + } + + if (name.text.isBlank()) { + accountStateViewModel.toast( + context.getString(R.string.name), + context.getString(R.string.name_cannot_be_empty), + ) + + return@Button + } + + scope.launch(Dispatchers.IO) { + val application = + ApplicationEntity( + secret.value, + name.text, + relays, + "", + "", + "", + account.keyPair.pubKey.toHexKey(), + false, + secret.value, + true, + account.signPolicy, + ) + + database.applicationDao().insertApplication( + application, + ) + scope.launch(Dispatchers.Main) { + navController.navigate("NewNsecBunkerCreated/${secret.value}") + } + } + }, + content = { + Text(text = stringResource(R.string.create)) + }, + ) + } + } + } +} + +@Composable +fun NewNsecBunkerCreatedScreen( + database: AppDatabase, + modifier: Modifier = Modifier, + account: Account, + accountStateViewModel: AccountStateViewModel, + key: String, +) { + val isLoading = remember { mutableStateOf(false) } + var application by remember { mutableStateOf(ApplicationEntity.empty()) } + val clipboardManager = LocalClipboardManager.current + + LaunchedEffect(Unit) { + isLoading.value = true + launch(Dispatchers.IO) { + application = database.applicationDao().getByKey(key)?.application ?: ApplicationEntity.empty() + isLoading.value = false + } + } + + if (isLoading.value) { + CenterCircularProgressIndicator( + modifier = Modifier.fillMaxSize(), + ) + } else { + val relays = application.relays.joinToString(separator = "&") { "relay=${it.url}" } + val localSecret = "&secret=${application.secret}" + val bunkerUri = "bunker://${account.keyPair.pubKey.toHexKey()}?$relays$localSecret" + + Column( + modifier = modifier + .fillMaxSize() + .padding(16.dp), + ) { + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + Text( + text = stringResource(R.string.your_nsec_bunker_has_been_created), + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + Text(stringResource(R.string.use_this_url_in_your_app)) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + bunkerUri, + fontSize = 18.sp, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + QrCodeDrawer(bunkerUri) + + Spacer(modifier = Modifier.height(8.dp)) + + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + Button( + onClick = { + clipboardManager.setText(AnnotatedString(bunkerUri)) + }, + content = { + Text(text = stringResource(R.string.copy_to_clipboard)) + }, + ) + } + } + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt index de9affd7..e4996c17 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt @@ -391,7 +391,7 @@ fun DefaultRelaysScreen( } } -private suspend fun onAddRelay( +suspend fun onAddRelay( textFieldRelay: MutableState, isLoading: MutableState, relays2: SnapshotStateList, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt index d39ef01f..879a29f0 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt @@ -11,30 +11,32 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.navigation.NavController import com.greenart7c3.nostrsigner.R +import com.greenart7c3.nostrsigner.ui.navigation.routes @OptIn(ExperimentalMaterial3Api::class) @Composable fun BackButtonScaffold( navController: NavController, + title: String, + backButtonTitle: String? = null, + onNav: () -> Unit = { + navController.navigateUp() + }, content: @Composable (paddingValues: PaddingValues) -> Unit, ) { - val context = LocalContext.current - val title = remember { - navController.previousBackStackEntry?.destination?.route?.let { context.getString(R.string.back_to, it) } ?: "" - } Scaffold( bottomBar = { + val localBackButtonTitle = remember { + backButtonTitle ?: routes.find { it.route == navController.previousBackStackEntry?.destination?.route }?.title ?: "" + } BottomAppBar { IconRow( - title = title, + title = stringResource(R.string.back_to, localBackButtonTitle), icon = Icons.AutoMirrored.Filled.ArrowBack, - onClick = { - navController.navigateUp() - }, + onClick = onNav, tint = MaterialTheme.colorScheme.onSurfaceVariant, ) } @@ -42,7 +44,7 @@ fun BackButtonScaffold( topBar = { CenterAlignedTopAppBar( title = { - Text(stringResource(R.string.add_a_new_application)) + Text(title) }, ) }, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt index df25702f..f4069c4c 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt @@ -117,9 +117,41 @@ sealed class Route( ) data object NewApplication : Route( - title = "", + title = NostrSigner.getInstance().getString(R.string.add_a_new_application), route = "NewApplication", icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) + + data object NewNsecBunker : Route( + title = NostrSigner.getInstance().getString(R.string.add_a_nsecbunker), + route = "NewNsecBunker", + icon = Icons.Outlined.Settings, + selectedIcon = Icons.Default.Settings, + ) + + data object NSecBunkerCreated : Route( + title = NostrSigner.getInstance().getString(R.string.add_a_nsecbunker), + route = "NewNsecBunkerCreated/{key}", + icon = Icons.Outlined.Settings, + selectedIcon = Icons.Default.Settings, + ) } + +val routes = listOf( + Route.IncomingRequest, + Route.Applications, + Route.Settings, + Route.Permission, + Route.AccountBackup, + Route.Logs, + Route.ActiveRelays, + Route.Language, + Route.NotificationType, + Route.DefaultRelays, + Route.SignPolicy, + Route.Security, + Route.Accounts, + Route.NewApplication, + Route.NewNsecBunker, +) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1a828d8d..88b86d69 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -392,4 +392,13 @@ If your app offeres you a Nostr Connect URL, you can paste it here. Usually this login mode it is offered by web apps. Usually Nostr applications have a “Login with Amber” option that permits to use Amber as external signer; when you press that button Amber is launched and you are asked to approve some permissions.\n\nBut you have other options to use Amber: Add a new application + Account backup + To create a new nsecbunker you need to give it a name and select one or more relays, that’s all! + Create + Name + No relays added + wss://... + "Name can't be empty " + Your nsecbunker is ready! + Use this url in your app: From 6557bc3d4aa071b5221630051a622a0269659696 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 18 Oct 2024 07:58:45 -0300 Subject: [PATCH 11/30] Redesign activity screen --- .../nostrsigner/ui/EditPermission.kt | 428 +++++++++--------- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 23 + .../nostrsigner/ui/actions/ActivityDialog.kt | 154 ------- .../nostrsigner/ui/actions/ActivityScreen.kt | 139 ++++++ ...ialog.kt => RemoveAllPermissionsDialog.kt} | 8 +- .../nostrsigner/ui/navigation/Route.kt | 7 + app/src/main/res/values/strings.xml | 7 +- 7 files changed, 384 insertions(+), 382 deletions(-) delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ActivityDialog.kt create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ActivityScreen.kt rename app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/{DeleteDialog.kt => RemoveAllPermissionsDialog.kt} (78%) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt index 24028283..48d3e1e1 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -6,21 +6,22 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ContentPaste import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.QrCode import androidx.compose.material3.Button +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -40,8 +41,6 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -50,24 +49,19 @@ import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController -import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.NostrSigner import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.database.AppDatabase import com.greenart7c3.nostrsigner.database.ApplicationEntity import com.greenart7c3.nostrsigner.database.ApplicationPermissionsEntity -import com.greenart7c3.nostrsigner.database.ApplicationWithPermissions import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.models.kindToNipUrl import com.greenart7c3.nostrsigner.models.nipToUrl -import com.greenart7c3.nostrsigner.ui.actions.ActivityDialog -import com.greenart7c3.nostrsigner.ui.actions.DeleteDialog import com.greenart7c3.nostrsigner.ui.actions.EditRelaysDialog import com.greenart7c3.nostrsigner.ui.actions.QrCodeDialog -import com.greenart7c3.nostrsigner.ui.navigation.Route +import com.greenart7c3.nostrsigner.ui.actions.RemoveAllPermissionsDialog import com.vitorpamplona.quartz.encoders.toHexKey -import com.vitorpamplona.quartz.encoders.toNpub import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -84,18 +78,14 @@ fun EditPermission( val uriHandler = LocalUriHandler.current val clipboardManager = LocalClipboardManager.current val context = LocalContext.current - val localAccount = LocalPreferences.loadFromEncryptedStorage(context, account.keyPair.pubKey.toNpub())!! val permissions = remember { mutableStateListOf() } var applicationData by remember { mutableStateOf(ApplicationEntity(selectedPackage, "", emptyList(), "", "", "", "", true, "", false, 1)) } - var textFieldvalue by remember(applicationData.name) { - mutableStateOf(TextFieldValue(applicationData.name)) - } - var wantsToDelete by remember { mutableStateOf(false) } + var wantsToRemovePermissions by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() var checked by remember { @@ -148,25 +138,19 @@ fun EditPermission( } } - if (wantsToDelete) { - DeleteDialog( + if (wantsToRemovePermissions) { + RemoveAllPermissionsDialog( onCancel = { - wantsToDelete = false + wantsToRemovePermissions = false }, ) { scope.launch(Dispatchers.IO) { database .applicationDao() - .delete(applicationData) + .deletePermissions(applicationData.key) - if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) { - NostrSigner.getInstance().checkForNewRelays() - } - } - - scope.launch(Dispatchers.Main) { - navController.navigateUp() - accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Applications.route) + permissions.clear() + wantsToRemovePermissions = false } } } @@ -175,20 +159,6 @@ fun EditPermission( mutableStateOf(false) } - var showActivityDialog by remember { - mutableStateOf(false) - } - - if (showActivityDialog) { - ActivityDialog( - database = database, - key = selectedPackage, - onClose = { - showActivityDialog = false - }, - ) - } - if (showDialog) { QrCodeDialog( content = bunkerUri, @@ -198,17 +168,39 @@ fun EditPermission( } Column( - modifier = modifier, + modifier = modifier + .padding(8.dp), ) { - Text( - stringResource(R.string.permissions), - Modifier - .fillMaxWidth() - .padding(8.dp), - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - fontSize = 18.sp, - ) + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + Button( + onClick = { + navController.navigate("Activity/${applicationData.key}") + }, + Modifier + .fillMaxWidth() + .padding(6.dp), + ) { + Text(stringResource(R.string.activity)) + } + } + + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + Button( + onClick = { + }, + Modifier + .fillMaxWidth() + .padding(6.dp), + ) { + Text(stringResource(R.string.edit_configuration)) + } + } if (!applicationData.isConnected) { Row( @@ -268,22 +260,16 @@ fun EditPermission( ) } } - OutlinedTextField( + + Text( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), - value = textFieldvalue.text, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, - ), - onValueChange = { - textFieldvalue = TextFieldValue(it) - }, - label = { - Text("Name") - }, + text = stringResource(R.string.edit_permissions_description), ) + Spacer(Modifier.height(8.dp)) + Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, @@ -302,19 +288,6 @@ fun EditPermission( } } } - - Row( - horizontalArrangement = Arrangement.Center, - ) { - Button( - onClick = { - showActivityDialog = true - }, - Modifier.padding(6.dp), - ) { - Text(stringResource(R.string.activity)) - } - } } LazyColumn( @@ -333,113 +306,120 @@ fun EditPermission( } else { localPermission.toLocalizedString(context) } - Row( - verticalAlignment = Alignment.CenterVertically, + ElevatedCard( modifier = Modifier - .padding(vertical = 8.dp, horizontal = 25.dp) + .padding(4.dp) .fillMaxWidth(), ) { Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .weight(1f) - .padding(end = 8.dp) - .clickable { - val localPermissions = - permissions.map { - if (it.id == permission.id) { - it.copy(acceptable = !permission.acceptable) - } else { - it.copy() - } - } - permissions.clear() - permissions.addAll(localPermissions) - }, + .padding(vertical = 8.dp, horizontal = 8.dp) + .fillMaxWidth(), ) { - Text( - modifier = Modifier.weight(1f), - text = message, - fontSize = 18.sp, - ) - Switch( - checked = permission.acceptable, - onCheckedChange = { - val localPermissions = - permissions.map { - if (it.id == permission.id) { - it.copy(acceptable = !permission.acceptable) - } else { - it.copy() + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .weight(1f) + .padding(end = 8.dp) + .clickable { + val localPermissions = + permissions.map { + if (it.id == permission.id) { + it.copy(acceptable = !permission.acceptable) + } else { + it.copy() + } } + permissions.clear() + permissions.addAll(localPermissions) + }, + ) { + Switch( + modifier = Modifier.padding(end = 8.dp), + checked = permission.acceptable, + onCheckedChange = { + val localPermissions = + permissions.map { + if (it.id == permission.id) { + it.copy(acceptable = !permission.acceptable) + } else { + it.copy() + } + } + permissions.clear() + permissions.addAll(localPermissions) + }, + ) + Text( + modifier = Modifier.weight(1f), + text = message, + fontSize = 18.sp, + ) + } + IconButton( + content = { + Icon( + Icons.Default.Delete, + stringResource(R.string.remove_permission), + modifier = Modifier + .fillMaxHeight(), + tint = Color.Red, + ) + }, + onClick = { + permissions.remove(permission) + }, + ) + IconButton( + content = { + Icon( + Icons.Default.Info, + stringResource(R.string.more_info), + modifier = Modifier + .fillMaxHeight(), + ) + }, + onClick = { + if (permission.type.toLowerCase(Locale.current) == "sign_event") { + val nip = permission.kind?.kindToNipUrl() + if (nip == null) { + Toast.makeText( + context, + context.getString(R.string.nip_not_found_for_the_event_kind, permission.kind.toString()), + Toast.LENGTH_SHORT, + ).show() + return@IconButton } - permissions.clear() - permissions.addAll(localPermissions) + uriHandler.openUri(nip) + } else if (permission.type.toLowerCase(Locale.current) == "nip") { + val nip = permission.kind?.nipToUrl() + if (nip == null) { + Toast.makeText( + context, + context.getString(R.string.nip_not_found, permission.kind.toString()), + Toast.LENGTH_SHORT, + ).show() + return@IconButton + } + uriHandler.openUri(nip) + } else if ((permission.type.toUpperCase(Locale.current) == "NIP04_ENCRYPT") || (permission.type.toUpperCase(Locale.current) == "NIP04_DECRYPT")) { + uriHandler.openUri("https://github.com/nostr-protocol/nips/blob/master/04.md") + } else if ((permission.type.toUpperCase(Locale.current) == "NIP44_ENCRYPT") || (permission.type.toUpperCase(Locale.current) == "NIP44_DECRYPT")) { + uriHandler.openUri("https://github.com/nostr-protocol/nips/blob/master/44.md") + } else if (permission.type.toLowerCase(Locale.current) == "decrypt_zap_event") { + uriHandler.openUri("https://github.com/nostr-protocol/nips/blob/master/57.md") + } else { + Toast.makeText( + context, + context.getString(R.string.no_information_available_for, localPermission.toLocalizedString(context)), + Toast.LENGTH_SHORT, + ).show() + } }, ) } - IconButton( - content = { - Icon( - Icons.Default.Delete, - stringResource(R.string.remove_permission), - modifier = Modifier - .fillMaxHeight(), - tint = Color.Red, - ) - }, - onClick = { - permissions.remove(permission) - }, - ) - IconButton( - content = { - Icon( - Icons.Default.Info, - stringResource(R.string.more_info), - modifier = Modifier - .fillMaxHeight(), - ) - }, - onClick = { - if (permission.type.toLowerCase(Locale.current) == "sign_event") { - val nip = permission.kind?.kindToNipUrl() - if (nip == null) { - Toast.makeText( - context, - context.getString(R.string.nip_not_found_for_the_event_kind, permission.kind.toString()), - Toast.LENGTH_SHORT, - ).show() - return@IconButton - } - uriHandler.openUri(nip) - } else if (permission.type.toLowerCase(Locale.current) == "nip") { - val nip = permission.kind?.nipToUrl() - if (nip == null) { - Toast.makeText( - context, - context.getString(R.string.nip_not_found, permission.kind.toString()), - Toast.LENGTH_SHORT, - ).show() - return@IconButton - } - uriHandler.openUri(nip) - } else if ((permission.type.toUpperCase(Locale.current) == "NIP04_ENCRYPT") || (permission.type.toUpperCase(Locale.current) == "NIP04_DECRYPT")) { - uriHandler.openUri("https://github.com/nostr-protocol/nips/blob/master/04.md") - } else if ((permission.type.toUpperCase(Locale.current) == "NIP44_ENCRYPT") || (permission.type.toUpperCase(Locale.current) == "NIP44_DECRYPT")) { - uriHandler.openUri("https://github.com/nostr-protocol/nips/blob/master/44.md") - } else if (permission.type.toLowerCase(Locale.current) == "decrypt_zap_event") { - uriHandler.openUri("https://github.com/nostr-protocol/nips/blob/master/57.md") - } else { - Toast.makeText( - context, - context.getString(R.string.no_information_available_for, localPermission.toLocalizedString(context)), - Toast.LENGTH_SHORT, - ).show() - } - }, - ) } } } @@ -450,63 +430,65 @@ fun EditPermission( ) { Button( onClick = { - wantsToDelete = true + wantsToRemovePermissions = true }, - Modifier.padding(6.dp), + Modifier + .fillMaxWidth() + .padding(6.dp), ) { - Text(stringResource(R.string.delete_application)) + Text(stringResource(R.string.remove_all_permissions)) } } - Row( - Modifier - .fillMaxWidth(), - Arrangement.Center, - ) { - Button( - onClick = { - if (!applicationData.isConnected) { - val relays = applicationData.relays.joinToString(separator = "&") { "relay=${it.url}" } - val localSecret = if (applicationData.useSecret) "&secret=${applicationData.secret}" else "" - val localBunkerUri = "bunker://${account.keyPair.pubKey.toHexKey()}?$relays$localSecret" - clipboardManager.setText(AnnotatedString(localBunkerUri)) - } - - navController.navigateUp() - }, - Modifier.padding(6.dp), - ) { - Text(stringResource(id = R.string.cancel)) - } - Button( - onClick = { - scope.launch(Dispatchers.IO) { - val localApplicationData = - applicationData.copy( - name = textFieldvalue.text, - useSecret = checked, - ) - database.applicationDao().delete(applicationData) - database.applicationDao().insertApplicationWithPermissions( - ApplicationWithPermissions( - localApplicationData, - permissions, - ), - ) - if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) { - NostrSigner.getInstance().checkForNewRelays() - } - - scope.launch(Dispatchers.Main) { - navController.navigateUp() - accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Applications.route) - } - } - }, - Modifier.padding(6.dp), - ) { - Text(stringResource(id = R.string.confirm)) - } - } +// Row( +// Modifier +// .fillMaxWidth(), +// Arrangement.Center, +// ) { +// Button( +// onClick = { +// if (!applicationData.isConnected) { +// val relays = applicationData.relays.joinToString(separator = "&") { "relay=${it.url}" } +// val localSecret = if (applicationData.useSecret) "&secret=${applicationData.secret}" else "" +// val localBunkerUri = "bunker://${account.keyPair.pubKey.toHexKey()}?$relays$localSecret" +// clipboardManager.setText(AnnotatedString(localBunkerUri)) +// } +// +// navController.navigateUp() +// }, +// Modifier.padding(6.dp), +// ) { +// Text(stringResource(id = R.string.cancel)) +// } +// Button( +// onClick = { +// scope.launch(Dispatchers.IO) { +// val localApplicationData = +// applicationData.copy( +// name = textFieldvalue.text, +// useSecret = checked, +// ) +// database.applicationDao().delete(applicationData) +// database.applicationDao().insertApplicationWithPermissions( +// ApplicationWithPermissions( +// localApplicationData, +// permissions, +// ), +// ) +// if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) { +// NostrSigner.getInstance().checkForNewRelays() +// } +// +// scope.launch(Dispatchers.Main) { +// navController.navigateUp() +// accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Applications.route) +// } +// } +// }, +// Modifier.padding(6.dp), +// ) { +// Text(stringResource(id = R.string.confirm)) +// } +// } } } 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 ccc75117..0f34685c 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -91,6 +91,7 @@ import com.greenart7c3.nostrsigner.service.PushNotificationUtils import com.greenart7c3.nostrsigner.service.getAppCompatActivity import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen import com.greenart7c3.nostrsigner.ui.actions.ActiveRelaysScreen +import com.greenart7c3.nostrsigner.ui.actions.ActivityScreen import com.greenart7c3.nostrsigner.ui.actions.DefaultRelaysScreen import com.greenart7c3.nostrsigner.ui.components.BackButtonScaffold import com.greenart7c3.nostrsigner.ui.components.MainScaffold @@ -1063,5 +1064,27 @@ fun MainScreen( } }, ) + + composable( + Route.Activity.route, + arguments = listOf(navArgument("key") { type = NavType.StringType }), + content = { + it.arguments?.getString("key")?.let { key -> + BackButtonScaffold( + title = stringResource(R.string.activity_title), + navController = navController, + ) { padding -> + ActivityScreen( + database = database, + key = key, + modifier = + Modifier + .fillMaxSize() + .padding(padding), + ) + } + } + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ActivityDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ActivityDialog.kt deleted file mode 100644 index a4bce26c..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ActivityDialog.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.greenart7c3.nostrsigner.ui.actions - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Close -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.toMutableStateList -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.text.toLowerCase -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.database.AppDatabase -import com.greenart7c3.nostrsigner.database.HistoryEntity -import com.greenart7c3.nostrsigner.models.Permission -import com.greenart7c3.nostrsigner.models.TimeUtils.convertLongToDateTime -import com.greenart7c3.nostrsigner.ui.components.CloseButton -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -@Composable -fun ActivityDialog( - database: AppDatabase, - key: String, - onClose: () -> Unit, -) { - val activities = - remember { - mutableStateListOf() - } - val context = LocalContext.current - - LaunchedEffect(Unit) { - launch(Dispatchers.IO) { - activities.clear() - activities.addAll( - database.applicationDao().getAllHistory(key).toMutableStateList(), - ) - } - } - - Dialog( - onDismissRequest = onClose, - properties = DialogProperties(usePlatformDefaultWidth = false), - ) { - Surface( - modifier = - Modifier - .fillMaxSize(), - ) { - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .fillMaxSize(), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - CloseButton { - onClose() - } - } - - if (activities.isEmpty()) { - Text( - stringResource(R.string.no_activities_found), - Modifier - .fillMaxWidth() - .padding(8.dp), - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - fontSize = 18.sp, - ) - } - - LazyColumn( - Modifier - .fillMaxHeight() - .fillMaxWidth(), - ) { - items(activities.size) { - val activity = activities[it] - val permission = - Permission( - activity.type.toLowerCase(Locale.current), - activity.kind, - ) - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Column( - Modifier - .weight(0.9f) - .padding(horizontal = 16.dp, vertical = 8.dp), - ) { - Text( - if (permission.type == "connect") stringResource(R.string.connect) else permission.toLocalizedString(context), - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - convertLongToDateTime(activity.time * 1000), - fontSize = 14.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - IconButton(onClick = { }) { - Icon( - if (activity.accepted) Icons.Default.Check else Icons.Default.Close, - contentDescription = if (activity.accepted) stringResource(R.string.accepted) else stringResource(R.string.rejected), - tint = if (activity.accepted) Color.Green else Color.Red, - ) - } - } - } - } - } - } - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ActivityScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ActivityScreen.kt new file mode 100644 index 00000000..1a57b569 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ActivityScreen.kt @@ -0,0 +1,139 @@ +package com.greenart7c3.nostrsigner.ui.actions + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.toLowerCase +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.greenart7c3.nostrsigner.R +import com.greenart7c3.nostrsigner.database.AppDatabase +import com.greenart7c3.nostrsigner.database.HistoryEntity +import com.greenart7c3.nostrsigner.models.Permission +import com.greenart7c3.nostrsigner.models.TimeUtils.convertLongToDateTime +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +fun ActivityScreen( + modifier: Modifier, + database: AppDatabase, + key: String, +) { + val activities = + remember { + mutableStateListOf() + } + val context = LocalContext.current + + LaunchedEffect(Unit) { + launch(Dispatchers.IO) { + activities.clear() + activities.addAll( + database.applicationDao().getAllHistory(key).toMutableStateList(), + ) + } + } + + Column( + modifier = modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxSize(), + ) { + if (activities.isEmpty()) { + Text( + stringResource(R.string.no_activities_found), + Modifier + .fillMaxWidth() + .padding(8.dp), + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + fontSize = 18.sp, + ) + } + + LazyColumn( + Modifier + .fillMaxHeight() + .fillMaxWidth(), + ) { + items(activities.size) { + val activity = activities[it] + val permission = + Permission( + activity.type.toLowerCase(Locale.current), + activity.kind, + ) + ElevatedCard( + Modifier.padding(8.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + Modifier + .weight(0.9f) + .padding(horizontal = 16.dp, vertical = 8.dp), + ) { + val textColor = if (activity.accepted) Color.Unspecified else Color.Gray + val textAlpha = if (activity.accepted) 1f else 0.5f + + Text( + if (permission.type == "connect") stringResource(R.string.connect) else permission.toLocalizedString(context), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = textColor, + modifier = Modifier.alpha(textAlpha), + ) + Text( + convertLongToDateTime(activity.time * 1000), + fontSize = 14.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = textColor, + modifier = Modifier.alpha(textAlpha), + ) + } + IconButton(onClick = { }) { + Icon( + if (activity.accepted) Icons.Default.Check else Icons.Default.Close, + contentDescription = if (activity.accepted) stringResource(R.string.accepted) else stringResource(R.string.rejected), + tint = if (activity.accepted) Color(0xFF1D8802) else Color(0xFFFF6B00), + ) + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/DeleteDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/RemoveAllPermissionsDialog.kt similarity index 78% rename from app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/DeleteDialog.kt rename to app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/RemoveAllPermissionsDialog.kt index da9b3edd..7bba950b 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/DeleteDialog.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/RemoveAllPermissionsDialog.kt @@ -8,16 +8,16 @@ import androidx.compose.ui.res.stringResource import com.greenart7c3.nostrsigner.R @Composable -fun DeleteDialog( +fun RemoveAllPermissionsDialog( onCancel: () -> Unit, onConfirm: () -> Unit, ) { AlertDialog( title = { - Text(text = stringResource(R.string.delete)) + Text(text = stringResource(R.string.remove)) }, text = { - Text(text = "Are you sure you want to remove this application?") + Text(text = "Are you sure you want to remove all permissions from this application?") }, onDismissRequest = { onCancel() @@ -28,7 +28,7 @@ fun DeleteDialog( onConfirm() }, ) { - Text(text = stringResource(R.string.delete)) + Text(text = stringResource(R.string.remove)) } }, dismissButton = { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt index f4069c4c..a7120c53 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt @@ -136,6 +136,13 @@ sealed class Route( icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) + + data object Activity : Route( + title = NostrSigner.getInstance().getString(R.string.activity), + route = "Activity/{key}", + icon = Icons.Outlined.Settings, + selectedIcon = Icons.Default.Settings, + ) } val routes = listOf( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 88b86d69..51642fe1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -105,7 +105,7 @@ Direct Connection Notification Type Select the type of notification you want to receive - Activity + Show activity Accepted Rejected Language @@ -401,4 +401,9 @@ "Name can't be empty " Your nsecbunker is ready! Use this url in your app: + Edit configuration + Manage the app permissions by temporarily disabling or completely reverting them. New permission are added by the app when required. + Remove all permissions + Remove + Activity From de4b2d087e58d2a7fcdb842172581c37af5a4dcf Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 18 Oct 2024 08:16:14 -0300 Subject: [PATCH 12/30] Fix color of dialogs --- .../nostrsigner/ui/LanguageScreen.kt | 41 +++--- .../greenart7c3/nostrsigner/ui/LoginScreen.kt | 1 + .../greenart7c3/nostrsigner/ui/LogsScreen.kt | 125 +++++++++--------- .../nostrsigner/ui/NotificationTypeScreen.kt | 105 +++++++-------- .../ui/SignPolicySettingsScreen.kt | 109 ++++++++------- .../ui/actions/AccountsBottomSheet.kt | 5 +- .../ui/actions/ConnectOrbotDialog.kt | 4 +- .../nostrsigner/ui/components/TextSpinner.kt | 1 + 8 files changed, 195 insertions(+), 196 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LanguageScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LanguageScreen.kt index a0af0df7..002ae2ce 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LanguageScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LanguageScreen.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -35,29 +34,27 @@ fun LanguageScreen( val languageList = remember { languageEntries.keys.map { TitleExplainer(it) }.toImmutableList() } val languageIndex = getLanguageIndex(languageEntries, account.language) - Surface( - modifier.fillMaxSize(), + Column( + modifier = modifier + .fillMaxSize() + .padding(10.dp), ) { - Column( - modifier = Modifier.padding(10.dp), - ) { - Column { - Box( - Modifier - .padding(8.dp), + Column { + Box( + Modifier + .padding(8.dp), + ) { + SettingsRow( + R.string.language, + R.string.language_description, + languageList, + languageIndex, ) { - SettingsRow( - R.string.language, - R.string.language_description, - languageList, - languageIndex, - ) { - account.language = languageEntries[languageList[it].title] - LocalPreferences.saveToEncryptedStorage(context, account) - AppCompatDelegate.setApplicationLocales( - LocaleListCompat.forLanguageTags(account.language), - ) - } + account.language = languageEntries[languageList[it].title] + LocalPreferences.saveToEncryptedStorage(context, account) + AppCompatDelegate.setApplicationLocales( + LocaleListCompat.forLanguageTags(account.language), + ) } } } 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 1ab1d5f2..bed0fd27 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt @@ -135,6 +135,7 @@ fun MainPage( Column( modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp), verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, ) { ElevatedButton( onClick = { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LogsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LogsScreen.kt index 1f2628fd..8dbc1207 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LogsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LogsScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Button import androidx.compose.material3.Card -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope @@ -35,73 +34,73 @@ fun LogsScreen( modifier: Modifier = Modifier, ) { val scope = rememberCoroutineScope() - Surface(modifier.fillMaxSize()) { - Column( - modifier = Modifier.padding(10.dp), - ) { - val logsFlow = NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()).applicationDao().getLogs() - val logs = logsFlow.collectAsStateWithLifecycle(initialValue = emptyList()) + Column( + modifier = modifier + .fillMaxSize() + .padding(10.dp), + ) { + val logsFlow = NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()).applicationDao().getLogs() + val logs = logsFlow.collectAsStateWithLifecycle(initialValue = emptyList()) - LazyColumn( - Modifier.weight(1f), - ) { - items(logs.value.size) { index -> - Card( - Modifier - .fillMaxWidth() - .padding(6.dp), - ) { - Column(Modifier.padding(6.dp)) { - val log = logs.value[index] - Text( - buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append("Date: ") - } - append(TimeUtils.convertLongToDateTime(log.time)) - }, - ) - Text( - buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append("URL: ") - } - append(log.url) - }, - ) - Text( - buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append("Type: ") - } - append(log.type) - }, - ) - Text( - buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append("Message: ") - } - append(log.message) - }, - ) - } + LazyColumn( + Modifier.weight(1f), + ) { + items(logs.value.size) { index -> + Card( + Modifier + .fillMaxWidth() + .padding(6.dp), + ) { + Column(Modifier.padding(6.dp)) { + val log = logs.value[index] + Text( + buildAnnotatedString { + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append("Date: ") + } + append(TimeUtils.convertLongToDateTime(log.time)) + }, + ) + Text( + buildAnnotatedString { + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append("URL: ") + } + append(log.url) + }, + ) + Text( + buildAnnotatedString { + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append("Type: ") + } + append(log.type) + }, + ) + Text( + buildAnnotatedString { + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append("Message: ") + } + append(log.message) + }, + ) } } } - Box( - Modifier.fillMaxWidth(), - contentAlignment = Alignment.Center, + } + Box( + Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Button( + onClick = { + scope.launch(Dispatchers.IO) { + NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()).applicationDao().clearLogs() + } + }, ) { - Button( - onClick = { - scope.launch(Dispatchers.IO) { - NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()).applicationDao().clearLogs() - } - }, - ) { - Text(text = stringResource(R.string.clear_logs)) - } + Text(text = stringResource(R.string.clear_logs)) } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NotificationTypeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NotificationTypeScreen.kt index 0eee0801..ee61e6cb 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NotificationTypeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NotificationTypeScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -69,70 +68,68 @@ fun NotificationTypeScreen( stringResource(R.string.direct_notifications_explainer), ), ) - Surface( - modifier.fillMaxSize(), + Column( + modifier = modifier + .fillMaxSize() + .padding(10.dp), ) { Column( - modifier = Modifier.padding(10.dp), + Modifier.weight(1f), ) { - Column( - Modifier.weight(1f), + Box( + Modifier + .padding(8.dp), ) { + SettingsRow( + R.string.notification_type, + R.string.select_the_type_of_notification_you_want_to_receive, + notificationItems, + notificationItemsIndex, + ) { + notificationItemsIndex = it + } + } + + @Suppress("KotlinConstantConditions") + if (BuildConfig.FLAVOR == "free") { Box( Modifier .padding(8.dp), ) { - SettingsRow( - R.string.notification_type, - R.string.select_the_type_of_notification_you_want_to_receive, - notificationItems, - notificationItemsIndex, - ) { - notificationItemsIndex = it - } - } - - @Suppress("KotlinConstantConditions") - if (BuildConfig.FLAVOR == "free") { - Box( - Modifier - .padding(8.dp), - ) { - PushNotificationSettingsRow() - } + PushNotificationSettingsRow() } } - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), + } + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + PostButton( + isActive = true, ) { - PostButton( - isActive = true, - ) { - scope.launch(Dispatchers.IO) { - NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( - notificationType = parseNotificationType(notificationItemsIndex), - ) - LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) - PushNotificationUtils.hasInit = false - PushNotificationUtils.init(LocalPreferences.allSavedAccounts(context)) - if (notificationItemsIndex == 0) { - NotificationDataSource.stopSync() - RelayPool.disconnect() - } else { - NostrSigner.getInstance().checkForNewRelays() - NotificationDataSource.start() - } - NostrSigner.getInstance().applicationContext.startForegroundService( - Intent( - NostrSigner.getInstance().applicationContext, - ConnectivityService::class.java, - ), - ) - scope.launch(Dispatchers.Main) { - onDone() - } + scope.launch(Dispatchers.IO) { + NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( + notificationType = parseNotificationType(notificationItemsIndex), + ) + LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) + PushNotificationUtils.hasInit = false + PushNotificationUtils.init(LocalPreferences.allSavedAccounts(context)) + if (notificationItemsIndex == 0) { + NotificationDataSource.stopSync() + RelayPool.disconnect() + } else { + NostrSigner.getInstance().checkForNewRelays() + NotificationDataSource.start() + } + NostrSigner.getInstance().applicationContext.startForegroundService( + Intent( + NostrSigner.getInstance().applicationContext, + ConnectivityService::class.java, + ), + ) + scope.launch(Dispatchers.Main) { + onDone() } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SignPolicySettingsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SignPolicySettingsScreen.kt index c1e727fb..ce15f36a 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SignPolicySettingsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SignPolicySettingsScreen.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -56,73 +55,73 @@ fun SignPolicySettingsScreen( val scope = rememberCoroutineScope() val context = LocalContext.current - Surface(modifier.fillMaxSize()) { - Column( - modifier = Modifier.padding(10.dp), + Column( + modifier = modifier + .fillMaxSize() + .padding(10.dp), + ) { + LazyColumn( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, ) { - LazyColumn( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - itemsIndexed(radioOptions) { index, option -> - Row( - Modifier - .fillMaxWidth() - .selectable( - selected = selectedOption == index, - onClick = { - selectedOption = index - }, - ) - .border( - width = 1.dp, - color = if (selectedOption == index) { - MaterialTheme.colorScheme.primary - } else { - Color.Transparent - }, - shape = RoundedCornerShape(8.dp), - ) - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - RadioButton( + itemsIndexed(radioOptions) { index, option -> + Row( + Modifier + .fillMaxWidth() + .selectable( selected = selectedOption == index, onClick = { selectedOption = index }, ) - Column( - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { + .border( + width = 1.dp, + color = if (selectedOption == index) { + MaterialTheme.colorScheme.primary + } else { + Color.Transparent + }, + shape = RoundedCornerShape(8.dp), + ) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + RadioButton( + selected = selectedOption == index, + onClick = { + selectedOption = index + }, + ) + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = option.title, + modifier = Modifier.padding(start = 16.dp), + style = MaterialTheme.typography.titleLarge, + ) + option.explainer?.let { Text( - text = option.title, + text = it, modifier = Modifier.padding(start = 16.dp), - style = MaterialTheme.typography.titleLarge, ) - option.explainer?.let { - Text( - text = it, - modifier = Modifier.padding(start = 16.dp), - ) - } } } } } - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - ) { - PostButton(isActive = true) { - scope.launch(Dispatchers.IO) { - account.signPolicy = selectedOption - LocalPreferences.saveToEncryptedStorage(context, account) - scope.launch(Dispatchers.Main) { - navController.navigateUp() - } + } + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + PostButton(isActive = true) { + scope.launch(Dispatchers.IO) { + account.signPolicy = selectedOption + LocalPreferences.saveToEncryptedStorage(context, account) + scope.launch(Dispatchers.Main) { + navController.navigateUp() } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountsBottomSheet.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountsBottomSheet.kt index e0037eca..d6d9b712 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountsBottomSheet.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountsBottomSheet.kt @@ -160,7 +160,10 @@ fun AccountsBottomSheet( onDismissRequest = { popupExpanded = false }, properties = DialogProperties(usePlatformDefaultWidth = false), ) { - Surface(modifier = Modifier.fillMaxSize()) { + Surface( + color = MaterialTheme.colorScheme.background, + modifier = Modifier.fillMaxSize(), + ) { Box { MainLoginPage(accountStateViewModel) TopAppBar( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ConnectOrbotDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ConnectOrbotDialog.kt index 3e2bdfbb..1012b54b 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ConnectOrbotDialog.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/ConnectOrbotDialog.kt @@ -71,7 +71,9 @@ fun ConnectOrbotDialog( onDismissRequest = onClose, properties = DialogProperties(usePlatformDefaultWidth = false, decorFitsSystemWindows = false), ) { - Surface { + Surface( + color = MaterialTheme.colorScheme.background, + ) { Column( modifier = Modifier.padding(10.dp), ) { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/TextSpinner.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/TextSpinner.kt index c29c5d8b..8e0755fb 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/TextSpinner.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/TextSpinner.kt @@ -164,6 +164,7 @@ fun SpinnerSelectionDialog( ) { Dialog(onDismissRequest = onDismiss) { Surface( + color = MaterialTheme.colorScheme.background, border = BorderStroke(0.25.dp, Color.LightGray), shape = RoundedCornerShape(5.dp), ) { From 42ef11d81227141b3b991ecd051787121c835e73 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 18 Oct 2024 09:58:00 -0300 Subject: [PATCH 13/30] new design for the relay logs --- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 25 +++ .../ui/actions/EditRelaysDialog.kt | 144 ++++++++---------- .../nostrsigner/ui/navigation/Route.kt | 7 + 3 files changed, 94 insertions(+), 82 deletions(-) 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 0f34685c..d4c3a3df 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -93,6 +93,7 @@ import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen import com.greenart7c3.nostrsigner.ui.actions.ActiveRelaysScreen import com.greenart7c3.nostrsigner.ui.actions.ActivityScreen import com.greenart7c3.nostrsigner.ui.actions.DefaultRelaysScreen +import com.greenart7c3.nostrsigner.ui.actions.RelayLogScreen import com.greenart7c3.nostrsigner.ui.components.BackButtonScaffold import com.greenart7c3.nostrsigner.ui.components.MainScaffold import com.greenart7c3.nostrsigner.ui.navigation.Route @@ -890,6 +891,7 @@ fun MainScreen( false, ) { padding -> ActiveRelaysScreen( + navController = navController, modifier = Modifier .fillMaxSize() @@ -1086,5 +1088,28 @@ fun MainScreen( } }, ) + + composable( + Route.RelayLogScreen.route, + arguments = listOf(navArgument("url") { type = NavType.StringType }), + content = { + it.arguments?.getString("url")?.let { url -> + val localUrl = Base64.getDecoder().decode(url).toString(Charsets.UTF_8) + + BackButtonScaffold( + title = localUrl, + navController = navController, + ) { padding -> + RelayLogScreen( + url = localUrl, + modifier = + Modifier + .fillMaxSize() + .padding(padding), + ) + } + } + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt index e4996c17..432a9ce9 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt @@ -18,7 +18,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Circle import androidx.compose.material.icons.filled.Delete -import androidx.compose.material3.Card +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -74,6 +74,7 @@ import com.vitorpamplona.quartz.encoders.RelayUrlFormatter import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.signers.NostrSignerInternal +import java.util.Base64 import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -112,6 +113,7 @@ fun EditRelaysDialog( properties = DialogProperties(usePlatformDefaultWidth = false), ) { Surface( + color = MaterialTheme.colorScheme.background, modifier = Modifier .fillMaxSize(), ) { @@ -263,6 +265,7 @@ fun DefaultRelaysScreen( } Surface( + color = MaterialTheme.colorScheme.background, modifier = modifier .fillMaxSize(), ) { @@ -550,78 +553,67 @@ suspend fun onAddRelay( } @Composable -fun RelayLogDialog( +fun RelayLogScreen( + modifier: Modifier, url: String, - onClose: () -> Unit, ) { val context = LocalContext.current - Dialog( - onDismissRequest = onClose, - properties = DialogProperties(usePlatformDefaultWidth = false), + + Surface( + modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, ) { - Surface(Modifier.fillMaxSize()) { - Column( - modifier = Modifier.padding(10.dp), + Column( + modifier = Modifier.padding(10.dp), + ) { + val flows = LocalPreferences.allSavedAccounts(context).map { + NostrSigner.getInstance().getDatabase(it.npub).applicationDao().getLogsByUrl(url) + }.merge() + + val logs = flows.collectAsStateWithLifecycle(initialValue = emptyList()) + + LazyColumn( + Modifier.weight(1f), ) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - ) { - CloseButton( - onCancel = onClose, - ) - } - - val flows = LocalPreferences.allSavedAccounts(context).map { - NostrSigner.getInstance().getDatabase(it.npub).applicationDao().getLogsByUrl(url) - }.merge() - - val logs = flows.collectAsStateWithLifecycle(initialValue = emptyList()) - - LazyColumn( - Modifier.weight(1f), - ) { - itemsIndexed(logs.value) { _, log -> - Card( - Modifier - .fillMaxWidth() - .padding(6.dp), - ) { - Column(Modifier.padding(6.dp)) { - Text( - buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append("Date: ") - } - append(TimeUtils.convertLongToDateTime(log.time)) - }, - ) - Text( - buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append("URL: ") - } - append(log.url) - }, - ) - Text( - buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append("Type: ") - } - append(log.type) - }, - ) - Text( - buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append("Message: ") - } - append(log.message) - }, - ) - } + itemsIndexed(logs.value) { _, log -> + ElevatedCard( + Modifier + .fillMaxWidth() + .padding(6.dp), + ) { + Column(Modifier.padding(6.dp)) { + Text( + buildAnnotatedString { + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append("Date: ") + } + append(TimeUtils.convertLongToDateTime(log.time)) + }, + ) + Text( + buildAnnotatedString { + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append("URL: ") + } + append(log.url) + }, + ) + Text( + buildAnnotatedString { + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append("Type: ") + } + append(log.type) + }, + ) + Text( + buildAnnotatedString { + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append("Message: ") + } + append(log.message) + }, + ) } } } @@ -633,31 +625,19 @@ fun RelayLogDialog( @Composable fun ActiveRelaysScreen( modifier: Modifier, + navController: NavController, ) { val relays2 = remember { mutableStateListOf() } - var relayLogUrl by remember { - mutableStateOf("") - } - LaunchedEffect(Unit) { withContext(Dispatchers.IO) { relays2.addAll(NostrSigner.getInstance().getSavedRelays()) } } - if (relayLogUrl.isNotBlank()) { - RelayLogDialog( - url = relayLogUrl, - onClose = { - relayLogUrl = "" - }, - ) - } - Surface( modifier = modifier .fillMaxSize(), @@ -677,7 +657,7 @@ fun ActiveRelaysScreen( Modifier .padding(6.dp) .clickable { - relayLogUrl = relays2[it].url + navController.navigate("RelayLogScreen/${Base64.getEncoder().encodeToString(relays2[it].url.toByteArray())}") }, verticalAlignment = Alignment.CenterVertically, ) { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt index a7120c53..23bed4a0 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt @@ -143,6 +143,13 @@ sealed class Route( icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) + + data object RelayLogScreen : Route( + title = NostrSigner.getInstance().getString(R.string.relays), + route = "RelayLogScreen/{url}", + icon = Icons.Outlined.Settings, + selectedIcon = Icons.Default.Settings, + ) } val routes = listOf( From b09c972854ec80261b2d9222c95bedea9c81472f Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 18 Oct 2024 12:33:44 -0300 Subject: [PATCH 14/30] finish edit configuration button --- .../nostrsigner/ui/EditConfigurationScreen.kt | 274 ++++++++++++++++++ .../nostrsigner/ui/EditPermission.kt | 109 ++----- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 61 +++- .../nostrsigner/ui/navigation/Route.kt | 11 + app/src/main/res/values/strings.xml | 2 + 5 files changed, 359 insertions(+), 98 deletions(-) create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt new file mode 100644 index 00000000..08f63ad4 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt @@ -0,0 +1,274 @@ +package com.greenart7c3.nostrsigner.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import com.greenart7c3.nostrsigner.NostrSigner +import com.greenart7c3.nostrsigner.R +import com.greenart7c3.nostrsigner.database.AppDatabase +import com.greenart7c3.nostrsigner.database.ApplicationWithPermissions +import com.greenart7c3.nostrsigner.models.Account +import com.greenart7c3.nostrsigner.ui.actions.onAddRelay +import com.greenart7c3.nostrsigner.ui.navigation.Route +import com.vitorpamplona.ammolite.relays.RelaySetupInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +fun EditConfigurationScreen( + modifier: Modifier = Modifier, + database: AppDatabase, + key: String, + accountStateViewModel: AccountStateViewModel, + account: Account, + navController: NavController, +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + var application by remember { mutableStateOf(null) } + var name by remember { mutableStateOf(TextFieldValue(AnnotatedString(""))) } + val relays = remember { mutableStateListOf() } + val textFieldRelay = remember { mutableStateOf(TextFieldValue(AnnotatedString(""))) } + val isLoading = remember { mutableStateOf(true) } + LaunchedEffect(Unit) { + launch(Dispatchers.IO) { + application = database.applicationDao().getByKey(key) + name = TextFieldValue(AnnotatedString(application?.application?.name ?: "")) + + application?.application?.relays?.forEach { + relays.add( + it.copy(), + ) + } + + isLoading.value = false + } + } + + if (isLoading.value) { + CenterCircularProgressIndicator(modifier) + } else { + Column( + modifier + .fillMaxSize() + .padding(16.dp), + ) { + Text(stringResource(R.string.edit_configuration_description)) + + Spacer(modifier = Modifier.height(16.dp)) + + OutlinedTextField( + value = name, + onValueChange = { name = it }, + label = { Text(stringResource(R.string.name)) }, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth(0.9f) + .padding(end = 16.dp), + value = textFieldRelay.value.text, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, + ), + onValueChange = { + textFieldRelay.value = TextFieldValue(it) + }, + keyboardActions = KeyboardActions( + onDone = { + scope.launch(Dispatchers.IO) { + onAddRelay( + textFieldRelay, + isLoading, + relays, + scope, + accountStateViewModel, + account, + context, + ) + } + }, + ), + label = { + Text(stringResource(R.string.wss)) + }, + ) + IconButton( + onClick = { + scope.launch(Dispatchers.IO) { + onAddRelay( + textFieldRelay, + isLoading, + relays, + scope, + accountStateViewModel, + account, + context, + ) + } + }, + ) { + Icon( + Icons.Default.Add, + null, + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + if ((application?.application?.secret?.isNotEmpty() == true || application?.application?.relays?.isNotEmpty() == true) && application?.application?.isConnected == false) { + LazyColumn( + Modifier.weight(1f), + ) { + items(relays.size) { + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + Text( + relays[it].url, + Modifier + .weight(0.9f) + .padding(8.dp), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + IconButton( + onClick = { + relays.removeAt(it) + }, + ) { + Icon( + Icons.Default.Delete, + stringResource(R.string.delete), + ) + } + } + } + } + } + + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + Button( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + onClick = { + application?.let { + scope.launch(Dispatchers.IO) { + val localApplicationData = + it.application.copy( + name = name.text, + relays = relays, + ) + database.applicationDao().delete(it.application) + database.applicationDao().insertApplicationWithPermissions( + ApplicationWithPermissions( + localApplicationData, + it.permissions, + ), + ) + if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) { + NostrSigner.getInstance().checkForNewRelays() + } + + scope.launch(Dispatchers.Main) { + navController.navigate(Route.Applications.route) { + popUpTo(0) + } + } + } + } + }, + content = { + Text(stringResource(R.string.update)) + }, + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + Button( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + onClick = { + application?.let { + scope.launch(Dispatchers.IO) { + database.applicationDao().delete(it.application) + + if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) { + NostrSigner.getInstance().checkForNewRelays() + } + + scope.launch(Dispatchers.Main) { + navController.navigate(Route.Applications.route) { + popUpTo(0) + } + } + } + } + }, + content = { + Text(stringResource(R.string.delete_application)) + }, + ) + } + } + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt index 48d3e1e1..ab6b809e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -14,10 +14,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ContentPaste import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Info -import androidx.compose.material.icons.filled.QrCode import androidx.compose.material3.Button import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon @@ -40,10 +38,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.toLowerCase import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.unit.dp @@ -59,7 +55,6 @@ import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.models.kindToNipUrl import com.greenart7c3.nostrsigner.models.nipToUrl import com.greenart7c3.nostrsigner.ui.actions.EditRelaysDialog -import com.greenart7c3.nostrsigner.ui.actions.QrCodeDialog import com.greenart7c3.nostrsigner.ui.actions.RemoveAllPermissionsDialog import com.vitorpamplona.quartz.encoders.toHexKey import kotlinx.coroutines.Dispatchers @@ -111,14 +106,6 @@ fun EditPermission( } } - LaunchedEffect(bunkerUri) { - launch(Dispatchers.IO) { - if (!applicationData.isConnected) { - clipboardManager.setText(AnnotatedString(bunkerUri)) - } - } - } - if (editRelaysDialog) { EditRelaysDialog( applicationData = applicationData, @@ -155,22 +142,34 @@ fun EditPermission( } } - var showDialog by remember { - mutableStateOf(false) - } - - if (showDialog) { - QrCodeDialog( - content = bunkerUri, - ) { - showDialog = false - } - } - Column( modifier = modifier .padding(8.dp), ) { + if (!applicationData.isConnected) { + Text( + bunkerUri, + Modifier + .padding(8.dp), + textAlign = TextAlign.Start, + fontSize = 18.sp, + ) + + Button( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + onClick = { + clipboardManager.setText(AnnotatedString(bunkerUri)) + }, + content = { + Text(stringResource(R.string.copy_to_clipboard)) + }, + ) + + Spacer(Modifier.height(12.dp)) + } + Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, @@ -193,6 +192,7 @@ fun EditPermission( ) { Button( onClick = { + navController.navigate("EditConfiguration/${applicationData.key}") }, Modifier .fillMaxWidth() @@ -202,65 +202,6 @@ fun EditPermission( } } - if (!applicationData.isConnected) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - bunkerUri, - Modifier - .weight(0.9f) - .padding(8.dp), - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - fontSize = 18.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - IconButton( - onClick = { - clipboardManager.setText(AnnotatedString(bunkerUri)) - }, - ) { - Icon(Icons.Default.ContentPaste, stringResource(R.string.copy_to_clipboard)) - } - IconButton( - onClick = { - showDialog = true - }, - ) { - Icon(Icons.Default.QrCode, stringResource(R.string.show_qr_code)) - } - } - - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .padding(horizontal = 8.dp) - .clickable { - checked = !checked - val relays = applicationData.relays.joinToString(separator = "&") { "relay=${it.url}" } - val localSecret = if (checked) "&secret=${applicationData.secret}" else "" - bunkerUri = "bunker://${account.keyPair.pubKey.toHexKey()}?$relays$localSecret" - }, - ) { - Text( - modifier = Modifier.weight(1f), - text = stringResource(R.string.use_secret_to_connect_to_the_application), - ) - Switch( - checked = checked, - onCheckedChange = { - checked = !checked - val relays = applicationData.relays.joinToString(separator = "&") { "relay=${it.url}" } - val localSecret = if (checked) "&secret=${applicationData.secret}" else "" - bunkerUri = "bunker://${account.keyPair.pubKey.toHexKey()}?$relays$localSecret" - }, - ) - } - } - Text( modifier = Modifier .fillMaxWidth() 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 d4c3a3df..6830f3c5 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -842,21 +842,30 @@ fun MainScreen( Route.Permission.route, arguments = listOf(navArgument("packageName") { type = NavType.StringType }), content = { - BackButtonScaffold( - title = stringResource(R.string.permissions), - navController = navController, - ) { padding -> - EditPermission( - modifier = - Modifier - .fillMaxSize() - .padding(padding), - account = account, - accountStateViewModel = accountStateViewModel, - selectedPackage = it.arguments?.getString("packageName")!!, + it.arguments?.getString("packageName")?.let { packageName -> + var title by remember { mutableStateOf(packageName) } + LaunchedEffect(Unit) { + launch(Dispatchers.IO) { + title = database.applicationDao().getByKey(packageName)?.application?.name ?: packageName + } + } + + BackButtonScaffold( + title = title, navController = navController, - database = database, - ) + ) { padding -> + EditPermission( + modifier = + Modifier + .fillMaxSize() + .padding(padding), + account = account, + accountStateViewModel = accountStateViewModel, + selectedPackage = packageName, + navController = navController, + database = database, + ) + } } }, ) @@ -1111,5 +1120,29 @@ fun MainScreen( } }, ) + + composable( + Route.EditConfiguration.route, + arguments = listOf(navArgument("key") { type = NavType.StringType }), + content = { + it.arguments?.getString("key")?.let { key -> + BackButtonScaffold( + title = stringResource(R.string.edit_configuration), + navController = navController, + ) { padding -> + EditConfigurationScreen( + modifier = Modifier + .fillMaxSize() + .padding(padding), + database = database, + key = key, + accountStateViewModel = accountStateViewModel, + account = account, + navController = navController, + ) + } + } + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt index 23bed4a0..e7066a1e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt @@ -150,6 +150,13 @@ sealed class Route( icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) + + data object EditConfiguration : Route( + title = NostrSigner.getInstance().getString(R.string.edit_configuration), + route = "EditConfiguration/{key}", + icon = Icons.Outlined.Settings, + selectedIcon = Icons.Default.Settings, + ) } val routes = listOf( @@ -168,4 +175,8 @@ val routes = listOf( Route.Accounts, Route.NewApplication, Route.NewNsecBunker, + Route.NSecBunkerCreated, + Route.Activity, + Route.RelayLogScreen, + Route.EditConfiguration, ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 51642fe1..c69e9738 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -406,4 +406,6 @@ Remove all permissions Remove Activity + To create a new nsecbunker you need to give it a name and select one or more relays, that’s all! + Update From be01a09bd97fad716837673e1af108ef64934bdc Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 18 Oct 2024 12:59:09 -0300 Subject: [PATCH 15/30] create a shouldShowRelays function --- .../com/greenart7c3/nostrsigner/database/ApplicationEntity.kt | 4 ++++ .../com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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 f749deda..2723a728 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationEntity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationEntity.kt @@ -53,6 +53,10 @@ data class ApplicationEntity( signPolicy = 0, ) } + + fun shouldShowRelays(): Boolean { + return (secret.isNotEmpty() || relays.isNotEmpty()) && !isConnected + } } data class ApplicationWithPermissions( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt index 08f63ad4..e472d5cc 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt @@ -161,7 +161,7 @@ fun EditConfigurationScreen( Spacer(modifier = Modifier.height(16.dp)) - if ((application?.application?.secret?.isNotEmpty() == true || application?.application?.relays?.isNotEmpty() == true) && application?.application?.isConnected == false) { + if (application?.application?.shouldShowRelays() == true) { LazyColumn( Modifier.weight(1f), ) { From 515b96ef8ba757b9c663c972f58b9ecfa156d55a Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 18 Oct 2024 13:04:31 -0300 Subject: [PATCH 16/30] Remove relays button --- .../nostrsigner/ui/EditPermission.kt | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt index ab6b809e..a71c5a7b 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -211,26 +211,6 @@ fun EditPermission( Spacer(Modifier.height(8.dp)) - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - if (applicationData.secret.isNotEmpty() || applicationData.relays.isNotEmpty()) { - Row( - horizontalArrangement = Arrangement.Center, - ) { - Button( - onClick = { - editRelaysDialog = true - }, - Modifier.padding(6.dp), - ) { - Text(stringResource(R.string.relays)) - } - } - } - } - LazyColumn( Modifier.weight(1f), ) { From 51f466177446b43fe7812902af11f7c78b8936ca Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 21 Oct 2024 08:15:47 -0300 Subject: [PATCH 17/30] Fix bottom and title recomposition --- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 728 ++++++------------ .../nostrsigner/ui/SecurityScreen.kt | 6 +- .../ui/components/BackButtonScaffold.kt | 54 -- .../nostrsigner/ui/components/MainScaffold.kt | 157 ---- .../nostrsigner/ui/navigation/Route.kt | 18 +- 5 files changed, 254 insertions(+), 709 deletions(-) delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt 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 6830f3c5..350a404e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -10,36 +10,30 @@ import android.widget.Toast import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.ContentPaste -import androidx.compose.material.icons.filled.QrCode import androidx.compose.material3.AlertDialog +import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Button +import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon -import androidx.compose.material3.Surface +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SheetValue import androidx.compose.material3.Text -import androidx.compose.material3.TooltipBox -import androidx.compose.material3.TooltipDefaults -import androidx.compose.material3.rememberTooltipState +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState @@ -50,10 +44,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ClipboardManager -import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -90,15 +82,15 @@ import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.service.PushNotificationUtils import com.greenart7c3.nostrsigner.service.getAppCompatActivity import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen +import com.greenart7c3.nostrsigner.ui.actions.AccountsBottomSheet import com.greenart7c3.nostrsigner.ui.actions.ActiveRelaysScreen import com.greenart7c3.nostrsigner.ui.actions.ActivityScreen import com.greenart7c3.nostrsigner.ui.actions.DefaultRelaysScreen import com.greenart7c3.nostrsigner.ui.actions.RelayLogScreen -import com.greenart7c3.nostrsigner.ui.components.BackButtonScaffold -import com.greenart7c3.nostrsigner.ui.components.MainScaffold +import com.greenart7c3.nostrsigner.ui.components.IconRow import com.greenart7c3.nostrsigner.ui.navigation.Route +import com.greenart7c3.nostrsigner.ui.navigation.routes import com.vitorpamplona.quartz.encoders.toHexKey -import com.vitorpamplona.quartz.encoders.toNpub import java.io.ByteArrayOutputStream import java.util.Base64 import java.util.zip.GZIPOutputStream @@ -350,260 +342,20 @@ fun sendResult( } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun PermissionsFloatingActionButton( - accountStateViewModel: AccountStateViewModel, - account: Account, - goToTop: () -> Unit, onClick: () -> Unit, ) { - val clipboardManager = LocalClipboardManager.current - var expanded by remember { mutableStateOf(false) } - var dialogOpen by remember { mutableStateOf(false) } - val context = LocalContext.current - - if (dialogOpen) { - SimpleQrCodeScanner { - dialogOpen = false - if (!it.isNullOrEmpty()) { - val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(it) - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) - context.getAppCompatActivity()?.startActivity(intent) - accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.IncomingRequest.route) - } - } - } - Column( horizontalAlignment = Alignment.End, ) { - AnimatedVisibility(visible = expanded) { - Column( - horizontalAlignment = Alignment.End, - ) { - Row( - Modifier - .padding(end = 10.dp) - .clickable { - clipboardManager.getText()?.let { - if (it.text.isBlank()) { - Toast.makeText( - context, - context.getString(R.string.invalid_nostr_connect_uri), - Toast.LENGTH_SHORT, - ).show() - return@let - } - if (!it.text.startsWith("nostrconnect://")) { - Toast.makeText( - context, - context.getString(R.string.invalid_nostr_connect_uri), - Toast.LENGTH_SHORT, - ).show() - return@let - } - - val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(it.text) - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) - context.getAppCompatActivity()?.startActivity(intent) - accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.IncomingRequest.route) - } - expanded = false - }, - verticalAlignment = Alignment.CenterVertically, - ) { - val tooltipState = rememberTooltipState() - val positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider() - - TooltipBox( - content = { - Surface( - shape = TooltipDefaults.richTooltipContainerShape, - color = TooltipDefaults.richTooltipColors().containerColor, - ) { - Column( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - Text( - stringResource(R.string.paste_from_clipboard), - modifier = Modifier.padding(vertical = 4.dp), - ) - } - } - }, - state = tooltipState, - positionProvider = positionProvider, - tooltip = {}, - ) - - Spacer(modifier = Modifier.size(4.dp)) - FloatingActionButton( - onClick = { - clipboardManager.getText()?.let { - if (it.text.isBlank()) { - Toast.makeText( - context, - context.getString(R.string.invalid_nostr_connect_uri), - Toast.LENGTH_SHORT, - ).show() - return@let - } - if (!it.text.startsWith("nostrconnect://")) { - Toast.makeText( - context, - context.getString(R.string.invalid_nostr_connect_uri), - Toast.LENGTH_SHORT, - ).show() - return@let - } - - val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(it.text) - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) - context.getAppCompatActivity()?.startActivity(intent) - accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.IncomingRequest.route) - } - expanded = false - }, - modifier = Modifier.size(35.dp), - shape = CircleShape, - ) { - Icon( - Icons.Default.ContentPaste, - contentDescription = stringResource(R.string.paste_from_clipboard), - tint = Color.White, - ) - } - } - - Spacer(modifier = Modifier.height(20.dp)) - - Row( - Modifier - .padding(end = 10.dp) - .clickable { - dialogOpen = true - expanded = false - }, - verticalAlignment = Alignment.CenterVertically, - ) { - val tooltipState = rememberTooltipState() - val positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider() - - TooltipBox( - content = { - Surface( - shape = TooltipDefaults.richTooltipContainerShape, - color = TooltipDefaults.richTooltipColors().containerColor, - ) { - Column( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - Text( - stringResource(R.string.connect_app), - modifier = Modifier.padding(vertical = 4.dp), - ) - } - } - }, - state = tooltipState, - positionProvider = positionProvider, - tooltip = { }, - ) - - Spacer(modifier = Modifier.size(4.dp)) - FloatingActionButton( - onClick = { - dialogOpen = true - expanded = false - }, - modifier = Modifier.size(35.dp), - shape = CircleShape, - ) { - Icon( - Icons.Default.QrCode, - contentDescription = stringResource(R.string.connect_app), - tint = Color.White, - ) - } - } - - Spacer(modifier = Modifier.height(20.dp)) - - Row( - Modifier - .padding(end = 10.dp) - .clickable { - goToTop() - expanded = false - }, - verticalAlignment = Alignment.CenterVertically, - ) { - val tooltipState = rememberTooltipState() - val positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider() - - TooltipBox( - content = { - Surface( - shape = TooltipDefaults.richTooltipContainerShape, - color = TooltipDefaults.richTooltipColors().containerColor, - ) { - Column( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - Text( - stringResource(R.string.new_app), - modifier = Modifier.padding(vertical = 4.dp), - ) - } - } - }, - state = tooltipState, - positionProvider = positionProvider, - tooltip = { }, - ) - - Spacer(modifier = Modifier.size(4.dp)) - FloatingActionButton( - onClick = { - goToTop() - expanded = false - }, - modifier = Modifier.size(35.dp), - shape = CircleShape, - ) { - Icon( - Icons.Default.Add, - contentDescription = stringResource(R.string.new_app), - tint = Color.White, - ) - } - } - - Spacer(modifier = Modifier.height(20.dp)) - } - } - - val rotation by animateFloatAsState( - targetValue = if (expanded) 0f else 180f, - animationSpec = tween( - durationMillis = 150, - easing = LinearEasing, - ), - label = "rotation", - ) - FloatingActionButton( onClick = onClick, shape = CircleShape, ) { Icon( - if (expanded) Icons.Default.Close else Icons.Default.Add, + Icons.Default.Add, contentDescription = stringResource(R.string.connect_app), - modifier = Modifier.rotate(rotation), tint = Color.White, ) } @@ -644,6 +396,7 @@ private suspend fun initNotifications(context: Context) { PushNotificationUtils.init(LocalPreferences.allSavedAccounts(context)) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun MainScreen( account: Account, @@ -743,23 +496,133 @@ fun MainScreen( } } - NavHost( - navController, - startDestination = localRoute, - enterTransition = { fadeIn(animationSpec = tween(200)) }, - exitTransition = { fadeOut(animationSpec = tween(200)) }, - ) { - composable( - Route.IncomingRequest.route, - content = { - MainScaffold( - accountStateViewModel, - account, - database, - navController, - destinationRoute, - false, - ) { padding -> + val items = mutableListOf(Route.Applications, Route.IncomingRequest, Route.ActiveRelays, Route.Settings, Route.Accounts) + @Suppress("KotlinConstantConditions") + if (BuildConfig.FLAVOR == "offline") { + items.remove(Route.ActiveRelays) + } + + var shouldShowBottomSheet by remember { mutableStateOf(false) } + val sheetState = + rememberModalBottomSheetState( + confirmValueChange = { it != SheetValue.PartiallyExpanded }, + skipPartiallyExpanded = true, + ) + + if (shouldShowBottomSheet) { + AccountsBottomSheet( + sheetState = sheetState, + account = account, + accountStateViewModel = accountStateViewModel, + onClose = { + scope.launch { + shouldShowBottomSheet = false + sheetState.hide() + } + }, + ) + } + + Scaffold( + topBar = { + CenterAlignedTopAppBar( + title = { + var title by remember { mutableStateOf(routes.find { it.route.startsWith(destinationRoute) }?.title ?: "") } + LaunchedEffect(destinationRoute) { + if (destinationRoute.startsWith("Permission/")) { + launch(Dispatchers.IO) { + navBackStackEntry?.arguments?.getString("packageName")?.let { packageName -> + title = database.applicationDao().getByKey(packageName)?.application?.name ?: packageName + } + } + } else { + title = routes.find { it.route == destinationRoute }?.title ?: "" + } + } + + Text(title) + }, + ) + }, + bottomBar = { + if (destinationRoute in items.map { it.route }) { + NavigationBar(tonalElevation = 0.dp) { + items.forEach { + val selected = destinationRoute == it.route + NavigationBarItem( + selected = selected, + onClick = { + if (it.route == Route.Accounts.route) { + scope.launch { + sheetState.show() + shouldShowBottomSheet = true + } + } else { + navController.navigate(it.route) { + popUpTo(0) + } + } + }, + icon = { + Icon( + if (selected) it.selectedIcon else it.icon, + it.route, + ) + }, + ) + } + } + } else { + val localBackButtonTitle = remember { + routes.find { it.route == navController.previousBackStackEntry?.destination?.route }?.title ?: "" + } + if (localBackButtonTitle.isNotBlank()) { + BottomAppBar { + IconRow( + title = if (destinationRoute.startsWith("NewNsecBunkerCreated/")) { + stringResource(R.string.back_to, localBackButtonTitle) + } else { + if (destinationRoute == "NewNsecBunker") { + stringResource(R.string.back_to, stringResource(R.string.add_a_new_application)) + } else { + stringResource(R.string.back_to, localBackButtonTitle) + } + }, + icon = Icons.AutoMirrored.Filled.ArrowBack, + onClick = { + if (destinationRoute.startsWith("NewNsecBunkerCreated/")) { + navController.navigate(Route.Applications.route) { + popUpTo(0) + } + } else { + navController.navigateUp() + } + }, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + }, + floatingActionButton = { + if (navBackStackEntry?.destination?.route == Route.Applications.route) { + PermissionsFloatingActionButton( + onClick = { + navController.navigate(Route.NewApplication.route) + }, + ) + } + }, + ) { padding -> + NavHost( + navController, + startDestination = localRoute, + enterTransition = { fadeIn(animationSpec = tween(200)) }, + exitTransition = { fadeOut(animationSpec = tween(200)) }, + ) { + composable( + Route.IncomingRequest.route, + content = { HomeScreen( Modifier .fillMaxSize() @@ -770,21 +633,12 @@ fun MainScreen( account, database, ) - } - }, - ) + }, + ) - composable( - Route.Applications.route, - content = { - MainScaffold( - accountStateViewModel, - account, - database, - navController, - destinationRoute, - true, - ) { padding -> + composable( + Route.Applications.route, + content = { PermissionsScreen( modifier = Modifier .fillMaxSize() @@ -794,21 +648,12 @@ fun MainScreen( navController = navController, database = database, ) - } - }, - ) + }, + ) - composable( - Route.Settings.route, - content = { - MainScaffold( - accountStateViewModel, - account, - database, - navController, - destinationRoute, - false, - ) { padding -> + composable( + Route.Settings.route, + content = { SettingsScreen( Modifier .fillMaxSize() @@ -817,43 +662,26 @@ fun MainScreen( account, navController, ) - } - }, - ) + }, + ) - composable( - Route.AccountBackup.route, - content = { - BackButtonScaffold( - title = stringResource(R.string.account_backup), - navController = navController, - ) { padding -> + composable( + Route.AccountBackup.route, + content = { AccountBackupScreen( Modifier .fillMaxSize() .padding(padding), account, ) - } - }, - ) + }, + ) - composable( - Route.Permission.route, - arguments = listOf(navArgument("packageName") { type = NavType.StringType }), - content = { - it.arguments?.getString("packageName")?.let { packageName -> - var title by remember { mutableStateOf(packageName) } - LaunchedEffect(Unit) { - launch(Dispatchers.IO) { - title = database.applicationDao().getByKey(packageName)?.application?.name ?: packageName - } - } - - BackButtonScaffold( - title = title, - navController = navController, - ) { padding -> + composable( + Route.Permission.route, + arguments = listOf(navArgument("packageName") { type = NavType.StringType }), + content = { + it.arguments?.getString("packageName")?.let { packageName -> EditPermission( modifier = Modifier @@ -866,17 +694,12 @@ fun MainScreen( database = database, ) } - } - }, - ) + }, + ) - composable( - Route.Logs.route, - content = { - BackButtonScaffold( - title = stringResource(R.string.logs), - navController = navController, - ) { padding -> + composable( + Route.Logs.route, + content = { LogsScreen( modifier = Modifier @@ -884,21 +707,12 @@ fun MainScreen( .padding(padding), account = account, ) - } - }, - ) + }, + ) - composable( - Route.ActiveRelays.route, - content = { - MainScaffold( - accountStateViewModel, - account, - database, - navController = navController, - destinationRoute = destinationRoute, - false, - ) { padding -> + composable( + Route.ActiveRelays.route, + content = { ActiveRelaysScreen( navController = navController, modifier = @@ -906,17 +720,12 @@ fun MainScreen( .fillMaxSize() .padding(padding), ) - } - }, - ) + }, + ) - composable( - Route.Language.route, - content = { - BackButtonScaffold( - title = stringResource(R.string.language), - navController = navController, - ) { padding -> + composable( + Route.Language.route, + content = { LanguageScreen( modifier = Modifier @@ -924,17 +733,12 @@ fun MainScreen( .padding(padding), account = account, ) - } - }, - ) + }, + ) - composable( - Route.NotificationType.route, - content = { - BackButtonScaffold( - title = stringResource(R.string.notification_type), - navController = navController, - ) { padding -> + composable( + Route.NotificationType.route, + content = { NotificationTypeScreen( modifier = Modifier @@ -944,17 +748,12 @@ fun MainScreen( navController.navigateUp() }, ) - } - }, - ) + }, + ) - composable( - Route.DefaultRelays.route, - content = { - BackButtonScaffold( - title = stringResource(R.string.default_relays), - navController = navController, - ) { padding -> + composable( + Route.DefaultRelays.route, + content = { DefaultRelaysScreen( modifier = Modifier @@ -964,17 +763,12 @@ fun MainScreen( accountStateViewModel = accountStateViewModel, account = account, ) - } - }, - ) + }, + ) - composable( - Route.SignPolicy.route, - content = { - BackButtonScaffold( - title = stringResource(R.string.sign_policy), - navController = navController, - ) { padding -> + composable( + Route.SignPolicy.route, + content = { SignPolicySettingsScreen( modifier = Modifier @@ -983,17 +777,12 @@ fun MainScreen( account = account, navController = navController, ) - } - }, - ) + }, + ) - composable( - Route.Security.route, - content = { - BackButtonScaffold( - title = stringResource(R.string.security), - navController = navController, - ) { padding -> + composable( + Route.Security.route, + content = { SecurityScreen( modifier = Modifier @@ -1001,17 +790,12 @@ fun MainScreen( .padding(padding), navController = navController, ) - } - }, - ) + }, + ) - composable( - Route.NewApplication.route, - content = { - BackButtonScaffold( - title = stringResource(R.string.add_a_new_application), - navController = navController, - ) { padding -> + composable( + Route.NewApplication.route, + content = { NewApplicationScreen( account = account, accountStateViewModel = accountStateViewModel, @@ -1021,17 +805,12 @@ fun MainScreen( .fillMaxSize() .padding(padding), ) - } - }, - ) + }, + ) - composable( - Route.NewNsecBunker.route, - content = { - BackButtonScaffold( - title = stringResource(R.string.add_a_nsecbunker), - navController = navController, - ) { padding -> + composable( + Route.NewNsecBunker.route, + content = { NewNsecBunkerScreen( database = database, account = account, @@ -1042,25 +821,14 @@ fun MainScreen( .fillMaxSize() .padding(padding), ) - } - }, - ) + }, + ) - composable( - Route.NSecBunkerCreated.route, - arguments = listOf(navArgument("key") { type = NavType.StringType }), - content = { - it.arguments?.getString("key")?.let { key -> - BackButtonScaffold( - title = stringResource(R.string.add_a_nsecbunker), - navController = navController, - backButtonTitle = stringResource(R.string.add_a_new_application), - onNav = { - navController.navigate(Route.Applications.route) { - popUpTo(0) - } - }, - ) { padding -> + composable( + Route.NSecBunkerCreated.route, + arguments = listOf(navArgument("key") { type = NavType.StringType }), + content = { + it.arguments?.getString("key")?.let { key -> NewNsecBunkerCreatedScreen( database = database, account = account, @@ -1072,19 +840,14 @@ fun MainScreen( .padding(padding), ) } - } - }, - ) + }, + ) - composable( - Route.Activity.route, - arguments = listOf(navArgument("key") { type = NavType.StringType }), - content = { - it.arguments?.getString("key")?.let { key -> - BackButtonScaffold( - title = stringResource(R.string.activity_title), - navController = navController, - ) { padding -> + composable( + Route.Activity.route, + arguments = listOf(navArgument("key") { type = NavType.StringType }), + content = { + it.arguments?.getString("key")?.let { key -> ActivityScreen( database = database, key = key, @@ -1094,21 +857,15 @@ fun MainScreen( .padding(padding), ) } - } - }, - ) + }, + ) - composable( - Route.RelayLogScreen.route, - arguments = listOf(navArgument("url") { type = NavType.StringType }), - content = { - it.arguments?.getString("url")?.let { url -> - val localUrl = Base64.getDecoder().decode(url).toString(Charsets.UTF_8) - - BackButtonScaffold( - title = localUrl, - navController = navController, - ) { padding -> + composable( + Route.RelayLogScreen.route, + arguments = listOf(navArgument("url") { type = NavType.StringType }), + content = { + it.arguments?.getString("url")?.let { url -> + val localUrl = Base64.getDecoder().decode(url).toString(Charsets.UTF_8) RelayLogScreen( url = localUrl, modifier = @@ -1117,19 +874,14 @@ fun MainScreen( .padding(padding), ) } - } - }, - ) + }, + ) - composable( - Route.EditConfiguration.route, - arguments = listOf(navArgument("key") { type = NavType.StringType }), - content = { - it.arguments?.getString("key")?.let { key -> - BackButtonScaffold( - title = stringResource(R.string.edit_configuration), - navController = navController, - ) { padding -> + composable( + Route.EditConfiguration.route, + arguments = listOf(navArgument("key") { type = NavType.StringType }), + content = { + it.arguments?.getString("key")?.let { key -> EditConfigurationScreen( modifier = Modifier .fillMaxSize() @@ -1141,8 +893,8 @@ fun MainScreen( navController = navController, ) } - } - }, - ) + }, + ) + } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt index 294995eb..ec1eedb0 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text @@ -49,7 +50,10 @@ fun SecurityScreen( mutableIntStateOf(NostrSigner.getInstance().settings.biometricsTimeType.screenCode) } val scope = rememberCoroutineScope() - Surface(modifier.fillMaxSize()) { + Surface( + modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + ) { Column( modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt deleted file mode 100644 index 879a29f0..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BackButtonScaffold.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.greenart7c3.nostrsigner.ui.components - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.BottomAppBar -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.res.stringResource -import androidx.navigation.NavController -import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.ui.navigation.routes - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun BackButtonScaffold( - navController: NavController, - title: String, - backButtonTitle: String? = null, - onNav: () -> Unit = { - navController.navigateUp() - }, - content: @Composable (paddingValues: PaddingValues) -> Unit, -) { - Scaffold( - bottomBar = { - val localBackButtonTitle = remember { - backButtonTitle ?: routes.find { it.route == navController.previousBackStackEntry?.destination?.route }?.title ?: "" - } - BottomAppBar { - IconRow( - title = stringResource(R.string.back_to, localBackButtonTitle), - icon = Icons.AutoMirrored.Filled.ArrowBack, - onClick = onNav, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - }, - topBar = { - CenterAlignedTopAppBar( - title = { - Text(title) - }, - ) - }, - ) { padding -> - content(padding) - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt deleted file mode 100644 index 38a53e71..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MainScaffold.kt +++ /dev/null @@ -1,157 +0,0 @@ -package com.greenart7c3.nostrsigner.ui.components - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SheetValue -import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import com.greenart7c3.nostrsigner.BuildConfig -import com.greenart7c3.nostrsigner.NostrSigner -import com.greenart7c3.nostrsigner.database.AppDatabase -import com.greenart7c3.nostrsigner.database.ApplicationEntity -import com.greenart7c3.nostrsigner.models.Account -import com.greenart7c3.nostrsigner.ui.AccountStateViewModel -import com.greenart7c3.nostrsigner.ui.PermissionsFloatingActionButton -import com.greenart7c3.nostrsigner.ui.actions.AccountsBottomSheet -import com.greenart7c3.nostrsigner.ui.navigation.Route -import com.vitorpamplona.quartz.encoders.toHexKey -import java.util.UUID -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun MainScaffold( - accountStateViewModel: AccountStateViewModel, - account: Account, - database: AppDatabase, - navController: NavController, - destinationRoute: String, - shouldShowFloatingButton: Boolean, - content: @Composable (PaddingValues) -> Unit, -) { - val scope = rememberCoroutineScope() - val clipboardManager = LocalClipboardManager.current - val items = mutableListOf(Route.Applications, Route.IncomingRequest, Route.ActiveRelays, Route.Settings, Route.Accounts) - @Suppress("KotlinConstantConditions") - if (BuildConfig.FLAVOR == "offline") { - items.remove(Route.ActiveRelays) - } - var shouldShowBottomSheet by remember { mutableStateOf(false) } - val sheetState = - rememberModalBottomSheetState( - confirmValueChange = { it != SheetValue.PartiallyExpanded }, - skipPartiallyExpanded = true, - ) - - if (shouldShowBottomSheet) { - AccountsBottomSheet( - sheetState = sheetState, - account = account, - accountStateViewModel = accountStateViewModel, - onClose = { - scope.launch { - shouldShowBottomSheet = false - sheetState.hide() - } - }, - ) - } - - Scaffold( - floatingActionButton = { - @Suppress("KotlinConstantConditions") - if (BuildConfig.FLAVOR != "offline" && shouldShowFloatingButton) { - PermissionsFloatingActionButton( - accountStateViewModel, - account, - goToTop = { - val secret = UUID.randomUUID().toString().substring(0, 6) - scope.launch(Dispatchers.IO) { - val application = - ApplicationEntity( - secret, - "", - NostrSigner.getInstance().settings.defaultRelays, - "", - "", - "", - account.keyPair.pubKey.toHexKey(), - false, - secret, - false, - account.signPolicy, - ) - - database.applicationDao().insertApplication( - application, - ) - val relayString = NostrSigner.getInstance().settings.defaultRelays.joinToString(separator = "&") { "relay=${it.url}" } - val bunkerUrl = "bunker://${account.keyPair.pubKey.toHexKey()}?$relayString" - clipboardManager.setText(AnnotatedString(bunkerUrl)) - scope.launch(Dispatchers.Main) { - navController.navigate("Permission/$secret") - } - } - }, - onClick = { - navController.navigate(Route.NewApplication.route) - }, - ) - } - }, - topBar = { - CenterAlignedTopAppBar( - title = { - val title = items.find { it.route == destinationRoute }?.title ?: "" - Text(title) - }, - ) - }, - bottomBar = { - NavigationBar(tonalElevation = 0.dp) { - items.forEach { - val selected = destinationRoute == it.route - NavigationBarItem( - selected = selected, - onClick = { - if (it.route == Route.Accounts.route) { - scope.launch { - sheetState.show() - shouldShowBottomSheet = true - } - } else { - navController.navigate(it.route) { - popUpTo(0) - } - } - }, - icon = { - Icon( - if (selected) it.selectedIcon else it.icon, - it.route, - ) - }, - ) - } - } - }, - ) { padding -> - content(padding) - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt index e7066a1e..aafccad9 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt @@ -54,14 +54,14 @@ sealed class Route( ) data object AccountBackup : Route( - title = "", + title = NostrSigner.getInstance().getString(R.string.account_backup), route = "AccountBackup", icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) data object Logs : Route( - title = "", + title = NostrSigner.getInstance().getString(R.string.logs), route = "Logs", icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, @@ -75,35 +75,35 @@ sealed class Route( ) data object Language : Route( - title = "", + title = NostrSigner.getInstance().getString(R.string.language), route = "Language", icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) data object NotificationType : Route( - title = "", + title = NostrSigner.getInstance().getString(R.string.notification_type), route = "NotificationType", icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) data object DefaultRelays : Route( - title = "", + title = NostrSigner.getInstance().getString(R.string.default_relays), route = "DefaultRelays", icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) data object SignPolicy : Route( - title = "", + title = NostrSigner.getInstance().getString(R.string.sign_policy), route = "SignPolicy", icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) data object Security : Route( - title = "", + title = NostrSigner.getInstance().getString(R.string.security), route = "Security", icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, @@ -138,14 +138,14 @@ sealed class Route( ) data object Activity : Route( - title = NostrSigner.getInstance().getString(R.string.activity), + title = NostrSigner.getInstance().getString(R.string.activity_title), route = "Activity/{key}", icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) data object RelayLogScreen : Route( - title = NostrSigner.getInstance().getString(R.string.relays), + title = NostrSigner.getInstance().getString(R.string.logs), route = "RelayLogScreen/{url}", icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, From ad321d9e37c0ea48cc126d88f540e23dab5b08f2 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 21 Oct 2024 08:35:50 -0300 Subject: [PATCH 18/30] Add padding top --- .../greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt index 2de71060..279d5941 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt @@ -148,6 +148,7 @@ fun AccountBackupScreen( RichText( style = RichTextStyle().resolveDefaults(), + modifier = Modifier.padding(top = 16.dp), ) { BasicMarkdown(astNode) } From 3ba00a777086296bd7edfd0eaff274bc6501e5e1 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 21 Oct 2024 09:28:31 -0300 Subject: [PATCH 19/30] Use AmberButton everywhere --- .../nostrsigner/ui/EditPermission.kt | 117 ++++------------- .../greenart7c3/nostrsigner/ui/LoginScreen.kt | 39 +++--- .../greenart7c3/nostrsigner/ui/LogsScreen.kt | 35 +++-- .../nostrsigner/ui/NewApplicationScreen.kt | 118 +++++++---------- .../nostrsigner/ui/NotificationTypeScreen.kt | 26 ++-- .../nostrsigner/ui/SecurityScreen.kt | 21 +-- .../ui/SignPolicySettingsScreen.kt | 20 +-- .../ui/actions/AccountBackupDialog.kt | 121 +++++------------- .../ui/actions/EditRelaysDialog.kt | 46 +++---- .../nostrsigner/ui/components/AmberButton.kt | 57 +++++++++ .../ui/components/SeedWordsPage.kt | 40 +++--- 11 files changed, 269 insertions(+), 371 deletions(-) create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt index a71c5a7b..8d987700 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -56,6 +56,7 @@ import com.greenart7c3.nostrsigner.models.kindToNipUrl import com.greenart7c3.nostrsigner.models.nipToUrl import com.greenart7c3.nostrsigner.ui.actions.EditRelaysDialog import com.greenart7c3.nostrsigner.ui.actions.RemoveAllPermissionsDialog +import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.vitorpamplona.quartz.encoders.toHexKey import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -170,37 +171,23 @@ fun EditPermission( Spacer(Modifier.height(12.dp)) } - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - Button( - onClick = { - navController.navigate("Activity/${applicationData.key}") - }, - Modifier - .fillMaxWidth() - .padding(6.dp), - ) { + AmberButton( + onClick = { + navController.navigate("Activity/${applicationData.key}") + }, + content = { Text(stringResource(R.string.activity)) - } - } + }, + ) - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - Button( - onClick = { - navController.navigate("EditConfiguration/${applicationData.key}") - }, - Modifier - .fillMaxWidth() - .padding(6.dp), - ) { + AmberButton( + onClick = { + navController.navigate("EditConfiguration/${applicationData.key}") + }, + content = { Text(stringResource(R.string.edit_configuration)) - } - } + }, + ) Text( modifier = Modifier @@ -344,72 +331,14 @@ fun EditPermission( } } } - Row( - Modifier - .fillMaxWidth(), - Arrangement.Center, - ) { - Button( - onClick = { - wantsToRemovePermissions = true - }, - Modifier - .fillMaxWidth() - .padding(6.dp), - ) { - Text(stringResource(R.string.remove_all_permissions)) - } - } -// Row( -// Modifier -// .fillMaxWidth(), -// Arrangement.Center, -// ) { -// Button( -// onClick = { -// if (!applicationData.isConnected) { -// val relays = applicationData.relays.joinToString(separator = "&") { "relay=${it.url}" } -// val localSecret = if (applicationData.useSecret) "&secret=${applicationData.secret}" else "" -// val localBunkerUri = "bunker://${account.keyPair.pubKey.toHexKey()}?$relays$localSecret" -// clipboardManager.setText(AnnotatedString(localBunkerUri)) -// } -// -// navController.navigateUp() -// }, -// Modifier.padding(6.dp), -// ) { -// Text(stringResource(id = R.string.cancel)) -// } -// Button( -// onClick = { -// scope.launch(Dispatchers.IO) { -// val localApplicationData = -// applicationData.copy( -// name = textFieldvalue.text, -// useSecret = checked, -// ) -// database.applicationDao().delete(applicationData) -// database.applicationDao().insertApplicationWithPermissions( -// ApplicationWithPermissions( -// localApplicationData, -// permissions, -// ), -// ) -// if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) { -// NostrSigner.getInstance().checkForNewRelays() -// } -// -// scope.launch(Dispatchers.Main) { -// navController.navigateUp() -// accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Applications.route) -// } -// } -// }, -// Modifier.padding(6.dp), -// ) { -// Text(stringResource(id = R.string.confirm)) -// } -// } + AmberButton( + onClick = { + wantsToRemovePermissions = true + }, + content = { + Text(stringResource(R.string.remove_all_permissions)) + }, + ) } } 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 bed0fd27..4eee0b1c 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt @@ -32,7 +32,6 @@ import androidx.compose.material.icons.outlined.Visibility import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material3.Button import androidx.compose.material3.Checkbox -import androidx.compose.material3.ElevatedButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -80,6 +79,8 @@ import com.greenart7c3.nostrsigner.BuildConfig import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.service.PackageUtils import com.greenart7c3.nostrsigner.ui.actions.ConnectOrbotDialog +import com.greenart7c3.nostrsigner.ui.components.AmberButton +import com.greenart7c3.nostrsigner.ui.components.AmberElevatedButton import com.greenart7c3.nostrsigner.ui.components.SeedWordsPage import com.greenart7c3.nostrsigner.ui.components.TitleExplainer import com.vitorpamplona.quartz.crypto.CryptoUtils.random @@ -137,7 +138,7 @@ fun MainPage( verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - ElevatedButton( + AmberElevatedButton( onClick = { scope.launch { state.animateScrollToPage(1) @@ -150,7 +151,7 @@ fun MainPage( Text(text = stringResource(R.string.add_a_key)) } - Button( + AmberButton( modifier = Modifier .height(50.dp), onClick = { @@ -338,22 +339,19 @@ fun OrbotPage( } Spacer(modifier = Modifier.height(20.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - Button( - modifier = Modifier - .height(50.dp), - onClick = { - scope.launch { - pageState.animateScrollToPage(2) - } - }, - ) { + + AmberButton( + Modifier + .padding(start = 40.dp, end = 40.dp), + onClick = { + scope.launch { + pageState.animateScrollToPage(2) + } + }, + content = { Text(text = stringResource(R.string.next)) - } - } + }, + ) } } } @@ -457,7 +455,10 @@ fun SignPolicyScreen( } } Spacer(modifier = Modifier.height(40.dp)) - Button( + + AmberButton( + Modifier + .padding(start = 40.dp, end = 40.dp), onClick = { if (key.isBlank()) { accountViewModel.newKey( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LogsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LogsScreen.kt index 8dbc1207..1cd9693f 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LogsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LogsScreen.kt @@ -1,17 +1,14 @@ package com.greenart7c3.nostrsigner.ui -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.Button -import androidx.compose.material3.Card +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle @@ -24,6 +21,7 @@ import com.greenart7c3.nostrsigner.NostrSigner import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.TimeUtils +import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.vitorpamplona.quartz.encoders.toNpub import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -46,7 +44,7 @@ fun LogsScreen( Modifier.weight(1f), ) { items(logs.value.size) { index -> - Card( + ElevatedCard( Modifier .fillMaxWidth() .padding(6.dp), @@ -89,19 +87,18 @@ fun LogsScreen( } } } - Box( - Modifier.fillMaxWidth(), - contentAlignment = Alignment.Center, - ) { - Button( - onClick = { - scope.launch(Dispatchers.IO) { - NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()).applicationDao().clearLogs() - } - }, - ) { - Text(text = stringResource(R.string.clear_logs)) - } - } + + AmberButton( + onClick = { + scope.launch(Dispatchers.IO) { + NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()).applicationDao().clearLogs() + } + }, + content = { + Text( + text = stringResource(R.string.clear_logs), + ) + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt index 7d7279e3..5cc03eef 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewApplicationScreen.kt @@ -3,13 +3,9 @@ package com.greenart7c3.nostrsigner.ui import android.content.Intent import android.net.Uri import android.widget.Toast -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -25,6 +21,7 @@ import androidx.navigation.NavController import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.service.getAppCompatActivity +import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.greenart7c3.nostrsigner.ui.navigation.Route import com.vitorpamplona.quartz.encoders.toNpub @@ -62,84 +59,63 @@ fun NewApplicationScreen( text = stringResource(R.string.new_app_description), ) - Row( - Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.Center, - ) { - Button( - onClick = { - clipboardManager.getText()?.let { - if (it.text.isBlank()) { - Toast.makeText( - context, - context.getString(R.string.invalid_nostr_connect_uri), - Toast.LENGTH_SHORT, - ).show() - return@let - } - if (!it.text.startsWith("nostrconnect://")) { - Toast.makeText( - context, - context.getString(R.string.invalid_nostr_connect_uri), - Toast.LENGTH_SHORT, - ).show() - return@let - } - - val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(it.text) - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) - context.getAppCompatActivity()?.startActivity(intent) - accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.IncomingRequest.route) + AmberButton( + onClick = { + clipboardManager.getText()?.let { + if (it.text.isBlank()) { + Toast.makeText( + context, + context.getString(R.string.invalid_nostr_connect_uri), + Toast.LENGTH_SHORT, + ).show() + return@let } - }, - content = { - Text(text = stringResource(R.string.paste_from_clipboard)) - }, - ) - } + if (!it.text.startsWith("nostrconnect://")) { + Toast.makeText( + context, + context.getString(R.string.invalid_nostr_connect_uri), + Toast.LENGTH_SHORT, + ).show() + return@let + } + + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Uri.parse(it.text) + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + context.getAppCompatActivity()?.startActivity(intent) + accountStateViewModel.switchUser(account.keyPair.pubKey.toNpub(), Route.IncomingRequest.route) + } + }, + content = { + Text(text = stringResource(R.string.paste_from_clipboard)) + }, + ) Text( stringResource(R.string.nostr_connect_description), ) - Row( - Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.Center, - ) { - Button( - onClick = { - dialogOpen = true - }, - content = { - Text(text = stringResource(R.string.scan_qr_code)) - }, - ) - } + AmberButton( + onClick = { + dialogOpen = true + }, + content = { + Text(text = stringResource(R.string.scan_qr_code)) + }, + ) Text( stringResource(R.string.nostr_connect_qr_description), ) - Row( - Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.Center, - ) { - Button( - onClick = { - navController.navigate(Route.NewNsecBunker.route) - }, - content = { - Text(text = stringResource(R.string.add_a_nsecbunker)) - }, - ) - } + AmberButton( + onClick = { + navController.navigate(Route.NewNsecBunker.route) + }, + content = { + Text(text = stringResource(R.string.add_a_nsecbunker)) + }, + ) Text( stringResource(R.string.nsecbunker_description), diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NotificationTypeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NotificationTypeScreen.kt index ee61e6cb..f2a3e659 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NotificationTypeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NotificationTypeScreen.kt @@ -1,13 +1,11 @@ package com.greenart7c3.nostrsigner.ui import android.content.Intent -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -15,7 +13,6 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -28,7 +25,7 @@ import com.greenart7c3.nostrsigner.relays.AmberListenerSingleton import com.greenart7c3.nostrsigner.service.ConnectivityService import com.greenart7c3.nostrsigner.service.NotificationDataSource import com.greenart7c3.nostrsigner.service.PushNotificationUtils -import com.greenart7c3.nostrsigner.ui.components.PostButton +import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.greenart7c3.nostrsigner.ui.components.TitleExplainer import com.vitorpamplona.ammolite.relays.RelayPool import kotlinx.collections.immutable.persistentListOf @@ -100,14 +97,8 @@ fun NotificationTypeScreen( } } } - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - ) { - PostButton( - isActive = true, - ) { + AmberButton( + onClick = { scope.launch(Dispatchers.IO) { NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( notificationType = parseNotificationType(notificationItemsIndex), @@ -132,7 +123,12 @@ fun NotificationTypeScreen( onDone() } } - } - } + }, + content = { + Text( + text = stringResource(R.string.save), + ) + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt index ec1eedb0..19c02da8 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -27,7 +26,7 @@ import androidx.navigation.NavController import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.NostrSigner import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.ui.components.PostButton +import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.greenart7c3.nostrsigner.ui.components.TitleExplainer import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Dispatchers @@ -96,12 +95,9 @@ fun SecurityScreen( } } } - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - ) { - PostButton(isActive = true) { + + AmberButton( + onClick = { scope.launch(Dispatchers.IO) { NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( useAuth = enableBiometrics, @@ -112,8 +108,13 @@ fun SecurityScreen( navController.navigateUp() } } - } - } + }, + content = { + Text( + text = stringResource(R.string.save), + ) + }, + ) } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SignPolicySettingsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SignPolicySettingsScreen.kt index ce15f36a..3082912f 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SignPolicySettingsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SignPolicySettingsScreen.kt @@ -30,7 +30,7 @@ import androidx.navigation.NavController import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account -import com.greenart7c3.nostrsigner.ui.components.PostButton +import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.greenart7c3.nostrsigner.ui.components.TitleExplainer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -111,12 +111,9 @@ fun SignPolicySettingsScreen( } } } - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - ) { - PostButton(isActive = true) { + + AmberButton( + onClick = { scope.launch(Dispatchers.IO) { account.signPolicy = selectedOption LocalPreferences.saveToEncryptedStorage(context, account) @@ -124,7 +121,12 @@ fun SignPolicySettingsScreen( navController.navigateUp() } } - } - } + }, + content = { + Text( + text = stringResource(R.string.save), + ) + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt index 279d5941..2666da57 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt @@ -9,7 +9,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -20,13 +19,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.List -import androidx.compose.material.icons.filled.Key -import androidx.compose.material.icons.filled.QrCode import androidx.compose.material.icons.outlined.Visibility import androidx.compose.material.icons.outlined.VisibilityOff -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -67,9 +61,9 @@ import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.service.Biometrics.authenticate import com.greenart7c3.nostrsigner.ui.QrCodeDrawer +import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.greenart7c3.nostrsigner.ui.components.CloseButton import com.greenart7c3.nostrsigner.ui.components.SeedWordsPage -import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder import com.greenart7c3.nostrsigner.ui.theme.Size35dp import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser import com.halilibo.richtext.commonmark.MarkdownParseOptions @@ -164,7 +158,7 @@ fun AccountBackupScreen( } val context = LocalContext.current - Button( + AmberButton( onClick = { authenticate( title = context.getString(R.string.show_seed_words), @@ -182,18 +176,10 @@ fun AccountBackupScreen( }, ) }, - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp), - ) { - Icon( - Icons.AutoMirrored.Filled.List, - contentDescription = stringResource(R.string.show_seed_words), - ) + content = { Text(text = stringResource(R.string.show_seed_words)) - } - } + }, + ) } NSecCopyButton(account) NSecQrButton(account) @@ -229,6 +215,7 @@ fun AccountBackupScreen( OutlinedTextField( modifier = Modifier + .fillMaxWidth() .onGloballyPositioned { coordinates -> autofillNode.boundingBox = coordinates.boundsInWindow() } @@ -317,8 +304,7 @@ private fun NSecQrButton(account: Account) { } } - Button( - modifier = Modifier.padding(horizontal = 3.dp), + AmberButton( onClick = { authenticate( title = context.getString(R.string.show_qr_code), @@ -338,20 +324,13 @@ private fun NSecQrButton(account: Account) { }, ) }, - shape = ButtonBorder, - contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp), - ) { - Icon( - tint = MaterialTheme.colorScheme.onPrimary, - imageVector = Icons.Default.QrCode, - contentDescription = stringResource(R.string.shows_qr_code_with_you_private_key), - modifier = Modifier.padding(end = 5.dp), - ) - Text( - stringResource(id = R.string.show_qr_code), - color = MaterialTheme.colorScheme.onPrimary, - ) - } + content = { + Text( + stringResource(id = R.string.show_qr_code), + color = MaterialTheme.colorScheme.onPrimary, + ) + }, + ) } @Composable @@ -413,8 +392,7 @@ private fun NSecCopyButton(account: Account) { } } - Button( - modifier = Modifier.padding(horizontal = 3.dp), + AmberButton( onClick = { authenticate( title = context.getString(R.string.copy_my_secret_key), @@ -434,17 +412,10 @@ private fun NSecCopyButton(account: Account) { }, ) }, - shape = ButtonBorder, - contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp), - ) { - Icon( - tint = MaterialTheme.colorScheme.onPrimary, - imageVector = Icons.Default.Key, - contentDescription = stringResource(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup), - modifier = Modifier.padding(end = 5.dp), - ) - Text(stringResource(id = R.string.copy_my_secret_key), color = MaterialTheme.colorScheme.onPrimary) - } + content = { + Text(stringResource(id = R.string.copy_my_secret_key)) + }, + ) } private fun copyNSec( @@ -482,8 +453,7 @@ private fun EncryptNSecCopyButton( } } - Button( - modifier = Modifier.padding(horizontal = 3.dp), + AmberButton( onClick = { authenticate( title = context.getString(R.string.copy_my_secret_key), @@ -501,24 +471,14 @@ private fun EncryptNSecCopyButton( }, ) }, - shape = ButtonBorder, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - ), - contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp), enabled = password.value.text.isNotBlank(), - ) { - Icon( - tint = MaterialTheme.colorScheme.onPrimary, - imageVector = Icons.Default.Key, - contentDescription = stringResource(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup), - modifier = Modifier.padding(end = 5.dp), - ) - Text( - stringResource(id = R.string.encrypt_and_copy_my_secret_key), - color = MaterialTheme.colorScheme.onPrimary, - ) - } + content = { + Text( + stringResource(id = R.string.encrypt_and_copy_my_secret_key), + color = MaterialTheme.colorScheme.onPrimary, + ) + }, + ) } @Composable @@ -549,8 +509,7 @@ private fun EncryptNSecQRButton( } } - Button( - modifier = Modifier.padding(horizontal = 3.dp), + AmberButton( onClick = { authenticate( title = context.getString(R.string.copy_my_secret_key), @@ -568,24 +527,14 @@ private fun EncryptNSecQRButton( }, ) }, - shape = ButtonBorder, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - ), - contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp), enabled = password.value.text.isNotBlank(), - ) { - Icon( - tint = MaterialTheme.colorScheme.onPrimary, - imageVector = Icons.Default.QrCode, - contentDescription = stringResource(R.string.shows_qr_code_with_you_private_key), - modifier = Modifier.padding(end = 5.dp), - ) - Text( - stringResource(id = R.string.show_qr_code), - color = MaterialTheme.colorScheme.onPrimary, - ) - } + content = { + Text( + stringResource(id = R.string.show_qr_code), + color = MaterialTheme.colorScheme.onPrimary, + ) + }, + ) } private fun encryptCopyNSec( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt index 432a9ce9..4b0b69d0 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt @@ -62,6 +62,7 @@ import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.service.Nip11Retriever import com.greenart7c3.nostrsigner.ui.AccountStateViewModel import com.greenart7c3.nostrsigner.ui.CenterCircularProgressIndicator +import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.greenart7c3.nostrsigner.ui.components.CloseButton import com.greenart7c3.nostrsigner.ui.components.PostButton import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES @@ -333,31 +334,6 @@ fun DefaultRelaysScreen( } } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - PostButton( - isActive = relays2.isNotEmpty(), - onPost = { - scope.launch(Dispatchers.IO) { - if (relays2.isNotEmpty()) { - NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( - defaultRelays = relays2, - ) - LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) - } - scope.launch(Dispatchers.Main) { - navController.navigateUp() - } - } - }, - ) - } - LazyColumn( Modifier .weight(1f), @@ -389,6 +365,26 @@ fun DefaultRelaysScreen( } } } + AmberButton( + onClick = { + scope.launch(Dispatchers.IO) { + if (relays2.isNotEmpty()) { + NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( + defaultRelays = relays2, + ) + LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) + } + scope.launch(Dispatchers.Main) { + navController.navigateUp() + } + } + }, + content = { + Text( + text = stringResource(R.string.save), + ) + }, + ) } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt new file mode 100644 index 00000000..0c5608c1 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt @@ -0,0 +1,57 @@ +package com.greenart7c3.nostrsigner.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ElevatedButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun AmberButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + enabled: Boolean = true, + content: @Composable () -> Unit, +) { + Row( + modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + Button( + enabled = enabled, + onClick = onClick, + modifier = Modifier + .fillMaxWidth() + .padding(6.dp), + ) { + content() + } + } +} + +@Composable +fun AmberElevatedButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + enabled: Boolean = true, + content: @Composable () -> Unit, +) { + Row( + modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + ElevatedButton( + enabled = enabled, + onClick = onClick, + modifier = Modifier + .fillMaxWidth() + .padding(6.dp), + ) { + content() + } + } +} 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 519d0cd4..f365bc8f 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 @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -58,18 +57,17 @@ fun SeedWordsPage( Spacer(modifier = Modifier.height(20.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - Button( - onClick = { - clipboardManager.setText(AnnotatedString(seedWords.joinToString(" "))) - }, - ) { + AmberButton( + Modifier + .padding(start = 40.dp, end = 40.dp), + onClick = { + clipboardManager.setText(AnnotatedString(seedWords.joinToString(" "))) + }, + content = { Text(text = stringResource(R.string.copy_to_clipboard)) - } - } + }, + ) + Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, @@ -129,19 +127,15 @@ fun SeedWordsPage( } } if (showNextButton) { - Row( + AmberButton( Modifier - .fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - Button( - modifier = Modifier - .height(50.dp), - onClick = onNextPage, - ) { + .padding(start = 40.dp, end = 40.dp) + .height(50.dp), + onClick = onNextPage, + content = { Text(text = stringResource(R.string.next)) - } - } + }, + ) } } } From fb79cebbebce01657315bb649cdc9e2bbe9ad8f5 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 21 Oct 2024 11:00:10 -0300 Subject: [PATCH 20/30] Show permissions without a dialog --- .../ui/actions/AdjustPermissionsDialog.kt | 147 ------------------ .../nostrsigner/ui/components/AmberButton.kt | 4 + .../ui/components/LoginWithPubKey.kt | 128 +++++++++------ 3 files changed, 84 insertions(+), 195 deletions(-) delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AdjustPermissionsDialog.kt diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AdjustPermissionsDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AdjustPermissionsDialog.kt deleted file mode 100644 index 8520a9f5..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AdjustPermissionsDialog.kt +++ /dev/null @@ -1,147 +0,0 @@ -package com.greenart7c3.nostrsigner.ui.actions - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.models.Permission -import com.greenart7c3.nostrsigner.ui.components.CloseButton -import com.greenart7c3.nostrsigner.ui.components.PostButton - -@Composable -fun AdjustPermissionsDialog( - localPermissions: List, - onClose: () -> Unit, - onPost: (List) -> Unit, -) { - var dialogPermissions by remember { - mutableStateOf(localPermissions.map { it.copy() }) - } - var selectAll by remember { - mutableStateOf(true) - } - val context = LocalContext.current - Dialog( - onDismissRequest = onClose, - properties = DialogProperties(usePlatformDefaultWidth = false), - ) { - Surface(modifier = Modifier.fillMaxSize()) { - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .fillMaxSize(), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - CloseButton( - onCancel = onClose, - ) - - PostButton( - isActive = true, - onPost = { - onPost(dialogPermissions) - }, - ) - } - - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .padding(horizontal = 8.dp) - .clickable { - selectAll = !selectAll - dialogPermissions = - dialogPermissions.map { item -> - item.copy(checked = selectAll) - } - }, - ) { - Text( - modifier = Modifier.weight(1f), - text = stringResource(R.string.select_deselect_all), - ) - Switch( - checked = selectAll, - onCheckedChange = { - selectAll = !selectAll - dialogPermissions = - dialogPermissions.map { item -> - item.copy(checked = selectAll) - } - }, - ) - } - - LazyColumn( - Modifier.padding(16.dp), - ) { - items(dialogPermissions.size) { - val permission = dialogPermissions[it] - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .clickable { - dialogPermissions = - dialogPermissions.mapIndexed { j, item -> - if (it == j) { - item.copy(checked = !item.checked) - } else { - item - } - } - }, - ) { - Text( - modifier = Modifier.weight(1f), - text = permission.toLocalizedString(context), - ) - Switch( - checked = permission.checked, - onCheckedChange = { _ -> - dialogPermissions = - dialogPermissions.mapIndexed { j, item -> - if (it == j) { - item.copy(checked = !item.checked) - } else { - item - } - } - }, - ) - } - } - } - } - } - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt index 0c5608c1..adb61112 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt @@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ElevatedButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -15,6 +17,7 @@ fun AmberButton( modifier: Modifier = Modifier, onClick: () -> Unit, enabled: Boolean = true, + colors: ButtonColors = ButtonDefaults.buttonColors(), content: @Composable () -> Unit, ) { Row( @@ -24,6 +27,7 @@ fun AmberButton( Button( enabled = enabled, onClick = onClick, + colors = colors, modifier = Modifier .fillMaxWidth() .padding(6.dp), diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt index e2f656e5..782ccf55 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt @@ -1,6 +1,7 @@ package com.greenart7c3.nostrsigner.ui.components import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -14,21 +15,22 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton +import androidx.compose.material3.Switch import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -40,8 +42,6 @@ import androidx.compose.ui.unit.sp import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Permission -import com.greenart7c3.nostrsigner.ui.actions.AdjustPermissionsDialog -import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder @Composable fun LoginWithPubKey( @@ -53,27 +53,12 @@ fun LoginWithPubKey( onAccept: (List?, Int) -> Unit, onReject: () -> Unit, ) { - var localPermissions by remember { - mutableStateOf(permissions) - } - - var showAdjustDialog by remember { - mutableStateOf(false) - } - - if (showAdjustDialog) { - AdjustPermissionsDialog( - localPermissions ?: emptyList(), - onClose = { - showAdjustDialog = false - }, - ) { dialogPermissions -> - localPermissions = - dialogPermissions.map { - it.copy() - } - showAdjustDialog = false + val localPermissions = remember { + val snapshot = mutableStateListOf() + permissions?.forEach { + snapshot.add(it) } + snapshot } Column( @@ -186,45 +171,92 @@ fun LoginWithPubKey( Alignment.CenterHorizontally, ) { if (selectedOption == 1) { - localPermissions?.let { - if (it.isNotEmpty()) { - TextButton( - onClick = { - showAdjustDialog = true + var selectAll by remember { + mutableStateOf(true) + } + val enabledPermissions = localPermissions.map { + remember { mutableStateOf(it.checked) } + } + if (localPermissions.isNotEmpty()) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clickable { + selectAll = !selectAll + val permissions2 = localPermissions.map { permission -> + permission.copy(checked = selectAll) + } + localPermissions.clear() + localPermissions.addAll(permissions2) + enabledPermissions.forEach { + it.value = selectAll + } }, + ) { + Text( + modifier = Modifier.weight(1f), + text = stringResource(R.string.select_deselect_all), + ) + Switch( + checked = selectAll, + onCheckedChange = { + selectAll = !selectAll + val permissions2 = localPermissions.map { permission -> + permission.copy(checked = selectAll) + } + localPermissions.clear() + localPermissions.addAll(permissions2) + enabledPermissions.forEach { + it.value = selectAll + } + }, + ) + } + localPermissions.forEachIndexed { index, permission -> + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .padding(8.dp), + .clickable { + permission.checked = !permission.checked + enabledPermissions[index].value = permission.checked + }, ) { - Text(text = stringResource(R.string.adjust)) + Text( + modifier = Modifier.weight(1f), + text = permission.toLocalizedString(LocalContext.current), + ) + Switch( + checked = enabledPermissions[index].value, + onCheckedChange = { _ -> + permission.checked = !permission.checked + enabledPermissions[index].value = permission.checked + }, + ) } } } } - Button( - modifier = Modifier - .fillMaxWidth(0.8f) - .padding(8.dp), - shape = ButtonBorder, + AmberButton( onClick = { onAccept(localPermissions, selectedOption) }, - ) { - Text(stringResource(R.string.grant_permissions)) - } - Spacer(modifier = Modifier.width(8.dp)) - Button( - modifier = - Modifier - .padding(8.dp), - shape = ButtonBorder, + content = { + Text(stringResource(R.string.grant_permissions)) + }, + ) + Spacer(modifier = Modifier.width(16.dp)) + AmberButton( onClick = onReject, colors = ButtonDefaults.buttonColors( containerColor = Color(0xFF5A5554), ), - ) { - Text(stringResource(R.string.reject)) - } + content = { + Text(stringResource(R.string.reject)) + }, + ) } } } From 8ac7cc609f16f90bcf97bd84d2e571e6bffc4fb6 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 21 Oct 2024 13:47:18 -0300 Subject: [PATCH 21/30] Add padding to the navigation bar --- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) 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 350a404e..5cba3504 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -13,7 +13,9 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape @@ -546,30 +548,38 @@ fun MainScreen( }, bottomBar = { if (destinationRoute in items.map { it.route }) { - NavigationBar(tonalElevation = 0.dp) { - items.forEach { - val selected = destinationRoute == it.route - NavigationBarItem( - selected = selected, - onClick = { - if (it.route == Route.Accounts.route) { - scope.launch { - sheetState.show() - shouldShowBottomSheet = true + NavigationBar( + tonalElevation = 0.dp, + ) { + Row( + Modifier + .padding(horizontal = 40.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + items.forEach { + val selected = destinationRoute == it.route + NavigationBarItem( + selected = selected, + onClick = { + if (it.route == Route.Accounts.route) { + scope.launch { + sheetState.show() + shouldShowBottomSheet = true + } + } else { + navController.navigate(it.route) { + popUpTo(0) + } } - } else { - navController.navigate(it.route) { - popUpTo(0) - } - } - }, - icon = { - Icon( - if (selected) it.selectedIcon else it.icon, - it.route, - ) - }, - ) + }, + icon = { + Icon( + if (selected) it.selectedIcon else it.icon, + it.route, + ) + }, + ) + } } } } else { From 776f693df98a5cf3e8aead9c30eb6e1792430e7e Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 21 Oct 2024 15:35:03 -0300 Subject: [PATCH 22/30] Use isNip04 from quartz --- .../nostrsigner/service/EventNotificationConsumer.kt | 3 ++- .../java/com/greenart7c3/nostrsigner/service/IntentUtils.kt | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) 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 090b8d7e..d674e92a 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/EventNotificationConsumer.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/EventNotificationConsumer.kt @@ -43,6 +43,7 @@ import com.greenart7c3.nostrsigner.models.SignerType import com.greenart7c3.nostrsigner.service.NotificationUtils.sendNotification import com.greenart7c3.nostrsigner.service.model.AmberEvent import com.greenart7c3.nostrsigner.ui.NotificationType +import com.vitorpamplona.quartz.crypto.nip04.Nip04 import com.vitorpamplona.quartz.encoders.Hex import com.vitorpamplona.quartz.encoders.toNpub import com.vitorpamplona.quartz.events.Event @@ -123,7 +124,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { acc: Account, ) { if (event.content.isEmpty()) return - if (IntentUtils.isNip04(event.content)) { + if (Nip04.isNIP04(event.content)) { acc.signer.nip04Decrypt(event.content, event.pubKey) { notify(event, acc, it, EncryptionType.NIP04) } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt index 171757b5..f12fafb2 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt @@ -987,10 +987,4 @@ object IntentUtils { return AmberEvent.toEvent(event) } - - fun isNip04(content: String): Boolean { - if (!content.contains("?iv=")) return false - if (content.length < 28) return false - return content[content.length - 28] == '?' && content[content.length - 27] == 'i' && content[content.length - 26] == 'v' && content[content.length - 25] == '=' - } } From 1f60a55b9938b87908c776ff2b4552ee90332d37 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 21 Oct 2024 17:55:08 -0300 Subject: [PATCH 23/30] Try to go to the incoming request screen when pressing the notification --- .../greenart7c3/nostrsigner/MainActivity.kt | 5 ++++- .../greenart7c3/nostrsigner/MainViewModel.kt | 18 ++++++++++++++++++ .../nostrsigner/ui/AccountScreen.kt | 4 +++- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 4 ++-- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt index e2ddeaa5..e13c4ab9 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.compose.rememberNavController import com.greenart7c3.nostrsigner.service.Biometrics import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.ui.AccountScreen @@ -74,6 +75,8 @@ class MainActivity : AppCompatActivity() { } NostrSignerTheme { + val navController = rememberNavController() + mainViewModel.navController = navController var isAuthenticated by remember { mutableStateOf(false) } val keyguardLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> @@ -158,7 +161,7 @@ class MainActivity : AppCompatActivity() { } } - AccountScreen(accountStateViewModel, intent, packageName, appName, mainViewModel.intents) + AccountScreen(accountStateViewModel, intent, packageName, appName, mainViewModel.intents, navController) } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt index 8cf920a0..ae8c9018 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.navigation.NavHostController import com.greenart7c3.nostrsigner.models.IntentData import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.ui.navigation.Route @@ -19,6 +20,7 @@ import kotlinx.coroutines.launch @SuppressLint("StaticFieldLeak") class MainViewModel(val context: Context) : ViewModel() { val intents = MutableStateFlow>(listOf()) + var navController: NavHostController? = null fun getAccount(): String? { val pubKeys = @@ -56,6 +58,14 @@ class MainViewModel(val context: Context) : ViewModel() { } } + if (requests.isNotEmpty()) { + viewModelScope.launch(Dispatchers.Main) { + navController?.navigate(Route.IncomingRequest.route) { + popUpTo(0) + } + } + } + viewModelScope.launch(Dispatchers.IO) { delay(10000) IntentUtils.clearRequests() @@ -79,6 +89,14 @@ class MainViewModel(val context: Context) : ViewModel() { intents.value.map { it.copy() }.toMutableList() + + intent.getStringExtra("route")?.let { + viewModelScope.launch(Dispatchers.Main) { + navController?.navigate(it) { + popUpTo(0) + } + } + } } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt index 36d17cd2..6eabd994 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController import com.greenart7c3.nostrsigner.BuildConfig import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.NostrSigner @@ -60,6 +61,7 @@ fun AccountScreen( packageName: String?, appName: String?, flow: MutableStateFlow>, + navController: NavHostController, ) { val accountState by accountStateViewModel.accountContent.collectAsState() val intents by flow.collectAsState(initial = emptyList()) @@ -160,7 +162,7 @@ fun AccountScreen( AmberListenerSingleton.accountStateViewModel = accountStateViewModel DisplayErrorMessages(accountStateViewModel) - MainScreen(state.account, accountStateViewModel, newIntents, packageName, appName, localRoute, database) + MainScreen(state.account, accountStateViewModel, newIntents, packageName, appName, localRoute, database, navController) } else { AmberListenerSingleton.accountStateViewModel = accountStateViewModel DisplayErrorMessages(accountStateViewModel) 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 5cba3504..2cd1c8ab 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -55,11 +55,11 @@ import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat +import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.greenart7c3.nostrsigner.BuildConfig import com.greenart7c3.nostrsigner.LocalPreferences @@ -408,8 +408,8 @@ fun MainScreen( appName: String?, route: MutableState, database: AppDatabase, + navController: NavHostController, ) { - val navController = rememberNavController() val navBackStackEntry by navController.currentBackStackEntryAsState() val destinationRoute = navBackStackEntry?.destination?.route ?: "" val scope = rememberCoroutineScope() From 4515b948602873b85a4eb4185d3636e32075b20d Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 23 Oct 2024 07:57:41 -0300 Subject: [PATCH 24/30] Fix notification icon size --- .../nostrsigner/service/ConnectivityService.kt | 2 +- .../nostrsigner/service/NotificationUtils.kt | 4 ++-- .../main/res/drawable-hdpi/ic_notification.png | Bin 0 -> 1134 bytes .../main/res/drawable-mdpi/ic_notification.png | Bin 0 -> 743 bytes .../main/res/drawable-xhdpi/ic_notification.png | Bin 0 -> 1642 bytes .../main/res/drawable-xxhdpi/ic_notification.png | Bin 0 -> 2614 bytes .../res/drawable-xxxhdpi/ic_notification.png | Bin 0 -> 3961 bytes 7 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-mdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_notification.png 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 31f7df9c..a5498c63 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/ConnectivityService.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/ConnectivityService.kt @@ -90,7 +90,7 @@ class ConnectivityService : Service() { NotificationCompat.Builder(this, channelId) .setContentTitle(getString(R.string.amber_is_running_in_background)) .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setSmallIcon(R.mipmap.ic_launcher_foreground) + .setSmallIcon(R.drawable.ic_notification) .setColor(0xFFBF00) return notificationBuilder.build() diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt index 9d58f95c..29e86797 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt @@ -119,7 +119,7 @@ object NotificationUtils { applicationContext, channelId, ) - .setSmallIcon(R.mipmap.ic_launcher_foreground) + .setSmallIcon(R.drawable.ic_notification) .setColor(0xFFBF00) .setContentTitle(messageTitle) .setContentText(applicationContext.getString(R.string.new_event_to_sign)) @@ -142,7 +142,7 @@ object NotificationUtils { applicationContext, channelId, ) - .setSmallIcon(R.mipmap.ic_launcher_foreground) + .setSmallIcon(R.drawable.ic_notification) .setColor(0xFFBF00) .setContentTitle(messageTitle) .setContentText(messageBody) diff --git a/app/src/main/res/drawable-hdpi/ic_notification.png b/app/src/main/res/drawable-hdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..533682be3bd1dfe95f42454c99054f3610d3410c GIT binary patch literal 1134 zcmV-!1d;oRP)Px(C`m*?R9Hvtms_Y+RTPH5m06aTOhLSmB!Xh%Lu6uP(Thr$nm1H@C=FB;5)2AK zOMM890u?la5-9|=ywF1tA_zj{YZ-WfFoXy!=$zxFbFtO@VXVzObI-ZfI%mI}pa~m1 z+cC%d$3MnD#@HDH>|_HB5C1(td7dXhXIYlV81B!V!cX!s&*=jYWl~`NNu<2&j_&2D zV9Edu0-gsx0EPq)%js=kEpP)^4Ls@W_5h=S&w%lfKy>dz;JAW`frm1G|&a?vsA`hTm@bMehFFX z1;`3nwevD?3>Y5q)(Na|sr(*T4jc|_^!IjP3~E{+4vJN1lB z)KU2Lb?0=I0aXW%r6=Y}Y`^LheaY|IVNnZ^Ql(~h$aUPuYk<0Dj>VjM*Yp~9?m3&C zz(HfUF-B6de+<~*^1i!RDziNo`V* z6s_{qRDDXQOqydqa8S3A(WI|@AG_Uag*-++~Fk^8w!Q7r)qlqXOxn(|brh03(p z39J;QeO$jxl8R~>kk`r84y3&Kw$Hq#CI<+R2J8{wFB=QR)UPp7;y(Z@FPiey4%9fs z)MZ;g1n`8ZNi#|Vw@;^5QO7*yd9ETCQ>rU9xuUzP&xoy3d8bhJ8<3Yie}OChooswn zbhQM?YYB~aQoq@&fE54VGN8H-)wTN{P!&`E0@e;zG}5#G;Q#;t07*qoM6N<$f+6Gy A(EtDd literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_notification.png b/app/src/main/res/drawable-mdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..bd6c60f51cda7dbf3f37ea638415d7847bc45c9f GIT binary patch literal 743 zcmV?P)Px%p-DtRR7gv;l}o6NQ5462+xrnF-bHy#49rBygb^u2lt)sO5#_BbB|=Ih1ImO* zA(Vlb2onP%kAaaJdF64rZ|6l*Lc3_a7Fv$6- z0f;dU0Y(EOGw>hwuJvAEJXFQ^039KOKR&JsAjYWoX~0Tgw%hm1O~(f%WcW z-+=9aSX?sc1n?C&mzXK0civSbuq6i=W>lr^+L8`m zZ2qc~76M0rCE4k;U%+B;E6c8vt0qeRD5=mT{i^^>CfQAz zt}vI(a~3QEQ1WIw)Q!}Dtwr)000WW!6M%Krtuq~yoPm;m2F}_j`4U3-?b+3xrZGm< zLw%u+)i@{`F9&EbOR9g~0zG~X)n(xNVmL6%SSYbv<^g2UyUC)*fmdqAJb5XA1JU@H z4;%!RTZeW5*MTQC{N(v=uY11|6&vhskh2zB5l_h>squ~hO@M6KS(dy{1{sJhpk+2e zRang)$Px*BuPX;RA@u(nQ5r4V-&}K+dO2BG7p&|qVPdN_~HXXGDaej%=0|Y88VbHnIlOu zMx>0956Do+JeJJ&;$GYHJFVyToPFML-*eN=eQS5lx%*wuv;J%Nui*}u&!543u0IO^ z{MR1*|9D_H916Az;Nf5}7*=_v#~F&(o>QLPs!(z1R=~Qz!VO`g;;#dr1OEVP0m}jJ zgv_eIw!oq-XZ#4f0Q_BNj4a;>Ae3JSSS2ZPEg%U0u6w>sm!1W@tOXO;i-1jleSulP zrocJCmMyvS32+ARmWF5o(FQuj{5Y^8a47Itc;i^$ zA;+aq^b#-we1LSUY5l=`(=` zf#pN7BU`-i8*o#cIit=Cl>iFsuL)cQ?AamqwfKD|9&k0VKd?jqFdtrxT$lmu7)IH= z#VE>{3$N69p$>r7mnja4IBc@M$~~2m^z0PtpGBf;x%Ees`wqAixF?0zQYp#L3-2!0 z0AgksP$T|c6-HA46xLrSVpU5vJu;6LH)K3dC6!sSLg;zG(D|vz1(~Voka0@AMAw>n!8^{rP?8FBp;AYcI*ZcUVK4oJN+)2y;CQ~{8AwuzWF z_UjHd_CHMDUy$hX%i#T^0qL!N*bTJlkAbFG6b6V_8OK`zR>d!ZxQSRC&PSB?GCv zAO9{Jx!kC!j0gMi_sm?%W|>lhAIUU!pHE=O*<=RUYu)mI-!{@(hW#Co5CFcYl(-CKUFZ zBiatB`CgCNW8Df!+4TTCU^pD+P|#wh2c0$*n>xL2jsz8))%4Uc04V!nbhWnWQfemv zCYRi7+bf;!&~cb4b<|>3XO++pHfDVvL(szbOp>=lHU1}crcIStt4nh`1yyd`^Y+^V zfH5q96MM=VF4slTGY4cZ0Zi&jn|dL=V6|Kt*e~@Wr*xPHN*x8@@R9+X&;x)9-y1Et zZHJ*_E=(D2n!3@P3szN=`Z(aQXotJ#GA;m>eImfy09wSfe&uA=WuTGQ%?BqdtM1-4 zMQ0S$H~{q4OVL|dxKv*LSNVPeO~FWd!h8^f^T8GAl3iNn1I5Py@NOvoMxlJ|H0C8; zwi0C_IsqT4u8Oa>y4<-O~qvJ(Zq) zz`-TL#;4N_WHL0&dyrClk%Mb6d+1wS_wEH zO;^W9f{&BLvWv6CjezsPAGy(MTWIwPq5N_&Zt4l?RqMMt@iRJAwRyk_?XJjv>HP>D z0hzRwtH~-icglfMumF*hdc*L-UL$MAHUQ4L#=VT=3Prk{)GBu=HoXB9;bc+^!b)w= zWV_(HBtK06J_zNXjaE4;U&-}NK*j=4Fq8eip(Hcy97&zG40QnP>D{y1D%dhiDBp!~ zrX_?%@*!{Ua?f;Nv>dJ@?YDtu>3`s>GGoM{D6!%Pl8$HX%Ck=VZ3{u*9K zMbg?He^Jn7ucRFXF?I;mFq58exOdZF7wL}3xONuUIsh5}a(!>cnRXAY59# zL8QdpV;q!mR6Hx+J87rg0|kiOGs#XSw?9?XXN8D=lhu!5)#<;G+NgA{LujN%(^b literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification.png b/app/src/main/res/drawable-xxhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..35cc8d0eb3e34c811c29f61f49b622613fa1c5fe GIT binary patch literal 2614 zcmV-63d!|}P)Px;@JU2LRCr$PoC&C{RT+lglbS=CV_FVr7>-zogkph6K`Nz~Ln>mah*OqB38gkz zT86WQ7J}xGsF*o5;(#ckg7X-a1BjM}h~=!O|Ji=u@w@B(|Nhrnd$Ixhj}8f6=OXk zX(vgK+PFnbssV^y#1yuZbfqK~R@(j|>C=fR0m_BOl4}}3_731JCFyIDeq5=Wr%O7{ z0Ah2pLCOZm*aJ9B7Jy*S=Sz{1^p1i1py?2LM4#;RN}<2aB|T}s^En!XxvXC?CVIDp zq$eDtTs6Tjv|!K0i8Bvg< zqD3dz*qB|f7Ucru+Vg%%mo~ewb1ov`IkabAV~d*?T86HCtfN1^^I1vv8X$}hgE7+@ z7QG~C8`Cxbdb>}HasYDef#RQOOzTe-;P90@&3zM(U|zJZGz&hnr@t2(hwuqWubLLY zqCF*@YQL9G=Q1;0`s>xAp#ZgL50u}!bVjx~Oz97j&=z*@BTamASB9yw2!ccw=32Bt z6Hp?$8&=E(^WMdn+smR(fLgSNXqLckg^EdGMtCkL&ZKC^OFBBUuNx%oZ_INo;`;=f zHSMfsqrQ`)MSTFd_F!IU56G7`7(kFm@CSSNohY2BnY1Feu|c;77OgJnkCN8Q?I1k& zPm_zM`#jn(fMVRUrKH;`bD{eTSZv<42R7hQN*dr23h*!Dju!1^8dj5|T`uW>5N@=3 zv{rzUY@Jl%calC;vKo7qAdevM<`5XNNTTRo2hlR#*Db{0BRm>fbWx*E8&}K?Px`r_ zMR?4`N+OF*GH8!S&6w3C7O0iZ1J)gAaSuDsct13gEA5Nt>2EK~;XM67OKuu9ivdD=cDIb33Grf)eY?-#yl%C~*W(^`9a@vI)0dm^u*K02yNh-q@TWB5OjOKw znIBpNNIthdpyE zE$IS1?F;?lZhq5$i0QI{?cwRmM-<@a@ZD!^R@`<=< z3KTtF?bHRh=yf$eP%wI^_DrW|T&j1jGdC$`rn5MBbe3`k_vRQAT%0BDV*M*Xm)wZ}P#^>vJ`fipP|9jX9w@pD%XK*TURSRTcW zkE;RDS~g)X;!}O3Br>iB80R-@_=z1@5(9bI@J?6ZtS_67t{xVVAR?w{QCFF?W^((^+~QnlglfnTPeT2#|?n?CO! z6F~I+h~MPv$C7o$h z9YI2m5Ya1!6y0-yn-o9*LWzXJ8abS^hj5~|9zE2H#g&EB!}BNCBh2lSlFqKI5Bo4} z01?QI;>GY%KehT1Kf77E?sN)sTzfBr7FO_He&!&#slg=9%6^$q2I*qdBmkm@^?j=y zIdh7@$!1UjQaqpcvIU~enb)yaZ_YKXNihhI#R21U!K@Zs`e5OiH{G%sWP0m_Y#~&zwm?Jjwx`C%O|*2U2Yn^=BKK?ngdP#_{k-siaU1|~ZI5#@%DnEeqFH5$atc-z zKrJA_B-bNA;soSp1zu=WfM5xit1bw!jx%SW0-38jRbH%(hDoFiliag$`pTsP%q?Du znv@qK$KsrgV`bN!Vvp4}xoQR_qV07fuU%tq6#F@D{+^{L!%V%FLFfuWAlG4DZt+-y z3S|v*e6k|})Zy78NM!uDTaJ1r0T4MBXJXuWj5!t_t4fr|ra`fzAlhETc1?mwE1Rhi zOm=NZ4TCtY`J#bSoMYv>*pV5O1d*|Z6w{H9TRK8o(kf>y+5E6Lh+;cI>7QE?(5xJ5 z>Hu|kHjk0eqs=5;-%%bZWzfl1{36GSX@r}V7yol{=zMN$3~FIgJaqIJiM!Fbe~yb6 zvC>Qi;hDG}iAzV6{nR!VzQzU)6C680fl2Y|o|_vv8&_YrNLd3AITp8MpSSGXtAZZu zjO4M#?&gQOrT2>dv8D}^INtfL-O})b!B}$TXWplx7|R`8G}y0s{XJpM&|{lXZEPY8 zYSE(ImgOn*2EYz1SI!t0L48a4kNj(%K5)*35??w8t)N_b3c-}hy2RH83co)M9DcI zO&p*W(T+^wXa45w9Qz}c6d*2so)ixF76E5q0-1%c95ALT(&=DHV(TDz#LYE5N+j`Y zo;o+~9FOIfp8TTISkj0b(5Awmz#@Kh_wTiB|9zq2XV2j6sPXt+9?!3)2F}Dqx`Ra# zql}M(i32K^SSw@s*y_Px^Gf6~2RCr$Podv8UO%;XD;_mM59^4&*yAxazT!K6N5Ii^pcXx*nT!RNl2p-(s z<iMi4uFLvu<-C}wSc|7y-CkMaNxk+I^?o#^I?sX1T-X^sSE9U z?i5hAWgflj8WQH1@56#4uwy~awSTTP{=hgRfK`0SQucjmbcn*s^s-&AY^LnhL zGnJ}X&wVWP9g=P*=^yDn7S4D_lk_!7r4I9mTf}}IdU1@SDpOADLNx#oxz_BEKO450f zH2~CIC4Dfv4~x9Fqz6knVTC#Ih?huuqNJZDD_DUr)fLSJz^(^(fY2Wy>9&;?iqHY` zrOk8ylWF!%B|Sa37A)g)jU`-P(qGfP%nbmrg11V#NTsyDd z?Pq~#KN@+hBou=0+zWwzLDH=n4JHV)Ks1f{o=DQ$BwaKUC_(x$DIhyeFh7*z4h)AP2U*+TfzBiOfF?)cbD{0N&lSVibeyF1zv0aRVBSh(#a|shg&2Ob7W8Kgw09pbsi$N2g zn$pi23q#;{PTGu*jqc~3lJ3`&Rw8sVqlZcY495Z75J8xf_S`ilnUv!el0F?WqR|0) z3IH+ZXJJ=JsowD_E$o{in37i7{ov-vKCU|uObOoHhZP_YtblnQy%E;cQWnGCYh15y z+}*uW;AftmYjHp>2B1Ux5&DahdDmDF3n0TG03&=p7UDgc&DcX4Ydh}1Kv=;WQU*n; zXe$5!V!@kJ`dVYYSSX;f3AC$RQ5B`GXe%9A6%+xG@jVEh52+`+%&Qj)W#~4t3DQoq9|59K zXus1)(mu4f^g_r_lJw(1UGS{ara*qN(gzjX$F3Z{YP1!M0U+1@D>b?JdkQ93atmhr zNlEujDPCO2nNzlLqml=Od(cGO7Mn1h=U9N^Ah72(8Pjn_KC|og_oodgo51~8P~wKh z0$}YYqj+s2w5wEF04|3N=i`ZVv-XodLhe0l;x3jA8~c-=WEl|*DI2pZPJC{@Y=}ZK zBK9GptY8p;mb@P`zFuMm(Wbc<+W&z>;{7mN0NRfmxyFn^?wBkUA8ji@^RJUYU}rt| z!c}4gcS-4Ku@#H~z}kP(Bz&}=1z-Bbje%#LUXKE=)0@ejpVHz7H zI%nE1kj2zk2OBa{(szxqf?fc!)YIE~cBoHlc9j4z_d;{tn|80?%`zYIewatHmbusM z6NQXsB5Z;=R=`@wmTJ@sP#mm)xC0n=qrHWj5&&ZGg$j6K%6)63aZD5!L!kUym^5Is zA7HE+*OwDj{>N-zaIVBF;o@po0kMX9!CS&wP_oXAStkH38!rS-nn=N=2II-Z$e}5? zd_QTwX=los#03OY>%u=n?nMgy*s0n^_Ep0QNS|5L{X^BPm!2pE0F69B+6>_{F=7Ai zU>xa?;9r`u2R&YkfX0LXaGmNW%x;NyCL&9`p>Q5a|i+QKFY(hT9pqRS~HkpabV7|VckjbmZB zRA^BBd|Z*4RmU`T$7f8WqAB(We4GBnZVkrb<1uih6m6)|?Z<(xJ;4|Nm@pc{$K;j) z7!nMNFaY_4(a0FE%-whl16>M09LMRPX##Ug%*RwU&nM&Jr~hpTfVTB`9p=9iEh(M=AWy~u^|1* z*VCp{v-bf}D+yQ^NTtU(>4LfL-_oW%c28b-U-rZAf2kqv{*bkxkfmKHwa2u9m5&=W zd``9%K_Hv^cG`7EYpY@bO!D}NHXx_w&r4h^m>M)~C>APnL->$yY!;Weh%->q4^L%E zTX%1DY|zY-8?sBl{g56$GHv5)?1sk*i-oZl`D_Njg%bdzu$#V^6I2QeD|Khs(fzpA zM@vru0HL2ZQ3&wro4!~qMIbC-warid+5qJ3Dw~)eZ7|fKslc(pEDFFyfu_X*oHw&` zd~(C%oPQH53}XPmH4^~dRAHe200*;a0Lc64(xWX0AY#hf%LB~;01)6&a0Rt`K}&H2 z0J`vO0nX_G0Ip}#0MLE$%oKRN8H*`tl^78B0^Sh=0+|P#JuQK?+ERvqK;YL-0nZyN zEP&omYSguw!-nR;_t4LflRrq*N(5jP1BS|yr7^&1ns=tA3E0B3V0tg#Xrv?Hh1eTVJfRRlD|yi~OVq35 z9sr;u3IHbXm=i8&Qvg7=(I!J90py$cA9GIodw|&%kN}9g#>k%l09L?RYk4hT^<=nivZJ0J@qpS@dqQ zTdvgvn@=79I0t|QuxKt@z`6k-#iUG1V=i2+X8}wLw?{0J`=* z))b?j-Lyx3Bb^C|n(tWD%l)@0*=XTpAjEj!4H|KQnlT3{=sF-(t$FnUKs#dXeE`BG zqER^!?f0Js>AL%l3DTD1wki1F-ObPWKUhsX3Z#sfOg=%glhCP2@< zHU+?IlHcbfBw*yOXSVQ1r=e|<>o_^%-7@qdBj*^Ymkh#wh z#6iy<0Ks&{sZ!e>z4TMBbpk-IDm!^zgMj9@Wjs|h-;1vXK$f27nUNcx$c)?;&dW8X zWJc=(fSoR-R!;puIoR*JN$#%%$>0p6C7#{)VlsA^F|;Jrd)6rnZ%Xg=-nloa83x-y;>$yUSfN07!Wycpb; zw`e&l^8UbQc8LX?iXYQTQWF4kJkM<^dtdL!c=p>FYYV?h8rp?QPf(7;-Km>Y?@sA} zTj^P?S^yweI39Mo1l`E-M89u_v-tFTv8IkR{V~s7-P=OknLV{7z!CA@H|T3R9ZHUk z)&PKKa3}=TQje!}?2*M7|XsD1g7XUIIUcLv%^Q&yL728QB6Kq5BSbGr8C(sd;u74gl1V zdE*Kk|5`boQcyMqKnTkGG2bc2%m@%TSk0#u%ZX$hXVGYz`A$bk(d=gRT)z*DjRMe; z8O0+=&WzmbMLd8(J;bA>z93q`^R9?4Hk8IGgwK0@+B83w71xq(c7d>Q05TA|EP?ct z6PX<1=Cv}BuITFvY6F1$o>!Q`@x&&oyoq)rL3gnj5C`rF%I1ySXT&?G>A62g`28*b z=!~N;C>G_(c&KX8$3uB>Emf^jf4^87S`d`;YMMLl$cA{00Pl0)wa6<2>S;}k0HE#f zY&zwj8QjN%*2gznt(9gd0cH^wesw?xZpSJS>H0Sab7f!^J%6=xi04fT9 zv7ozk0CZ$V0O5FG5D3bornEltCY2lj=nM{M&^(Lge<-|xCD;5>FV-t@B~$NN)CF}| zLVi*{5_lI(#c~cskW$9e^|&BTs$c~iRf1rPr{rTjcae-|tAg+$$^DT#WuL~$Pv!G> z;=-j;e4l(v)i@W8E! z0WI+$w}gDIc0W;(;yrvH1z)wBp+L4pw`?TT5e^>xuK(ha>ugJL8(MD1<@Q9nX zHU^AX!(n~v4uFN>+X1ixU||U?Jp9fAb^t6afrW?PS-=i}g(a}?@H-1w7=Zr+=|8-z T?h#eZ00000NkvXXu0mjfWi?hn literal 0 HcmV?d00001 From 881de46e9369180e5fecacd02d6d8626bcc93044 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 23 Oct 2024 09:28:08 -0300 Subject: [PATCH 25/30] Save data on change --- .../nostrsigner/ui/EditConfigurationScreen.kt | 212 +++++++------ .../nostrsigner/ui/EditPermission.kt | 24 -- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 2 - .../nostrsigner/ui/NewNsecBunkerScreen.kt | 6 +- .../ui/actions/EditRelaysDialog.kt | 291 ++++-------------- app/src/main/res/values/strings.xml | 1 + 6 files changed, 192 insertions(+), 344 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt index e472d5cc..60a3fe45 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditConfigurationScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.Button import androidx.compose.material3.Icon @@ -34,6 +33,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -45,6 +45,7 @@ import com.greenart7c3.nostrsigner.database.AppDatabase import com.greenart7c3.nostrsigner.database.ApplicationWithPermissions import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.ui.actions.onAddRelay +import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.greenart7c3.nostrsigner.ui.navigation.Route import com.vitorpamplona.ammolite.relays.RelaySetupInfo import kotlinx.coroutines.Dispatchers @@ -95,50 +96,50 @@ fun EditConfigurationScreen( OutlinedTextField( value = name, - onValueChange = { name = it }, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.None, + autoCorrectEnabled = false, + imeAction = ImeAction.Next, + ), + onValueChange = { + name = it + scope.launch(Dispatchers.IO) { + application?.let { + val localApplicationData = + it.application.copy( + name = name.text, + relays = relays, + ) + database.applicationDao().delete(it.application) + database.applicationDao().insertApplicationWithPermissions( + ApplicationWithPermissions( + localApplicationData, + it.permissions, + ), + ) + } + } + }, label = { Text(stringResource(R.string.name)) }, modifier = Modifier.fillMaxWidth(), ) Spacer(modifier = Modifier.height(16.dp)) - Row( - Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - OutlinedTextField( - modifier = Modifier - .fillMaxWidth(0.9f) - .padding(end = 16.dp), - value = textFieldRelay.value.text, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, - ), - onValueChange = { - textFieldRelay.value = TextFieldValue(it) - }, - keyboardActions = KeyboardActions( - onDone = { - scope.launch(Dispatchers.IO) { - onAddRelay( - textFieldRelay, - isLoading, - relays, - scope, - accountStateViewModel, - account, - context, - ) - } - }, - ), - label = { - Text(stringResource(R.string.wss)) - }, - ) - IconButton( - onClick = { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth(), + value = textFieldRelay.value.text, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.None, + autoCorrectEnabled = false, + imeAction = ImeAction.Done, + ), + onValueChange = { + textFieldRelay.value = TextFieldValue(it) + }, + keyboardActions = KeyboardActions( + onDone = { scope.launch(Dispatchers.IO) { onAddRelay( textFieldRelay, @@ -148,16 +149,75 @@ fun EditConfigurationScreen( accountStateViewModel, account, context, + onDone = { + scope.launch(Dispatchers.IO) { + application?.let { + val localApplicationData = + it.application.copy( + name = name.text, + relays = relays, + ) + database.applicationDao().delete(it.application) + database.applicationDao().insertApplicationWithPermissions( + ApplicationWithPermissions( + localApplicationData, + it.permissions, + ), + ) + if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) { + NostrSigner.getInstance().checkForNewRelays() + } + } + } + }, ) } }, - ) { - Icon( - Icons.Default.Add, - null, - ) - } - } + ), + label = { + Text(stringResource(R.string.wss)) + }, + ) + + AmberButton( + onClick = { + scope.launch(Dispatchers.IO) { + onAddRelay( + textFieldRelay, + isLoading, + relays, + scope, + accountStateViewModel, + account, + context, + onDone = { + scope.launch(Dispatchers.IO) { + application?.let { + val localApplicationData = + it.application.copy( + name = name.text, + relays = relays, + ) + database.applicationDao().delete(it.application) + database.applicationDao().insertApplicationWithPermissions( + ApplicationWithPermissions( + localApplicationData, + it.permissions, + ), + ) + if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) { + NostrSigner.getInstance().checkForNewRelays() + } + } + } + }, + ) + } + }, + content = { + Text(stringResource(R.string.add)) + }, + ) Spacer(modifier = Modifier.height(16.dp)) @@ -184,6 +244,25 @@ fun EditConfigurationScreen( IconButton( onClick = { relays.removeAt(it) + scope.launch(Dispatchers.IO) { + application?.let { + val localApplicationData = + it.application.copy( + name = name.text, + relays = relays, + ) + database.applicationDao().delete(it.application) + database.applicationDao().insertApplicationWithPermissions( + ApplicationWithPermissions( + localApplicationData, + it.permissions, + ), + ) + if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) { + NostrSigner.getInstance().checkForNewRelays() + } + } + } }, ) { Icon( @@ -196,47 +275,6 @@ fun EditConfigurationScreen( } } - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - Button( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - onClick = { - application?.let { - scope.launch(Dispatchers.IO) { - val localApplicationData = - it.application.copy( - name = name.text, - relays = relays, - ) - database.applicationDao().delete(it.application) - database.applicationDao().insertApplicationWithPermissions( - ApplicationWithPermissions( - localApplicationData, - it.permissions, - ), - ) - if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) { - NostrSigner.getInstance().checkForNewRelays() - } - - scope.launch(Dispatchers.Main) { - navController.navigate(Route.Applications.route) { - popUpTo(0) - } - } - } - } - }, - content = { - Text(stringResource(R.string.update)) - }, - ) - } - Spacer(modifier = Modifier.height(16.dp)) Row( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt index 8d987700..a0e241de 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -54,7 +54,6 @@ import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.models.kindToNipUrl import com.greenart7c3.nostrsigner.models.nipToUrl -import com.greenart7c3.nostrsigner.ui.actions.EditRelaysDialog import com.greenart7c3.nostrsigner.ui.actions.RemoveAllPermissionsDialog import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.vitorpamplona.quartz.encoders.toHexKey @@ -66,7 +65,6 @@ import kotlinx.coroutines.launch fun EditPermission( modifier: Modifier, account: Account, - accountStateViewModel: AccountStateViewModel, selectedPackage: String, navController: NavController, database: AppDatabase, @@ -92,9 +90,6 @@ fun EditPermission( val relayString = NostrSigner.getInstance().settings.defaultRelays.joinToString(separator = "&") { "relay=${it.url}" } mutableStateOf("bunker://${account.keyPair.pubKey.toHexKey()}?$relayString$secret") } - var editRelaysDialog by remember { - mutableStateOf(false) - } LaunchedEffect(Unit) { launch(Dispatchers.IO) { @@ -107,25 +102,6 @@ fun EditPermission( } } - if (editRelaysDialog) { - EditRelaysDialog( - applicationData = applicationData, - accountStateViewModel = accountStateViewModel, - account = account, - onClose = { - editRelaysDialog = false - }, - ) { relays2 -> - applicationData = - applicationData.copy( - relays = relays2.map { it }, - ) - val relays = applicationData.relays.joinToString(separator = "&") { "relay=${it.url}" } - bunkerUri = "bunker://${account.keyPair.pubKey.toHexKey()}?$relays$secret" - editRelaysDialog = false - } - } - if (wantsToRemovePermissions) { RemoveAllPermissionsDialog( onCancel = { 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 2cd1c8ab..d390ad53 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -698,7 +698,6 @@ fun MainScreen( .fillMaxSize() .padding(padding), account = account, - accountStateViewModel = accountStateViewModel, selectedPackage = packageName, navController = navController, database = database, @@ -842,7 +841,6 @@ fun MainScreen( NewNsecBunkerCreatedScreen( database = database, account = account, - accountStateViewModel = accountStateViewModel, key = key, modifier = Modifier diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewNsecBunkerScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewNsecBunkerScreen.kt index 6aa24ed2..9ae80773 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewNsecBunkerScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/NewNsecBunkerScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -130,6 +129,8 @@ fun NewNsecBunkerScreen( accountStateViewModel, account, context, + onDone = { + }, ) } }, @@ -149,6 +150,8 @@ fun NewNsecBunkerScreen( accountStateViewModel, account, context, + onDone = { + }, ) } }, @@ -257,7 +260,6 @@ fun NewNsecBunkerCreatedScreen( database: AppDatabase, modifier: Modifier = Modifier, account: Account, - accountStateViewModel: AccountStateViewModel, key: String, ) { val isLoading = remember { mutableStateOf(false) } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt index 4b0b69d0..3921e262 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/EditRelaysDialog.kt @@ -3,7 +3,6 @@ package com.greenart7c3.nostrsigner.ui.actions import android.content.Context import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight @@ -15,7 +14,6 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Circle import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.ElevatedCard @@ -28,12 +26,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -44,27 +40,23 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.NostrSigner import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.database.ApplicationEntity import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.service.Nip11Retriever import com.greenart7c3.nostrsigner.ui.AccountStateViewModel import com.greenart7c3.nostrsigner.ui.CenterCircularProgressIndicator import com.greenart7c3.nostrsigner.ui.components.AmberButton -import com.greenart7c3.nostrsigner.ui.components.CloseButton -import com.greenart7c3.nostrsigner.ui.components.PostButton import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES import com.vitorpamplona.ammolite.relays.Client import com.vitorpamplona.ammolite.relays.Relay @@ -83,162 +75,6 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -@Composable -fun EditRelaysDialog( - applicationData: ApplicationEntity, - accountStateViewModel: AccountStateViewModel, - account: Account, - onClose: () -> Unit, - onPost: (SnapshotStateList) -> Unit, -) { - val scope = rememberCoroutineScope() - val context = LocalContext.current - val textFieldRelay = remember { - mutableStateOf(TextFieldValue("")) - } - val isLoading = remember { - mutableStateOf(false) - } - val relays2 = - remember { - val localRelays = mutableStateListOf() - applicationData.relays.forEach { - localRelays.add( - it.copy(), - ) - } - localRelays - } - Dialog( - onDismissRequest = onClose, - properties = DialogProperties(usePlatformDefaultWidth = false), - ) { - Surface( - color = MaterialTheme.colorScheme.background, - modifier = Modifier - .fillMaxSize(), - ) { - if (isLoading.value) { - CenterCircularProgressIndicator(Modifier) - } else { - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .fillMaxSize(), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - CloseButton { - onClose() - } - PostButton( - isActive = !applicationData.isConnected, - onPost = { - onPost(relays2) - }, - ) - } - LazyColumn( - Modifier - .fillMaxWidth(), - ) { - items(relays2.size) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - relays2[it].url, - Modifier - .weight(0.9f) - .padding(8.dp), - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - IconButton( - onClick = { - relays2.removeAt(it) - }, - ) { - Icon( - Icons.Default.Delete, - stringResource(R.string.delete), - ) - } - } - } - } - Row( - Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - OutlinedTextField( - enabled = !applicationData.isConnected, - modifier = Modifier - .fillMaxWidth(0.9f) - .padding(horizontal = 16.dp), - value = textFieldRelay.value.text, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, - ), - onValueChange = { - textFieldRelay.value = TextFieldValue(it) - }, - keyboardActions = KeyboardActions( - onDone = { - if (applicationData.isConnected) return@KeyboardActions - - scope.launch(Dispatchers.IO) { - onAddRelay( - textFieldRelay, - isLoading, - relays2, - scope, - accountStateViewModel, - account, - context, - ) - } - }, - ), - label = { - Text("Relay") - }, - ) - IconButton( - onClick = { - if (applicationData.isConnected) return@IconButton - scope.launch(Dispatchers.IO) { - onAddRelay( - textFieldRelay, - isLoading, - relays2, - scope, - accountStateViewModel, - account, - context, - ) - } - }, - ) { - Icon( - Icons.Default.Add, - null, - ) - } - } - } - } - } - } -} - @Composable fun DefaultRelaysScreen( modifier: Modifier, @@ -278,42 +114,21 @@ fun DefaultRelaysScreen( .background(MaterialTheme.colorScheme.background) .fillMaxSize(), ) { - Row( - Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - OutlinedTextField( - modifier = Modifier - .fillMaxWidth(0.9f) - .padding(horizontal = 16.dp), - value = textFieldRelay.value.text, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, - ), - onValueChange = { - textFieldRelay.value = TextFieldValue(it) - }, - keyboardActions = KeyboardActions( - onDone = { - scope.launch(Dispatchers.IO) { - onAddRelay( - textFieldRelay, - isLoading, - relays2, - scope, - accountStateViewModel, - account, - context, - ) - } - }, - ), - label = { - Text("Relay") - }, - ) - IconButton( - onClick = { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + value = textFieldRelay.value.text, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.None, + autoCorrectEnabled = false, + imeAction = ImeAction.Done, + ), + onValueChange = { + textFieldRelay.value = TextFieldValue(it) + }, + keyboardActions = KeyboardActions( + onDone = { scope.launch(Dispatchers.IO) { onAddRelay( textFieldRelay, @@ -323,16 +138,48 @@ fun DefaultRelaysScreen( accountStateViewModel, account, context, + onDone = { + NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( + defaultRelays = relays2, + ) + LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) + }, ) } }, - ) { - Icon( - Icons.Default.Add, - null, - ) - } - } + ), + label = { + Text("Relay") + }, + ) + + AmberButton( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + onClick = { + scope.launch(Dispatchers.IO) { + onAddRelay( + textFieldRelay, + isLoading, + relays2, + scope, + accountStateViewModel, + account, + context, + onDone = { + NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( + defaultRelays = relays2, + ) + LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) + }, + ) + } + }, + content = { + Text(stringResource(R.string.add)) + }, + ) LazyColumn( Modifier @@ -346,7 +193,7 @@ fun DefaultRelaysScreen( relays2[it].url, Modifier .weight(0.9f) - .padding(8.dp), + .padding(16.dp), fontWeight = FontWeight.Bold, fontSize = 18.sp, maxLines = 1, @@ -355,6 +202,10 @@ fun DefaultRelaysScreen( IconButton( onClick = { relays2.removeAt(it) + NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( + defaultRelays = relays2, + ) + LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) }, ) { Icon( @@ -365,26 +216,6 @@ fun DefaultRelaysScreen( } } } - AmberButton( - onClick = { - scope.launch(Dispatchers.IO) { - if (relays2.isNotEmpty()) { - NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( - defaultRelays = relays2, - ) - LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) - } - scope.launch(Dispatchers.Main) { - navController.navigateUp() - } - } - }, - content = { - Text( - text = stringResource(R.string.save), - ) - }, - ) } } } @@ -398,6 +229,7 @@ suspend fun onAddRelay( accountStateViewModel: AccountStateViewModel, account: Account, context: Context, + onDone: () -> Unit, ) { val url = textFieldRelay.value.text if (url.isNotBlank() && url != "/") { @@ -492,6 +324,7 @@ suspend fun onAddRelay( feedTypes = COMMON_FEED_TYPES, ), ) + onDone() } else { accountStateViewModel.toast( context.getString(R.string.relay), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c69e9738..b77af3da 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -408,4 +408,5 @@ Activity To create a new nsecbunker you need to give it a name and select one or more relays, that’s all! Update + Add From c1fecd3be4ef2c84be4ccd4511dafca3106cc5f4 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 23 Oct 2024 11:08:19 -0300 Subject: [PATCH 26/30] Fix crash on navigation --- .../greenart7c3/nostrsigner/MainViewModel.kt | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt index ae8c9018..afcb9920 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.net.Uri +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.NavHostController @@ -60,8 +61,15 @@ class MainViewModel(val context: Context) : ViewModel() { if (requests.isNotEmpty()) { viewModelScope.launch(Dispatchers.Main) { - navController?.navigate(Route.IncomingRequest.route) { - popUpTo(0) + val hasNavGraph = navController?.currentDestination?.id + while (hasNavGraph == null || hasNavGraph == 0) { + Log.d("MainViewModel", "Waiting for nav graph") + delay(100) + } + if (hasNavGraph != 0) { + navController?.navigate(Route.IncomingRequest.route) { + popUpTo(0) + } } } } @@ -92,8 +100,15 @@ class MainViewModel(val context: Context) : ViewModel() { intent.getStringExtra("route")?.let { viewModelScope.launch(Dispatchers.Main) { - navController?.navigate(it) { - popUpTo(0) + val hasNavGraph = navController?.currentDestination?.id + while (hasNavGraph == null || hasNavGraph == 0) { + Log.d("MainViewModel", "Waiting for nav graph") + delay(100) + } + if (hasNavGraph != 0) { + navController?.navigate(Route.IncomingRequest.route) { + popUpTo(0) + } } } } From b60880f071b3855798a463ee6dde874420273679 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 23 Oct 2024 11:20:09 -0300 Subject: [PATCH 27/30] Fix crash on navigation --- .../greenart7c3/nostrsigner/MainViewModel.kt | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt index afcb9920..6313204d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt @@ -61,14 +61,18 @@ class MainViewModel(val context: Context) : ViewModel() { if (requests.isNotEmpty()) { viewModelScope.launch(Dispatchers.Main) { - val hasNavGraph = navController?.currentDestination?.id - while (hasNavGraph == null || hasNavGraph == 0) { - Log.d("MainViewModel", "Waiting for nav graph") + var error = true + var count = 0 + while (error && count < 10) { delay(100) - } - if (hasNavGraph != 0) { - navController?.navigate(Route.IncomingRequest.route) { - popUpTo(0) + count++ + try { + navController?.navigate(Route.IncomingRequest.route) { + popUpTo(0) + } + error = false + } catch (e: Exception) { + Log.e("MainViewModel", "Error showing bunker requests", e) } } } @@ -100,14 +104,18 @@ class MainViewModel(val context: Context) : ViewModel() { intent.getStringExtra("route")?.let { viewModelScope.launch(Dispatchers.Main) { - val hasNavGraph = navController?.currentDestination?.id - while (hasNavGraph == null || hasNavGraph == 0) { - Log.d("MainViewModel", "Waiting for nav graph") + var error = true + var count = 0 + while (error && count < 10) { delay(100) - } - if (hasNavGraph != 0) { - navController?.navigate(Route.IncomingRequest.route) { - popUpTo(0) + count++ + try { + navController?.navigate(Route.IncomingRequest.route) { + popUpTo(0) + } + error = false + } catch (e: Exception) { + Log.e("MainViewModel", "Error showing bunker requests", e) } } } From 5c03a99000b91c79367108608d52237a14b0cd64 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 25 Oct 2024 08:41:13 -0300 Subject: [PATCH 28/30] Support for custom pin --- .../nostrsigner/LocalPreferences.kt | 28 +++ .../greenart7c3/nostrsigner/MainActivity.kt | 104 ++++++++--- .../nostrsigner/models/AmberSettings.kt | 1 + .../greenart7c3/nostrsigner/ui/MainScreen.kt | 29 +++ .../nostrsigner/ui/SecurityScreen.kt | 34 ++++ .../nostrsigner/ui/SettingsScreen.kt | 27 ++- .../nostrsigner/ui/SetupPinScreen.kt | 72 +++++++ .../ui/components/RandomPinInput.kt | 176 ++++++++++++++++++ .../nostrsigner/ui/navigation/Route.kt | 16 ++ app/src/main/res/values/strings.xml | 5 + 10 files changed, 451 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/SetupPinScreen.kt create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RandomPinInput.kt diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt index 1c664724..8b1a4dbf 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt @@ -33,6 +33,8 @@ private const val DEBUG_PLAINTEXT_PREFERENCES = false private const val DEBUG_PREFERENCES_NAME = "debug_prefs" private object PrefKeys { + const val PIN = "pin" + const val USE_PIN = "use_pin" const val CURRENT_ACCOUNT = "currently_logged_in_account" const val SAVED_ACCOUNTS = "all_saved_accounts" const val NOSTR_PRIVKEY = "nostr_privkey" @@ -115,6 +117,23 @@ object LocalPreferences { putBoolean(PrefKeys.USE_AUTH, settings.useAuth) putInt(PrefKeys.NOTIFICATION_TYPE, settings.notificationType.screenCode) putInt(PrefKeys.BIOMETRICS_TYPE, settings.biometricsTimeType.screenCode) + putBoolean(PrefKeys.USE_PIN, settings.usePin) + }.apply() + } + + fun loadPinFromEncryptedStorage(): String? { + val context = NostrSigner.getInstance() + return encryptedPreferences(context).getString(PrefKeys.PIN, null) + } + + fun savePinToEncryptedStorage(pin: String?) { + val context = NostrSigner.getInstance() + encryptedPreferences(context).edit().apply { + if (pin == null) { + remove(PrefKeys.PIN) + } else { + putString(PrefKeys.PIN, pin) + } }.apply() } @@ -138,6 +157,7 @@ object LocalPreferences { useAuth = getBoolean(PrefKeys.USE_AUTH, false), notificationType = parseNotificationType(getInt(PrefKeys.NOTIFICATION_TYPE, 1)), biometricsTimeType = parseBiometricsTimeType(getInt(PrefKeys.BIOMETRICS_TYPE, 0)), + usePin = getBoolean(PrefKeys.USE_PIN, false), ) } } @@ -404,4 +424,12 @@ object LocalPreferences { return account } } + + fun clearPin() { + val context = NostrSigner.getInstance() + encryptedPreferences(context).edit().apply { + remove(PrefKeys.USE_PIN) + }.apply() + NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(usePin = false) + } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt index e13c4ab9..59c2ee95 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt @@ -26,6 +26,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.lifecycle.viewmodel.compose.viewModel @@ -35,6 +37,7 @@ import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.ui.AccountScreen import com.greenart7c3.nostrsigner.ui.AccountStateViewModel import com.greenart7c3.nostrsigner.ui.BiometricsTimeType +import com.greenart7c3.nostrsigner.ui.components.RandomPinInput import com.greenart7c3.nostrsigner.ui.theme.NostrSignerTheme import com.vitorpamplona.ammolite.service.HttpClientManager import java.time.Duration @@ -58,7 +61,6 @@ class MainActivity : AppCompatActivity() { mainViewModel = MainViewModel(applicationContext) HttpClientManager.setDefaultUserAgent("Amber/${BuildConfig.VERSION_NAME}") - setContent { if (intent.isLaunchFromHistory()) { Log.d("isLaunchFromHistory", "Cleared intent history") @@ -78,6 +80,7 @@ class MainActivity : AppCompatActivity() { val navController = rememberNavController() mainViewModel.navController = navController var isAuthenticated by remember { mutableStateOf(false) } + var showPinDialog by remember { mutableStateOf(false) } val keyguardLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> if (result.resultCode == Activity.RESULT_OK) { @@ -89,43 +92,48 @@ class MainActivity : AppCompatActivity() { LaunchedEffect(Unit) { launch(Dispatchers.IO) { - val lastAuthTime = NostrSigner.getInstance().settings.lastBiometricsTime - val whenToAsk = NostrSigner.getInstance().settings.biometricsTimeType + val settings = NostrSigner.getInstance().settings + val lastAuthTime = settings.lastBiometricsTime + val whenToAsk = settings.biometricsTimeType val isTimeToAsk = when (whenToAsk) { BiometricsTimeType.EVERY_TIME -> true BiometricsTimeType.ONE_MINUTE -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 1 BiometricsTimeType.FIVE_MINUTES -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 5 BiometricsTimeType.TEN_MINUTES -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 10 } - val shouldAuthenticate = NostrSigner.getInstance().settings.useAuth && isTimeToAsk + val shouldAuthenticate = (settings.useAuth || settings.usePin) && isTimeToAsk if (!shouldAuthenticate) { isAuthenticated = true } else { if (!isAuthenticated) { launch(Dispatchers.Main) { - Biometrics.authenticate( - getString(R.string.authenticate), - this@MainActivity, - keyguardLauncher, - { - NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( - lastBiometricsTime = System.currentTimeMillis(), - ) + if (settings.usePin) { + showPinDialog = true + } else { + Biometrics.authenticate( + getString(R.string.authenticate), + this@MainActivity, + keyguardLauncher, + { + NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( + lastBiometricsTime = System.currentTimeMillis(), + ) - LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) - isAuthenticated = true - }, - { _, message -> - this@MainActivity.finish() - scope.launch { - Toast.makeText( - context, - message, - Toast.LENGTH_SHORT, - ).show() - } - }, - ) + LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) + isAuthenticated = true + }, + { _, message -> + this@MainActivity.finish() + scope.launch { + Toast.makeText( + context, + message, + Toast.LENGTH_SHORT, + ).show() + } + }, + ) + } } } } @@ -136,6 +144,50 @@ class MainActivity : AppCompatActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background, ) { + if (showPinDialog) { + Dialog( + properties = DialogProperties(usePlatformDefaultWidth = false), + onDismissRequest = { + showPinDialog = false + this@MainActivity.finish() + scope.launch { + Toast.makeText( + context, + getString(R.string.pin_does_not_match), + Toast.LENGTH_SHORT, + ).show() + } + }, + ) { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + ) { + RandomPinInput( + text = getString(R.string.enter_pin), + onPinEntered = { + val pin = LocalPreferences.loadPinFromEncryptedStorage() + if (it == pin) { + NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy( + lastBiometricsTime = System.currentTimeMillis(), + ) + + LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) + isAuthenticated = true + showPinDialog = false + } else { + Toast.makeText( + context, + getString(R.string.pin_does_not_match), + Toast.LENGTH_SHORT, + ).show() + } + }, + ) + } + } + } + if (!isAuthenticated) { Box( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/AmberSettings.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/AmberSettings.kt index 158e56a8..16836a6c 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/AmberSettings.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/AmberSettings.kt @@ -13,4 +13,5 @@ data class AmberSettings( val useAuth: Boolean = false, val notificationType: NotificationType = NotificationType.DIRECT, val biometricsTimeType: BiometricsTimeType = BiometricsTimeType.EVERY_TIME, + val usePin: Boolean = false, ) 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 d390ad53..cd347b63 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -903,6 +903,35 @@ fun MainScreen( } }, ) + + composable( + Route.SetupPin.route, + content = { + SetupPinScreen( + modifier = Modifier + .fillMaxSize() + .padding(padding), + navController = navController, + ) + }, + ) + + composable( + Route.ConfirmPin.route, + arguments = listOf(navArgument("pin") { type = NavType.StringType }), + content = { + it.arguments?.getString("pin")?.let { pin -> + ConfirmPinScreen( + modifier = Modifier + .fillMaxSize() + .padding(padding), + accountStateViewModel = accountStateViewModel, + pin = pin, + navController = navController, + ) + } + }, + ) } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt index 19c02da8..e42da153 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt @@ -28,6 +28,7 @@ import com.greenart7c3.nostrsigner.NostrSigner import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.greenart7c3.nostrsigner.ui.components.TitleExplainer +import com.greenart7c3.nostrsigner.ui.navigation.Route import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -45,6 +46,7 @@ fun SecurityScreen( TitleExplainer(stringResource(BiometricsTimeType.TEN_MINUTES.resourceId)), ) var enableBiometrics by remember { mutableStateOf(NostrSigner.getInstance().settings.useAuth) } + var setupPin by remember { mutableStateOf(NostrSigner.getInstance().settings.usePin) } var biometricsIndex by remember { mutableIntStateOf(NostrSigner.getInstance().settings.biometricsTimeType.screenCode) } @@ -81,6 +83,38 @@ fun SecurityScreen( }, ) } + + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding(horizontal = 8.dp) + .clickable { + if (setupPin) { + val pin = LocalPreferences.loadPinFromEncryptedStorage() + navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin") + } else { + navController.navigate(Route.SetupPin.route) + } + }, + ) { + Text( + modifier = Modifier.weight(1f), + text = stringResource(R.string.setup_pin), + ) + Switch( + checked = setupPin, + onCheckedChange = { + if (setupPin) { + val pin = LocalPreferences.loadPinFromEncryptedStorage() + navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin") + } else { + navController.navigate(Route.SetupPin.route) + } + }, + ) + } + Box( Modifier .padding(8.dp), 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 77c38b45..1643c68e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt @@ -54,7 +54,6 @@ import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.NostrSigner import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account -import com.greenart7c3.nostrsigner.service.Biometrics import com.greenart7c3.nostrsigner.service.NotificationDataSource import com.greenart7c3.nostrsigner.ui.actions.ConnectOrbotDialog import com.greenart7c3.nostrsigner.ui.actions.LogoutDialog @@ -101,20 +100,18 @@ fun SettingsScreen( modifier .verticalScroll(rememberScrollState()), ) { - if (Biometrics.isFingerprintAvailable(context)) { - Box( - Modifier - .padding(8.dp), - ) { - IconRow( - title = stringResource(R.string.security), - icon = Icons.Default.Security, - tint = MaterialTheme.colorScheme.onBackground, - onClick = { - navController.navigate(Route.Security.route) - }, - ) - } + Box( + Modifier + .padding(8.dp), + ) { + IconRow( + title = stringResource(R.string.security), + icon = Icons.Default.Security, + tint = MaterialTheme.colorScheme.onBackground, + onClick = { + navController.navigate(Route.Security.route) + }, + ) } Box( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SetupPinScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SetupPinScreen.kt new file mode 100644 index 00000000..9d4213ea --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SetupPinScreen.kt @@ -0,0 +1,72 @@ +package com.greenart7c3.nostrsigner.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.greenart7c3.nostrsigner.LocalPreferences +import com.greenart7c3.nostrsigner.NostrSigner +import com.greenart7c3.nostrsigner.R +import com.greenart7c3.nostrsigner.ui.components.RandomPinInput +import com.greenart7c3.nostrsigner.ui.navigation.Route + +@Composable +fun SetupPinScreen( + modifier: Modifier = Modifier, + navController: NavController, +) { + Column( + modifier + .padding(16.dp), + ) { + RandomPinInput { pin -> + navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin") + } + } +} + +@Composable +fun ConfirmPinScreen( + modifier: Modifier = Modifier, + accountStateViewModel: AccountStateViewModel, + pin: String, + navController: NavController, +) { + val context = LocalContext.current + Column( + modifier + .padding(16.dp), + ) { + RandomPinInput( + onPinEntered = { enteredPin -> + if (enteredPin != pin) { + accountStateViewModel.toast(context.getString(R.string.pin), context.getString(R.string.pin_does_not_match)) + } else { + val usePin = NostrSigner.getInstance().settings.usePin + if (usePin) { + NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(usePin = false) + LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) + LocalPreferences.savePinToEncryptedStorage(null) + navController.navigate(Route.Security.route) { + popUpTo(Route.Security.route) { + inclusive = true + } + } + } else { + NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(usePin = true) + LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings) + LocalPreferences.savePinToEncryptedStorage(pin) + navController.navigate(Route.Security.route) { + popUpTo(Route.Security.route) { + inclusive = true + } + } + } + } + }, + ) + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RandomPinInput.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RandomPinInput.kt new file mode 100644 index 00000000..d198e735 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RandomPinInput.kt @@ -0,0 +1,176 @@ +package com.greenart7c3.nostrsigner.ui.components + +import android.view.WindowManager +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Backspace +import androidx.compose.material.icons.filled.Backspace +import androidx.compose.material.icons.filled.Done +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.LocalLifecycleOwner +import com.greenart7c3.nostrsigner.service.getAppCompatActivity + +@Composable +fun RandomPinInput( + text: String? = null, + onPinEntered: (String) -> Unit, +) { + val randomNumbers = remember { (0..9).shuffled() } + var selectedPin by remember { mutableStateOf("") } + var obscureText by remember { mutableStateOf(true) } + + val activity = LocalContext.current.getAppCompatActivity() + val lifecycleOwner = LocalLifecycleOwner.current + + DisposableEffect(key1 = lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_START -> { + activity?.window?.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE, + ) + } + + Lifecycle.Event.ON_PAUSE -> { + activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + else -> {} + } + } + lifecycleOwner.lifecycle.addObserver(observer) + + onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } + } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .height(100.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + text?.let { + Text( + text = it, + fontWeight = FontWeight.Bold, + ) + Spacer(modifier = Modifier.height(8.dp)) + } + + Text( + text = if (!obscureText) selectedPin else selectedPin.map { '*' }.joinToString(""), + ) + + if (selectedPin.isNotEmpty()) { + IconButton( + onClick = { obscureText = !obscureText }, + ) { + Icon( + imageVector = if (obscureText) Icons.Default.Visibility else Icons.Default.VisibilityOff, + contentDescription = "Show/Hide", + ) + } + } + } + + LazyVerticalGrid( + columns = GridCells.Fixed(3), + modifier = Modifier.fillMaxWidth(), + ) { + itemsIndexed(randomNumbers.dropLast(1)) { _, number -> + Button( + onClick = { + if (selectedPin.length < 6) { + selectedPin += number.toString() + } + }, + modifier = Modifier.padding(8.dp), + ) { + Text(number.toString()) + } + } + + item { + Button( + onClick = { + if (selectedPin.isNotEmpty()) { + selectedPin = selectedPin.dropLast(1) // Remove last digit + } + }, + modifier = Modifier.weight(1f).padding(8.dp), + ) { + Icon( + Icons.AutoMirrored.Filled.Backspace, + contentDescription = "Erase", + ) + } + } + + item { + Button( + onClick = { + if (selectedPin.length < 6) { + selectedPin += randomNumbers.last().toString() + } + }, + modifier = Modifier.weight(1f).padding(8.dp), + ) { + Text(randomNumbers.last().toString()) + } + } + + item { + Button( + enabled = selectedPin.length >= 4, + onClick = { + if (selectedPin.length >= 4) { + onPinEntered(selectedPin) // Confirm the PIN + } + }, + modifier = Modifier.weight(1f).padding(8.dp), + ) { + Icon( + Icons.Default.Done, + contentDescription = "Done", + ) + } + } + } + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt index aafccad9..f245df01 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/navigation/Route.kt @@ -157,6 +157,20 @@ sealed class Route( icon = Icons.Outlined.Settings, selectedIcon = Icons.Default.Settings, ) + + data object SetupPin : Route( + title = NostrSigner.getInstance().getString(R.string.setup_pin), + route = "SetupPin", + icon = Icons.Outlined.Settings, + selectedIcon = Icons.Default.Settings, + ) + + data object ConfirmPin : Route( + title = NostrSigner.getInstance().getString(R.string.confirm_pin), + route = "ConfirmPin/{pin}", + icon = Icons.Outlined.Settings, + selectedIcon = Icons.Default.Settings, + ) } val routes = listOf( @@ -179,4 +193,6 @@ val routes = listOf( Route.Activity, Route.RelayLogScreen, Route.EditConfiguration, + Route.SetupPin, + Route.ConfirmPin, ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b77af3da..1c885de4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -409,4 +409,9 @@ To create a new nsecbunker you need to give it a name and select one or more relays, that’s all! Update Add + Setup PIN + Pin + Pin does not match + Confirm Pin + Enter PIN From 213e10d8eb3c171705f3a2bf166e5a80e39e2855 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 25 Oct 2024 09:49:48 -0300 Subject: [PATCH 29/30] Show colored border for each account --- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) 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 cd347b63..a8c4c602 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build +import android.util.Log import android.widget.Toast import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult @@ -13,6 +14,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -398,6 +400,13 @@ private suspend fun initNotifications(context: Context) { PushNotificationUtils.init(LocalPreferences.allSavedAccounts(context)) } +fun Color.Companion.fromHex(colorString: String) = try { + Color(android.graphics.Color.parseColor("#$colorString")) +} catch (e: Exception) { + Log.e("Color", "Failed to parse color: $colorString", e) + Unspecified +} + @OptIn(ExperimentalMaterial3Api::class) @Composable fun MainScreen( @@ -573,10 +582,22 @@ fun MainScreen( } }, icon = { - Icon( - if (selected) it.selectedIcon else it.icon, - it.route, - ) + if (it.route == Route.Accounts.route) { + Icon( + if (selected) it.selectedIcon else it.icon, + it.route, + modifier = Modifier.border( + 2.dp, + Color.fromHex(account.keyPair.pubKey.toHexKey().slice(0..5)), + CircleShape, + ), + ) + } else { + Icon( + if (selected) it.selectedIcon else it.icon, + it.route, + ) + } }, ) } From 7faf183f1c03bb54580b12a6f9c2a1d5edbc02b2 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 25 Oct 2024 13:07:22 -0300 Subject: [PATCH 30/30] refactor BootReceiver --- .../nostrsigner/service/BootReceiver.kt | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/BootReceiver.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/BootReceiver.kt index a2dce3d5..1ffbbb2b 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/BootReceiver.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/BootReceiver.kt @@ -4,7 +4,6 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.os.Build -import android.util.Log import com.greenart7c3.nostrsigner.BuildConfig class BootReceiver : BroadcastReceiver() { @@ -12,9 +11,26 @@ class BootReceiver : BroadcastReceiver() { @Suppress("KotlinConstantConditions") if (BuildConfig.FLAVOR == "offline") return - if (intent.action == Intent.ACTION_PACKAGE_REPLACED && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - if (intent.dataString?.contains("com.greenart7c3.nostrsigner") == true) { - Log.d("BootReceiver", "Starting ConnectivityService ACTION_PACKAGE_REPLACED") + when (intent.action) { + Intent.ACTION_PACKAGE_REPLACED -> { + if (intent.dataString?.contains("com.greenart7c3.nostrsigner") == true && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + context.startForegroundService( + Intent( + context, + ConnectivityService::class.java, + ), + ) + } + } + Intent.ACTION_MY_PACKAGE_REPLACED -> { + context.startForegroundService( + Intent( + context, + ConnectivityService::class.java, + ), + ) + } + Intent.ACTION_BOOT_COMPLETED -> { context.startForegroundService( Intent( context, @@ -23,25 +39,5 @@ class BootReceiver : BroadcastReceiver() { ) } } - - if (intent.action == Intent.ACTION_MY_PACKAGE_REPLACED) { - Log.d("BootReceiver", "Starting ConnectivityService ACTION_MY_PACKAGE_REPLACED") - context.startForegroundService( - Intent( - context, - ConnectivityService::class.java, - ), - ) - } - - if (intent.action == Intent.ACTION_BOOT_COMPLETED) { - Log.d("BootReceiver", "Starting ConnectivityService ACTION_BOOT_COMPLETED") - context.startForegroundService( - Intent( - context, - ConnectivityService::class.java, - ), - ) - } } }