diff --git a/lib/app.dart b/lib/app.dart index 14e339b..0025085 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -11,6 +11,7 @@ import 'package:twonly/src/views/components/app_outdated.dart'; import 'package:twonly/src/views/home.view.dart'; import 'package:twonly/src/views/onboarding/onboarding.view.dart'; import 'package:twonly/src/views/onboarding/register.view.dart'; +import 'package:twonly/src/views/settings/backup/twonly_safe_backup.view.dart'; import 'package:twonly/src/views/updates/62_database_migration.view.dart'; class App extends StatefulWidget { @@ -181,9 +182,17 @@ class _AppMainWidgetState extends State { if (_showDatabaseMigration) { child = const DatabaseMigrationView(); } else if (_isUserCreated) { - child = HomeView( - initialPage: widget.initialPage, - ); + if (gUser.twonlySafeBackup == null) { + child = TwonlyIdentityBackupView( + callBack: () { + setState(() {}); + }, + ); + } else { + child = HomeView( + initialPage: widget.initialPage, + ); + } } else if (_showOnboarding) { child = OnboardingView( callbackOnSuccess: () => setState(() { diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 9b1026f..4e91f4c 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -311,6 +311,7 @@ "backupLastBackupSize": "Backup-Größe", "backupLastBackupResult": "Ergebnis", "deleteBackupTitle": "Bist du sicher?", + "backupNoPasswordRecovery": "Aufgrund des Sicherheitssystems von twonly gibt es (derzeit) keine Funktion zur Wiederherstellung des Passworts. Daher musst du dir dein Passwort merken oder, besser noch, aufschreiben.", "deleteBackupBody": "Ohne ein Backup kannst du dein Benutzerkonto nicht wiederherstellen.", "backupData": "Daten-Backup", "backupDataDesc": "Das Daten-Backup enthält neben deiner twonly-Identität auch alle deine Mediendateien. Dieses Backup ist ebenfalls verschlüsselt, wird jedoch lokal gespeichert. Du musst es dann manuell auf deinen Laptop oder ein Gerät deiner Wahl kopieren.", @@ -318,18 +319,19 @@ "backupInsecurePasswordDesc": "Das gewählte Passwort ist sehr unsicher und kann daher leicht von Angreifern erraten werden. Bitte wähle ein sicheres Passwort.", "backupInsecurePasswordOk": "Trotzdem fortfahren", "backupInsecurePasswordCancel": "Erneut versuchen", - "backupTwonlySafeLongDesc": "twonly hat keine zentralen Benutzerkonten. Während der Installation wird ein Schlüsselpaar erstellt, das aus einem öffentlichen und einem privaten Schlüssel besteht. Der private Schlüssel wird nur auf deinem Gerät gespeichert, um ihn vor unbefugtem Zugriff zu schützen. Der öffentliche Schlüssel wird auf den Server hochgeladen und mit deinem gewählten Benutzernamen verknüpft, damit andere dich finden können.\n\ntwonly Safe erstellt regelmäßig ein verschlüsseltes, anonymes Backup deines privaten Schlüssels zusammen mit deinen Kontakten und Einstellungen. Dein Benutzername und das gewählte Passwort reichen aus, um diese Daten auf einem anderen Gerät wiederherzustellen.", - "backupSelectStrongPassword": "Wähle ein sicheres Passwort. Dies ist erforderlich, wenn du dein twonly Safe-Backup wiederherstellen möchtest.", + "backupTwonlySafeLongDesc": "twonly hat keine zentralen Benutzerkonten. Während der Installation wird ein Schlüsselpaar erstellt, das aus einem öffentlichen und einem privaten Schlüssel besteht. Der private Schlüssel wird nur auf deinem Gerät gespeichert, um ihn vor unbefugtem Zugriff zu schützen. Der öffentliche Schlüssel wird auf den Server hochgeladen und mit deinem gewählten Benutzernamen verknüpft, damit andere dich finden können.\n\ntwonly Backup erstellt regelmäßig ein verschlüsseltes, anonymes Backup deines privaten Schlüssels zusammen mit deinen Kontakten und Einstellungen. Dein Benutzername und das gewählte Passwort reichen aus, um diese Daten auf einem anderen Gerät wiederherzustellen.", + "backupSelectStrongPassword": "Wähle ein sicheres Passwort. Dies ist erforderlich, wenn du dein twonly Backup wiederherstellen möchtest.", "password": "Passwort", "passwordRepeated": "Passwort wiederholen", "passwordRepeatedNotEqual": "Passwörter stimmen nicht überein.", "backupPasswordRequirement": "Das Passwort muss mindestens 8 Zeichen lang sein.", "backupExpertSettings": "Experteneinstellungen", "backupEnableBackup": "Automatische Sicherung aktivieren", - "backupOwnServerDesc": "Speichere dein twonly Safe-Backups auf einem Server deiner Wahl.", + "backupOwnServerDesc": "Speichere dein twonly Backup auf einem Server deiner Wahl.", "backupUseOwnServer": "Server verwenden", "backupResetServer": "Standardserver verwenden", "backupTwonlySaveNow": "Jetzt speichern", + "backupChangePassword": "Password ändern", "inviteFriends": "Freunde einladen", "inviteFriendsShareBtn": "Teilen", "inviteFriendsShareText": "Wechseln wir zu twonly: {url}", diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 795dfb6..8b565d0 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -457,6 +457,7 @@ "backupFailed": "Failed", "backupSuccess": "Success", "backupTwonlySafeDesc": "Back up your twonly identity, as this is the only way to restore your account if you uninstall the app or lose your phone.", + "backupNoPasswordRecovery": "Due to twonly's security system, there is (currently) no password recovery function. Therefore, you must remember your password or, better yet, write it down.", "backupServer": "Server", "backupMaxBackupSize": "max. backup size", "backupStorageRetention": "Storage retention", @@ -471,20 +472,21 @@ "backupInsecurePasswordDesc": "The chosen password is very insecure and can therefore easily be guessed by attackers. Please choose a secure password.", "backupInsecurePasswordOk": "Continue anyway", "backupInsecurePasswordCancel": "Try again", - "backupTwonlySafeLongDesc": "twonly does not have any central user accounts. A key pair is created during installation, which consists of a public and a private key. The private key is only stored on your device to protect it from unauthorized access. The public key is uploaded to the server and linked to your chosen username so that others can find you.\n\ntwonly Safe regularly creates an encrypted, anonymous backup of your private key together with your contacts and settings. Your username and chosen password are enough to restore this data on another device.", - "backupSelectStrongPassword": "Choose a secure password. This is required if you want to restore your twonly Safe backup.", + "backupTwonlySafeLongDesc": "twonly does not have any central user accounts. A key pair is created during installation, which consists of a public and a private key. The private key is only stored on your device to protect it from unauthorized access. The public key is uploaded to the server and linked to your chosen username so that others can find you.\n\ntwonly Backup regularly creates an encrypted, anonymous backup of your private key together with your contacts and settings. Your username and chosen password are enough to restore this data on another device.", + "backupSelectStrongPassword": "Choose a secure password. This is required if you want to restore your twonly Backup.", "password": "Password", "passwordRepeated": "Repeat password", "passwordRepeatedNotEqual": "Passwords do not match.", "backupPasswordRequirement": "Password must be at least 8 characters long.", "backupExpertSettings": "Expert settings", "backupEnableBackup": "Activate automatic backup", - "backupOwnServerDesc": "Save your twonly safe backups at twonly or on any server of your choice.", + "backupOwnServerDesc": "Save your twonly Backup at twonly or on any server of your choice.", "backupUseOwnServer": "Use server", "backupResetServer": "Use standard server", "backupTwonlySaveNow": "Save now", + "backupChangePassword": "Change password", "twonlySafeRecoverTitle": "Recovery", - "twonlySafeRecoverDesc": "If you have created a backup with twonly Safe, you can restore it here.", + "twonlySafeRecoverDesc": "If you have created a backup with twonly Backup, you can restore it here.", "twonlySafeRecoverBtn": "Restore backup", "inviteFriends": "Invite your friends", "inviteFriendsShareBtn": "Share", diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 90fd5c2..dab9c79 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -1844,6 +1844,12 @@ abstract class AppLocalizations { /// **'Back up your twonly identity, as this is the only way to restore your account if you uninstall the app or lose your phone.'** String get backupTwonlySafeDesc; + /// No description provided for @backupNoPasswordRecovery. + /// + /// In en, this message translates to: + /// **'Due to twonly\'s security system, there is (currently) no password recovery function. Therefore, you must remember your password or, better yet, write it down.'** + String get backupNoPasswordRecovery; + /// No description provided for @backupServer. /// /// In en, this message translates to: @@ -1931,13 +1937,13 @@ abstract class AppLocalizations { /// No description provided for @backupTwonlySafeLongDesc. /// /// In en, this message translates to: - /// **'twonly does not have any central user accounts. A key pair is created during installation, which consists of a public and a private key. The private key is only stored on your device to protect it from unauthorized access. The public key is uploaded to the server and linked to your chosen username so that others can find you.\n\ntwonly Safe regularly creates an encrypted, anonymous backup of your private key together with your contacts and settings. Your username and chosen password are enough to restore this data on another device.'** + /// **'twonly does not have any central user accounts. A key pair is created during installation, which consists of a public and a private key. The private key is only stored on your device to protect it from unauthorized access. The public key is uploaded to the server and linked to your chosen username so that others can find you.\n\ntwonly Backup regularly creates an encrypted, anonymous backup of your private key together with your contacts and settings. Your username and chosen password are enough to restore this data on another device.'** String get backupTwonlySafeLongDesc; /// No description provided for @backupSelectStrongPassword. /// /// In en, this message translates to: - /// **'Choose a secure password. This is required if you want to restore your twonly Safe backup.'** + /// **'Choose a secure password. This is required if you want to restore your twonly Backup.'** String get backupSelectStrongPassword; /// No description provided for @password. @@ -1979,7 +1985,7 @@ abstract class AppLocalizations { /// No description provided for @backupOwnServerDesc. /// /// In en, this message translates to: - /// **'Save your twonly safe backups at twonly or on any server of your choice.'** + /// **'Save your twonly Backup at twonly or on any server of your choice.'** String get backupOwnServerDesc; /// No description provided for @backupUseOwnServer. @@ -2000,6 +2006,12 @@ abstract class AppLocalizations { /// **'Save now'** String get backupTwonlySaveNow; + /// No description provided for @backupChangePassword. + /// + /// In en, this message translates to: + /// **'Change password'** + String get backupChangePassword; + /// No description provided for @twonlySafeRecoverTitle. /// /// In en, this message translates to: @@ -2009,7 +2021,7 @@ abstract class AppLocalizations { /// No description provided for @twonlySafeRecoverDesc. /// /// In en, this message translates to: - /// **'If you have created a backup with twonly Safe, you can restore it here.'** + /// **'If you have created a backup with twonly Backup, you can restore it here.'** String get twonlySafeRecoverDesc; /// No description provided for @twonlySafeRecoverBtn. diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index e41102a..f372df6 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -973,6 +973,10 @@ class AppLocalizationsDe extends AppLocalizations { String get backupTwonlySafeDesc => 'Sichere deine twonly-Identität, da dies die einzige Möglichkeit ist, dein Konto wiederherzustellen, wenn du die App deinstallierst oder dein Handy verlierst.'; + @override + String get backupNoPasswordRecovery => + 'Aufgrund des Sicherheitssystems von twonly gibt es (derzeit) keine Funktion zur Wiederherstellung des Passworts. Daher musst du dir dein Passwort merken oder, besser noch, aufschreiben.'; + @override String get backupServer => 'Server'; @@ -1020,11 +1024,11 @@ class AppLocalizationsDe extends AppLocalizations { @override String get backupTwonlySafeLongDesc => - 'twonly hat keine zentralen Benutzerkonten. Während der Installation wird ein Schlüsselpaar erstellt, das aus einem öffentlichen und einem privaten Schlüssel besteht. Der private Schlüssel wird nur auf deinem Gerät gespeichert, um ihn vor unbefugtem Zugriff zu schützen. Der öffentliche Schlüssel wird auf den Server hochgeladen und mit deinem gewählten Benutzernamen verknüpft, damit andere dich finden können.\n\ntwonly Safe erstellt regelmäßig ein verschlüsseltes, anonymes Backup deines privaten Schlüssels zusammen mit deinen Kontakten und Einstellungen. Dein Benutzername und das gewählte Passwort reichen aus, um diese Daten auf einem anderen Gerät wiederherzustellen.'; + 'twonly hat keine zentralen Benutzerkonten. Während der Installation wird ein Schlüsselpaar erstellt, das aus einem öffentlichen und einem privaten Schlüssel besteht. Der private Schlüssel wird nur auf deinem Gerät gespeichert, um ihn vor unbefugtem Zugriff zu schützen. Der öffentliche Schlüssel wird auf den Server hochgeladen und mit deinem gewählten Benutzernamen verknüpft, damit andere dich finden können.\n\ntwonly Backup erstellt regelmäßig ein verschlüsseltes, anonymes Backup deines privaten Schlüssels zusammen mit deinen Kontakten und Einstellungen. Dein Benutzername und das gewählte Passwort reichen aus, um diese Daten auf einem anderen Gerät wiederherzustellen.'; @override String get backupSelectStrongPassword => - 'Wähle ein sicheres Passwort. Dies ist erforderlich, wenn du dein twonly Safe-Backup wiederherstellen möchtest.'; + 'Wähle ein sicheres Passwort. Dies ist erforderlich, wenn du dein twonly Backup wiederherstellen möchtest.'; @override String get password => 'Passwort'; @@ -1047,7 +1051,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get backupOwnServerDesc => - 'Speichere dein twonly Safe-Backups auf einem Server deiner Wahl.'; + 'Speichere dein twonly Backup auf einem Server deiner Wahl.'; @override String get backupUseOwnServer => 'Server verwenden'; @@ -1058,12 +1062,15 @@ class AppLocalizationsDe extends AppLocalizations { @override String get backupTwonlySaveNow => 'Jetzt speichern'; + @override + String get backupChangePassword => 'Password ändern'; + @override String get twonlySafeRecoverTitle => 'Recovery'; @override String get twonlySafeRecoverDesc => - 'If you have created a backup with twonly Safe, you can restore it here.'; + 'If you have created a backup with twonly Backup, you can restore it here.'; @override String get twonlySafeRecoverBtn => 'Restore backup'; diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 71ffab8..db0c3cc 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -967,6 +967,10 @@ class AppLocalizationsEn extends AppLocalizations { String get backupTwonlySafeDesc => 'Back up your twonly identity, as this is the only way to restore your account if you uninstall the app or lose your phone.'; + @override + String get backupNoPasswordRecovery => + 'Due to twonly\'s security system, there is (currently) no password recovery function. Therefore, you must remember your password or, better yet, write it down.'; + @override String get backupServer => 'Server'; @@ -1014,11 +1018,11 @@ class AppLocalizationsEn extends AppLocalizations { @override String get backupTwonlySafeLongDesc => - 'twonly does not have any central user accounts. A key pair is created during installation, which consists of a public and a private key. The private key is only stored on your device to protect it from unauthorized access. The public key is uploaded to the server and linked to your chosen username so that others can find you.\n\ntwonly Safe regularly creates an encrypted, anonymous backup of your private key together with your contacts and settings. Your username and chosen password are enough to restore this data on another device.'; + 'twonly does not have any central user accounts. A key pair is created during installation, which consists of a public and a private key. The private key is only stored on your device to protect it from unauthorized access. The public key is uploaded to the server and linked to your chosen username so that others can find you.\n\ntwonly Backup regularly creates an encrypted, anonymous backup of your private key together with your contacts and settings. Your username and chosen password are enough to restore this data on another device.'; @override String get backupSelectStrongPassword => - 'Choose a secure password. This is required if you want to restore your twonly Safe backup.'; + 'Choose a secure password. This is required if you want to restore your twonly Backup.'; @override String get password => 'Password'; @@ -1041,7 +1045,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get backupOwnServerDesc => - 'Save your twonly safe backups at twonly or on any server of your choice.'; + 'Save your twonly Backup at twonly or on any server of your choice.'; @override String get backupUseOwnServer => 'Use server'; @@ -1052,12 +1056,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get backupTwonlySaveNow => 'Save now'; + @override + String get backupChangePassword => 'Change password'; + @override String get twonlySafeRecoverTitle => 'Recovery'; @override String get twonlySafeRecoverDesc => - 'If you have created a backup with twonly Safe, you can restore it here.'; + 'If you have created a backup with twonly Backup, you can restore it here.'; @override String get twonlySafeRecoverBtn => 'Restore backup'; diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 3683f2d..7de2f52 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -311,7 +311,9 @@ class ApiService { } if (res.error == ErrorCode.NewDeviceRegistered) { globalCallbackNewDeviceRegistered(); - Log.error('Device is disabled, as a newer device restore twonly Safe.'); + Log.error( + 'Device is disabled, as a newer device restore twonly Backup.', + ); appIsOutdated = true; await close(() {}); return Result.error(ErrorCode.InternalError); diff --git a/lib/src/services/twonly_safe/create_backup.twonly_safe.dart b/lib/src/services/twonly_safe/create_backup.twonly_safe.dart index 80dd6e3..21212ec 100644 --- a/lib/src/services/twonly_safe/create_backup.twonly_safe.dart +++ b/lib/src/services/twonly_safe/create_backup.twonly_safe.dart @@ -40,7 +40,7 @@ Future performTwonlySafeBackup({bool force = false}) async { } } - Log.info('Starting new twonly Safe-Backup!'); + Log.info('Starting new twonly Backup!'); final baseDir = (await getApplicationSupportDirectory()).path; @@ -161,7 +161,7 @@ Future performTwonlySafeBackup({bool force = false}) async { await encryptedBackupBytesFile.writeAsBytes(encryptedBackupBytes); Log.info( - 'Create twonly Safe backup with a size of ${encryptedBackupBytes.length} bytes.', + 'Create twonly Backup with a size of ${encryptedBackupBytes.length} bytes.', ); if (gUser.backupServer != null) { @@ -187,7 +187,7 @@ Future performTwonlySafeBackup({bool force = false}) async { }, ); if (await FileDownloader().enqueue(task)) { - Log.info('Starting upload from twonly Safe backup.'); + Log.info('Starting upload from twonly Backup.'); await updateUserdata((user) { user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.pending; user.twonlySafeBackup!.lastBackupDone = DateTime.now(); @@ -196,7 +196,7 @@ Future performTwonlySafeBackup({bool force = false}) async { }); gUpdateBackupView(); } else { - Log.error('Error starting UploadTask for twonly Safe.'); + Log.error('Error starting UploadTask for twonly Backup.'); } } @@ -204,7 +204,7 @@ Future handleBackupStatusUpdate(TaskStatusUpdate update) async { if (update.status == TaskStatus.failed || update.status == TaskStatus.canceled) { Log.error( - 'twonly Safe upload failed. ${update.responseStatusCode} ${update.responseBody} ${update.responseHeaders} ${update.exception}', + 'twonly Backup upload failed. ${update.responseStatusCode} ${update.responseBody} ${update.responseHeaders} ${update.exception}', ); await updateUserdata((user) { if (user.twonlySafeBackup != null) { @@ -214,7 +214,7 @@ Future handleBackupStatusUpdate(TaskStatusUpdate update) async { }); } else if (update.status == TaskStatus.complete) { Log.error( - 'twonly Safe uploaded with status code ${update.responseStatusCode}', + 'twonly Backup uploaded with status code ${update.responseStatusCode}', ); await updateUserdata((user) { if (user.twonlySafeBackup != null) { diff --git a/lib/src/views/onboarding/recover.view.dart b/lib/src/views/onboarding/recover.view.dart index 4590aef..e9b061c 100644 --- a/lib/src/views/onboarding/recover.view.dart +++ b/lib/src/views/onboarding/recover.view.dart @@ -60,13 +60,13 @@ class _BackupRecoveryViewState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('twonly Safe ${context.lang.twonlySafeRecoverTitle}'), + title: Text('twonly Backup ${context.lang.twonlySafeRecoverTitle}'), actions: [ IconButton( onPressed: () async { await showAlertDialog( context, - 'twonly Safe', + 'twonly Backup', context.lang.backupTwonlySafeLongDesc, ); }, diff --git a/lib/src/views/onboarding/register.view.dart b/lib/src/views/onboarding/register.view.dart index 4a1b97a..e652f63 100644 --- a/lib/src/views/onboarding/register.view.dart +++ b/lib/src/views/onboarding/register.view.dart @@ -166,30 +166,6 @@ class _RegisterViewState extends State { ), textAlign: TextAlign.center, ), - // const SizedBox(height: 5), - // Center( - // child: Padding( - // padding: EdgeInsets.only(left: 10, right: 10), - // child: Text( - // context.lang.registerUsernameLimits, - // textAlign: TextAlign.center, - // style: const TextStyle(fontSize: 9), - // ), - // ), - // ), - // const SizedBox(height: 30), - // Center( - // child: Text( - // context.lang.registerTwonlyCodeText, - // textAlign: TextAlign.center, - // ), - // ), - // const SizedBox(height: 10), - // TextField( - // controller: inviteCodeController, - // decoration: - // getInputDecoration(context.lang.registerTwonlyCodeLabel), - // ), const SizedBox(height: 30), Column( children: [ diff --git a/lib/src/views/settings/backup/backup.view.dart b/lib/src/views/settings/backup/backup.view.dart index 92a42eb..28417bf 100644 --- a/lib/src/views/settings/backup/backup.view.dart +++ b/lib/src/views/settings/backup/backup.view.dart @@ -3,10 +3,8 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/model/json/userdata.dart'; -import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart'; import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/settings/backup/twonly_safe_backup.view.dart'; void Function() gUpdateBackupView = () {}; @@ -25,8 +23,6 @@ BackupServer defaultBackupServer = BackupServer( ); class _BackupViewState extends State { - TwonlySafeBackup? twonlySafeBackup; - BackupServer backupServer = defaultBackupServer; bool isLoading = false; int activePageIdx = 0; @@ -47,11 +43,6 @@ class _BackupViewState extends State { } Future initAsync() async { - twonlySafeBackup = gUser.twonlySafeBackup; - backupServer = defaultBackupServer; - if (gUser.backupServer != null) { - backupServer = gUser.backupServer!; - } setState(() {}); } @@ -68,8 +59,25 @@ class _BackupViewState extends State { } } + Future changeTwonlySafePassword() async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const TwonlyIdentityBackupView( + isPasswordChangeOnly: true, + ); + }, + ), + ); + setState(() { + // gUser was updated + }); + } + @override Widget build(BuildContext context) { + final backupServer = gUser.backupServer ?? defaultBackupServer; return Scaffold( appBar: AppBar( title: Text(context.lang.settingsBackup), @@ -83,10 +91,13 @@ class _BackupViewState extends State { }, children: [ BackupOption( - title: 'twonly Safe', + title: 'twonly Backup', description: context.lang.backupTwonlySafeDesc, - autoBackupEnabled: twonlySafeBackup != null, - child: (twonlySafeBackup == null) + bottomButton: FilledButton( + onPressed: changeTwonlySafePassword, + child: Text(context.lang.backupChangePassword), + ), + child: (gUser.twonlySafeBackup == null) ? null : Column( children: [ @@ -114,16 +125,20 @@ class _BackupViewState extends State { context.lang.backupLastBackupDate, formatDateTime( context, - twonlySafeBackup!.lastBackupDone, + gUser.twonlySafeBackup!.lastBackupDone, ) ), ( context.lang.backupLastBackupSize, - formatBytes(twonlySafeBackup!.lastBackupSize) + formatBytes( + gUser.twonlySafeBackup!.lastBackupSize, + ) ), ( context.lang.backupLastBackupResult, - backupStatus(twonlySafeBackup!.backupUploadState) + backupStatus( + gUser.twonlySafeBackup!.backupUploadState, + ) ), ].map((pair) { return TableRow( @@ -134,8 +149,9 @@ class _BackupViewState extends State { ), TableCell( child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 4), + padding: const EdgeInsets.symmetric( + vertical: 4, + ), child: Text( pair.$2, textAlign: TextAlign.right, @@ -148,7 +164,7 @@ class _BackupViewState extends State { ], ), const SizedBox(height: 10), - FilledButton( + OutlinedButton( onPressed: isLoading ? null : () async { @@ -164,37 +180,10 @@ class _BackupViewState extends State { ), ], ), - onTap: () async { - if (twonlySafeBackup != null) { - final disable = await showAlertDialog( - context, - context.lang.deleteBackupTitle, - context.lang.deleteBackupBody, - ); - if (disable) { - await disableTwonlySafe(); - } - } else { - setState(() { - isLoading = true; - }); - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const TwonlyIdentityBackupView(); - }, - ), - ); - } - await initAsync(); - }, ), BackupOption( title: '${context.lang.backupData} (Coming Soon)', description: context.lang.backupDataDesc, - autoBackupEnabled: false, - onTap: null, ), ], ), @@ -209,7 +198,7 @@ class _BackupViewState extends State { items: [ const BottomNavigationBarItem( icon: FaIcon(FontAwesomeIcons.vault, size: 17), - label: 'twonly Safe', + label: 'twonly Backup', ), BottomNavigationBarItem( icon: const FaIcon(FontAwesomeIcons.boxArchive, size: 17), @@ -236,51 +225,35 @@ class BackupOption extends StatelessWidget { const BackupOption({ required this.title, required this.description, - required this.autoBackupEnabled, - required this.onTap, + this.bottomButton, super.key, this.child, }); final String title; final String description; final Widget? child; - final bool autoBackupEnabled; - final void Function()? onTap; + final Widget? bottomButton; @override Widget build(BuildContext context) { - return GestureDetector( - onTap: autoBackupEnabled ? null : onTap, - child: Card( - margin: const EdgeInsets.all(16), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: - const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - Text(description), - const SizedBox(height: 8), - if (child != null) child! else Container(), - Expanded(child: Container()), - Center( - child: autoBackupEnabled - ? OutlinedButton( - onPressed: onTap, - child: Text(context.lang.disable), - ) - : FilledButton( - onPressed: onTap, - child: Text(context.lang.enable), - ), - ), - ], - ), + return Card( + margin: const EdgeInsets.all(16), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Text(description), + const SizedBox(height: 8), + if (child != null) child! else Container(), + Expanded(child: Container()), + if (bottomButton != null) Center(child: bottomButton), + ], ), ), ); diff --git a/lib/src/views/settings/backup/twonly_safe_backup.view.dart b/lib/src/views/settings/backup/twonly_safe_backup.view.dart index 7d7915f..64f7ba1 100644 --- a/lib/src/views/settings/backup/twonly_safe_backup.view.dart +++ b/lib/src/views/settings/backup/twonly_safe_backup.view.dart @@ -8,7 +8,16 @@ import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/settings/backup/twonly_safe_server.view.dart'; class TwonlyIdentityBackupView extends StatefulWidget { - const TwonlyIdentityBackupView({super.key}); + const TwonlyIdentityBackupView({ + this.isPasswordChangeOnly = false, + this.callBack, + super.key, + }); + + // in case a callback is defined the callback + // is called instead of the Navigator.pop() + final VoidCallback? callBack; + final bool isPasswordChangeOnly; @override State createState() => @@ -56,148 +65,165 @@ class _TwonlyIdentityBackupViewState extends State { isLoading = false; }); - Navigator.pop(context); + if (widget.callBack != null) { + widget.callBack!(); + } else { + Navigator.pop(context); + } } @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('twonly Safe'), - actions: [ - IconButton( - onPressed: () async { - await showAlertDialog( - context, - 'twonly Safe', - context.lang.backupTwonlySafeLongDesc, - ); - }, - icon: const FaIcon(FontAwesomeIcons.circleInfo), - iconSize: 18, - ), - ], - ), - body: Padding( - padding: - const EdgeInsetsGeometry.symmetric(vertical: 40, horizontal: 40), - child: ListView( - children: [ - Text( - context.lang.backupSelectStrongPassword, - textAlign: TextAlign.center, - ), - const SizedBox(height: 30), - Stack( - children: [ - TextField( - controller: passwordCtrl, - onChanged: (value) { - setState(() {}); - }, - style: const TextStyle(fontSize: 17), - obscureText: obscureText, - decoration: getInputDecoration( - context, - context.lang.password, - ), - ), - Positioned( - right: 0, - top: 0, - bottom: 0, - child: IconButton( - onPressed: () { - setState(() { - obscureText = !obscureText; - }); - }, - icon: FaIcon( - obscureText - ? FontAwesomeIcons.eye - : FontAwesomeIcons.eyeSlash, - size: 16, - ), - ), - ), - ], - ), - Padding( - padding: const EdgeInsetsGeometry.all(5), - child: Text( - context.lang.backupPasswordRequirement, - style: TextStyle( - fontSize: 13, - color: (passwordCtrl.text.length < 8 && - passwordCtrl.text.isNotEmpty) - ? Colors.red - : Colors.transparent, - ), - ), - ), - const SizedBox(height: 5), - TextField( - controller: repeatedPasswordCtrl, - onChanged: (value) { - setState(() {}); + return GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Scaffold( + appBar: AppBar( + title: const Text('twonly Backup'), + actions: [ + IconButton( + onPressed: () async { + await showAlertDialog( + context, + 'twonly Backup', + context.lang.backupTwonlySafeLongDesc, + ); }, - style: const TextStyle(fontSize: 17), - obscureText: true, - decoration: getInputDecoration( - context, - context.lang.passwordRepeated, - ), - ), - Padding( - padding: const EdgeInsetsGeometry.all(5), - child: Text( - context.lang.passwordRepeatedNotEqual, - style: TextStyle( - fontSize: 13, - color: (passwordCtrl.text != repeatedPasswordCtrl.text && - repeatedPasswordCtrl.text.isNotEmpty) - ? Colors.red - : Colors.transparent, - ), - ), - ), - const SizedBox(height: 10), - Center( - child: OutlinedButton( - onPressed: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const TwonlySafeServerView(); - }, - ), - ); - }, - child: Text(context.lang.backupExpertSettings), - ), - ), - const SizedBox(height: 10), - Center( - child: FilledButton.icon( - onPressed: (!isLoading && - (passwordCtrl.text == repeatedPasswordCtrl.text && - passwordCtrl.text.length >= 8 || - kDebugMode)) - ? onPressedEnableTwonlySafe - : null, - icon: isLoading - ? const SizedBox( - height: 12, - width: 12, - child: CircularProgressIndicator(strokeWidth: 1), - ) - : const Icon(Icons.lock_clock_rounded), - label: Text(context.lang.backupEnableBackup), - ), + icon: const FaIcon(FontAwesomeIcons.circleInfo), + iconSize: 18, ), ], ), + body: Padding( + padding: + const EdgeInsetsGeometry.symmetric(vertical: 40, horizontal: 40), + child: ListView( + children: [ + Text( + context.lang.backupSelectStrongPassword, + textAlign: TextAlign.center, + ), + const SizedBox(height: 30), + Stack( + children: [ + TextField( + controller: passwordCtrl, + onChanged: (value) { + setState(() {}); + }, + style: const TextStyle(fontSize: 17), + obscureText: obscureText, + decoration: getInputDecoration( + context, + context.lang.password, + ), + ), + Positioned( + right: 0, + top: 0, + bottom: 0, + child: IconButton( + onPressed: () { + setState(() { + obscureText = !obscureText; + }); + }, + icon: FaIcon( + obscureText + ? FontAwesomeIcons.eye + : FontAwesomeIcons.eyeSlash, + size: 16, + ), + ), + ), + ], + ), + Padding( + padding: const EdgeInsetsGeometry.all(5), + child: Text( + context.lang.backupPasswordRequirement, + style: TextStyle( + fontSize: 13, + color: (passwordCtrl.text.length < 8 && + passwordCtrl.text.isNotEmpty) + ? Colors.red + : Colors.transparent, + ), + ), + ), + const SizedBox(height: 5), + TextField( + controller: repeatedPasswordCtrl, + onChanged: (value) { + setState(() {}); + }, + style: const TextStyle(fontSize: 17), + obscureText: true, + decoration: getInputDecoration( + context, + context.lang.passwordRepeated, + ), + ), + Padding( + padding: const EdgeInsetsGeometry.all(5), + child: Text( + context.lang.passwordRepeatedNotEqual, + style: TextStyle( + fontSize: 13, + color: (passwordCtrl.text != repeatedPasswordCtrl.text && + repeatedPasswordCtrl.text.isNotEmpty) + ? Colors.red + : Colors.transparent, + ), + ), + ), + const SizedBox(height: 10), + Center( + child: OutlinedButton( + onPressed: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const TwonlySafeServerView(); + }, + ), + ); + }, + child: Text(context.lang.backupExpertSettings), + ), + ), + const SizedBox(height: 10), + Text( + context.lang.backupNoPasswordRecovery, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 12), + ), + const SizedBox(height: 10), + Center( + child: FilledButton.icon( + onPressed: (!isLoading && + (passwordCtrl.text == repeatedPasswordCtrl.text && + passwordCtrl.text.length >= 8 || + kDebugMode)) + ? onPressedEnableTwonlySafe + : null, + icon: isLoading + ? const SizedBox( + height: 12, + width: 12, + child: CircularProgressIndicator(strokeWidth: 1), + ) + : const Icon(Icons.lock_clock_rounded), + label: Text( + widget.isPasswordChangeOnly + ? context.lang.backupChangePassword + : context.lang.backupEnableBackup, + ), + ), + ), + ], + ), + ), ), ); } diff --git a/lib/src/views/settings/backup/twonly_safe_server.view.dart b/lib/src/views/settings/backup/twonly_safe_server.view.dart index a7c2cf8..c0e4bfd 100644 --- a/lib/src/views/settings/backup/twonly_safe_server.view.dart +++ b/lib/src/views/settings/backup/twonly_safe_server.view.dart @@ -107,7 +107,7 @@ class _TwonlySafeServerViewState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('twonly Safe Server'), + title: const Text('twonly Backup Server'), ), body: Padding( padding: const EdgeInsets.all(40), diff --git a/scripts/generate_proto.sh b/scripts/generate_proto.sh index ceb9d77..3824b77 100755 --- a/scripts/generate_proto.sh +++ b/scripts/generate_proto.sh @@ -7,7 +7,7 @@ if [ ! -f "pubspec.yaml" ]; then exit 1 fi -# Definitions for twonly Safe +# Definitions for twonly Backup GENERATED_DIR="./lib/src/model/protobuf/client/generated/" CLIENT_DIR="./lib/src/model/protobuf/client/"