diff --git a/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/4.json b/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/4.json index 664a6cc7..6f5dd754 100644 --- a/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/4.json +++ b/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/4.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 4, - "identityHash": "5074b7c061015dc0f49005f018955d21", + "identityHash": "ffb55f8c910a34676fa829322921d260", "entities": [ { "tableName": "application", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `name` TEXT NOT NULL, `relays` TEXT NOT NULL, `url` TEXT NOT NULL, `icon` TEXT NOT NULL, `description` TEXT NOT NULL, `pubKey` TEXT NOT NULL, `isConnected` INTEGER NOT NULL, `secret` TEXT NOT NULL, `useSecret` INTEGER NOT NULL, PRIMARY KEY(`key`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `name` TEXT NOT NULL, `relays` TEXT NOT NULL, `url` TEXT NOT NULL, `icon` TEXT NOT NULL, `description` TEXT NOT NULL, `pubKey` TEXT NOT NULL, `isConnected` INTEGER NOT NULL, `secret` TEXT NOT NULL, `useSecret` INTEGER NOT NULL, `signPolicy` INTEGER NOT NULL, PRIMARY KEY(`key`))", "fields": [ { "fieldPath": "key", @@ -67,6 +67,12 @@ "columnName": "useSecret", "affinity": "INTEGER", "notNull": true + }, + { + "fieldPath": "signPolicy", + "columnName": "signPolicy", + "affinity": "INTEGER", + "notNull": true } ], "primaryKey": { @@ -335,7 +341,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5074b7c061015dc0f49005f018955d21')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ffb55f8c910a34676fa829322921d260')" ] } } \ No newline at end of file diff --git a/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/5.json b/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/5.json new file mode 100644 index 00000000..f16641eb --- /dev/null +++ b/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/5.json @@ -0,0 +1,347 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "ffb55f8c910a34676fa829322921d260", + "entities": [ + { + "tableName": "application", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `name` TEXT NOT NULL, `relays` TEXT NOT NULL, `url` TEXT NOT NULL, `icon` TEXT NOT NULL, `description` TEXT NOT NULL, `pubKey` TEXT NOT NULL, `isConnected` INTEGER NOT NULL, `secret` TEXT NOT NULL, `useSecret` INTEGER NOT NULL, `signPolicy` INTEGER NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "relays", + "columnName": "relays", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pubKey", + "columnName": "pubKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isConnected", + "columnName": "isConnected", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "secret", + "columnName": "secret", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "useSecret", + "columnName": "useSecret", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "signPolicy", + "columnName": "signPolicy", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [ + { + "name": "index_key", + "unique": true, + "columnNames": [ + "key" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_key` ON `${TABLE_NAME}` (`key`)" + }, + { + "name": "index_name", + "unique": false, + "columnNames": [ + "name" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "applicationPermission", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `pkKey` TEXT NOT NULL, `type` TEXT NOT NULL, `kind` INTEGER, `acceptable` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`pkKey`) REFERENCES `application`(`key`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "pkKey", + "columnName": "pkKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "kind", + "columnName": "kind", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "acceptable", + "columnName": "acceptable", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "permissions_by_pk_key", + "unique": false, + "columnNames": [ + "pkKey" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `permissions_by_pk_key` ON `${TABLE_NAME}` (`pkKey`)" + }, + { + "name": "permissions_unique", + "unique": true, + "columnNames": [ + "pkKey", + "type", + "kind" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `permissions_unique` ON `${TABLE_NAME}` (`pkKey`, `type`, `kind`)" + } + ], + "foreignKeys": [ + { + "table": "application", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "pkKey" + ], + "referencedColumns": [ + "key" + ] + } + ] + }, + { + "tableName": "notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `eventId` TEXT NOT NULL, `time` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "eventId", + "columnName": "eventId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `pkKey` TEXT NOT NULL, `type` TEXT NOT NULL, `kind` INTEGER, `time` INTEGER NOT NULL, `accepted` INTEGER NOT NULL, FOREIGN KEY(`pkKey`) REFERENCES `application`(`key`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pkKey", + "columnName": "pkKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "kind", + "columnName": "kind", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accepted", + "columnName": "accepted", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "history_by_pk_key", + "unique": false, + "columnNames": [ + "pkKey" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `history_by_pk_key` ON `${TABLE_NAME}` (`pkKey`)" + }, + { + "name": "history_by_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `history_by_id` ON `${TABLE_NAME}` (`id`)" + } + ], + "foreignKeys": [ + { + "table": "application", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "pkKey" + ], + "referencedColumns": [ + "key" + ] + } + ] + }, + { + "tableName": "amber_log", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `type` TEXT NOT NULL, `message` TEXT NOT NULL, `time` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ffb55f8c910a34676fa829322921d260')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt index a6bca13a..86536847 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt @@ -333,6 +333,7 @@ object LocalPreferences { true, "", false, + 1, ), ) database.applicationDao().insertPermissions( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt index 9a4e2446..8bcc7d71 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt @@ -58,6 +58,7 @@ class SignerProvider : ContentProvider() { val account = LocalPreferences.loadFromEncryptedStorage(context!!, npub) ?: return null val currentSelection = selection ?: "0" val database = NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()) + val signPolicy = database.applicationDao().getSignPolicy(sortOrder ?: packageName) val permission = database .applicationDao() @@ -65,7 +66,7 @@ class SignerProvider : ContentProvider() { sortOrder ?: packageName, "SIGN_MESSAGE", ) - val isRemembered = permission?.acceptable ?: return null + val isRemembered = if (signPolicy == 2) true else permission?.acceptable ?: return null if (!isRemembered) { database.applicationDao().addHistory( HistoryEntity( @@ -136,7 +137,8 @@ class SignerProvider : ContentProvider() { ) } } - val isRemembered = permission?.acceptable ?: return null + val signPolicy = database.applicationDao().getSignPolicy(sortOrder ?: packageName) + val isRemembered = if (signPolicy == 2) true else permission?.acceptable ?: return null if (!isRemembered) { database.applicationDao().addHistory( HistoryEntity( @@ -240,8 +242,8 @@ class SignerProvider : ContentProvider() { ) } } - - val isRemembered = permission?.acceptable ?: return null + val signPolicy = database.applicationDao().getSignPolicy(sortOrder ?: packageName) + val isRemembered = if (signPolicy == 2) true else permission?.acceptable ?: return null if (!isRemembered) { database.applicationDao().addHistory( HistoryEntity( @@ -317,7 +319,8 @@ class SignerProvider : ContentProvider() { "GET_PUBLIC_KEY", ) - val isRemembered = permission?.acceptable ?: return null + val signPolicy = database.applicationDao().getSignPolicy(sortOrder ?: packageName) + val isRemembered = if (signPolicy == 2) true else permission?.acceptable ?: return null if (!isRemembered) { database.applicationDao().addHistory( HistoryEntity( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/database/AppDatabase.kt b/app/src/main/java/com/greenart7c3/nostrsigner/database/AppDatabase.kt index a6fb34c7..ef05148a 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/AppDatabase.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/AppDatabase.kt @@ -33,6 +33,13 @@ val MIGRATION_3_4 = } } +val MIGRATION_4_5 = + object : Migration(4, 5) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE `application` ADD COLUMN `signPolicy` INTEGER NOT NULL DEFAULT 1") + } + } + @Database( entities = [ ApplicationEntity::class, @@ -41,7 +48,7 @@ val MIGRATION_3_4 = HistoryEntity::class, LogEntity::class, ], - version = 4, + version = 5, ) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { @@ -62,6 +69,7 @@ abstract class AppDatabase : RoomDatabase() { .addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_2_3) .addMigrations(MIGRATION_3_4) + .addMigrations(MIGRATION_4_5) .build() instance } 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 e16a04b7..d6d07ef4 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt @@ -11,6 +11,9 @@ import kotlinx.coroutines.flow.Flow @Dao interface ApplicationDao { + @Query("SELECT signPolicy FROM application WHERE `key` = :key") + fun getSignPolicy(key: String): Int? + @Query("SELECT MAX(time) FROM notification") fun getLatestNotification(): Long? 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 d7134f5d..e89cef8b 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationEntity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationEntity.kt @@ -36,6 +36,7 @@ data class ApplicationEntity( var isConnected: Boolean, val secret: String, val useSecret: Boolean, + var signPolicy: Int, ) data class ApplicationWithPermissions( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/BasicPermissions.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/BasicPermissions.kt new file mode 100644 index 00000000..710b19de --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/BasicPermissions.kt @@ -0,0 +1,28 @@ +package com.greenart7c3.nostrsigner.models + +val basicPermissions = listOf( + Permission("get_public_key", null), + Permission("nip04_encrypt", null), + Permission("nip04_decrypt", null), + Permission("nip44_decrypt", null), + Permission("nip44_encrypt", null), + Permission("decrypt_zap_event", null), + Permission("sign_event", 0), + Permission("sign_event", 1), + Permission("sign_event", 3), + Permission("sign_event", 4), + Permission("sign_event", 5), + Permission("sign_event", 6), + Permission("sign_event", 7), + Permission("sign_event", 9734), + Permission("sign_event", 9735), + Permission("sign_event", 10000), + Permission("sign_event", 10002), + Permission("sign_event", 10003), + Permission("sign_event", 10013), + Permission("sign_event", 31234), + Permission("sign_event", 30078), + Permission("sign_event", 22242), + Permission("sign_event", 27235), + Permission("sign_event", 30023), +) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt index 7e60a8af..eda044b7 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt @@ -166,6 +166,7 @@ object AmberUtils { true, intentData.bunkerRequest?.secret ?: "", intentData.bunkerRequest?.secret != null, + account.signPolicy, ), permissions = mutableListOf(), ) 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 e223cb21..b99fe768 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -87,7 +87,7 @@ fun EditPermission( mutableStateListOf() } var applicationData by remember { - mutableStateOf(ApplicationEntity(selectedPackage, "", emptyList(), "", "", "", "", true, "", false)) + mutableStateOf(ApplicationEntity(selectedPackage, "", emptyList(), "", "", "", "", true, "", false, 1)) } var textFieldvalue by remember(applicationData.name) { mutableStateOf(TextFieldValue(applicationData.name)) 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 911f4b81..7a849b6d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -94,6 +94,7 @@ import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.models.ReturnType import com.greenart7c3.nostrsigner.models.SignerType import com.greenart7c3.nostrsigner.models.TimeUtils +import com.greenart7c3.nostrsigner.models.basicPermissions import com.greenart7c3.nostrsigner.service.EventNotificationConsumer import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.service.PushNotificationUtils @@ -130,6 +131,7 @@ fun sendResult( onLoading: (Boolean) -> Unit, permissions: List? = null, appName: String? = null, + signPolicy: Int? = null, ) { onLoading(true) GlobalScope.launch(Dispatchers.IO) { @@ -169,25 +171,54 @@ fun sendResult( true, intentData.bunkerRequest?.secret ?: "", intentData.bunkerRequest?.secret != null, + account.signPolicy, ), permissions = mutableListOf(), ) application.application.isConnected = true - permissions?.filter { it.checked }?.forEach { - if (application.permissions.any { permission -> permission.type == it.type.toUpperCase(Locale.current) && permission.kind == it.kind }) { - return@forEach + if (signPolicy != null) { + when (signPolicy) { + 0 -> { + application.application.signPolicy = 0 + basicPermissions.forEach { + if (application.permissions.any { permission -> permission.type == it.type.toUpperCase(Locale.current) && permission.kind == it.kind }) { + return@forEach + } + application.permissions.add( + ApplicationPermissionsEntity( + null, + key, + it.type.toUpperCase(Locale.current), + it.kind, + true, + ), + ) + } + } + 1 -> { + application.application.signPolicy = 1 + permissions?.filter { it.checked }?.forEach { + if (application.permissions.any { permission -> permission.type == it.type.toUpperCase(Locale.current) && permission.kind == it.kind }) { + return@forEach + } + application.permissions.add( + ApplicationPermissionsEntity( + null, + key, + it.type.toUpperCase(Locale.current), + it.kind, + true, + ), + ) + } + } + 2 -> { + application.application.signPolicy = 2 + } } - application.permissions.add( - ApplicationPermissionsEntity( - null, - key, - it.type.toUpperCase(Locale.current), - it.kind, - true, - ), - ) } + if (rememberChoice) { application.permissions.add( ApplicationPermissionsEntity( @@ -594,6 +625,7 @@ fun MainScreen( false, secret, false, + account.signPolicy, ) database.applicationDao().insertApplication( 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 30ad0d46..bfa67d56 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,5 +1,6 @@ package com.greenart7c3.nostrsigner.ui.components +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -9,25 +10,25 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Done +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.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton 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.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 @@ -36,22 +37,23 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp 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( + account: Account, appName: String, applicationName: String?, permissions: List?, - onAccept: (List?) -> Unit, + onAccept: (List?, Int) -> Unit, onReject: () -> Unit, ) { var localPermissions by remember { mutableStateOf(permissions) } - val context = LocalContext.current var showAdjustDialog by remember { mutableStateOf(false) @@ -75,7 +77,8 @@ fun LoginWithPubKey( Column( Modifier .fillMaxSize() - .padding(16.dp), + .padding(16.dp) + .verticalScroll(rememberScrollState()), ) { Text( buildAnnotatedString { @@ -87,43 +90,74 @@ fun LoginWithPubKey( fontSize = 18.sp, ) - Spacer(Modifier.size(4.dp)) + Spacer(Modifier.size(8.dp)) - localPermissions?.let { - LazyColumn( + val radioOptions = listOf( + TitleExplainer( + title = stringResource(R.string.sign_policy_basic), + explainer = stringResource(R.string.sign_policy_basic_explainer), + ), + TitleExplainer( + title = stringResource(R.string.sign_policy_manual_new_app), + explainer = stringResource(R.string.sign_policy_manual_new_app_explainer), + ), + TitleExplainer( + title = stringResource(R.string.sign_policy_fully), + explainer = stringResource(R.string.sign_policy_fully_explainer), + ), + ) + var selectedOption by remember { mutableIntStateOf(account.signPolicy) } + + Text( + text = stringResource(R.string.handle_application_permissions), + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + ) + + Spacer(modifier = Modifier.size(8.dp)) + + radioOptions.forEachIndexed { index, option -> + Row( Modifier .fillMaxWidth() - .padding(top = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - itemsIndexed(it.take(3)) { _, item -> - Row( - Modifier - .fillMaxWidth(), - horizontalArrangement = Arrangement.Start, - ) { - Icon( - if (item.checked) Icons.Default.Done else Icons.Default.Close, - item.toLocalizedString(context), - tint = if (item.checked) Color.Green else Color.Red, - ) - Text(text = item.toLocalizedString(context)) - } - } - } - if (it.size > 3) { - Row( - Modifier - .fillMaxWidth(), - horizontalArrangement = Arrangement.Start, - ) { - val isAnyChecked = it.take(3).any { permission -> permission.checked } - Icon( - if (isAnyChecked) Icons.Default.Done else Icons.Default.Close, - stringResource(R.string.and_more), - tint = if (isAnyChecked) Color.Green else Color.Red, + .selectable( + selected = selectedOption == index, + onClick = { + selectedOption = index + }, ) - Text(text = stringResource(R.string.more, it.size - 3)) + .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), + fontWeight = FontWeight.Bold, + ) + option.explainer?.let { + Text( + text = it, + modifier = Modifier.padding(start = 16.dp), + ) + } } } } @@ -137,16 +171,18 @@ fun LoginWithPubKey( Arrangement.Center, Alignment.CenterHorizontally, ) { - localPermissions?.let { - if (it.isNotEmpty()) { - TextButton( - onClick = { - showAdjustDialog = true - }, - modifier = Modifier - .padding(8.dp), - ) { - Text(text = stringResource(R.string.adjust)) + if (selectedOption == 1) { + localPermissions?.let { + if (it.isNotEmpty()) { + TextButton( + onClick = { + showAdjustDialog = true + }, + modifier = Modifier + .padding(8.dp), + ) { + Text(text = stringResource(R.string.adjust)) + } } } } @@ -157,7 +193,7 @@ fun LoginWithPubKey( .padding(8.dp), shape = ButtonBorder, onClick = { - onAccept(localPermissions) + onAccept(localPermissions, selectedOption) }, ) { Text(stringResource(R.string.grant_permissions)) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt index d1bdf507..b6e15540 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt @@ -196,6 +196,7 @@ fun MultiEventHomeScreen( true, intentData.bunkerRequest?.secret ?: "", intentData.bunkerRequest?.secret != null, + localAccount.signPolicy, ), permissions = mutableListOf(), ) @@ -269,6 +270,7 @@ fun MultiEventHomeScreen( true, intentData.bunkerRequest?.secret ?: "", intentData.bunkerRequest?.secret != null, + localAccount.signPolicy, ), permissions = mutableListOf(), ) @@ -335,6 +337,7 @@ fun MultiEventHomeScreen( true, intentData.bunkerRequest?.secret ?: "", intentData.bunkerRequest?.secret != null, + localAccount.signPolicy, ), permissions = mutableListOf(), ) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SingleEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SingleEventHomeScreen.kt index 0053fa17..98205093 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SingleEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SingleEventHomeScreen.kt @@ -93,10 +93,11 @@ fun SingleEventHomeScreen( } LoginWithPubKey( + account, appName, applicationName, intentData.permissions, - { permissions -> + { permissions, signPolicy -> val sig = if (intentData.type == SignerType.CONNECT) { "ack" @@ -121,6 +122,7 @@ fun SingleEventHomeScreen( appName = applicationName ?: appName, database = database, onLoading = onLoading, + signPolicy = signPolicy, ) } return@LoginWithPubKey @@ -219,6 +221,7 @@ fun SingleEventHomeScreen( true, intentData.bunkerRequest?.secret ?: "", intentData.bunkerRequest?.secret != null, + account.signPolicy, ), permissions = mutableListOf(), ) @@ -348,6 +351,7 @@ fun SingleEventHomeScreen( true, intentData.bunkerRequest?.secret ?: "", intentData.bunkerRequest?.secret != null, + account.signPolicy, ), permissions = mutableListOf(), ) @@ -499,6 +503,7 @@ fun SingleEventHomeScreen( true, intentData.bunkerRequest?.secret ?: "", intentData.bunkerRequest?.secret != null, + account.signPolicy, ), permissions = mutableListOf(), ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 26a37e92..a0ce3544 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -349,4 +349,9 @@ These words let you recreate your account in case you lose access. Write them down in a safe place Show seed words Sign policy + How to handle this application permissions? + Manually approve each permission + Recommended for privacy-minded people who would like control over each permission. Choosing this will prompt you set a preference every time you receive a new permission. + I fully trust this application + Sign automatically every request