diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/NostrSigner.kt b/app/src/main/java/com/greenart7c3/nostrsigner/NostrSigner.kt index 2deca26..d3757d4 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/NostrSigner.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/NostrSigner.kt @@ -34,31 +34,21 @@ import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.EventInterface import com.vitorpamplona.quartz.events.MetadataEvent import com.vitorpamplona.quartz.utils.TimeUtils -import com.vitorpamplona.quartz.utils.TimeUtils.ONE_WEEK -import java.util.Timer -import java.util.TimerTask import java.util.UUID import java.util.concurrent.ConcurrentHashMap -import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel -import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking class NostrSigner : Application() { - private var timer: Timer? = null val client: NostrClient = NostrClient(OkHttpWebSocket.Builder()) val applicationIOScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private var databases = ConcurrentHashMap() lateinit var settings: AmberSettings - var job: Job? = null - val status = MutableStateFlow("") val isOnMobileDataState = mutableStateOf(false) val isOnWifiDataState = mutableStateOf(false) @@ -110,79 +100,6 @@ class NostrSigner : Application() { } } - timer?.cancel() - timer = Timer() - timer?.schedule( - object : TimerTask() { - override fun run() { - job?.cancelChildren() - job?.cancel() - job = applicationIOScope.launch { - LocalPreferences.allSavedAccounts(this@NostrSigner).forEach { - databases[it.npub]?.let { database -> - try { - status.value = "Deleting old log entries from ${it.npub}" - val oneWeek = System.currentTimeMillis() - ONE_WEEK - val oneWeekAgo = TimeUtils.oneWeekAgo() - val countHistory = database.applicationDao().countOldHistory(oneWeekAgo) - if (countHistory > 0) { - status.value = "Deleting $countHistory old history entries" - var logs = database.applicationDao().getOldHistory(oneWeekAgo) - var count = 0 - while (logs.isNotEmpty()) { - count++ - status.value = "Deleting ${100 * count}/$countHistory old history entries" - logs.forEach { history -> - database.applicationDao().deleteHistory(history) - } - logs = database.applicationDao().getOldHistory(oneWeekAgo) - } - } - - val countNotification = database.applicationDao().countOldNotification(oneWeekAgo) - if (countNotification > 0) { - status.value = "Deleting $countNotification old notification entries" - var logs = database.applicationDao().getOldNotification(oneWeekAgo) - var count = 0 - while (logs.isNotEmpty()) { - count++ - status.value = "Deleting ${100 * count}/$countNotification old notification entries" - logs.forEach { history -> - database.applicationDao().deleteNotification(history) - } - logs = database.applicationDao().getOldNotification(oneWeekAgo) - } - } - - val countLog = database.applicationDao().countOldLog(oneWeek) - if (countLog > 0) { - status.value = "Deleting $countLog old notification entries" - var logs = database.applicationDao().getOldLog(oneWeek) - var count = 0 - while (logs.isNotEmpty()) { - count++ - status.value = "Deleting ${100 * count}/$countLog old log entries" - logs.forEach { history -> - database.applicationDao().deleteLog(history) - } - logs = database.applicationDao().getOldLog(oneWeek) - } - } - status.value = "" - } catch (e: Exception) { - if (e is CancellationException) throw e - Log.e("NostrSigner", "Error deleting old log entries", e) - status.value = "" - } - } - } - } - } - }, - 0, - 3_600_000, - ) - runBlocking { settings = LocalPreferences.loadSettingsFromEncryptedStorage() } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt index 7f99334..48df488 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/PermissionsScreen.kt @@ -12,10 +12,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -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 @@ -32,15 +29,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController -import com.greenart7c3.nostrsigner.NostrSigner import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.database.AppDatabase import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.service.toShortenHex import com.vitorpamplona.quartz.encoders.toHexKey -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch @Composable fun PermissionsScreen( @@ -49,95 +43,80 @@ fun PermissionsScreen( navController: NavController, database: AppDatabase, ) { - var isLoading by remember { mutableStateOf(false) } + val applications = database.applicationDao().getAllFlow(account.signer.keyPair.pubKey.toHexKey()).collectAsStateWithLifecycle(emptyList()) - LaunchedEffect(Unit) { - isLoading = true - launch(Dispatchers.IO) { - NostrSigner.getInstance().job?.join() - isLoading = false - } - } - - if (isLoading) { - val status = NostrSigner.getInstance().status.collectAsStateWithLifecycle() - CenterCircularProgressIndicator(modifier, status.value) - } else { - val applications = database.applicationDao().getAllFlow(account.signer.keyPair.pubKey.toHexKey()).collectAsStateWithLifecycle(emptyList()) - - Column( - modifier, - ) { - if (applications.value.isEmpty()) { - Text( - text = stringResource(R.string.congratulations_your_new_account_is_ready), - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - ) + Column( + modifier, + ) { + if (applications.value.isEmpty()) { + Text( + text = stringResource(R.string.congratulations_your_new_account_is_ready), + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + ) - Text( - buildAnnotatedString { - append(stringResource(R.string.your_account_is_ready_to_use)) - withLink( - LinkAnnotation.Url( - "https://" + stringResource(R.string.nostr_app), - styles = TextLinkStyles( - style = SpanStyle( - textDecoration = TextDecoration.Underline, - ), + Text( + buildAnnotatedString { + append(stringResource(R.string.your_account_is_ready_to_use)) + withLink( + LinkAnnotation.Url( + "https://" + stringResource(R.string.nostr_app), + styles = TextLinkStyles( + style = SpanStyle( + textDecoration = TextDecoration.Underline, ), ), - ) { - append(" " + stringResource(R.string.nostr_app)) - } - }, - ) - } else { - applications.value.forEach { applicationWithHistory -> - Row( - modifier = Modifier - .fillMaxSize() - .padding(vertical = 4.dp) - .clickable { - navController.navigate("Permission/${applicationWithHistory.application.key}") - }, - verticalAlignment = Alignment.CenterVertically, + ), ) { - Column( - verticalArrangement = Arrangement.Center, + append(" " + stringResource(R.string.nostr_app)) + } + }, + ) + } else { + applications.value.forEach { applicationWithHistory -> + Row( + modifier = Modifier + .fillMaxSize() + .padding(vertical = 4.dp) + .clickable { + navController.navigate("Permission/${applicationWithHistory.application.key}") + }, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + verticalArrangement = Arrangement.Center, + ) { + Text( + modifier = Modifier.padding(top = 16.dp), + text = applicationWithHistory.application.name.ifBlank { applicationWithHistory.application.key.toShortenHex() }, + fontSize = 24.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, ) { Text( - modifier = Modifier.padding(top = 16.dp), - text = applicationWithHistory.application.name.ifBlank { applicationWithHistory.application.key.toShortenHex() }, - fontSize = 24.sp, + modifier = Modifier.padding(top = 4.dp, bottom = 16.dp), + text = applicationWithHistory.application.key.toShortenHex(), + fontSize = 16.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Text( - modifier = Modifier.padding(top = 4.dp, bottom = 16.dp), - text = applicationWithHistory.application.key.toShortenHex(), - fontSize = 16.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - modifier = Modifier.padding(top = 4.dp, bottom = 16.dp), - text = if (applicationWithHistory.latestTime == null) stringResource(R.string.never) else TimeUtils.formatLongToCustomDateTime(applicationWithHistory.latestTime * 1000), - fontSize = 16.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - - Spacer(Modifier.weight(1f)) - HorizontalDivider( - color = MaterialTheme.colorScheme.primary, + Text( + modifier = Modifier.padding(top = 4.dp, bottom = 16.dp), + text = if (applicationWithHistory.latestTime == null) stringResource(R.string.never) else TimeUtils.formatLongToCustomDateTime(applicationWithHistory.latestTime * 1000), + fontSize = 16.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) } + + Spacer(Modifier.weight(1f)) + HorizontalDivider( + color = MaterialTheme.colorScheme.primary, + ) } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt index 85df9c1..7213906 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt @@ -1,5 +1,6 @@ package com.greenart7c3.nostrsigner.ui +import android.util.Log import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -47,7 +48,6 @@ import androidx.compose.ui.text.withLink import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import com.greenart7c3.nostrsigner.BuildConfig import com.greenart7c3.nostrsigner.LocalPreferences @@ -56,13 +56,16 @@ import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.service.NotificationDataSource import com.greenart7c3.nostrsigner.ui.actions.LogoutDialog +import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.greenart7c3.nostrsigner.ui.components.IconRow import com.greenart7c3.nostrsigner.ui.components.TextSpinner import com.greenart7c3.nostrsigner.ui.components.TitleExplainer import com.greenart7c3.nostrsigner.ui.navigation.Route import com.vitorpamplona.quartz.encoders.toNpub -import java.io.File +import com.vitorpamplona.quartz.utils.TimeUtils +import com.vitorpamplona.quartz.utils.TimeUtils.ONE_WEEK import java.text.DecimalFormat +import kotlin.coroutines.cancellation.CancellationException import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -82,11 +85,15 @@ fun SettingsScreen( var allowNewConnections by remember { mutableStateOf(account.allowNewConnections) } val scope = rememberCoroutineScope() var isLoading by remember { mutableStateOf(false) } + var sizeInMBFormatted by remember { mutableStateOf("") } + var status by remember { mutableStateOf("") } LaunchedEffect(Unit) { isLoading = true launch(Dispatchers.IO) { - NostrSigner.getInstance().job?.join() + val dbFile = context.getDatabasePath("amber_db_${account.signer.keyPair.pubKey.toNpub()}") + val df = DecimalFormat("#.###") + sizeInMBFormatted = df.format(dbFile.length() / (1024.0 * 1024.0)) isLoading = false } } @@ -104,8 +111,7 @@ fun SettingsScreen( } if (isLoading) { - val status = NostrSigner.getInstance().status.collectAsStateWithLifecycle() - CenterCircularProgressIndicator(modifier, status.value) + CenterCircularProgressIndicator(modifier, status) } else { Column( modifier, @@ -249,23 +255,92 @@ fun SettingsScreen( val primaryColor = MaterialTheme.colorScheme.primary - val dbFile = context.getDatabasePath("amber_db_${account.signer.keyPair.pubKey.toNpub()}") - val df = DecimalFormat("#.###") - val sizeInMBFormatted: String = df.format(dbFile.length() / (1024.0 * 1024.0)) + Column( + modifier = Modifier + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text("Database size: $sizeInMBFormatted MB", color = Color.Gray) + AmberButton( + modifier = Modifier.fillMaxWidth(), + text = "Clear logs and activity", + onClick = { + NostrSigner.getInstance().applicationIOScope.launch { + NotificationDataSource.stopSync() + isLoading = true + LocalPreferences.allSavedAccounts(NostrSigner.getInstance()).forEach { + NostrSigner.getInstance().getDatabase(it.npub).let { database -> + try { + status = "Deleting old log entries from ${it.npub}" + val oneWeek = System.currentTimeMillis() - ONE_WEEK + val oneWeekAgo = TimeUtils.oneWeekAgo() + val countHistory = database.applicationDao().countOldHistory(oneWeekAgo) + if (countHistory > 0) { + status = "Deleting $countHistory old history entries" + var logs = database.applicationDao().getOldHistory(oneWeekAgo) + var count = 0 + while (logs.isNotEmpty()) { + count++ + status = "Deleting ${100 * count}/$countHistory old history entries" + logs.forEach { history -> + database.applicationDao().deleteHistory(history) + } + logs = database.applicationDao().getOldHistory(oneWeekAgo) + } + } - val sharedPrefsDir = File(context.applicationInfo.dataDir + "/shared_prefs") - val sharedPrefsFiles = sharedPrefsDir.listFiles() - var totalSize = 0L - if (sharedPrefsFiles != null) { - for (file in sharedPrefsFiles) { - if (file.isFile) { - totalSize += file.length() - } - } + val countNotification = database.applicationDao().countOldNotification(oneWeekAgo) + if (countNotification > 0) { + status = "Deleting $countNotification old notification entries" + var logs = database.applicationDao().getOldNotification(oneWeekAgo) + var count = 0 + while (logs.isNotEmpty()) { + count++ + status = "Deleting ${100 * count}/$countNotification old notification entries" + logs.forEach { history -> + database.applicationDao().deleteNotification(history) + } + logs = database.applicationDao().getOldNotification(oneWeekAgo) + } + } + + val countLog = database.applicationDao().countOldLog(oneWeek) + if (countLog > 0) { + status = "Deleting $countLog old log entries" + var logs = database.applicationDao().getOldLog(oneWeek) + var count = 0 + while (logs.isNotEmpty()) { + count++ + status = "Deleting ${100 * count}/$countLog old log entries" + logs.forEach { history -> + database.applicationDao().deleteLog(history) + } + logs = database.applicationDao().getOldLog(oneWeek) + } + } + val dbFile = context.getDatabasePath("amber_db_${account.signer.keyPair.pubKey.toNpub()}") + val df = DecimalFormat("#.###") + sizeInMBFormatted = df.format(dbFile.length() / (1024.0 * 1024.0)) + status = "" + isLoading = false + NotificationDataSource.start() + } catch (e: Exception) { + isLoading = false + if (e is CancellationException) throw e + Log.e("NostrSigner", "Error deleting old log entries", e) + val dbFile = context.getDatabasePath("amber_db_${account.signer.keyPair.pubKey.toNpub()}") + val df = DecimalFormat("#.###") + sizeInMBFormatted = df.format(dbFile.length() / (1024.0 * 1024.0)) + status = "" + NotificationDataSource.start() + } + } + } + } + }, + ) } - Text("Database size: $sizeInMBFormatted MB", color = Color.Gray) - Text("Shared Prefs size: ${df.format(totalSize / (1024.0 * 1024.0))} MB", color = Color.Gray) Text( buildAnnotatedString { withStyle(