feat(initializing): Implement server type selection for initialization page

routes-refactor
NaiJi ✨ 2022-10-15 21:51:37 +00:00
parent fe820ef5be
commit 72760e7980
10 changed files with 203 additions and 101 deletions

View File

@ -268,6 +268,8 @@
"place_where_data": "A place where your data and SelfPrivacy services will reside:",
"how": "How to obtain API token",
"provider_bad_key_error": "Provider API key is invalid",
"no_locations_found": "No available locations found. Make sure your account is accessible.",
"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",

View File

@ -267,6 +267,8 @@
"place_where_data": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:",
"how": "Как получить API Token",
"provider_bad_key_error": "API ключ провайдера неверен",
"no_locations_found": "Не найдено локаций. Убедитесь, что ваш аккаунт доступен.",
"no_server_types_found": "Не удалось получить список серверов. Убедитесь, что ваш аккаунт доступен и попытайтесь сменить локацию сервера.",
"cloudflare_bad_key_error": "Cloudflare API ключ неверен",
"backblaze_bad_key_error": "Информация о Backblaze хранилище неверна",
"connect_cloudflare": "Подключите CloudFlare",

View File

@ -90,6 +90,9 @@ class BNames {
/// A String field of [serverInstallationBox] box.
static String cloudFlareKey = 'cloudFlareKey';
/// A String field of [serverTypeIdentifier] box.
static String serverTypeIdentifier = 'serverTypeIdentifier';
/// A [User] field of [serverInstallationBox] box.
static String rootUser = 'rootUser';

View File

@ -6,8 +6,6 @@ import 'package:equatable/equatable.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
@ -82,7 +80,9 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
repository.serverProviderApiFactory!
.getServerProvider(
settings: const ServerProviderApiSettings(
region: 'fra1', isWithToken: false),
region: 'fra1',
isWithToken: false,
),
)
.isApiTokenValid(providerToken);
@ -101,12 +101,25 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
}
return repository.serverProviderApiFactory!
.getServerProvider(
settings: const ServerProviderApiSettings(region: 'fra1'),
)
.getAvailableLocations();
.getServerProvider(
settings: const ServerProviderApiSettings(region: 'fra1'),
)
.getAvailableLocations();
}
Future<List<ServerType>> fetchAvailableTypesByLocation(
final ServerProviderLocation location,
) async {
if (repository.serverProviderApiFactory == null) {
return [];
}
return repository.serverProviderApiFactory!
.getServerProvider(
settings: const ServerProviderApiSettings(region: 'fra1'),
)
.getServerTypesByLocation(location: location);
}
void setServerProviderKey(final String serverProviderKey) async {
await repository.saveServerProviderKey(serverProviderKey);
@ -128,6 +141,16 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
);
}
void setServerType(final String serverTypeId) async {
await repository.saveServerType(serverTypeId);
emit(
(state as ServerInstallationNotFinished).copyWith(
serverTypeIdentificator: serverTypeId,
),
);
}
void setCloudflareKey(final String cloudFlareKey) async {
if (state is ServerInstallationRecovery) {
setAndValidateCloudflareToken(cloudFlareKey);

View File

@ -50,6 +50,7 @@ class ServerInstallationRepository {
Future<ServerInstallationState> load() async {
final String? providerApiToken = getIt<ApiConfigModel>().serverProviderKey;
final String? cloudflareToken = getIt<ApiConfigModel>().cloudFlareKey;
final String? serverTypeIdentificator = getIt<ApiConfigModel>().serverType;
final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain;
final BackblazeCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
@ -73,6 +74,7 @@ class ServerInstallationRepository {
if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
return ServerInstallationFinished(
providerApiToken: providerApiToken!,
serverTypeIdentificator: serverTypeIdentificator!,
cloudFlareKey: cloudflareToken!,
serverDomain: serverDomain!,
backblazeCredential: backblazeCredential!,
@ -697,6 +699,10 @@ class ServerInstallationRepository {
await getIt<ApiConfigModel>().storeServerProviderKey(key);
}
Future<void> saveServerType(final String serverType) async {
await getIt<ApiConfigModel>().storeServerTypeIdentifier(serverType);
}
Future<void> deleteServerProviderKey() async {
await box.delete(BNames.hetznerKey);
getIt<ApiConfigModel>().init();

View File

@ -3,7 +3,7 @@ part of '../server_installation/server_installation_cubit.dart';
abstract class ServerInstallationState extends Equatable {
const ServerInstallationState({
required this.providerApiToken,
required this.serverType,
required this.serverTypeIdentificator,
required this.cloudFlareKey,
required this.backblazeCredential,
required this.serverDomain,
@ -17,6 +17,7 @@ abstract class ServerInstallationState extends Equatable {
@override
List<Object?> get props => [
providerApiToken,
serverTypeIdentificator,
cloudFlareKey,
backblazeCredential,
serverDomain,
@ -28,17 +29,17 @@ abstract class ServerInstallationState extends Equatable {
final String? providerApiToken;
final String? cloudFlareKey;
final String? serverTypeIdentificator;
final BackblazeCredential? backblazeCredential;
final ServerDomain? serverDomain;
final User? rootUser;
final ServerHostingDetails? serverDetails;
final ServerType? serverType;
final bool isServerStarted;
final bool isServerResetedFirstTime;
final bool isServerResetedSecondTime;
bool get isServerProviderApiKeyFilled => providerApiToken != null;
bool get isServerTypeFilled => serverType != null;
bool get isServerTypeFilled => serverTypeIdentificator != null;
bool get isDnsProviderFilled => cloudFlareKey != null;
bool get isBackupsProviderFilled => backblazeCredential != null;
bool get isDomainSelected => serverDomain != null;
@ -85,7 +86,7 @@ class TimerState extends ServerInstallationNotFinished {
this.duration,
}) : super(
providerApiToken: dataState.providerApiToken,
serverType: dataState.serverType,
serverTypeIdentificator: dataState.serverTypeIdentificator,
cloudFlareKey: dataState.cloudFlareKey,
backblazeCredential: dataState.backblazeCredential,
serverDomain: dataState.serverDomain,
@ -130,7 +131,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
required this.isLoading,
required this.dnsMatches,
super.providerApiToken,
super.serverType,
super.serverTypeIdentificator,
super.cloudFlareKey,
super.backblazeCredential,
super.serverDomain,
@ -143,7 +144,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
@override
List<Object?> get props => [
providerApiToken,
serverType,
serverTypeIdentificator,
cloudFlareKey,
backblazeCredential,
serverDomain,
@ -157,7 +158,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
ServerInstallationNotFinished copyWith({
final String? providerApiToken,
final ServerType? serverType,
final String? serverTypeIdentificator,
final String? cloudFlareKey,
final BackblazeCredential? backblazeCredential,
final ServerDomain? serverDomain,
@ -171,7 +172,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
}) =>
ServerInstallationNotFinished(
providerApiToken: providerApiToken ?? this.providerApiToken,
serverType: serverType ?? this.serverType,
serverTypeIdentificator: serverTypeIdentificator ?? this.serverTypeIdentificator,
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
serverDomain: serverDomain ?? this.serverDomain,
@ -188,7 +189,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
ServerInstallationFinished finish() => ServerInstallationFinished(
providerApiToken: providerApiToken!,
serverType: serverType!,
serverTypeIdentificator: serverTypeIdentificator!,
cloudFlareKey: cloudFlareKey!,
backblazeCredential: backblazeCredential!,
serverDomain: serverDomain!,
@ -204,6 +205,7 @@ class ServerInstallationEmpty extends ServerInstallationNotFinished {
const ServerInstallationEmpty()
: super(
providerApiToken: null,
serverTypeIdentificator: null,
cloudFlareKey: null,
backblazeCredential: null,
serverDomain: null,
@ -220,7 +222,7 @@ class ServerInstallationEmpty extends ServerInstallationNotFinished {
class ServerInstallationFinished extends ServerInstallationState {
const ServerInstallationFinished({
required String super.providerApiToken,
required ServerType super.serverType,
required String super.serverTypeIdentificator,
required String super.cloudFlareKey,
required BackblazeCredential super.backblazeCredential,
required ServerDomain super.serverDomain,
@ -234,7 +236,7 @@ class ServerInstallationFinished extends ServerInstallationState {
@override
List<Object?> get props => [
providerApiToken,
serverType,
serverTypeIdentificator,
cloudFlareKey,
backblazeCredential,
serverDomain,
@ -273,7 +275,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
required this.currentStep,
required this.recoveryCapabilities,
super.providerApiToken,
super.serverType,
super.serverTypeIdentificator,
super.cloudFlareKey,
super.backblazeCredential,
super.serverDomain,
@ -290,7 +292,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
@override
List<Object?> get props => [
providerApiToken,
serverType,
serverTypeIdentificator,
cloudFlareKey,
backblazeCredential,
serverDomain,
@ -303,7 +305,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
ServerInstallationRecovery copyWith({
final String? providerApiToken,
final ServerType? serverType,
final String? serverTypeIdentificator,
final String? cloudFlareKey,
final BackblazeCredential? backblazeCredential,
final ServerDomain? serverDomain,
@ -314,7 +316,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
}) =>
ServerInstallationRecovery(
providerApiToken: providerApiToken ?? this.providerApiToken,
serverType: serverType ?? this.serverType,
serverTypeIdentificator: serverTypeIdentificator ?? this.serverTypeIdentificator,
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
serverDomain: serverDomain ?? this.serverDomain,
@ -326,7 +328,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
ServerInstallationFinished finish() => ServerInstallationFinished(
providerApiToken: providerApiToken!,
serverType: serverType!,
serverTypeIdentificator: serverTypeIdentificator!,
cloudFlareKey: cloudFlareKey!,
backblazeCredential: backblazeCredential!,
serverDomain: serverDomain!,

View File

@ -10,6 +10,7 @@ class ApiConfigModel {
ServerHostingDetails? get serverDetails => _serverDetails;
String? get serverProviderKey => _serverProviderKey;
String? get serverType => _serverType;
String? get cloudFlareKey => _cloudFlareKey;
BackblazeCredential? get backblazeCredential => _backblazeCredential;
ServerDomain? get serverDomain => _serverDomain;
@ -17,6 +18,7 @@ class ApiConfigModel {
String? _serverProviderKey;
String? _cloudFlareKey;
String? _serverType;
ServerHostingDetails? _serverDetails;
BackblazeCredential? _backblazeCredential;
ServerDomain? _serverDomain;
@ -32,6 +34,11 @@ class ApiConfigModel {
_cloudFlareKey = value;
}
Future<void> storeServerTypeIdentifier(final String typeIdentifier) async {
await _box.put(BNames.serverTypeIdentifier, typeIdentifier);
_serverType = typeIdentifier;
}
Future<void> storeBackblazeCredential(final BackblazeCredential value) async {
await _box.put(BNames.backblazeCredential, value);
_backblazeCredential = value;

View File

@ -1,7 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
class DiskSize {
const DiskSize({final this.byte = 0});
const DiskSize({this.byte = 0});
DiskSize.fromKibibyte(final double kibibyte)
: this(byte: (kibibyte * 1024).round());

View File

@ -140,15 +140,17 @@ class InitializingPage extends StatelessWidget {
}
}
Widget _stepServerProviderToken(final ServerInstallationCubit serverInstallationCubit) =>
Widget _stepServerProviderToken(
final ServerInstallationCubit serverInstallationCubit) =>
ServerProviderPicker(
serverInstallationCubit: serverInstallationCubit,
);
Widget _stepServerType(final ServerInstallationCubit serverInstallationCubit) =>
ServerTypePicker(
serverInstallationCubit: serverInstallationCubit,
);
Widget _stepServerType(
final ServerInstallationCubit serverInstallationCubit) =>
ServerTypePicker(
serverInstallationCubit: serverInstallationCubit,
);
void _showModal(final BuildContext context, final Widget widget) {
showModalBottomSheet<void>(

View File

@ -1,8 +1,9 @@
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/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/server_provider_location.dart';
import 'package:selfprivacy/logic/models/server_type.dart';
class ServerTypePicker extends StatefulWidget {
const ServerTypePicker({
@ -18,6 +19,7 @@ class ServerTypePicker extends StatefulWidget {
class _ServerTypePickerState extends State<ServerTypePicker> {
ServerProviderLocation? serverProviderLocation;
ServerType? serverType;
void setServerProviderLocation(final ServerProviderLocation location) {
setState(() {
@ -27,92 +29,145 @@ class _ServerTypePickerState extends State<ServerTypePicker> {
@override
Widget build(final BuildContext context) {
if (serverProviderLocation == null) {
return SelectLocationPage(
serverInstallationCubit: widget.serverInstallationCubit,
callback: setServerProviderLocation,
);
}
return SelectTypePage(
location: serverProviderLocation!,
serverInstallationCubit: widget.serverInstallationCubit,
);
}
}
class SelectLocationPage extends StatelessWidget {
const SelectLocationPage({
required this.serverInstallationCubit,
super.key,
});
final ServerInstallationCubit serverInstallationCubit;
@override
Widget build(final BuildContext context) => FutureBuilder(
future: serverInstallationCubit.repository.serverProviderApiFactory,
builder: (
final BuildContext context,
final AsyncSnapshot<Object?> snapshot,
) {
if (snapshot.hasData) {
return _KeyDisplay(
newDeviceKey: snapshot.data.toString(),
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
}
class ProviderSelectionPage extends StatelessWidget {
const ProviderSelectionPage({
required this.callback,
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.place_where_data'.tr(),
),
const SizedBox(height: 10),
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 320,
),
child: Row(
Widget build(final BuildContext context) => FutureBuilder(
future: serverInstallationCubit.fetchAvailableLocations(),
builder: (
final BuildContext context,
final AsyncSnapshot<Object?> snapshot,
) {
if (snapshot.hasData) {
if ((snapshot.data as List<ServerProviderLocation>).isEmpty) {
return Text('initializing.no_locations_found'.tr());
}
return ListView(
padding: paddingH15V0,
children: [
InkWell(
onTap: () {
context
.read<ServerInstallationCubit>()
.setServerProviderType(ServerProvider.hetzner);
callback(ServerProvider.hetzner);
},
child: Image.asset(
'assets/images/logos/hetzner.png',
width: 150,
),
),
const SizedBox(
width: 20,
),
InkWell(
onTap: () {
context
.read<ServerInstallationCubit>()
.setServerProviderType(ServerProvider.digitalOcean);
callback(ServerProvider.digitalOcean);
},
child: Image.asset(
'assets/images/logos/digital_ocean.png',
width: 150,
...(snapshot.data! as List<ServerProviderLocation>).map(
(final location) => InkWell(
onTap: () {
callback(location);
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (location.flag != null) Text(location.flag!),
const SizedBox(height: 8),
Text(location.title),
const SizedBox(height: 8),
if (location.description != null)
Text(location.description!),
],
),
],
),
),
),
),
),
const SizedBox(height: 24),
],
),
),
],
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
);
}
class SelectTypePage extends StatelessWidget {
const SelectTypePage({
required this.location,
required this.serverInstallationCubit,
super.key,
});
final ServerProviderLocation location;
final ServerInstallationCubit serverInstallationCubit;
@override
Widget build(final BuildContext context) => FutureBuilder(
future: serverInstallationCubit.fetchAvailableTypesByLocation(location),
builder: (
final BuildContext context,
final AsyncSnapshot<Object?> snapshot,
) {
if (snapshot.hasData) {
if ((snapshot.data as List<ServerType>).isEmpty) {
return Text('initializing.no_server_types_found'.tr());
}
return ListView(
padding: paddingH15V0,
children: [
...(snapshot.data! as List<ServerType>).map(
(final type) => InkWell(
onTap: () {
serverInstallationCubit.setServerType(type.identifier);
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(type.title),
const SizedBox(height: 8),
Text('cores: $type.cores'),
const SizedBox(height: 8),
Text('ram: $type.ram'),
const SizedBox(height: 8),
Text('disk: $type.disk.gibibyte'),
const SizedBox(height: 8),
Text('price: $type.price.value $type.price.currency'),
],
),
],
),
),
),
),
),
const SizedBox(height: 24),
],
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
);
}