Merge pull request 'Pull data into fdroid-ready repository' (#4) from master into fdroid

Reviewed-on: kherel/selfprivacy.org.app#4
fdroid
ilchub 2021-03-18 15:43:32 +02:00
commit 3be5ca615e
109 changed files with 1753 additions and 1109 deletions

Binary file not shown.

BIN
assets/fonts/Inter-Bold.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

@ -0,0 +1,12 @@
### О проекте
Всё больше организаций хотят владеть нашими данными
А мы сами хотим распоряжаться своими **данными** на своем сервере.
### Миссия проекта
Цифровая независимость и приватность доступная каждому
### Цель
Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких

View File

@ -0,0 +1,12 @@
### О проекте
Всё больше организаций хотят владеть нашими данными
А мы сами хотим распоряжаться своими **данными** на своем сервере.
### Миссия проекта
Цифровая независимость и приватность доступная каждому
### Цель
Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких

View File

@ -0,0 +1,20 @@
1. Visit the following [link](https://console.hetzner.cloud/) and sign
into newly created account.
2. Enter into previously created project. If you haven't created one,
then please proceed.
3. Hover side panel with mouse cursor. Panel should expand and show us
a menu. We're interested in the last one — **Security** (icon of a
key).
4. Next, in the upper part of an interface, we can see approximately
the following: **SSH Keys, API Tokens, Certificates, Members.** You
need **API Tokens**. Click on it.
5. In the right part of the interface, there should be **Generate API
token** button. If you're using mobile version og a webpage, in the
lower right corner you'll see **red cross**. Push that button.
6. In the **Description** field, give our token a name (this can be any
name that you like. It doesn't influence the essence.
7. Under the **Description** field we can see a possibility to choose
**permissions**. Pick **Read & Write**.
8. Click **Generate API Token.**
9. After that, our key will be shown. Store it in the reliable place,
or in the password manager, which is better.

View File

@ -0,0 +1,7 @@
### Как получить Hetzner API Token
1. Переходим по ссылке https://hetzner.com
2. Заходим в созданный нами проект. Если такового - нет, значит создаём.
3. Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).
4. Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.
5. В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.
6. В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.

View File

@ -1,3 +1,171 @@
{ {
"test": "en-test" "test": "en-test",
"locale": "en",
"basis": {
"_comment": "Basic interface elements",
"providers": "Providers",
"services": "Services",
"users": "Users",
"more": "More",
"next": "Next",
"got_it": "Got it",
"settings": "Settings",
"password": "Password",
"create": "Add new",
"confirmation": "Confirmation",
"cancel": "Cancel",
"delete": "Delete",
"close": "Close",
"connect": "Connect",
"domain": "Domain",
"saving": "Saving..",
"nickname": "nickname",
"loading": "loading"
},
"more": {
"_comment": "'More' tab",
"configuration_wizard": "Setup wizard",
"settings": "Application settings",
"about_project": "About us",
"about_app": "About application",
"onboarding": "Onboarding",
"console": "Console",
"about_app_page": {
"text": "Тут любая служебная информация, v.{}"
}
},
"onboarding": {
"_comment": "Onboarding pages",
"page1_title": "Digital independence, available to all of us",
"page1_text": "Mail, VPN, Messenger, social network and much more on your private server, under your control.",
"page2_title": "SelfPrivacy — it's not a cloud, but your personal datacenter",
"page2_text": "SelfPrivacy works only with your provider accounts: Hetzner, Cloudflare, Backblaze. If you do not own those, we'll help you to create them"
},
"providers": {
"_comment": "'Providers' tab",
"page_title": "Your Data Center",
"server": {
"card_title": "Server",
"bottom_sheet": {
"1": "It's a virtual computer, where all your services live.",
"2": "1 CPU, RAM 4Gb, 40Gb — $5 per month",
"3": "Status — Good"
}
},
"domain": {
"card_title": "Domain",
"bottom_sheet": {
"1": "It's your personal internet address that will point to the server and other services of yours.",
"2": "{} — expires on {}",
"3": "Status — Good"
}
},
"backup": {
"card_title": "Backup",
"bottom_sheet": {
"1": "Will save your day in case of incident: hackers attack, server deletion, etc.",
"2": "3Gb/10Gb, last backup was yesterday {}",
"3": "Status — Good"
}
}
},
"not_ready_card": {
"_comment": "Card shown when user skips initial setup",
"1": "Please finish application setup using ",
"2": "@:more.configuration_wizard",
"3": " for further work"
},
"services": {
"_comment": "Вкладка сервисы",
"title": "Your personal, private and independent services.",
"mail": {
"title": "E-Mail",
"subtitle": "E-Mail for company and family.",
"bottom_sheet": {
"1": "To connect to the mailserver, please use {} domain alongside with username and password, that you created. Also feel free to invite",
"2": "new users"
}
},
"messenger": {
"title": "Messenger",
"subtitle": "Telegram or Signal not so private as Delta.Chat that uses your private server.",
"bottom_sheet": {
"1": "For connection, please use {} domain and credentials that you created."
}
},
"password_manager": {
"title": "Password Manager",
"subtitle": "Base of your security. Bitwarden will help you to create, store and move passwords between devices, as well as input them, when requested using autocompletion.",
"bottom_sheet": {
"1": "You can connect to the service and create a user via this link:"
}
},
"video": {
"title": "Videomeet",
"subtitle": "Zoom and Google Meet are good, but Jitsi Meet is a worth alternative that also gives you confidence that you're not being listened.",
"bottom_sheet": {
"1": "Using Jitsi as simple as just visiting this link:"
}
},
"cloud": {
"title": "Cloud Storage",
"subtitle": "Do not allow cloud services to read your data by using NextCloud.",
"bottom_sheet": {
"1": "You can connect and create a new user here:"
}
},
"social_network": {
"title": "Social Network",
"subtitle": "It's hard to believe, but it became possible to create your own social network, with your own rules and target audience.",
"bottom_sheet": {
"1": "You can connect and create new social user here:"
}
},
"git": {
"title": "Git-server",
"subtitle": "Private alternative to the Github, that belongs to you, but not a Microsoft.",
"bottom_sheet": {
"1": "You can connect and create a new user here:"
}
}
},
"users": {
"_comment": "'Users' tab",
"add_new_user": "Add a first user",
"new_user": "New user",
"not_ready": "Please connect server, domain and DNS in the Providers tab, to be able to add a first user",
"nobody_here": "Здесь пока никого",
"login": "Login",
"onboarding": "Onboarding",
"console": "Console",
"new_user_info_note": "New user will automatically be granted an access to all of the services",
"delete_confirm_question": "Are you sure?",
"reset_password": "Reset password",
"account": "Account",
"send_regisration_data": "Share login credentials"
},
"initializing": {
"_comment": "initializing page",
"1": "Connect Hetzner server",
"2": "Here, your data and SelfPrivacy services wiil reside",
"how": "How to obtain API token",
"3": "Connect CloudFlare",
"4": "To manage your domain's DNS",
"5": "CloudFlare API Token",
"6": "Connect Backblaze storage",
"7": "No connected domains at the moment",
"8": "Loading domains list",
"9": "Found more than one domain. For your own security, please be asked to delete unnecessary domains",
"10": "Save domain",
"11": "Create server",
"what": "What does it mean?",
"13": "Server rebooted. Waiting for the last verification...",
"14": "Server started. It will be validated and rebooted now...",
"15": "Server created. DNS checks and server boot in progress...",
"16": "Until the next check: ",
"17": "Check",
"18": "How to obtain Hetzner API Token",
"19": "1 Go via this link ",
"20": "\n"
}
} }

View File

@ -1,3 +1,171 @@
{ {
"test": "ру-тест" "test": "ru-test",
"locale": "ru",
"basis": {
"_comment": "базовые элементы интерфейса",
"providers": "Провайдеры",
"services": "Сервисы",
"users": "Пользователи",
"more": "Еще",
"next": "Далее",
"got_it": "Понял",
"settings": "Настройки",
"password": "Пароль",
"create": "Создать",
"confirmation": "Подтверждение",
"cancel": "Отменить",
"delete": "Удалить",
"close": "Закрыть",
"connect": "Подключить",
"domain": "Домен",
"saving": "Сохранение..",
"nickname": "Никнейм",
"loading": "Загрузка"
},
"more": {
"_comment": "вкладка еще",
"configuration_wizard": "Мастер Подключения",
"settings": "Настройки приложения",
"about_project": "О проекте SelfPrivacy",
"about_app": "О приложении",
"onboarding": "Onboarding",
"console": "Console",
"about_app_page": {
"text": "Тут любая служебная информация, v.{}"
}
},
"onboarding": {
"_comment": "страницы онбординга",
"page1_title": "Цифровая независимость доступна каждому",
"page1_text": "Почта, VPN, Мессенджер, социальная сеть и многое другое на вашем личном сервере, под вашим полным контролем.",
"page2_title": "SelfPrivacy — это не облако, а ваш личный дата-центр",
"page2_text": "У SelfPrivacy работает только с вашими сервис-провадерами: Hetzner, Cloudflare, Backblaze. Если у вас нет учетных записей, мы поможем их создать."
},
"providers": {
"_comment": "вкладка провайдеры",
"page_title": "Ваш Дата-центр",
"server": {
"card_title": "Сервер",
"bottom_sheet": {
"1": "Это виртульный компьютер на котором работают все ваши сервисы.",
"2": "1 CPU, RAM 4Gb, 40Gb — $5 в месяц",
"3": "Статус — в норме"
}
},
"domain": {
"card_title": "Домен",
"bottom_sheet": {
"1": "Это ваш личный адрес в интернете, который будет указывать на сервер и другие ваши сервисы.",
"2": "{} — продлен до {}",
"3": "Статус — в норме"
}
},
"backup": {
"card_title": "Резервное копирование",
"bottom_sheet": {
"1": "Выручит в любой ситуации: хакерская атака, удаление сервера и т.п.",
"2": "3Gb — бестплатно до 10Gb, последний вчера в {}",
"3": "Статус — в норме"
}
}
},
"not_ready_card": {
"_comment": "Карточка показывающая когда человек скипнул настройку, на карте текст из 3 блоков, средний содержит ссыку на мастер подключения",
"1": "Завершите настройку приложения используя ",
"2": "@:more.configuration_wizard",
"3": " для продолжения работы"
},
"services": {
"_comment": "Вкладка сервисы",
"title": "Ваши личные приватные и независимые сервисы",
"mail": {
"title": "Почта",
"subtitle": "Электронная почта для семьи или компании",
"bottom_sheet": {
"1": "Для подключения почтового ящика используйте {} и логин пароль, который вы создали. Так же приглашайте",
"2": "новых пользователей"
}
},
"messenger": {
"title": "Мессенджер",
"subtitle": "Telegram и Signal не так приватны, как Delta.Chat использующий ваш личный сервер.",
"bottom_sheet": {
"1": "Для подключения используйте {} и логин пароль, который вы создали"
}
},
"password_manager": {
"title": "Менеджер паролей",
"subtitle": "Фундамент безопасности. Создавать, хранить, копировать пароли между устройствами, вбивать их в формы поможет — Bitwarden.",
"bottom_sheet": {
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
}
},
"video": {
"title": "Видеоконференция",
"subtitle": "Zoom и Google meet отличные инструменты, но Jitsi meet не хуже и дает уверенность, что вас никто не подслушивает.",
"bottom_sheet": {
"1": "Для использования просто перейдите по ссылке:"
}
},
"cloud": {
"title": "Файловое облако",
"subtitle": "Не позволяйте облачным сервисам читать ваши данные используйте NextCloud.",
"bottom_sheet": {
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
}
},
"social_network": {
"title": "Социальная сеть",
"subtitle": "Сложно поверить, но стало возможным создать свою собственную социальную сеть, со своими правилами и аудиторией.",
"bottom_sheet": {
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
}
},
"git": {
"title": "Git-сервер",
"subtitle": "Приватная альтернатива Github, которая принадлежит вам, а не Microsoft.",
"bottom_sheet": {
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
}
}
},
"users": {
"_comment": "'Users' tab",
"add_new_user": "Добавьте первого пользователя",
"new_user": "Новый пользователь",
"not_ready": "Подключите сервер, домен и DNS в разделу Провайдеры, чтобы добавить первого пользователя",
"nobody_here": "'Здесь пока никого'",
"login": "Логин",
"onboarding": "Onboarding",
"console": "Console",
"new_user_info_note": "Новый пользователь автоматически получит доступ ко всем сервисам. Ещё какое-то описание.",
"delete_confirm_question": "удалить учетную запись?",
"reset_password": "Сбросить пароль",
"account": "Учетная запись",
"send_regisration_data": "Отправить реквизиты для входа"
},
"initializing": {
"_comment": "initializing page",
"1": "Подключите сервер Hetzner",
"2": "Здесь будут жить наши данные и SelfPrivacy-сервисы",
"how": "Как получить API Token",
"3": "'Подключите CloudFlare'",
"4": "Для управления DNS вашего домена",
"5": "CloudFlare API Token",
"6": "Подключите облачное хранилище Backblaze",
"7": "На данный момент подлюченных доменов нет",
"8": "Загружаем список доменов",
"9": "Найдено больше одного домена, для вашей безопастности, просим вам удалить не нужные домены",
"10": "Сохранить домен",
"11": "Создать сервер",
"what": "Что это значит?",
"13": "Сервер презагружен, ждем последнюю проверку",
"14": "Cервер запушен, сейчас он будет проверен и перезагружен",
"15": "Cервер создан, идет проверка ДНС адресов и запуск сервера",
"16": "До следующей проверки: ",
"17": "Проверка",
"18": "Как получить Hetzner API Token'",
"19": "1 Переходим по ссылке ",
"20": "\n2 Заходим в созданный нами проект. Если такового - нет, значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет."
}
} }

View File

@ -39,13 +39,13 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock/ios" :path: ".symlinks/plugins/wakelock/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
wakelock: bfc7955c418d0db797614075aabbc58a39ab5107 wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c

View File

@ -2,6 +2,6 @@
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "self:">
</FileRef> </FileRef>
</Workspace> </Workspace>

Binary file not shown.

View File

@ -3,13 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_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'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
class BlocAndProviderConfig extends StatelessWidget { class BlocAndProviderConfig extends StatelessWidget {
const BlocAndProviderConfig({Key key, this.child}) : super(key: key); const BlocAndProviderConfig({Key? key, this.child}) : super(key: key);
final Widget child; final Widget? child;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -30,7 +29,6 @@ class BlocAndProviderConfig extends StatelessWidget {
lazy: false, lazy: false,
create: (_) => AppConfigCubit()..load(), create: (_) => AppConfigCubit()..load(),
), ),
BlocProvider(create: (_) => ServicesCubit()),
BlocProvider(create: (_) => ProvidersCubit()), BlocProvider(create: (_) => ProvidersCubit()),
BlocProvider(create: (_) => UsersCubit()), BlocProvider(create: (_) => UsersCubit()),
], ],

View File

@ -2,14 +2,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/ui/components/error/error.dart'; import 'package:selfprivacy/ui/components/error/error.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'get_it_config.dart'; import './get_it_config.dart';
class SimpleBlocObserver extends BlocObserver { class SimpleBlocObserver extends BlocObserver {
SimpleBlocObserver(); SimpleBlocObserver();
@override @override
void onError(Cubit cubit, Object error, StackTrace stackTrace) { void onError(BlocBase cubit, Object error, StackTrace stackTrace) {
final navigator = getIt.get<NavigationService>().navigator; final navigator = getIt.get<NavigationService>().navigator!;
navigator.push( navigator.push(
materialRoute( materialRoute(

View File

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:selfprivacy/config/text_themes.dart'; import 'package:selfprivacy/config/text_themes.dart';
import 'brand_colors.dart'; import 'brand_colors.dart';
final ligtTheme = ThemeData( final ligtTheme = ThemeData(
primaryColor: BrandColors.primary, primaryColor: BrandColors.primary,
fontFamily: 'Inter',
brightness: Brightness.light, brightness: Brightness.light,
scaffoldBackgroundColor: BrandColors.scaffoldBackground, scaffoldBackgroundColor: BrandColors.scaffoldBackground,
inputDecorationTheme: InputDecorationTheme( inputDecorationTheme: InputDecorationTheme(
@ -33,21 +33,17 @@ final ligtTheme = ThemeData(
color: BrandColors.red1, color: BrandColors.red1,
), ),
), ),
errorStyle: GoogleFonts.inter( errorStyle: TextStyle(
textStyle: TextStyle( fontSize: 12,
fontSize: 12, color: BrandColors.red1,
color: BrandColors.red1,
),
), ),
), ),
textTheme: GoogleFonts.interTextTheme( textTheme: TextTheme(
TextTheme( headline1: headline1Style,
headline1: headline1Style, headline2: headline2Style,
headline2: headline2Style, caption: headline4Style,
caption: headline4Style, bodyText1: body1Style,
bodyText1: body1Style, subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
),
), ),
); );
@ -57,14 +53,12 @@ var darkTheme = ligtTheme.copyWith(
iconTheme: IconThemeData(color: BrandColors.gray3), iconTheme: IconThemeData(color: BrandColors.gray3),
cardColor: BrandColors.gray1, cardColor: BrandColors.gray1,
dialogBackgroundColor: Color(0xFF202120), dialogBackgroundColor: Color(0xFF202120),
textTheme: GoogleFonts.interTextTheme( textTheme: TextTheme(
TextTheme( headline1: headline1Style.copyWith(color: BrandColors.white),
headline1: headline1Style.copyWith(color: BrandColors.white), headline2: headline2Style.copyWith(color: BrandColors.white),
headline2: headline2Style.copyWith(color: BrandColors.white), caption: headline4Style.copyWith(color: BrandColors.white),
caption: headline4Style.copyWith(color: BrandColors.white), bodyText1: body1Style.copyWith(color: BrandColors.white),
bodyText1: body1Style.copyWith(color: BrandColors.white), subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
),
), ),
inputDecorationTheme: InputDecorationTheme( inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(color: BrandColors.white), labelStyle: TextStyle(color: BrandColors.white),

View File

@ -32,7 +32,8 @@ class HiveConfig {
await secureStorage.write(key: BNames.key, value: base64UrlEncode(key)); await secureStorage.write(key: BNames.key, value: base64UrlEncode(key));
} }
return base64Url.decode(await secureStorage.read(key: BNames.key)); String? string = await secureStorage.read(key: BNames.key);
return base64Url.decode(string!);
} }
} }

View File

@ -3,20 +3,20 @@ import 'package:flutter/material.dart';
class Localization extends StatelessWidget { class Localization extends StatelessWidget {
const Localization({ const Localization({
Key key, Key? key,
this.child, this.child,
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget? child;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return EasyLocalization( return EasyLocalization(
preloaderColor: Colors.black,
supportedLocales: [Locale('ru'), Locale('en')], supportedLocales: [Locale('ru'), Locale('en')],
path: 'assets/translations', path: 'assets/translations',
fallbackLocale: Locale('en'), fallbackLocale: Locale('ru'),
saveLocale: false,
useOnlyLangCode: true, useOnlyLangCode: true,
child: child, child: child!,
); );
} }
} }

0
lib/config/md_files.dart Normal file
View File

View File

@ -1,41 +1,38 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:selfprivacy/utils/named_font_weight.dart'; import 'package:selfprivacy/utils/named_font_weight.dart';
import 'brand_colors.dart'; import 'brand_colors.dart';
final defaultTextStyle = GoogleFonts.inter( final defaultTextStyle = TextStyle(
textStyle: TextStyle( fontSize: 15,
fontSize: 15, color: BrandColors.textColor1,
color: BrandColors.textColor1,
),
); );
final headline1Style = GoogleFonts.inter( final headline1Style = defaultTextStyle.copyWith(
fontSize: 40, fontSize: 40,
fontWeight: NamedFontWeight.extraBold, fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor, color: BrandColors.headlineColor,
); );
final headline2Style = GoogleFonts.inter( final headline2Style = defaultTextStyle.copyWith(
fontSize: 24, fontSize: 24,
fontWeight: NamedFontWeight.extraBold, fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor, color: BrandColors.headlineColor,
); );
final onboardingTitle = GoogleFonts.inter( final onboardingTitle = defaultTextStyle.copyWith(
fontSize: 30, fontSize: 30,
fontWeight: NamedFontWeight.extraBold, fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor, color: BrandColors.headlineColor,
); );
final headline3Style = GoogleFonts.inter( final headline3Style = defaultTextStyle.copyWith(
fontSize: 20, fontSize: 20,
fontWeight: NamedFontWeight.extraBold, fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor, color: BrandColors.headlineColor,
); );
final headline4Style = GoogleFonts.inter( final headline4Style = defaultTextStyle.copyWith(
fontSize: 18, fontSize: 18,
fontWeight: NamedFontWeight.medium, fontWeight: NamedFontWeight.medium,
color: BrandColors.headlineColor, color: BrandColors.headlineColor,
@ -59,16 +56,12 @@ final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45);
final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue); final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue);
final progressTextStyleLight = GoogleFonts.inter( final progressTextStyleLight = TextStyle(
textStyle: TextStyle( fontSize: 11,
fontSize: 11, color: BrandColors.textColor1,
color: BrandColors.textColor1,
),
); );
final progressTextStyleDark = GoogleFonts.inter( final progressTextStyleDark = TextStyle(
textStyle: TextStyle( fontSize: 11,
fontSize: 11, color: BrandColors.white,
color: BrandColors.white,
),
); );

View File

@ -18,9 +18,9 @@ abstract class ApiMap {
}; };
loggedClient = client; loggedClient = client;
} }
String rootAddress; String? rootAddress;
Dio loggedClient; late Dio loggedClient;
void close() { void close() {
loggedClient.close(); loggedClient.close();
@ -33,37 +33,46 @@ class ConsoleInterceptor extends InterceptorsWrapper {
} }
@override @override
Future onRequest(RequestOptions options) async { Future onRequest(
RequestOptions options,
RequestInterceptorHandler requestInterceptorHandler,
) async {
addMessage( addMessage(
Message( Message(
text: text:
'request-uri: ${options.uri}\nheaders: ${options.headers}\ndata: ${options.data}', 'request-uri: ${options.uri}\nheaders: ${options.headers}\ndata: ${options.data}',
), ),
); );
return super.onRequest(options); return super.onRequest(options, requestInterceptorHandler);
} }
@override @override
Future onResponse(Response response) async { Future onResponse(
Response response,
ResponseInterceptorHandler requestInterceptorHandler,
) async {
addMessage( addMessage(
Message( Message(
text: text:
'response-uri: ${response.request.uri}\ncode: ${response.statusCode}\ndata: ${response.toString()}\n', 'response-uri: ${response.realUri}\ncode: ${response.statusCode}\ndata: ${response.toString()}\n',
), ),
); );
return super.onResponse(response); return super.onResponse(
response,
requestInterceptorHandler,
);
} }
@override @override
Future onError(DioError err) async { Future onError(DioError err, ErrorInterceptorHandler handler) async {
var response = err.response; var response = err.response;
log(err.toString()); log(err.toString());
addMessage( addMessage(
Message.warn( Message.warn(
text: text:
'response-uri: ${response?.request?.uri}\ncode: ${response?.statusCode}\ndata: ${response?.toString()}\n', 'response-uri: ${response?.realUri}\ncode: ${response?.statusCode}\ndata: ${response?.toString()}\n',
), ),
); );
return super.onError(err); return super.onError(err, handler);
} }
} }

View File

@ -3,17 +3,17 @@ import 'package:dio/dio.dart';
import 'package:selfprivacy/logic/api_maps/api_map.dart'; import 'package:selfprivacy/logic/api_maps/api_map.dart';
class BackblazeApi extends ApiMap { class BackblazeApi extends ApiMap {
BackblazeApi([String token]) { BackblazeApi([String? token]) {
if (token != null) { if (token != null) {
loggedClient.options = BaseOptions( loggedClient.options = BaseOptions(
headers: {'Authorization': 'Basic $token'}, headers: {'Authorization': 'Basic $token'},
baseUrl: rootAddress, baseUrl: rootAddress!,
); );
} }
} }
@override @override
String rootAddress = String? rootAddress =
'https://api.backblazeb2.com/b2api/v2/b2_authorize_account'; 'https://api.backblazeb2.com/b2api/v2/b2_authorize_account';
Future<bool> isValid(String token) async { Future<bool> isValid(String token) async {
@ -24,7 +24,7 @@ class BackblazeApi extends ApiMap {
}, },
); );
Response response = await loggedClient.get(rootAddress, options: options); Response response = await loggedClient.get(rootAddress!, options: options);
if (response.statusCode == HttpStatus.ok) { if (response.statusCode == HttpStatus.ok) {
return true; return true;

View File

@ -5,7 +5,7 @@ import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/dns_records.dart'; import 'package:selfprivacy/logic/models/dns_records.dart';
class CloudflareApi extends ApiMap { class CloudflareApi extends ApiMap {
CloudflareApi([String token]) { CloudflareApi([String? token]) {
if (token != null) { if (token != null) {
loggedClient.options = loggedClient.options =
BaseOptions(headers: {'Authorization': 'Bearer $token'}); BaseOptions(headers: {'Authorization': 'Bearer $token'});
@ -13,7 +13,7 @@ class CloudflareApi extends ApiMap {
} }
@override @override
String rootAddress = 'https://api.cloudflare.com/client/v4'; String? rootAddress = 'https://api.cloudflare.com/client/v4';
Future<bool> isValid(String token) async { Future<bool> isValid(String token) async {
var url = '$rootAddress/user/tokens/verify'; var url = '$rootAddress/user/tokens/verify';
@ -35,7 +35,7 @@ class CloudflareApi extends ApiMap {
} }
} }
Future<String> getZoneId(String token, String domain) async { Future<String?> getZoneId(String? token, String domain) async {
var url = '$rootAddress/zones'; var url = '$rootAddress/zones';
var options = Options( var options = Options(
@ -59,8 +59,8 @@ class CloudflareApi extends ApiMap {
} }
Future<void> removeSimilarRecords({ Future<void> removeSimilarRecords({
String ip4, String? ip4,
CloudFlareDomain cloudFlareDomain, required CloudFlareDomain cloudFlareDomain,
}) async { }) async {
var domainName = cloudFlareDomain.domainName; var domainName = cloudFlareDomain.domainName;
var domainZoneId = cloudFlareDomain.zoneId; var domainZoneId = cloudFlareDomain.zoneId;
@ -82,8 +82,8 @@ class CloudflareApi extends ApiMap {
} }
Future<void> createMultipleDnsRecords({ Future<void> createMultipleDnsRecords({
String ip4, String? ip4,
CloudFlareDomain cloudFlareDomain, required CloudFlareDomain cloudFlareDomain,
}) async { }) async {
var domainName = cloudFlareDomain.domainName; var domainName = cloudFlareDomain.domainName;
var domainZoneId = cloudFlareDomain.zoneId; var domainZoneId = cloudFlareDomain.zoneId;
@ -120,7 +120,7 @@ class CloudflareApi extends ApiMap {
// ); // );
// } // }
List<DnsRecords> projectDnsRecords(String domainName, String ip4) { List<DnsRecords> projectDnsRecords(String? domainName, String? ip4) {
var domainA = DnsRecords(type: 'A', name: domainName, content: ip4); var domainA = DnsRecords(type: 'A', name: domainName, content: ip4);
var mx = DnsRecords(type: 'MX', name: '@', content: domainName); var mx = DnsRecords(type: 'MX', name: '@', content: domainName);
@ -161,7 +161,7 @@ class CloudflareApi extends ApiMap {
]; ];
} }
Future<List<String>> domainList() async { Future<List<String>?> domainList() async {
var url = '$rootAddress/zones?per_page=50'; var url = '$rootAddress/zones?per_page=50';
var response = await loggedClient.get( var response = await loggedClient.get(
url, url,
@ -169,7 +169,7 @@ class CloudflareApi extends ApiMap {
); );
return response.data['result'] return response.data['result']
.map<String>((el) => el['name'] as String) .map<String>((el) => el['name'] as String?)
.toList(); .toList();
} }
} }

View File

@ -2,24 +2,23 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:selfprivacy/logic/api_maps/api_map.dart'; import 'package:selfprivacy/logic/api_maps/api_map.dart';
import 'package:selfprivacy/logic/models/server_details.dart'; import 'package:selfprivacy/logic/models/server_details.dart';
import 'package:selfprivacy/logic/models/user.dart'; import 'package:selfprivacy/logic/models/user.dart';
import 'package:selfprivacy/utils/password_generator2.dart'; import 'package:selfprivacy/utils/password_generator2.dart';
class HetznerApi extends ApiMap { class HetznerApi extends ApiMap {
HetznerApi([String token]) { HetznerApi([String? token]) {
if (token != null) { if (token != null) {
loggedClient.options = BaseOptions( loggedClient.options = BaseOptions(
headers: {'Authorization': 'Bearer $token'}, headers: {'Authorization': 'Bearer $token'},
baseUrl: rootAddress, baseUrl: rootAddress!,
); );
} }
} }
@override @override
String rootAddress = 'https://api.hetzner.cloud/v1/servers'; String? rootAddress = 'https://api.hetzner.cloud/v1/servers';
Future<bool> isValid(String token) async { Future<bool> isValid(String token) async {
var options = Options( var options = Options(
@ -29,7 +28,7 @@ class HetznerApi extends ApiMap {
}, },
); );
Response response = await loggedClient.get(rootAddress, options: options); Response response = await loggedClient.get(rootAddress!, options: options);
if (response.statusCode == HttpStatus.ok) { if (response.statusCode == HttpStatus.ok) {
return true; return true;
@ -41,9 +40,9 @@ class HetznerApi extends ApiMap {
} }
Future<HetznerServerDetails> createServer({ Future<HetznerServerDetails> createServer({
@required String cloudFlareKey, required String? cloudFlareKey,
@required User rootUser, required User rootUser,
@required String domainName, required String? domainName,
}) async { }) async {
var dbPassword = getRandomString(40); var dbPassword = getRandomString(40);
@ -52,7 +51,7 @@ class HetznerApi extends ApiMap {
); );
Response response = await loggedClient.post( Response response = await loggedClient.post(
rootAddress, rootAddress!,
data: data, data: data,
); );
@ -64,17 +63,17 @@ class HetznerApi extends ApiMap {
} }
Future<void> deleteSelfprivacyServer({ Future<void> deleteSelfprivacyServer({
@required String cloudFlareKey, required String? cloudFlareKey,
}) async { }) async {
Response response = await loggedClient.get(rootAddress); Response response = await loggedClient.get(rootAddress!);
List list = response.data['servers']; List list = response.data['servers'];
var server = list.firstWhere((el) => el['name'] == 'selfprivacy-server'); var server = list.firstWhere((el) => el['name'] == 'selfprivacy-server');
return await loggedClient.delete('$rootAddress/${server['id']}'); await loggedClient.delete('$rootAddress/${server['id']}');
} }
Future<HetznerServerDetails> startServer({ Future<HetznerServerDetails> startServer({
HetznerServerDetails server, required HetznerServerDetails server,
}) async { }) async {
await loggedClient.post('/${server.id}/actions/poweron'); await loggedClient.post('/${server.id}/actions/poweron');
@ -84,7 +83,7 @@ class HetznerApi extends ApiMap {
} }
Future<HetznerServerDetails> restart({ Future<HetznerServerDetails> restart({
HetznerServerDetails server, required HetznerServerDetails server,
}) async { }) async {
await loggedClient.post('/${server.id}/actions/poweron'); await loggedClient.post('/${server.id}/actions/poweron');

View File

@ -5,7 +5,7 @@ import 'package:dio/dio.dart';
import 'api_map.dart'; import 'api_map.dart';
class ServerApi extends ApiMap { class ServerApi extends ApiMap {
ServerApi(String domainName) { ServerApi(String? domainName) {
loggedClient.options = BaseOptions( loggedClient.options = BaseOptions(
baseUrl: 'https://api.$domainName', baseUrl: 'https://api.$domainName',
); );

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:selfprivacy/logic/models/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/backblaze_credential.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart'; import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
@ -10,6 +9,7 @@ import 'package:selfprivacy/logic/models/server_details.dart';
import 'package:selfprivacy/logic/models/user.dart'; import 'package:selfprivacy/logic/models/user.dart';
import 'app_config_repository.dart'; import 'app_config_repository.dart';
export 'package:provider/provider.dart';
part 'app_config_state.dart'; part 'app_config_state.dart';
@ -64,23 +64,23 @@ class AppConfigCubit extends Cubit<AppConfigState> {
} }
void startServerIfDnsIsOkay({ void startServerIfDnsIsOkay({
AppConfigState state, AppConfigState? state,
bool isImmediate = false, bool isImmediate = false,
}) async { }) async {
state = state ?? this.state; state = state ?? this.state;
final work = () async { final work = () async {
emit(TimerState(dataState: state, isLoading: true)); emit(TimerState(dataState: state!, isLoading: true));
var ip4 = state.hetznerServer.ip4; var ip4 = state.hetznerServer!.ip4;
var domainName = state.cloudFlareDomain.domainName; var domainName = state.cloudFlareDomain!.domainName;
var isMatch = await repository.isDnsAddressesMatch(domainName, ip4); var isMatch = await repository.isDnsAddressesMatch(domainName, ip4);
if (isMatch) { if (isMatch) {
var server = await repository.startServer( var server = await repository.startServer(
state.hetznerKey, state.hetznerKey,
state.hetznerServer, state.hetznerServer!,
); );
repository.saveServerDetails(server); repository.saveServerDetails(server);
emit( emit(
@ -111,16 +111,16 @@ class AppConfigCubit extends Cubit<AppConfigState> {
} }
void resetServerIfServerIsOkay({ void resetServerIfServerIsOkay({
AppConfigState state, AppConfigState? state,
bool isImmediate = false, bool isImmediate = false,
}) async { }) async {
state = state ?? this.state; state = state ?? this.state;
var work = () async { var work = () async {
emit(TimerState(dataState: state, isLoading: true)); emit(TimerState(dataState: state!, isLoading: true));
var isServerWorking = await repository.isHttpServerWorking( var isServerWorking = await repository.isHttpServerWorking(
state.cloudFlareDomain.domainName, state.cloudFlareDomain!.domainName,
); );
if (isServerWorking) { if (isServerWorking) {
@ -133,8 +133,8 @@ class AppConfigCubit extends Cubit<AppConfigState> {
)); ));
timer = Timer(pauseDuration, () async { timer = Timer(pauseDuration, () async {
var hetznerServerDetails = await repository.restart( var hetznerServerDetails = await repository.restart(
state.hetznerKey, state!.hetznerKey,
state.hetznerServer, state.hetznerServer!,
); );
emit( emit(
state.copyWith( state.copyWith(
@ -165,19 +165,19 @@ class AppConfigCubit extends Cubit<AppConfigState> {
} }
} }
Timer timer; Timer? timer;
void finishCheckIfServerIsOkay({ void finishCheckIfServerIsOkay({
AppConfigState state, AppConfigState? state,
bool isImmediate = false, bool isImmediate = false,
}) async { }) async {
state = state ?? this.state; state = state ?? this.state;
var work = () async { var work = () async {
emit(TimerState(dataState: state, isLoading: true)); emit(TimerState(dataState: state!, isLoading: true));
var isServerWorking = await repository.isHttpServerWorking( var isServerWorking = await repository.isHttpServerWorking(
state.cloudFlareDomain.domainName, state.cloudFlareDomain!.domainName,
); );
if (isServerWorking) { if (isServerWorking) {
@ -238,12 +238,12 @@ class AppConfigCubit extends Cubit<AppConfigState> {
} }
void createServerAndSetDnsRecords() async { void createServerAndSetDnsRecords() async {
var _stateCopy = state; AppConfigState _stateCopy = state;
var onSuccess = (serverDetails) async { var onSuccess = (serverDetails) async {
await repository.createDnsRecords( await repository.createDnsRecords(
state.cloudFlareKey, state.cloudFlareKey,
serverDetails.ip4, serverDetails.ip4,
state.cloudFlareDomain, state.cloudFlareDomain!,
); );
emit(state.copyWith( emit(state.copyWith(
@ -259,8 +259,8 @@ class AppConfigCubit extends Cubit<AppConfigState> {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
await repository.createServer( await repository.createServer(
state.hetznerKey, state.hetznerKey,
state.rootUser, state.rootUser!,
state.cloudFlareDomain.domainName, state.cloudFlareDomain!.domainName,
state.cloudFlareKey, state.cloudFlareKey,
onCancel: onCancel, onCancel: onCancel,
onSuccess: onSuccess, onSuccess: onSuccess,
@ -277,54 +277,8 @@ class AppConfigCubit extends Cubit<AppConfigState> {
} }
void _closeTimer() { void _closeTimer() {
if (timer != null && timer.isActive) { if (timer != null && timer!.isActive) {
timer.cancel(); timer!.cancel();
} }
} }
} }
// void checkDnsAndStartServer() async {
// var ip4 = state.hetznerServer.ip4;
// var domainName = state.cloudFlareDomain.domainName;
// var isMatch = await repository.isDnsAddressesMatch(domainName, ip4);
// if (isMatch) {
// var server = await repository.startServer(
// state.hetznerKey,
// state.hetznerServer,
// );
// repository.saveServerDetails(server);
// emit(
// state.copyWith(
// hasFinalChecked: true,
// isServerStarted: true,
// isLoading: false,
// hetznerServer: server,
// ),
// );
// } else {
// emit(state.copyWith(lastDnsCheckTime: DateTime.now()));
// }
// }
// void serverReset() async {
// var callBack = () async {
// var isServerWorking = await repository.isHttpServerWorking(
// state.cloudFlareDomain.domainName,
// );
// if (!isServerWorking) {
// var last = DateTime.now();
// // emit(state.copyWith(lastServerStatusCheckTime: last));
// return;
// }
// var hetznerServerDetails = await repository.restart(
// state.hetznerKey,
// state.hetznerServer,
// );
// emit(state.copyWith(hetznerServer: hetznerServerDetails));
// };
// _tryOrAddError(state, callBack);
// }

View File

@ -60,7 +60,7 @@ class AppConfigRepository {
} }
Future<HetznerServerDetails> startServer( Future<HetznerServerDetails> startServer(
String hetznerKey, String? hetznerKey,
HetznerServerDetails hetznerServer, HetznerServerDetails hetznerServer,
) async { ) async {
var hetznerApi = HetznerApi(hetznerKey); var hetznerApi = HetznerApi(hetznerKey);
@ -75,7 +75,7 @@ class AppConfigRepository {
await box.put(BNames.hetznerServer, serverDetails); await box.put(BNames.hetznerServer, serverDetails);
} }
Future<bool> isDnsAddressesMatch(String domainName, String ip4) async { Future<bool> isDnsAddressesMatch(String? domainName, String? ip4) async {
print(domainName); print(domainName);
var addresses = <String>[ var addresses = <String>[
'$domainName', '$domainName',
@ -116,12 +116,12 @@ class AppConfigRepository {
} }
Future<void> createServer( Future<void> createServer(
String hetznerKey, String? hetznerKey,
User rootUser, User rootUser,
String domainName, String? domainName,
String cloudFlareKey, { String? cloudFlareKey, {
void Function() onCancel, void Function()? onCancel,
Future<void> Function(HetznerServerDetails serverDetails) onSuccess, required Future<void> Function(HetznerServerDetails serverDetails) onSuccess,
}) async { }) async {
var hetznerApi = HetznerApi(hetznerKey); var hetznerApi = HetznerApi(hetznerKey);
@ -135,7 +135,7 @@ class AppConfigRepository {
hetznerApi.close(); hetznerApi.close();
onSuccess(serverDetails); onSuccess(serverDetails);
} on DioError catch (e) { } on DioError catch (e) {
if (e.response.data['error']['code'] == 'uniqueness_error') { if (e.response!.data['error']['code'] == 'uniqueness_error') {
var nav = getIt.get<NavigationService>(); var nav = getIt.get<NavigationService>();
nav.showPopUpDialog( nav.showPopUpDialog(
BrandAlert( BrandAlert(
@ -165,7 +165,7 @@ class AppConfigRepository {
text: 'Отменить', text: 'Отменить',
onPressed: () { onPressed: () {
hetznerApi.close(); hetznerApi.close();
onCancel(); onCancel!();
}, },
), ),
], ],
@ -176,8 +176,8 @@ class AppConfigRepository {
} }
Future<void> createDnsRecords( Future<void> createDnsRecords(
String cloudFlareKey, String? cloudFlareKey,
String ip4, String? ip4,
CloudFlareDomain cloudFlareDomain, CloudFlareDomain cloudFlareDomain,
) async { ) async {
var cloudflareApi = CloudflareApi(cloudFlareKey); var cloudflareApi = CloudflareApi(cloudFlareKey);
@ -195,7 +195,7 @@ class AppConfigRepository {
cloudflareApi.close(); cloudflareApi.close();
} }
Future<bool> isHttpServerWorking(String domainName) async { Future<bool> isHttpServerWorking(String? domainName) async {
var api = ServerApi(domainName); var api = ServerApi(domainName);
var isHttpServerWorking = await api.isHttpServerWorking(); var isHttpServerWorking = await api.isHttpServerWorking();
api.close(); api.close();
@ -203,7 +203,7 @@ class AppConfigRepository {
} }
Future<HetznerServerDetails> restart( Future<HetznerServerDetails> restart(
String hetznerKey, String? hetznerKey,
HetznerServerDetails server, HetznerServerDetails server,
) async { ) async {
var hetznerApi = HetznerApi(hetznerKey); var hetznerApi = HetznerApi(hetznerKey);

View File

@ -2,21 +2,21 @@ part of 'app_config_cubit.dart';
class AppConfigState extends Equatable { class AppConfigState extends Equatable {
const AppConfigState({ const AppConfigState({
@required this.hetznerKey, required this.hetznerKey,
@required this.cloudFlareKey, required this.cloudFlareKey,
@required this.backblazeCredential, required this.backblazeCredential,
@required this.cloudFlareDomain, required this.cloudFlareDomain,
@required this.rootUser, required this.rootUser,
@required this.hetznerServer, required this.hetznerServer,
@required this.isServerStarted, required this.isServerStarted,
@required this.isServerReseted, required this.isServerReseted,
@required this.hasFinalChecked, required this.hasFinalChecked,
@required this.isLoading, required this.isLoading,
@required this.error, required this.error,
}); });
@override @override
List<Object> get props => [ List<Object?> get props => [
hetznerKey, hetznerKey,
cloudFlareKey, cloudFlareKey,
backblazeCredential, backblazeCredential,
@ -30,31 +30,31 @@ class AppConfigState extends Equatable {
error, error,
]; ];
final String hetznerKey; final String? hetznerKey;
final String cloudFlareKey; final String? cloudFlareKey;
final BackblazeCredential backblazeCredential; final BackblazeCredential? backblazeCredential;
final CloudFlareDomain cloudFlareDomain; final CloudFlareDomain? cloudFlareDomain;
final User rootUser; final User? rootUser;
final HetznerServerDetails hetznerServer; final HetznerServerDetails? hetznerServer;
final bool isServerStarted; final bool? isServerStarted;
final bool isServerReseted; final bool? isServerReseted;
final bool hasFinalChecked; final bool? hasFinalChecked;
final bool isLoading; final bool? isLoading;
final Exception error; final Exception? error;
AppConfigState copyWith({ AppConfigState copyWith({
String hetznerKey, String? hetznerKey,
String cloudFlareKey, String? cloudFlareKey,
BackblazeCredential backblazeCredential, BackblazeCredential? backblazeCredential,
CloudFlareDomain cloudFlareDomain, CloudFlareDomain? cloudFlareDomain,
User rootUser, User? rootUser,
HetznerServerDetails hetznerServer, HetznerServerDetails? hetznerServer,
bool isServerStarted, bool? isServerStarted,
bool isServerReseted, bool? isServerReseted,
bool hasFinalChecked, bool? hasFinalChecked,
bool isLoading, bool? isLoading,
Exception error, Exception? error,
}) => }) =>
AppConfigState( AppConfigState(
hetznerKey: hetznerKey ?? this.hetznerKey, hetznerKey: hetznerKey ?? this.hetznerKey,
@ -77,10 +77,10 @@ class AppConfigState extends Equatable {
bool get isUserFilled => rootUser != null; bool get isUserFilled => rootUser != null;
bool get isServerCreated => hetznerServer != null; bool get isServerCreated => hetznerServer != null;
bool get isFullyInitilized => _fulfilementList.every((el) => el); bool get isFullyInitilized => _fulfilementList.every((el) => el!);
int get progress => _fulfilementList.where((el) => el).length; int get progress => _fulfilementList.where((el) => el!).length;
List<bool> get _fulfilementList => [ List<bool?> get _fulfilementList => [
isHetznerFilled, isHetznerFilled,
isCloudFlareFilled, isCloudFlareFilled,
isBackblazeFilled, isBackblazeFilled,
@ -112,10 +112,10 @@ class InitialAppConfigState extends AppConfigState {
class TimerState extends AppConfigState { class TimerState extends AppConfigState {
TimerState({ TimerState({
@required this.dataState, required this.dataState,
this.timerStart, this.timerStart,
this.duration, this.duration,
@required bool isLoading, required bool isLoading,
}) : super( }) : super(
hetznerKey: dataState.hetznerKey, hetznerKey: dataState.hetznerKey,
cloudFlareKey: dataState.cloudFlareKey, cloudFlareKey: dataState.cloudFlareKey,
@ -131,11 +131,11 @@ class TimerState extends AppConfigState {
); );
final AppConfigState dataState; final AppConfigState dataState;
final DateTime timerStart; final DateTime? timerStart;
final Duration duration; final Duration? duration;
@override @override
List<Object> get props => [ List<Object?> get props => [
dataState, dataState,
timerStart, timerStart,
duration, duration,

View File

@ -1,6 +1,5 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/config/hive_config.dart';
export 'package:provider/provider.dart'; export 'package:provider/provider.dart';
@ -9,8 +8,8 @@ part 'app_settings_state.dart';
class AppSettingsCubit extends Cubit<AppSettingsState> { class AppSettingsCubit extends Cubit<AppSettingsState> {
AppSettingsCubit({ AppSettingsCubit({
@required bool isDarkModeOn, required bool isDarkModeOn,
@required bool isOnbordingShowing, required bool isOnbordingShowing,
}) : super( }) : super(
AppSettingsState( AppSettingsState(
isDarkModeOn: isDarkModeOn, isDarkModeOn: isDarkModeOn,
@ -21,15 +20,15 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
Box box = Hive.box(BNames.appSettings); Box box = Hive.box(BNames.appSettings);
void load() { void load() {
bool isDarkModeOn = box.get(BNames.isDarkModeOn); bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
bool isOnbordingShowing = box.get(BNames.isOnbordingShowing); bool? isOnbordingShowing = box.get(BNames.isOnbordingShowing);
emit(state.copyWith( emit(state.copyWith(
isDarkModeOn: isDarkModeOn, isDarkModeOn: isDarkModeOn,
isOnbordingShowing: isOnbordingShowing, isOnbordingShowing: isOnbordingShowing,
)); ));
} }
void updateDarkMode({@required bool isDarkModeOn}) { void updateDarkMode({required bool isDarkModeOn}) {
box.put(BNames.isDarkModeOn, isDarkModeOn); box.put(BNames.isDarkModeOn, isDarkModeOn);
emit(state.copyWith(isDarkModeOn: isDarkModeOn)); emit(state.copyWith(isDarkModeOn: isDarkModeOn));
} }

View File

@ -2,8 +2,8 @@ part of 'app_settings_cubit.dart';
class AppSettingsState extends Equatable { class AppSettingsState extends Equatable {
const AppSettingsState({ const AppSettingsState({
@required this.isDarkModeOn, required this.isDarkModeOn,
@required this.isOnbordingShowing, required this.isOnbordingShowing,
}); });
final bool isDarkModeOn; final bool isDarkModeOn;

View File

@ -42,13 +42,14 @@ class BackblazeFormCubit extends FormCubit {
final AppConfigCubit initializingCubit; final AppConfigCubit initializingCubit;
FieldCubit<String> keyId; // ignore: close_sinks
late final FieldCubit<String> keyId;
FieldCubit<String> applicationKey; // ignore: close_sinks
late final FieldCubit<String> applicationKey;
@override @override
FutureOr<bool> asyncValidation() async { FutureOr<bool> asyncValidation() async {
bool isKeyValid; late bool isKeyValid;
try { try {
String encodedApiKey = encodedBackblazeKey( String encodedApiKey = encodedBackblazeKey(
keyId.state.value, keyId.state.value,

View File

@ -30,11 +30,11 @@ class CloudFlareFormCubit extends FormCubit {
final AppConfigCubit initializingCubit; final AppConfigCubit initializingCubit;
FieldCubit<String> apiKey; late final FieldCubit<String> apiKey;
@override @override
FutureOr<bool> asyncValidation() async { FutureOr<bool> asyncValidation() async {
bool isKeyValid; late bool isKeyValid;
try { try {
isKeyValid = await apiClient.isValid(apiKey.state.value); isKeyValid = await apiClient.isValid(apiKey.state.value);

View File

@ -5,7 +5,7 @@ import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
class DomainSetupCubit extends Cubit<DomainSetupState> { class DomainSetupCubit extends Cubit<DomainSetupState> {
DomainSetupCubit(this.initializingCubit) : super(Initial()) { DomainSetupCubit(this.initializingCubit) : super(Initial()) {
var token = (initializingCubit.state.cloudFlareKey); var token = initializingCubit.state.cloudFlareKey;
assert(token != null, 'no cloudflare token'); assert(token != null, 'no cloudflare token');
@ -13,11 +13,11 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
} }
AppConfigCubit initializingCubit; AppConfigCubit initializingCubit;
CloudflareApi api; late CloudflareApi api;
Future<void> load() async { Future<void> load() async {
emit(Loading(LoadingTypes.loadingDomain)); emit(Loading(LoadingTypes.loadingDomain));
var list = await api.domainList(); var list = await (api.domainList() as Future<List<String>>);
if (list.isEmpty) { if (list.isEmpty) {
emit(Empty()); emit(Empty());
} else if (list.length == 1) { } else if (list.length == 1) {

View File

@ -30,11 +30,12 @@ class HetznerFormCubit extends FormCubit {
final AppConfigCubit initializingCubit; final AppConfigCubit initializingCubit;
FieldCubit<String> apiKey; // ignore: close_sinks
late final FieldCubit<String> apiKey;
@override @override
FutureOr<bool> asyncValidation() async { FutureOr<bool> asyncValidation() async {
bool isKeyValid; late bool isKeyValid;
try { try {
isKeyValid = await apiClient.isValid(apiKey.state.value); isKeyValid = await apiClient.isValid(apiKey.state.value);
} catch (e) { } catch (e) {

View File

@ -46,9 +46,12 @@ class RootUserFormCubit extends FormCubit {
final AppConfigCubit initializingCubit; final AppConfigCubit initializingCubit;
FieldCubit<String> userName; // ignore: close_sinks
FieldCubit<String> password; late final FieldCubit<String> userName;
FieldCubit<bool> isVisible; // ignore: close_sinks
late final FieldCubit<String> password;
// ignore: close_sinks
late final FieldCubit<bool> isVisible;
@override @override
Future<void> close() async { Future<void> close() async {

View File

@ -7,8 +7,8 @@ import 'package:selfprivacy/utils/password_generator.dart';
class UserFormCubit extends FormCubit { class UserFormCubit extends FormCubit {
UserFormCubit({ UserFormCubit({
this.usersCubit, required this.usersCubit,
User user, User? user,
}) { }) {
var isEdit = user != null; var isEdit = user != null;
@ -16,7 +16,7 @@ class UserFormCubit extends FormCubit {
var passwordRegExp = RegExp(r"[\n\r\s]+"); var passwordRegExp = RegExp(r"[\n\r\s]+");
login = FieldCubit( login = FieldCubit(
initalValue: isEdit ? user.login : '', initalValue: isEdit ? user!.login : '',
validations: [ validations: [
RequiredStringValidation('required'), RequiredStringValidation('required'),
ValidationModel<String>( ValidationModel<String>(
@ -25,7 +25,7 @@ class UserFormCubit extends FormCubit {
); );
password = FieldCubit( password = FieldCubit(
initalValue: isEdit ? user.password : genPass(), initalValue: isEdit ? user!.password : genPass(),
validations: [ validations: [
RequiredStringValidation('required'), RequiredStringValidation('required'),
ValidationModel<String>( ValidationModel<String>(
@ -42,15 +42,16 @@ class UserFormCubit extends FormCubit {
login: login.state.value, login: login.state.value,
password: password.state.value, password: password.state.value,
); );
usersCubit.add(user); usersCubit.addUser(user);
} }
FieldCubit<String> login; // ignore: close_sinks
FieldCubit<String> password; late FieldCubit<String> login;
late FieldCubit<String> password;
void genNewPassword() { void genNewPassword() {
password.externalSetValue(genPass()); password.externalSetValue(genPass());
} }
UsersCubit usersCubit; late UsersCubit usersCubit;
} }

View File

@ -5,7 +5,7 @@ class LegnthStringValidationWithLenghShowing extends ValidationModel<String> {
: super((n) => n.length != length, errorText); : super((n) => n.length != length, errorText);
@override @override
String check(String val) { String? check(String val) {
var length = val.length; var length = val.length;
var errorMassage = this.errorMassage.replaceAll("[]", length.toString()); var errorMassage = this.errorMassage.replaceAll("[]", length.toString());
return test(val) ? errorMassage : null; return test(val) ? errorMassage : null;

View File

@ -1,28 +1,28 @@
import 'package:bloc/bloc.dart'; // import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; // import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart'; // import 'package:meta/meta.dart';
import 'package:selfprivacy/logic/models/service.dart'; // import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/logic/models/state_types.dart'; // import 'package:selfprivacy/logic/models/state_types.dart';
export 'package:provider/provider.dart'; // export 'package:provider/provider.dart';
export 'package:selfprivacy/logic/models/state_types.dart'; // export 'package:selfprivacy/logic/models/state_types.dart';
part 'services_state.dart'; // part 'services_state.dart';
class ServicesCubit extends Cubit<ServicesState> { // class ServicesCubit extends Cubit<ServicesState> {
ServicesCubit() : super(ServicesState(all)); // ServicesCubit() : super(ServicesState(all));
void connect(Service service) { // void connect(Service service) {
var newState = state.updateElement(service, StateType.stable); // var newState = state.updateElement(service, StateType.stable);
emit(newState); // emit(newState);
} // }
} // }
final all = ServiceTypes.values // final all = ServiceTypes.values
.map( // .map(
(type) => Service( // (type) => Service(
state: StateType.uninitialized, // state: StateType.uninitialized,
type: type, // type: type,
), // ),
) // )
.toList(); // .toList();

View File

@ -1,26 +1,26 @@
part of 'services_cubit.dart'; // part of 'services_cubit.dart';
@immutable // @immutable
class ServicesState extends Equatable{ // class ServicesState extends Equatable{
ServicesState(this.all); // ServicesState(this.all);
final List<Service> all; // final List<Service> all;
ServicesState updateElement(Service service, StateType newState) { // ServicesState updateElement(Service service, StateType newState) {
var newList = [...all]; // var newList = [...all];
var index = newList.indexOf(service); // var index = newList.indexOf(service);
newList[index] = service.updateState(newState); // newList[index] = service.updateState(newState);
return ServicesState(newList); // return ServicesState(newList);
} // }
List<Service> get connected => all // List<Service> get connected => all
.where((service) => service.state != StateType.uninitialized) // .where((service) => service.state != StateType.uninitialized)
.toList(); // .toList();
List<Service> get uninitialized => all // List<Service> get uninitialized => all
.where((service) => service.state == StateType.uninitialized) // .where((service) => service.state == StateType.uninitialized)
.toList(); // .toList();
@override // @override
List<Object> get props => all; // List<Object> get props => all;
} // }

View File

@ -8,14 +8,14 @@ part 'users_state.dart';
class UsersCubit extends Cubit<UsersState> { class UsersCubit extends Cubit<UsersState> {
UsersCubit() : super(UsersState([])); UsersCubit() : super(UsersState([]));
void add(User user) { void addUser(User user) {
var users = [...state.users]; var users = [...state.users];
users.add(user); users.add(user);
emit(UsersState(users)); emit(UsersState(users));
} }
void remove(User user) { void remove(User? user) {
var users = [...state.users]; var users = [...state.users];
users.remove(user); users.remove(user);

View File

@ -3,10 +3,10 @@ import 'package:flutter/widgets.dart';
class NavigationService { class NavigationService {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
NavigatorState get navigator => navigatorKey.currentState; NavigatorState? get navigator => navigatorKey.currentState;
void showPopUpDialog(AlertDialog dialog) { void showPopUpDialog(AlertDialog dialog) {
final context = navigatorKey.currentState.overlay.context; final context = navigatorKey.currentState!.overlay!.context;
showDialog( showDialog(
context: context, context: context,

View File

@ -9,10 +9,10 @@ class BackblazeCredential {
BackblazeCredential({this.keyId, this.applicationKey}); BackblazeCredential({this.keyId, this.applicationKey});
@HiveField(0) @HiveField(0)
final String keyId; final String? keyId;
@HiveField(1) @HiveField(1)
final String applicationKey; final String? applicationKey;
get encodedApiKey => encodedBackblazeKey(keyId, applicationKey); get encodedApiKey => encodedBackblazeKey(keyId, applicationKey);
@ -22,7 +22,7 @@ class BackblazeCredential {
} }
} }
String encodedBackblazeKey(String keyId, String applicationKey) { String encodedBackblazeKey(String? keyId, String? applicationKey) {
String _apiKey = '$keyId:$applicationKey'; String _apiKey = '$keyId:$applicationKey';
String encodedApiKey = base64.encode(utf8.encode(_apiKey)); String encodedApiKey = base64.encode(utf8.encode(_apiKey));
return encodedApiKey; return encodedApiKey;

View File

@ -17,8 +17,8 @@ class BackblazeCredentialAdapter extends TypeAdapter<BackblazeCredential> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; };
return BackblazeCredential( return BackblazeCredential(
keyId: fields[0] as String, keyId: fields[0] as String?,
applicationKey: fields[1] as String, applicationKey: fields[1] as String?,
); );
} }

View File

@ -7,10 +7,10 @@ class CloudFlareDomain {
CloudFlareDomain({this.domainName, this.zoneId}); CloudFlareDomain({this.domainName, this.zoneId});
@HiveField(0) @HiveField(0)
final String domainName; final String? domainName;
@HiveField(1) @HiveField(1)
final String zoneId; final String? zoneId;
@override @override
String toString() { String toString() {

View File

@ -17,8 +17,8 @@ class CloudFlareDomainAdapter extends TypeAdapter<CloudFlareDomain> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; };
return CloudFlareDomain( return CloudFlareDomain(
domainName: fields[0] as String, domainName: fields[0] as String?,
zoneId: fields[1] as String, zoneId: fields[1] as String?,
); );
} }

View File

@ -1,4 +1,3 @@
import 'package:flutter/foundation.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'dns_records.g.dart'; part 'dns_records.g.dart';
@ -6,17 +5,17 @@ part 'dns_records.g.dart';
@JsonSerializable(createToJson: true, createFactory: false) @JsonSerializable(createToJson: true, createFactory: false)
class DnsRecords { class DnsRecords {
DnsRecords({ DnsRecords({
@required this.type, required this.type,
@required this.name, required this.name,
@required this.content, required this.content,
this.ttl = 3600, this.ttl = 3600,
this.priority = 10, this.priority = 10,
this.proxied = false, this.proxied = false,
}); });
final String type; final String type;
final String name; final String? name;
final String content; final String? content;
final int ttl; final int ttl;
final int priority; final int priority;
final bool proxied; final bool proxied;

View File

@ -5,12 +5,12 @@ final formater = new DateFormat('hh:mm');
class Message { class Message {
Message({this.text, this.type = MessageType.normal}) : time = DateTime.now(); Message({this.text, this.type = MessageType.normal}) : time = DateTime.now();
final String text; final String? text;
final DateTime time; final DateTime time;
final MessageType type; final MessageType type;
String get timeString => formater.format(time); String get timeString => formater.format(time);
static Message warn({String text}) => Message( static Message warn({String? text}) => Message(
text: text, text: text,
type: MessageType.warning, type: MessageType.warning,
); );

View File

@ -10,7 +10,7 @@ enum ProviderType {
} }
class ProviderModel extends Equatable { class ProviderModel extends Equatable {
const ProviderModel({this.state, this.type}); const ProviderModel({required this.state, required this.type});
final StateType state; final StateType state;
final ProviderType type; final ProviderType type;
@ -21,7 +21,7 @@ class ProviderModel extends Equatable {
); );
@override @override
List<Object> get props => [state, type]; List<Object?> get props => [state, type];
IconData get icon { IconData get icon {
switch (type) { switch (type) {
@ -31,10 +31,8 @@ class ProviderModel extends Equatable {
case ProviderType.domain: case ProviderType.domain:
return BrandIcons.globe; return BrandIcons.globe;
break;
case ProviderType.backup: case ProviderType.backup:
return BrandIcons.save; return BrandIcons.save;
} }
return null;
} }
} }

View File

@ -1,4 +1,3 @@
import 'package:flutter/widgets.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
part 'server_details.g.dart'; part 'server_details.g.dart';
@ -6,25 +5,25 @@ part 'server_details.g.dart';
@HiveType(typeId: 2) @HiveType(typeId: 2)
class HetznerServerDetails { class HetznerServerDetails {
HetznerServerDetails({ HetznerServerDetails({
@required this.ip4, required this.ip4,
@required this.id, required this.id,
@required this.createTime, required this.createTime,
this.startTime, this.startTime,
}); });
@HiveField(0) @HiveField(0)
final String ip4; final String? ip4;
@HiveField(1) @HiveField(1)
final int id; final int? id;
@HiveField(3) @HiveField(3)
final DateTime createTime; final DateTime? createTime;
@HiveField(2) @HiveField(2)
final DateTime startTime; final DateTime? startTime;
HetznerServerDetails copyWith({DateTime startTime}) { HetznerServerDetails copyWith({DateTime? startTime}) {
return HetznerServerDetails( return HetznerServerDetails(
startTime: startTime ?? this.startTime, startTime: startTime ?? this.startTime,
createTime: createTime, createTime: createTime,

View File

@ -17,10 +17,10 @@ class HetznerServerDetailsAdapter extends TypeAdapter<HetznerServerDetails> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; };
return HetznerServerDetails( return HetznerServerDetails(
ip4: fields[0] as String, ip4: fields[0] as String?,
id: fields[1] as int, id: fields[1] as int?,
createTime: fields[3] as DateTime, createTime: fields[3] as DateTime?,
startTime: fields[2] as DateTime, startTime: fields[2] as DateTime?,
); );
} }

View File

@ -1,12 +1,10 @@
import 'package:flutter/foundation.dart';
class ServerStatus { class ServerStatus {
final StatusTypes http; final StatusTypes http;
final StatusTypes imap; final StatusTypes imap;
final StatusTypes smtp; final StatusTypes smtp;
ServerStatus({ ServerStatus({
@required this.http, required this.http,
this.imap = StatusTypes.nodata, this.imap = StatusTypes.nodata,
this.smtp = StatusTypes.nodata, this.smtp = StatusTypes.nodata,
}); });
@ -20,7 +18,7 @@ class ServerStatus {
} }
} }
StatusTypes statusTypeFromNumber(int number) { StatusTypes statusTypeFromNumber(int? number) {
if (number == 0) { if (number == 0) {
return StatusTypes.ok; return StatusTypes.ok;
} else if (number == 1) { } else if (number == 1) {

View File

@ -1,25 +1,25 @@
import 'package:equatable/equatable.dart'; // import 'package:equatable/equatable.dart';
import 'package:selfprivacy/logic/models/state_types.dart'; // import 'package:selfprivacy/logic/models/state_types.dart';
enum ServiceTypes { // enum ServiceTypes {
messanger, // messanger,
mail, // mail,
passwordManager, // passwordManager,
github, // github,
cloud, // cloud,
} // }
class Service extends Equatable { // class Service extends Equatable {
const Service({this.state, this.type}); // const Service({required this.state, required this.type});
final StateType state; // final StateType state;
final ServiceTypes type; // final ServiceTypes type;
Service updateState(StateType newState) => Service( // Service updateState(StateType newState) => Service(
state: newState, // state: newState,
type: type, // type: type,
); // );
@override // @override
List<Object> get props => [state, type]; // List<Object?> get props => [state, type];
} // }

View File

@ -1,7 +1,6 @@
import 'dart:ui'; import 'dart:ui';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:selfprivacy/utils/color_utils.dart'; import 'package:selfprivacy/utils/color_utils.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:selfprivacy/utils/crypto.dart'; import 'package:selfprivacy/utils/crypto.dart';
@ -11,18 +10,18 @@ part 'user.g.dart';
@HiveType(typeId: 1) @HiveType(typeId: 1)
class User extends Equatable { class User extends Equatable {
User({ User({
@required this.login, required this.login,
@required this.password, required this.password,
}); });
@HiveField(0) @HiveField(0)
final String login; final String login;
@HiveField(1) @HiveField(1)
final String password; final String password;
@override @override
List<Object> get props => [login, password]; List<Object?> get props => [login, password];
Color get color => stringToColor(login); Color get color => stringToColor(login);

View File

@ -22,6 +22,7 @@ void main() async {
Wakelock.enable(); Wakelock.enable();
getItSetup(); getItSetup();
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
runApp( runApp(
Localization( Localization(
@ -35,7 +36,7 @@ void main() async {
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var appSettings = context.watch<AppSettingsCubit>().state; AppSettingsState appSettings = context.watch<AppSettingsCubit>().state;
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light, // Manually changnig appbar color value: SystemUiOverlayStyle.light, // Manually changnig appbar color
@ -50,12 +51,12 @@ class MyApp extends StatelessWidget {
home: appSettings.isOnbordingShowing home: appSettings.isOnbordingShowing
? OnboardingPage(nextPage: InitializingPage()) ? OnboardingPage(nextPage: InitializingPage())
: RootPage(), : RootPage(),
builder: (BuildContext context, Widget widget) { builder: (BuildContext context, Widget? widget) {
Widget error = Text('...rendering error...'); Widget error = Text('...rendering error...');
if (widget is Scaffold || widget is Navigator) if (widget is Scaffold || widget is Navigator)
error = Scaffold(body: Center(child: error)); error = Scaffold(body: Center(child: error));
ErrorWidget.builder = (FlutterErrorDetails errorDetails) => error; ErrorWidget.builder = (FlutterErrorDetails errorDetails) => error;
return widget; return widget!;
}, },
), ),
); );

View File

@ -3,14 +3,14 @@ import 'package:selfprivacy/config/brand_colors.dart';
class ActionButton extends StatelessWidget { class ActionButton extends StatelessWidget {
const ActionButton({ const ActionButton({
Key key, Key? key,
this.text, this.text,
this.onPressed, this.onPressed,
this.isRed = false, this.isRed = false,
}) : super(key: key); }) : super(key: key);
final VoidCallback onPressed; final VoidCallback? onPressed;
final String text; final String? text;
final bool isRed; final bool isRed;
@override @override
@ -19,12 +19,12 @@ class ActionButton extends StatelessWidget {
return TextButton( return TextButton(
child: Text( child: Text(
text, text!,
style: isRed ? TextStyle(color: BrandColors.red1) : null, style: isRed ? TextStyle(color: BrandColors.red1) : null,
), ),
onPressed: () { onPressed: () {
navigator.pop(); navigator.pop();
if (onPressed != null) onPressed(); if (onPressed != null) onPressed!();
}, },
); );
} }

View File

@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
class BrandAlert extends AlertDialog { class BrandAlert extends AlertDialog {
BrandAlert({ BrandAlert({
Key key, Key? key,
String title, String? title,
String contentText, String? contentText,
List<Widget> acitons, List<Widget>? acitons,
}) : super( }) : super(
key: key, key: key,
title: title != null ? Text(title) : null, title: title != null ? Text(title) : null,
content: title != null ? Text(contentText) : null, content: title != null ? Text(contentText!) : null,
actions: acitons, actions: acitons,
); );
} }

View File

@ -8,10 +8,10 @@ enum BrandButtonTypes { rised, text, iconText }
class BrandButton { class BrandButton {
static rised({ static rised({
Key key, Key? key,
@required VoidCallback onPressed, required VoidCallback? onPressed,
String title, String? title,
Widget child, Widget? child,
}) { }) {
assert(title == null || child == null, 'required title or child'); assert(title == null || child == null, 'required title or child');
assert(title != null || child != null, 'required title or child'); assert(title != null || child != null, 'required title or child');
@ -24,9 +24,9 @@ class BrandButton {
} }
static text({ static text({
Key key, Key? key,
@required VoidCallback onPressed, required VoidCallback onPressed,
@required String title, required String title,
}) => }) =>
_TextButton( _TextButton(
key: key, key: key,
@ -35,10 +35,10 @@ class BrandButton {
); );
static iconText({ static iconText({
Key key, Key? key,
@required VoidCallback onPressed, required VoidCallback onPressed,
@required String title, required String title,
@required Icon icon, required Icon icon,
}) => }) =>
_IconTextButton( _IconTextButton(
key: key, key: key,
@ -50,15 +50,15 @@ class BrandButton {
class _RisedButton extends StatelessWidget { class _RisedButton extends StatelessWidget {
const _RisedButton({ const _RisedButton({
Key key, Key? key,
this.onPressed, this.onPressed,
this.title, this.title,
this.child, this.child,
}) : super(key: key); }) : super(key: key);
final VoidCallback onPressed; final VoidCallback? onPressed;
final String title; final String? title;
final Widget child; final Widget? child;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -88,13 +88,13 @@ class _RisedButton extends StatelessWidget {
class _TextButton extends StatelessWidget { class _TextButton extends StatelessWidget {
const _TextButton({ const _TextButton({
Key key, Key? key,
this.onPressed, this.onPressed,
this.title, this.title,
}) : super(key: key); }) : super(key: key);
final VoidCallback onPressed; final VoidCallback? onPressed;
final String title; final String? title;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -106,7 +106,7 @@ class _TextButton extends StatelessWidget {
alignment: Alignment.center, alignment: Alignment.center,
padding: EdgeInsets.all(12), padding: EdgeInsets.all(12),
child: Text( child: Text(
title, title!,
style: TextStyle( style: TextStyle(
color: BrandColors.blue, color: BrandColors.blue,
fontSize: 16, fontSize: 16,
@ -120,12 +120,12 @@ class _TextButton extends StatelessWidget {
} }
class _IconTextButton extends StatelessWidget { class _IconTextButton extends StatelessWidget {
const _IconTextButton({Key key, this.onPressed, this.title, this.icon}) const _IconTextButton({Key? key, this.onPressed, this.title, this.icon})
: super(key: key); : super(key: key);
final VoidCallback onPressed; final VoidCallback? onPressed;
final String title; final String? title;
final Icon icon; final Icon? icon;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -4,11 +4,11 @@ import 'package:selfprivacy/utils/extensions/elevation_extension.dart';
class BrandCard extends StatelessWidget { class BrandCard extends StatelessWidget {
const BrandCard({ const BrandCard({
Key key, Key? key,
this.child, this.child,
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget? child;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_colors.dart';
class BrandDivider extends StatelessWidget { class BrandDivider extends StatelessWidget {
const BrandDivider({Key key}) : super(key: key); const BrandDivider({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -4,8 +4,8 @@ import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class BrandHeader extends StatelessWidget { class BrandHeader extends StatelessWidget {
const BrandHeader({ const BrandHeader({
Key key, Key? key,
@required this.title, required this.title,
this.hasBackButton = false, this.hasBackButton = false,
}) : super(key: key); }) : super(key: key);

View File

@ -1,5 +1,5 @@
/// Flutter icons BrandIcons /// Flutter icons BrandIcons
/// Copyright (C) 2020 by original authors @ fluttericon.com, fontello.com /// Copyright (C) 2021 by original authors @ fluttericon.com, fontello.com
/// This font was generated by FlutterIcon.com, which is derived from Fontello. /// This font was generated by FlutterIcon.com, which is derived from Fontello.
/// ///
/// To use this font, place it in your fonts/ directory and include the /// To use this font, place it in your fonts/ directory and include the
@ -19,7 +19,7 @@ class BrandIcons {
BrandIcons._(); BrandIcons._();
static const _kFontFam = 'BrandIcons'; static const _kFontFam = 'BrandIcons';
static const _kFontPkg = null; static const String? _kFontPkg = null;
static const IconData connection = static const IconData connection =
IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
@ -39,14 +39,22 @@ class BrandIcons {
IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData check = static const IconData check =
IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData webcam =
IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData refresh = static const IconData refresh =
IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData git =
IconData(0xe80b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData social =
IconData(0xe80c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData settings = static const IconData settings =
IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData share = static const IconData share =
IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData triangle = static const IconData triangle =
IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData engineer =
IconData(0xe810, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData server = static const IconData server =
IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData box = static const IconData box =
@ -59,8 +67,16 @@ class BrandIcons {
IconData(0xe815, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe815, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData upload = static const IconData upload =
IconData(0xe816, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe816, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData github =
IconData(0xe817, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData arrow_left = static const IconData arrow_left =
IconData(0xe818, fontFamily: _kFontFam, fontPackage: _kFontPkg); IconData(0xe818, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData shape =
IconData(0xe819, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData keyhole =
IconData(0xe81a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData terminal =
IconData(0xe81b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData fire =
IconData(0xe81c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData start =
IconData(0xe81d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
} }

View File

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/text_themes.dart';
import 'package:url_launcher/url_launcher.dart';
class BrandMarkdown extends StatefulWidget {
const BrandMarkdown({
Key? key,
required this.fileName,
}) : super(key: key);
final String fileName;
@override
_BrandMarkdownState createState() => _BrandMarkdownState();
}
class _BrandMarkdownState extends State<BrandMarkdown> {
String _mdContent = '';
@override
void initState() {
super.initState();
_loadMdFile();
}
void _loadMdFile() async {
String mdFromFile = await rootBundle
.loadString('assets/markdown/${widget.fileName}-${'locale'.tr()}.md');
setState(() {
_mdContent = mdFromFile;
});
}
@override
Widget build(BuildContext context) {
var isDark = Theme.of(context).brightness == Brightness.dark;
var markdown = MarkdownStyleSheet(
p: defaultTextStyle,
h1: headline1Style.copyWith(
color: isDark ? BrandColors.white : null,
),
h2: headline2Style.copyWith(
color: isDark ? BrandColors.white : null,
),
h3: headline3Style.copyWith(
color: isDark ? BrandColors.white : null,
),
h4: headline4Style.copyWith(
color: isDark ? BrandColors.white : null,
),
);
return Markdown(
styleSheet: markdown,
onTapLink: (String text, String? href, String title) {
if (href != null) {
canLaunch(href).then((canLaunchURL) {
if (canLaunchURL) {
launch(href);
}
});
}
},
data: _mdContent,
);
}
}

View File

@ -4,11 +4,11 @@ var navigatorKey = GlobalKey<NavigatorState>();
class BrandModalSheet extends StatelessWidget { class BrandModalSheet extends StatelessWidget {
const BrandModalSheet({ const BrandModalSheet({
Key key, Key? key,
this.child, this.child,
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget? child;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DraggableScrollableSheet( return DraggableScrollableSheet(
@ -43,18 +43,17 @@ class BrandModalSheet extends StatelessWidget {
), ),
), ),
Container( Container(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - 32 - 4, minHeight: MediaQuery.of(context).size.height - 132,
maxHeight: MediaQuery.of(context).size.height, maxHeight: MediaQuery.of(context).size.height - 132,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
BorderRadius.vertical(top: Radius.circular(20)), BorderRadius.vertical(top: Radius.circular(20)),
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
), ),
width: double.infinity, width: double.infinity,
child: child child: child),
),
], ],
), ),
), ),

View File

@ -5,21 +5,19 @@ import 'package:url_launcher/url_launcher.dart';
class BrandSpanButton extends TextSpan { class BrandSpanButton extends TextSpan {
BrandSpanButton({ BrandSpanButton({
@required String text, required String text,
@required VoidCallback onTap, required VoidCallback onTap,
TextStyle style, TextStyle? style,
}) : assert(text != null), }) : super(
assert(onTap != null),
super(
recognizer: TapGestureRecognizer()..onTap = onTap, recognizer: TapGestureRecognizer()..onTap = onTap,
text: text, text: text,
style: (style ?? TextStyle()).copyWith(color: BrandColors.blue), style: (style ?? TextStyle()).copyWith(color: BrandColors.blue),
); );
static link({ static link({
@required String text, required String text,
String urlString, String? urlString,
TextStyle style, TextStyle? style,
}) => }) =>
BrandSpanButton( BrandSpanButton(
text: text, text: text,

View File

@ -1,37 +1,38 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:easy_localization/easy_localization.dart';
final _kBottomTabBarHeight = 51; final _kBottomTabBarHeight = 51;
class BrandTabBar extends StatefulWidget { class BrandTabBar extends StatefulWidget {
BrandTabBar({Key key, this.controller}) : super(key: key); BrandTabBar({Key? key, this.controller}) : super(key: key);
final TabController controller; final TabController? controller;
@override @override
_BrandTabBarState createState() => _BrandTabBarState(); _BrandTabBarState createState() => _BrandTabBarState();
} }
class _BrandTabBarState extends State<BrandTabBar> { class _BrandTabBarState extends State<BrandTabBar> {
int currentIndex; int? currentIndex;
@override @override
void initState() { void initState() {
currentIndex = widget.controller.index; currentIndex = widget.controller!.index;
widget.controller.addListener(_listener); widget.controller!.addListener(_listener);
super.initState(); super.initState();
} }
_listener() { _listener() {
if (currentIndex != widget.controller.index) { if (currentIndex != widget.controller!.index) {
setState(() { setState(() {
currentIndex = widget.controller.index; currentIndex = widget.controller!.index;
}); });
} }
} }
@override @override
void dispose() { void dispose() {
widget.controller ?? widget.controller.removeListener(_listener); widget.controller ?? widget.controller!.removeListener(_listener);
super.dispose(); super.dispose();
} }
@ -50,10 +51,10 @@ class _BrandTabBarState extends State<BrandTabBar> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_getIconButton('Провайдеры', BrandIcons.server, 0), _getIconButton('basis.providers'.tr(), BrandIcons.server, 0),
_getIconButton('Сервисы', BrandIcons.box, 1), _getIconButton('basis.services'.tr(), BrandIcons.box, 1),
_getIconButton('Пользователи', BrandIcons.users, 2), _getIconButton('basis.users'.tr(), BrandIcons.users, 2),
_getIconButton('Еще', BrandIcons.menu, 3), _getIconButton('basis.more'.tr(), BrandIcons.menu, 3),
], ],
), ),
), ),
@ -68,7 +69,7 @@ class _BrandTabBarState extends State<BrandTabBar> {
var isActive = currentIndex == index; var isActive = currentIndex == index;
var color = isActive ? acitivColor : BrandColors.inactive; var color = isActive ? acitivColor : BrandColors.inactive;
return InkWell( return InkWell(
onTap: () => widget.controller.animateTo(index), onTap: () => widget.controller!.animateTo(index),
child: Padding( child: Padding(
padding: EdgeInsets.all(6), padding: EdgeInsets.all(6),
child: ConstrainedBox( child: ConstrainedBox(

View File

@ -17,26 +17,26 @@ enum TextType {
class BrandText extends StatelessWidget { class BrandText extends StatelessWidget {
const BrandText( const BrandText(
this.text, { this.text, {
Key key, Key? key,
this.style, this.style,
@required this.type, required this.type,
this.overflow, this.overflow,
this.softWrap, this.softWrap,
this.textAlign, this.textAlign,
}) : super(key: key); }) : super(key: key);
final String text; final String? text;
final TextStyle style; final TextStyle? style;
final TextType type; final TextType type;
final TextOverflow overflow; final TextOverflow? overflow;
final bool softWrap; final bool? softWrap;
final TextAlign textAlign; final TextAlign? textAlign;
factory BrandText.h1( factory BrandText.h1(
String text, { String? text, {
TextStyle style, TextStyle? style,
TextOverflow overflow, TextOverflow? overflow,
bool softWrap, bool? softWrap,
}) => }) =>
BrandText( BrandText(
text, text,
@ -44,18 +44,18 @@ class BrandText extends StatelessWidget {
style: style, style: style,
); );
factory BrandText.onboardingTitle(String text, {TextStyle style}) => factory BrandText.onboardingTitle(String text, {TextStyle? style}) =>
BrandText( BrandText(
text, text,
type: TextType.onboardingTitle, type: TextType.onboardingTitle,
style: style, style: style,
); );
factory BrandText.h2(String text, {TextStyle style}) => BrandText( factory BrandText.h2(String? text, {TextStyle? style}) => BrandText(
text, text,
type: TextType.h2, type: TextType.h2,
style: style, style: style,
); );
factory BrandText.h3(String text, {TextStyle style, TextAlign textAlign}) => factory BrandText.h3(String text, {TextStyle? style, TextAlign? textAlign}) =>
BrandText( BrandText(
text, text,
type: TextType.h3, type: TextType.h3,
@ -63,35 +63,35 @@ class BrandText extends StatelessWidget {
textAlign: textAlign, textAlign: textAlign,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
); );
factory BrandText.h4(String text, {TextStyle style}) => BrandText( factory BrandText.h4(String? text, {TextStyle? style}) => BrandText(
text, text,
type: TextType.h4, type: TextType.h4,
style: style, style: style,
); );
factory BrandText.body1(String text, {TextStyle style}) => BrandText( factory BrandText.body1(String? text, {TextStyle? style}) => BrandText(
text, text,
type: TextType.body1, type: TextType.body1,
style: style, style: style,
); );
factory BrandText.body2(String text, {TextStyle style}) => BrandText( factory BrandText.body2(String? text, {TextStyle? style}) => BrandText(
text, text,
type: TextType.body2, type: TextType.body2,
style: style, style: style,
); );
factory BrandText.medium(String text, factory BrandText.medium(String? text,
{TextStyle style, TextAlign textAlign}) => {TextStyle? style, TextAlign? textAlign}) =>
BrandText( BrandText(
text, text,
type: TextType.medium, type: TextType.medium,
style: style, style: style,
textAlign: textAlign, textAlign: textAlign,
); );
factory BrandText.small(String text, {TextStyle style}) => BrandText( factory BrandText.small(String text, {TextStyle? style}) => BrandText(
text, text,
type: TextType.small, type: TextType.small,
style: style, style: style,
); );
factory BrandText.buttonTitleText(String text, {TextStyle style}) => factory BrandText.buttonTitleText(String? text, {TextStyle? style}) =>
BrandText( BrandText(
text, text,
type: TextType.buttonTitleText, type: TextType.buttonTitleText,
@ -153,7 +153,7 @@ class BrandText extends StatelessWidget {
style = style.merge(this.style); style = style.merge(this.style);
} }
return Text( return Text(
text, text!,
style: style, style: style,
overflow: overflow, overflow: overflow,
softWrap: softWrap, softWrap: softWrap,

View File

@ -6,21 +6,21 @@ import 'package:selfprivacy/utils/named_font_weight.dart';
class BrandTimer extends StatefulWidget { class BrandTimer extends StatefulWidget {
const BrandTimer({ const BrandTimer({
Key key, Key? key,
@required this.startDateTime, required this.startDateTime,
@required this.duration, required this.duration,
}) : super(key: key); }) : super(key: key);
final DateTime startDateTime; final DateTime? startDateTime;
final Duration duration; final Duration? duration;
@override @override
_BrandTimerState createState() => _BrandTimerState(); _BrandTimerState createState() => _BrandTimerState();
} }
class _BrandTimerState extends State<BrandTimer> { class _BrandTimerState extends State<BrandTimer> {
String _timeString; String? _timeString;
Timer timer; late Timer timer;
@override @override
void initState() { void initState() {
@ -31,8 +31,8 @@ class _BrandTimerState extends State<BrandTimer> {
_timerStart() { _timerStart() {
_timeString = diffenceFromStart; _timeString = diffenceFromStart;
timer = Timer.periodic(Duration(seconds: 1), (Timer t) { timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
var timePassed = DateTime.now().difference(widget.startDateTime); var timePassed = DateTime.now().difference(widget.startDateTime!);
if (timePassed > widget.duration) { if (timePassed > widget.duration!) {
t.cancel(); t.cancel();
} else { } else {
_getTime(); _getTime();
@ -66,12 +66,12 @@ class _BrandTimerState extends State<BrandTimer> {
} }
String get diffenceFromStart => String get diffenceFromStart =>
_durationToString(DateTime.now().difference(widget.startDateTime)); _durationToString(DateTime.now().difference(widget.startDateTime!));
String _durationToString(Duration duration) { String _durationToString(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, "0"); String twoDigits(int n) => n.toString().padLeft(2, "0");
String twoDigitSeconds = String twoDigitSeconds =
twoDigits(widget.duration.inSeconds - duration.inSeconds.remainder(60)); twoDigits(widget.duration!.inSeconds - duration.inSeconds.remainder(60));
return "$twoDigitSeconds cек"; return "$twoDigitSeconds cек";
} }

View File

@ -3,9 +3,9 @@ import 'package:selfprivacy/config/brand_colors.dart';
class DotsIndicator extends StatelessWidget { class DotsIndicator extends StatelessWidget {
const DotsIndicator({ const DotsIndicator({
Key key, Key? key,
@required this.activeIndex, required this.activeIndex,
@required this.count, required this.count,
}) : super(key: key); }) : super(key: key);
final int activeIndex; final int activeIndex;

View File

@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class BrandError extends StatelessWidget { class BrandError extends StatelessWidget {
const BrandError({Key key, this.error, this.stackTrace}) : super(key: key); const BrandError({Key? key, this.error, this.stackTrace}) : super(key: key);
final Object error; final Object? error;
final StackTrace stackTrace; final StackTrace? stackTrace;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -3,14 +3,14 @@ import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/logic/models/state_types.dart';
class IconStatusMask extends StatelessWidget { class IconStatusMask extends StatelessWidget {
IconStatusMask({this.child, this.status}); IconStatusMask({required this.child, required this.status});
final Icon child; final Icon child;
final StateType status; final StateType status;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Color> colors; late List<Color> colors;
switch (status) { switch (status) {
case StateType.uninitialized: case StateType.uninitialized:
colors = BrandColors.uninitializedGradientColors; colors = BrandColors.uninitializedGradientColors;

View File

@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/ui/pages/initializing/initializing.dart'; import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
class NotReadyCard extends StatelessWidget { class NotReadyCard extends StatelessWidget {
const NotReadyCard({Key key}) : super(key: key); const NotReadyCard({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -16,29 +18,34 @@ class NotReadyCard extends StatelessWidget {
text: TextSpan( text: TextSpan(
children: [ children: [
TextSpan( TextSpan(
text: 'Завершите настройку приложения используя ', text: 'not_ready_card.1'.tr(),
style: TextStyle(color: BrandColors.white), style: TextStyle(color: BrandColors.white),
), ),
WidgetSpan( WidgetSpan(
child: GestureDetector( child: Padding(
child: Text( padding: const EdgeInsets.only(bottom: 0.5),
'Мастер подключения', child: GestureDetector(
style: TextStyle( onTap: () => Navigator.of(context).push(
materialRoute(
InitializingPage(),
),
),
child: Text(
'not_ready_card.2'.tr(),
style: body1Style.copyWith(
color: Theme.of(context).brightness == Brightness.dark color: Theme.of(context).brightness == Brightness.dark
? Colors.blueAccent ? Colors.black
: BrandColors.white, : BrandColors.white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
decoration: TextDecoration.underline), decoration: TextDecoration.underline,
), // height: 1.1,
onTap: () => Navigator.of(context).push( ),
materialRoute(
InitializingPage(),
), ),
), ),
), ),
), ),
TextSpan( TextSpan(
text: ' для продолжения работы', text: 'not_ready_card.3'.tr(),
style: TextStyle(color: BrandColors.white), style: TextStyle(color: BrandColors.white),
), ),
], ],

View File

@ -7,9 +7,9 @@ import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class ProgressBar extends StatefulWidget { class ProgressBar extends StatefulWidget {
ProgressBar({ ProgressBar({
Key key, Key? key,
@required this.steps, required this.steps,
@required this.activeIndex, required this.activeIndex,
}) : super(key: key); }) : super(key: key);
final int activeIndex; final int activeIndex;
@ -102,14 +102,14 @@ class _ProgressBarState extends State<ProgressBar> {
} }
Expanded _stepTitle({ Expanded _stepTitle({
int index, required int index,
TextStyle style, TextStyle? style,
String step, String? step,
}) { }) {
var isActive = index == widget.activeIndex; var isActive = index == widget.activeIndex;
var checked = index < widget.activeIndex; var checked = index < widget.activeIndex;
style = isActive ? style.copyWith(fontWeight: FontWeight.w700) : style; style = isActive ? style!.copyWith(fontWeight: FontWeight.w700) : style;
return Expanded( return Expanded(
flex: 2, flex: 2,
child: RichText( child: RichText(

View File

@ -3,10 +3,10 @@ import 'package:selfprivacy/config/brand_colors.dart';
class SwitcherBlock extends StatelessWidget { class SwitcherBlock extends StatelessWidget {
const SwitcherBlock({ const SwitcherBlock({
Key key, Key? key,
@required this.child, required this.child,
@required this.isActive, required this.isActive,
@required this.onChange, required this.onChange,
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget child;

View File

@ -1,9 +1,7 @@
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; 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/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/backblaze_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/cloudflare_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/cloudflare_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/domain_cloudflare.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/domain_cloudflare.dart';
@ -13,13 +11,14 @@ import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
import 'package:selfprivacy/ui/components/brand_card/brand_card.dart'; import 'package:selfprivacy/ui/components/brand_card/brand_card.dart';
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.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/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart'; import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart'; import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
import 'package:selfprivacy/ui/pages/rootRoute.dart'; import 'package:selfprivacy/ui/pages/rootRoute.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
class InitializingPage extends StatelessWidget { class InitializingPage extends StatelessWidget {
@override @override
@ -60,9 +59,9 @@ class InitializingPage extends StatelessWidget {
'Domain', 'Domain',
'User', 'User',
'Server', 'Server',
'Check1', '',
'Check2', '',
'Check3' ''
], ],
activeIndex: cubit.state.progress, activeIndex: cubit.state.progress,
), ),
@ -76,12 +75,13 @@ class InitializingPage extends StatelessWidget {
), ),
), ),
BrandButton.text( BrandButton.text(
title: title: cubit.state.isFullyInitilized
cubit.state.isFullyInitilized ? 'Close' : 'Настрою потом', ? 'basis.close'.tr()
: 'Настрою потом',
onPressed: () { onPressed: () {
Navigator.of(context).pushAndRemoveUntil( Navigator.of(context).pushAndRemoveUntil(
materialRoute(RootPage()), materialRoute(RootPage()),
(predicate) => predicate == null, (predicate) => false,
); );
}), }),
SizedBox(height: 30), SizedBox(height: 30),
@ -96,7 +96,7 @@ class InitializingPage extends StatelessWidget {
return BlocProvider( return BlocProvider(
create: (context) => HetznerFormCubit(initializingCubit), create: (context) => HetznerFormCubit(initializingCubit),
child: Builder(builder: (context) { child: Builder(builder: (context) {
var formCubit = context.watch<HetznerFormCubit>(); var formCubitState = context.watch<HetznerFormCubit>().state;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -105,13 +105,12 @@ class InitializingPage extends StatelessWidget {
width: 150, width: 150,
), ),
SizedBox(height: 10), SizedBox(height: 10),
BrandText.h2('Подключите сервер Hetzner'), BrandText.h2('initializing.1'.tr()),
SizedBox(height: 10), SizedBox(height: 10),
BrandText.body2( BrandText.body2('initializing.2'.tr()),
'Здесь будут жить наши данные и SelfPrivacy-сервисы'),
Spacer(), Spacer(),
CubitFormTextField( CubitFormTextField(
formFieldCubit: formCubit.apiKey, formFieldCubit: context.read<HetznerFormCubit>().apiKey,
textAlign: TextAlign.center, textAlign: TextAlign.center,
scrollPadding: EdgeInsets.only(bottom: 70), scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration( decoration: InputDecoration(
@ -120,14 +119,15 @@ class InitializingPage extends StatelessWidget {
), ),
Spacer(), Spacer(),
BrandButton.rised( BrandButton.rised(
onPressed: onPressed: formCubitState.isSubmitting
formCubit.state.isSubmitting ? null : formCubit.trySubmit, ? null
title: 'Подключить', : () => context.read<HetznerFormCubit>().trySubmit(),
title: 'basis.connect'.tr(),
), ),
SizedBox(height: 10), SizedBox(height: 10),
BrandButton.text( BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()), onPressed: () => _showModal(context, _HowHetzner()),
title: 'Как получить API Token', title: 'initializing.how'.tr(),
), ),
], ],
); );
@ -150,7 +150,7 @@ class InitializingPage extends StatelessWidget {
return BlocProvider( return BlocProvider(
create: (context) => CloudFlareFormCubit(initializingCubit), create: (context) => CloudFlareFormCubit(initializingCubit),
child: Builder(builder: (context) { child: Builder(builder: (context) {
var formCubit = context.watch<CloudFlareFormCubit>(); var formCubitState = context.watch<CloudFlareFormCubit>().state;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -160,28 +160,29 @@ class InitializingPage extends StatelessWidget {
width: 150, width: 150,
), ),
SizedBox(height: 10), SizedBox(height: 10),
BrandText.h2('Подключите CloudFlare'), BrandText.h2('initializing.3'.tr()),
SizedBox(height: 10), SizedBox(height: 10),
BrandText.body2('Для управления DNS вашего домена'), BrandText.body2('initializing.4'.tr()),
Spacer(), Spacer(),
CubitFormTextField( CubitFormTextField(
formFieldCubit: formCubit.apiKey, formFieldCubit: context.read<CloudFlareFormCubit>().apiKey,
textAlign: TextAlign.center, textAlign: TextAlign.center,
scrollPadding: EdgeInsets.only(bottom: 70), scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'CloudFlare API Token', hintText: 'initializing.5'.tr(),
), ),
), ),
Spacer(), Spacer(),
BrandButton.rised( BrandButton.rised(
onPressed: onPressed: formCubitState.isSubmitting
formCubit.state.isSubmitting ? null : formCubit.trySubmit, ? null
title: 'Подключить', : () => context.read<CloudFlareFormCubit>().trySubmit(),
title: 'basis.connect'.tr(),
), ),
SizedBox(height: 10), SizedBox(height: 10),
BrandButton.text( BrandButton.text(
onPressed: () {}, onPressed: () => _showModal(context, _HowHetzner()),
title: 'Как получить API Token', title: 'initializing.how'.tr(),
), ),
], ],
); );
@ -193,7 +194,7 @@ class InitializingPage extends StatelessWidget {
return BlocProvider( return BlocProvider(
create: (context) => BackblazeFormCubit(initializingCubit), create: (context) => BackblazeFormCubit(initializingCubit),
child: Builder(builder: (context) { child: Builder(builder: (context) {
var formCubit = context.watch<BackblazeFormCubit>(); var formCubitState = context.watch<BackblazeFormCubit>().state;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -202,11 +203,11 @@ class InitializingPage extends StatelessWidget {
height: 50, height: 50,
), ),
SizedBox(height: 10), SizedBox(height: 10),
BrandText.h2('Подключите облачное хранилище Backblaze'), BrandText.h2('initializing.6'.tr()),
SizedBox(height: 10), SizedBox(height: 10),
Spacer(), Spacer(),
CubitFormTextField( CubitFormTextField(
formFieldCubit: formCubit.keyId, formFieldCubit: context.read<BackblazeFormCubit>().keyId,
textAlign: TextAlign.center, textAlign: TextAlign.center,
scrollPadding: EdgeInsets.only(bottom: 70), scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration( decoration: InputDecoration(
@ -215,7 +216,7 @@ class InitializingPage extends StatelessWidget {
), ),
Spacer(), Spacer(),
CubitFormTextField( CubitFormTextField(
formFieldCubit: formCubit.applicationKey, formFieldCubit: context.read<BackblazeFormCubit>().applicationKey,
textAlign: TextAlign.center, textAlign: TextAlign.center,
scrollPadding: EdgeInsets.only(bottom: 70), scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration( decoration: InputDecoration(
@ -224,14 +225,15 @@ class InitializingPage extends StatelessWidget {
), ),
Spacer(), Spacer(),
BrandButton.rised( BrandButton.rised(
onPressed: onPressed: formCubitState.isSubmitting
formCubit.state.isSubmitting ? null : formCubit.trySubmit, ? null
title: 'Подключить', : () => context.read<BackblazeFormCubit>().trySubmit(),
title: 'basis.connect'.tr(),
), ),
SizedBox(height: 10), SizedBox(height: 10),
BrandButton.text( BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()), onPressed: () => _showModal(context, _HowHetzner()),
title: 'Как получить API Token', title: 'initializing.how'.tr(),
), ),
], ],
); );
@ -243,8 +245,7 @@ class InitializingPage extends StatelessWidget {
return BlocProvider( return BlocProvider(
create: (context) => DomainSetupCubit(initializingCubit)..load(), create: (context) => DomainSetupCubit(initializingCubit)..load(),
child: Builder(builder: (context) { child: Builder(builder: (context) {
var domainSetup = context.watch<DomainSetupCubit>(); DomainSetupState state = context.watch<DomainSetupCubit>().state;
var state = domainSetup.state;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -253,19 +254,18 @@ class InitializingPage extends StatelessWidget {
width: 150, width: 150,
), ),
SizedBox(height: 30), SizedBox(height: 30),
BrandText.h2('Домен'), BrandText.h2('basis.domain'.tr()),
SizedBox(height: 10), SizedBox(height: 10),
if (state is Empty) if (state is Empty) BrandText.body2('initializing.7'.tr()),
BrandText.body2('На данный момент подлюченных доменов нет'),
if (state is Loading) if (state is Loading)
BrandText.body2( BrandText.body2(
state.type == LoadingTypes.loadingDomain state.type == LoadingTypes.loadingDomain
? 'Загружаем список доменов' ? 'initializing.8'.tr()
: 'Сохранение..', : 'basis.saving'.tr(),
), ),
if (state is MoreThenOne) if (state is MoreThenOne)
BrandText.body2( BrandText.body2(
'Найдено больше одного домена, для вашей безопастности, просим вам удалить не нужные домены', 'initializing.9'.tr(),
), ),
if (state is Loaded) ...[ if (state is Loaded) ...[
SizedBox(height: 10), SizedBox(height: 10),
@ -283,7 +283,7 @@ class InitializingPage extends StatelessWidget {
Container( Container(
width: 50, width: 50,
child: BrandButton.rised( child: BrandButton.rised(
onPressed: () => domainSetup.load(), onPressed: () => context.read<DomainSetupCubit>().load(),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -302,7 +302,7 @@ class InitializingPage extends StatelessWidget {
if (state is Empty) ...[ if (state is Empty) ...[
SizedBox(height: 30), SizedBox(height: 30),
BrandButton.rised( BrandButton.rised(
onPressed: () => domainSetup.load(), onPressed: () => context.read<DomainSetupCubit>().load(),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -319,8 +319,8 @@ class InitializingPage extends StatelessWidget {
if (state is Loaded) ...[ if (state is Loaded) ...[
SizedBox(height: 30), SizedBox(height: 30),
BrandButton.rised( BrandButton.rised(
onPressed: () => domainSetup.saveDomain(), onPressed: () => context.read<DomainSetupCubit>().saveDomain(),
title: 'Сохранить домен', title: 'initializing.10'.tr(),
), ),
], ],
SizedBox(height: 10), SizedBox(height: 10),
@ -328,7 +328,7 @@ class InitializingPage extends StatelessWidget {
SizedBox(height: 10), SizedBox(height: 10),
BrandButton.text( BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()), onPressed: () => _showModal(context, _HowHetzner()),
title: 'Как получить API Token', title: 'initializing.how'.tr(),
), ),
], ],
); );
@ -340,7 +340,7 @@ class InitializingPage extends StatelessWidget {
return BlocProvider( return BlocProvider(
create: (context) => RootUserFormCubit(initializingCubit), create: (context) => RootUserFormCubit(initializingCubit),
child: Builder(builder: (context) { child: Builder(builder: (context) {
var formCubit = context.watch<RootUserFormCubit>(); var formCubitState = context.watch<RootUserFormCubit>().state;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -348,30 +348,33 @@ class InitializingPage extends StatelessWidget {
Spacer(), Spacer(),
SizedBox(height: 10), SizedBox(height: 10),
CubitFormTextField( CubitFormTextField(
formFieldCubit: formCubit.userName, formFieldCubit: context.read<RootUserFormCubit>().userName,
textAlign: TextAlign.center, textAlign: TextAlign.center,
scrollPadding: EdgeInsets.only(bottom: 70), scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Никнейм', hintText: 'basis.nickname'.tr(),
), ),
), ),
SizedBox(height: 10), SizedBox(height: 10),
BlocBuilder<FieldCubit<bool>, FieldCubitState<bool>>( BlocBuilder<FieldCubit<bool>, FieldCubitState<bool>>(
cubit: formCubit.isVisible, bloc: context.read<RootUserFormCubit>().isVisible,
builder: (context, state) { builder: (context, state) {
var isVisible = state.value; var isVisible = state.value;
return CubitFormTextField( return CubitFormTextField(
obscureText: !isVisible, obscureText: !isVisible,
formFieldCubit: formCubit.password, formFieldCubit: context.read<RootUserFormCubit>().password,
textAlign: TextAlign.center, textAlign: TextAlign.center,
scrollPadding: EdgeInsets.only(bottom: 70), scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Пароль', hintText: 'basis.password'.tr(),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
isVisible ? Icons.visibility : Icons.visibility_off, isVisible ? Icons.visibility : Icons.visibility_off,
), ),
onPressed: () => formCubit.isVisible.setValue(!isVisible), onPressed: () => context
.read<RootUserFormCubit>()
.isVisible
.setValue(!isVisible),
), ),
suffixIconConstraints: BoxConstraints(minWidth: 60), suffixIconConstraints: BoxConstraints(minWidth: 60),
prefixIconConstraints: BoxConstraints(maxWidth: 85), prefixIconConstraints: BoxConstraints(maxWidth: 85),
@ -382,14 +385,15 @@ class InitializingPage extends StatelessWidget {
), ),
Spacer(), Spacer(),
BrandButton.rised( BrandButton.rised(
onPressed: onPressed: formCubitState.isSubmitting
formCubit.state.isSubmitting ? null : formCubit.trySubmit, ? null
title: 'Подключить', : () => context.read<RootUserFormCubit>().trySubmit(),
title: 'basis.connect'.tr(),
), ),
SizedBox(height: 10), SizedBox(height: 10),
BrandButton.text( BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()), onPressed: () => _showModal(context, _HowHetzner()),
title: 'Как получить API Token', title: 'initializing.how'.tr(),
), ),
], ],
); );
@ -404,19 +408,19 @@ class InitializingPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Spacer(flex: 2), Spacer(flex: 2),
BrandText.h2('Создать сервер'), BrandText.h2('initializing.how'.tr()),
SizedBox(height: 10), SizedBox(height: 10),
BrandText.body2('Создать сервер'), BrandText.body2('initializing.11'.tr()),
Spacer(), Spacer(),
BrandButton.rised( BrandButton.rised(
onPressed: onPressed:
isLoading ? null : appConfigCubit.createServerAndSetDnsRecords, isLoading! ? null : appConfigCubit.createServerAndSetDnsRecords,
title: isLoading ? 'loading' : 'Создать сервер', title: isLoading ? 'loading' : 'initializing.11'.tr(),
), ),
Spacer(flex: 2), Spacer(flex: 2),
BrandButton.text( BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()), onPressed: () => _showModal(context, _HowHetzner()),
title: 'Что это значит?', title: 'initializing.what'.tr(),
), ),
], ],
); );
@ -427,13 +431,13 @@ class InitializingPage extends StatelessWidget {
assert(appConfigCubit.state is TimerState, 'wronge state'); assert(appConfigCubit.state is TimerState, 'wronge state');
var state = appConfigCubit.state as TimerState; var state = appConfigCubit.state as TimerState;
String text; String? text;
if (state.isServerReseted) { if (state.isServerReseted!) {
text = 'Сервер презагружен, ждем последнюю проверку'; text = 'initializing.13'.tr();
} else if (state.isServerStarted) { } else if (state.isServerStarted!) {
text = 'Cервер запушен, сейчас он будет проверен и перезагружен'; text = 'initializing.14'.tr();
} else if (state.isServerCreated) { } else if (state.isServerCreated) {
text = 'Cервер создан, идет проверка ДНС адресов и запуск сервера'; text = 'initializing.15'.tr();
} }
return Builder(builder: (context) { return Builder(builder: (context) {
return Column( return Column(
@ -443,23 +447,23 @@ class InitializingPage extends StatelessWidget {
SizedBox(height: 10), SizedBox(height: 10),
BrandText.body2(text), BrandText.body2(text),
SizedBox(height: 10), SizedBox(height: 10),
if (!state.isLoading) if (!state.isLoading!)
Row( Row(
children: [ children: [
BrandText.body2('До следующей проверки: '), BrandText.body2('initializing.16'.tr()),
BrandTimer( BrandTimer(
startDateTime: state.timerStart, startDateTime: state.timerStart,
duration: state.duration, duration: state.duration,
) )
], ],
), ),
if (state.isLoading) BrandText.body2('Проверка'), if (state.isLoading!) BrandText.body2('initializing.17'.tr()),
Spacer( Spacer(
flex: 2, flex: 2,
), ),
BrandButton.text( BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()), onPressed: () => _showModal(context, _HowHetzner()),
title: 'Что это значит?', title: 'initializing.what'.tr(),
), ),
], ],
); );
@ -477,58 +481,17 @@ class InitializingPage extends StatelessWidget {
class _HowHetzner extends StatelessWidget { class _HowHetzner extends StatelessWidget {
const _HowHetzner({ const _HowHetzner({
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var isDark = Theme.of(context).brightness == Brightness.dark;
return BrandModalSheet( return BrandModalSheet(
child: Padding( child: Padding(
padding: brandPagePadding2, padding: brandPagePadding2.copyWith(top: 25),
child: Column( child: BrandMarkdown(
children: [ fileName: 'how_hetzner',
SizedBox(height: 40), )),
BrandText.h2('Как получить Hetzner API Token'),
SizedBox(height: 20),
RichText(
text: TextSpan(
children: [
TextSpan(
text: '1 Переходим по ссылке ',
style: body1Style.copyWith(
color: isDark ? BrandColors.white : BrandColors.black,
),
),
BrandSpanButton.link(
text: 'hetzner.com',
urlString: 'https://hetzner.com',
),
TextSpan(
text: '''
2 Заходим в созданный нами проект. Если такового - нет, значит создаём.
3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний Security (с иконкой ключика).
4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.
5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.
6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.
''',
style: body1Style.copyWith(
color: isDark ? BrandColors.white : BrandColors.black,
),
),
],
),
),
],
),
),
); );
} }
} }

View File

@ -1,48 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart';
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_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
class AboutPage extends StatelessWidget { class AboutPage extends StatelessWidget {
const AboutPage({Key key}) : super(key: key); const AboutPage({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
child: BrandHeader(title: 'О проекте', hasBackButton: true), child: BrandHeader(
title: 'more.about_project'.tr(), hasBackButton: true),
preferredSize: Size.fromHeight(52), preferredSize: Size.fromHeight(52),
), ),
body: ListView( body: Container(
padding: brandPagePadding2, child: BrandMarkdown(
children: [ fileName: 'about',
BrandDivider(), ),
SizedBox(height: 20),
BrandText.h3('О проекте'),
SizedBox(height: 10),
BrandText.body1(
'Всё больше организаций хотят владеть нашими данными'),
SizedBox(height: 10),
BrandText.body1(
'А мы сами хотим распоряжаться своими данными на своем сервере.'),
SizedBox(height: 20),
BrandDivider(),
SizedBox(height: 10),
BrandText.h3('Миссия проекта'),
SizedBox(height: 10),
BrandText.body1(
'Цифровая независимость и приватность доступная каждому'),
SizedBox(height: 20),
BrandDivider(),
SizedBox(height: 10),
BrandText.h3('Цель'),
SizedBox(height: 10),
BrandText.body1(
'Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких'),
SizedBox(height: 10),
],
), ),
), ),
); );

View File

@ -9,9 +9,10 @@ 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_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/utils/named_font_weight.dart'; import 'package:selfprivacy/utils/named_font_weight.dart';
import 'package:easy_localization/easy_localization.dart';
class AppSettingsPage extends StatefulWidget { class AppSettingsPage extends StatefulWidget {
const AppSettingsPage({Key key}) : super(key: key); const AppSettingsPage({Key? key}) : super(key: key);
@override @override
_AppSettingsPageState createState() => _AppSettingsPageState(); _AppSettingsPageState createState() => _AppSettingsPageState();
@ -20,16 +21,14 @@ class AppSettingsPage extends StatefulWidget {
class _AppSettingsPageState extends State<AppSettingsPage> { class _AppSettingsPageState extends State<AppSettingsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var appSettings = context.watch<AppSettingsCubit>(); var isDarkModeOn = context.watch<AppSettingsCubit>().state.isDarkModeOn;
var isDarkModeOn = appSettings.state.isDarkModeOn;
return SafeArea( return SafeArea(
child: Builder(builder: (context) { child: Builder(builder: (context) {
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
child: child:
BrandHeader(title: 'Настройки приложения', hasBackButton: true), BrandHeader(title: 'more.settings'.tr(), hasBackButton: true),
preferredSize: Size.fromHeight(52), preferredSize: Size.fromHeight(52),
), ),
body: ListView( body: ListView(
@ -57,8 +56,9 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
activeColor: BrandColors.green1, activeColor: BrandColors.green1,
activeTrackColor: BrandColors.green2, activeTrackColor: BrandColors.green2,
value: Theme.of(context).brightness == Brightness.dark, value: Theme.of(context).brightness == Brightness.dark,
onChanged: (value) => appSettings.updateDarkMode( onChanged: (value) => context
isDarkModeOn: !isDarkModeOn), .read<AppSettingsCubit>()
.updateDarkMode(isDarkModeOn: !isDarkModeOn),
), ),
], ],
), ),
@ -80,8 +80,10 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
), ),
), ),
SizedBox(width: 5), SizedBox(width: 5),
RaisedButton( ElevatedButton(
color: BrandColors.red1, style: ElevatedButton.styleFrom(
primary: BrandColors.red1,
),
child: Text( child: Text(
'Reset', 'Reset',
style: TextStyle( style: TextStyle(
@ -92,24 +94,26 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, context: context,
child: BrandAlert( builder: (_) {
title: 'Вы уверенны', return BrandAlert(
contentText: 'Сбросить все ключи?', title: 'Вы уверенны',
acitons: [ contentText: 'Сбросить все ключи?',
ActionButton( acitons: [
text: 'Да, сбросить', ActionButton(
isRed: true, text: 'Да, сбросить',
onPressed: () { isRed: true,
context onPressed: () {
.read<AppConfigCubit>() context
.clearAppConfig(); .read<AppConfigCubit>()
Navigator.of(context).pop(); .clearAppConfig();
}), Navigator.of(context).pop();
ActionButton( }),
text: 'Отмена', ActionButton(
), text: 'Отмена',
], ),
), ],
);
},
); );
}, },
) )
@ -126,9 +130,9 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
class _TextColumn extends StatelessWidget { class _TextColumn extends StatelessWidget {
const _TextColumn({ const _TextColumn({
Key key, Key? key,
@required this.title, required this.title,
@required this.value, required this.value,
this.hasWarning = false, this.hasWarning = false,
}) : super(key: key); }) : super(key: key);

View File

@ -9,7 +9,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_header/brand_header.dart';
class Console extends StatefulWidget { class Console extends StatefulWidget {
const Console({Key key}) : super(key: key); const Console({Key? key}) : super(key: key);
@override @override
_ConsoleState createState() => _ConsoleState(); _ConsoleState createState() => _ConsoleState();

View File

@ -4,16 +4,17 @@ 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_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:package_info/package_info.dart'; import 'package:package_info/package_info.dart';
import 'package:easy_localization/easy_localization.dart';
class InfoPage extends StatelessWidget { class InfoPage extends StatelessWidget {
const InfoPage({Key key}) : super(key: key); const InfoPage({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
child: BrandHeader(title: 'О приложении', hasBackButton: true), child: BrandHeader(title: 'more.about_app'.tr(), hasBackButton: true),
preferredSize: Size.fromHeight(52), preferredSize: Size.fromHeight(52),
), ),
body: ListView( body: ListView(
@ -24,8 +25,8 @@ class InfoPage extends StatelessWidget {
FutureBuilder( FutureBuilder(
future: _version(), future: _version(),
builder: (context, snapshot) { builder: (context, snapshot) {
return BrandText.body1( return BrandText.body1('more.about_app_page.text'
'Тут любая служебная информация, v.${snapshot.data}'); .tr(args: [snapshot.data.toString()]));
}), }),
], ],
), ),

View File

@ -9,6 +9,7 @@ import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
import 'package:selfprivacy/ui/pages/rootRoute.dart'; import 'package:selfprivacy/ui/pages/rootRoute.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
import 'about/about.dart'; import 'about/about.dart';
import 'app_settings/app_setting.dart'; import 'app_settings/app_setting.dart';
@ -16,13 +17,13 @@ import 'console/console.dart';
import 'info/info.dart'; import 'info/info.dart';
class MorePage extends StatelessWidget { class MorePage extends StatelessWidget {
const MorePage({Key key}) : super(key: key); const MorePage({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
child: BrandHeader(title: 'Еще'), child: BrandHeader(title: 'basis.more'.tr()),
preferredSize: Size.fromHeight(52), preferredSize: Size.fromHeight(52),
), ),
body: ListView( body: ListView(
@ -33,33 +34,33 @@ class MorePage extends StatelessWidget {
children: [ children: [
BrandDivider(), BrandDivider(),
_NavItem( _NavItem(
title: 'Мастер Подключения', title: 'more.configuration_wizard'.tr(),
iconData: BrandIcons.settings, iconData: BrandIcons.triangle,
goTo: InitializingPage(), goTo: InitializingPage(),
), ),
_NavItem( _NavItem(
title: 'Настройки приложения', title: 'more.settings'.tr(),
iconData: BrandIcons.settings, iconData: BrandIcons.settings,
goTo: AppSettingsPage(), goTo: AppSettingsPage(),
), ),
_NavItem( _NavItem(
title: 'О проекте Selfprivacy', title: 'more.about_project'.tr(),
iconData: BrandIcons.triangle, iconData: BrandIcons.engineer,
goTo: AboutPage(), goTo: AboutPage(),
), ),
_NavItem( _NavItem(
title: 'О приложении', title: 'more.about_app'.tr(),
iconData: BrandIcons.help, iconData: BrandIcons.fire,
goTo: InfoPage(), goTo: InfoPage(),
), ),
_NavItem( _NavItem(
title: 'Onboarding', title: 'more.onboarding'.tr(),
iconData: BrandIcons.triangle, iconData: BrandIcons.start,
goTo: OnboardingPage(nextPage: RootPage()), goTo: OnboardingPage(nextPage: RootPage()),
), ),
_NavItem( _NavItem(
title: 'Console', title: 'more.console'.tr(),
iconData: BrandIcons.triangle, iconData: BrandIcons.terminal,
goTo: Console(), goTo: Console(),
), ),
], ],
@ -73,10 +74,10 @@ class MorePage extends StatelessWidget {
class _NavItem extends StatelessWidget { class _NavItem extends StatelessWidget {
const _NavItem({ const _NavItem({
Key key, Key? key,
@required this.iconData, required this.iconData,
@required this.goTo, required this.goTo,
@required this.title, required this.title,
}) : super(key: key); }) : super(key: key);
final IconData iconData; final IconData iconData;

View File

@ -3,9 +3,10 @@ import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
class OnboardingPage extends StatefulWidget { class OnboardingPage extends StatefulWidget {
const OnboardingPage({Key key, @required this.nextPage}) : super(key: key); const OnboardingPage({Key? key, required this.nextPage}) : super(key: key);
final Widget nextPage; final Widget nextPage;
@override @override
@ -54,10 +55,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
children: [ children: [
SizedBox(height: 30), SizedBox(height: 30),
BrandText.h2( BrandText.h2(
'Цифровая независимость и приватность, доступная каждому'), 'onboarding.page1_title'.tr(),
),
SizedBox(height: 20), SizedBox(height: 20),
BrandText.body2( BrandText.body2('onboarding.page1_text'.tr()),
'Почта и мессенджер с открытым исходным кодом на вашем личном сервере под вашим полным контролем.'),
Flexible( Flexible(
child: Center( child: Center(
child: Image.asset( child: Image.asset(
@ -78,7 +79,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
curve: Curves.easeIn, curve: Curves.easeIn,
); );
}, },
title: 'Далее', title: 'basis.next'.tr(),
), ),
SizedBox(height: 30), SizedBox(height: 30),
], ],
@ -94,10 +95,9 @@ class _OnboardingPageState extends State<OnboardingPage> {
child: Column( child: Column(
children: [ children: [
SizedBox(height: 30), SizedBox(height: 30),
BrandText.h2('Для работы понадобятся ваши аккаунты'), BrandText.h2('onboarding.page2_title'.tr()),
SizedBox(height: 20), SizedBox(height: 20),
BrandText.body2( BrandText.body2('onboarding.page2_text'.tr()),
'Для максимальноей приватности и независимости SelfPrivacy не использует свои серверы. \n \n Если у вас нет домена, аккаунтов на Hetzner, AWS и Clouflare, мы поможем их создать и подключить.'),
SizedBox(height: 20), SizedBox(height: 20),
Center( Center(
child: Image.asset( child: Image.asset(
@ -127,7 +127,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
Navigator.of(context) Navigator.of(context)
.pushReplacement(materialRoute(widget.nextPage)); .pushReplacement(materialRoute(widget.nextPage));
}, },
title: 'Понял', title: 'basis.got_it'.tr(),
), ),
SizedBox(height: 30), SizedBox(height: 30),
], ],
@ -137,10 +137,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
} }
String _fileName({ String _fileName({
@required BuildContext context, required BuildContext context,
@required String path, required String path,
@required String fileName, required String fileName,
@required String fileExtention, required String fileExtention,
}) { }) {
var theme = Theme.of(context); var theme = Theme.of(context);
var isDark = theme.brightness == Brightness.dark; var isDark = theme.brightness == Brightness.dark;

View File

@ -12,9 +12,10 @@ import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package:selfprivacy/ui/pages/providers/settings/settings.dart'; import 'package:selfprivacy/ui/pages/providers/settings/settings.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
class ProvidersPage extends StatefulWidget { class ProvidersPage extends StatefulWidget {
ProvidersPage({Key key}) : super(key: key); ProvidersPage({Key? key}) : super(key: key);
@override @override
_ProvidersPageState createState() => _ProvidersPageState(); _ProvidersPageState createState() => _ProvidersPageState();
@ -32,7 +33,7 @@ class _ProvidersPageState extends State<ProvidersPage> {
.toList(); .toList();
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
child: BrandHeader(title: 'Провайдеры'), child: BrandHeader(title: 'providers.page_title'.tr()),
preferredSize: Size.fromHeight(52), preferredSize: Size.fromHeight(52),
), ),
body: ListView( body: ListView(
@ -50,32 +51,31 @@ class _ProvidersPageState extends State<ProvidersPage> {
} }
class _Card extends StatelessWidget { class _Card extends StatelessWidget {
const _Card({Key key, @required this.provider}) : super(key: key); const _Card({Key? key, required this.provider}) : super(key: key);
final ProviderModel provider; final ProviderModel provider;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String title; String? title;
String message; String? message;
String stableText; String? stableText;
var appConfig = context.watch<AppConfigCubit>().state; AppConfigState appConfig = context.watch<AppConfigCubit>().state;
var domainName = var domainName =
appConfig.isDomainFilled ? appConfig.cloudFlareDomain.domainName : ''; appConfig.isDomainFilled ? appConfig.cloudFlareDomain!.domainName : '';
switch (provider.type) { switch (provider.type) {
case ProviderType.server: case ProviderType.server:
title = 'Сервер'; title = 'providers.server.card_title'.tr();
stableText = 'В норме'; stableText = 'В норме';
break; break;
case ProviderType.domain: case ProviderType.domain:
title = 'Домен'; title = 'providers.domain.card_title'.tr();
message = domainName; message = domainName;
stableText = 'Домен настроен'; stableText = 'Домен настроен';
break; break;
case ProviderType.backup: case ProviderType.backup:
// message = '22 янв 2021 14:30'; title = 'providers.backup.card_title'.tr();
title = 'Резервное копирование';
stableText = 'В норме'; stableText = 'В норме';
break; break;
} }
@ -116,28 +116,56 @@ class _Card extends StatelessWidget {
class _ProviderDetails extends StatelessWidget { class _ProviderDetails extends StatelessWidget {
const _ProviderDetails({ const _ProviderDetails({
Key key, Key? key,
@required this.provider, required this.provider,
@required this.statusText, required this.statusText,
}) : super(key: key); }) : super(key: key);
final ProviderModel provider; final ProviderModel provider;
final String statusText; final String? statusText;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String title; late String title;
late List<Widget> children;
var config = context.watch<AppConfigCubit>().state;
var domainName = config.isDomainFilled
? config.cloudFlareDomain!.domainName!
: 'example.com';
switch (provider.type) { switch (provider.type) {
case ProviderType.server: case ProviderType.server:
title = 'Сервер'; title = 'providers.server.card_title'.tr();
children = [
BrandText.body1('providers.server.bottom_sheet.1'.tr()),
SizedBox(height: 10),
BrandText.body1('providers.server.bottom_sheet.2'.tr()),
SizedBox(height: 10),
BrandText.body1('providers.server.bottom_sheet.3'.tr()),
];
break; break;
case ProviderType.domain: case ProviderType.domain:
title = 'Домен'; title = 'providers.domain.card_title'.tr();
children = [
BrandText.body1('providers.domain.bottom_sheet.1'.tr()),
SizedBox(height: 10),
BrandText.body1(
'providers.domain.bottom_sheet.2'.tr(args: [domainName, 'Date'])),
SizedBox(height: 10),
BrandText.body1('providers.domain.bottom_sheet.3'.tr()),
];
break; break;
case ProviderType.backup: case ProviderType.backup:
title = 'Резервное копирование'; title = 'providers.backup.card_title'.tr();
children = [
BrandText.body1('providers.backup.bottom_sheet.1'.tr()),
SizedBox(height: 10),
BrandText.body1(
'providers.backup.bottom_sheet.2'.tr(args: [domainName, 'Time'])),
SizedBox(height: 10),
BrandText.body1('providers.backup.bottom_sheet.3'.tr()),
];
break; break;
} }
return BrandModalSheet( return BrandModalSheet(
@ -163,7 +191,7 @@ class _ProviderDetails extends StatelessWidget {
onSelected: (_PopupMenuItemType result) { onSelected: (_PopupMenuItemType result) {
switch (result) { switch (result) {
case _PopupMenuItemType.setting: case _PopupMenuItemType.setting:
navigatorKey.currentState navigatorKey.currentState!
.push(materialRoute(SettingsPage())); .push(materialRoute(SettingsPage()));
break; break;
} }
@ -174,7 +202,7 @@ class _ProviderDetails extends StatelessWidget {
value: _PopupMenuItemType.setting, value: _PopupMenuItemType.setting,
child: Container( child: Container(
padding: EdgeInsets.only(left: 5), padding: EdgeInsets.only(left: 5),
child: Text('Настройки'), child: Text('basis.settings'.tr()),
), ),
), ),
], ],
@ -182,11 +210,10 @@ class _ProviderDetails extends StatelessWidget {
), ),
), ),
Padding( Padding(
padding: brandPagePadding1, padding: brandPagePadding2,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 13),
IconStatusMask( IconStatusMask(
status: provider.state, status: provider.state,
child: child:
@ -195,11 +222,7 @@ class _ProviderDetails extends StatelessWidget {
SizedBox(height: 10), SizedBox(height: 10),
BrandText.h1(title), BrandText.h1(title),
SizedBox(height: 10), SizedBox(height: 10),
BrandText.body1(statusText), ...children
SizedBox(
height: 20,
),
Text('Статусы сервера и сервис провайдера и т.д.')
], ],
), ),
) )

View File

@ -5,9 +5,10 @@ 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_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/switch_block/switch_bloc.dart'; import 'package:selfprivacy/ui/components/switch_block/switch_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
class SettingsPage extends StatelessWidget { class SettingsPage extends StatelessWidget {
const SettingsPage({Key key}) : super(key: key); const SettingsPage({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -15,7 +16,7 @@ class SettingsPage extends StatelessWidget {
padding: brandPagePadding2, padding: brandPagePadding2,
children: [ children: [
SizedBox(height: 10), SizedBox(height: 10),
BrandHeader(title: 'Настройки', hasBackButton: true), BrandHeader(title: 'basis.settings'.tr(), hasBackButton: true),
BrandDivider(), BrandDivider(),
SwitcherBlock( SwitcherBlock(
onChange: (_) {}, onChange: (_) {},
@ -62,9 +63,9 @@ class SettingsPage extends StatelessWidget {
class _Button extends StatelessWidget { class _Button extends StatelessWidget {
const _Button({ const _Button({
Key key, Key? key,
@required this.onTap, required this.onTap,
@required this.child, required this.child,
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget child;
@ -88,9 +89,9 @@ class _Button extends StatelessWidget {
class _TextColumn extends StatelessWidget { class _TextColumn extends StatelessWidget {
const _TextColumn({ const _TextColumn({
Key key, Key? key,
@required this.title, required this.title,
@required this.value, required this.value,
this.hasWarning = false, this.hasWarning = false,
}) : super(key: key); }) : super(key: key);

View File

@ -1,5 +1,6 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart'; import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart';
import 'package:selfprivacy/ui/pages/more/more.dart'; import 'package:selfprivacy/ui/pages/more/more.dart';
import 'package:selfprivacy/ui/pages/providers/providers.dart'; import 'package:selfprivacy/ui/pages/providers/providers.dart';
@ -7,7 +8,7 @@ import 'package:selfprivacy/ui/pages/services/services.dart';
import 'package:selfprivacy/ui/pages/users/users.dart'; import 'package:selfprivacy/ui/pages/users/users.dart';
class RootPage extends StatefulWidget { class RootPage extends StatefulWidget {
const RootPage({Key key}) : super(key: key); const RootPage({Key? key}) : super(key: key);
@override @override
_RootPageState createState() => _RootPageState(); _RootPageState createState() => _RootPageState();
@ -15,7 +16,7 @@ class RootPage extends StatefulWidget {
class _RootPageState extends State<RootPage> class _RootPageState extends State<RootPage>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
TabController tabController; late TabController tabController;
@override @override
void initState() { void initState() {
@ -33,14 +34,17 @@ class _RootPageState extends State<RootPage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
body: TabBarView( body: Provider<ChangeTab>(
controller: tabController, create: (_) => ChangeTab(tabController.animateTo),
children: [ child: TabBarView(
ProvidersPage(), controller: tabController,
ServicesPage(), children: [
UsersPage(), ProvidersPage(),
MorePage(), ServicesPage(),
], UsersPage(),
MorePage(),
],
),
), ),
bottomNavigationBar: BrandTabBar( bottomNavigationBar: BrandTabBar(
controller: tabController, controller: tabController,
@ -49,3 +53,9 @@ class _RootPageState extends State<RootPage>
); );
} }
} }
class ChangeTab {
final ValueChanged<int> onPress;
ChangeTab(this.onPress);
}

View File

@ -1,18 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/logic/models/service.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_card/brand_card.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.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_icons/brand_icons.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/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:url_launcher/url_launcher.dart';
import '../rootRoute.dart';
class ServicesPage extends StatefulWidget { class ServicesPage extends StatefulWidget {
ServicesPage({Key key}) : super(key: key); ServicesPage({Key? key}) : super(key: key);
@override @override
_ServicesPageState createState() => _ServicesPageState(); _ServicesPageState createState() => _ServicesPageState();
@ -21,27 +27,20 @@ class ServicesPage extends StatefulWidget {
class _ServicesPageState extends State<ServicesPage> { class _ServicesPageState extends State<ServicesPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final serviceCubit = context.watch<ServicesCubit>();
final connected = serviceCubit.state.connected;
final uninitialized = serviceCubit.state.uninitialized;
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized; var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
child: BrandHeader(title: 'Сервисы'), child: BrandHeader(title: 'basis.services'.tr()),
preferredSize: Size.fromHeight(52), preferredSize: Size.fromHeight(52),
), ),
body: ListView( body: ListView(
padding: brandPagePadding2, padding: brandPagePadding2,
children: [ children: [
if (!isReady) NotReadyCard(), BrandText.body1('services.title'.tr()),
SizedBox(height: 24), SizedBox(height: 24),
...connected.map((service) => _Card(service: service)).toList(), if (!isReady) ...[NotReadyCard(), SizedBox(height: 24)],
if (uninitialized.isNotEmpty) ...[ ...ServiceTypes.values.map((t) => _Card(serviceType: t)).toList()
BrandText.body1('не подключены'),
SizedBox(height: 30),
],
...uninitialized.map((service) => _Card(service: service)).toList()
], ],
), ),
); );
@ -49,67 +48,329 @@ class _ServicesPageState extends State<ServicesPage> {
} }
class _Card extends StatelessWidget { class _Card extends StatelessWidget {
const _Card({Key key, @required this.service}) : super(key: key); const _Card({Key? key, required this.serviceType}) : super(key: key);
final Service service; final ServiceTypes serviceType;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String title; String title;
IconData iconData; IconData iconData;
String description; String subtitle;
switch (service.type) { switch (serviceType) {
case ServiceTypes.messanger:
iconData = BrandIcons.messanger;
title = 'Мессенджер';
description =
'Delta Chat. Если бы мне надо было обсудить что-то от чего зависит жизнь. Я бы выбрал Delta.Chat + свой почтовый сервер.';
break;
case ServiceTypes.mail: case ServiceTypes.mail:
iconData = BrandIcons.envelope; iconData = BrandIcons.envelope;
title = 'Почта'; title = 'services.mail.title'.tr();
description = 'Электронная почта для семьи или компании '; subtitle = 'services.mail.subtitle'.tr();
break;
case ServiceTypes.messenger:
iconData = BrandIcons.messanger;
title = 'services.messenger.title'.tr();
subtitle = 'services.messenger.subtitle'.tr();
break; break;
case ServiceTypes.passwordManager: case ServiceTypes.passwordManager:
iconData = BrandIcons.key; iconData = BrandIcons.key;
title = 'Менеджер паролей'; title = 'services.password_manager.title'.tr();
description = 'Надёжное хранилище для ваших паролей и ключей доступа'; subtitle = 'services.password_manager.subtitle'.tr();
break; break;
case ServiceTypes.github: case ServiceTypes.video:
iconData = BrandIcons.github; iconData = BrandIcons.webcam;
title = 'Git сервер'; title = 'services.video.title'.tr();
description = 'Сервис для приватного хранения своих разработок'; subtitle = 'services.video.subtitle'.tr();
break; break;
case ServiceTypes.cloud: case ServiceTypes.cloud:
iconData = BrandIcons.upload; iconData = BrandIcons.upload;
title = 'Файловое Облако'; title = 'services.cloud.title'.tr();
description = 'Сервис для доступа к вашим файлам в любой точке мира'; subtitle = 'services.cloud.subtitle'.tr();
break;
case ServiceTypes.socialNetwork:
iconData = BrandIcons.social;
title = 'services.social_network.title'.tr();
subtitle = 'services.social_network.subtitle'.tr();
break;
case ServiceTypes.git:
iconData = BrandIcons.git;
title = 'services.git.title'.tr();
subtitle = 'services.git.subtitle'.tr();
break; break;
} }
return BrandCard(
child: Column( var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
crossAxisAlignment: CrossAxisAlignment.start, var changeTab = context.read<ChangeTab>().onPress;
children: [ return GestureDetector(
IconStatusMask( onTap: () => showModalBottomSheet<void>(
status: service.state, context: context,
child: Icon(iconData, size: 30, color: Colors.white), isScrollControlled: true,
), backgroundColor: Colors.transparent,
SizedBox(height: 10), builder: (BuildContext context) {
BrandText.h2(title), return _ServiceDetails(
SizedBox(height: 10), serviceType: serviceType,
if (service.state == StateType.uninitialized) ...[ status: isReady ? StateType.stable : StateType.uninitialized,
BrandText.body1(description), title: title,
icon: iconData,
changeTab: changeTab,
);
},
),
child: BrandCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconStatusMask(
status: isReady ? StateType.stable : StateType.uninitialized,
child: Icon(iconData, size: 30, color: Colors.white),
),
SizedBox(height: 10),
BrandText.h2(title),
SizedBox(height: 10),
BrandText.body2(subtitle),
SizedBox(height: 10), SizedBox(height: 10),
BrandButton.text(
title: 'Подключить',
onPressed: () {
context.read<ServicesCubit>().connect(service);
})
], ],
if (service.state == StateType.stable) BrandText.body2('Подключен'), ),
],
), ),
); );
} }
} }
enum ServiceTypes {
mail,
messenger,
passwordManager,
video,
cloud,
socialNetwork,
git,
}
class _ServiceDetails extends StatelessWidget {
const _ServiceDetails({
Key? key,
required this.serviceType,
required this.icon,
required this.status,
required this.title,
required this.changeTab,
}) : super(key: key);
final ServiceTypes serviceType;
final IconData icon;
final StateType status;
final String title;
final ValueChanged<int> changeTab;
@override
Widget build(BuildContext context) {
late Widget child;
var config = context.watch<AppConfigCubit>().state;
var domainName = config.isDomainFilled
? config.cloudFlareDomain!.domainName!
: 'example.com';
var linksStyle = body1Style.copyWith(
fontSize: 15,
color: Theme.of(context).brightness == Brightness.dark
? Colors.white
: BrandColors.black,
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
// height: 1.1,
);
var textStyle = body1Style.copyWith(
color: Theme.of(context).brightness == Brightness.dark
? Colors.white
: BrandColors.black,
);
switch (serviceType) {
case ServiceTypes.mail:
child = RichText(
text: TextSpan(
children: [
TextSpan(
text: 'services.mail.bottom_sheet.1'.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
child: GestureDetector(
child: Text(
'services.mail.bottom_sheet.2'.tr(),
style: linksStyle,
),
onTap: () {
Navigator.of(context).pop();
changeTab(2);
},
),
),
),
],
));
break;
case ServiceTypes.messenger:
child = RichText(
text: TextSpan(
children: [
TextSpan(
text: 'services.messenger.bottom_sheet.1'.tr(args: [domainName]),
style: textStyle,
)
],
));
break;
case ServiceTypes.passwordManager:
child = RichText(
text: TextSpan(
children: [
TextSpan(
text: 'services.password_manager.bottom_sheet.1'
.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
child: GestureDetector(
onTap: () => _launchURL('https://password.$domainName'),
child: Text(
'password.$domainName',
style: linksStyle,
),
),
),
),
],
));
break;
case ServiceTypes.video:
child = RichText(
text: TextSpan(
children: [
TextSpan(
text: 'services.video.bottom_sheet.1'.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
child: GestureDetector(
onTap: () => _launchURL('https://meet.$domainName'),
child: Text(
'meet.$domainName',
style: linksStyle,
),
),
),
),
],
));
break;
case ServiceTypes.cloud:
child = RichText(
text: TextSpan(
children: [
TextSpan(
text: 'services.cloud.bottom_sheet.1'.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
child: GestureDetector(
onTap: () => _launchURL('https://cloud.$domainName'),
child: Text(
'cloud.$domainName',
style: linksStyle,
),
),
),
),
],
));
break;
case ServiceTypes.socialNetwork:
child = RichText(
text: TextSpan(
children: [
TextSpan(
text: 'services.social_network.bottom_sheet.1'
.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
child: GestureDetector(
onTap: () => _launchURL('https://social_network.$domainName'),
child: Text(
'social.$domainName',
style: linksStyle,
),
),
),
),
],
));
break;
case ServiceTypes.git:
child = RichText(
text: TextSpan(
children: [
TextSpan(
text: 'services.git.bottom_sheet.1'.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
child: GestureDetector(
onTap: () => _launchURL('https://git.$domainName'),
child: Text(
'git.$domainName',
style: linksStyle,
),
),
),
),
],
));
break;
}
return BrandModalSheet(
child: Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (_) {
return materialRoute(
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: brandPagePadding1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 13),
IconStatusMask(
status: status,
child: Icon(icon, size: 40, color: Colors.white),
),
SizedBox(height: 10),
BrandText.h1(title),
child,
],
),
)
],
),
);
},
),
);
}
void _launchURL(url) async =>
await canLaunch(url) ? await launch(url) : throw 'Could not launch $url';
}

View File

@ -1,9 +1,7 @@
part of 'users.dart'; part of 'users.dart';
class _NoUsers extends StatelessWidget { class _NoUsers extends StatelessWidget {
const _NoUsers({Key key, @required this.text}) const _NoUsers({Key? key, required this.text}) : super(key: key);
: assert(text != null),
super(key: key);
final String text; final String text;
@ -17,7 +15,7 @@ class _NoUsers extends StatelessWidget {
Icon(BrandIcons.users, size: 50, color: BrandColors.grey7), Icon(BrandIcons.users, size: 50, color: BrandColors.grey7),
SizedBox(height: 20), SizedBox(height: 20),
BrandText.h2( BrandText.h2(
'Здесь пока никого', 'users.nobody_here'.tr(),
style: TextStyle( style: TextStyle(
color: BrandColors.grey7, color: BrandColors.grey7,
), ),

View File

@ -1,7 +1,7 @@
part of 'users.dart'; part of 'users.dart';
class _Fab extends StatelessWidget { class _Fab extends StatelessWidget {
const _Fab({Key key}) : super(key: key); const _Fab({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -3,13 +3,18 @@ part of 'users.dart';
class _NewUser extends StatelessWidget { class _NewUser extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final usersCubit = context.watch<UsersCubit>(); var config = context.watch<AppConfigCubit>().state;
var domainName = config.isDomainFilled
? config.cloudFlareDomain!.domainName!
: 'example.com';
return BrandModalSheet( return BrandModalSheet(
child: BlocProvider( child: BlocProvider(
create: (context) => UserFormCubit(usersCubit: usersCubit), create: (context) =>
UserFormCubit(usersCubit: context.watch<UsersCubit>()),
child: Builder(builder: (context) { child: Builder(builder: (context) {
var formCubit = context.watch<UserFormCubit>(); var formCubitState = context.watch<UserFormCubit>().state;
return BlocListener<UserFormCubit, FormCubitState>( return BlocListener<UserFormCubit, FormCubitState>(
listener: (context, state) { listener: (context, state) {
@ -20,25 +25,27 @@ class _NewUser extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
BrandHeader(title: 'Новый пользователь'), BrandHeader(
title: 'users.new_user'.tr(),
),
SizedBox(width: 14), SizedBox(width: 14),
Padding( Padding(
padding: brandPagePadding2, padding: brandPagePadding2,
child: Column( child: Column(
children: [ children: [
CubitFormTextField( CubitFormTextField(
formFieldCubit: formCubit.login, formFieldCubit: context.read<UserFormCubit>().login,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Логин', labelText: 'users.login'.tr(),
suffixText: '@example', suffixText: '@$domainName',
), ),
), ),
SizedBox(height: 20), SizedBox(height: 20),
CubitFormTextField( CubitFormTextField(
formFieldCubit: formCubit.password, formFieldCubit: context.read<UserFormCubit>().password,
decoration: InputDecoration( decoration: InputDecoration(
alignLabelWithHint: false, alignLabelWithHint: false,
labelText: 'Пароль', labelText: 'basis.password'.tr(),
suffixIcon: Padding( suffixIcon: Padding(
padding: const EdgeInsets.only(right: 8), padding: const EdgeInsets.only(right: 8),
child: IconButton( child: IconButton(
@ -46,23 +53,21 @@ class _NewUser extends StatelessWidget {
BrandIcons.refresh, BrandIcons.refresh,
color: BrandColors.blue, color: BrandColors.blue,
), ),
onPressed: formCubit.genNewPassword, onPressed:
context.read<UserFormCubit>().genNewPassword,
), ),
), ),
), ),
), ),
SizedBox(height: 30), SizedBox(height: 30),
BrandButton.rised( BrandButton.rised(
onPressed: formCubit.state.isSubmitting onPressed: formCubitState.isSubmitting
? null ? null
: () { : () => context.read<UserFormCubit>().trySubmit(),
formCubit.trySubmit(); title: 'basis.create'.tr(),
},
title: 'Создать',
), ),
SizedBox(height: 40), SizedBox(height: 40),
Text( Text('users.new_user_info_note'.tr()),
'Новый пользователь автоматически получит доступ ко всем сервисам. Ещё какое-то описание.'),
SizedBox(height: 30), SizedBox(height: 30),
], ],
), ),

Some files were not shown because too many files have changed in this diff Show More