diff --git a/assets/images/logos/backblaze.png b/assets/images/logos/backblaze.png new file mode 100644 index 00000000..c4818556 Binary files /dev/null and b/assets/images/logos/backblaze.png differ diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index 1c852e23..0b686adf 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -50,6 +50,6 @@ class BNames { static String hetznerServer = 'hetznerServer'; static String isDkimSetted = 'isDkimSetted'; static String isDnsChecked = 'isDnsChecked'; - static String isServerStarted = 'isServerStarted'; + static String backblazeKey = 'backblazeKey'; } diff --git a/lib/logic/api_maps/backblaze.dart b/lib/logic/api_maps/backblaze.dart new file mode 100644 index 00000000..025f0b87 --- /dev/null +++ b/lib/logic/api_maps/backblaze.dart @@ -0,0 +1,38 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:selfprivacy/logic/api_maps/api_map.dart'; + +class BackblazeApi extends ApiMap { + BackblazeApi([String token]) { + if (token != null) { + loggedClient.options = BaseOptions( + headers: {'Authorization': 'Basic $token'}, + baseUrl: rootAddress, + ); + } + } + + @override + String rootAddress = + 'https://api.backblazeb2.com/b2api/v2/b2_authorize_account'; + + Future isValid(String token) async { + var options = Options( + headers: {'Authorization': 'Basic $token'}, + validateStatus: (status) { + return status == HttpStatus.ok || status == HttpStatus.unauthorized; + }, + ); + + Response response = await loggedClient.get(rootAddress, options: options); + + if (response.statusCode == HttpStatus.ok) { + print(response); + return true; + } else if (response.statusCode == HttpStatus.unauthorized) { + return false; + } else { + throw Exception('code: ${response.statusCode}'); + } + } +} \ No newline at end of file diff --git a/lib/logic/api_maps/hetzner.dart b/lib/logic/api_maps/hetzner.dart index 802f8333..1753b13c 100644 --- a/lib/logic/api_maps/hetzner.dart +++ b/lib/logic/api_maps/hetzner.dart @@ -69,4 +69,14 @@ class HetznerApi extends ApiMap { startTime: DateTime.now(), ); } + + Future reset({ + HetznerServerDetails server, + }) async { + await loggedClient.post('/${server.id}/actions/poweron'); + + return server.copyWith( + startTime: DateTime.now(), + ); + } } diff --git a/lib/logic/cubit/app_config/app_config_cubit.dart b/lib/logic/cubit/app_config/app_config_cubit.dart index 20948829..8b0828bf 100644 --- a/lib/logic/cubit/app_config/app_config_cubit.dart +++ b/lib/logic/cubit/app_config/app_config_cubit.dart @@ -12,7 +12,7 @@ import 'app_config_repository.dart'; part 'app_config_state.dart'; -/// initializeing steps: +/// initializeing steps: /// 1. Hetzner key |setHetznerKey /// 2. Cloudflare key |setCloudflareKey /// 3. Set Domain address |setDomain @@ -34,8 +34,8 @@ class AppConfigCubit extends Cubit { emit(state); } - void reset() { - repository.reset(); + void clearAppConfig() { + repository.clearAppConfig(); emit(InitialAppConfigState()); } @@ -76,7 +76,16 @@ class AppConfigCubit extends Cubit { state.cloudFlareKey, state.cloudFlareDomain.zoneId, ); - emit(state.copyWith(isDkimSetted: true)); + var hetznerServerDetails = await repository.reset( + state.hetznerKey, + state.hetznerServer, + ); + emit( + state.copyWith( + isDkimSetted: true, + hetznerServer: hetznerServerDetails, + ), + ); }; _tryOrAddError(state, callBack); @@ -143,4 +152,9 @@ class AppConfigCubit extends Cubit { emit(state); } } + + void setBackblazeKey(String backblazeKey) { + repository.saveBackblazeKey(backblazeKey); + emit(state.copyWith(backblazeKey: backblazeKey)); + } } diff --git a/lib/logic/cubit/app_config/app_config_repository.dart b/lib/logic/cubit/app_config/app_config_repository.dart index d9beba3e..d0844470 100644 --- a/lib/logic/cubit/app_config/app_config_repository.dart +++ b/lib/logic/cubit/app_config/app_config_repository.dart @@ -20,6 +20,7 @@ class AppConfigRepository { hetznerKey: box.get(BNames.hetznerKey), cloudFlareKey: box.get(BNames.cloudFlareKey), cloudFlareDomain: box.get(BNames.cloudFlareDomain), + backblazeKey: box.get(BNames.backblazeKey), rootUser: box.get(BNames.rootUser), hetznerServer: box.get(BNames.hetznerServer), isServerStarted: box.get(BNames.isServerStarted, defaultValue: false), @@ -28,7 +29,7 @@ class AppConfigRepository { ); } - void reset() { + void clearAppConfig() { box.clear(); } @@ -36,6 +37,10 @@ class AppConfigRepository { box.put(BNames.hetznerKey, key); } + void saveBackblazeKey(String key) { + box.put(BNames.backblazeKey, key); + } + void saveCloudFlare(String key) { box.put(BNames.cloudFlareKey, key); } @@ -104,12 +109,8 @@ class AppConfigRepository { return true; } - Future createServer( - String hetznerKey, - User rootUser, - String domainName, - String cloudFlareKey - ) async { + Future createServer(String hetznerKey, User rootUser, + String domainName, String cloudFlareKey) async { var hetznerApi = HetznerApi(hetznerKey); var serverDetails = await hetznerApi.createServer( cloudFlareKey: cloudFlareKey, @@ -164,4 +165,12 @@ class AppConfigRepository { cloudflareApi.close(); } + + Future reset( + String hetznerKey, + HetznerServerDetails server, + ) async { + var hetznerApi = HetznerApi(hetznerKey); + return await hetznerApi.reset(server: server); + } } diff --git a/lib/logic/cubit/app_config/app_config_state.dart b/lib/logic/cubit/app_config/app_config_state.dart index aa6ecb6b..37cb368d 100644 --- a/lib/logic/cubit/app_config/app_config_state.dart +++ b/lib/logic/cubit/app_config/app_config_state.dart @@ -4,6 +4,7 @@ class AppConfigState extends Equatable { const AppConfigState({ this.hetznerKey, this.cloudFlareKey, + this.backblazeKey, this.cloudFlareDomain, this.rootUser, this.hetznerServer, @@ -20,6 +21,7 @@ class AppConfigState extends Equatable { List get props => [ hetznerKey, cloudFlareKey, + backblazeKey, cloudFlareDomain, rootUser, hetznerServer, @@ -33,6 +35,7 @@ class AppConfigState extends Equatable { final String hetznerKey; final String cloudFlareKey; + final String backblazeKey; final CloudFlareDomain cloudFlareDomain; final User rootUser; final HetznerServerDetails hetznerServer; @@ -47,6 +50,7 @@ class AppConfigState extends Equatable { AppConfigState copyWith({ String hetznerKey, String cloudFlareKey, + String backblazeKey, CloudFlareDomain cloudFlareDomain, User rootUser, HetznerServerDetails hetznerServer, @@ -61,6 +65,7 @@ class AppConfigState extends Equatable { AppConfigState( hetznerKey: hetznerKey ?? this.hetznerKey, cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey, + backblazeKey: backblazeKey ?? this.backblazeKey, cloudFlareDomain: cloudFlareDomain ?? this.cloudFlareDomain, rootUser: rootUser ?? this.rootUser, hetznerServer: hetznerServer ?? this.hetznerServer, @@ -76,6 +81,7 @@ class AppConfigState extends Equatable { bool get isHetznerFilled => hetznerKey != null; bool get isCloudFlareFilled => cloudFlareKey != null; + bool get isBackblazeFilled => backblazeKey != null; bool get isDomainFilled => cloudFlareDomain != null; bool get isUserFilled => rootUser != null; bool get isServerFilled => hetznerServer != null; @@ -90,6 +96,7 @@ class AppConfigState extends Equatable { List get _fulfilementList => [ isHetznerFilled, isCloudFlareFilled, + isBackblazeFilled, isDomainFilled, isUserFilled, isServerFilled, diff --git a/lib/logic/cubit/forms/initializing/backblaze_form_cubit.dart b/lib/logic/cubit/forms/initializing/backblaze_form_cubit.dart new file mode 100644 index 00000000..8b594c09 --- /dev/null +++ b/lib/logic/cubit/forms/initializing/backblaze_form_cubit.dart @@ -0,0 +1,81 @@ + +import 'dart:async'; +import 'dart:convert'; +import 'package:cubit_form/cubit_form.dart'; +import 'package:selfprivacy/logic/api_maps/backblaze.dart'; +import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; + +class BackblazeFormCubit extends FormCubit { + BackblazeApi apiClient = BackblazeApi(); + + BackblazeFormCubit(this.initializingCubit) { + //var regExp = RegExp(r"\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]"); + keyId = FieldCubit( + initalValue: '', + validations: [ + RequiredStringValidation('required'), + //ValidationModel( + //(s) => regExp.hasMatch(s), 'invalid key format'), + //LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64') + ], + ); + + applicationKey = FieldCubit( + initalValue: '', + validations: [ + RequiredStringValidation('required'), + //ValidationModel( + //(s) => regExp.hasMatch(s), 'invalid key format'), + //LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64') + ], + ); + + super.setFields([keyId, applicationKey]); + } + + @override + FutureOr onSubmit() async { + String encodedApiKey = + encodeToBase64(keyId.state.value, applicationKey.state.value); + + initializingCubit.setBackblazeKey(encodedApiKey); + } + + final AppConfigCubit initializingCubit; + + FieldCubit keyId; + + FieldCubit applicationKey; + + @override + FutureOr asyncValidation() async { + bool isKeyValid; + try { + String encodedApiKey = + encodeToBase64(keyId.state.value, applicationKey.state.value); + isKeyValid = await apiClient.isValid(encodedApiKey); + } catch (e) { + addError(e); + } + + if (!isKeyValid) { + keyId.setError('bad key'); + applicationKey.setError('bad key'); + return false; + } + return true; + } + + @override + Future close() async { + apiClient.close(); + + return super.close(); + } + + String encodeToBase64(String keyId, String applicationKey) { + String _apiKey = '$keyId:$applicationKey'; + String encodedApiKey = base64.encode(utf8.encode(_apiKey)); + return encodedApiKey; + } +} \ No newline at end of file diff --git a/lib/ui/components/progress_bar/progress_bar.dart b/lib/ui/components/progress_bar/progress_bar.dart index 00f196cc..2adf8a97 100644 --- a/lib/ui/components/progress_bar/progress_bar.dart +++ b/lib/ui/components/progress_bar/progress_bar.dart @@ -44,10 +44,23 @@ class _ProgressBarState extends State { } else { odd.add(step); } - i++; + i++; } - even.add(Spacer()); - odd.insert(0, Spacer()); + // even.add(SizedBox( + // width: 0, + // )); + odd + ..insert( + 0, + SizedBox( + width: 50, + ), + ) + ..add( + SizedBox( + width: 50, + ), + ); return Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/ui/pages/initializing/initializing.dart b/lib/ui/pages/initializing/initializing.dart index 65ee206e..04266bc8 100644 --- a/lib/ui/pages/initializing/initializing.dart +++ b/lib/ui/pages/initializing/initializing.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/text_themes.dart'; +import 'package:selfprivacy/logic/cubit/forms/initializing/backblaze_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/cloudflare_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/domain_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/hetzner_form_cubit.dart'; @@ -27,12 +28,13 @@ class InitializingPage extends StatelessWidget { var actualPage = [ _stepHetzner(cubit), _stepCloudflare(cubit), + _stepBackblaze(cubit), _stepDomain(cubit), _stepUser(cubit), _stepServer(cubit), _stepCheck(cubit), Container(child: Text('Everythigng is initialized')) - ][cubit.state.progress]; + ][2]; return BlocListener( listener: (context, state) { if (state.isFullyInitilized) { @@ -46,12 +48,13 @@ class InitializingPage extends StatelessWidget { Padding( padding: brandPagePadding1, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ ProgressBar( steps: [ 'Hetzner', 'CloudFlare', + 'Backblaze', 'Domain', 'User', 'Server', @@ -177,6 +180,55 @@ class InitializingPage extends StatelessWidget { ); } + Widget _stepBackblaze(AppConfigCubit initializingCubit) { + return BlocProvider( + create: (context) => BackblazeFormCubit(initializingCubit), + child: Builder(builder: (context) { + var formCubit = context.watch(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Spacer(), + Image.asset('assets/images/logos/backblaze.png'), + SizedBox(height: 10), + BrandText.h2('Подключите облачное хранилище Backblaze'), + SizedBox(height: 10), + BrandText.body2('Здесь будут храниться данные'), + Spacer(), + CubitFormTextField( + formFieldCubit: formCubit.keyId, + textAlign: TextAlign.center, + scrollPadding: EdgeInsets.only(bottom: 70), + decoration: InputDecoration( + hintText: 'KeyID', + ), + ), + Spacer(), + CubitFormTextField( + formFieldCubit: formCubit.applicationKey, + textAlign: TextAlign.center, + scrollPadding: EdgeInsets.only(bottom: 70), + decoration: InputDecoration( + hintText: 'Master Application Key', + ), + ), + Spacer(), + BrandButton.rised( + onPressed: + formCubit.state.isSubmitting ? null : formCubit.trySubmit, + title: 'Подключить', + ), + SizedBox(height: 10), + BrandButton.text( + onPressed: () => _showModal(context, _HowHetzner()), + title: 'Как получить API Token', + ), + ], + ); + }), + ); + } + Widget _stepDomain(AppConfigCubit initializingCubit) { return BlocProvider( create: (context) => DomainFormCubit(initializingCubit), @@ -296,8 +348,8 @@ class InitializingPage extends StatelessWidget { SizedBox(height: 10), BrandText.body2( isDnsChecked - ? 'Dns сервера вступили в силу, мы стартанули сервер, как только он поднимиться, мы закончим инициализацию.' - : 'Мы начали процесс инциализации сервера, раз в минуты мы будем проверять наличие DNS записей, как только они вступят в силу мы продолжим инциализацию', + ? 'Dns сервера вступили в силу, мы стартанули сервер, как только он поднимется, мы закончим инициализацию.' + : 'Мы начали процесс инциализации сервера, раз в минуту мы будем проверять наличие DNS записей, как только они вступят в силу мы продолжим инциализацию', ), SizedBox(height: 10), Row( diff --git a/lib/ui/pages/more/app_settings/app_setting.dart b/lib/ui/pages/more/app_settings/app_setting.dart index 2e6113f5..1b00529a 100644 --- a/lib/ui/pages/more/app_settings/app_setting.dart +++ b/lib/ui/pages/more/app_settings/app_setting.dart @@ -108,7 +108,7 @@ class _AppSettingsPageState extends State { ), ), onPressed: () { - context.read().reset(); + context.read().clearAppConfig(); Navigator.of(context)..pop()..pop(); }, ), diff --git a/lib/ui/pages/onboarding/onboarding.full.dart b/lib/ui/pages/onboarding/onboarding.full.dart deleted file mode 100644 index 7123261f..00000000 --- a/lib/ui/pages/onboarding/onboarding.full.dart +++ /dev/null @@ -1,231 +0,0 @@ -// 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); - -// @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; -// }, -// ); -// } -// } - -// 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: ''' - -// 2 Заходим в созданный нами проект. Если такового - нет, значит создаём. - -// 3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика). - -// 4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему. - -// 5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку. - -// 6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет. - -// ''', -// style: body1Style, -// ), -// ], -// ), -// ), -// ], -// ), -// ), -// ); -// } -// } - -// class _MockForm extends StatelessWidget { -// const _MockForm({ -// Key key, -// @required this.hintText, -// this.submitButtonText = 'Подключить', -// }) : super(key: key); - -// 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), -// ], -// ); -// } -// } diff --git a/lib/ui/pages/onboarding/onboarding.old.dart b/lib/ui/pages/onboarding/onboarding.old.dart deleted file mode 100644 index ee204670..00000000 --- a/lib/ui/pages/onboarding/onboarding.old.dart +++ /dev/null @@ -1,341 +0,0 @@ -// 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:selfprivacy/ui/components/dots_indicator/dots_indicator.dart'; -// import 'package:selfprivacy/ui/pages/rootRoute.dart'; -// import 'package:selfprivacy/utils/route_transitions/basic.dart'; - -// class InitializingPage extends StatefulWidget { -// const InitializingPage({Key key}) : super(key: key); - -// @override -// _InitializingPageState createState() => _InitializingPageState(); -// } - -// class _InitializingPageState extends State { -// PageController controller; -// var currentPage = 0; - -// @override -// void initState() { -// controller = PageController( -// initialPage: 0, -// )..addListener(() { -// if (currentPage != controller.page.toInt()) { -// setState(() { -// currentPage = controller.page.toInt(); -// }); -// } -// }); -// super.initState(); -// WidgetsBinding.instance.addPostFrameCallback((_) {}); -// } - -// @override -// void dispose() { -// controller.dispose(); -// super.dispose(); -// } - -// @override -// Widget build(BuildContext context) { -// var steps = getSteps(); - -// return SafeArea( -// child: Scaffold( -// body: ListView( -// shrinkWrap: true, -// 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: 'selfprivacy.org/start', -// urlString: 'https://selfprivacy.org/start', -// ), -// ], -// ), -// ), -// ], -// ), -// ), -// Container( -// height: 480, -// child: PageView.builder( -// physics: NeverScrollableScrollPhysics(), -// allowImplicitScrolling: false, -// controller: controller, -// itemBuilder: (_, index) { -// return Padding( -// padding: brandPagePadding2, -// child: steps[index], -// ); -// }, -// itemCount: 4, -// ), -// ), -// DotsIndicator( -// activeIndex: currentPage, -// count: steps.length, -// ), -// SizedBox(height: 50), -// ], -// ), -// ), -// ); -// } - -// List getSteps() => [ -// BrandCard( -// child: Column( -// mainAxisSize: MainAxisSize.min, -// 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( -// onPressed: _nextPage, -// hintText: 'Hetzner API Token', -// length: 2, -// ), -// SizedBox(height: 20), -// Spacer(), -// BrandButton.text( -// onPressed: () => _showModal(context, _HowHetzner()), -// title: 'Как получить API Token', -// ), -// ], -// ), -// ), -// BrandCard( -// child: Column( -// mainAxisSize: MainAxisSize.min, -// 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( -// onPressed: _nextPage, -// hintText: 'Домен, например, selfprivacy.org', -// submitButtonText: 'Проверить DNS', -// length: 2, -// ), -// Spacer(), -// BrandButton.text( -// onPressed: () {}, -// title: 'Как настроить DNS CloudFlare', -// ), -// ], -// ), -// ), -// BrandCard( -// child: Column( -// mainAxisSize: MainAxisSize.min, -// 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( -// onPressed: _nextPage, -// hintText: 'CloudFlare API Token', -// length: 2, -// ), -// Spacer(), -// BrandButton.text( -// onPressed: () {}, -// title: 'Как получить API Token', -// ), -// ], -// ), -// ), -// BrandCard( -// child: Column( -// mainAxisSize: MainAxisSize.min, -// 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( -// onPressed: () { -// Navigator.of(context) -// .pushReplacement(materialRoute(RootPage())); -// }, -// hintText: 'Amazon AWS Access Key', -// length: 2, -// ), -// Spacer(), -// 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 _nextPage() => controller.nextPage( -// duration: Duration(milliseconds: 300), -// curve: Curves.easeIn, -// ); -// } - -// 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: ''' - -// 2 Заходим в созданный нами проект. Если такового - нет, значит создаём. - -// 3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика). - -// 4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему. - -// 5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку. - -// 6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет. - -// ''', -// style: body1Style, -// ), -// ], -// ), -// ), -// ], -// ), -// ), -// ); -// } -// } - -// class _MockForm extends StatefulWidget { -// const _MockForm({ -// Key key, -// @required this.hintText, -// this.submitButtonText = 'Подключить', -// @required this.onPressed, -// @required this.length, -// }) : super(key: key); - -// final String hintText; -// final String submitButtonText; -// final int length; - -// final VoidCallback onPressed; - -// @override -// __MockFormState createState() => __MockFormState(); -// } - -// class __MockFormState extends State<_MockForm> { -// String text = ''; - -// @override -// Widget build(BuildContext context) { -// return Column( -// children: [ -// SizedBox(height: 20), -// TextField( -// onChanged: (value) => { -// setState(() { -// text = value; -// }) -// }, -// decoration: InputDecoration(hintText: widget.hintText), -// ), -// SizedBox(height: 20), -// BrandButton.rised( -// onPressed: -// text.length == widget.length ? widget.onPressed ?? () {} : null, -// title: widget.submitButtonText, -// ), -// ], -// ); -// } -// }