From 8d8f7b2524906ca5ad6fe4015f100135af6ad227 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 28 Mar 2023 11:26:45 +0100 Subject: [PATCH] migrations UI --- .../app/views/database/DatabaseErrorView.kt | 126 ++++++++++-------- .../app/src/main/res/values/strings.xml | 6 + 2 files changed, 73 insertions(+), 59 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt index 623e6049f5..28859f3587 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt @@ -14,11 +14,13 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.simplex.app.* import chat.simplex.app.R import chat.simplex.app.model.AppPreferences +import chat.simplex.app.model.DBEncryptionConfig import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.app.views.usersettings.NotificationsMode @@ -40,45 +42,45 @@ fun DatabaseErrorView( var useKeychain by remember { mutableStateOf(appPreferences.storeDBPassphrase.get()) } val context = LocalContext.current val restoreDbFromBackup = remember { mutableStateOf(shouldShowRestoreDbButton(appPreferences, context)) } - val saveAndRunChatOnClick: () -> Unit = { + + fun callRunChat(confirmMigrations: MigrationConfirmation? = null) { + runChat(dbKey.value, confirmMigrations, chatDbStatus, progressIndicator, appPreferences) + } + + fun saveAndRunChatOnClick() { DatabaseUtils.setDatabaseKey(dbKey.value) storedDBKey = dbKey.value appPreferences.storeDBPassphrase.set(true) useKeychain = true appPreferences.initialRandomDBPassphrase.set(false) - runChat(dbKey.value, chatDbStatus, progressIndicator, appPreferences) + callRunChat() + } + + @Composable + fun DatabaseErrorDetails(@StringRes title: Int, content: @Composable ColumnScope.() -> Unit) { + Text( + generalGetString(title), + Modifier.padding(start = 16.dp, top = 16.dp, bottom = 24.dp), + style = MaterialTheme.typography.h1 + ) + SectionView(null, padding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), content) + } + + @Composable + fun fileNameText(dbFile: String) { + Text(String.format(generalGetString(R.string.file_with_path), dbFile)) + } + + @Composable + fun migrationsText(ms: List) { + Text(String.format(generalGetString(R.string.database_migrations), ms.joinToString(", "))) } -// val status = chatDbStatus.value -// val title = when (status) { -// is DBMigrationResult.OK -> "" -// is DBMigrationResult.ErrorNotADatabase -> if (useKeychain && !storedDBKey.isNullOrEmpty()) -// generalGetString(R.string.wrong_passphrase) -// else -// generalGetString(R.string.encrypted_database) -// is DBMigrationResult.ErrorMigration -> when (status.migrationError) { -// is MigrationError.Upgrade -> generalGetString(R.string.database_upgrade) -// is MigrationError.Downgrade -> generalGetString(R.string.database_downgrade) -// is MigrationError.Error -> generalGetString(R.string.incompatible_database_version) -// } -// is DBMigrationResult.ErrorSQL -> generalGetString(R.string.database_error) -// is DBMigrationResult.ErrorKeychain -> generalGetString(R.string.keychain_error) - // this can only happen if incorrect parameter is passed -// is DBMigrationResult.InvalidConfirmation -> "Invalid migration confirmation" -// is DBMigrationResult.Unknown -> generalGetString(R.string.database_error) -// null -> "" // should never be here -// } Column( Modifier.fillMaxSize().verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Center, ) { -// Text( -// title, -// Modifier.padding(start = 16.dp, top = 16.dp, bottom = 24.dp), -// style = MaterialTheme.typography.h1 -// ) -// SectionView(null, padding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)) { val buttonEnabled = validKey(dbKey.value) && !progressIndicator.value when (val status = chatDbStatus.value) { is DBMigrationResult.ErrorNotADatabase -> @@ -88,40 +90,49 @@ fun DatabaseErrorView( DatabaseKeyField(dbKey, buttonEnabled) { saveAndRunChatOnClick() } - SaveAndOpenButton(buttonEnabled, saveAndRunChatOnClick) + SaveAndOpenButton(buttonEnabled, ::saveAndRunChatOnClick) SectionSpacer() - Text(String.format(generalGetString(R.string.file_with_path), status.dbFile)) + fileNameText(status.dbFile) } } else { DatabaseErrorDetails(R.string.encrypted_database) { Text(generalGetString(R.string.database_passphrase_is_required)) - DatabaseKeyField(dbKey, buttonEnabled) { - if (useKeychain) saveAndRunChatOnClick() else runChat(dbKey.value, chatDbStatus, progressIndicator, appPreferences) - } if (useKeychain) { - SaveAndOpenButton(buttonEnabled, saveAndRunChatOnClick) + DatabaseKeyField(dbKey, buttonEnabled, ::saveAndRunChatOnClick) + SaveAndOpenButton(buttonEnabled, ::saveAndRunChatOnClick) } else { - OpenChatButton(buttonEnabled) { runChat(dbKey.value, chatDbStatus, progressIndicator, appPreferences) } + DatabaseKeyField(dbKey, buttonEnabled, { callRunChat() }) + OpenChatButton(buttonEnabled) { callRunChat() } } } } - is DBMigrationResult.ErrorMigration -> when (status.migrationError) { + is DBMigrationResult.ErrorMigration -> when (val err = status.migrationError) { is MigrationError.Upgrade -> DatabaseErrorDetails(R.string.database_upgrade) { - + TextButton({ callRunChat(MigrationConfirmation.YesUp) }, Modifier.align(Alignment.CenterHorizontally), enabled = !progressIndicator.value) { + Text(generalGetString(R.string.upgrade_and_open_chat)) + } + fileNameText(status.dbFile) + migrationsText(err.upMigrations.map { it.upName }) } is MigrationError.Downgrade -> DatabaseErrorDetails(R.string.database_downgrade) { - + Text(generalGetString(R.string.database_downgrade_warning), fontWeight = FontWeight.Bold) + TextButton({ callRunChat(MigrationConfirmation.YesUpDown) }, Modifier.align(Alignment.CenterHorizontally), enabled = !progressIndicator.value) { + Text(generalGetString(R.string.downgrade_and_open_chat)) + } + fileNameText(status.dbFile) + migrationsText(err.downMigrations) } is MigrationError.Error -> DatabaseErrorDetails(R.string.incompatible_database_version) { - + fileNameText(status.dbFile) + Text(String.format(generalGetString(R.string.error_with_info), mtrErrorDescription(err.mtrError))) } } is DBMigrationResult.ErrorSQL -> DatabaseErrorDetails(R.string.database_error) { - Text(String.format(generalGetString(R.string.file_with_path), status.dbFile)) + fileNameText(status.dbFile) Text(String.format(generalGetString(R.string.error_with_info), status.migrationSQLError)) } is DBMigrationResult.ErrorKeychain -> @@ -153,7 +164,6 @@ fun DatabaseErrorView( ) } } -// } } if (progressIndicator.value) { Box( @@ -171,18 +181,9 @@ fun DatabaseErrorView( } } -@Composable -private fun DatabaseErrorDetails(@StringRes title: Int, content: @Composable ColumnScope.() -> Unit) { - Text( - generalGetString(title), - Modifier.padding(start = 16.dp, top = 16.dp, bottom = 24.dp), - style = MaterialTheme.typography.h1 - ) - SectionView(null, padding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), content) -} - private fun runChat( dbKey: String, + confirmMigrations: MigrationConfirmation? = null, chatDbStatus: State, progressIndicator: MutableState, prefs: AppPreferences @@ -191,7 +192,7 @@ private fun runChat( if (progressIndicator.value) return@launch progressIndicator.value = true try { - SimplexApp.context.initChatController(dbKey) + SimplexApp.context.initChatController(dbKey, confirmMigrations) } catch (e: Exception) { Log.d(TAG, "initializeChat ${e.stackTraceToString()}") } @@ -204,18 +205,17 @@ private fun runChat( NotificationsMode.PERIODIC.name -> SimplexApp.context.schedulePeriodicWakeUp() } } - is DBMigrationResult.ErrorNotADatabase -> { + is DBMigrationResult.ErrorNotADatabase -> AlertManager.shared.showAlertMsg(generalGetString(R.string.wrong_passphrase_title), generalGetString(R.string.enter_correct_passphrase)) - } - is DBMigrationResult.ErrorSQL -> { + is DBMigrationResult.ErrorSQL -> AlertManager.shared.showAlertMsg(generalGetString(R.string.database_error), status.migrationSQLError) - } - is DBMigrationResult.ErrorKeychain -> { + is DBMigrationResult.ErrorKeychain -> AlertManager.shared.showAlertMsg(generalGetString(R.string.keychain_error)) - } - is DBMigrationResult.Unknown -> { + is DBMigrationResult.Unknown -> AlertManager.shared.showAlertMsg(generalGetString(R.string.unknown_error), status.json) - } + is DBMigrationResult.InvalidConfirmation -> + AlertManager.shared.showAlertMsg(generalGetString(R.string.invalid_migration_confirmation)) + is DBMigrationResult.ErrorMigration -> {} null -> {} } } @@ -245,6 +245,14 @@ private fun restoreDb(restoreDbFromBackup: MutableState, prefs: AppPref } } +private fun mtrErrorDescription(err: MTRError): String = + when (err) { + is MTRError.NoDown -> + String.format(generalGetString(R.string.mtr_error_no_down_migration), err.dbMigrations.joinToString(", ")) + is MTRError.Different -> + String.format(generalGetString(R.string.mtr_error_different), err.appMigration, err.dbMigration) + } + @Composable private fun DatabaseKeyField(text: MutableState, enabled: Boolean, onClick: (() -> Unit)? = null) { PassphraseField( diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 50566c333e..e7f7ec87e7 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -829,6 +829,12 @@ Incompatible database version Confirm database upgrades "Invalid migration confirmation" + Upgrade and open chat + Downgrade and open chat + database version is newer than the app, but no down migration for: %s + different migration in the app/database: %s / %s + Migrations: %s + "Warning: you may lose some data!" Chat is stopped