From e560de58e7cd98671a63c4c90395d4c694ce1413 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 30 Dec 2022 07:25:18 +0400 Subject: [PATCH] feat: Implement DNS provider picker page --- assets/translations/en.json | 7 +- assets/translations/ru.json | 5 +- lib/config/hive_config.dart | 3 + ...t.dart => server_provider_form_cubit.dart} | 4 +- .../server_installation_cubit.dart | 19 +- .../server_installation_repository.dart | 4 + lib/logic/get_it/api_config.dart | 9 + .../initializing/dns_provider_picker.dart | 205 ++++++++++++++++++ .../setup/initializing/initializing.dart | 62 ++---- .../initializing/server_provider_picker.dart | 8 +- .../recovery_server_provider_connected.dart | 11 +- 11 files changed, 267 insertions(+), 70 deletions(-) rename lib/logic/cubit/forms/setup/initializing/{provider_form_cubit.dart => server_provider_form_cubit.dart} (91%) create mode 100644 lib/ui/pages/setup/initializing/dns_provider_picker.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index daa4544f..05bb823b 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -268,9 +268,10 @@ "no_ssh_notice": "Only email and SSH accounts are created for this user. Single Sign On for all services is coming soon." }, "initializing": { - "connect_to_server": "Connect a server", + "connect_to_server": "Connect the server provider", "select_provider": "Select your provider", - "place_where_data": "A place where your data and SelfPrivacy services will reside:", + "server_provider_description": "A place where your data and SelfPrivacy services will reside:", + "dns_provider_description": "A service which lets your IP point towards domain names:", "how": "How to obtain API token", "provider_bad_key_error": "Provider API key is invalid", "could_not_connect": "Counldn't connect to the provider.", @@ -280,7 +281,7 @@ "no_server_types_found": "No available server types found. Make sure your account is accessible and try to change your server location.", "cloudflare_bad_key_error": "Cloudflare API key is invalid", "backblaze_bad_key_error": "Backblaze storage information is invalid", - "connect_cloudflare": "Connect CloudFlare", + "connect_to_dns": "Connect the DNS provider", "manage_domain_dns": "To manage your domain's DNS", "cloudflare_api_token": "CloudFlare API Token", "connect_backblaze_storage": "Connect Backblaze storage", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index a5cc6634..f9fb3fb2 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -269,7 +269,8 @@ }, "initializing": { "connect_to_server": "Подключите сервер", - "place_where_data": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:", + "server_provider_description": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:", + "dns_provider_description": "Это позволит связать ваш домен с IP адресом:", "how": "Как получить API Token", "provider_bad_key_error": "API ключ провайдера неверен", "could_not_connect": "Не удалось соединиться с провайдером.", @@ -279,7 +280,7 @@ "no_server_types_found": "Не удалось получить список серверов. Убедитесь, что ваш аккаунт доступен и попытайтесь сменить локацию сервера.", "cloudflare_bad_key_error": "Cloudflare API ключ неверен", "backblaze_bad_key_error": "Информация о Backblaze хранилище неверна", - "connect_cloudflare": "Подключите CloudFlare", + "connect_to_dns": "Подключите DNS провайдер", "manage_domain_dns": "Для управления DNS вашего домена", "cloudflare_api_token": "CloudFlare API ключ", "connect_backblaze_storage": "Подключите облачное хранилище Backblaze", diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index 93bce6ee..b300e247 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -90,6 +90,9 @@ class BNames { /// A String field of [serverInstallationBox] box. static String serverProvider = 'serverProvider'; + /// A String field of [serverInstallationBox] box. + static String dnsProvider = 'dnsProvider'; + /// A String field of [serverLocation] box. static String serverLocation = 'serverLocation'; diff --git a/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart b/lib/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart similarity index 91% rename from lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart rename to lib/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart index ebabb5e7..97144d29 100644 --- a/lib/logic/cubit/forms/setup/initializing/provider_form_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart @@ -4,8 +4,8 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -class ProviderFormCubit extends FormCubit { - ProviderFormCubit(this.serverInstallationCubit) { +class ServerProviderFormCubit extends FormCubit { + ServerProviderFormCubit(this.serverInstallationCubit) { //final int tokenLength = // serverInstallationCubit.serverProviderApiTokenValidation().length; apiKey = FieldCubit( diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 244cd5de..1d8a4c5e 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -66,6 +66,15 @@ class ServerInstallationCubit extends Cubit { ); } + void setDnsProviderType(final DnsProvider providerType) async { + await repository.saveDnsProviderType(providerType); + ApiController.initDnsProviderApiFactory( + DnsProviderApiFactorySettings( + provider: providerType, + ), + ); + } + ProviderApiTokenValidation serverProviderApiTokenValidation() => ApiController.currentServerProviderApiFactory! .getServerProvider() @@ -101,16 +110,6 @@ class ServerInstallationCubit extends Cubit { Future isDnsProviderApiTokenValid( final String providerToken, ) async { - if (ApiController.currentDnsProviderApiFactory == null) { - // No other DNS provider is supported for now, - // so it's safe to hardcode Cloudflare - ApiController.initDnsProviderApiFactory( - DnsProviderApiFactorySettings( - provider: DnsProvider.cloudflare, - ), - ); - } - final APIGenericResult apiResponse = await ApiController.currentDnsProviderApiFactory! .getDnsProvider( diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 0cb476ea..817db846 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -688,6 +688,10 @@ class ServerInstallationRepository { await getIt().storeServerProviderType(type); } + Future saveDnsProviderType(final DnsProvider type) async { + await getIt().storeDnsProviderType(type); + } + Future saveServerProviderKey(final String key) async { await getIt().storeServerProviderKey(key); } diff --git a/lib/logic/get_it/api_config.dart b/lib/logic/get_it/api_config.dart index 6697c2f8..2ca9d53b 100644 --- a/lib/logic/get_it/api_config.dart +++ b/lib/logic/get_it/api_config.dart @@ -14,6 +14,7 @@ class ApiConfigModel { String? get serverType => _serverType; String? get dnsProviderKey => _dnsProviderKey; ServerProvider? get serverProvider => _serverProvider; + DnsProvider? get dnsProvider => _dnsProvider; BackblazeCredential? get backblazeCredential => _backblazeCredential; ServerDomain? get serverDomain => _serverDomain; BackblazeBucket? get backblazeBucket => _backblazeBucket; @@ -23,6 +24,7 @@ class ApiConfigModel { String? _dnsProviderKey; String? _serverType; ServerProvider? _serverProvider; + DnsProvider? _dnsProvider; ServerHostingDetails? _serverDetails; BackblazeCredential? _backblazeCredential; ServerDomain? _serverDomain; @@ -33,6 +35,11 @@ class ApiConfigModel { _serverProvider = value; } + Future storeDnsProviderType(final DnsProvider value) async { + await _box.put(BNames.dnsProvider, value); + _dnsProvider = value; + } + Future storeServerProviderKey(final String value) async { await _box.put(BNames.hetznerKey, value); _serverProviderKey = value; @@ -75,6 +82,7 @@ class ApiConfigModel { void clear() { _serverProviderKey = null; + _dnsProvider = null; _serverLocation = null; _dnsProviderKey = null; _backblazeCredential = null; @@ -95,5 +103,6 @@ class ApiConfigModel { _backblazeBucket = _box.get(BNames.backblazeBucket); _serverType = _box.get(BNames.serverTypeIdentifier); _serverProvider = _box.get(BNames.serverProvider); + _dnsProvider = _box.get(BNames.dnsProvider); } } diff --git a/lib/ui/pages/setup/initializing/dns_provider_picker.dart b/lib/ui/pages/setup/initializing/dns_provider_picker.dart new file mode 100644 index 00000000..74bb9b4b --- /dev/null +++ b/lib/ui/pages/setup/initializing/dns_provider_picker.dart @@ -0,0 +1,205 @@ +import 'package:cubit_form/cubit_form.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/brand_theme.dart'; +import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart'; +import 'package:selfprivacy/logic/models/hive/server_details.dart'; +import 'package:selfprivacy/logic/models/hive/server_domain.dart'; +import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; +import 'package:selfprivacy/ui/components/brand_button/filled_button.dart'; +import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; + +class DnsProviderPicker extends StatefulWidget { + const DnsProviderPicker({ + required this.formCubit, + required this.serverInstallationCubit, + super.key, + }); + + final DnsProviderFormCubit formCubit; + final ServerInstallationCubit serverInstallationCubit; + + @override + State createState() => _DnsProviderPickerState(); +} + +class _DnsProviderPickerState extends State { + DnsProvider selectedProvider = DnsProvider.unknown; + + void setProvider(final DnsProvider provider) { + setState(() { + selectedProvider = provider; + }); + } + + @override + Widget build(final BuildContext context) { + switch (selectedProvider) { + case DnsProvider.unknown: + return ProviderSelectionPage( + serverInstallationCubit: widget.serverInstallationCubit, + callback: setProvider, + ); + + case DnsProvider.cloudflare: + return ProviderInputDataPage( + providerCubit: widget.formCubit, + providerInfo: ProviderPageInfo( + providerType: DnsProvider.cloudflare, + pathToHow: 'how_cloudflare', + image: Image.asset( + 'assets/images/logos/cloudflare.png', + width: 150, + ), + ), + ); + + case DnsProvider.digitalOcean: + return ProviderInputDataPage( + providerCubit: widget.formCubit, + providerInfo: ProviderPageInfo( + providerType: DnsProvider.digitalOcean, + pathToHow: 'how_cloudflare', + image: Image.asset( + 'assets/images/logos/digital_ocean.png', + width: 150, + ), + ), + ); + } + } +} + +class ProviderPageInfo { + const ProviderPageInfo({ + required this.providerType, + required this.pathToHow, + required this.image, + }); + + final String pathToHow; + final Image image; + final DnsProvider providerType; +} + +class ProviderInputDataPage extends StatelessWidget { + const ProviderInputDataPage({ + required this.providerInfo, + required this.providerCubit, + super.key, + }); + + final ProviderPageInfo providerInfo; + final DnsProviderFormCubit providerCubit; + + @override + Widget build(final BuildContext context) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + providerInfo.image, + const SizedBox(height: 10), + Text( + 'initializing.connect_to_dns'.tr(), + style: Theme.of(context).textTheme.titleLarge, + ), + const Spacer(), + CubitFormTextField( + formFieldCubit: providerCubit.apiKey, + textAlign: TextAlign.center, + scrollPadding: const EdgeInsets.only(bottom: 70), + decoration: const InputDecoration( + hintText: 'Provider API Token', + ), + ), + const Spacer(), + FilledButton( + title: 'basis.connect'.tr(), + onPressed: () => providerCubit.trySubmit(), + ), + const SizedBox(height: 10), + OutlinedButton( + child: Text('initializing.how'.tr()), + onPressed: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (final BuildContext context) => BrandBottomSheet( + isExpended: true, + child: Padding( + padding: paddingH15V0, + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 16), + children: [ + BrandMarkdown( + fileName: providerInfo.pathToHow, + ), + ], + ), + ), + ), + ), + ), + ], + ); +} + +class ProviderSelectionPage extends StatelessWidget { + const ProviderSelectionPage({ + required this.callback, + required this.serverInstallationCubit, + super.key, + }); + + final Function callback; + final ServerInstallationCubit serverInstallationCubit; + + @override + Widget build(final BuildContext context) => Column( + children: [ + Text( + 'initializing.select_provider'.tr(), + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 10), + Text( + 'initializing.dns_provider_description'.tr(), + ), + const SizedBox(height: 10), + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 320, + ), + child: Row( + children: [ + InkWell( + onTap: () { + serverInstallationCubit + .setDnsProviderType(DnsProvider.cloudflare); + callback(DnsProvider.cloudflare); + }, + child: Image.asset( + 'assets/images/logos/cloudflare.png', + width: 150, + ), + ), + const SizedBox( + width: 20, + ), + InkWell( + onTap: () { + serverInstallationCubit + .setDnsProviderType(DnsProvider.digitalOcean); + callback(DnsProvider.digitalOcean); + }, + child: Image.asset( + 'assets/images/logos/digital_ocean.png', + width: 150, + ), + ), + ], + ), + ), + ], + ); +} diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index 1e3e7982..831b6ebb 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -2,7 +2,7 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.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/initializing/backblaze_form_cubit.dart'; @@ -17,6 +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/root_route.dart'; +import 'package:selfprivacy/ui/pages/setup/initializing/dns_provider_picker.dart'; import 'package:selfprivacy/ui/pages/setup/initializing/server_provider_picker.dart'; import 'package:selfprivacy/ui/pages/setup/initializing/server_type_picker.dart'; import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart'; @@ -37,7 +38,7 @@ class InitializingPage extends StatelessWidget { actualInitializingPage = [ () => _stepServerProviderToken(cubit), () => _stepServerType(cubit), - () => _stepCloudflare(cubit), + () => _stepDnsProviderToken(cubit), () => _stepBackblaze(cubit), () => _stepDomain(cubit), () => _stepUser(cubit), @@ -149,10 +150,11 @@ class InitializingPage extends StatelessWidget { final ServerInstallationCubit serverInstallationCubit, ) => BlocProvider( - create: (final context) => ProviderFormCubit(serverInstallationCubit), + create: (final context) => + ServerProviderFormCubit(serverInstallationCubit), child: Builder( builder: (final context) { - final providerCubit = context.watch(); + final providerCubit = context.watch(); return ServerProviderPicker( formCubit: providerCubit, serverInstallationCubit: serverInstallationCubit, @@ -165,7 +167,8 @@ class InitializingPage extends StatelessWidget { final ServerInstallationCubit serverInstallationCubit, ) => BlocProvider( - create: (final context) => ProviderFormCubit(serverInstallationCubit), + create: (final context) => + ServerProviderFormCubit(serverInstallationCubit), child: Builder( builder: (final context) => ServerTypePicker( serverInstallationCubit: serverInstallationCubit, @@ -182,48 +185,19 @@ class InitializingPage extends StatelessWidget { ); } - Widget _stepCloudflare(final ServerInstallationCubit initializingCubit) => + Widget _stepDnsProviderToken( + final ServerInstallationCubit initializingCubit, + ) => BlocProvider( create: (final context) => DnsProviderFormCubit(initializingCubit), child: Builder( - builder: (final context) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset( - 'assets/images/logos/cloudflare.png', - width: 150, - ), - const SizedBox(height: 10), - BrandText.h2('initializing.connect_cloudflare'.tr()), - const SizedBox(height: 10), - BrandText.body2('initializing.manage_domain_dns'.tr()), - const Spacer(), - CubitFormTextField( - formFieldCubit: context.read().apiKey, - textAlign: TextAlign.center, - scrollPadding: const EdgeInsets.only(bottom: 70), - decoration: InputDecoration( - hintText: 'initializing.cloudflare_api_token'.tr(), - ), - ), - const Spacer(), - BrandButton.rised( - onPressed: () => - context.read().trySubmit(), - text: 'basis.connect'.tr(), - ), - const SizedBox(height: 10), - BrandButton.text( - onPressed: () => _showModal( - context, - const _HowTo( - fileName: 'how_cloudflare', - ), - ), - title: 'initializing.how'.tr(), - ), - ], - ), + builder: (final context) { + final providerCubit = context.watch(); + return DnsProviderPicker( + formCubit: providerCubit, + serverInstallationCubit: initializingCubit, + ); + }, ), ); diff --git a/lib/ui/pages/setup/initializing/server_provider_picker.dart b/lib/ui/pages/setup/initializing/server_provider_picker.dart index 4934d1e3..947da77b 100644 --- a/lib/ui/pages/setup/initializing/server_provider_picker.dart +++ b/lib/ui/pages/setup/initializing/server_provider_picker.dart @@ -3,7 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; -import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_button/filled_button.dart'; @@ -16,7 +16,7 @@ class ServerProviderPicker extends StatefulWidget { super.key, }); - final ProviderFormCubit formCubit; + final ServerProviderFormCubit formCubit; final ServerInstallationCubit serverInstallationCubit; @override @@ -90,7 +90,7 @@ class ProviderInputDataPage extends StatelessWidget { }); final ProviderPageInfo providerInfo; - final ProviderFormCubit providerCubit; + final ServerProviderFormCubit providerCubit; @override Widget build(final BuildContext context) => Column( @@ -162,7 +162,7 @@ class ProviderSelectionPage extends StatelessWidget { ), const SizedBox(height: 10), Text( - 'initializing.place_where_data'.tr(), + 'initializing.server_provider_description'.tr(), ), const SizedBox(height: 10), ConstrainedBox( diff --git a/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart b/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart index 152e4308..f2771bf5 100644 --- a/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart +++ b/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_button/filled_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; @@ -19,11 +19,12 @@ class RecoveryServerProviderConnected extends StatelessWidget { context.watch(); return BlocProvider( - create: (final BuildContext context) => ProviderFormCubit(appConfig), + create: (final BuildContext context) => + ServerProviderFormCubit(appConfig), child: Builder( builder: (final BuildContext context) { final FormCubitState formCubitState = - context.watch().state; + context.watch().state; return BrandHeroScreen( heroTitle: 'recovering.server_provider_connected'.tr(), @@ -37,7 +38,7 @@ class RecoveryServerProviderConnected extends StatelessWidget { }, children: [ CubitFormTextField( - formFieldCubit: context.read().apiKey, + formFieldCubit: context.read().apiKey, decoration: InputDecoration( border: const OutlineInputBorder(), labelText: @@ -49,7 +50,7 @@ class RecoveryServerProviderConnected extends StatelessWidget { title: 'basis.continue'.tr(), onPressed: formCubitState.isSubmitting ? null - : () => context.read().trySubmit(), + : () => context.read().trySubmit(), ), const SizedBox(height: 16), BrandButton.text(