diff --git a/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationBanner.kt b/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationBanner.kt index 9205a0e5203c2ed7c4308855c4250e9fdca8cce2..17621db95c5a5dd532e2d6049b3d86e97c4ab279 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationBanner.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationBanner.kt @@ -1,3 +1,21 @@ +/* + * Briar Desktop + * Copyright (C) 2021-2022 The Briar Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + package org.briarproject.briar.desktop.expiration import androidx.compose.foundation.layout.Arrangement @@ -5,6 +23,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface @@ -20,14 +39,10 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import org.briarproject.briar.desktop.expiration.ExpirationUtils.getDaysLeft -import org.briarproject.briar.desktop.expiration.ExpirationUtils.isExpired +import org.briarproject.briar.desktop.expiration.ExpirationUtils.periodicallyCheckIfExpired import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nP import org.briarproject.briar.desktop.utils.PreviewUtils.preview -import kotlin.time.Duration.Companion.hours fun main() = preview { Column { @@ -39,20 +54,15 @@ fun main() = preview { fun ExpirationBanner(onExpired: () -> Unit) { var daysLeft by remember { mutableStateOf(0) } + var expired by remember { mutableStateOf(false) } LaunchedEffect(Unit) { - launch { - while (true) { - daysLeft = getDaysLeft() - if (isExpired()) { - onExpired() - break - } - delay(1.hours.inWholeMilliseconds) - } - } + periodicallyCheckIfExpired( + reportDaysLeft = { daysLeft = it }, + onExpired = { expired = true; onExpired() }, + ) } - ExpirationBanner(daysLeft) + if (!expired) ExpirationBanner(daysLeft) } @Composable @@ -63,11 +73,14 @@ fun ExpirationBanner( modifier = Modifier.fillMaxWidth() ) { Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp), verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(8.dp) + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) ) { - Icon(Icons.Filled.Warning, i18n("warning")) - Text(i18nP("expiration.banner", daysLeft)) + Icon(Icons.Filled.Warning, i18n("warning"), Modifier.size(40.dp)) + Text( + text = "${i18nP("expiration.banner.part1", daysLeft)} ${i18n("expiration.banner.part2")}", + style = MaterialTheme.typography.body2 + ) } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationUtils.kt b/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationUtils.kt index 7a51a741e5a4f25c7bf859379cd40c803b0e0be0..d9716edd921d51dcfb78bbf386f16f2490d77ce0 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationUtils.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationUtils.kt @@ -1,12 +1,55 @@ +/* + * Briar Desktop + * Copyright (C) 2021-2022 The Briar Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + package org.briarproject.briar.desktop.expiration +import kotlinx.coroutines.delay import org.briarproject.briar.desktop.BuildData import java.time.Instant -import java.time.temporal.ChronoUnit +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.milliseconds object ExpirationUtils { - fun getDaysLeft() = 90 - ChronoUnit.DAYS.between(Instant.ofEpochMilli(BuildData.GIT_TIME), Instant.now()).toInt() + private val EXPIRE_AFTER = BuildData.GIT_TIME + 91.days.inWholeMilliseconds + private val CHECK_INTERVAL = 1.hours.inWholeMilliseconds + + // for testing uncomment the following instead + // private val EXPIRE_AFTER = Instant.now().toEpochMilli() + 10.seconds.inWholeMilliseconds + // private val CHECK_INTERVAL = 1.seconds.inWholeMilliseconds + + private fun getMillisLeft() = (EXPIRE_AFTER - Instant.now().toEpochMilli()).milliseconds + + private fun getDaysLeft() = getMillisLeft().inWholeDays.toInt() + + private fun isExpired() = getMillisLeft() <= 0.milliseconds - fun isExpired() = getDaysLeft() <= 0 + suspend fun periodicallyCheckIfExpired( + reportDaysLeft: (Int) -> Unit, + onExpired: () -> Unit, + ) { + while (true) { + if (isExpired()) { + onExpired() + break + } else reportDaysLeft(getDaysLeft()) + delay(CHECK_INTERVAL) + } + } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt index 871f581bf6a815b40e2591479fd34cb491b42785..bad371c4c82ef021de02b3bad852bba4d8b0c92e 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt @@ -56,12 +56,9 @@ import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n import org.briarproject.briar.desktop.utils.PreviewUtils.preview fun main() = preview { - var error: ErrorSubViewModel.Error by remember { mutableStateOf(ErrorSubViewModel.ExpirationError) } + var error: ErrorSubViewModel.Error by remember { mutableStateOf(RegistrationSubViewModel.RegistrationError) } Row(horizontalArrangement = spacedBy(8.dp)) { - Button(onClick = { error = ErrorSubViewModel.ExpirationError }) { - Text("Expiration") - } Button(onClick = { error = RegistrationSubViewModel.RegistrationError }) { Text("Registration") } @@ -82,10 +79,34 @@ fun ErrorScreen(viewHolder: ErrorSubViewModel) = @Composable fun ErrorScreen( error: ErrorSubViewModel.Error, - onBackButton: () -> Unit, + onBackButton: (() -> Unit)?, +) { + val text = when (error) { + is RegistrationSubViewModel.RegistrationError -> i18n("startup.failed.registration") + is StartupViewModel.StartingError -> { + when (error.error) { + CLOCK_ERROR -> i18n("startup.failed.clock_error") + DB_ERROR -> i18n("startup.failed.db_error") + DATA_TOO_OLD_ERROR -> i18n("startup.failed.data_too_old_error") + DATA_TOO_NEW_ERROR -> i18n("startup.failed.data_too_new_error") + SERVICE_ERROR -> i18n("startup.failed.service_error") + else -> "" + } + } + } + + ErrorScreen(text, onBackButton) +} + +@Composable +fun ErrorScreen( + text: String, + onBackButton: (() -> Unit)? = null, ) = Surface { - IconButton(onClick = onBackButton) { - Icon(Icons.Filled.ArrowBack, i18n("back")) + if (onBackButton != null) { + IconButton(onClick = onBackButton) { + Icon(Icons.Filled.ArrowBack, i18n("back")) + } } Column( @@ -101,21 +122,6 @@ fun ErrorScreen( ) Text(i18n("sorry"), style = MaterialTheme.typography.h5) - - val text = when (error) { - is ErrorSubViewModel.ExpirationError -> i18n("startup.failed.expired") - is RegistrationSubViewModel.RegistrationError -> i18n("startup.failed.registration") - is StartupViewModel.StartingError -> { - when (error.error) { - CLOCK_ERROR -> i18n("startup.failed.clock_error") - DB_ERROR -> i18n("startup.failed.db_error") - DATA_TOO_OLD_ERROR -> i18n("startup.failed.data_too_old_error") - DATA_TOO_NEW_ERROR -> i18n("startup.failed.data_too_new_error") - SERVICE_ERROR -> i18n("startup.failed.service_error") - else -> "" - } - } - } Text( text = text, style = MaterialTheme.typography.body1, diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorSubViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorSubViewModel.kt index e4129ebdea14789586c081e07e4fec3d08f55f96..07a2acc9d3554c851d243f817a2ed7df2376a70a 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorSubViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorSubViewModel.kt @@ -21,9 +21,7 @@ package org.briarproject.briar.desktop.login class ErrorSubViewModel( private val viewModel: StartupViewModel, val error: Error, - val onBackButton: () -> Unit, + val onBackButton: (() -> Unit)?, ) : StartupViewModel.SubViewModel { sealed interface Error - - object ExpirationError : Error } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt index 8cd391466f051952da30243631ce36fcfc5cd03a..247d7451dac89e1e88848651d2d8932e8d261266 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt @@ -30,7 +30,6 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent -import org.briarproject.briar.desktop.expiration.ExpirationUtils.isExpired import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel import org.briarproject.briar.desktop.viewmodel.asState @@ -62,8 +61,7 @@ constructor( val currentSubViewModel = _currentSubViewModel.asState() private fun decideSubViewModel(): SubViewModel = - if (isExpired()) makeError(ErrorSubViewModel.ExpirationError) - else if (accountManager.accountExists()) makeLogin() + if (accountManager.accountExists()) makeLogin() else makeRegistration() private fun makeLogin() = LoginSubViewModel( diff --git a/src/main/kotlin/org/briarproject/briar/desktop/theme/Theme.kt b/src/main/kotlin/org/briarproject/briar/desktop/theme/Theme.kt index 1cc47ade5aab8a7fa3fc1861a967492ce303a1d7..1f2f74cfefaf0358b416333fb040bdb64a2c6a66 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/theme/Theme.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/theme/Theme.kt @@ -48,11 +48,12 @@ val DarkColors = darkColors( secondary = Lime500, background = materialDarkBg, surface = materialDarkBg, + error = briarError, onPrimary = Color.White, onSecondary = Color.White, onBackground = Color.White, onSurface = Color.White, - error = briarError + onError = Color.White, ) val LightColors = lightColors( primary = Blue500, @@ -60,11 +61,12 @@ val LightColors = lightColors( secondary = Lime300, background = Color.White, surface = Color.White, + error = briarError, onPrimary = Color.White, onSecondary = Color.Black, onBackground = Color.Black, onSurface = Color.Black, - error = briarError + onError = Color.White, ) @Composable diff --git a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt index ffafaaf8c251f591343708a2121eb0242fef12df..cbd5c1f1af90b17c83e475088f47db2c20798b4b 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt @@ -39,9 +39,11 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RU import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent import org.briarproject.briar.desktop.DesktopFeatureFlags import org.briarproject.briar.desktop.expiration.ExpirationBanner +import org.briarproject.briar.desktop.login.ErrorScreen import org.briarproject.briar.desktop.login.StartupScreen import org.briarproject.briar.desktop.settings.SettingsViewModel import org.briarproject.briar.desktop.theme.BriarTheme +import org.briarproject.briar.desktop.ui.Screen.EXPIRED import org.briarproject.briar.desktop.ui.Screen.MAIN import org.briarproject.briar.desktop.ui.Screen.STARTUP import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n @@ -54,7 +56,8 @@ import javax.inject.Singleton enum class Screen { STARTUP, - MAIN + MAIN, + EXPIRED, } interface BriarUi { @@ -119,10 +122,11 @@ constructor( val settingsViewModel: SettingsViewModel = viewModel() BriarTheme(isDarkTheme = settingsViewModel.isDarkMode.value) { Column(Modifier.fillMaxSize()) { - ExpirationBanner(onExpired = { screenState = STARTUP }) + ExpirationBanner { screenState = EXPIRED; stop() } when (screenState) { STARTUP -> StartupScreen() MAIN -> MainScreen(settingsViewModel) + EXPIRED -> ErrorScreen(i18n("startup.failed.expired")) } } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/ui/Constants.kt b/src/main/kotlin/org/briarproject/briar/desktop/ui/Constants.kt index 67c1b749c3eeeb832aa123b7a3c5ee6ae4631883..d860f80b8f4cdfe80313b67ff66793ca3c6bb38e 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/ui/Constants.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/Constants.kt @@ -21,7 +21,6 @@ package org.briarproject.briar.desktop.ui import androidx.compose.ui.unit.dp object Constants { - val BANNER_HEIGHT = 24.dp val HEADER_SIZE = 56.dp val COLUMN_WIDTH = 275.dp val STARTUP_FIELDS_WIDTH = 400.dp diff --git a/src/main/resources/strings/BriarDesktop.properties b/src/main/resources/strings/BriarDesktop.properties index d5ae8d74ef2f875b0eeee2dfa55de3901bc7a1a7..5c4135d1272fae16cdac0940002978367af8f50e 100644 --- a/src/main/resources/strings/BriarDesktop.properties +++ b/src/main/resources/strings/BriarDesktop.properties @@ -154,7 +154,8 @@ startup.database.creating=Creating Account... startup.database.opening=Decrypting Database... startup.database.migrating=Upgrading Database... startup.database.compacting=Compacting Database... -expiration.banner={0, plural, one {This is a test version of Briar that will expire in {0} day. Please update to a newer version in time.} other {This is a test version of Briar that will expire in {0} days. Please update to a newer version in time.}} +expiration.banner.part1={0, plural, =0 {This is a test version of Briar that will expire today.} one {This is a test version of Briar that will expire tomorrow.} other {This is a test version of Briar that will expire in {0} days.}} +expiration.banner.part2=Please update to a newer version in time. # Settings settings.title=Settings