feat: Implement DNS provider picker page

pull/213/head
NaiJi ✨ 2022-12-30 07:25:18 +04:00
parent d333787f37
commit e560de58e7
11 changed files with 267 additions and 70 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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';

View File

@ -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(

View File

@ -66,6 +66,15 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
);
}
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<ServerInstallationState> {
Future<bool?> 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<bool> apiResponse =
await ApiController.currentDnsProviderApiFactory!
.getDnsProvider(

View File

@ -688,6 +688,10 @@ class ServerInstallationRepository {
await getIt<ApiConfigModel>().storeServerProviderType(type);
}
Future<void> saveDnsProviderType(final DnsProvider type) async {
await getIt<ApiConfigModel>().storeDnsProviderType(type);
}
Future<void> saveServerProviderKey(final String key) async {
await getIt<ApiConfigModel>().storeServerProviderKey(key);
}

View File

@ -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<void> storeDnsProviderType(final DnsProvider value) async {
await _box.put(BNames.dnsProvider, value);
_dnsProvider = value;
}
Future<void> 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);
}
}

View File

@ -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<DnsProviderPicker> createState() => _DnsProviderPickerState();
}
class _DnsProviderPickerState extends State<DnsProviderPicker> {
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<void>(
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,
),
),
],
),
),
],
);
}

View File

@ -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<ProviderFormCubit>();
final providerCubit = context.watch<ServerProviderFormCubit>();
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<DnsProviderFormCubit>().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<DnsProviderFormCubit>().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<DnsProviderFormCubit>();
return DnsProviderPicker(
formCubit: providerCubit,
serverInstallationCubit: initializingCubit,
);
},
),
);

View File

@ -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(

View File

@ -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<ServerInstallationCubit>();
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<ProviderFormCubit>().state;
context.watch<ServerProviderFormCubit>().state;
return BrandHeroScreen(
heroTitle: 'recovering.server_provider_connected'.tr(),
@ -37,7 +38,7 @@ class RecoveryServerProviderConnected extends StatelessWidget {
},
children: [
CubitFormTextField(
formFieldCubit: context.read<ProviderFormCubit>().apiKey,
formFieldCubit: context.read<ServerProviderFormCubit>().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<ProviderFormCubit>().trySubmit(),
: () => context.read<ServerProviderFormCubit>().trySubmit(),
),
const SizedBox(height: 16),
BrandButton.text(