diff --git a/assets/markdown/how_fallback_old-en.md b/assets/markdown/how_fallback_old-en.md new file mode 100644 index 00000000..368ea83a --- /dev/null +++ b/assets/markdown/how_fallback_old-en.md @@ -0,0 +1,15 @@ +### How to get Cloudflare API Token +1. Visit the following link: https://dash.cloudflare.com/ +2. the right corner, click on the profile icon (a man in a circle). For the mobile version of the site, in the upper left corner, click the **Menu** button (three horizontal bars), in the dropdown menu, click on **My Profile** +3. There are four configuration categories to choose from: *Communication*, *Authentication*, **API Tokens**, *Session*. Choose **API Tokens**. +4. Click on **Create Token** button. +5. Go down to the bottom and see the **Create Custom Token** field and press **Get Started** button on the right side. +6. In the **Token Name** field, give your token a name. +7. Next we have Permissions. In the leftmost field, select **Zone**. In the longest field, center, select **DNS**. In the rightmost field, select **Edit**. +8. Next, right under this line, click Add More. Similar field will appear. +9. In the leftmost field of the new line, select, similar to the last line — **Zone**. In the center — a little different. Here choose the same as in the left — **Zone**. In the rightmost field, select **Read**. +10. Next look at **Zone Resources**. Under this inscription there is a line with two fields. The left must have **Include** and the right must have **Specific Zone**. Once you select Specific Zone, another field appears on the right. Choose your domain in it. +11. Flick to the bottom and press the blue **Continue to Summary** button. +12. Check if you got everything right. A similar string must be present: *Domain — DNS:Edit, Zone:Read*. +13. Click on **Create Token**. +14. We copy the created token, and save it in a reliable place (preferably in the password manager). diff --git a/assets/markdown/how_fallback_old-ru.md b/assets/markdown/how_fallback_old-ru.md new file mode 100644 index 00000000..2c0ad22b --- /dev/null +++ b/assets/markdown/how_fallback_old-ru.md @@ -0,0 +1,13 @@ +### Как получить Cloudflare API Token +1. Переходим по [ссылке](https://dash.cloudflare.com/) и авторизуемся в ранее созданном аккаунте. https://dash.cloudflare.com/ +В правом углу кликаем на иконку профиля (человечек в кружочке). Для мобильной версии сайта, в верхнем левом углу, нажимаем кнопку **Меню** (три горизонтальных полоски), в выпавшем меню, ищем пункт **My Profile**. +3. Нам предлагается на выбор, четыре категории настройки: **Preferences**, **Authentication**, **API Tokens**, **Sessions**. Выбираем **API Tokens**. +4. Самым первым пунктом видим кнопку **Create Token**. С полной уверенностью в себе и желанием обрести приватность, нажимаем на неё. +5. Спускаемся в самый низ и видим поле **Create Custom Token** и кнопку **Get Started** с правой стороны. Нажимаем. +6. В поле **Token Name** даём своему токену имя. Можете покреативить и отнестись к этому как к наименованию домашнего зверька :) +7. Далее, у нас **Permissions**. В первом поле выбираем Zone. Во втором поле, по центру, выбираем **DNS**. В последнем поле выбираем **Edit**. +8. Далее смотрим на **Zone Resources**. Под этой надписью есть строка с двумя полями. В первом должно быть **Include**, а во втором — **Specific Zone**. Как только Вы выберите **Specific Zone**, справа появится ещё одно поле. В нём выбираем наш домен. +9. Листаем в самый низ и нажимаем на синюю кнопку **Continue to Summary**. +10. Проверяем, всё ли мы правильно выбрали. Должна присутствовать подобная строка: ваш.домен — **DNS:Edit, Zone:Read**. +11. Нажимаем **Create Token**. +12. Копируем созданный токен, и сохраняем его в надёжном месте (желательно — в менеджере паролей). \ No newline at end of file diff --git a/assets/markdown/how_fallback_ssh-en.md b/assets/markdown/how_fallback_ssh-en.md new file mode 100644 index 00000000..368ea83a --- /dev/null +++ b/assets/markdown/how_fallback_ssh-en.md @@ -0,0 +1,15 @@ +### How to get Cloudflare API Token +1. Visit the following link: https://dash.cloudflare.com/ +2. the right corner, click on the profile icon (a man in a circle). For the mobile version of the site, in the upper left corner, click the **Menu** button (three horizontal bars), in the dropdown menu, click on **My Profile** +3. There are four configuration categories to choose from: *Communication*, *Authentication*, **API Tokens**, *Session*. Choose **API Tokens**. +4. Click on **Create Token** button. +5. Go down to the bottom and see the **Create Custom Token** field and press **Get Started** button on the right side. +6. In the **Token Name** field, give your token a name. +7. Next we have Permissions. In the leftmost field, select **Zone**. In the longest field, center, select **DNS**. In the rightmost field, select **Edit**. +8. Next, right under this line, click Add More. Similar field will appear. +9. In the leftmost field of the new line, select, similar to the last line — **Zone**. In the center — a little different. Here choose the same as in the left — **Zone**. In the rightmost field, select **Read**. +10. Next look at **Zone Resources**. Under this inscription there is a line with two fields. The left must have **Include** and the right must have **Specific Zone**. Once you select Specific Zone, another field appears on the right. Choose your domain in it. +11. Flick to the bottom and press the blue **Continue to Summary** button. +12. Check if you got everything right. A similar string must be present: *Domain — DNS:Edit, Zone:Read*. +13. Click on **Create Token**. +14. We copy the created token, and save it in a reliable place (preferably in the password manager). diff --git a/assets/markdown/how_fallback_ssh-ru.md b/assets/markdown/how_fallback_ssh-ru.md new file mode 100644 index 00000000..2c0ad22b --- /dev/null +++ b/assets/markdown/how_fallback_ssh-ru.md @@ -0,0 +1,13 @@ +### Как получить Cloudflare API Token +1. Переходим по [ссылке](https://dash.cloudflare.com/) и авторизуемся в ранее созданном аккаунте. https://dash.cloudflare.com/ +В правом углу кликаем на иконку профиля (человечек в кружочке). Для мобильной версии сайта, в верхнем левом углу, нажимаем кнопку **Меню** (три горизонтальных полоски), в выпавшем меню, ищем пункт **My Profile**. +3. Нам предлагается на выбор, четыре категории настройки: **Preferences**, **Authentication**, **API Tokens**, **Sessions**. Выбираем **API Tokens**. +4. Самым первым пунктом видим кнопку **Create Token**. С полной уверенностью в себе и желанием обрести приватность, нажимаем на неё. +5. Спускаемся в самый низ и видим поле **Create Custom Token** и кнопку **Get Started** с правой стороны. Нажимаем. +6. В поле **Token Name** даём своему токену имя. Можете покреативить и отнестись к этому как к наименованию домашнего зверька :) +7. Далее, у нас **Permissions**. В первом поле выбираем Zone. Во втором поле, по центру, выбираем **DNS**. В последнем поле выбираем **Edit**. +8. Далее смотрим на **Zone Resources**. Под этой надписью есть строка с двумя полями. В первом должно быть **Include**, а во втором — **Specific Zone**. Как только Вы выберите **Specific Zone**, справа появится ещё одно поле. В нём выбираем наш домен. +9. Листаем в самый низ и нажимаем на синюю кнопку **Continue to Summary**. +10. Проверяем, всё ли мы правильно выбрали. Должна присутствовать подобная строка: ваш.домен — **DNS:Edit, Zone:Read**. +11. Нажимаем **Create Token**. +12. Копируем созданный токен, и сохраняем его в надёжном месте (желательно — в менеджере паролей). \ No newline at end of file diff --git a/assets/markdown/how_fallback_terminal-en.md b/assets/markdown/how_fallback_terminal-en.md new file mode 100644 index 00000000..368ea83a --- /dev/null +++ b/assets/markdown/how_fallback_terminal-en.md @@ -0,0 +1,15 @@ +### How to get Cloudflare API Token +1. Visit the following link: https://dash.cloudflare.com/ +2. the right corner, click on the profile icon (a man in a circle). For the mobile version of the site, in the upper left corner, click the **Menu** button (three horizontal bars), in the dropdown menu, click on **My Profile** +3. There are four configuration categories to choose from: *Communication*, *Authentication*, **API Tokens**, *Session*. Choose **API Tokens**. +4. Click on **Create Token** button. +5. Go down to the bottom and see the **Create Custom Token** field and press **Get Started** button on the right side. +6. In the **Token Name** field, give your token a name. +7. Next we have Permissions. In the leftmost field, select **Zone**. In the longest field, center, select **DNS**. In the rightmost field, select **Edit**. +8. Next, right under this line, click Add More. Similar field will appear. +9. In the leftmost field of the new line, select, similar to the last line — **Zone**. In the center — a little different. Here choose the same as in the left — **Zone**. In the rightmost field, select **Read**. +10. Next look at **Zone Resources**. Under this inscription there is a line with two fields. The left must have **Include** and the right must have **Specific Zone**. Once you select Specific Zone, another field appears on the right. Choose your domain in it. +11. Flick to the bottom and press the blue **Continue to Summary** button. +12. Check if you got everything right. A similar string must be present: *Domain — DNS:Edit, Zone:Read*. +13. Click on **Create Token**. +14. We copy the created token, and save it in a reliable place (preferably in the password manager). diff --git a/assets/markdown/how_fallback_terminal-ru.md b/assets/markdown/how_fallback_terminal-ru.md new file mode 100644 index 00000000..2c0ad22b --- /dev/null +++ b/assets/markdown/how_fallback_terminal-ru.md @@ -0,0 +1,13 @@ +### Как получить Cloudflare API Token +1. Переходим по [ссылке](https://dash.cloudflare.com/) и авторизуемся в ранее созданном аккаунте. https://dash.cloudflare.com/ +В правом углу кликаем на иконку профиля (человечек в кружочке). Для мобильной версии сайта, в верхнем левом углу, нажимаем кнопку **Меню** (три горизонтальных полоски), в выпавшем меню, ищем пункт **My Profile**. +3. Нам предлагается на выбор, четыре категории настройки: **Preferences**, **Authentication**, **API Tokens**, **Sessions**. Выбираем **API Tokens**. +4. Самым первым пунктом видим кнопку **Create Token**. С полной уверенностью в себе и желанием обрести приватность, нажимаем на неё. +5. Спускаемся в самый низ и видим поле **Create Custom Token** и кнопку **Get Started** с правой стороны. Нажимаем. +6. В поле **Token Name** даём своему токену имя. Можете покреативить и отнестись к этому как к наименованию домашнего зверька :) +7. Далее, у нас **Permissions**. В первом поле выбираем Zone. Во втором поле, по центру, выбираем **DNS**. В последнем поле выбираем **Edit**. +8. Далее смотрим на **Zone Resources**. Под этой надписью есть строка с двумя полями. В первом должно быть **Include**, а во втором — **Specific Zone**. Как только Вы выберите **Specific Zone**, справа появится ещё одно поле. В нём выбираем наш домен. +9. Листаем в самый низ и нажимаем на синюю кнопку **Continue to Summary**. +10. Проверяем, всё ли мы правильно выбрали. Должна присутствовать подобная строка: ваш.домен — **DNS:Edit, Zone:Read**. +11. Нажимаем **Create Token**. +12. Копируем созданный токен, и сохраняем его в надёжном месте (желательно — в менеджере паролей). \ No newline at end of file diff --git a/assets/translations/en.json b/assets/translations/en.json index 3b7bf3af..88889d8d 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -296,7 +296,7 @@ "method_device_button": "I have received my token", "method_device_input_description": "Enter your authorization token", "method_device_input_placeholder": "Token", - "method_recovery_input_description": "Enter your recovery token", + "method_recovery_input_description": "Enter your recovery key", "fallback_select_description": "What exactly do you have? Pick the first available option:", "fallback_select_token_copy": "Copy of auth token from other version of the application.", "fallback_select_root_ssh": "Root SSH access to the server.", diff --git a/lib/logic/cubit/forms/setup/initializing/backblaze_form_cubit.dart b/lib/logic/cubit/forms/setup/initializing/backblaze_form_cubit.dart index 50fc8e80..f3554ef4 100644 --- a/lib/logic/cubit/forms/setup/initializing/backblaze_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/backblaze_form_cubit.dart @@ -6,7 +6,7 @@ import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:easy_localization/easy_localization.dart'; class BackblazeFormCubit extends FormCubit { - BackblazeFormCubit(this.initializingCubit) { + BackblazeFormCubit(this.serverSetupCubit) { //var regExp = RegExp(r"\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]"); keyId = FieldCubit( initalValue: '', @@ -27,13 +27,13 @@ class BackblazeFormCubit extends FormCubit { @override FutureOr onSubmit() async { - initializingCubit.setBackblazeKey( + serverSetupCubit.setBackblazeKey( keyId.state.value, applicationKey.state.value, ); } - final ServerInstallationCubit initializingCubit; + final ServerInstallationCubit serverSetupCubit; late final FieldCubit keyId; late final FieldCubit applicationKey; diff --git a/lib/logic/cubit/forms/setup/initializing/domain_cloudflare.dart b/lib/logic/cubit/forms/setup/initializing/domain_cloudflare.dart index 363cee7c..07c46c74 100644 --- a/lib/logic/cubit/forms/setup/initializing/domain_cloudflare.dart +++ b/lib/logic/cubit/forms/setup/initializing/domain_cloudflare.dart @@ -4,9 +4,9 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; class DomainSetupCubit extends Cubit { - DomainSetupCubit(this.initializingCubit) : super(Initial()); + DomainSetupCubit(this.serverSetupCubit) : super(Initial()); - final ServerInstallationCubit initializingCubit; + final ServerInstallationCubit serverSetupCubit; Future load() async { emit(Loading(LoadingTypes.loadingDomain)); @@ -42,7 +42,7 @@ class DomainSetupCubit extends Cubit { provider: DnsProvider.Cloudflare, ); - initializingCubit.setDomain(domain); + serverSetupCubit.setDomain(domain); emit(DomainSet()); } } diff --git a/lib/logic/cubit/forms/setup/initializing/hetzner_form_cubit.dart b/lib/logic/cubit/forms/setup/initializing/hetzner_form_cubit.dart index 42517c64..6871942e 100644 --- a/lib/logic/cubit/forms/setup/initializing/hetzner_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/hetzner_form_cubit.dart @@ -7,7 +7,7 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_ import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; class HetznerFormCubit extends FormCubit { - HetznerFormCubit(this.initializingCubit) { + HetznerFormCubit(this.serverSetupCubit) { var regExp = RegExp(r"\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]"); apiKey = FieldCubit( initalValue: '', @@ -24,10 +24,10 @@ class HetznerFormCubit extends FormCubit { @override FutureOr onSubmit() async { - initializingCubit.setHetznerKey(apiKey.state.value); + serverSetupCubit.setHetznerKey(apiKey.state.value); } - final ServerInstallationCubit initializingCubit; + final ServerInstallationCubit serverSetupCubit; late final FieldCubit apiKey; diff --git a/lib/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart b/lib/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart index 3bff2aba..81ccd88d 100644 --- a/lib/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart @@ -7,7 +7,7 @@ import 'package:selfprivacy/logic/models/hive/user.dart'; class RootUserFormCubit extends FormCubit { RootUserFormCubit( - this.initializingCubit, final FieldCubitFactory fieldFactory) { + this.serverSetupCubit, final FieldCubitFactory fieldFactory) { userName = fieldFactory.createUserLoginField(); password = fieldFactory.createUserPasswordField(); @@ -22,10 +22,10 @@ class RootUserFormCubit extends FormCubit { login: userName.state.value, password: password.state.value, ); - initializingCubit.setRootUser(user); + serverSetupCubit.setRootUser(user); } - final ServerInstallationCubit initializingCubit; + final ServerInstallationCubit serverSetupCubit; late final FieldCubit userName; late final FieldCubit password; diff --git a/lib/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart b/lib/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart index 59e512d2..921cf996 100644 --- a/lib/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart @@ -43,6 +43,10 @@ class RecoveryDomainFormCubit extends FormCubit { return domainValid; } + FutureOr setCustomError(String error) { + serverDomainField.setError(error); + } + final ServerInstallationCubit initializingCubit; late final FieldCubit serverDomainField; } diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 3c80f2eb..33d360fe 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -305,6 +305,46 @@ class ServerInstallationCubit extends Cubit { } } + void revertRecoveryStep() { + final dataState = this.state as ServerInstallationRecovery; + switch (dataState.currentStep) { + case RecoveryStep.Selecting: + emit(ServerInstallationEmpty()); + break; + case RecoveryStep.RecoveryKey: + case RecoveryStep.NewDeviceKey: + case RecoveryStep.OldToken: + emit(dataState.copyWith( + currentStep: RecoveryStep.Selecting, + )); + break; + // We won't revert steps after client is authorized + default: + break; + } + } + + void selectRecoveryMethod(ServerRecoveryMethods method) { + final dataState = this.state as ServerInstallationRecovery; + switch (method) { + case ServerRecoveryMethods.newDeviceKey: + emit(dataState.copyWith( + currentStep: RecoveryStep.NewDeviceKey, + )); + break; + case ServerRecoveryMethods.recoveryKey: + emit(dataState.copyWith( + currentStep: RecoveryStep.RecoveryKey, + )); + break; + case ServerRecoveryMethods.oldToken: + emit(dataState.copyWith( + currentStep: RecoveryStep.OldToken, + )); + break; + } + } + void clearAppConfig() { closeTimer(); diff --git a/lib/ui/components/brand_header/brand_header.dart b/lib/ui/components/brand_header/brand_header.dart index 6795fdb4..ca36305e 100644 --- a/lib/ui/components/brand_header/brand_header.dart +++ b/lib/ui/components/brand_header/brand_header.dart @@ -9,11 +9,13 @@ class BrandHeader extends StatelessWidget { this.title = "", this.hasBackButton = false, this.hasFlashButton = false, + this.onBackButtonPressed, }) : super(key: key); final String title; final bool hasBackButton; final bool hasFlashButton; + final VoidCallback? onBackButtonPressed; @override Widget build(BuildContext context) { @@ -29,7 +31,8 @@ class BrandHeader extends StatelessWidget { if (hasBackButton) ...[ IconButton( icon: Icon(BrandIcons.arrow_left), - onPressed: () => Navigator.of(context).pop(), + onPressed: + onBackButtonPressed ?? () => Navigator.of(context).pop(), ), SizedBox(width: 10), ], diff --git a/lib/ui/components/brand_hero_screen/brand_hero_screen.dart b/lib/ui/components/brand_hero_screen/brand_hero_screen.dart index efa75515..e6163cd8 100644 --- a/lib/ui/components/brand_hero_screen/brand_hero_screen.dart +++ b/lib/ui/components/brand_hero_screen/brand_hero_screen.dart @@ -11,6 +11,7 @@ class BrandHeroScreen extends StatelessWidget { this.heroIcon, this.heroTitle, this.heroSubtitle, + this.onBackButtonPressed, }) : super(key: key); final List children; @@ -20,6 +21,7 @@ class BrandHeroScreen extends StatelessWidget { final IconData? heroIcon; final String? heroTitle; final String? heroSubtitle; + final VoidCallback? onBackButtonPressed; @override Widget build(BuildContext context) { @@ -31,6 +33,7 @@ class BrandHeroScreen extends StatelessWidget { title: headerTitle, hasBackButton: hasBackButton, hasFlashButton: hasFlashButton, + onBackButtonPressed: onBackButtonPressed, ), ), body: ListView( @@ -48,9 +51,7 @@ class BrandHeroScreen extends StatelessWidget { if (heroTitle != null) Text( heroTitle!, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.black, - ), + style: Theme.of(context).textTheme.headlineMedium, textAlign: TextAlign.start, ), SizedBox(height: 8.0), diff --git a/lib/ui/pages/setup/initializing.dart b/lib/ui/pages/setup/initializing.dart index e1f6203e..81630b63 100644 --- a/lib/ui/pages/setup/initializing.dart +++ b/lib/ui/pages/setup/initializing.dart @@ -17,7 +17,7 @@ import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart'; import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart'; import 'package:selfprivacy/ui/pages/rootRoute.dart'; -import 'package:selfprivacy/ui/pages/setup/recovering/recovery_domain.dart'; +import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart'; import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_select.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; @@ -37,6 +37,10 @@ class InitializingPage extends StatelessWidget { () => _stepCheck(cubit), () => Container(child: Center(child: Text('initializing.finish'.tr()))) ][cubit.state.progress.index](); + + if (cubit is ServerInstallationRecovery) { + return RecoveryRouting(); + } return BlocListener( listener: (context, state) { if (cubit.state is ServerInstallationFinished) { diff --git a/lib/ui/pages/setup/recovering/recovery_method_device_2.dart b/lib/ui/pages/setup/recovering/recover_by_new_device_key.dart similarity index 70% rename from lib/ui/pages/setup/recovering/recovery_method_device_2.dart rename to lib/ui/pages/setup/recovering/recover_by_new_device_key.dart index e9323803..63f7e127 100644 --- a/lib/ui/pages/setup/recovering/recovery_method_device_2.dart +++ b/lib/ui/pages/setup/recovering/recover_by_new_device_key.dart @@ -1,13 +1,35 @@ -import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart'; import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/utils/route_transitions/basic.dart'; +import 'package:cubit_form/cubit_form.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; -class RecoveryMethodDevice2 extends StatelessWidget { +class RecoverByNewDeviceKeyInstruction extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BrandHeroScreen( + heroTitle: "recovering.recovery_main_header".tr(), + heroSubtitle: "recovering.method_device_description".tr(), + hasBackButton: true, + hasFlashButton: false, + onBackButtonPressed: () => + context.read().revertRecoveryStep(), + children: [ + FilledButton( + title: "recovering.method_device_button".tr(), + onPressed: () => Navigator.of(context) + .push(materialRoute(RecoverByNewDeviceKeyInput())), + ) + ], + ); + } +} + +class RecoverByNewDeviceKeyInput extends StatelessWidget { @override Widget build(BuildContext context) { var appConfig = context.watch(); diff --git a/lib/ui/pages/setup/recovering/recover_by_old_token.dart b/lib/ui/pages/setup/recovering/recover_by_old_token.dart new file mode 100644 index 00000000..522494fd --- /dev/null +++ b/lib/ui/pages/setup/recovering/recover_by_old_token.dart @@ -0,0 +1,79 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart'; +import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart'; +import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; +import 'package:selfprivacy/utils/route_transitions/basic.dart'; +import 'package:cubit_form/cubit_form.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; + +class RecoverByOldTokenInstruction extends StatelessWidget { + @override + RecoverByOldTokenInstruction({required this.instructionFilename}); + + Widget build(BuildContext context) { + return BrandHeroScreen( + heroTitle: "recovering.recovery_main_header".tr(), + hasBackButton: true, + hasFlashButton: false, + onBackButtonPressed: () => + context.read().revertRecoveryStep(), + children: [ + BrandMarkdown( + fileName: instructionFilename, + ), + SizedBox(height: 16), + FilledButton( + title: "recovering.method_device_button".tr(), + onPressed: () => + Navigator.of(context).push(materialRoute(RecoverByOldToken())), + ) + ], + ); + } + + final String instructionFilename; +} + +class RecoverByOldToken extends StatelessWidget { + @override + Widget build(BuildContext context) { + var appConfig = context.watch(); + + return BlocProvider( + create: (context) => + RecoveryDeviceFormCubit(appConfig, FieldCubitFactory(context)), + child: Builder( + builder: (context) { + var formCubitState = context.watch().state; + + return BrandHeroScreen( + heroTitle: "recovering.recovery_main_header".tr(), + heroSubtitle: "recovering.method_device_input_description".tr(), + hasBackButton: true, + hasFlashButton: false, + children: [ + CubitFormTextField( + formFieldCubit: + context.read().tokenField, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: "recovering.method_device_input_placeholder".tr(), + ), + ), + SizedBox(height: 16), + FilledButton( + title: "more.continue".tr(), + onPressed: formCubitState.isSubmitting + ? null + : () => context.read().trySubmit(), + ) + ], + ); + }, + ), + ); + } +} diff --git a/lib/ui/pages/setup/recovering/recovery_method_token.dart b/lib/ui/pages/setup/recovering/recover_by_recovery_key.dart similarity index 91% rename from lib/ui/pages/setup/recovering/recovery_method_token.dart rename to lib/ui/pages/setup/recovering/recover_by_recovery_key.dart index 7b0c2564..780c1ca4 100644 --- a/lib/ui/pages/setup/recovering/recovery_method_token.dart +++ b/lib/ui/pages/setup/recovering/recover_by_recovery_key.dart @@ -7,7 +7,7 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_ import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart'; import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; -class RecoveryMethodToken extends StatelessWidget { +class RecoverByRecoveryKey extends StatelessWidget { @override Widget build(BuildContext context) { var appConfig = context.watch(); @@ -24,6 +24,8 @@ class RecoveryMethodToken extends StatelessWidget { heroSubtitle: "recovering.method_recovery_input_description".tr(), hasBackButton: true, hasFlashButton: false, + onBackButtonPressed: () => + context.read().revertRecoveryStep(), children: [ CubitFormTextField( formFieldCubit: diff --git a/lib/ui/pages/setup/recovering/recovery_domain.dart b/lib/ui/pages/setup/recovering/recovery_domain.dart deleted file mode 100644 index f38a554f..00000000 --- a/lib/ui/pages/setup/recovering/recovery_domain.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:cubit_form/cubit_form.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; -import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart'; -import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; - -class RecoveryDomain extends StatelessWidget { - @override - Widget build(BuildContext context) { - var serverInstallation = context.watch(); - - return BlocProvider( - create: (context) => RecoveryDomainFormCubit( - serverInstallation, FieldCubitFactory(context)), - child: Builder( - builder: (context) { - var formCubitState = context.watch().state; - - return BrandHeroScreen( - heroTitle: "recovering.recovery_main_header".tr(), - heroSubtitle: "recovering.domain_recovery_description".tr(), - hasBackButton: true, - hasFlashButton: false, - children: [ - CubitFormTextField( - formFieldCubit: - context.read().serverDomainField, - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: "recovering.domain_recover_placeholder".tr(), - ), - ), - SizedBox(height: 16), - FilledButton( - title: "more.continue".tr(), - onPressed: formCubitState.isSubmitting - ? null - : () => context.read().trySubmit(), - ) - ], - ); - }, - ), - ); - } -} diff --git a/lib/ui/pages/setup/recovering/recovery_fallback_select.dart b/lib/ui/pages/setup/recovering/recovery_fallback_select.dart deleted file mode 100644 index bca3d07c..00000000 --- a/lib/ui/pages/setup/recovering/recovery_fallback_select.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; -import 'package:selfprivacy/utils/route_transitions/basic.dart'; -import 'package:selfprivacy/ui/pages/rootRoute.dart'; - -class RecoveryFallbackSelect extends StatelessWidget { - @override - Widget build(BuildContext context) { - return BrandHeroScreen( - heroTitle: "recovering.recovery_main_header".tr(), - heroSubtitle: "recovering.fallback_select_description".tr(), - hasBackButton: true, - hasFlashButton: false, - children: [ - BrandCards.outlined( - child: ListTile( - title: Text( - "recovering.fallback_select_token_copy".tr(), - style: Theme.of(context).textTheme.titleMedium, - ), - leading: Icon(Icons.vpn_key), - onTap: () => Navigator.of(context).push(materialRoute(RootPage())), - ), - ), - SizedBox(height: 16), - BrandCards.outlined( - child: ListTile( - title: Text( - "recovering.fallback_select_root_ssh".tr(), - style: Theme.of(context).textTheme.titleMedium, - ), - leading: Icon(Icons.terminal), - onTap: () => Navigator.of(context).push(materialRoute(RootPage())), - ), - ), - SizedBox(height: 16), - BrandCards.outlined( - child: ListTile( - title: Text( - "recovering.fallback_select_provider_console".tr(), - style: Theme.of(context).textTheme.titleMedium, - ), - subtitle: Text( - "recovering.fallback_select_provider_console_hint".tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - leading: Icon(Icons.web), - onTap: () => Navigator.of(context).push(materialRoute(RootPage())), - ), - ), - ], - ); - } -} diff --git a/lib/ui/pages/setup/recovering/recovery_method_device_1.dart b/lib/ui/pages/setup/recovering/recovery_method_device_1.dart deleted file mode 100644 index 43b071b8..00000000 --- a/lib/ui/pages/setup/recovering/recovery_method_device_1.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; -import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_device_2.dart'; -import 'package:selfprivacy/utils/route_transitions/basic.dart'; - -class RecoveryMethodDevice1 extends StatelessWidget { - @override - Widget build(BuildContext context) { - return BrandHeroScreen( - heroTitle: "recovering.recovery_main_header".tr(), - heroSubtitle: "recovering.method_device_description".tr(), - hasBackButton: true, - hasFlashButton: false, - children: [ - FilledButton( - title: "recovering.method_device_button".tr(), - onPressed: () => Navigator.of(context) - .push(materialRoute(RecoveryMethodDevice2())), - ) - ], - ); - } -} diff --git a/lib/ui/pages/setup/recovering/recovery_method_select.dart b/lib/ui/pages/setup/recovering/recovery_method_select.dart index f92545eb..57c336b8 100644 --- a/lib/ui/pages/setup/recovering/recovery_method_select.dart +++ b/lib/ui/pages/setup/recovering/recovery_method_select.dart @@ -1,13 +1,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart'; import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; -import 'package:selfprivacy/ui/pages/setup/recovering/recovery_fallback_select.dart'; -import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_device_1.dart'; -import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_token.dart'; +import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_old_token.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; -import 'package:selfprivacy/ui/pages/rootRoute.dart'; class RecoveryMethodSelect extends StatelessWidget { @override @@ -25,8 +23,9 @@ class RecoveryMethodSelect extends StatelessWidget { style: Theme.of(context).textTheme.titleMedium, ), leading: Icon(Icons.offline_share_outlined), - onTap: () => Navigator.of(context) - .push(materialRoute(RecoveryMethodDevice1())), + onTap: () => context + .read() + .selectRecoveryMethod(ServerRecoveryMethods.newDeviceKey), ), ), SizedBox(height: 16), @@ -37,17 +36,77 @@ class RecoveryMethodSelect extends StatelessWidget { style: Theme.of(context).textTheme.titleMedium, ), leading: Icon(Icons.password_outlined), - onTap: () => Navigator.of(context) - .push(materialRoute(RecoveryMethodToken())), + onTap: () => context + .read() + .selectRecoveryMethod(ServerRecoveryMethods.recoveryKey), ), ), SizedBox(height: 16), BrandButton.text( title: "recovering.method_select_nothing".tr(), onPressed: () => Navigator.of(context) - .push(materialRoute(RecoveryFallbackSelect())), + .push(materialRoute(RecoveryFallbackMethodSelect())), ) ], ); } } + +class RecoveryFallbackMethodSelect extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BrandHeroScreen( + heroTitle: "recovering.recovery_main_header".tr(), + heroSubtitle: "recovering.fallback_select_description".tr(), + hasBackButton: true, + hasFlashButton: false, + children: [ + BrandCards.outlined( + child: ListTile( + title: Text( + "recovering.fallback_select_token_copy".tr(), + style: Theme.of(context).textTheme.titleMedium, + ), + leading: Icon(Icons.vpn_key), + onTap: () => Navigator.of(context) + .push(materialRoute(RecoverByOldTokenInstruction( + instructionFilename: 'how_fallback_old', + ))), + ), + ), + SizedBox(height: 16), + BrandCards.outlined( + child: ListTile( + title: Text( + "recovering.fallback_select_root_ssh".tr(), + style: Theme.of(context).textTheme.titleMedium, + ), + leading: Icon(Icons.terminal), + onTap: () => Navigator.of(context) + .push(materialRoute(RecoverByOldTokenInstruction( + instructionFilename: 'how_fallback_ssh', + ))), + ), + ), + SizedBox(height: 16), + BrandCards.outlined( + child: ListTile( + title: Text( + "recovering.fallback_select_provider_console".tr(), + style: Theme.of(context).textTheme.titleMedium, + ), + subtitle: Text( + "recovering.fallback_select_provider_console_hint".tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + leading: Icon(Icons.web), + onTap: () => Navigator.of(context) + .push(materialRoute(RecoverByOldTokenInstruction( + instructionFilename: 'how_fallback_terminal', + ))), + ), + ), + ], + ); + } +} diff --git a/lib/ui/pages/setup/recovering/recovery_routing.dart b/lib/ui/pages/setup/recovering/recovery_routing.dart new file mode 100644 index 00000000..5db32eef --- /dev/null +++ b/lib/ui/pages/setup/recovering/recovery_routing.dart @@ -0,0 +1,109 @@ +import 'package:cubit_form/cubit_form.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; +import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart'; +import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart'; +import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_recovery_key.dart'; +import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_new_device_key.dart'; +import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_select.dart'; + +class RecoveryRouting extends StatelessWidget { + @override + Widget build(BuildContext context) { + var serverInstallation = context.watch(); + + StatelessWidget currentPage = SelectDomainToRecover(); + + if (serverInstallation is ServerInstallationRecovery) { + final state = (serverInstallation as ServerInstallationRecovery); + switch (state.currentStep) { + case RecoveryStep.Selecting: + if (state.recoveryCapabilities != ServerRecoveryCapabilities.none) + currentPage = RecoveryMethodSelect(); + break; + case RecoveryStep.RecoveryKey: + currentPage = RecoverByRecoveryKey(); + break; + case RecoveryStep.NewDeviceKey: + currentPage = RecoverByNewDeviceKeyInstruction(); + break; + case RecoveryStep.OldToken: + break; + case RecoveryStep.HetznerToken: + break; + case RecoveryStep.CloudflareToken: + break; + case RecoveryStep.BackblazeToken: + break; + } + } + + return AnimatedSwitcher( + duration: Duration(milliseconds: 300), + child: currentPage, + ); + } +} + +class SelectDomainToRecover extends StatelessWidget { + @override + Widget build(BuildContext context) { + var serverInstallation = context.watch(); + + return BlocProvider( + create: (context) => RecoveryDomainFormCubit( + serverInstallation, FieldCubitFactory(context)), + child: Builder( + builder: (context) { + var formCubitState = context.watch().state; + + return BlocListener( + listener: (context, state) { + if (state is ServerInstallationRecovery) { + if (state.currentStep == RecoveryStep.Selecting) { + if (state.recoveryCapabilities == + ServerRecoveryCapabilities.none) { + context + .read() + .setCustomError("recovering.domain_recover_error".tr()); + } + } + } + }, + child: BrandHeroScreen( + heroTitle: "recovering.recovery_main_header".tr(), + heroSubtitle: "recovering.domain_recovery_description".tr(), + hasBackButton: true, + hasFlashButton: false, + onBackButtonPressed: + serverInstallation is ServerInstallationRecovery + ? () => serverInstallation.clearAppConfig() + : null, + children: [ + CubitFormTextField( + formFieldCubit: + context.read().serverDomainField, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: "recovering.domain_recover_placeholder".tr(), + ), + ), + SizedBox(height: 16), + FilledButton( + title: "more.continue".tr(), + onPressed: formCubitState.isSubmitting + ? null + : () => + context.read().trySubmit(), + ) + ], + ), + ); + }, + ), + ); + } +}