refactor(initializing): Refresh the server istallation UI

pull/158/head
Inex Code 2022-12-31 07:16:10 +03:00
parent 040de69268
commit b007fec75b
14 changed files with 3222 additions and 385 deletions

View File

@ -0,0 +1,6 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.0013 24.0128V19.3592C16.927 19.3592 20.7505 14.4743 18.8591 9.2901C18.1652 7.37152 16.6276 5.83395 14.709 5.14C9.52482 3.26224 4.63995 7.07217 4.63995 11.9979H0C0 4.14669 7.59264 -1.97641 15.8248 0.595295C19.417 1.72467 22.2881 4.58211 23.4038 8.17433C25.9755 16.4201 19.8661 24.0128 12.0013 24.0128Z" fill="white"/>
<path d="M12.0149 19.3729H7.38855V14.7466H12.0149V19.3729Z" fill="white"/>
<path d="M7.38861 22.9376H3.82361V19.3726H7.38861V22.9376Z" fill="white"/>
<path d="M3.82354 19.373H0.843628V16.3931H3.82354V19.373Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 657 B

View File

@ -0,0 +1,10 @@
<svg width="24" height="23" viewBox="0 0 24 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_51804_9018)">
<path d="M22.5704 0H19.3252C18.5948 0 18.2817 0.302609 18.2817 1.04348V9.25565H5.35304V1.04348C5.35304 0.313043 5.05044 0 4.30957 0H1.04348C0.302609 0 0 0.302609 0 1.04348V22.1739C0 22.9148 0.302609 23.2174 1.04348 23.2174H4.30957C5.04 23.2174 5.35304 22.9252 5.35304 22.1739V13.8261H18.2922V22.1739C18.2922 22.9043 18.5948 23.2174 19.3357 23.2174H22.5809C23.3113 23.2174 23.6243 22.9148 23.6243 22.1739V1.04348C23.6035 0.333913 23.3009 0 22.5704 0Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_51804_9018">
<rect width="24" height="22.9565" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 722 B

File diff suppressed because it is too large Load Diff

View File

@ -90,7 +90,8 @@ class BackblazeApi extends ApiMap {
), ),
); );
if (response.statusCode == HttpStatus.ok) { if (response.statusCode == HttpStatus.ok) {
isTokenValid = response.data['allowed']['capabilities'].contains('listBuckets'); isTokenValid =
response.data['allowed']['capabilities'].contains('listBuckets');
} else if (response.statusCode == HttpStatus.unauthorized) { } else if (response.statusCode == HttpStatus.unauthorized) {
isTokenValid = false; isTokenValid = false;
} else { } else {

View File

@ -1,7 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:material_color_utilities/material_color_utilities.dart'
as color_utils;
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
export 'package:provider/provider.dart'; export 'package:provider/provider.dart';
@ -20,7 +25,7 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
Box box = Hive.box(BNames.appSettingsBox); Box box = Hive.box(BNames.appSettingsBox);
void load() { void load() async {
final bool? isDarkModeOn = box.get(BNames.isDarkModeOn); final bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing); final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing);
emit( emit(
@ -29,6 +34,14 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
isOnboardingShowing: isOnboardingShowing, isOnboardingShowing: isOnboardingShowing,
), ),
); );
WidgetsFlutterBinding.ensureInitialized();
final color_utils.CorePalette? colorPalette =
await AppThemeFactory.getCorePalette();
emit(
state.copyWith(
corePalette: colorPalette,
),
);
} }
void updateDarkMode({required final bool isDarkModeOn}) { void updateDarkMode({required final bool isDarkModeOn}) {

View File

@ -4,20 +4,27 @@ class AppSettingsState extends Equatable {
const AppSettingsState({ const AppSettingsState({
required this.isDarkModeOn, required this.isDarkModeOn,
required this.isOnboardingShowing, required this.isOnboardingShowing,
this.corePalette,
}); });
final bool isDarkModeOn; final bool isDarkModeOn;
final bool isOnboardingShowing; final bool isOnboardingShowing;
final color_utils.CorePalette? corePalette;
AppSettingsState copyWith({ AppSettingsState copyWith({
final bool? isDarkModeOn, final bool? isDarkModeOn,
final bool? isOnboardingShowing, final bool? isOnboardingShowing,
final color_utils.CorePalette? corePalette,
}) => }) =>
AppSettingsState( AppSettingsState(
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn, isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing, isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing,
corePalette: corePalette ?? this.corePalette,
); );
color_utils.CorePalette get corePaletteOrDefault =>
corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value);
@override @override
List<Object> get props => [isDarkModeOn, isOnboardingShowing]; List<dynamic> get props => [isDarkModeOn, isOnboardingShowing, corePalette];
} }

View File

@ -95,4 +95,15 @@ enum ServerProvider {
return unknown; return unknown;
} }
} }
String get displayName {
switch (this) {
case ServerProvider.hetzner:
return 'Hetzner Cloud';
case ServerProvider.digitalOcean:
return 'Digital Ocean';
default:
return 'Unknown';
}
}
} }

View File

@ -29,8 +29,7 @@ abstract class AppThemeFactory {
brightness: brightness, brightness: brightness,
); );
final ColorScheme colorScheme = final ColorScheme colorScheme = dynamicColorsScheme ?? fallbackColorScheme;
dynamicColorsScheme ?? fallbackColorScheme;
final Typography appTypography = Typography.material2021(); final Typography appTypography = Typography.material2021();

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/text_themes.dart'; import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class ProgressBar extends StatefulWidget { class ProgressBar extends StatefulWidget {
const ProgressBar({ const ProgressBar({
@ -63,13 +62,6 @@ class _ProgressBarState extends State<ProgressBar> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
BrandText.h2('Progress'),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: even,
),
const SizedBox(height: 7),
Container( Container(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -98,11 +90,6 @@ class _ProgressBarState extends State<ProgressBar> {
), ),
), ),
), ),
const SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: odd,
),
], ],
); );
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -49,11 +48,16 @@ class _OnboardingPageState extends State<OnboardingPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 30), const SizedBox(height: 30),
BrandText.h2( Text(
'onboarding.page1_title'.tr(), 'onboarding.page1_title'.tr(),
style: Theme.of(context).textTheme.headlineSmall,
), ),
const SizedBox(height: 20), const SizedBox(height: 16),
BrandText.body2('onboarding.page1_text'.tr()), Text(
'onboarding.page1_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
Flexible( Flexible(
child: Center( child: Center(
child: Image.asset( child: Image.asset(
@ -86,34 +90,49 @@ class _OnboardingPageState extends State<OnboardingPage> {
maxHeight: MediaQuery.of(context).size.height, maxHeight: MediaQuery.of(context).size.height,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 30), const SizedBox(height: 30),
BrandText.h2('onboarding.page2_title'.tr()), Text(
const SizedBox(height: 20), 'onboarding.page2_title'.tr(),
BrandText.body2('onboarding.page2_text'.tr()), style: Theme.of(context).textTheme.headlineSmall,
const SizedBox(height: 20),
Center(
child: Image.asset(
_fileName(
context: context,
path: 'assets/images/onboarding',
fileExtention: 'png',
fileName: 'logos_line',
),
),
), ),
Flexible( const SizedBox(height: 16),
child: Center( Text(
child: Image.asset( 'onboarding.page2_text'.tr(),
_fileName( style: Theme.of(context).textTheme.bodyMedium,
context: context,
path: 'assets/images/onboarding',
fileExtention: 'png',
fileName: 'onboarding2',
),
),
),
), ),
const SizedBox(height: 16),
Text(
'onboarding.page2_server_provider_title'.tr(),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
Text(
'onboarding.page2_server_provider_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
Text(
'onboarding.page2_dns_provider_title'.tr(),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
Text(
'onboarding.page2_dns_provider_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
Text(
'onboarding.page2_backup_provider_title'.tr(),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
Text(
'onboarding.page2_backup_provider_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
BrandButton.rised( BrandButton.rised(
onPressed: () { onPressed: () {
context.read<AppSettingsCubit>().turnOffOnboarding(); context.read<AppSettingsCubit>().turnOffOnboarding();

View File

@ -1,5 +1,3 @@
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';

View File

@ -11,7 +11,6 @@ import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_setup_cu
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart';
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.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_md/brand_md.dart'; import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; 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/brand_timer/brand_timer.dart';
@ -56,88 +55,91 @@ class InitializingPage extends StatelessWidget {
.pushReplacement(materialRoute(const RootPage())); .pushReplacement(materialRoute(const RootPage()));
} }
}, },
child: SafeArea( child: Scaffold(
child: Scaffold( appBar: AppBar(
body: SingleChildScrollView( actions: [
child: Column( if (cubit.state is ServerInstallationFinished)
mainAxisAlignment: MainAxisAlignment.center, IconButton(
children: [ icon: const Icon(Icons.check),
Padding( onPressed: () {
padding: paddingH15V0.copyWith(top: 10, bottom: 10), Navigator.of(context)
child: cubit.state is ServerInstallationFinished .pushReplacement(materialRoute(const RootPage()));
? const SizedBox( },
height: 80, )
) ],
: ProgressBar( bottom: PreferredSize(
steps: const [ preferredSize: const Size.fromHeight(28),
'Hosting', child: Padding(
'Server Type', padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
'CloudFlare', child: ProgressBar(
'Backblaze', steps: const [
'Domain', 'Hosting',
'User', 'Server Type',
'Server', 'CloudFlare',
'Installation', 'Backblaze',
], 'Domain',
activeIndex: cubit.state.porgressBar, 'User',
), 'Server',
'Installation',
],
activeIndex: cubit.state.porgressBar,
),
),
),
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 0, 16.0, 0.0),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: actualInitializingPage,
), ),
if (cubit.state.porgressBar == ),
ServerSetupProgress.serverProviderFilled.index) ConstrainedBox(
BrandText.h2( constraints: BoxConstraints(
'initializing.choose_location_type'.tr(), minHeight: MediaQuery.of(context).size.height -
), MediaQuery.of(context).padding.top -
_addCard( MediaQuery.of(context).padding.bottom -
AnimatedSwitcher( 566,
duration: const Duration(milliseconds: 300),
child: actualInitializingPage,
),
), ),
ConstrainedBox( child: Column(
constraints: BoxConstraints( mainAxisAlignment: MainAxisAlignment.center,
minHeight: MediaQuery.of(context).size.height - children: [
MediaQuery.of(context).padding.top - Container(
MediaQuery.of(context).padding.bottom - alignment: Alignment.center,
566, child: BrandButton.text(
), title: cubit.state is ServerInstallationFinished
child: Column( ? 'basis.close'.tr()
mainAxisAlignment: MainAxisAlignment.center, : 'basis.later'.tr(),
children: [ onPressed: () {
Navigator.of(context).pushAndRemoveUntil(
materialRoute(const RootPage()),
(final predicate) => false,
);
},
),
),
if (cubit.state is ServerInstallationEmpty)
Container( Container(
alignment: Alignment.center, alignment: Alignment.center,
child: BrandButton.text( child: BrandButton.text(
title: cubit.state is ServerInstallationFinished title: 'basis.connect_to_existing'.tr(),
? 'basis.close'.tr()
: 'basis.later'.tr(),
onPressed: () { onPressed: () {
Navigator.of(context).pushAndRemoveUntil( Navigator.of(context).push(
materialRoute(const RootPage()), materialRoute(
(final predicate) => false, const RecoveryRouting(),
),
); );
}, },
), ),
), )
if (cubit.state is ServerInstallationFinished) ],
Container()
else
Container(
alignment: Alignment.center,
child: BrandButton.text(
title: 'basis.connect_to_existing'.tr(),
onPressed: () {
Navigator.of(context).push(
materialRoute(
const RecoveryRouting(),
),
);
},
),
)
],
),
), ),
], ),
), ],
), ),
), ),
), ),
@ -189,15 +191,16 @@ class InitializingPage extends StatelessWidget {
builder: (final context) => Column( builder: (final context) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Image.asset( Text(
'assets/images/logos/cloudflare.png', '${'initializing.connect_to_server_provider'.tr()}Cloudflare',
width: 150, style: Theme.of(context).textTheme.headlineSmall,
), ),
const SizedBox(height: 10), const SizedBox(height: 16),
BrandText.h2('initializing.connect_cloudflare'.tr()), Text(
const SizedBox(height: 10), 'initializing.manage_domain_dns'.tr(),
BrandText.body2('initializing.manage_domain_dns'.tr()), style: Theme.of(context).textTheme.bodyMedium,
const Spacer(), ),
const SizedBox(height: 32),
CubitFormTextField( CubitFormTextField(
formFieldCubit: context.read<DnsProviderFormCubit>().apiKey, formFieldCubit: context.read<DnsProviderFormCubit>().apiKey,
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -206,7 +209,7 @@ class InitializingPage extends StatelessWidget {
hintText: 'initializing.cloudflare_api_token'.tr(), hintText: 'initializing.cloudflare_api_token'.tr(),
), ),
), ),
const Spacer(), const SizedBox(height: 32),
BrandButton.rised( BrandButton.rised(
onPressed: () => onPressed: () =>
context.read<DnsProviderFormCubit>().trySubmit(), context.read<DnsProviderFormCubit>().trySubmit(),
@ -236,14 +239,11 @@ class InitializingPage extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Image.asset( Text(
'assets/images/logos/backblaze.png', '${'initializing.connect_to_server_provider'.tr()}Backblaze',
height: 50, style: Theme.of(context).textTheme.headlineSmall,
), ),
const SizedBox(height: 10), const SizedBox(height: 32),
BrandText.h2('initializing.connect_backblaze_storage'.tr()),
const SizedBox(height: 10),
const Spacer(),
CubitFormTextField( CubitFormTextField(
formFieldCubit: context.read<BackblazeFormCubit>().keyId, formFieldCubit: context.read<BackblazeFormCubit>().keyId,
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -252,7 +252,7 @@ class InitializingPage extends StatelessWidget {
hintText: 'KeyID', hintText: 'KeyID',
), ),
), ),
const Spacer(), const SizedBox(height: 16),
CubitFormTextField( CubitFormTextField(
formFieldCubit: formFieldCubit:
context.read<BackblazeFormCubit>().applicationKey, context.read<BackblazeFormCubit>().applicationKey,
@ -262,7 +262,7 @@ class InitializingPage extends StatelessWidget {
hintText: 'Master Application Key', hintText: 'Master Application Key',
), ),
), ),
const Spacer(), const SizedBox(height: 32),
BrandButton.rised( BrandButton.rised(
onPressed: formCubitState.isSubmitting onPressed: formCubitState.isSubmitting
? null ? null
@ -292,91 +292,85 @@ class InitializingPage extends StatelessWidget {
builder: (final context) { builder: (final context) {
final DomainSetupState state = final DomainSetupState state =
context.watch<DomainSetupCubit>().state; context.watch<DomainSetupCubit>().state;
return Column( return SizedBox(
crossAxisAlignment: CrossAxisAlignment.start, width: double.infinity,
children: [ child: Column(
Image.asset( crossAxisAlignment: CrossAxisAlignment.start,
'assets/images/logos/cloudflare.png', children: [
width: 150, Text(
), 'initializing.use_this_domain'.tr(),
const SizedBox(height: 30), style: Theme.of(context).textTheme.headlineSmall,
BrandText.h2('basis.domain'.tr()),
const SizedBox(height: 10),
if (state is Empty)
BrandText.body2('initializing.no_connected_domains'.tr()),
if (state is Loading)
BrandText.body2(
state.type == LoadingTypes.loadingDomain
? 'initializing.loading_domain_list'.tr()
: 'basis.saving'.tr(),
), ),
if (state is MoreThenOne) const SizedBox(height: 16),
BrandText.body2( Text(
'initializing.found_more_domains'.tr(), 'initializing.use_this_domain_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
), ),
if (state is Loaded) ...[ const SizedBox(height: 32),
const SizedBox(height: 10), if (state is Empty)
Row( Text(
mainAxisSize: MainAxisSize.min, 'initializing.no_connected_domains'.tr(),
mainAxisAlignment: MainAxisAlignment.end, style: Theme.of(context).textTheme.bodyMedium,
crossAxisAlignment: CrossAxisAlignment.center, ),
children: [ if (state is Loading)
Expanded( Text(
child: BrandText.h3( state.type == LoadingTypes.loadingDomain
? 'initializing.loading_domain_list'.tr()
: 'basis.saving'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
if (state is MoreThenOne)
Text(
'initializing.found_more_domains'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
if (state is Loaded) ...[
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
state.domain, state.domain,
style: Theme.of(context)
.textTheme
.headlineMedium
?.copyWith(
color:
Theme.of(context).colorScheme.onBackground,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
),
SizedBox(
width: 56,
child: BrandButton.rised(
onPressed: () =>
context.read<DomainSetupCubit>().load(),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: const [
Icon(
Icons.refresh,
color: Colors.white,
),
],
),
),
),
],
)
],
if (state is Empty) ...[
const SizedBox(height: 30),
BrandButton.rised(
onPressed: () => context.read<DomainSetupCubit>().load(),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.refresh,
color: Colors.white,
),
const SizedBox(width: 10),
BrandText.buttonTitleText('domain.update_list'.tr()),
], ],
), ),
), ],
if (state is Empty) ...[
const SizedBox(height: 30),
BrandButton.rised(
onPressed: () => context.read<DomainSetupCubit>().load(),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.refresh,
color: Colors.white,
),
const SizedBox(width: 10),
BrandText.buttonTitleText('domain.update_list'.tr()),
],
),
),
],
if (state is Loaded) ...[
const SizedBox(height: 32),
BrandButton.rised(
onPressed: () =>
context.read<DomainSetupCubit>().saveDomain(),
text: 'initializing.save_domain'.tr(),
),
],
], ],
if (state is Loaded) ...[ ),
const SizedBox(height: 30),
BrandButton.rised(
onPressed: () =>
context.read<DomainSetupCubit>().saveDomain(),
text: 'initializing.save_domain'.tr(),
),
],
const SizedBox(
height: 10,
width: double.infinity,
),
],
); );
}, },
), ),
@ -393,12 +387,16 @@ class InitializingPage extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
BrandText.h2('initializing.create_master_account'.tr()), Text(
const SizedBox(height: 10), 'initializing.create_master_account'.tr(),
BrandText.body2( style: Theme.of(context).textTheme.headlineSmall,
'initializing.enter_username_and_password'.tr(),
), ),
const Spacer(), const SizedBox(height: 16),
Text(
'initializing.enter_username_and_password'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
if (formCubitState.isErrorShown) const SizedBox(height: 16),
if (formCubitState.isErrorShown) if (formCubitState.isErrorShown)
Text( Text(
'users.username_rule'.tr(), 'users.username_rule'.tr(),
@ -406,7 +404,7 @@ class InitializingPage extends StatelessWidget {
color: Theme.of(context).colorScheme.error, color: Theme.of(context).colorScheme.error,
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 32),
CubitFormTextField( CubitFormTextField(
formFieldCubit: context.read<RootUserFormCubit>().userName, formFieldCubit: context.read<RootUserFormCubit>().userName,
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -415,7 +413,7 @@ class InitializingPage extends StatelessWidget {
hintText: 'basis.username'.tr(), hintText: 'basis.username'.tr(),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 16),
BlocBuilder<FieldCubit<bool>, FieldCubitState<bool>>( BlocBuilder<FieldCubit<bool>, FieldCubitState<bool>>(
bloc: context.read<RootUserFormCubit>().isVisible, bloc: context.read<RootUserFormCubit>().isVisible,
builder: (final context, final state) { builder: (final context, final state) {
@ -446,7 +444,7 @@ class InitializingPage extends StatelessWidget {
); );
}, },
), ),
const Spacer(), const SizedBox(height: 32),
BrandButton.rised( BrandButton.rised(
onPressed: formCubitState.isSubmitting onPressed: formCubitState.isSubmitting
? null ? null
@ -466,11 +464,16 @@ class InitializingPage extends StatelessWidget {
builder: (final context) => Column( builder: (final context) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Spacer(flex: 2), Text(
BrandText.h2('initializing.final'.tr()), 'initializing.final'.tr(),
const SizedBox(height: 10), style: Theme.of(context).textTheme.headlineSmall,
BrandText.body2('initializing.create_server'.tr()), ),
const Spacer(), const SizedBox(height: 16),
Text(
'initializing.create_server'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 128),
BrandButton.rised( BrandButton.rised(
onPressed: onPressed:
isLoading ? null : appConfigCubit.createServerAndSetDnsRecords, isLoading ? null : appConfigCubit.createServerAndSetDnsRecords,
@ -505,55 +508,64 @@ class InitializingPage extends StatelessWidget {
doneCount = 0; doneCount = 0;
} }
return Builder( return Builder(
builder: (final context) => Column( builder: (final context) => SizedBox(
crossAxisAlignment: CrossAxisAlignment.start, width: double.infinity,
children: [ child: Column(
const SizedBox(height: 15), crossAxisAlignment: CrossAxisAlignment.start,
BrandText.h4( children: [
'initializing.checks'.tr(args: [doneCount.toString(), '4']), Text(
), 'initializing.checks'.tr(args: [doneCount.toString(), '4']),
const Spacer(flex: 2), style: Theme.of(context).textTheme.headlineSmall,
const SizedBox(height: 10),
BrandText.body2(text),
const SizedBox(height: 10),
if (doneCount == 0 && state.dnsMatches != null)
Column(
children: state.dnsMatches!.entries.map((final entry) {
final String domain = entry.key;
final bool isCorrect = entry.value;
return Row(
children: [
if (isCorrect) const Icon(Icons.check, color: Colors.green),
if (!isCorrect)
const Icon(Icons.schedule, color: Colors.amber),
const SizedBox(width: 10),
Text(domain),
],
);
}).toList(),
), ),
const SizedBox(height: 10), const SizedBox(height: 16),
if (!state.isLoading) if (text != null)
Row( Text(
children: [ text,
BrandText.body2('initializing.until_the_next_check'.tr()), style: Theme.of(context).textTheme.bodyMedium,
BrandTimer( ),
startDateTime: state.timerStart!, const SizedBox(height: 128),
duration: state.duration!, const SizedBox(height: 10),
) if (doneCount == 0 && state.dnsMatches != null)
], Column(
), children: state.dnsMatches!.entries.map((final entry) {
if (state.isLoading) BrandText.body2('initializing.check'.tr()), final String domain = entry.key;
], final bool isCorrect = entry.value;
return Row(
children: [
if (isCorrect)
const Icon(Icons.check, color: Colors.green),
if (!isCorrect)
const Icon(Icons.schedule, color: Colors.amber),
const SizedBox(width: 10),
Text(domain),
],
);
}).toList(),
),
const SizedBox(height: 10),
if (!state.isLoading)
Row(
children: [
Text(
'initializing.until_the_next_check'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
BrandTimer(
startDateTime: state.timerStart!,
duration: state.duration!,
)
],
),
if (state.isLoading)
Text(
'initializing.check'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
), ),
); );
} }
Widget _addCard(final Widget child) => Container(
height: 450,
padding: paddingH15V0,
child: BrandCards.big(child: child),
);
} }
class _HowTo extends StatelessWidget { class _HowTo extends StatelessWidget {

View File

@ -1,13 +1,18 @@
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:selfprivacy/config/brand_theme.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/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/provider_form_cubit.dart';
import 'package:selfprivacy/logic/models/hive/server_details.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_bottom_sheet/brand_bottom_sheet.dart';
import 'package:selfprivacy/ui/components/brand_button/filled_button.dart'; import 'package:selfprivacy/ui/components/brand_button/filled_button.dart';
import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart';
import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart';
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
import 'package:url_launcher/url_launcher.dart';
class ServerProviderPicker extends StatefulWidget { class ServerProviderPicker extends StatefulWidget {
const ServerProviderPicker({ const ServerProviderPicker({
@ -96,13 +101,16 @@ class ProviderInputDataPage extends StatelessWidget {
Widget build(final BuildContext context) => Column( Widget build(final BuildContext context) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
providerInfo.image,
const SizedBox(height: 10),
Text( Text(
'initializing.connect_to_server'.tr(), "${'initializing.connect_to_server_provider'.tr()}${providerInfo.providerType.displayName}",
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.headlineSmall,
), ),
const Spacer(), const SizedBox(height: 16),
Text(
'initializing.connect_to_server_provider_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 32),
CubitFormTextField( CubitFormTextField(
formFieldCubit: providerCubit.apiKey, formFieldCubit: providerCubit.apiKey,
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -111,13 +119,13 @@ class ProviderInputDataPage extends StatelessWidget {
hintText: 'Provider API Token', hintText: 'Provider API Token',
), ),
), ),
const Spacer(), const SizedBox(height: 32),
FilledButton( FilledButton(
title: 'basis.connect'.tr(), title: 'basis.connect'.tr(),
onPressed: () => providerCubit.trySubmit(), onPressed: () => providerCubit.trySubmit(),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
OutlinedButton( BrandOutlinedButton(
child: Text('initializing.how'.tr()), child: Text('initializing.how'.tr()),
onPressed: () => showModalBottomSheet<void>( onPressed: () => showModalBottomSheet<void>(
context: context, context: context,
@ -154,51 +162,189 @@ class ProviderSelectionPage extends StatelessWidget {
final ServerInstallationCubit serverInstallationCubit; final ServerInstallationCubit serverInstallationCubit;
@override @override
Widget build(final BuildContext context) => Column( Widget build(final BuildContext context) => SizedBox(
children: [ width: double.infinity,
Text( child: Column(
'initializing.select_provider'.tr(), crossAxisAlignment: CrossAxisAlignment.start,
style: Theme.of(context).textTheme.titleLarge, children: [
), Text(
const SizedBox(height: 10), 'initializing.connect_to_server'.tr(),
Text( style: Theme.of(context).textTheme.headlineSmall,
'initializing.place_where_data'.tr(),
),
const SizedBox(height: 10),
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 320,
), ),
child: Row( const SizedBox(height: 10),
children: [ Text(
InkWell( 'initializing.select_provider'.tr(),
onTap: () { style: Theme.of(context).textTheme.bodyMedium,
serverInstallationCubit
.setServerProviderType(ServerProvider.hetzner);
callback(ServerProvider.hetzner);
},
child: Image.asset(
'assets/images/logos/hetzner.png',
width: 150,
),
),
const SizedBox(
width: 20,
),
InkWell(
onTap: () {
serverInstallationCubit
.setServerProviderType(ServerProvider.digitalOcean);
callback(ServerProvider.digitalOcean);
},
child: Image.asset(
'assets/images/logos/digital_ocean.png',
width: 150,
),
),
],
), ),
), const SizedBox(height: 10),
], OutlinedCard(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 40,
height: 40,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
color: const Color(0xFFD50C2D),
),
child: SvgPicture.asset(
'assets/images/logos/hetzner.svg',
),
),
const SizedBox(width: 16),
Text(
'Hetzner Cloud',
style: Theme.of(context).textTheme.titleMedium,
),
],
),
const SizedBox(height: 16),
Text(
'initializing.select_provider_countries_title'.tr(),
style: Theme.of(context).textTheme.bodyLarge,
),
Text(
'initializing.select_provider_countries_text_hetzner'
.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 16),
Text(
'initializing.select_provider_price_title'.tr(),
style: Theme.of(context).textTheme.bodyLarge,
),
Text(
'initializing.select_provider_price_text_hetzner'.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 16),
Text(
'initializing.select_provider_payment_title'.tr(),
style: Theme.of(context).textTheme.bodyLarge,
),
Text(
'initializing.select_provider_payment_text_hetzner'.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 16),
Text(
'initializing.select_provider_email_notice'.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 16),
FilledButton(
title: 'basis.select'.tr(),
onPressed: () {
serverInstallationCubit
.setServerProviderType(ServerProvider.hetzner);
callback(ServerProvider.hetzner);
},
),
// Outlined button that will open website
BrandOutlinedButton(
onPressed: () =>
_launchURL('https://www.hetzner.com/cloud'),
title: 'initializing.select_provider_site_button'.tr(),
),
],
),
),
),
const SizedBox(height: 16),
OutlinedCard(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 40,
height: 40,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
color: const Color(0xFF0080FF),
),
child: SvgPicture.asset(
'assets/images/logos/digital_ocean.svg',
),
),
const SizedBox(width: 16),
Text(
'Digital Ocean',
style: Theme.of(context).textTheme.titleMedium,
),
],
),
const SizedBox(height: 16),
Text(
'initializing.select_provider_countries_title'.tr(),
style: Theme.of(context).textTheme.bodyLarge,
),
Text(
'initializing.select_provider_countries_text_do'.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 16),
Text(
'initializing.select_provider_price_title'.tr(),
style: Theme.of(context).textTheme.bodyLarge,
),
Text(
'initializing.select_provider_price_text_do'.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 16),
Text(
'initializing.select_provider_payment_title'.tr(),
style: Theme.of(context).textTheme.bodyLarge,
),
Text(
'initializing.select_provider_payment_text_do'.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 16),
FilledButton(
title: 'basis.select'.tr(),
onPressed: () {
serverInstallationCubit
.setServerProviderType(ServerProvider.digitalOcean);
callback(ServerProvider.digitalOcean);
},
),
// Outlined button that will open website
BrandOutlinedButton(
onPressed: () =>
_launchURL('https://www.digitalocean.com'),
title: 'initializing.select_provider_site_button'.tr(),
),
],
),
),
),
const SizedBox(height: 16),
InfoBox(text: 'initializing.select_provider_notice'.tr()),
],
),
); );
} }
void _launchURL(final url) async {
try {
final Uri uri = Uri.parse(url);
await launchUrl(
uri,
mode: LaunchMode.externalApplication,
);
} catch (e) {
print(e);
}
}

View File

@ -1,10 +1,12 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/illustrations/stray_deer.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/logic/models/server_provider_location.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart';
import 'package:selfprivacy/logic/models/server_type.dart'; import 'package:selfprivacy/logic/models/server_type.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
class ServerTypePicker extends StatefulWidget { class ServerTypePicker extends StatefulWidget {
const ServerTypePicker({ const ServerTypePicker({
@ -68,27 +70,43 @@ class SelectLocationPage extends StatelessWidget {
if ((snapshot.data as List<ServerProviderLocation>).isEmpty) { if ((snapshot.data as List<ServerProviderLocation>).isEmpty) {
return Text('initializing.no_locations_found'.tr()); return Text('initializing.no_locations_found'.tr());
} }
return ListView( return Column(
padding: paddingH15V0,
children: [ children: [
Text(
'initializing.choose_location_type'.tr(),
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
Text(
'initializing.choose_location_type_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
...(snapshot.data! as List<ServerProviderLocation>).map( ...(snapshot.data! as List<ServerProviderLocation>).map(
(final location) => InkWell( (final location) => SizedBox(
onTap: () { width: double.infinity,
callback(location); child: InkWell(
}, onTap: () {
child: Card( callback(location);
child: Padding( },
padding: const EdgeInsets.all(16.0), child: Card(
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(16.0),
children: [ child: Column(
if (location.flag != null) Text(location.flag!), crossAxisAlignment: CrossAxisAlignment.start,
const SizedBox(height: 8), children: [
Text(location.title), Text(
const SizedBox(height: 8), '${location.flag ?? ''} ${location.title}',
if (location.description != null) style: Theme.of(context).textTheme.titleMedium,
Text(location.description!), ),
], const SizedBox(height: 8),
if (location.description != null)
Text(
location.description!,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
), ),
), ),
), ),
@ -126,11 +144,33 @@ class SelectTypePage extends StatelessWidget {
if (snapshot.hasData) { if (snapshot.hasData) {
if ((snapshot.data as List<ServerType>).isEmpty) { if ((snapshot.data as List<ServerType>).isEmpty) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'initializing.no_server_types_found'.tr(), 'initializing.locations_not_found'.tr(),
style: Theme.of(context).textTheme.headlineSmall,
), ),
const SizedBox(height: 10), const SizedBox(height: 16),
Text(
'initializing.locations_not_found_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
LayoutBuilder(
builder: (final context, final constraints) => CustomPaint(
size: Size(
constraints.maxWidth,
(constraints.maxWidth * 1).toDouble(),
),
painter: StrayDeerPainter(
colorScheme: Theme.of(context).colorScheme,
colorPalette: context
.read<AppSettingsCubit>()
.state
.corePaletteOrDefault,
),
),
),
const SizedBox(height: 16),
BrandButton.rised( BrandButton.rised(
onPressed: () { onPressed: () {
backToLocationPickingCallback(); backToLocationPickingCallback();
@ -140,51 +180,118 @@ class SelectTypePage extends StatelessWidget {
], ],
); );
} }
return ListView( return Column(
padding: paddingH15V0, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(
'initializing.choose_server_type'.tr(),
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
Text(
'initializing.choose_server_type_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
...(snapshot.data! as List<ServerType>).map( ...(snapshot.data! as List<ServerType>).map(
(final type) => InkWell( (final type) => SizedBox(
onTap: () { width: double.infinity,
serverInstallationCubit.setServerType(type); child: InkWell(
}, onTap: () {
child: Card( serverInstallationCubit.setServerType(type);
child: Padding( },
padding: const EdgeInsets.all(16.0), child: Card(
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(16.0),
children: [ child: Column(
Text( crossAxisAlignment: CrossAxisAlignment.start,
type.title, children: [
style: Theme.of(context).textTheme.bodyMedium, Text(
), type.title,
const SizedBox(height: 8), style: Theme.of(context).textTheme.titleMedium,
Text( ),
'cores: ${type.cores.toString()}', const SizedBox(height: 8),
style: Theme.of(context).textTheme.bodySmall, Row(
), children: [
const SizedBox(height: 8), Icon(
Text( Icons.memory_outlined,
'ram: ${type.ram.toString()}', color:
style: Theme.of(context).textTheme.bodySmall, Theme.of(context).colorScheme.onSurface,
), ),
const SizedBox(height: 8), const SizedBox(width: 8),
Text( Text(
'disk: ${type.disk.gibibyte.toString()}', 'server.core_count'.plural(type.cores),
style: Theme.of(context).textTheme.bodySmall, style:
), Theme.of(context).textTheme.bodyMedium,
const SizedBox(height: 8), ),
Text( ],
'price: ${type.price.value.toString()} ${type.price.currency}', ),
style: Theme.of(context).textTheme.bodySmall, const SizedBox(height: 8),
), Row(
], children: [
Icon(
Icons.memory_outlined,
color:
Theme.of(context).colorScheme.onSurface,
),
const SizedBox(width: 8),
Text(
'initializing.choose_server_type_ram'
.tr(args: [type.ram.toString()]),
style:
Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.sd_card_outlined,
color:
Theme.of(context).colorScheme.onSurface,
),
const SizedBox(width: 8),
Text(
'initializing.choose_server_type_storage'
.tr(args: [
type.disk.gibibyte.toString()
]),
style:
Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 8),
const Divider(height: 8),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.payments_outlined,
color:
Theme.of(context).colorScheme.onSurface,
),
const SizedBox(width: 8),
Text(
'initializing.choose_server_type_payment_per_month'
.tr(args: [
'${type.price.value.toString()} ${type.price.currency}'
]),
style:
Theme.of(context).textTheme.bodyLarge,
),
],
),
],
),
), ),
), ),
), ),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 16),
InfoBox(text: 'initializing.choose_server_type_notice'.tr()),
], ],
); );
} else { } else {