From 2b157d0364a0e88f1c7fdce0dc92ddc156a74889 Mon Sep 17 00:00:00 2001
From: ialokim <ialokim@mailbox.org>
Date: Sat, 15 Jan 2022 12:58:26 +0100
Subject: [PATCH] add expiration banner and screen

---
 .../desktop/expiration/ExpirationBanner.kt    | 73 +++++++++++++++++++
 .../desktop/expiration/ExpirationUtils.kt     | 12 +++
 .../briar/desktop/login/ErrorScreen.kt        |  6 +-
 .../briar/desktop/login/ErrorSubViewModel.kt  |  2 +
 .../briar/desktop/login/StartupViewModel.kt   |  6 +-
 .../briarproject/briar/desktop/ui/BriarUi.kt  | 13 +++-
 .../briar/desktop/ui/Constants.kt             |  1 +
 .../resources/strings/BriarDesktop.properties |  3 +
 8 files changed, 110 insertions(+), 6 deletions(-)
 create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationBanner.kt
 create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationUtils.kt

diff --git a/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationBanner.kt b/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationBanner.kt
new file mode 100644
index 0000000000..9205a0e520
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationBanner.kt
@@ -0,0 +1,73 @@
+package org.briarproject.briar.desktop.expiration
+
+import androidx.compose.foundation.layout.Arrangement
+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.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Warning
+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
+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.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 {
+        ExpirationBanner {}
+    }
+}
+
+@Composable
+fun ExpirationBanner(onExpired: () -> Unit) {
+
+    var daysLeft by remember { mutableStateOf(0) }
+    LaunchedEffect(Unit) {
+        launch {
+            while (true) {
+                daysLeft = getDaysLeft()
+                if (isExpired()) {
+                    onExpired()
+                    break
+                }
+                delay(1.hours.inWholeMilliseconds)
+            }
+        }
+    }
+
+    ExpirationBanner(daysLeft)
+}
+
+@Composable
+fun ExpirationBanner(
+    daysLeft: Int,
+) = Surface(
+    color = MaterialTheme.colors.error,
+    modifier = Modifier.fillMaxWidth()
+) {
+    Row(
+        horizontalArrangement = Arrangement.spacedBy(8.dp),
+        verticalAlignment = Alignment.CenterVertically,
+        modifier = Modifier.padding(8.dp)
+    ) {
+        Icon(Icons.Filled.Warning, i18n("warning"))
+        Text(i18nP("expiration.banner", daysLeft))
+    }
+}
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationUtils.kt b/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationUtils.kt
new file mode 100644
index 0000000000..7a51a741e5
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/expiration/ExpirationUtils.kt
@@ -0,0 +1,12 @@
+package org.briarproject.briar.desktop.expiration
+
+import org.briarproject.briar.desktop.BuildData
+import java.time.Instant
+import java.time.temporal.ChronoUnit
+
+object ExpirationUtils {
+
+    fun getDaysLeft() = 90 - ChronoUnit.DAYS.between(Instant.ofEpochMilli(BuildData.GIT_TIME), Instant.now()).toInt()
+
+    fun isExpired() = getDaysLeft() <= 0
+}
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 dc7e70b570..871f581bf6 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt
@@ -56,9 +56,12 @@ 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(RegistrationSubViewModel.RegistrationError) }
+    var error: ErrorSubViewModel.Error by remember { mutableStateOf(ErrorSubViewModel.ExpirationError) }
 
     Row(horizontalArrangement = spacedBy(8.dp)) {
+        Button(onClick = { error = ErrorSubViewModel.ExpirationError }) {
+            Text("Expiration")
+        }
         Button(onClick = { error = RegistrationSubViewModel.RegistrationError }) {
             Text("Registration")
         }
@@ -100,6 +103,7 @@ 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) {
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 5f0c1dd228..e4129ebdea 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorSubViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorSubViewModel.kt
@@ -24,4 +24,6 @@ class ErrorSubViewModel(
     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 ecb09900eb..8cd391466f 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt
@@ -30,6 +30,7 @@ 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
@@ -60,8 +61,9 @@ constructor(
     private val _currentSubViewModel = mutableStateOf(decideSubViewModel())
     val currentSubViewModel = _currentSubViewModel.asState()
 
-    private fun decideSubViewModel() =
-        if (accountManager.accountExists()) makeLogin()
+    private fun decideSubViewModel(): SubViewModel =
+        if (isExpired()) makeError(ErrorSubViewModel.ExpirationError)
+        else if (accountManager.accountExists()) makeLogin()
         else makeRegistration()
 
     private fun makeLogin() = LoginSubViewModel(
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 ef1141c379..ffafaaf8c2 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
@@ -18,12 +18,15 @@
 
 package org.briarproject.briar.desktop.ui
 
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.window.FrameWindowScope
 import androidx.compose.ui.window.Window
@@ -35,6 +38,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager
 import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING
 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.StartupScreen
 import org.briarproject.briar.desktop.settings.SettingsViewModel
 import org.briarproject.briar.desktop.theme.BriarTheme
@@ -114,9 +118,12 @@ constructor(
             ) {
                 val settingsViewModel: SettingsViewModel = viewModel()
                 BriarTheme(isDarkTheme = settingsViewModel.isDarkMode.value) {
-                    when (screenState) {
-                        STARTUP -> StartupScreen()
-                        MAIN -> MainScreen(settingsViewModel)
+                    Column(Modifier.fillMaxSize()) {
+                        ExpirationBanner(onExpired = { screenState = STARTUP })
+                        when (screenState) {
+                            STARTUP -> StartupScreen()
+                            MAIN -> MainScreen(settingsViewModel)
+                        }
                     }
                 }
             }
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 d860f80b8f..67c1b749c3 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/ui/Constants.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/Constants.kt
@@ -21,6 +21,7 @@ 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 7b65487cb9..d5ae8d74ef 100644
--- a/src/main/resources/strings/BriarDesktop.properties
+++ b/src/main/resources/strings/BriarDesktop.properties
@@ -121,6 +121,7 @@ next=Next
 open=Open
 sorry=Sorry
 error=Error
+warning=Warning
 unsupported_feature=Unfortunately, this feature is not yet supported by Briar Desktop.
 
 # Startup screen
@@ -142,6 +143,7 @@ startup.error.decryption.text=Briar cannot check your password. Please try reboo
 startup.password_forgotten.button=I have forgotten my password
 startup.password_forgotten.title=Lost Password
 startup.password_forgotten.text=Your Briar account is stored encrypted on your device, not in the cloud, so we can't reset your password. Would you like to delete your account and start again?\n\nCaution: Your identities, contacts and messages will be permanently lost.
+startup.failed.expired=This software has expired. Thank you for testing!\n\nTo continue using Briar, please download the latest release. You will be able to continue using your account.
 startup.failed.registration=Briar was unable to create your account.\n\nPlease upgrade to the latest version and try again.
 startup.failed.clock_error=Briar was unable to start because your device's clock is wrong.\n\nPlease set your device's clock to the right time and try again.
 startup.failed.db_error=Briar was unable to open the database containing your account, your contacts and your messages.\n\nPlease check if Briar is already running on this device. Otherwise, upgrade to the latest version of the app and try again, or set up a new account by choosing 'I have forgotten my password' at the password prompt.
@@ -152,6 +154,7 @@ 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.}}
 
 # Settings
 settings.title=Settings
-- 
GitLab