twonly Safe is now mandatory

This commit is contained in:
otsmr 2025-10-29 11:13:13 +01:00
parent 404aee1e18
commit d40e33b247
14 changed files with 290 additions and 274 deletions

View file

@ -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/home.view.dart';
import 'package:twonly/src/views/onboarding/onboarding.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/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'; import 'package:twonly/src/views/updates/62_database_migration.view.dart';
class App extends StatefulWidget { class App extends StatefulWidget {
@ -181,9 +182,17 @@ class _AppMainWidgetState extends State<AppMainWidget> {
if (_showDatabaseMigration) { if (_showDatabaseMigration) {
child = const DatabaseMigrationView(); child = const DatabaseMigrationView();
} else if (_isUserCreated) { } else if (_isUserCreated) {
child = HomeView( if (gUser.twonlySafeBackup == null) {
initialPage: widget.initialPage, child = TwonlyIdentityBackupView(
); callBack: () {
setState(() {});
},
);
} else {
child = HomeView(
initialPage: widget.initialPage,
);
}
} else if (_showOnboarding) { } else if (_showOnboarding) {
child = OnboardingView( child = OnboardingView(
callbackOnSuccess: () => setState(() { callbackOnSuccess: () => setState(() {

View file

@ -311,6 +311,7 @@
"backupLastBackupSize": "Backup-Größe", "backupLastBackupSize": "Backup-Größe",
"backupLastBackupResult": "Ergebnis", "backupLastBackupResult": "Ergebnis",
"deleteBackupTitle": "Bist du sicher?", "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.", "deleteBackupBody": "Ohne ein Backup kannst du dein Benutzerkonto nicht wiederherstellen.",
"backupData": "Daten-Backup", "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.", "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.", "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", "backupInsecurePasswordOk": "Trotzdem fortfahren",
"backupInsecurePasswordCancel": "Erneut versuchen", "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.", "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 Safe-Backup wiederherstellen möchtest.", "backupSelectStrongPassword": "Wähle ein sicheres Passwort. Dies ist erforderlich, wenn du dein twonly Backup wiederherstellen möchtest.",
"password": "Passwort", "password": "Passwort",
"passwordRepeated": "Passwort wiederholen", "passwordRepeated": "Passwort wiederholen",
"passwordRepeatedNotEqual": "Passwörter stimmen nicht überein.", "passwordRepeatedNotEqual": "Passwörter stimmen nicht überein.",
"backupPasswordRequirement": "Das Passwort muss mindestens 8 Zeichen lang sein.", "backupPasswordRequirement": "Das Passwort muss mindestens 8 Zeichen lang sein.",
"backupExpertSettings": "Experteneinstellungen", "backupExpertSettings": "Experteneinstellungen",
"backupEnableBackup": "Automatische Sicherung aktivieren", "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", "backupUseOwnServer": "Server verwenden",
"backupResetServer": "Standardserver verwenden", "backupResetServer": "Standardserver verwenden",
"backupTwonlySaveNow": "Jetzt speichern", "backupTwonlySaveNow": "Jetzt speichern",
"backupChangePassword": "Password ändern",
"inviteFriends": "Freunde einladen", "inviteFriends": "Freunde einladen",
"inviteFriendsShareBtn": "Teilen", "inviteFriendsShareBtn": "Teilen",
"inviteFriendsShareText": "Wechseln wir zu twonly: {url}", "inviteFriendsShareText": "Wechseln wir zu twonly: {url}",

View file

@ -457,6 +457,7 @@
"backupFailed": "Failed", "backupFailed": "Failed",
"backupSuccess": "Success", "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.", "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", "backupServer": "Server",
"backupMaxBackupSize": "max. backup size", "backupMaxBackupSize": "max. backup size",
"backupStorageRetention": "Storage retention", "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.", "backupInsecurePasswordDesc": "The chosen password is very insecure and can therefore easily be guessed by attackers. Please choose a secure password.",
"backupInsecurePasswordOk": "Continue anyway", "backupInsecurePasswordOk": "Continue anyway",
"backupInsecurePasswordCancel": "Try again", "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.", "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 Safe backup.", "backupSelectStrongPassword": "Choose a secure password. This is required if you want to restore your twonly Backup.",
"password": "Password", "password": "Password",
"passwordRepeated": "Repeat password", "passwordRepeated": "Repeat password",
"passwordRepeatedNotEqual": "Passwords do not match.", "passwordRepeatedNotEqual": "Passwords do not match.",
"backupPasswordRequirement": "Password must be at least 8 characters long.", "backupPasswordRequirement": "Password must be at least 8 characters long.",
"backupExpertSettings": "Expert settings", "backupExpertSettings": "Expert settings",
"backupEnableBackup": "Activate automatic backup", "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", "backupUseOwnServer": "Use server",
"backupResetServer": "Use standard server", "backupResetServer": "Use standard server",
"backupTwonlySaveNow": "Save now", "backupTwonlySaveNow": "Save now",
"backupChangePassword": "Change password",
"twonlySafeRecoverTitle": "Recovery", "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", "twonlySafeRecoverBtn": "Restore backup",
"inviteFriends": "Invite your friends", "inviteFriends": "Invite your friends",
"inviteFriendsShareBtn": "Share", "inviteFriendsShareBtn": "Share",

View file

@ -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.'** /// **'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; 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. /// No description provided for @backupServer.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@ -1931,13 +1937,13 @@ abstract class AppLocalizations {
/// No description provided for @backupTwonlySafeLongDesc. /// No description provided for @backupTwonlySafeLongDesc.
/// ///
/// In en, this message translates to: /// 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; String get backupTwonlySafeLongDesc;
/// No description provided for @backupSelectStrongPassword. /// No description provided for @backupSelectStrongPassword.
/// ///
/// In en, this message translates to: /// 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; String get backupSelectStrongPassword;
/// No description provided for @password. /// No description provided for @password.
@ -1979,7 +1985,7 @@ abstract class AppLocalizations {
/// No description provided for @backupOwnServerDesc. /// No description provided for @backupOwnServerDesc.
/// ///
/// In en, this message translates to: /// 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; String get backupOwnServerDesc;
/// No description provided for @backupUseOwnServer. /// No description provided for @backupUseOwnServer.
@ -2000,6 +2006,12 @@ abstract class AppLocalizations {
/// **'Save now'** /// **'Save now'**
String get backupTwonlySaveNow; String get backupTwonlySaveNow;
/// No description provided for @backupChangePassword.
///
/// In en, this message translates to:
/// **'Change password'**
String get backupChangePassword;
/// No description provided for @twonlySafeRecoverTitle. /// No description provided for @twonlySafeRecoverTitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@ -2009,7 +2021,7 @@ abstract class AppLocalizations {
/// No description provided for @twonlySafeRecoverDesc. /// No description provided for @twonlySafeRecoverDesc.
/// ///
/// In en, this message translates to: /// 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; String get twonlySafeRecoverDesc;
/// No description provided for @twonlySafeRecoverBtn. /// No description provided for @twonlySafeRecoverBtn.

View file

@ -973,6 +973,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get backupTwonlySafeDesc => 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.'; '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 @override
String get backupServer => 'Server'; String get backupServer => 'Server';
@ -1020,11 +1024,11 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get backupTwonlySafeLongDesc => 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 @override
String get backupSelectStrongPassword => 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 @override
String get password => 'Passwort'; String get password => 'Passwort';
@ -1047,7 +1051,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get backupOwnServerDesc => String get backupOwnServerDesc =>
'Speichere dein twonly Safe-Backups auf einem Server deiner Wahl.'; 'Speichere dein twonly Backup auf einem Server deiner Wahl.';
@override @override
String get backupUseOwnServer => 'Server verwenden'; String get backupUseOwnServer => 'Server verwenden';
@ -1058,12 +1062,15 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get backupTwonlySaveNow => 'Jetzt speichern'; String get backupTwonlySaveNow => 'Jetzt speichern';
@override
String get backupChangePassword => 'Password ändern';
@override @override
String get twonlySafeRecoverTitle => 'Recovery'; String get twonlySafeRecoverTitle => 'Recovery';
@override @override
String get twonlySafeRecoverDesc => 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 @override
String get twonlySafeRecoverBtn => 'Restore backup'; String get twonlySafeRecoverBtn => 'Restore backup';

View file

@ -967,6 +967,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get backupTwonlySafeDesc => 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.'; '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 @override
String get backupServer => 'Server'; String get backupServer => 'Server';
@ -1014,11 +1018,11 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get backupTwonlySafeLongDesc => 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 @override
String get backupSelectStrongPassword => 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 @override
String get password => 'Password'; String get password => 'Password';
@ -1041,7 +1045,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get backupOwnServerDesc => 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 @override
String get backupUseOwnServer => 'Use server'; String get backupUseOwnServer => 'Use server';
@ -1052,12 +1056,15 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get backupTwonlySaveNow => 'Save now'; String get backupTwonlySaveNow => 'Save now';
@override
String get backupChangePassword => 'Change password';
@override @override
String get twonlySafeRecoverTitle => 'Recovery'; String get twonlySafeRecoverTitle => 'Recovery';
@override @override
String get twonlySafeRecoverDesc => 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 @override
String get twonlySafeRecoverBtn => 'Restore backup'; String get twonlySafeRecoverBtn => 'Restore backup';

View file

@ -311,7 +311,9 @@ class ApiService {
} }
if (res.error == ErrorCode.NewDeviceRegistered) { if (res.error == ErrorCode.NewDeviceRegistered) {
globalCallbackNewDeviceRegistered(); 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; appIsOutdated = true;
await close(() {}); await close(() {});
return Result.error(ErrorCode.InternalError); return Result.error(ErrorCode.InternalError);

View file

@ -40,7 +40,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
} }
} }
Log.info('Starting new twonly Safe-Backup!'); Log.info('Starting new twonly Backup!');
final baseDir = (await getApplicationSupportDirectory()).path; final baseDir = (await getApplicationSupportDirectory()).path;
@ -161,7 +161,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
await encryptedBackupBytesFile.writeAsBytes(encryptedBackupBytes); await encryptedBackupBytesFile.writeAsBytes(encryptedBackupBytes);
Log.info( 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) { if (gUser.backupServer != null) {
@ -187,7 +187,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
}, },
); );
if (await FileDownloader().enqueue(task)) { if (await FileDownloader().enqueue(task)) {
Log.info('Starting upload from twonly Safe backup.'); Log.info('Starting upload from twonly Backup.');
await updateUserdata((user) { await updateUserdata((user) {
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.pending; user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.pending;
user.twonlySafeBackup!.lastBackupDone = DateTime.now(); user.twonlySafeBackup!.lastBackupDone = DateTime.now();
@ -196,7 +196,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
}); });
gUpdateBackupView(); gUpdateBackupView();
} else { } else {
Log.error('Error starting UploadTask for twonly Safe.'); Log.error('Error starting UploadTask for twonly Backup.');
} }
} }
@ -204,7 +204,7 @@ Future<void> handleBackupStatusUpdate(TaskStatusUpdate update) async {
if (update.status == TaskStatus.failed || if (update.status == TaskStatus.failed ||
update.status == TaskStatus.canceled) { update.status == TaskStatus.canceled) {
Log.error( 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) { await updateUserdata((user) {
if (user.twonlySafeBackup != null) { if (user.twonlySafeBackup != null) {
@ -214,7 +214,7 @@ Future<void> handleBackupStatusUpdate(TaskStatusUpdate update) async {
}); });
} else if (update.status == TaskStatus.complete) { } else if (update.status == TaskStatus.complete) {
Log.error( Log.error(
'twonly Safe uploaded with status code ${update.responseStatusCode}', 'twonly Backup uploaded with status code ${update.responseStatusCode}',
); );
await updateUserdata((user) { await updateUserdata((user) {
if (user.twonlySafeBackup != null) { if (user.twonlySafeBackup != null) {

View file

@ -60,13 +60,13 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('twonly Safe ${context.lang.twonlySafeRecoverTitle}'), title: Text('twonly Backup ${context.lang.twonlySafeRecoverTitle}'),
actions: [ actions: [
IconButton( IconButton(
onPressed: () async { onPressed: () async {
await showAlertDialog( await showAlertDialog(
context, context,
'twonly Safe', 'twonly Backup',
context.lang.backupTwonlySafeLongDesc, context.lang.backupTwonlySafeLongDesc,
); );
}, },

View file

@ -166,30 +166,6 @@ class _RegisterViewState extends State<RegisterView> {
), ),
textAlign: TextAlign.center, 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), const SizedBox(height: 30),
Column( Column(
children: [ children: [

View file

@ -3,10 +3,8 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/model/json/userdata.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/services/twonly_safe/create_backup.twonly_safe.dart';
import 'package:twonly/src/utils/misc.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'; import 'package:twonly/src/views/settings/backup/twonly_safe_backup.view.dart';
void Function() gUpdateBackupView = () {}; void Function() gUpdateBackupView = () {};
@ -25,8 +23,6 @@ BackupServer defaultBackupServer = BackupServer(
); );
class _BackupViewState extends State<BackupView> { class _BackupViewState extends State<BackupView> {
TwonlySafeBackup? twonlySafeBackup;
BackupServer backupServer = defaultBackupServer;
bool isLoading = false; bool isLoading = false;
int activePageIdx = 0; int activePageIdx = 0;
@ -47,11 +43,6 @@ class _BackupViewState extends State<BackupView> {
} }
Future<void> initAsync() async { Future<void> initAsync() async {
twonlySafeBackup = gUser.twonlySafeBackup;
backupServer = defaultBackupServer;
if (gUser.backupServer != null) {
backupServer = gUser.backupServer!;
}
setState(() {}); setState(() {});
} }
@ -68,8 +59,25 @@ class _BackupViewState extends State<BackupView> {
} }
} }
Future<void> changeTwonlySafePassword() async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return const TwonlyIdentityBackupView(
isPasswordChangeOnly: true,
);
},
),
);
setState(() {
// gUser was updated
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final backupServer = gUser.backupServer ?? defaultBackupServer;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(context.lang.settingsBackup), title: Text(context.lang.settingsBackup),
@ -83,10 +91,13 @@ class _BackupViewState extends State<BackupView> {
}, },
children: [ children: [
BackupOption( BackupOption(
title: 'twonly Safe', title: 'twonly Backup',
description: context.lang.backupTwonlySafeDesc, description: context.lang.backupTwonlySafeDesc,
autoBackupEnabled: twonlySafeBackup != null, bottomButton: FilledButton(
child: (twonlySafeBackup == null) onPressed: changeTwonlySafePassword,
child: Text(context.lang.backupChangePassword),
),
child: (gUser.twonlySafeBackup == null)
? null ? null
: Column( : Column(
children: [ children: [
@ -114,16 +125,20 @@ class _BackupViewState extends State<BackupView> {
context.lang.backupLastBackupDate, context.lang.backupLastBackupDate,
formatDateTime( formatDateTime(
context, context,
twonlySafeBackup!.lastBackupDone, gUser.twonlySafeBackup!.lastBackupDone,
) )
), ),
( (
context.lang.backupLastBackupSize, context.lang.backupLastBackupSize,
formatBytes(twonlySafeBackup!.lastBackupSize) formatBytes(
gUser.twonlySafeBackup!.lastBackupSize,
)
), ),
( (
context.lang.backupLastBackupResult, context.lang.backupLastBackupResult,
backupStatus(twonlySafeBackup!.backupUploadState) backupStatus(
gUser.twonlySafeBackup!.backupUploadState,
)
), ),
].map((pair) { ].map((pair) {
return TableRow( return TableRow(
@ -134,8 +149,9 @@ class _BackupViewState extends State<BackupView> {
), ),
TableCell( TableCell(
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(vertical: 4), vertical: 4,
),
child: Text( child: Text(
pair.$2, pair.$2,
textAlign: TextAlign.right, textAlign: TextAlign.right,
@ -148,7 +164,7 @@ class _BackupViewState extends State<BackupView> {
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
FilledButton( OutlinedButton(
onPressed: isLoading onPressed: isLoading
? null ? null
: () async { : () async {
@ -164,37 +180,10 @@ class _BackupViewState extends State<BackupView> {
), ),
], ],
), ),
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( BackupOption(
title: '${context.lang.backupData} (Coming Soon)', title: '${context.lang.backupData} (Coming Soon)',
description: context.lang.backupDataDesc, description: context.lang.backupDataDesc,
autoBackupEnabled: false,
onTap: null,
), ),
], ],
), ),
@ -209,7 +198,7 @@ class _BackupViewState extends State<BackupView> {
items: [ items: [
const BottomNavigationBarItem( const BottomNavigationBarItem(
icon: FaIcon(FontAwesomeIcons.vault, size: 17), icon: FaIcon(FontAwesomeIcons.vault, size: 17),
label: 'twonly Safe', label: 'twonly Backup',
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: const FaIcon(FontAwesomeIcons.boxArchive, size: 17), icon: const FaIcon(FontAwesomeIcons.boxArchive, size: 17),
@ -236,51 +225,35 @@ class BackupOption extends StatelessWidget {
const BackupOption({ const BackupOption({
required this.title, required this.title,
required this.description, required this.description,
required this.autoBackupEnabled, this.bottomButton,
required this.onTap,
super.key, super.key,
this.child, this.child,
}); });
final String title; final String title;
final String description; final String description;
final Widget? child; final Widget? child;
final bool autoBackupEnabled; final Widget? bottomButton;
final void Function()? onTap;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return Card(
onTap: autoBackupEnabled ? null : onTap, margin: const EdgeInsets.all(16),
child: Card( child: Padding(
margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Padding( child: Column(
padding: const EdgeInsets.all(16), crossAxisAlignment: CrossAxisAlignment.start,
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, Text(
children: [ title,
Text( style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
title, ),
style: const SizedBox(height: 8),
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), Text(description),
), const SizedBox(height: 8),
const SizedBox(height: 8), if (child != null) child! else Container(),
Text(description), Expanded(child: Container()),
const SizedBox(height: 8), if (bottomButton != null) Center(child: bottomButton),
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),
),
),
],
),
), ),
), ),
); );

View file

@ -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'; import 'package:twonly/src/views/settings/backup/twonly_safe_server.view.dart';
class TwonlyIdentityBackupView extends StatefulWidget { 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 @override
State<TwonlyIdentityBackupView> createState() => State<TwonlyIdentityBackupView> createState() =>
@ -56,148 +65,165 @@ class _TwonlyIdentityBackupViewState extends State<TwonlyIdentityBackupView> {
isLoading = false; isLoading = false;
}); });
Navigator.pop(context); if (widget.callBack != null) {
widget.callBack!();
} else {
Navigator.pop(context);
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return GestureDetector(
appBar: AppBar( onTap: () => FocusScope.of(context).unfocus(),
title: const Text('twonly Safe'), child: Scaffold(
actions: [ appBar: AppBar(
IconButton( title: const Text('twonly Backup'),
onPressed: () async { actions: [
await showAlertDialog( IconButton(
context, onPressed: () async {
'twonly Safe', await showAlertDialog(
context.lang.backupTwonlySafeLongDesc, context,
); 'twonly Backup',
}, 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(() {});
}, },
style: const TextStyle(fontSize: 17), icon: const FaIcon(FontAwesomeIcons.circleInfo),
obscureText: true, iconSize: 18,
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),
),
), ),
], ],
), ),
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,
),
),
),
],
),
),
), ),
); );
} }

View file

@ -107,7 +107,7 @@ class _TwonlySafeServerViewState extends State<TwonlySafeServerView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('twonly Safe Server'), title: const Text('twonly Backup Server'),
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(40), padding: const EdgeInsets.all(40),

View file

@ -7,7 +7,7 @@ if [ ! -f "pubspec.yaml" ]; then
exit 1 exit 1
fi fi
# Definitions for twonly Safe # Definitions for twonly Backup
GENERATED_DIR="./lib/src/model/protobuf/client/generated/" GENERATED_DIR="./lib/src/model/protobuf/client/generated/"
CLIENT_DIR="./lib/src/model/protobuf/client/" CLIENT_DIR="./lib/src/model/protobuf/client/"