diff --git a/assets/fonts/BrandIcons.ttf b/assets/fonts/BrandIcons.ttf index 7c975969..bf40d009 100644 Binary files a/assets/fonts/BrandIcons.ttf and b/assets/fonts/BrandIcons.ttf differ diff --git a/assets/images/icon/check.svg b/assets/images/icon/check.svg new file mode 100644 index 00000000..876185df --- /dev/null +++ b/assets/images/icon/check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/onboarding/logos_line.png b/assets/images/onboarding/logos_line.png new file mode 100644 index 00000000..eb8ecf82 Binary files /dev/null and b/assets/images/onboarding/logos_line.png differ diff --git a/assets/images/onboarding/onboarding1.png b/assets/images/onboarding/onboarding1.png new file mode 100644 index 00000000..fd9a7b2f Binary files /dev/null and b/assets/images/onboarding/onboarding1.png differ diff --git a/assets/images/onboarding/onboarding2.png b/assets/images/onboarding/onboarding2.png new file mode 100644 index 00000000..5786558c Binary files /dev/null and b/assets/images/onboarding/onboarding2.png differ diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index ac0d9874..414c2418 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; +import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; @@ -20,6 +21,7 @@ class BlocAndProviderConfig extends StatelessWidget { return MultiProvider( providers: [ BlocProvider(create: (_) => AppSettingsCubit(isDarkModeOn: isDark)), + BlocProvider(create: (_) => InitializingCubit()), BlocProvider(create: (_) => ServicesCubit()), BlocProvider(create: (_) => ProvidersCubit()), BlocProvider(create: (_) => UsersCubit()), diff --git a/lib/config/brand_colors.dart b/lib/config/brand_colors.dart index 1ca3f87f..0bd0c44c 100644 --- a/lib/config/brand_colors.dart +++ b/lib/config/brand_colors.dart @@ -30,7 +30,6 @@ class BrandColors { /// ![](https://www.colorhexa.com/0F8849.png) static const Color green2 = Color(0xFF0F8849); - static get navBackgroundLight => white.withOpacity(0.8); static get navBackgroundDark => black.withOpacity(0.8); @@ -42,12 +41,16 @@ class BrandColors { Color(0xFF093CEF), Color(0xFF14A1CB), ]; + + static const List progressGradientColors = [ + Color(0xFF093CEF), + Color(0xFF14A1CB), + ]; static const List warningGradientColors = [ Color(0xFFEF4E09), Color(0xFFEFD135), ]; - static const primary = blue; static const headlineColor = black; static const inactive = gray2; @@ -58,6 +61,4 @@ class BrandColors { static const textColor2 = gray1; static const dividerColor = gray5; static const warning = red; - - } diff --git a/lib/config/text_themes.dart b/lib/config/text_themes.dart index 5be2aa5c..acc9425f 100644 --- a/lib/config/text_themes.dart +++ b/lib/config/text_themes.dart @@ -18,7 +18,7 @@ final headline1Style = GoogleFonts.inter( ); final headline2Style = GoogleFonts.inter( - fontSize: 24, + fontSize: 30, fontWeight: NamedFontWeight.extraBold, color: BrandColors.headlineColor, ); @@ -41,3 +41,19 @@ final body2Style = defaultTextStyle.copyWith( ); final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45); + +final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue); + +final progressTextStyleLight = GoogleFonts.inter( + textStyle: TextStyle( + fontSize: 13, + color: BrandColors.textColor1, + ), +); + +final progressTextStyleDark = GoogleFonts.inter( + textStyle: TextStyle( + fontSize: 13, + color: BrandColors.white, + ), +); diff --git a/lib/logic/cubit/forms/initializing/cloudflare_form_cubit.dart b/lib/logic/cubit/forms/initializing/cloudflare_form_cubit.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/logic/cubit/forms/initializing/domain_form_cubit.dart b/lib/logic/cubit/forms/initializing/domain_form_cubit.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/logic/cubit/forms/initializing/hetzner_form_cubit.dart b/lib/logic/cubit/forms/initializing/hetzner_form_cubit.dart new file mode 100644 index 00000000..0a62d715 --- /dev/null +++ b/lib/logic/cubit/forms/initializing/hetzner_form_cubit.dart @@ -0,0 +1,44 @@ +import 'dart:async'; + +import 'package:cubit_form/cubit_form.dart'; +import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; + +class HetznerFormCubit extends FormCubit { + HetznerFormCubit(this.initializingCubit) { + var regExp = RegExp(r"\s+|[-!$%^&*()_@+|~=`{}\[\]:" ";<>?,.\/]"); + apiKey = FieldCubit( + initalValue: '', + validations: [ + RequiredStringValidation('required'), + ValidationModel( + (s) => regExp.hasMatch(s), 'invalid key format'), + LegnthStringValidation(11, 'length is [] shoud be 11') + ], + ); + + super.setFields([apiKey]); + } + + @override + FutureOr onSubmit() async { + print(apiKey.state.value); + await Future.delayed(const Duration(milliseconds: 300)); + // initializingCubit.setHetznerKey(apiKey.state.value); + } + + final InitializingCubit initializingCubit; + + FieldCubit apiKey; +} + +class LegnthStringValidation extends ValidationModel { + LegnthStringValidation(int length, String errorText) + : super((n) => n.length != length, errorText); + + @override + String check(String val) { + var length = val.length; + var errorMassage = this.errorMassage.replaceAll("[]", length.toString()); + return test(val) ? errorMassage : null; + } +} diff --git a/lib/logic/cubit/forms/initializing/user_form_cubit.dart b/lib/logic/cubit/forms/initializing/user_form_cubit.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/logic/cubit/initializing/initializing_cubit.dart b/lib/logic/cubit/initializing/initializing_cubit.dart new file mode 100644 index 00000000..0fe6bcd9 --- /dev/null +++ b/lib/logic/cubit/initializing/initializing_cubit.dart @@ -0,0 +1,30 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:selfprivacy/logic/models/config.dart'; +import 'package:selfprivacy/logic/models/user.dart'; + +part 'initializing_state.dart'; + +class InitializingCubit extends Cubit { + InitializingCubit() : super(InitialInitializingState()); + + void setHetznerKey(String key) { + var newCofig = state.appConfig.copyWith(hatzner: key); + emit(InitializingState(newCofig)); + } + + void setCloudFlare(String cloudFlareKey) { + var newCofig = state.appConfig.copyWith(cloudFlare: cloudFlareKey); + emit(InitializingState(newCofig)); + } + + void setDomain(String domain) { + var newCofig = state.appConfig.copyWith(domain: domain); + emit(InitializingState(newCofig)); + } + + void setRootUser(User rootUser) { + var newCofig = state.appConfig.copyWith(rootUser: rootUser); + emit(InitializingState(newCofig)); + } +} diff --git a/lib/logic/cubit/initializing/initializing_state.dart b/lib/logic/cubit/initializing/initializing_state.dart new file mode 100644 index 00000000..7827987d --- /dev/null +++ b/lib/logic/cubit/initializing/initializing_state.dart @@ -0,0 +1,26 @@ +part of 'initializing_cubit.dart'; + +class InitializingState extends Equatable { + const InitializingState(this.appConfig); + + final AppConfig appConfig; + + @override + List get props => [appConfig]; + + bool get isHatznerFilled => appConfig.hatzner != null; + bool get isCloudFlareFilled => appConfig.cloudFlare != null; + bool get isDomainFilled => appConfig.domain != null; + bool get isUserFilled => appConfig.rootUser != null; + + bool get isFullyInitilized => _fulfilementList.every((el) => el); + + int get progress => _fulfilementList.where((el) => el).length; + + List get _fulfilementList => + [isHatznerFilled, isCloudFlareFilled, isDomainFilled, isUserFilled]; +} + +class InitialInitializingState extends InitializingState { + InitialInitializingState() : super(AppConfig.empty()); +} diff --git a/lib/logic/cubit/providers/providers_cubit.dart b/lib/logic/cubit/providers/providers_cubit.dart index 88fee648..9482a194 100644 --- a/lib/logic/cubit/providers/providers_cubit.dart +++ b/lib/logic/cubit/providers/providers_cubit.dart @@ -9,7 +9,7 @@ export 'package:provider/provider.dart'; part 'providers_state.dart'; class ProvidersCubit extends Cubit { - ProvidersCubit() : super(ProvidersState(all)); + ProvidersCubit() : super(InitialProviderState()); void connect(ProviderModel provider) { var newState = state.updateElement(provider, StateType.stable); @@ -17,11 +17,4 @@ class ProvidersCubit extends Cubit { } } -final all = ProviderType.values - .map( - (type) => ProviderModel( - state: StateType.uninitialized, - type: type, - ), - ) - .toList(); + diff --git a/lib/logic/cubit/providers/providers_state.dart b/lib/logic/cubit/providers/providers_state.dart index 8e85d62a..8297699d 100644 --- a/lib/logic/cubit/providers/providers_state.dart +++ b/lib/logic/cubit/providers/providers_state.dart @@ -23,3 +23,17 @@ class ProvidersState extends Equatable { @override List get props => all; } + +class InitialProviderState extends ProvidersState { + InitialProviderState() + : super( + ProviderType.values + .map( + (type) => ProviderModel( + state: StateType.uninitialized, + type: type, + ), + ) + .toList(), + ); +} diff --git a/lib/logic/models/config.dart b/lib/logic/models/config.dart new file mode 100644 index 00000000..82552241 --- /dev/null +++ b/lib/logic/models/config.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; +import 'package:selfprivacy/logic/models/user.dart'; + +class AppConfig extends Equatable { + const AppConfig({ + this.hatzner, + this.cloudFlare, + this.domain, + this.rootUser, + }); + + final String hatzner; + final String cloudFlare; + final String domain; + final User rootUser; + + factory AppConfig.empty() { + return AppConfig(); + } + + AppConfig copyWith({ + hatzner, + cloudFlare, + domain, + rootUser, + }) => + AppConfig( + hatzner: hatzner ?? this.hatzner, + cloudFlare: cloudFlare ?? this.cloudFlare, + domain: domain ?? this.domain, + rootUser: rootUser ?? this.rootUser, + ); + + @override + List get props => [hatzner, cloudFlare, domain, rootUser]; +} diff --git a/lib/ui/components/brand_button/brand_button.dart b/lib/ui/components/brand_button/brand_button.dart index d410a4d4..163e3f1b 100644 --- a/lib/ui/components/brand_button/brand_button.dart +++ b/lib/ui/components/brand_button/brand_button.dart @@ -117,7 +117,7 @@ class _RisedButton extends StatelessWidget { color: BrandColors.white, fontSize: 16, fontWeight: FontWeight.bold, - height: 1.5, + height: 1, ), ), ), diff --git a/lib/ui/components/brand_card/brand_card.dart b/lib/ui/components/brand_card/brand_card.dart index 632ebd95..6908b363 100644 --- a/lib/ui/components/brand_card/brand_card.dart +++ b/lib/ui/components/brand_card/brand_card.dart @@ -1,15 +1,21 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_colors.dart'; +import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/utils/extensions/elevation_extension.dart'; class BrandCard extends StatelessWidget { - const BrandCard({Key key, this.child}) : super(key: key); + const BrandCard({ + Key key, + this.child, + this.isBlocked = false, + }) : super(key: key); final Widget child; + final bool isBlocked; @override Widget build(BuildContext context) { - return Container( + Widget res = Container( margin: EdgeInsets.only(bottom: 30), decoration: BoxDecoration( color: Theme.of(context).brightness == Brightness.dark @@ -23,5 +29,44 @@ class BrandCard extends StatelessWidget { ), child: child, ); + + if (!isBlocked) { + return res; + } + + return IgnorePointer( + child: Stack( + children: [ + ColorFiltered( + colorFilter: ColorFilter.mode( + Colors.white, + BlendMode.saturation, + ), + child: res, + ), + Positioned( + top: 0, + left: 0, + right: 0, + bottom: 0, + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white.withOpacity(0.8), + ), + padding: EdgeInsets.all(10), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + BrandText.h3('Blocked'), + BrandText.h4('finish initializing first') + ], + ), + ), + ) + ], + ), + ); } } diff --git a/lib/ui/components/brand_icons/brand_icons.dart b/lib/ui/components/brand_icons/brand_icons.dart index fa198d3a..06bad438 100644 --- a/lib/ui/components/brand_icons/brand_icons.dart +++ b/lib/ui/components/brand_icons/brand_icons.dart @@ -37,6 +37,8 @@ class BrandIcons { IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData refresh_1 = IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData check = + IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData refresh = IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData settings = diff --git a/lib/ui/components/progress_bar/progress_bar.dart b/lib/ui/components/progress_bar/progress_bar.dart new file mode 100644 index 00000000..ed3fc7ea --- /dev/null +++ b/lib/ui/components/progress_bar/progress_bar.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/brand_colors.dart'; +import 'package:selfprivacy/config/text_themes.dart'; +import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; +import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; +import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; + +class ProgressBar extends StatefulWidget { + ProgressBar({ + Key key, + @required this.steps, + @required this.activeIndex, + }) : super(key: key); + + final int activeIndex; + + final List steps; + + @override + _ProgressBarState createState() => _ProgressBarState(); +} + +class _ProgressBarState extends State { + @override + Widget build(BuildContext context) { + double progress = 1 / widget.steps.length * (widget.activeIndex + 0.3); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BrandText.h4('Progress'), + SizedBox(height: 10), + Row( + children: widget.steps + .asMap() + .map( + (i, step) { + var isActive = i == widget.activeIndex; + var checked = i < widget.activeIndex; + + var isDark = + context.watch().state.isDarkModeOn; + var style = + isDark ? progressTextStyleDark : progressTextStyleLight; + + style = isActive + ? style.copyWith(fontWeight: FontWeight.w700) + : style; + return MapEntry( + i, + Expanded( + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: progressTextStyleLight, + children: [ + checked + ? WidgetSpan( + child: Padding( + padding: const EdgeInsets.only( + bottom: 1, right: 2), + child: Icon(BrandIcons.check, size: 14), + )) + : TextSpan(text: '${i + 1}.', style: style), + TextSpan(text: step, style: style) + ], + ), + ), + )); + }, + ) + .values + .toList(), + ), + SizedBox(height: 3), + Container( + alignment: Alignment.centerLeft, + decoration: BoxDecoration( + color: BrandColors.gray4, + borderRadius: BorderRadius.circular(5), + ), + child: LayoutBuilder( + builder: (_, constraints) { + return AnimatedContainer( + width: constraints.maxWidth * progress, + height: 5, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: BrandColors.stableGradientColors, + ), + ), + duration: Duration( + milliseconds: 300, + ), + ); + }, + ), + ) + ], + ); + } +} diff --git a/lib/ui/pages/initializing/initializing.dart b/lib/ui/pages/initializing/initializing.dart index b15e8a7e..4e8448ee 100644 --- a/lib/ui/pages/initializing/initializing.dart +++ b/lib/ui/pages/initializing/initializing.dart @@ -1,55 +1,153 @@ +import 'package:cubit_form/cubit_form.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/config/brand_colors.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/text_themes.dart'; +import 'package:selfprivacy/logic/cubit/forms/initializing/hetzner_form_cubit.dart'; +import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; -import 'package:selfprivacy/logic/models/provider.dart'; +import 'package:selfprivacy/logic/models/user.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_card/brand_card.dart'; import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; import 'package:selfprivacy/ui/components/brand_span_button/brand_span_button.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; +import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart'; +import 'package:selfprivacy/ui/pages/rootRoute.dart'; +import 'package:selfprivacy/utils/route_transitions/basic.dart'; -class InitializingPage extends StatelessWidget { +class InitializingPage extends StatefulWidget { const InitializingPage({Key key}) : super(key: key); + @override + _InitializingPageState createState() => _InitializingPageState(); +} + +class _InitializingPageState extends State { + PageController pageController = PageController(viewportFraction: 1); + + @override + void dispose() { + pageController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - var cubit = context.watch(); - var connected = cubit.state.connected; - var uninitialized = cubit.state.uninitialized; - return Scaffold( - body: ListView( - padding: brandPagePadding1, - children: [ - BrandText.h4('Начало'), - BrandText.h1('SelfPrivacy'), - SizedBox( - height: 10, - ), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: - 'Для устойчивости и приватности требует много учёток. Полная инструкция на ', - style: body2Style, - ), - BrandSpanButton.link( - text: 'selfprivacy.org/start', - urlString: 'https://selfprivacy.org/start', - ), - ], + var cubit = context.watch(); + + return SafeArea( + child: Scaffold( + body: ListView( + children: [ + Padding( + padding: brandPagePadding1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BrandText.h4('Начало'), + BrandText.h1('SelfPrivacy'), + SizedBox( + height: 10, + ), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: + 'Для устойчивости и приватности требует много учёток. Полная инструкция на ', + style: body2Style, + ), + BrandSpanButton.link( + text: + 'https://selfprivacy.org/posts/getting_started/', + urlString: + 'https://selfprivacy.org/posts/getting_started/', + ), + ], + ), + ), + SizedBox(height: 10), + ProgressBar( + steps: ['Server', 'DNS', 'Domain', 'User'], + // progress: cubit.state.progress, + activeIndex: cubit.state.progress, + ), + SizedBox(height: 20), + ], + ), ), - ), - SizedBox(height: 50), - ...connected.map((p) => getCard(context, p)).toList(), - ...uninitialized.map((p) => getCard(context, p)).toList(), - ], + Container( + height: 500, + child: PageView( + // physics: NeverScrollableScrollPhysics(), + controller: pageController, + children: [ + _addCard(_stepOne(cubit)), + _addCard(_stepTwo(cubit)), + _addCard(_stepThree(cubit)), + _addCard(_stepFour(cubit)), + ], + ), + ), + BrandButton.text(title: 'Настрою потом', onPressed: _goToMainPage), + SizedBox(height: 30), + ], + ), ), ); } + void _goToMainPage() { + Navigator.of(context).pushAndRemoveUntil( + materialRoute(RootPage()), + (predicate) => predicate == null, + ); + } + + Widget _stepOne(InitializingCubit initializingCubit) { + return BlocProvider( + create: (context) => HetznerFormCubit(initializingCubit), + child: Builder(builder: (context) { + var formCubit = context.watch(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Spacer(), + Image.asset('assets/images/logos/hetzner.png'), + SizedBox(height: 10), + BrandText.h2('Подключите сервер Hetzner'), + SizedBox(height: 10), + BrandText.body2( + 'Здесь будут жить наши данные и SelfPrivacy-сервисы'), + Spacer(), + CubitFormTextField( + formFieldCubit: formCubit.apiKey, + textAlign: TextAlign.center, + keyboardType: TextInputType.number, + scrollPadding: EdgeInsets.only(bottom: 70), + decoration: InputDecoration( + hintText: 'Hetzner API Token', + ), + ), + SizedBox(height: 20), + BrandButton.rised( + onPressed: + formCubit.state.isSubmitting ? null : formCubit.trySubmit, + title: 'Подключить', + ), + Spacer(), + SizedBox(height: 10), + BrandButton.text( + onPressed: () => _showModal(context, _HowHetzner()), + title: 'Как получить API Token', + ), + ], + ); + }), + ); + } + void _showModal(BuildContext context, Widget widget) { showModalBottomSheet( context: context, @@ -61,135 +159,115 @@ class InitializingPage extends StatelessWidget { ); } - Widget getCard(BuildContext context, ProviderModel model) { - var cubit = context.watch(); - if (model.state == StateType.stable) { - return _MockSuccess(type: model.type); - } - switch (model.type) { - case ProviderType.server: - return BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset('assets/images/logos/hetzner.png'), - SizedBox(height: 10), - BrandText.h2('1. Подключите сервер Hetzner'), - SizedBox(height: 10), - BrandText.body2( - 'Здесь будут жить наши данные и SelfPrivacy-сервисы'), - _MockForm( - hintText: 'Hetzner API Token', - length: 2, - onPressed: () { - var provider = cubit.state.all - .firstWhere((p) => p.type == ProviderType.server); - cubit.connect(provider); - }, - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () => _showModal(context, _HowHetzner()), - title: 'Как получить API Token', - ), - ], + Widget _stepTwo(InitializingCubit cubit) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset('assets/images/logos/cloudflare.png'), + BrandText.h2('Подключите CloudFlare DNS'), + SizedBox(height: 10), + BrandText.body2('Для управления DNS вашего домена'), + Expanded( + child: _MockForm( + hintText: 'CloudFlare API Token', + length: 64, + onPressed: () { + cubit.setCloudFlare('key'); + pageController.animateToPage( + 2, + curve: Curves.easeIn, + duration: Duration(milliseconds: 200), + ); + }, ), - ); - break; - case ProviderType.domain: - return BrandCard( + ), + SizedBox(height: 20), + BrandButton.text( + onPressed: () {}, + title: 'Как получить API Token', + ), + ], + ); + } + + Widget _stepThree(InitializingCubit cubit) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 10), + BrandText.h2('Введите домен:'), + Expanded( + child: _MockForm( + hintText: 'домен', + length: 10, + onPressed: () { + cubit.setDomain('domain'); + pageController.animateToPage( + 3, + curve: Curves.easeIn, + duration: Duration(milliseconds: 200), + ); + }, + ), + ), + SizedBox(height: 10), + BrandButton.text( + onPressed: () => _showModal(context, _HowHetzner()), + title: 'Как получить API Token', + ), + ], + ); + } + + Widget _stepFour(InitializingCubit cubit) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 10), + Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset('assets/images/logos/namecheap.png'), - SizedBox(height: 10), - BrandText.h2('2. Настройте домен'), - SizedBox(height: 10), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: 'Зарегистрируйте домен в ', - style: body2Style, - ), - BrandSpanButton.link( - text: 'NameCheap', - urlString: 'https://www.namecheap.com', - ), - TextSpan( - text: - ' или у любого другого регистратора. После этого настройте его на DNS-сервер CloudFlare', - style: body2Style, - ), - ], + TextField( + decoration: InputDecoration( + hintText: 'нинейм', ), ), - _MockForm( - hintText: 'Домен, например, selfprivacy.org', - submitButtonText: 'Проверить DNS', - length: 2, - onPressed: () {}, + SizedBox(height: 10), + TextField( + obscureText: true, + decoration: InputDecoration( + hintText: 'пароль', + ), ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как настроить DNS CloudFlare', - ), - SizedBox(height: 10), - Image.asset('assets/images/logos/cloudflare.png'), - SizedBox(height: 10), - BrandText.h2('3. Подключите CloudFlare DNS'), - SizedBox(height: 10), - BrandText.body2('Для управления DNS вашего домена'), - _MockForm( - hintText: 'CloudFlare API Token', - length: 2, + Spacer(), + BrandButton.rised( onPressed: () { - var provider = cubit.state.all - .firstWhere((p) => p.type == ProviderType.domain); - cubit.connect(provider); + cubit.setRootUser( + User(login: 'aa', password: 'bbb'), + ); + _goToMainPage(); }, - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как получить API Token', + title: 'some text', ), ], ), - ); - break; - case ProviderType.backup: - return BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset('assets/images/logos/aws.png'), - SizedBox(height: 10), - BrandText.h2('4. Подключите Amazon AWS для бекапа'), - SizedBox(height: 10), - BrandText.body2( - 'IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде'), - _MockForm( - hintText: 'Amazon AWS Access Key', - length: 2, - onPressed: () { - var provider = cubit.state.all - .firstWhere((p) => p.type == ProviderType.backup); - cubit.connect(provider); - }, - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как получить API Token', - ), - ], - ), - ); - } + ), + SizedBox(height: 10), + BrandButton.text( + onPressed: () => _showModal(context, _HowHetzner()), + title: 'Как получить API Token', + ), + ], + ); + } - return null; + Widget _addCard(Widget child) { + return Padding( + padding: brandPagePadding2, + child: BrandCard( + child: child, + ), + ); } } @@ -245,40 +323,40 @@ class _HowHetzner extends StatelessWidget { } } -class _MockSuccess extends StatelessWidget { - const _MockSuccess({Key key, this.type}) : super(key: key); +// class _MockSuccess extends StatelessWidget { +// const _MockSuccess({Key key, this.type}) : super(key: key); - final ProviderType type; +// final ProviderType type; - @override - Widget build(BuildContext context) { - String text; +// @override +// Widget build(BuildContext context) { +// String text; - switch (type) { - case ProviderType.server: - text = '1. Cервер подключен'; - break; - case ProviderType.domain: - text = '2. Домен настроен'; - break; - case ProviderType.backup: - text = '3. Резервное копирование настроенно'; - break; - } - return BrandCard( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - BrandText.h3(text), - Icon( - Icons.check, - color: BrandColors.green1, - ), - ], - ), - ); - } -} +// switch (type) { +// case ProviderType.server: +// text = '1. Cервер подключен'; +// break; +// case ProviderType.domain: +// text = '2. Домен настроен'; +// break; +// case ProviderType.backup: +// text = '3. Резервное копирование настроенно'; +// break; +// } +// return BrandCard( +// child: Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// BrandText.h3(text), +// Icon( +// Icons.check, +// color: BrandColors.green1, +// ), +// ], +// ), +// ); +// } +// } class _MockForm extends StatefulWidget { const _MockForm({ @@ -322,6 +400,7 @@ class __MockFormState extends State<_MockForm> { @override Widget build(BuildContext context) { return Column( + mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: 20), TextField( @@ -350,7 +429,7 @@ class __MockFormState extends State<_MockForm> { _valid ? null : 'Длинна должна быть ${widget.length} символа', ), ), - SizedBox(height: 20), + Spacer(), BrandButton.rised( onPressed: _valid ? onPressed : null, title: widget.submitButtonText, @@ -359,3 +438,119 @@ class __MockFormState extends State<_MockForm> { ); } } + +// Widget getCard(BuildContext context, ProviderModel model) { +// var cubit = context.watch(); +// if (model.state == StateType.stable) { +// return _MockSuccess(type: model.type); +// } +// switch (model.type) { +// case ProviderType.server: +// return BrandCard( +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Image.asset('assets/images/logos/hetzner.png'), +// SizedBox(height: 10), +// BrandText.h2('1. Подключите сервер Hetzner'), +// SizedBox(height: 10), +// BrandText.body2( +// 'Здесь будут жить наши данные и SelfPrivacy-сервисы'), +// _MockForm( +// hintText: 'Hetzner API Token', +// length: 48, +// onPressed: () { +// var provider = cubit.state.all +// .firstWhere((p) => p.type == ProviderType.server); +// cubit.connect(provider); +// }, +// ), +// SizedBox(height: 20), +// BrandButton.text( +// onPressed: () => _showModal(context, _HowHetzner()), +// title: 'Как получить API Token', +// ), +// ], +// ), +// ); +// break; +// case ProviderType.domain: +// return BrandCard( +// isBlocked: true, +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Image.asset('assets/images/logos/namecheap.png'), +// SizedBox(height: 10), +// BrandText.h2('2. Настройте домен'), +// SizedBox(height: 10), +// RichText( +// text: TextSpan( +// children: [ +// TextSpan( +// text: 'Зарегистрируйте домен в ', +// style: body2Style, +// ), +// BrandSpanButton.link( +// text: 'NameCheap', +// urlString: 'https://www.namecheap.com', +// ), +// TextSpan( +// text: +// ' или у любого другого регистратора. После этого настройте его на DNS-сервер CloudFlare', +// style: body2Style, +// ), +// ], +// ), +// ), +// _MockForm( +// hintText: 'Домен, например, selfprivacy.org', +// submitButtonText: 'Проверить DNS', +// length: 2, +// onPressed: () {}, +// ), +// SizedBox(height: 20), +// BrandButton.text( +// onPressed: () {}, +// title: 'Как настроить DNS CloudFlare', +// ), +// SizedBox(height: 10), +// Image.asset('assets/images/logos/cloudflare.png'), +// SizedBox(height: 10), +// ], +// ), +// ); +// break; +// case ProviderType.backup: +// return BrandCard( +// isBlocked: true, +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Image.asset('assets/images/logos/aws.png'), +// SizedBox(height: 10), +// BrandText.h2('4. Подключите Amazon AWS для бекапа'), +// SizedBox(height: 10), +// BrandText.body2( +// 'IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде'), +// _MockForm( +// hintText: 'Amazon AWS Access Key', +// length: 2, +// onPressed: () { +// var provider = cubit.state.all +// .firstWhere((p) => p.type == ProviderType.backup); +// cubit.connect(provider); +// }, +// ), +// SizedBox(height: 20), +// BrandButton.text( +// onPressed: () {}, +// title: 'Как получить API Token', +// ), +// ], +// ), +// ); +// } + +// return null; +// } diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index ea24ba42..6a712b54 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.dart @@ -5,6 +5,7 @@ import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; +import 'package:selfprivacy/ui/pages/initializing/initializing.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'about/about.dart'; @@ -28,6 +29,11 @@ class MorePage extends StatelessWidget { child: Column( children: [ BrandDivider(), + _NavItem( + title: 'Мастер Подключения', + iconData: BrandIcons.settings, + goTo: InitializingPage(), + ), _NavItem( title: 'Настройки приложения', iconData: BrandIcons.settings, diff --git a/lib/ui/pages/onboarding/onboarding.dart b/lib/ui/pages/onboarding/onboarding.dart index e5adbb8d..2b10a335 100644 --- a/lib/ui/pages/onboarding/onboarding.dart +++ b/lib/ui/pages/onboarding/onboarding.dart @@ -1,53 +1,120 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; -import 'package:selfprivacy/ui/pages/rootRoute.dart'; +import 'package:selfprivacy/ui/pages/initializing/initializing.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; -class OnboardingPage extends StatelessWidget { +class OnboardingPage extends StatefulWidget { const OnboardingPage({Key key}) : super(key: key); + @override + _OnboardingPageState createState() => _OnboardingPageState(); +} + +class _OnboardingPageState extends State { + PageController pageController = PageController(); + + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( - body: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 15, - vertical: 45, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BrandText.h1( - 'Онбординг', - ), - SizedBox(height: 20), - BrandText.body2( - 'Тут рассказ на 1-2 слайда о том, что делает это приложение, какие твои проблемы решает и как (в общем чего ожидать от сервиса).', - ), - ], - ), - ), - ), - BrandButton.rised( - onPressed: () { - Navigator.of(context) - .pushReplacement(materialRoute(RootPage())); - }, - title: 'Приступим!', - ) - ], - ), + body: PageView( + controller: pageController, + children: [ + _withPadding(firstPage()), + _withPadding(secondPage()), + ], ), ), ); } + + Widget _withPadding(Widget child) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: child, + ); + } + + Widget firstPage() { + return ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 30), + BrandText.h2( + 'Цифровая независимость и приватность, доступная каждому'), + SizedBox(height: 20), + BrandText.body2( + 'Почта и мессенджер с открытым исходным кодом на вашем личном сервере под вашим полным контролем.'), + Flexible( + child: Center( + child: Image.asset( + 'assets/images/onboarding/onboarding1.png', + ), + ), + ), + BrandButton.rised( + onPressed: () { + pageController.animateToPage( + 1, + duration: Duration(milliseconds: 300), + curve: Curves.easeIn, + ); + }, + title: 'Далее', + ), + SizedBox(height: 30), + ], + ), + ); + } + + Widget secondPage() { + return ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height, + ), + child: Column( + children: [ + SizedBox(height: 30), + BrandText.h2('Для работы понадобятся ваши аккаунты'), + SizedBox(height: 20), + BrandText.body2( + 'Для максимальноей приватности и независимости SelfPrivacy не использует свои серверы. \n \n Если у вас нет домена, аккаунтов на Hetzner, AWS и Clouflare, мы поможем их создать и подключить.'), + SizedBox(height: 20), + Center( + child: Image.asset( + 'assets/images/onboarding/logos_line.png', + ), + ), + Flexible( + child: Center( + child: Image.asset( + 'assets/images/onboarding/onboarding2.png', + ), + ), + ), + BrandButton.rised( + onPressed: () { + Navigator.of(context) + .pushReplacement(materialRoute(InitializingPage())); + }, + title: 'Понял', + ), + SizedBox(height: 30), + ], + ), + ); + } } diff --git a/lib/ui/pages/onboarding/onboarding.full.dart b/lib/ui/pages/onboarding/onboarding.full.dart index cf311f46..7123261f 100644 --- a/lib/ui/pages/onboarding/onboarding.full.dart +++ b/lib/ui/pages/onboarding/onboarding.full.dart @@ -1,231 +1,231 @@ -import 'package:flutter/material.dart'; -import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/config/text_themes.dart'; -import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_card/brand_card.dart'; -import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; -import 'package:selfprivacy/ui/components/brand_span_button/brand_span_button.dart'; -import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; +// import 'package:flutter/material.dart'; +// import 'package:selfprivacy/config/brand_theme.dart'; +// import 'package:selfprivacy/config/text_themes.dart'; +// import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; +// import 'package:selfprivacy/ui/components/brand_card/brand_card.dart'; +// import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; +// import 'package:selfprivacy/ui/components/brand_span_button/brand_span_button.dart'; +// import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; -class OnboardingPage extends StatelessWidget { - const OnboardingPage({Key key}) : super(key: key); +// class OnboardingPage extends StatelessWidget { +// const OnboardingPage({Key key}) : super(key: key); - @override - Widget build(BuildContext context) { - return Scaffold( - body: ListView( - padding: brandPagePadding1, - children: [ - BrandText.h4('Начало'), - BrandText.h1('SelfPrivacy'), - SizedBox( - height: 10, - ), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: - 'Для устойчивости и приватности требует много учёток. Полная инструкция на ', - style: body2Style, - ), - BrandSpanButton.link( - text: 'selfprivacy.org/start', - urlString: 'https://selfprivacy.org/start', - ), - ], - ), - ), - SizedBox(height: 50), - BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset('assets/images/logos/hetzner.png'), - SizedBox(height: 10), - BrandText.h2('1. Подключите сервер Hetzner'), - SizedBox(height: 10), - BrandText.body2( - 'Здесь будут жить наши данные и SelfPrivacy-сервисы'), - _MockForm( - hintText: 'Hetzner API Token', - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () => _showModal(context, _HowHetzner()), - title: 'Как получить API Token', - ), - ], - ), - ), - BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset('assets/images/logos/namecheap.png'), - SizedBox(height: 10), - BrandText.h2('2. Настройте домен'), - SizedBox(height: 10), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: 'Зарегистрируйте домен в ', - style: body2Style, - ), - BrandSpanButton.link( - text: 'NameCheap', - urlString: 'https://www.namecheap.com', - ), - TextSpan( - text: - ' или у любого другого регистратора. После этого настройте его на DNS-сервер CloudFlare', - style: body2Style, - ), - ], - ), - ), - _MockForm( - hintText: 'Домен, например, selfprivacy.org', - submitButtonText: 'Проверить DNS', - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как настроить DNS CloudFlare', - ), - ], - ), - ), - BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset('assets/images/logos/cloudflare.png'), - SizedBox(height: 10), - BrandText.h2('3. Подключите CloudFlare DNS'), - SizedBox(height: 10), - BrandText.body2('Для управления DNS вашего домена'), - _MockForm( - hintText: 'CloudFlare API Token', - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как получить API Token', - ), - ], - ), - ), - BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset('assets/images/logos/aws.png'), - SizedBox(height: 10), - BrandText.h2('4. Подключите Amazon AWS для бекапа'), - SizedBox(height: 10), - BrandText.body2( - 'IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде'), - _MockForm( - hintText: 'Amazon AWS Access Key', - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как получить API Token', - ), - ], - ), - ) - ], - ), - ); - } +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// body: ListView( +// padding: brandPagePadding1, +// children: [ +// BrandText.h4('Начало'), +// BrandText.h1('SelfPrivacy'), +// SizedBox( +// height: 10, +// ), +// RichText( +// text: TextSpan( +// children: [ +// TextSpan( +// text: +// 'Для устойчивости и приватности требует много учёток. Полная инструкция на ', +// style: body2Style, +// ), +// BrandSpanButton.link( +// text: 'selfprivacy.org/start', +// urlString: 'https://selfprivacy.org/start', +// ), +// ], +// ), +// ), +// SizedBox(height: 50), +// BrandCard( +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Image.asset('assets/images/logos/hetzner.png'), +// SizedBox(height: 10), +// BrandText.h2('1. Подключите сервер Hetzner'), +// SizedBox(height: 10), +// BrandText.body2( +// 'Здесь будут жить наши данные и SelfPrivacy-сервисы'), +// _MockForm( +// hintText: 'Hetzner API Token', +// ), +// SizedBox(height: 20), +// BrandButton.text( +// onPressed: () => _showModal(context, _HowHetzner()), +// title: 'Как получить API Token', +// ), +// ], +// ), +// ), +// BrandCard( +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Image.asset('assets/images/logos/namecheap.png'), +// SizedBox(height: 10), +// BrandText.h2('2. Настройте домен'), +// SizedBox(height: 10), +// RichText( +// text: TextSpan( +// children: [ +// TextSpan( +// text: 'Зарегистрируйте домен в ', +// style: body2Style, +// ), +// BrandSpanButton.link( +// text: 'NameCheap', +// urlString: 'https://www.namecheap.com', +// ), +// TextSpan( +// text: +// ' или у любого другого регистратора. После этого настройте его на DNS-сервер CloudFlare', +// style: body2Style, +// ), +// ], +// ), +// ), +// _MockForm( +// hintText: 'Домен, например, selfprivacy.org', +// submitButtonText: 'Проверить DNS', +// ), +// SizedBox(height: 20), +// BrandButton.text( +// onPressed: () {}, +// title: 'Как настроить DNS CloudFlare', +// ), +// ], +// ), +// ), +// BrandCard( +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Image.asset('assets/images/logos/cloudflare.png'), +// SizedBox(height: 10), +// BrandText.h2('3. Подключите CloudFlare DNS'), +// SizedBox(height: 10), +// BrandText.body2('Для управления DNS вашего домена'), +// _MockForm( +// hintText: 'CloudFlare API Token', +// ), +// SizedBox(height: 20), +// BrandButton.text( +// onPressed: () {}, +// title: 'Как получить API Token', +// ), +// ], +// ), +// ), +// BrandCard( +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Image.asset('assets/images/logos/aws.png'), +// SizedBox(height: 10), +// BrandText.h2('4. Подключите Amazon AWS для бекапа'), +// SizedBox(height: 10), +// BrandText.body2( +// 'IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде'), +// _MockForm( +// hintText: 'Amazon AWS Access Key', +// ), +// SizedBox(height: 20), +// BrandButton.text( +// onPressed: () {}, +// title: 'Как получить API Token', +// ), +// ], +// ), +// ) +// ], +// ), +// ); +// } - void _showModal(BuildContext context, Widget widget) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (BuildContext context) { - return widget; - }, - ); - } -} +// void _showModal(BuildContext context, Widget widget) { +// showModalBottomSheet( +// context: context, +// isScrollControlled: true, +// backgroundColor: Colors.transparent, +// builder: (BuildContext context) { +// return widget; +// }, +// ); +// } +// } -class _HowHetzner extends StatelessWidget { - const _HowHetzner({ - Key key, - }) : super(key: key); +// class _HowHetzner extends StatelessWidget { +// const _HowHetzner({ +// Key key, +// }) : super(key: key); - @override - Widget build(BuildContext context) { - return BrandModalSheet( - child: Padding( - padding: brandPagePadding2, - child: Column( - children: [ - SizedBox(height: 40), - BrandText.h2('Как получить Hetzner API Token'), - SizedBox(height: 20), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: '1 Переходим по ссылке ', - style: body1Style, - ), - BrandSpanButton.link( - text: 'hetzner.com/sdfsdfsdfsdf', - urlString: 'https://hetzner.com/sdfsdfsdfsdf', - ), - TextSpan( - text: ''' +// @override +// Widget build(BuildContext context) { +// return BrandModalSheet( +// child: Padding( +// padding: brandPagePadding2, +// child: Column( +// children: [ +// SizedBox(height: 40), +// BrandText.h2('Как получить Hetzner API Token'), +// SizedBox(height: 20), +// RichText( +// text: TextSpan( +// children: [ +// TextSpan( +// text: '1 Переходим по ссылке ', +// style: body1Style, +// ), +// BrandSpanButton.link( +// text: 'hetzner.com/sdfsdfsdfsdf', +// urlString: 'https://hetzner.com/sdfsdfsdfsdf', +// ), +// TextSpan( +// text: ''' -2 Заходим в созданный нами проект. Если такового - нет, значит создаём. +// 2 Заходим в созданный нами проект. Если такового - нет, значит создаём. -3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика). +// 3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика). -4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему. +// 4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему. -5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку. +// 5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку. -6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет. +// 6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет. - ''', - style: body1Style, - ), - ], - ), - ), - ], - ), - ), - ); - } -} +// ''', +// style: body1Style, +// ), +// ], +// ), +// ), +// ], +// ), +// ), +// ); +// } +// } -class _MockForm extends StatelessWidget { - const _MockForm({ - Key key, - @required this.hintText, - this.submitButtonText = 'Подключить', - }) : super(key: key); +// class _MockForm extends StatelessWidget { +// const _MockForm({ +// Key key, +// @required this.hintText, +// this.submitButtonText = 'Подключить', +// }) : super(key: key); - final String hintText; - final String submitButtonText; +// final String hintText; +// final String submitButtonText; - @override - Widget build(BuildContext context) { - return Column( - children: [ - SizedBox(height: 20), - TextField(decoration: InputDecoration(hintText: hintText)), - SizedBox(height: 20), - BrandButton.rised(onPressed: () {}, title: submitButtonText), - ], - ); - } -} +// @override +// Widget build(BuildContext context) { +// return Column( +// children: [ +// SizedBox(height: 20), +// TextField(decoration: InputDecoration(hintText: hintText)), +// SizedBox(height: 20), +// BrandButton.rised(onPressed: () {}, title: submitButtonText), +// ], +// ); +// } +// } diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 75788a5f..dc9552a3 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; +import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/logic/models/provider.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; @@ -8,7 +9,7 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; -import 'package:selfprivacy/ui/pages/providers/settings/setting.dart'; +import 'package:selfprivacy/ui/pages/providers/settings/settings.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; class ProvidersPage extends StatefulWidget { @@ -47,6 +48,8 @@ class _Card extends StatelessWidget { String title; String message; String stableText; + var isFullyInitilized = + context.watch().state.isFullyInitilized; switch (provider.type) { case ProviderType.server: @@ -77,6 +80,7 @@ class _Card extends StatelessWidget { }, ), child: BrandCard( + isBlocked: !isFullyInitilized, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/ui/pages/providers/settings/setting.dart b/lib/ui/pages/providers/settings/settings.dart similarity index 100% rename from lib/ui/pages/providers/settings/setting.dart rename to lib/ui/pages/providers/settings/settings.dart diff --git a/lib/ui/pages/rootRoute.dart b/lib/ui/pages/rootRoute.dart index 0dc102fc..949180b4 100644 --- a/lib/ui/pages/rootRoute.dart +++ b/lib/ui/pages/rootRoute.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; @@ -8,9 +9,6 @@ import 'package:selfprivacy/ui/pages/providers/providers.dart'; import 'package:selfprivacy/ui/pages/services/services.dart'; import 'package:selfprivacy/ui/pages/users/users.dart'; -import 'initializing/initializing.dart'; - - class RootPage extends StatefulWidget { const RootPage({Key key}) : super(key: key); @@ -36,17 +34,17 @@ class _RootPageState extends State @override Widget build(BuildContext context) { - var isReady = - context.select((ProvidersCubit bloc) => bloc.state.isFullyInitialized); + var isUserFilled = + context.watch().state.isFullyInitilized; return SafeArea( child: Scaffold( body: TabBarView( controller: tabController, children: [ - isReady ? ProvidersPage() : InitializingPage(), - isReady ? ServicesPage() : _NotReady(), - isReady ? UsersPage() : _NotReady(), + ProvidersPage(), + ServicesPage(), + isUserFilled ? UsersPage() : _NotReady(), MorePage(), ], ), diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index e0902ed1..8fde4106 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; +import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; @@ -52,6 +53,7 @@ class _Card extends StatelessWidget { String title; IconData iconData; String description; + var isFullyInitilized = context.watch().state.isFullyInitilized; switch (service.type) { case ServiceTypes.messanger: @@ -83,6 +85,7 @@ class _Card extends StatelessWidget { break; } return BrandCard( + isBlocked: !isFullyInitilized, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -102,8 +105,7 @@ class _Card extends StatelessWidget { context.read().connect(service); }) ], - if (service.state == StateType.stable) - BrandText.body2('Подключен'), + if (service.state == StateType.stable) BrandText.body2('Подключен'), ], ), ); diff --git a/pubspec.lock b/pubspec.lock index 69f3d6fe..82c5c198 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -28,7 +28,7 @@ packages: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "6.1.0" + version: "6.1.1" boolean_selector: dependency: transitive description: @@ -78,6 +78,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.5" + cubit_form: + dependency: "direct main" + description: + name: cubit_form + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.14" cupertino_icons: dependency: "direct main" description: @@ -92,6 +99,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.3.3" + email_validator: + dependency: transitive + description: + name: email_validator + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.6" equatable: dependency: "direct main" description: @@ -189,6 +203,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.16.1" + mask_text_input_formatter: + dependency: transitive + description: + name: mask_text_input_formatter + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" matcher: dependency: transitive description: @@ -343,6 +364,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.1+3" + shortuuid: + dependency: transitive + description: + name: shortuuid + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" sky_engine: dependency: transitive description: flutter @@ -439,6 +467,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.1+3" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.2" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a774e87b..8c176b10 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: flutter: sdk: flutter + cubit_form: ^0.0.14 cupertino_icons: ^1.0.0 easy_localization: ^2.3.3 equatable: ^1.2.5 @@ -31,6 +32,8 @@ flutter_icons: flutter: uses-material-design: true assets: + - assets/images/ + - assets/images/onboarding/ - assets/images/logos/ - assets/translations/ fonts: