From a7d93697fd59af59cf9c87d0fd90b6be92bfbb57 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 12 Jan 2026 08:15:06 -0300 Subject: [PATCH] Refactor permission management and UI components - Add `updateExpiredPermissions` to `ApplicationDao` to reset expired temporary permissions. - Implement `AmberToggles` and `ToggleOption` reusable UI components. - Refactor `EditPermission` to use the new toggle components and ensure expired permissions are updated on load. - Update `ConnectivityService` to periodically clear expired permissions across all accounts. - Clean up redundant build flavor checks and logic in `ConnectivityService`. --- .../nostrsigner/database/ApplicationDao.kt | 5 +- .../service/ConnectivityService.kt | 10 +- .../nostrsigner/ui/EditPermission.kt | 311 ++++++------------ .../nostrsigner/ui/components/AmberToggles.kt | 72 ++++ .../nostrsigner/ui/components/ToogleOption.kt | 45 +++ 5 files changed, 232 insertions(+), 211 deletions(-) create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberToggles.kt create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/ToogleOption.kt 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 70a6e03a..5df4295e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt @@ -42,7 +42,10 @@ interface ApplicationDao { @Transaction suspend fun getBySecret(secret: String): ApplicationWithPermissions? - @Query("SELECT * FROM applicationPermission WHERE pkKey = :key and rememberType = 4") + @Query("UPDATE applicationPermission set acceptUntil = 0, rejectUntil = 0, rememberType = 0 where (acceptUntil < :time OR rejectUntil < :time) AND rememberType <> 4") + fun updateExpiredPermissions(time: Long) + + @Query("SELECT * FROM applicationPermission WHERE pkKey = :key") suspend fun getAllByKey(key: String): List @Query("SELECT `key`, `name` FROM application WHERE name <> ''") diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/ConnectivityService.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/ConnectivityService.kt index 048bb05c..a7275f1b 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/ConnectivityService.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/ConnectivityService.kt @@ -11,6 +11,7 @@ import com.greenart7c3.nostrsigner.Amber import com.greenart7c3.nostrsigner.BuildFlavorChecker import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.database.LogEntity +import com.vitorpamplona.quartz.utils.TimeUtils import java.util.Timer import java.util.TimerTask import kotlinx.coroutines.CoroutineScope @@ -109,11 +110,10 @@ class ConnectivityService : Service() { timer.schedule( object : TimerTask() { override fun run() { - if (Amber.instance.settings.killSwitch.value) return - scope.launch { LocalPreferences.allSavedAccounts(Amber.instance).forEach { accountInfo -> val now = System.currentTimeMillis() / 1000 + Amber.instance.getDatabase(accountInfo.npub).dao().updateExpiredPermissions(TimeUtils.now()) val deleted = Amber.instance.getDatabase(accountInfo.npub).dao().deleteOldApplications(now) if (deleted > 0) { Amber.instance.getLogDatabase(accountInfo.npub).dao().insertLog( @@ -125,9 +125,6 @@ class ConnectivityService : Service() { time = System.currentTimeMillis(), ), ) - if (!BuildFlavorChecker.isOfflineFlavor()) { - Amber.instance.notificationSubscription.updateFilter() - } } } } @@ -135,8 +132,11 @@ class ConnectivityService : Service() { if (BuildFlavorChecker.isOfflineFlavor()) { return } + if (Amber.instance.settings.killSwitch.value) return scope.launch { + Amber.instance.notificationSubscription.updateFilter() + if (!Amber.instance.client.isActive()) { Amber.instance.client.connect() } 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 b80b51d3..c5c87f65 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -1,23 +1,14 @@ package com.greenart7c3.nostrsigner.ui import android.content.ClipData -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape @@ -39,7 +30,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.ClipEntry @@ -62,6 +52,8 @@ import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.ui.actions.RemoveAllPermissionsDialog import com.greenart7c3.nostrsigner.ui.components.AmberButton +import com.greenart7c3.nostrsigner.ui.components.AmberToggles +import com.greenart7c3.nostrsigner.ui.components.ToggleOption import com.greenart7c3.nostrsigner.ui.theme.orange import com.vitorpamplona.quartz.utils.TimeUtils import kotlinx.coroutines.Dispatchers @@ -98,6 +90,7 @@ fun EditPermission( LaunchedEffect(selectedPackage) { val result = withContext(Dispatchers.IO) { val dao = Amber.instance.getDatabase(account.npub).dao() + dao.updateExpiredPermissions(TimeUtils.now()) val perms = dao.getAllByKey(selectedPackage) .sortedBy { "${it.type}-${it.kind}" } @@ -316,9 +309,7 @@ fun PermissionRow( } } val fixedSegmentWidth = 55.dp - val padding = 2.dp - val totalWidth = (fixedSegmentWidth * 3) + (padding * 2) - val totalWidthRememberType = (fixedSegmentWidth * 4) + (padding * 2) + var optionIndex by remember { if (permission.acceptUntil > 0) { mutableIntStateOf(0) @@ -352,215 +343,125 @@ fun PermissionRow( overflow = TextOverflow.Ellipsis, ) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, + AmberToggles( + count = 3, + selectedIndex = optionIndex, ) { - Box( - modifier = Modifier - .width(totalWidth) - .height(32.dp) - .clip(RoundedCornerShape(6.dp)) - .background(MaterialTheme.colorScheme.background) - .padding(padding), - ) { - val indicatorOffset by animateDpAsState( - targetValue = fixedSegmentWidth * optionIndex, - animationSpec = tween(durationMillis = 250), - label = "indicatorOffset", - ) + ToggleOption( + text = "Allow", + isSelected = optionIndex == 0, + modifier = Modifier.width(fixedSegmentWidth), + onClick = { + optionIndex = 0 - Box( - modifier = Modifier - .offset(x = indicatorOffset) - .width(fixedSegmentWidth) - .fillMaxHeight() - .clip(RoundedCornerShape(4.dp)) - .background( - MaterialTheme.colorScheme.primary, - ), - ) - - Row(modifier = Modifier.fillMaxSize()) { - ToggleOption( - text = "Allow", - isSelected = optionIndex == 0, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - optionIndex = 0 - - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, + onSetPermission( + optionIndex, + rememberTypeIndex, + permission, + onToggle, ) - ToggleOption( - text = "Deny", - isSelected = optionIndex == 1, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - optionIndex = 1 + }, + ) + ToggleOption( + text = "Deny", + isSelected = optionIndex == 1, + modifier = Modifier.width(fixedSegmentWidth), + onClick = { + optionIndex = 1 - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, + onSetPermission( + optionIndex, + rememberTypeIndex, + permission, + onToggle, ) - ToggleOption( - text = "Ask", - isSelected = optionIndex == 2, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - optionIndex = 2 + }, + ) + ToggleOption( + text = "Ask", + isSelected = optionIndex == 2, + modifier = Modifier.width(fixedSegmentWidth), + onClick = { + optionIndex = 2 - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, + onSetPermission( + optionIndex, + rememberTypeIndex, + permission, + onToggle, ) - } - } + }, + ) } if (optionIndex != 2) { - Row( - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Box( - modifier = Modifier - .width(totalWidthRememberType) - .height(32.dp) - .clip(RoundedCornerShape(6.dp)) - .background(MaterialTheme.colorScheme.background) - .padding(padding), + AmberToggles( + selectedIndex = rememberTypeIndex, + count = 4, + content = { + ToggleOption( + text = "Always", + isSelected = rememberTypeIndex == 0, + modifier = Modifier.width(fixedSegmentWidth), + onClick = { + rememberTypeIndex = 0 - ) { - val indicatorOffset by animateDpAsState( - targetValue = fixedSegmentWidth * rememberTypeIndex, - animationSpec = tween(durationMillis = 250), - label = "indicatorOffset", + onSetPermission( + optionIndex, + rememberTypeIndex, + permission, + onToggle, + ) + }, ) - - Box( - modifier = Modifier - .offset(x = indicatorOffset) - .width(fixedSegmentWidth) - .fillMaxHeight() - .clip(RoundedCornerShape(4.dp)) - .background( - MaterialTheme.colorScheme.primary, - ), + ToggleOption( + text = "1m", + isSelected = rememberTypeIndex == 1, + modifier = Modifier.width(fixedSegmentWidth), + onClick = { + rememberTypeIndex = 1 + onSetPermission( + optionIndex, + rememberTypeIndex, + permission, + onToggle, + ) + }, ) - - Row( - modifier = Modifier.fillMaxSize(), - ) { - ToggleOption( - text = "Always", - isSelected = rememberTypeIndex == 0, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - rememberTypeIndex = 0 - - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, - ) - ToggleOption( - text = "1m", - isSelected = rememberTypeIndex == 1, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - rememberTypeIndex = 1 - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, - ) - ToggleOption( - text = "5m", - isSelected = rememberTypeIndex == 2, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - rememberTypeIndex = 2 - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, - ) - ToggleOption( - text = "10m", - isSelected = rememberTypeIndex == 3, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - rememberTypeIndex = 3 - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, - ) - } - } - } + ToggleOption( + text = "5m", + isSelected = rememberTypeIndex == 2, + modifier = Modifier.width(fixedSegmentWidth), + onClick = { + rememberTypeIndex = 2 + onSetPermission( + optionIndex, + rememberTypeIndex, + permission, + onToggle, + ) + }, + ) + ToggleOption( + text = "10m", + isSelected = rememberTypeIndex == 3, + modifier = Modifier.width(fixedSegmentWidth), + onClick = { + rememberTypeIndex = 3 + onSetPermission( + optionIndex, + rememberTypeIndex, + permission, + onToggle, + ) + }, + ) + }, + ) } } } -@Composable -private fun ToggleOption( - text: String, - isSelected: Boolean, - modifier: Modifier = Modifier, - onClick: () -> Unit, -) { - val textColor by animateColorAsState( - targetValue = if (isSelected) Color.Black else MaterialTheme.colorScheme.onSurfaceVariant, - animationSpec = tween(durationMillis = 200), - label = "textColor", - ) - - Box( - modifier = modifier - .fillMaxHeight() - .clip(RoundedCornerShape(4.dp)) - .clickable(onClick = onClick), - contentAlignment = Alignment.Center, - ) { - Text( - text = text, - style = MaterialTheme.typography.bodyMedium, - color = textColor, - ) - } -} - @Composable fun RelayCard( modifier: Modifier = Modifier, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberToggles.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberToggles.kt new file mode 100644 index 00000000..7be4bc72 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberToggles.kt @@ -0,0 +1,72 @@ +package com.greenart7c3.nostrsigner.ui.components + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp + +@Composable +fun AmberToggles( + selectedIndex: Int, + count: Int, + content: @Composable RowScope.() -> Unit, +) { + val fixedSegmentWidth = 55.dp + val padding = 2.dp + val totalWidth = (fixedSegmentWidth * count) + (padding * 2) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .width(totalWidth) + .height(32.dp) + .clip(RoundedCornerShape(6.dp)) + .background(MaterialTheme.colorScheme.background) + .padding(padding), + ) { + val indicatorOffset by animateDpAsState( + targetValue = fixedSegmentWidth * selectedIndex, + animationSpec = tween(durationMillis = 250), + label = "indicatorOffset", + ) + + Box( + modifier = Modifier + .offset(x = indicatorOffset) + .width(fixedSegmentWidth) + .fillMaxHeight() + .clip(RoundedCornerShape(4.dp)) + .background( + MaterialTheme.colorScheme.primary, + ), + ) + + Row(modifier = Modifier.fillMaxSize()) { + this + .content() + } + } + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/ToogleOption.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/ToogleOption.kt new file mode 100644 index 00000000..d1459e67 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/ToogleOption.kt @@ -0,0 +1,45 @@ +package com.greenart7c3.nostrsigner.ui.components + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun ToggleOption( + text: String, + isSelected: Boolean, + modifier: Modifier = Modifier, + onClick: () -> Unit, +) { + val textColor by animateColorAsState( + targetValue = if (isSelected) Color.Black else MaterialTheme.colorScheme.onSurfaceVariant, + animationSpec = tween(durationMillis = 200), + label = "textColor", + ) + + Box( + modifier = modifier + .fillMaxHeight() + .clip(RoundedCornerShape(4.dp)) + .clickable(onClick = onClick), + contentAlignment = Alignment.Center, + ) { + Text( + text = text, + style = MaterialTheme.typography.bodyMedium, + color = textColor, + ) + } +}