Add a option to accept or reject a permission temporarily

This commit is contained in:
greenart7c3
2025-05-09 07:19:54 -03:00
parent c4ec5406c1
commit 5ad1bb869b
20 changed files with 819 additions and 129 deletions
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 8,
"identityHash": "cfdf9a16e23cb231083a71a03c572c39",
"identityHash": "08bd4cb1d1d0fd652f0234b33fcd367b",
"entities": [
{
"tableName": "application",
@@ -106,18 +106,16 @@
"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 )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `pkKey` TEXT NOT NULL, `type` TEXT NOT NULL, `kind` INTEGER, `acceptable` INTEGER NOT NULL, `rememberType` INTEGER NOT NULL, `acceptUntil` INTEGER NOT NULL, `rejectUntil` 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
"affinity": "INTEGER"
},
{
"fieldPath": "pkKey",
@@ -134,14 +132,31 @@
{
"fieldPath": "kind",
"columnName": "kind",
"affinity": "INTEGER",
"notNull": false
"affinity": "INTEGER"
},
{
"fieldPath": "acceptable",
"columnName": "acceptable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rememberType",
"columnName": "rememberType",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "acceptUntil",
"columnName": "acceptUntil",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rejectUntil",
"columnName": "rejectUntil",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
@@ -234,8 +249,7 @@
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_notification_time` ON `${TABLE_NAME}` (`time`)"
}
],
"foreignKeys": []
]
},
{
"tableName": "history",
@@ -262,8 +276,7 @@
{
"fieldPath": "kind",
"columnName": "kind",
"affinity": "INTEGER",
"notNull": false
"affinity": "INTEGER"
},
{
"fieldPath": "time",
@@ -358,9 +371,7 @@
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
},
{
"tableName": "history2",
@@ -387,8 +398,7 @@
{
"fieldPath": "kind",
"columnName": "kind",
"affinity": "INTEGER",
"notNull": false
"affinity": "INTEGER"
},
{
"fieldPath": "time",
@@ -428,14 +438,12 @@
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `history_by_id2` ON `${TABLE_NAME}` (`id`)"
}
],
"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, 'cfdf9a16e23cb231083a71a03c572c39')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '08bd4cb1d1d0fd652f0234b33fcd367b')"
]
}
}
@@ -0,0 +1,449 @@
{
"formatVersion": 1,
"database": {
"version": 9,
"identityHash": "08bd4cb1d1d0fd652f0234b33fcd367b",
"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, `closeApplication` 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
},
{
"fieldPath": "closeApplication",
"columnName": "closeApplication",
"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`)"
}
]
},
{
"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, `rememberType` INTEGER NOT NULL, `acceptUntil` INTEGER NOT NULL, `rejectUntil` 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"
},
{
"fieldPath": "pkKey",
"columnName": "pkKey",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "kind",
"columnName": "kind",
"affinity": "INTEGER"
},
{
"fieldPath": "acceptable",
"columnName": "acceptable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rememberType",
"columnName": "rememberType",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "acceptUntil",
"columnName": "acceptUntil",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rejectUntil",
"columnName": "rejectUntil",
"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": [
{
"name": "index_notification_eventId",
"unique": false,
"columnNames": [
"eventId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_notification_eventId` ON `${TABLE_NAME}` (`eventId`)"
},
{
"name": "index_notification_time",
"unique": false,
"columnNames": [
"time"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_notification_time` ON `${TABLE_NAME}` (`time`)"
}
]
},
{
"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"
},
{
"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"
]
}
},
{
"tableName": "history2",
"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)",
"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"
},
{
"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_key2",
"unique": false,
"columnNames": [
"pkKey"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `history_by_pk_key2` ON `${TABLE_NAME}` (`pkKey`)"
},
{
"name": "history_by_id2",
"unique": false,
"columnNames": [
"id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `history_by_id2` ON `${TABLE_NAME}` (`id`)"
}
]
}
],
"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, '08bd4cb1d1d0fd652f0234b33fcd367b')"
]
}
}
@@ -93,7 +93,20 @@ class SignerProvider : ContentProvider() {
sortOrder ?: packageName,
"SIGN_MESSAGE",
)
val isRemembered = if (signPolicy == 2) true else permission?.acceptable ?: return null
val rejectUntil = permission?.rejectUntil ?: 0
val acceptUntil = permission?.acceptUntil ?: 0
val isRemembered = if (signPolicy == 2) {
true
} else {
if (rejectUntil == 0L && acceptUntil == 0L) return null
if (rejectUntil > TimeUtils.now() && rejectUntil > 0) {
false
} else if (acceptUntil > TimeUtils.now() && acceptUntil > 0) {
true
} else {
return null
}
}
if (!isRemembered) {
scope.launch {
database.applicationDao().addHistory(
@@ -193,7 +206,20 @@ class SignerProvider : ContentProvider() {
}
}
val signPolicy = database.applicationDao().getSignPolicy(sortOrder ?: packageName)
val isRemembered = if (signPolicy == 2) true else permission?.acceptable ?: return null
val rejectUntil = permission?.rejectUntil ?: 0
val acceptUntil = permission?.acceptUntil ?: 0
val isRemembered = if (signPolicy == 2) {
true
} else {
if (rejectUntil == 0L && acceptUntil == 0L) return null
if (rejectUntil > TimeUtils.now() && rejectUntil > 0) {
false
} else if (acceptUntil > TimeUtils.now() && acceptUntil > 0) {
true
} else {
return null
}
}
if (!isRemembered) {
scope.launch {
database.applicationDao().addHistory(
@@ -308,7 +334,20 @@ class SignerProvider : ContentProvider() {
}
}
val signPolicy = database.applicationDao().getSignPolicy(sortOrder ?: packageName)
val isRemembered = if (signPolicy == 2) true else permission?.acceptable ?: return null
val rejectUntil = permission?.rejectUntil ?: 0
val acceptUntil = permission?.acceptUntil ?: 0
val isRemembered = if (signPolicy == 2) {
true
} else {
if (rejectUntil == 0L && acceptUntil == 0L) return null
if (rejectUntil > TimeUtils.now() && rejectUntil > 0) {
false
} else if (acceptUntil > TimeUtils.now() && acceptUntil > 0) {
true
} else {
return null
}
}
if (!isRemembered) {
scope.launch {
database.applicationDao().addHistory(
@@ -395,7 +434,20 @@ class SignerProvider : ContentProvider() {
)
val signPolicy = database.applicationDao().getSignPolicy(sortOrder ?: packageName)
val isRemembered = if (signPolicy == 2) true else permission?.acceptable ?: return null
val rejectUntil = permission?.rejectUntil ?: 0
val acceptUntil = permission?.acceptUntil ?: 0
val isRemembered = if (signPolicy == 2) {
true
} else {
if (rejectUntil == 0L && acceptUntil == 0L) return null
if (rejectUntil > TimeUtils.now() && rejectUntil > 0) {
false
} else if (acceptUntil > TimeUtils.now() && acceptUntil > 0) {
true
} else {
return null
}
}
if (!isRemembered) {
scope.launch {
database.applicationDao().addHistory(
@@ -67,6 +67,26 @@ val MIGRATION_7_8 =
}
}
val MIGRATION_8_9 =
object : Migration(8, 9) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE `applicationPermission` ADD COLUMN `rememberType` INTEGER NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE `applicationPermission` ADD COLUMN `acceptUntil` INTEGER NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE `applicationPermission` ADD COLUMN `rejectUntil` INTEGER NOT NULL DEFAULT 0")
val cursor = db.query("SELECT * FROM applicationPermission")
while (cursor.moveToNext()) {
val id = cursor.getInt(cursor.getColumnIndexOrThrow("id"))
val acceptable = cursor.getInt(cursor.getColumnIndexOrThrow("acceptable"))
val until = Long.MAX_VALUE / 1000
if (acceptable == 1) {
db.execSQL("UPDATE applicationPermission SET acceptUntil = $until, rejectUntil = 0, rememberType = 4 WHERE id = $id")
} else {
db.execSQL("UPDATE applicationPermission SET rejectUntil = $until, acceptUntil = 0, rememberType = 4 WHERE id = $id")
}
}
}
}
@Database(
entities = [
ApplicationEntity::class,
@@ -76,7 +96,7 @@ val MIGRATION_7_8 =
LogEntity::class,
HistoryEntity2::class,
],
version = 8,
version = 9,
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
@@ -106,8 +126,10 @@ abstract class AppDatabase : RoomDatabase() {
.addMigrations(MIGRATION_5_6)
.addMigrations(MIGRATION_6_7)
.addMigrations(MIGRATION_7_8)
.addMigrations(MIGRATION_8_9)
.build()
instance.openHelper.writableDatabase.execSQL("VACUUM")
instance
}
}
@@ -64,7 +64,7 @@ interface ApplicationDao {
@Transaction
suspend fun getBySecret(secret: String): ApplicationWithPermissions?
@Query("SELECT * FROM applicationPermission WHERE pkKey = :key")
@Query("SELECT * FROM applicationPermission WHERE pkKey = :key and rememberType = 4")
suspend fun getAllByKey(key: String): List<ApplicationPermissionsEntity>
@Query("SELECT * FROM application")
@@ -34,4 +34,7 @@ data class ApplicationPermissionsEntity(
val type: String,
val kind: Int?,
var acceptable: Boolean,
val rememberType: Int,
var acceptUntil: Long,
var rejectUntil: Long,
)
@@ -9,8 +9,8 @@ import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.BunkerRequest
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.kindToNip
import com.greenart7c3.nostrsigner.ui.IntentResultType
import com.greenart7c3.nostrsigner.ui.RememberType
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@@ -23,6 +23,7 @@ import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip57Zaps.PrivateZapEncryption
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.TimeUtils
object AmberUtils {
fun encryptOrDecryptData(
@@ -161,27 +162,39 @@ object AmberUtils {
intentData: IntentData,
kind: Int?,
value: Boolean,
rememberType: RememberType,
account: Account,
) {
val noPermission = application.permissions.none {
val nip = it.kind?.kindToNip()?.toIntOrNull()
(it.type == intentData.type.toString() && it.kind == kind) || (nip != null && it.type == "NIP" && it.kind == nip)
val until = when (rememberType) {
RememberType.ALWAYS -> Long.MAX_VALUE / 1000
RememberType.ONE_MINUTE -> TimeUtils.oneMinuteFromNow()
RememberType.FIVE_MINUTES -> TimeUtils.now() + TimeUtils.FIVE_MINUTES
RememberType.TEN_MINUTES -> TimeUtils.now() + TimeUtils.FIFTEEN_MINUTES
RememberType.NEVER -> 0L
}
if (noPermission) {
application.permissions.add(
ApplicationPermissionsEntity(
null,
key,
intentData.type.toString(),
kind,
value,
),
)
Amber.instance.getDatabase(account.npub)
.applicationDao()
.insertApplicationWithPermissions(application)
if (kind != null) {
application.permissions.removeIf { it.kind == kind && it.type == intentData.type.toString() }
} else {
application.permissions.removeIf { it.type == intentData.type.toString() && it.type != "SIGN_EVENT" }
}
application.permissions.add(
ApplicationPermissionsEntity(
null,
key,
intentData.type.toString(),
kind,
value,
rememberType.screenCode,
if (value) until else 0L,
if (!value) until else 0L,
),
)
Amber.instance.getDatabase(account.npub)
.applicationDao()
.insertApplicationWithPermissions(application)
}
}
@@ -212,7 +212,10 @@ fun EditPermission(
val localPermissions =
permissions.map {
if (it.id == permission.id) {
it.copy(acceptable = !permission.acceptable)
it.copy(
acceptable = !permission.acceptable,
rememberType = RememberType.ALWAYS.screenCode,
)
} else {
it.copy()
}
@@ -235,7 +238,10 @@ fun EditPermission(
val localPermissions =
permissions.map {
if (it.id == permission.id) {
it.copy(acceptable = !permission.acceptable)
it.copy(
acceptable = !permission.acceptable,
rememberType = RememberType.ALWAYS.screenCode,
)
} else {
it.copy()
}
@@ -142,6 +142,7 @@ fun sendResult(
signPolicy: Int? = null,
onRemoveIntentData: (List<IntentData>, IntentResultType) -> Unit,
shouldCloseApplication: Boolean? = null,
rememberType: RememberType,
) {
onLoading(true)
Amber.instance.applicationIOScope.launch {
@@ -196,7 +197,7 @@ fun sendResult(
intentData.bunkerRequest?.secret ?: "",
intentData.bunkerRequest?.secret != null,
account.signPolicy,
shouldCloseApplication ?: (intentData.bunkerRequest?.closeApplication != false),
shouldCloseApplication ?: (intentData.bunkerRequest?.closeApplication == false),
),
permissions = mutableListOf(),
)
@@ -217,6 +218,9 @@ fun sendResult(
it.type.toUpperCase(Locale.current),
it.kind,
true,
RememberType.ALWAYS.screenCode,
Long.MAX_VALUE / 1000,
0,
),
)
}
@@ -234,6 +238,9 @@ fun sendResult(
it.type.toUpperCase(Locale.current),
it.kind,
true,
RememberType.ALWAYS.screenCode,
Long.MAX_VALUE / 1000,
0,
),
)
}
@@ -244,7 +251,21 @@ fun sendResult(
}
}
if (rememberChoice) {
if (rememberType != RememberType.NEVER) {
val until = when (rememberType) {
RememberType.ALWAYS -> Long.MAX_VALUE / 1000
RememberType.ONE_MINUTE -> TimeUtils.oneMinuteFromNow()
RememberType.FIVE_MINUTES -> TimeUtils.now() + TimeUtils.FIVE_MINUTES
RememberType.TEN_MINUTES -> TimeUtils.now() + TimeUtils.FIFTEEN_MINUTES
RememberType.NEVER -> 0L
}
if (kind != null) {
application.permissions.removeIf { it.kind == kind && it.type == intentData.type.toString() }
} else {
application.permissions.removeIf { it.type == intentData.type.toString() && it.type != "SIGN_EVENT" }
}
application.permissions.add(
ApplicationPermissionsEntity(
null,
@@ -252,6 +273,9 @@ fun sendResult(
intentData.type.toString(),
kind,
true,
rememberType.screenCode,
until,
0,
),
)
}
@@ -272,6 +296,9 @@ fun sendResult(
SignerType.GET_PUBLIC_KEY.toString(),
null,
true,
RememberType.ALWAYS.screenCode,
Long.MAX_VALUE / 1000,
0,
),
)
}
@@ -309,7 +336,7 @@ fun sendResult(
onDone = {
Amber.instance.applicationIOScope.launch {
if (!it) {
if (rememberChoice) {
if (rememberType != RememberType.NEVER) {
if (intentData.type == SignerType.SIGN_EVENT) {
kind?.let {
database.applicationDao().deletePermissions(key, intentData.type.toString(), kind)
@@ -423,9 +423,11 @@ fun SettingsScreen(
@Composable
fun SettingsRow(
name: Int,
description: Int,
description: Int?,
selectedItems: ImmutableList<TitleExplainer>,
selectedIndex: Int,
maxLines: Int = 1,
overflow: TextOverflow = TextOverflow.Ellipsis,
onSelect: (Int) -> Unit,
) {
Row(
@@ -438,16 +440,18 @@ fun SettingsRow(
) {
Text(
text = stringResource(name),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = stringResource(description),
style = MaterialTheme.typography.bodySmall,
color = Color.Gray,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
maxLines = maxLines,
overflow = overflow,
)
description?.let {
Text(
text = stringResource(description),
style = MaterialTheme.typography.bodySmall,
color = Color.Gray,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
}
TextSpinner(
@@ -462,6 +466,14 @@ fun SettingsRow(
}
}
enum class RememberType(val screenCode: Int, val resourceId: Int) {
NEVER(0, R.string.never),
ONE_MINUTE(1, R.string.one_minute),
FIVE_MINUTES(2, R.string.five_minutes),
TEN_MINUTES(3, R.string.ten_minutes),
ALWAYS(4, R.string.always),
}
enum class BiometricsTimeType(val screenCode: Int, val resourceId: Int) {
EVERY_TIME(0, R.string.every_time),
ONE_MINUTE(1, R.string.one_minute),
@@ -122,7 +122,7 @@ fun AccountBackupScreen(
SeedWordsPage(
seedWords = account.seedWords,
showNextButton = false,
) {}
)
}
}
}
@@ -18,6 +18,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
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.Modifier
@@ -36,6 +37,7 @@ import androidx.compose.ui.unit.sp
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.ui.RememberType
import com.greenart7c3.nostrsigner.ui.verticalScrollbar
import kotlinx.coroutines.launch
@@ -51,8 +53,8 @@ fun EncryptDecryptData(
applicationName: String?,
appName: String,
type: SignerType,
onAccept: () -> Unit,
onReject: () -> Unit,
onAccept: (RememberType) -> Unit,
onReject: (RememberType) -> Unit,
) {
var showMore by androidx.compose.runtime.remember {
mutableStateOf(false)
@@ -61,6 +63,9 @@ fun EncryptDecryptData(
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val scrollState = rememberScrollState()
var rememberType by remember {
mutableStateOf(RememberType.NEVER)
}
Column(
Modifier
@@ -165,12 +170,17 @@ fun EncryptDecryptData(
onAccept,
onReject,
) {
remember.value = !remember.value
remember.value = it != RememberType.NEVER
rememberType = it
}
AcceptRejectButtons(
onAccept,
onReject,
onAccept = {
onAccept(rememberType)
},
onReject = {
onReject(rememberType)
},
)
}
}
@@ -18,6 +18,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
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.LocalContext
@@ -35,6 +36,7 @@ import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.service.model.AmberEvent
import com.greenart7c3.nostrsigner.ui.RememberType
import com.greenart7c3.nostrsigner.ui.verticalScrollbar
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
@@ -51,14 +53,17 @@ fun EventData(
event: Event,
rawJson: String,
type: SignerType,
onAccept: () -> Unit,
onReject: () -> Unit,
onAccept: (RememberType) -> Unit,
onReject: (RememberType) -> Unit,
) {
var showMore by androidx.compose.runtime.remember {
mutableStateOf(false)
}
val context = LocalContext.current
val scrollState = rememberScrollState()
var rememberType by remember {
mutableStateOf(RememberType.NEVER)
}
Column(
Modifier
@@ -157,12 +162,17 @@ fun EventData(
onAccept,
onReject,
) {
remember.value = !remember.value
remember.value = it != RememberType.NEVER
rememberType = it
}
AcceptRejectButtons(
onAccept,
onReject,
onAccept = {
onAccept(rememberType)
},
onReject = {
onReject(rememberType)
},
)
}
}
@@ -59,6 +59,7 @@ import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.service.toShortenHex
import com.greenart7c3.nostrsigner.ui.RememberType
import com.greenart7c3.nostrsigner.ui.fromHex
import com.greenart7c3.nostrsigner.ui.navigation.Route
import com.greenart7c3.nostrsigner.ui.verticalScrollbar
@@ -132,8 +133,8 @@ fun LoginWithPubKey(
appName: String,
applicationName: String?,
permissions: List<Permission>?,
onAccept: (List<Permission>?, Int, Boolean?) -> Unit,
onReject: () -> Unit,
onAccept: (List<Permission>?, Int, Boolean?, RememberType) -> Unit,
onReject: (RememberType) -> Unit,
) {
val localPermissions = remember {
val snapshot = mutableStateListOf<Permission>()
@@ -144,6 +145,7 @@ fun LoginWithPubKey(
}
val scrollState = rememberScrollState()
var rememberType by remember { mutableStateOf(RememberType.NEVER) }
if (isBunkerRequest) {
Column(
@@ -195,7 +197,8 @@ fun LoginWithPubKey(
onReject = onReject,
remember = remember.value,
onChanged = {
remember.value = !remember.value
remember.value = it != RememberType.NEVER
rememberType = it
},
packageName = packageName,
)
@@ -203,14 +206,16 @@ fun LoginWithPubKey(
AmberButton(
modifier = Modifier.padding(vertical = 20.dp),
onClick = {
onAccept(localPermissions, 1, null)
onAccept(localPermissions, 1, null, rememberType)
},
text = stringResource(R.string.grant_permissions),
)
AmberButton(
modifier = Modifier.padding(vertical = 20.dp),
onClick = onReject,
onClick = {
onReject(rememberType)
},
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFFF6B00),
),
@@ -405,14 +410,16 @@ fun LoginWithPubKey(
AmberButton(
modifier = Modifier.padding(vertical = 20.dp),
onClick = {
onAccept(localPermissions, selectedOption, closeApp)
onAccept(localPermissions, selectedOption, closeApp, rememberType)
},
text = stringResource(R.string.grant_permissions),
)
AmberButton(
modifier = Modifier.padding(vertical = 20.dp),
onClick = onReject,
onClick = {
onReject(rememberType)
},
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFFF6B00),
),
@@ -65,6 +65,7 @@ import com.greenart7c3.nostrsigner.service.MultiEventScreenIntents
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
import com.greenart7c3.nostrsigner.service.toShortenHex
import com.greenart7c3.nostrsigner.ui.IntentResultType
import com.greenart7c3.nostrsigner.ui.RememberType
import com.greenart7c3.nostrsigner.ui.Result
import com.greenart7c3.nostrsigner.ui.navigation.Route
import com.greenart7c3.nostrsigner.ui.theme.orange
@@ -230,6 +231,7 @@ fun MultiEventHomeScreen(
intentData,
localEvent.kind,
intentData.rememberMyChoice.value,
RememberType.ALWAYS,
thisAccount,
)
}
@@ -313,6 +315,7 @@ fun MultiEventHomeScreen(
intentData,
null,
intentData.rememberMyChoice.value,
RememberType.ALWAYS,
thisAccount,
)
}
@@ -438,6 +441,7 @@ fun MultiEventHomeScreen(
intentData,
null,
intentData.rememberMyChoice.value,
RememberType.ALWAYS,
thisAccount,
)
}
@@ -570,6 +574,7 @@ fun MultiEventHomeScreen(
intentData,
null,
false,
RememberType.ALWAYS,
thisAccount,
)
}
@@ -1,18 +1,17 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.text.style.TextOverflow
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.ui.RememberType
import com.greenart7c3.nostrsigner.ui.SettingsRow
import kotlinx.collections.immutable.persistentListOf
@Composable
fun RememberMyChoice(
@@ -20,39 +19,41 @@ fun RememberMyChoice(
remember: Boolean,
packageName: String?,
alwaysShow: Boolean = false,
onAccept: () -> Unit,
onReject: () -> Unit,
onChanged: () -> Unit,
onAccept: (RememberType) -> Unit,
onReject: (RememberType) -> Unit,
onChanged: (RememberType) -> Unit,
) {
val items =
persistentListOf(
TitleExplainer(stringResource(RememberType.NEVER.resourceId)),
TitleExplainer(stringResource(RememberType.ONE_MINUTE.resourceId)),
TitleExplainer(stringResource(RememberType.FIVE_MINUTES.resourceId)),
TitleExplainer(stringResource(RememberType.TEN_MINUTES.resourceId)),
TitleExplainer(stringResource(RememberType.ALWAYS.resourceId)),
)
var index by remember {
mutableIntStateOf(0)
}
if (shouldRunAcceptOrReject != null) {
LaunchedEffect(Unit) {
if (shouldRunAcceptOrReject) {
onAccept()
onAccept(RememberType.entries[index])
} else {
onReject()
onReject(RememberType.entries[index])
}
}
}
if (packageName != null || alwaysShow) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(vertical = 8.dp)
.clickable {
onChanged()
},
SettingsRow(
R.string.automatically_sign_this_for,
null,
items,
index,
maxLines = Int.MAX_VALUE,
overflow = TextOverflow.Clip,
) {
Text(
modifier = Modifier.weight(1f),
text = stringResource(R.string.remember_my_choice_and_don_t_ask_again),
)
Switch(
checked = remember,
onCheckedChange = {
onChanged()
},
)
index = it
onChanged(RememberType.entries[it])
}
}
}
@@ -33,7 +33,7 @@ import kotlinx.coroutines.launch
fun SeedWordsPage(
seedWords: Set<String>,
showNextButton: Boolean = true,
onNextPage: () -> Unit,
onNextPage: () -> Unit = {},
) {
val clipboardManager = LocalClipboard.current
val scope = rememberCoroutineScope()
@@ -18,6 +18,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
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.Modifier
@@ -36,6 +37,7 @@ import androidx.compose.ui.unit.sp
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.ui.RememberType
import com.greenart7c3.nostrsigner.ui.verticalScrollbar
import kotlinx.coroutines.launch
@@ -50,8 +52,8 @@ fun SignMessage(
applicationName: String?,
appName: String,
type: SignerType,
onAccept: () -> Unit,
onReject: () -> Unit,
onAccept: (RememberType) -> Unit,
onReject: (RememberType) -> Unit,
) {
var showMore by androidx.compose.runtime.remember {
mutableStateOf(false)
@@ -60,6 +62,9 @@ fun SignMessage(
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val scrollState = rememberScrollState()
var rememberType by remember {
mutableStateOf(RememberType.NEVER)
}
Column(
Modifier
@@ -158,12 +163,17 @@ fun SignMessage(
onAccept,
onReject,
) {
remember.value = !remember.value
remember.value = it != RememberType.NEVER
rememberType = it
}
AcceptRejectButtons(
onAccept,
onReject,
onAccept = {
onAccept(rememberType)
},
onReject = {
onReject(rememberType)
},
)
}
}
@@ -1,5 +1,6 @@
package com.greenart7c3.nostrsigner.ui.components
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -36,6 +37,7 @@ import com.greenart7c3.nostrsigner.service.AmberUtils
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
import com.greenart7c3.nostrsigner.service.toShortenHex
import com.greenart7c3.nostrsigner.ui.IntentResultType
import com.greenart7c3.nostrsigner.ui.RememberType
import com.greenart7c3.nostrsigner.ui.sendResult
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
@@ -96,7 +98,7 @@ fun SingleEventHomeScreen(
}
LoginWithPubKey(
applicationEntity?.application?.closeApplication ?: (intentData.bunkerRequest?.closeApplication == true),
applicationEntity?.application?.closeApplication ?: (intentData.bunkerRequest?.closeApplication != false),
paddingValues,
remember,
intentData.bunkerRequest != null && intentData.type == SignerType.GET_PUBLIC_KEY,
@@ -105,7 +107,7 @@ fun SingleEventHomeScreen(
appName,
applicationName,
intentData.permissions,
{ permissions, signPolicy, closeApplication ->
{ permissions, signPolicy, closeApplication, rememberType ->
val sig =
if (intentData.type == SignerType.CONNECT) {
intentData.bunkerRequest!!.nostrConnectSecret.ifBlank { "ack" }
@@ -134,6 +136,7 @@ fun SingleEventHomeScreen(
signPolicy = signPolicy,
onRemoveIntentData = onRemoveIntentData,
shouldCloseApplication = closeApplication,
rememberType = rememberType,
)
return@LoginWithPubKey
@@ -161,9 +164,9 @@ fun SingleEventHomeScreen(
)
}
} else {
onRemoveIntentData(listOf(intentData), IntentResultType.REMOVE)
context.getAppCompatActivity()?.intent = null
context.getAppCompatActivity()?.finish()
onRemoveIntentData(listOf(intentData), IntentResultType.REMOVE)
onLoading(false)
}
},
@@ -186,11 +189,24 @@ fun SingleEventHomeScreen(
} else {
packageName
}
val acceptUntil = permission?.acceptUntil ?: 0
val rejectUntil = permission?.rejectUntil ?: 0
val acceptOrReject = if (rejectUntil == 0L && acceptUntil == 0L) {
null
} else if (rejectUntil > TimeUtils.now() && rejectUntil > 0) {
false
} else if (acceptUntil > TimeUtils.now() && acceptUntil > 0) {
true
} else {
null
}
SignMessage(
account,
paddingValues,
intentData.data,
permission?.acceptable,
acceptOrReject,
remember,
localPackageName,
applicationName,
@@ -213,6 +229,7 @@ fun SingleEventHomeScreen(
null,
onLoading,
onRemoveIntentData = onRemoveIntentData,
rememberType = it,
)
}
},
@@ -246,13 +263,14 @@ fun SingleEventHomeScreen(
permissions = mutableListOf(),
)
if (remember.value) {
if (it != RememberType.NEVER) {
AmberUtils.acceptOrRejectPermission(
application,
key,
intentData,
null,
false,
it,
account,
)
}
@@ -288,11 +306,11 @@ fun SingleEventHomeScreen(
onRemoveIntentData = onRemoveIntentData,
)
} else {
onRemoveIntentData(listOf(intentData), IntentResultType.REMOVE)
context.getAppCompatActivity()?.intent = null
if (application.application.closeApplication) {
context.getAppCompatActivity()?.finish()
}
onRemoveIntentData(listOf(intentData), IntentResultType.REMOVE)
onLoading(false)
}
}
@@ -328,12 +346,26 @@ fun SingleEventHomeScreen(
} else {
packageName
}
val acceptUntil = permission?.acceptUntil ?: 0
val rejectUntil = permission?.rejectUntil ?: 0
val acceptOrReject = if (rejectUntil == 0L && acceptUntil == 0L) {
null
} else if (rejectUntil > TimeUtils.now() && rejectUntil > 0) {
false
} else if (acceptUntil > TimeUtils.now() && acceptUntil > 0) {
true
} else {
null
}
EncryptDecryptData(
account,
paddingValues,
intentData.data,
intentData.encryptedData ?: "",
permission?.acceptable,
acceptOrReject,
remember,
localPackageName,
applicationName,
@@ -360,6 +392,7 @@ fun SingleEventHomeScreen(
null,
onRemoveIntentData = onRemoveIntentData,
onLoading = onLoading,
rememberType = it,
)
},
{
@@ -389,13 +422,14 @@ fun SingleEventHomeScreen(
),
permissions = mutableListOf(),
)
if (remember.value) {
if (it != RememberType.NEVER) {
AmberUtils.acceptOrRejectPermission(
application,
key,
intentData,
null,
false,
it,
account,
)
}
@@ -431,11 +465,11 @@ fun SingleEventHomeScreen(
onLoading,
)
} else {
onRemoveIntentData(listOf(intentData), IntentResultType.REMOVE)
context.getAppCompatActivity()?.intent = null
if (application.application.closeApplication) {
context.getAppCompatActivity()?.finish()
}
onRemoveIntentData(listOf(intentData), IntentResultType.REMOVE)
onLoading(false)
}
}
@@ -480,10 +514,24 @@ fun SingleEventHomeScreen(
} else {
packageName
}
val acceptUntil = permission?.acceptUntil ?: 0
val rejectUntil = permission?.rejectUntil ?: 0
val acceptOrReject = if (rejectUntil == 0L && acceptUntil == 0L) {
null
} else if (rejectUntil > TimeUtils.now() && rejectUntil > 0) {
false
} else if (acceptUntil > TimeUtils.now() && acceptUntil > 0) {
true
} else {
null
}
EventData(
account,
paddingValues,
permission?.acceptable,
acceptOrReject,
remember,
localPackageName,
appName,
@@ -530,6 +578,7 @@ fun SingleEventHomeScreen(
signPolicy = null,
appName = null,
permissions = null,
rememberType = it,
)
},
{
@@ -568,13 +617,14 @@ fun SingleEventHomeScreen(
)
}
if (remember.value) {
if (it != RememberType.NEVER) {
AmberUtils.acceptOrRejectPermission(
application,
key,
intentData,
event.kind,
false,
it,
account,
)
}
@@ -604,11 +654,14 @@ fun SingleEventHomeScreen(
onRemoveIntentData = onRemoveIntentData,
)
} else {
onRemoveIntentData(listOf(intentData), IntentResultType.REMOVE)
context.getAppCompatActivity()?.intent = null
val activity = context.getAppCompatActivity()
Log.d("activity", "is activity null ${activity == null}")
activity?.intent = null
Log.d("activity", "Shold close app ${application.application.closeApplication}")
if (application.application.closeApplication) {
context.getAppCompatActivity()?.finish()
activity?.finish()
}
onRemoveIntentData(listOf(intentData), IntentResultType.REMOVE)
onLoading(false)
}
}
+2
View File
@@ -522,4 +522,6 @@
<string name="event_kind_39089">Starter packs</string>
<string name="event_kind_39092">Media starter packs</string>
<string name="event_kind_10020">Media follows</string>
<string name="automatically_sign_this_for">Automatically sign this for</string>
<string name="always">Always</string>
</resources>