fdroid
Kherel 2021-02-03 20:51:07 +01:00
parent 3de01fe12b
commit 25a386d511
13 changed files with 244 additions and 592 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -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';
}

View File

@ -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<bool> 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}');
}
}
}

View File

@ -69,4 +69,14 @@ class HetznerApi extends ApiMap {
startTime: DateTime.now(),
);
}
Future<HetznerServerDetails> reset({
HetznerServerDetails server,
}) async {
await loggedClient.post('/${server.id}/actions/poweron');
return server.copyWith(
startTime: DateTime.now(),
);
}
}

View File

@ -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<AppConfigState> {
emit(state);
}
void reset() {
repository.reset();
void clearAppConfig() {
repository.clearAppConfig();
emit(InitialAppConfigState());
}
@ -76,7 +76,16 @@ class AppConfigCubit extends Cubit<AppConfigState> {
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<AppConfigState> {
emit(state);
}
}
void setBackblazeKey(String backblazeKey) {
repository.saveBackblazeKey(backblazeKey);
emit(state.copyWith(backblazeKey: backblazeKey));
}
}

View File

@ -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<HetznerServerDetails> createServer(
String hetznerKey,
User rootUser,
String domainName,
String cloudFlareKey
) async {
Future<HetznerServerDetails> 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<HetznerServerDetails> reset(
String hetznerKey,
HetznerServerDetails server,
) async {
var hetznerApi = HetznerApi(hetznerKey);
return await hetznerApi.reset(server: server);
}
}

View File

@ -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<Object> 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<bool> get _fulfilementList => [
isHetznerFilled,
isCloudFlareFilled,
isBackblazeFilled,
isDomainFilled,
isUserFilled,
isServerFilled,

View File

@ -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<String>(
//(s) => regExp.hasMatch(s), 'invalid key format'),
//LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
],
);
applicationKey = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('required'),
//ValidationModel<String>(
//(s) => regExp.hasMatch(s), 'invalid key format'),
//LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
],
);
super.setFields([keyId, applicationKey]);
}
@override
FutureOr<void> onSubmit() async {
String encodedApiKey =
encodeToBase64(keyId.state.value, applicationKey.state.value);
initializingCubit.setBackblazeKey(encodedApiKey);
}
final AppConfigCubit initializingCubit;
FieldCubit<String> keyId;
FieldCubit<String> applicationKey;
@override
FutureOr<bool> 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<void> 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;
}
}

View File

@ -44,10 +44,23 @@ class _ProgressBarState extends State<ProgressBar> {
} 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,

View File

@ -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<AppConfigCubit, AppConfigState>(
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<BackblazeFormCubit>();
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(

View File

@ -108,7 +108,7 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
),
),
onPressed: () {
context.read<AppConfigCubit>().reset();
context.read<AppConfigCubit>().clearAppConfig();
Navigator.of(context)..pop()..pop();
},
),

View File

@ -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<void>(
// 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),
// ],
// );
// }
// }

View File

@ -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<InitializingPage> {
// 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<Widget> getSteps() => <Widget>[
// 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<void>(
// 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,
// ),
// ],
// );
// }
// }