diff --git a/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/2.json b/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/2.json index f84bc970..776de82f 100644 --- a/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/2.json +++ b/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/2.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 2, - "identityHash": "f43524ba310997fbd2fb8e145d477ef9", + "identityHash": "28d1982467c78cf1d57f06d94c384a2c", "entities": [ { "tableName": "application", @@ -205,12 +205,93 @@ }, "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, `content` TEXT 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": "content", + "columnName": "content", + "affinity": "TEXT", + "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" + ] + } + ] } ], "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, 'f43524ba310997fbd2fb8e145d477ef9')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '28d1982467c78cf1d57f06d94c384a2c')" ] } } \ No newline at end of file diff --git a/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/3.json b/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/3.json new file mode 100644 index 00000000..7869ecde --- /dev/null +++ b/app/schemas/com.greenart7c3.nostrsigner.database.AppDatabase/3.json @@ -0,0 +1,297 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "a4ed61fae12a0824cc46ce0b4e67d1ff", + "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`))", + "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 + } + ], + "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" + ] + } + ] + } + ], + "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, 'a4ed61fae12a0824cc46ce0b4e67d1ff')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt index c8c083d3..cc42ee7f 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt @@ -5,7 +5,9 @@ import android.content.ContentValues import android.database.Cursor import android.database.MatrixCursor import android.net.Uri +import com.greenart7c3.nostrsigner.database.HistoryEntity import com.greenart7c3.nostrsigner.models.SignerType +import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.service.AmberUtils import com.vitorpamplona.quartz.encoders.toNpub import com.vitorpamplona.quartz.events.Event @@ -51,8 +53,9 @@ class SignerProvider : ContentProvider() { val account = LocalPreferences.loadFromEncryptedStorage(projection[2]) ?: return null val event = Event.fromJson(json) val currentSelection = selection ?: "0" + val database = NostrSigner.instance.getDatabase(account.keyPair.pubKey.toNpub()) val permission = - NostrSigner.instance.getDatabase(account.keyPair.pubKey.toNpub()) + database .applicationDao() .getPermission( sortOrder ?: packageName, @@ -66,16 +69,49 @@ class SignerProvider : ContentProvider() { MatrixCursor(arrayOf("rejected")).also { it.addRow(arrayOf("true")) } + database.applicationDao().addHistory( + HistoryEntity( + 0, + sortOrder ?: packageName, + uri.toString().replace("content://$appId.", ""), + event.kind, + TimeUtils.now(), + false, + ), + ) return cursor } } else { val isRemembered = permission?.acceptable ?: false - if (!isRemembered) return null + if (!isRemembered) { + database.applicationDao().addHistory( + HistoryEntity( + 0, + sortOrder ?: packageName, + uri.toString().replace("content://$appId.", ""), + event.kind, + TimeUtils.now(), + false, + ), + ) + return null + } } var cursor: MatrixCursor? = null account.signer.sign(event.createdAt, event.kind, event.tags, event.content) { signedEvent -> + database.applicationDao().addHistory( + HistoryEntity( + 0, + sortOrder ?: packageName, + "SIGN_EVENT", + event.kind, + TimeUtils.now(), + true, + ), + ) + val localCursor = MatrixCursor(arrayOf("signature", "event")).also { val signature = @@ -113,9 +149,9 @@ class SignerProvider : ContentProvider() { val pubkey = projection[1] val account = LocalPreferences.loadFromEncryptedStorage(projection[2]) ?: return null val currentSelection = selection ?: "0" - + val database = NostrSigner.instance.getDatabase(account.keyPair.pubKey.toNpub()) val permission = - NostrSigner.instance.getDatabase(account.keyPair.pubKey.toNpub()) + database .applicationDao() .getPermission( sortOrder ?: packageName, @@ -129,11 +165,33 @@ class SignerProvider : ContentProvider() { MatrixCursor(arrayOf("rejected")).also { it.addRow(arrayOf("true")) } + database.applicationDao().addHistory( + HistoryEntity( + 0, + sortOrder ?: packageName, + uri.toString().replace("content://$appId.", ""), + null, + TimeUtils.now(), + false, + ), + ) return cursor } } else { val isRemembered = permission?.acceptable ?: false - if (!isRemembered) return null + if (!isRemembered) { + database.applicationDao().addHistory( + HistoryEntity( + 0, + sortOrder ?: packageName, + uri.toString().replace("content://$appId.", ""), + null, + TimeUtils.now(), + false, + ), + ) + return null + } } val type = @@ -158,6 +216,17 @@ class SignerProvider : ContentProvider() { "Could not decrypt the message" } + database.applicationDao().addHistory( + HistoryEntity( + 0, + sortOrder ?: packageName, + uri.toString().replace("content://$appId.", ""), + null, + TimeUtils.now(), + true, + ), + ) + if (type == SignerType.NIP04_ENCRYPT && result == "Could not decrypt the message") { return null } else { @@ -171,9 +240,9 @@ class SignerProvider : ContentProvider() { val packageName = callingPackage ?: return null val account = LocalPreferences.loadFromEncryptedStorage() ?: return null val currentSelection = selection ?: "0" - + val database = NostrSigner.instance.getDatabase(account.keyPair.pubKey.toNpub()) val permission = - NostrSigner.instance.getDatabase(account.keyPair.pubKey.toNpub()) + database .applicationDao() .getPermission( sortOrder ?: packageName, @@ -187,13 +256,46 @@ class SignerProvider : ContentProvider() { MatrixCursor(arrayOf("rejected")).also { it.addRow(arrayOf("true")) } + database.applicationDao().addHistory( + HistoryEntity( + 0, + sortOrder ?: packageName, + uri.toString().replace("content://$appId.", ""), + null, + TimeUtils.now(), + false, + ), + ) return cursor } } else { val isRemembered = permission?.acceptable ?: false - if (!isRemembered) return null + if (!isRemembered) { + database.applicationDao().addHistory( + HistoryEntity( + 0, + sortOrder ?: packageName, + uri.toString().replace("content://$appId.", ""), + null, + TimeUtils.now(), + false, + ), + ) + return null + } } + database.applicationDao().addHistory( + HistoryEntity( + 0, + sortOrder ?: packageName, + uri.toString().replace("content://$appId.", ""), + null, + TimeUtils.now(), + true, + ), + ) + val cursor = MatrixCursor(arrayOf("signature")) cursor.addRow(arrayOf(account.keyPair.pubKey.toNpub())) return cursor 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 5605b4a6..41261b83 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/AppDatabase.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/AppDatabase.kt @@ -17,9 +17,18 @@ val MIGRATION_1_2 = } } +val MIGRATION_2_3 = + object : Migration(2, 3) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS `history` (`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 )") + db.execSQL("CREATE INDEX IF NOT EXISTS `history_by_pk_key` ON `history` (`pkKey`)") + db.execSQL("CREATE INDEX IF NOT EXISTS `history_by_id` ON `history` (`id`)") + } + } + @Database( - entities = [ApplicationEntity::class, ApplicationPermissionsEntity::class, NotificationEntity::class], - version = 2, + entities = [ApplicationEntity::class, ApplicationPermissionsEntity::class, NotificationEntity::class, HistoryEntity::class], + version = 3, ) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { @@ -38,6 +47,7 @@ abstract class AppDatabase : RoomDatabase() { "amber_db_$npub", ) .addMigrations(MIGRATION_1_2) + .addMigrations(MIGRATION_2_3) .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 d3b5cb61..bb4084c9 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt @@ -1,5 +1,6 @@ package com.greenart7c3.nostrsigner.database +import android.util.Log import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert @@ -109,4 +110,20 @@ interface ApplicationDao { @Delete fun delete(entity: ApplicationEntity) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun innerAddHistory(entity: HistoryEntity) + + @Insert(onConflict = OnConflictStrategy.IGNORE) + @Transaction + fun addHistory(entity: HistoryEntity) { + try { + innerAddHistory(entity) + } catch (e: Exception) { + Log.e("ApplicationDao", "Error adding history", e) + } + } + + @Query("SELECT * FROM history where pkKey = :pk ORDER BY time DESC") + fun getAllHistory(pk: String): List } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/database/HistoryEntity.kt b/app/src/main/java/com/greenart7c3/nostrsigner/database/HistoryEntity.kt new file mode 100644 index 00000000..0131d6ff --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/HistoryEntity.kt @@ -0,0 +1,37 @@ +package com.greenart7c3.nostrsigner.database + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "history", + foreignKeys = [ + ForeignKey( + entity = ApplicationEntity::class, + childColumns = ["pkKey"], + parentColumns = ["key"], + onDelete = ForeignKey.CASCADE, + ), + ], + indices = [ + Index( + value = ["pkKey"], + name = "history_by_pk_key", + ), + Index( + value = ["id"], + name = "history_by_id", + ), + ], +) +data class HistoryEntity( + @PrimaryKey(autoGenerate = true) + val id: Int, + val pkKey: String, + val type: String, + val kind: Int?, + val time: Long, + val accepted: Boolean, +) 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 acc88116..35bb4124 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -38,6 +38,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -62,6 +63,7 @@ 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.database.HistoryEntity import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.relays.Client @@ -74,6 +76,126 @@ import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.encoders.toNpub import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle + +fun convertLongToDateTime(longValue: Long): String { + // Convert the Long timestamp to LocalDateTime + val dateTime = + LocalDateTime.ofInstant( + Instant.ofEpochMilli(longValue), + ZoneId.systemDefault(), + ) + + // Format the LocalDateTime with local format + val formatter = + DateTimeFormatter.ofLocalizedDateTime( + FormatStyle.SHORT, + FormatStyle.MEDIUM, + ) + + return dateTime.format(formatter) +} + +@Composable +fun ActivityDialog( + account: Account, + database: AppDatabase, + key: String, + onClose: () -> Unit, +) { + val activities = + remember { + mutableStateListOf() + } + + 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() + } + } + LazyColumn( + Modifier + .fillMaxHeight(0.9f) + .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( + permission.toString(), + 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 = null, + tint = if (activity.accepted) Color.Green else Color.Red, + ) + } + } + } + } + } + } + } +} @Composable fun EditPermission( @@ -299,6 +421,21 @@ fun EditPermission( mutableStateOf(false) } + var showActivityDialog by remember { + mutableStateOf(false) + } + + if (showActivityDialog) { + ActivityDialog( + account = account, + database = database, + key = selectedPackage, + onClose = { + showActivityDialog = false + }, + ) + } + if (showDialog) { QrCodeDialog( content = bunkerUri, @@ -410,6 +547,20 @@ fun EditPermission( } } + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + Button( + onClick = { + showActivityDialog = true + }, + Modifier.padding(6.dp), + ) { + Text(stringResource(R.string.activity)) + } + } + LazyColumn( Modifier.weight(1f), ) { 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 243206bf..03a7b671 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -85,6 +85,7 @@ 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.database.HistoryEntity import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.BunkerResponse import com.greenart7c3.nostrsigner.models.CompressionType @@ -92,6 +93,7 @@ import com.greenart7c3.nostrsigner.models.IntentData 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.relays.Relay import com.greenart7c3.nostrsigner.relays.RelayPool import com.greenart7c3.nostrsigner.service.EventNotificationConsumer @@ -167,12 +169,20 @@ fun sendResult( val relays = intentData.bunkerRequest?.relays ?: listOf() val savedApplication = database.applicationDao().getByKey(key) + val localAppName = + if (packageName != null) { + val info = context.packageManager.getApplicationInfo(packageName, 0) + context.packageManager.getApplicationLabel(info).toString() + } else { + appName + } + val application = savedApplication ?: ApplicationWithPermissions( application = ApplicationEntity( key, - appName ?: "", + appName ?: localAppName ?: "", relays, "", "", @@ -230,6 +240,16 @@ fun sendResult( onLoading, onSign = { database.applicationDao().insertApplicationWithPermissions(application) + database.applicationDao().addHistory( + HistoryEntity( + 0, + key, + intentData.type.toString(), + kind, + TimeUtils.now(), + true, + ), + ) PushNotificationUtils.hasInit = false GlobalScope.launch(Dispatchers.IO) { PushNotificationUtils.init(LocalPreferences.allSavedAccounts()) @@ -242,6 +262,16 @@ fun sendResult( } } else if (packageName != null) { database.applicationDao().insertApplicationWithPermissions(application) + database.applicationDao().addHistory( + HistoryEntity( + 0, + key, + intentData.type.toString(), + kind, + TimeUtils.now(), + true, + ), + ) val intent = Intent() intent.putExtra("signature", value) 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 bc622abb..4997fcfe 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 @@ -48,11 +48,15 @@ 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.ApplicationWithPermissions +import com.greenart7c3.nostrsigner.database.HistoryEntity import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.BunkerResponse import com.greenart7c3.nostrsigner.models.IntentData import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.models.SignerType +import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.relays.Relay import com.greenart7c3.nostrsigner.relays.RelayPool import com.greenart7c3.nostrsigner.service.AmberUtils @@ -66,6 +70,7 @@ import com.greenart7c3.nostrsigner.service.model.AmberEvent import com.greenart7c3.nostrsigner.service.toShortenHex import com.greenart7c3.nostrsigner.ui.Result import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder +import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.events.LnZapRequestEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -179,6 +184,39 @@ fun MultiEventHomeScreen( ) } + val application = + database + .applicationDao() + .getByKey(key) ?: ApplicationWithPermissions( + application = + ApplicationEntity( + key, + applicationEntity?.application?.name?.ifBlank { applicationEntity.application.key.toShortenHex() } ?: "", + listOf(), + "", + "", + "", + localAccount.keyPair.pubKey.toHexKey(), + true, + intentData.bunkerRequest?.secret ?: "", + intentData.bunkerRequest?.secret != null, + ), + permissions = mutableListOf(), + ) + + database.applicationDao().insertApplicationWithPermissions(application) + + database.applicationDao().addHistory( + HistoryEntity( + 0, + key, + intentData.type.toString(), + localEvent.kind, + TimeUtils.now(), + true, + ), + ) + if (intentData.bunkerRequest != null) { IntentUtils.sendBunkerResponse( localAccount, @@ -217,6 +255,40 @@ fun MultiEventHomeScreen( database, ) } + + val application = + database + .applicationDao() + .getByKey(key) ?: ApplicationWithPermissions( + application = + ApplicationEntity( + key, + applicationEntity?.application?.name?.ifBlank { applicationEntity.application.key.toShortenHex() } ?: "", + listOf(), + "", + "", + "", + localAccount.keyPair.pubKey.toHexKey(), + true, + intentData.bunkerRequest?.secret ?: "", + intentData.bunkerRequest?.secret != null, + ), + permissions = mutableListOf(), + ) + + database.applicationDao().insertApplicationWithPermissions(application) + + database.applicationDao().addHistory( + HistoryEntity( + 0, + key, + intentData.type.toString(), + null, + TimeUtils.now(), + true, + ), + ) + val signature = intentData.encryptedData ?: continue if (intentData.bunkerRequest != null) { 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 47949cc9..b0356054 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 @@ -24,10 +24,13 @@ import androidx.compose.ui.unit.sp import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.database.AppDatabase +import com.greenart7c3.nostrsigner.database.ApplicationEntity import com.greenart7c3.nostrsigner.database.ApplicationWithPermissions +import com.greenart7c3.nostrsigner.database.HistoryEntity import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.IntentData import com.greenart7c3.nostrsigner.models.SignerType +import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.relays.Relay import com.greenart7c3.nostrsigner.service.AmberUtils import com.greenart7c3.nostrsigner.service.getAppCompatActivity @@ -118,8 +121,12 @@ fun SingleEventHomeScreen( return@LoginWithPubKey }, { - if (remember.value) { - coroutineScope.launch(Dispatchers.IO) { + coroutineScope.launch(Dispatchers.IO) { + if (key == "null") { + return@launch + } + + if (remember.value) { AmberUtils.acceptOrRejectPermission( key, intentData, @@ -130,6 +137,39 @@ fun SingleEventHomeScreen( database, ) } + + val savedApplication = database.applicationDao().getByKey(key) + val relays = intentData.bunkerRequest?.relays ?: listOf() + val application = + savedApplication ?: ApplicationWithPermissions( + application = + ApplicationEntity( + key, + appName, + relays, + "", + "", + "", + account.keyPair.pubKey.toHexKey(), + true, + intentData.bunkerRequest?.secret ?: "", + intentData.bunkerRequest?.secret != null, + ), + permissions = mutableListOf(), + ) + + database.applicationDao().insertApplicationWithPermissions(application) + + database.applicationDao().addHistory( + HistoryEntity( + 0, + key, + intentData.type.toString(), + null, + TimeUtils.now(), + false, + ), + ) } if (intentData.bunkerRequest != null) { @@ -203,8 +243,12 @@ fun SingleEventHomeScreen( } }, { - if (remember.value) { - coroutineScope.launch(Dispatchers.IO) { + coroutineScope.launch(Dispatchers.IO) { + if (key == "null") { + return@launch + } + + if (remember.value) { AmberUtils.acceptOrRejectPermission( key, intentData, @@ -215,6 +259,39 @@ fun SingleEventHomeScreen( database, ) } + + val savedApplication = database.applicationDao().getByKey(key) + val relays = intentData.bunkerRequest?.relays ?: listOf() + val application = + savedApplication ?: ApplicationWithPermissions( + application = + ApplicationEntity( + key, + appName, + relays, + "", + "", + "", + account.keyPair.pubKey.toHexKey(), + true, + intentData.bunkerRequest?.secret ?: "", + intentData.bunkerRequest?.secret != null, + ), + permissions = mutableListOf(), + ) + + database.applicationDao().insertApplicationWithPermissions(application) + + database.applicationDao().addHistory( + HistoryEntity( + 0, + key, + intentData.type.toString(), + null, + TimeUtils.now(), + false, + ), + ) } if (intentData.bunkerRequest != null) { @@ -319,8 +396,12 @@ fun SingleEventHomeScreen( ) }, { - if (remember.value) { - coroutineScope.launch(Dispatchers.IO) { + coroutineScope.launch(Dispatchers.IO) { + if (key == "null") { + return@launch + } + + if (remember.value) { AmberUtils.acceptOrRejectPermission( key, intentData, @@ -331,6 +412,39 @@ fun SingleEventHomeScreen( database, ) } + + val savedApplication = database.applicationDao().getByKey(key) + val relays = intentData.bunkerRequest?.relays ?: listOf() + val application = + savedApplication ?: ApplicationWithPermissions( + application = + ApplicationEntity( + key, + appName, + relays, + "", + "", + "", + account.keyPair.pubKey.toHexKey(), + true, + intentData.bunkerRequest?.secret ?: "", + intentData.bunkerRequest?.secret != null, + ), + permissions = mutableListOf(), + ) + + database.applicationDao().insertApplicationWithPermissions(application) + + database.applicationDao().addHistory( + HistoryEntity( + 0, + key, + intentData.type.toString(), + event.kind, + TimeUtils.now(), + false, + ), + ) } if (intentData.bunkerRequest != null) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0c88a33b..aaf1b93e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,4 +119,5 @@ Direct Connection Notification Type Select the type of notification you want to receive - \ No newline at end of file + Activity +