From a11b8a852cefd2b245f5f747e7386cb0a320140e Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 6 Sep 2023 10:45:44 -0300 Subject: [PATCH] add remember my choice switch --- .../nostrsigner/LocalPreferences.kt | 19 +++- .../greenart7c3/nostrsigner/models/Account.kt | 3 +- .../nostrsigner/ui/AccountStateViewModel.kt | 10 +- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 106 +++++++++++++++++- .../ui/previews/EventDataPreview.kt | 6 + 5 files changed, 134 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt index d1661df0..1c60863d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt @@ -9,6 +9,7 @@ import com.greenart7c3.nostrsigner.models.toHexKey import com.greenart7c3.nostrsigner.service.KeyPair import com.greenart7c3.nostrsigner.service.toNpub import fr.acinq.secp256k1.jni.BuildConfig +import org.json.JSONObject import java.io.File // Release mode (!BuildConfig.DEBUG) always uses encrypted preferences @@ -22,6 +23,7 @@ private object PrefKeys { const val SAVED_ACCOUNTS = "all_saved_accounts" const val NOSTR_PRIVKEY = "nostr_privkey" const val NOSTR_PUBKEY = "nostr_pubkey" + const val REMEMBER_APPS = "remember_apps" } object LocalPreferences { @@ -151,6 +153,8 @@ object LocalPreferences { prefs.edit().apply { account.keyPair.privKey.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHexKey()) } account.keyPair.pubKey.let { putString(PrefKeys.NOSTR_PUBKEY, it.toHexKey()) } + val jsonObject = JSONObject(account.savedApps.toMap()) + putString(PrefKeys.REMEMBER_APPS, jsonObject.toString()) }.apply() } @@ -163,8 +167,21 @@ object LocalPreferences { val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return null val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null) + val jsonString = getString(PrefKeys.REMEMBER_APPS, null) + val outputMap = mutableMapOf() + if (jsonString != null) { + val jsonObject = JSONObject(jsonString) + val keysItr: Iterator = jsonObject.keys() + while (keysItr.hasNext()) { + val key = keysItr.next() + val value: Boolean = jsonObject.getBoolean(key) + outputMap[key] = value + } + } + return Account( - keyPair = KeyPair(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()) + keyPair = KeyPair(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()), + savedApps = outputMap ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt index 4537b6fd..3143e808 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt @@ -7,7 +7,8 @@ import com.greenart7c3.nostrsigner.service.KeyPair @Stable class Account( - val keyPair: KeyPair + val keyPair: KeyPair, + val savedApps: MutableMap ) { val saveable: AccountLiveData = AccountLiveData(this) } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt index 962bd7f7..774c04a2 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt @@ -52,9 +52,9 @@ class AccountStateViewModel : ViewModel() { fun startUI(key: String) { val account = if (key.startsWith("nsec")) { - Account(KeyPair(privKey = key.bechToBytes())) + Account(KeyPair(privKey = key.bechToBytes()), mutableMapOf()) } else { - Account(KeyPair(Hex.decode(key))) + Account(KeyPair(Hex.decode(key)), mutableMapOf()) } LocalPreferences.updatePrefsForLogin(account) @@ -62,16 +62,14 @@ class AccountStateViewModel : ViewModel() { } fun newKey() { - val account = Account(KeyPair()) + val account = Account(KeyPair(), mutableMapOf()) LocalPreferences.updatePrefsForLogin(account) startUI(account) } @OptIn(DelicateCoroutinesApi::class) fun startUI(account: Account) { - if (account.keyPair.privKey != null) { - _accountContent.update { AccountState.LoggedIn(account) } - } + _accountContent.update { AccountState.LoggedIn(account) } GlobalScope.launch(Dispatchers.Main) { account.saveable.observeForever(saveListener) 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 cafd7334..73179b76 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.Activity.RESULT_OK import android.content.Intent import android.widget.Toast +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -22,6 +23,7 @@ import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Scaffold +import androidx.compose.material.Switch import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccessTime @@ -31,6 +33,8 @@ import androidx.compose.material.icons.filled.Person import androidx.compose.material.rememberDrawerState import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -49,6 +53,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.IntentData @@ -76,7 +81,6 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j val coroutineScope = rememberCoroutineScope() val context = LocalContext.current val clipboardManager = LocalClipboardManager.current - Scaffold( scaffoldState = scaffoldState, drawerContent = { @@ -107,7 +111,15 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j val appName = packageName ?: it.name when (it.type) { SignerType.GET_PUBLIC_KEY -> { + val key = "$packageName-public-key" + val remember = remember { + mutableStateOf(account.savedApps[key] ?: false) + } + val shouldRunOnAccept = account.savedApps[key] ?: false LoginWithPubKey( + shouldRunOnAccept, + remember, + packageName, appName, { val sig = account.keyPair.pubKey.toNpub() @@ -115,6 +127,8 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j coroutineScope.launch { val activity = context.getAppCompatActivity() if (packageName != null) { + account.savedApps[key] = remember.value + LocalPreferences.saveToEncryptedStorage(account) val intent = Intent() intent.putExtra("signature", sig) activity?.setResult(RESULT_OK, intent) @@ -136,7 +150,19 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j ) } SignerType.NIP04_DECRYPT, SignerType.NIP04_ENCRYPT -> { + val key = if (it.type == SignerType.NIP04_DECRYPT) { + "$packageName-decryptnip4" + } else { + "$packageName-encryptnip4" + } + val remember = remember { + mutableStateOf(account.savedApps[key] ?: false) + } + val shouldRunOnAccept = account.savedApps[key] ?: false EncryptDecryptData( + shouldRunOnAccept, + remember, + packageName, appName, it.type, { @@ -157,6 +183,8 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j coroutineScope.launch { val activity = context.getAppCompatActivity() if (packageName != null) { + account.savedApps[key] = remember.value + LocalPreferences.saveToEncryptedStorage(account) val intent = Intent() intent.putExtra("signature", sig) activity?.setResult(RESULT_OK, intent) @@ -190,8 +218,15 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j } else -> { val event = IntentUtils.getIntent(it.data, account.keyPair) - + val key = "$packageName-${event.kind}" + val remember = remember { + mutableStateOf(account.savedApps[key] ?: false) + } + val shouldRunOnAccept = account.savedApps[key] ?: false EventData( + shouldRunOnAccept, + remember, + packageName, appName, event, event.toJson(), @@ -213,6 +248,8 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j coroutineScope.launch { val activity = context.getAppCompatActivity() if (packageName != null) { + account.savedApps[key] = remember.value + LocalPreferences.saveToEncryptedStorage(account) val intent = Intent() val signedEvent = Event(event.id, event.pubKey, event.createdAt, event.kind, event.tags, event.content, sig) intent.putExtra("event", signedEvent.toJson()) @@ -251,6 +288,41 @@ fun AppTitle(appName: String) { ) } +@Composable +fun RememberMyChoice( + shouldRunOnAccept: Boolean, + remember: MutableState, + packageName: String?, + onAccept: () -> Unit +) { + if (shouldRunOnAccept) { + LaunchedEffect(Unit) { + onAccept() + } + } + if (packageName != null) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clickable { + remember.value = !remember.value + } + ) { + Text( + modifier = Modifier.weight(1f), + text = "Remember my choice and don't ask again" + ) + Switch( + checked = remember.value, + onCheckedChange = { + remember.value = !remember.value + } + ) + } + } +} + @Composable fun AcceptRejectButtons( onAccept: () -> Unit, @@ -280,6 +352,9 @@ fun AcceptRejectButtons( @Composable fun EncryptDecryptData( + shouldRunOnAccept: Boolean, + remember: MutableState, + packageName: String?, appName: String, type: SignerType, onAccept: () -> Unit, @@ -307,6 +382,13 @@ fun EncryptDecryptData( Spacer(modifier = Modifier.weight(1f)) + RememberMyChoice( + shouldRunOnAccept, + remember, + packageName, + onAccept + ) + AcceptRejectButtons( onAccept, onReject @@ -316,6 +398,9 @@ fun EncryptDecryptData( @Composable fun LoginWithPubKey( + shouldRunOnAccept: Boolean, + remember: MutableState, + packageName: String?, appName: String, onAccept: () -> Unit, onReject: () -> Unit @@ -344,6 +429,13 @@ fun LoginWithPubKey( Spacer(modifier = Modifier.weight(1f)) + RememberMyChoice( + shouldRunOnAccept, + remember, + packageName, + onAccept + ) + AcceptRejectButtons( onAccept, onReject @@ -353,6 +445,9 @@ fun LoginWithPubKey( @Composable fun EventData( + shouldRunOnAccept: Boolean, + remember: MutableState, + packageName: String?, appName: String, event: Event, rawJson: String, @@ -428,6 +523,13 @@ fun EventData( Spacer(modifier = Modifier.weight(1f)) } + RememberMyChoice( + shouldRunOnAccept, + remember, + packageName, + onAccept + ) + AcceptRejectButtons( onAccept, onReject diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/previews/EventDataPreview.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/previews/EventDataPreview.kt index 118876aa..0ee03799 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/previews/EventDataPreview.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/previews/EventDataPreview.kt @@ -3,6 +3,8 @@ package com.greenart7c3.nostrsigner.ui.previews import android.annotation.SuppressLint import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.tooling.preview.Preview import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.service.model.Event @@ -15,10 +17,14 @@ import com.greenart7c3.nostrsigner.ui.theme.NostrSignerTheme fun EventDataPreview() { val event = Event("123", "7579076d9aff0a4cfdefa7e2045f2486c7e5d8bc63bfc6b45397233e1bbfcb19", TimeUtils.now(), 1, listOf(), "This is a test 123 123 123 123 123 123", "") val data = event.toJson() + val remember = remember { mutableStateOf(false) } NostrSignerTheme(darkTheme = true) { Scaffold { EventData( + false, + remember, + null, "App", event, data,