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',
), ),
const SizedBox(height: 16),
Text(
'onboarding.page2_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
), ),
const SizedBox(height: 16),
Text(
'onboarding.page2_server_provider_title'.tr(),
style: Theme.of(context).textTheme.titleLarge,
), ),
Flexible( const SizedBox(height: 16),
child: Center( Text(
child: Image.asset( 'onboarding.page2_server_provider_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_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,19 +55,23 @@ class InitializingPage extends StatelessWidget {
.pushReplacement(materialRoute(const RootPage())); .pushReplacement(materialRoute(const RootPage()));
} }
}, },
child: SafeArea(
child: Scaffold( child: Scaffold(
body: SingleChildScrollView( appBar: AppBar(
child: Column( actions: [
mainAxisAlignment: MainAxisAlignment.center, if (cubit.state is ServerInstallationFinished)
children: [ IconButton(
Padding( icon: const Icon(Icons.check),
padding: paddingH15V0.copyWith(top: 10, bottom: 10), onPressed: () {
child: cubit.state is ServerInstallationFinished Navigator.of(context)
? const SizedBox( .pushReplacement(materialRoute(const RootPage()));
height: 80, },
) )
: ProgressBar( ],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(28),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: ProgressBar(
steps: const [ steps: const [
'Hosting', 'Hosting',
'Server Type', 'Server Type',
@ -82,13 +85,15 @@ class InitializingPage extends StatelessWidget {
activeIndex: cubit.state.porgressBar, activeIndex: cubit.state.porgressBar,
), ),
), ),
if (cubit.state.porgressBar ==
ServerSetupProgress.serverProviderFilled.index)
BrandText.h2(
'initializing.choose_location_type'.tr(),
), ),
_addCard( ),
AnimatedSwitcher( 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), duration: const Duration(milliseconds: 300),
child: actualInitializingPage, child: actualInitializingPage,
), ),
@ -117,9 +122,7 @@ class InitializingPage extends StatelessWidget {
}, },
), ),
), ),
if (cubit.state is ServerInstallationFinished) if (cubit.state is ServerInstallationEmpty)
Container()
else
Container( Container(
alignment: Alignment.center, alignment: Alignment.center,
child: BrandButton.text( child: BrandButton.text(
@ -140,7 +143,6 @@ class InitializingPage extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
} }
@ -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,60 +292,57 @@ 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(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Image.asset( Text(
'assets/images/logos/cloudflare.png', 'initializing.use_this_domain'.tr(),
width: 150, style: Theme.of(context).textTheme.headlineSmall,
), ),
const SizedBox(height: 30), const SizedBox(height: 16),
BrandText.h2('basis.domain'.tr()), Text(
const SizedBox(height: 10), 'initializing.use_this_domain_text'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 32),
if (state is Empty) if (state is Empty)
BrandText.body2('initializing.no_connected_domains'.tr()), Text(
'initializing.no_connected_domains'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
if (state is Loading) if (state is Loading)
BrandText.body2( Text(
state.type == LoadingTypes.loadingDomain state.type == LoadingTypes.loadingDomain
? 'initializing.loading_domain_list'.tr() ? 'initializing.loading_domain_list'.tr()
: 'basis.saving'.tr(), : 'basis.saving'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
), ),
if (state is MoreThenOne) if (state is MoreThenOne)
BrandText.body2( Text(
'initializing.found_more_domains'.tr(), 'initializing.found_more_domains'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
), ),
if (state is Loaded) ...[ if (state is Loaded) ...[
const SizedBox(height: 10),
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Expanded( Text(
child: BrandText.h3(
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) ...[ if (state is Empty) ...[
const SizedBox(height: 30), const SizedBox(height: 30),
@ -365,18 +362,15 @@ class InitializingPage extends StatelessWidget {
), ),
], ],
if (state is Loaded) ...[ if (state is Loaded) ...[
const SizedBox(height: 30), const SizedBox(height: 32),
BrandButton.rised( BrandButton.rised(
onPressed: () => onPressed: () =>
context.read<DomainSetupCubit>().saveDomain(), context.read<DomainSetupCubit>().saveDomain(),
text: 'initializing.save_domain'.tr(), 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,16 +508,22 @@ class InitializingPage extends StatelessWidget {
doneCount = 0; doneCount = 0;
} }
return Builder( return Builder(
builder: (final context) => Column( builder: (final context) => SizedBox(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 15), Text(
BrandText.h4(
'initializing.checks'.tr(args: [doneCount.toString(), '4']), 'initializing.checks'.tr(args: [doneCount.toString(), '4']),
style: Theme.of(context).textTheme.headlineSmall,
), ),
const Spacer(flex: 2), const SizedBox(height: 16),
const SizedBox(height: 10), if (text != null)
BrandText.body2(text), Text(
text,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 128),
const SizedBox(height: 10), const SizedBox(height: 10),
if (doneCount == 0 && state.dnsMatches != null) if (doneCount == 0 && state.dnsMatches != null)
Column( Column(
@ -523,7 +532,8 @@ class InitializingPage extends StatelessWidget {
final bool isCorrect = entry.value; final bool isCorrect = entry.value;
return Row( return Row(
children: [ children: [
if (isCorrect) const Icon(Icons.check, color: Colors.green), if (isCorrect)
const Icon(Icons.check, color: Colors.green),
if (!isCorrect) if (!isCorrect)
const Icon(Icons.schedule, color: Colors.amber), const Icon(Icons.schedule, color: Colors.amber),
const SizedBox(width: 10), const SizedBox(width: 10),
@ -536,24 +546,26 @@ class InitializingPage extends StatelessWidget {
if (!state.isLoading) if (!state.isLoading)
Row( Row(
children: [ children: [
BrandText.body2('initializing.until_the_next_check'.tr()), Text(
'initializing.until_the_next_check'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
BrandTimer( BrandTimer(
startDateTime: state.timerStart!, startDateTime: state.timerStart!,
duration: state.duration!, duration: state.duration!,
) )
], ],
), ),
if (state.isLoading) BrandText.body2('initializing.check'.tr()), 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(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(
'initializing.connect_to_server'.tr(),
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 10),
Text( Text(
'initializing.select_provider'.tr(), 'initializing.select_provider'.tr(),
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.bodyMedium,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( OutlinedCard(
'initializing.place_where_data'.tr(), child: Padding(
), padding: const EdgeInsets.all(16.0),
const SizedBox(height: 10), child: Column(
ConstrainedBox( crossAxisAlignment: CrossAxisAlignment.start,
constraints: const BoxConstraints(
maxWidth: 320,
),
child: Row(
children: [ children: [
InkWell( Row(
onTap: () { 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 serverInstallationCubit
.setServerProviderType(ServerProvider.hetzner); .setServerProviderType(ServerProvider.hetzner);
callback(ServerProvider.hetzner); callback(ServerProvider.hetzner);
}, },
child: Image.asset( ),
'assets/images/logos/hetzner.png', // Outlined button that will open website
width: 150, BrandOutlinedButton(
onPressed: () =>
_launchURL('https://www.hetzner.com/cloud'),
title: 'initializing.select_provider_site_button'.tr(),
),
],
), ),
), ),
const SizedBox(
width: 20,
), ),
InkWell( const SizedBox(height: 16),
onTap: () { 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 serverInstallationCubit
.setServerProviderType(ServerProvider.digitalOcean); .setServerProviderType(ServerProvider.digitalOcean);
callback(ServerProvider.digitalOcean); callback(ServerProvider.digitalOcean);
}, },
child: Image.asset(
'assets/images/logos/digital_ocean.png',
width: 150,
), ),
// 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,11 +70,22 @@ 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(
width: double.infinity,
child: InkWell(
onTap: () { onTap: () {
callback(location); callback(location);
}, },
@ -82,18 +95,23 @@ class SelectLocationPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (location.flag != null) Text(location.flag!), Text(
const SizedBox(height: 8), '${location.flag ?? ''} ${location.title}',
Text(location.title), style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8), const SizedBox(height: 8),
if (location.description != null) if (location.description != null)
Text(location.description!), Text(
location.description!,
style: Theme.of(context).textTheme.bodyMedium,
),
], ],
), ),
), ),
), ),
), ),
), ),
),
const SizedBox(height: 24), const SizedBox(height: 24),
], ],
); );
@ -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,11 +180,23 @@ 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(
width: double.infinity,
child: InkWell(
onTap: () { onTap: () {
serverInstallationCubit.setServerType(type); serverInstallationCubit.setServerType(type);
}, },
@ -156,27 +208,80 @@ class SelectTypePage extends StatelessWidget {
children: [ children: [
Text( Text(
type.title, type.title,
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.memory_outlined,
color:
Theme.of(context).colorScheme.onSurface,
),
const SizedBox(width: 8),
Text( Text(
'cores: ${type.cores.toString()}', 'server.core_count'.plural(type.cores),
style: Theme.of(context).textTheme.bodySmall, style:
Theme.of(context).textTheme.bodyMedium,
),
],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.memory_outlined,
color:
Theme.of(context).colorScheme.onSurface,
),
const SizedBox(width: 8),
Text( Text(
'ram: ${type.ram.toString()}', 'initializing.choose_server_type_ram'
style: Theme.of(context).textTheme.bodySmall, .tr(args: [type.ram.toString()]),
style:
Theme.of(context).textTheme.bodyMedium,
),
],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.sd_card_outlined,
color:
Theme.of(context).colorScheme.onSurface,
),
const SizedBox(width: 8),
Text( Text(
'disk: ${type.disk.gibibyte.toString()}', 'initializing.choose_server_type_storage'
style: Theme.of(context).textTheme.bodySmall, .tr(args: [
type.disk.gibibyte.toString()
]),
style:
Theme.of(context).textTheme.bodyMedium,
),
],
), ),
const SizedBox(height: 8), 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( Text(
'price: ${type.price.value.toString()} ${type.price.currency}', 'initializing.choose_server_type_payment_per_month'
style: Theme.of(context).textTheme.bodySmall, .tr(args: [
'${type.price.value.toString()} ${type.price.currency}'
]),
style:
Theme.of(context).textTheme.bodyLarge,
),
],
), ),
], ],
), ),
@ -184,7 +289,9 @@ class SelectTypePage extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(height: 24), ),
const SizedBox(height: 16),
InfoBox(text: 'initializing.choose_server_type_notice'.tr()),
], ],
); );
} else { } else {