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"
SPEC CHECKSUMS:
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
wakelock: bfc7955c418d0db797614075aabbc58a39ab5107
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c

View File

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
location = "self:">
</FileRef>
</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_config/app_config_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';
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
Widget build(BuildContext context) {
@ -30,7 +29,6 @@ class BlocAndProviderConfig extends StatelessWidget {
lazy: false,
create: (_) => AppConfigCubit()..load(),
),
BlocProvider(create: (_) => ServicesCubit()),
BlocProvider(create: (_) => ProvidersCubit()),
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/utils/route_transitions/basic.dart';
import 'get_it_config.dart';
import './get_it_config.dart';
class SimpleBlocObserver extends BlocObserver {
SimpleBlocObserver();
@override
void onError(Cubit cubit, Object error, StackTrace stackTrace) {
final navigator = getIt.get<NavigationService>().navigator;
void onError(BlocBase cubit, Object error, StackTrace stackTrace) {
final navigator = getIt.get<NavigationService>().navigator!;
navigator.push(
materialRoute(

View File

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

View File

@ -32,7 +32,8 @@ class HiveConfig {
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 {
const Localization({
Key key,
Key? key,
this.child,
}) : super(key: key);
final Widget child;
final Widget? child;
@override
Widget build(BuildContext context) {
return EasyLocalization(
preloaderColor: Colors.black,
supportedLocales: [Locale('ru'), Locale('en')],
path: 'assets/translations',
fallbackLocale: Locale('en'),
fallbackLocale: Locale('ru'),
saveLocale: false,
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:google_fonts/google_fonts.dart';
import 'package:selfprivacy/utils/named_font_weight.dart';
import 'brand_colors.dart';
final defaultTextStyle = GoogleFonts.inter(
textStyle: TextStyle(
fontSize: 15,
color: BrandColors.textColor1,
),
final defaultTextStyle = TextStyle(
fontSize: 15,
color: BrandColors.textColor1,
);
final headline1Style = GoogleFonts.inter(
final headline1Style = defaultTextStyle.copyWith(
fontSize: 40,
fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor,
);
final headline2Style = GoogleFonts.inter(
final headline2Style = defaultTextStyle.copyWith(
fontSize: 24,
fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor,
);
final onboardingTitle = GoogleFonts.inter(
final onboardingTitle = defaultTextStyle.copyWith(
fontSize: 30,
fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor,
);
final headline3Style = GoogleFonts.inter(
final headline3Style = defaultTextStyle.copyWith(
fontSize: 20,
fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor,
);
final headline4Style = GoogleFonts.inter(
final headline4Style = defaultTextStyle.copyWith(
fontSize: 18,
fontWeight: NamedFontWeight.medium,
color: BrandColors.headlineColor,
@ -59,16 +56,12 @@ final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45);
final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue);
final progressTextStyleLight = GoogleFonts.inter(
textStyle: TextStyle(
fontSize: 11,
color: BrandColors.textColor1,
),
final progressTextStyleLight = TextStyle(
fontSize: 11,
color: BrandColors.textColor1,
);
final progressTextStyleDark = GoogleFonts.inter(
textStyle: TextStyle(
fontSize: 11,
color: BrandColors.white,
),
final progressTextStyleDark = TextStyle(
fontSize: 11,
color: BrandColors.white,
);

View File

@ -18,9 +18,9 @@ abstract class ApiMap {
};
loggedClient = client;
}
String rootAddress;
String? rootAddress;
Dio loggedClient;
late Dio loggedClient;
void close() {
loggedClient.close();
@ -33,37 +33,46 @@ class ConsoleInterceptor extends InterceptorsWrapper {
}
@override
Future onRequest(RequestOptions options) async {
Future onRequest(
RequestOptions options,
RequestInterceptorHandler requestInterceptorHandler,
) async {
addMessage(
Message(
text:
'request-uri: ${options.uri}\nheaders: ${options.headers}\ndata: ${options.data}',
),
);
return super.onRequest(options);
return super.onRequest(options, requestInterceptorHandler);
}
@override
Future onResponse(Response response) async {
Future onResponse(
Response response,
ResponseInterceptorHandler requestInterceptorHandler,
) async {
addMessage(
Message(
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
Future onError(DioError err) async {
Future onError(DioError err, ErrorInterceptorHandler handler) async {
var response = err.response;
log(err.toString());
addMessage(
Message.warn(
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';
class BackblazeApi extends ApiMap {
BackblazeApi([String token]) {
BackblazeApi([String? token]) {
if (token != null) {
loggedClient.options = BaseOptions(
headers: {'Authorization': 'Basic $token'},
baseUrl: rootAddress,
baseUrl: rootAddress!,
);
}
}
@override
String rootAddress =
String? rootAddress =
'https://api.backblazeb2.com/b2api/v2/b2_authorize_account';
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) {
return true;

View File

@ -5,7 +5,7 @@ import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/dns_records.dart';
class CloudflareApi extends ApiMap {
CloudflareApi([String token]) {
CloudflareApi([String? token]) {
if (token != null) {
loggedClient.options =
BaseOptions(headers: {'Authorization': 'Bearer $token'});
@ -13,7 +13,7 @@ class CloudflareApi extends ApiMap {
}
@override
String rootAddress = 'https://api.cloudflare.com/client/v4';
String? rootAddress = 'https://api.cloudflare.com/client/v4';
Future<bool> isValid(String token) async {
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 options = Options(
@ -59,8 +59,8 @@ class CloudflareApi extends ApiMap {
}
Future<void> removeSimilarRecords({
String ip4,
CloudFlareDomain cloudFlareDomain,
String? ip4,
required CloudFlareDomain cloudFlareDomain,
}) async {
var domainName = cloudFlareDomain.domainName;
var domainZoneId = cloudFlareDomain.zoneId;
@ -82,8 +82,8 @@ class CloudflareApi extends ApiMap {
}
Future<void> createMultipleDnsRecords({
String ip4,
CloudFlareDomain cloudFlareDomain,
String? ip4,
required CloudFlareDomain cloudFlareDomain,
}) async {
var domainName = cloudFlareDomain.domainName;
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 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 response = await loggedClient.get(
url,
@ -169,7 +169,7 @@ class CloudflareApi extends ApiMap {
);
return response.data['result']
.map<String>((el) => el['name'] as String)
.map<String>((el) => el['name'] as String?)
.toList();
}
}

View File

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

View File

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

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:selfprivacy/logic/models/backblaze_credential.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 'app_config_repository.dart';
export 'package:provider/provider.dart';
part 'app_config_state.dart';
@ -64,23 +64,23 @@ class AppConfigCubit extends Cubit<AppConfigState> {
}
void startServerIfDnsIsOkay({
AppConfigState state,
AppConfigState? state,
bool isImmediate = false,
}) async {
state = state ?? this.state;
final work = () async {
emit(TimerState(dataState: state, isLoading: true));
emit(TimerState(dataState: state!, isLoading: true));
var ip4 = state.hetznerServer.ip4;
var domainName = state.cloudFlareDomain.domainName;
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,
state.hetznerServer!,
);
repository.saveServerDetails(server);
emit(
@ -111,16 +111,16 @@ class AppConfigCubit extends Cubit<AppConfigState> {
}
void resetServerIfServerIsOkay({
AppConfigState state,
AppConfigState? state,
bool isImmediate = false,
}) async {
state = state ?? this.state;
var work = () async {
emit(TimerState(dataState: state, isLoading: true));
emit(TimerState(dataState: state!, isLoading: true));
var isServerWorking = await repository.isHttpServerWorking(
state.cloudFlareDomain.domainName,
state.cloudFlareDomain!.domainName,
);
if (isServerWorking) {
@ -133,8 +133,8 @@ class AppConfigCubit extends Cubit<AppConfigState> {
));
timer = Timer(pauseDuration, () async {
var hetznerServerDetails = await repository.restart(
state.hetznerKey,
state.hetznerServer,
state!.hetznerKey,
state.hetznerServer!,
);
emit(
state.copyWith(
@ -165,19 +165,19 @@ class AppConfigCubit extends Cubit<AppConfigState> {
}
}
Timer timer;
Timer? timer;
void finishCheckIfServerIsOkay({
AppConfigState state,
AppConfigState? state,
bool isImmediate = false,
}) async {
state = state ?? this.state;
var work = () async {
emit(TimerState(dataState: state, isLoading: true));
emit(TimerState(dataState: state!, isLoading: true));
var isServerWorking = await repository.isHttpServerWorking(
state.cloudFlareDomain.domainName,
state.cloudFlareDomain!.domainName,
);
if (isServerWorking) {
@ -238,12 +238,12 @@ class AppConfigCubit extends Cubit<AppConfigState> {
}
void createServerAndSetDnsRecords() async {
var _stateCopy = state;
AppConfigState _stateCopy = state;
var onSuccess = (serverDetails) async {
await repository.createDnsRecords(
state.cloudFlareKey,
serverDetails.ip4,
state.cloudFlareDomain,
state.cloudFlareDomain!,
);
emit(state.copyWith(
@ -259,8 +259,8 @@ class AppConfigCubit extends Cubit<AppConfigState> {
emit(state.copyWith(isLoading: true));
await repository.createServer(
state.hetznerKey,
state.rootUser,
state.cloudFlareDomain.domainName,
state.rootUser!,
state.cloudFlareDomain!.domainName,
state.cloudFlareKey,
onCancel: onCancel,
onSuccess: onSuccess,
@ -277,54 +277,8 @@ class AppConfigCubit extends Cubit<AppConfigState> {
}
void _closeTimer() {
if (timer != null && timer.isActive) {
timer.cancel();
if (timer != null && timer!.isActive) {
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(
String hetznerKey,
String? hetznerKey,
HetznerServerDetails hetznerServer,
) async {
var hetznerApi = HetznerApi(hetznerKey);
@ -75,7 +75,7 @@ class AppConfigRepository {
await box.put(BNames.hetznerServer, serverDetails);
}
Future<bool> isDnsAddressesMatch(String domainName, String ip4) async {
Future<bool> isDnsAddressesMatch(String? domainName, String? ip4) async {
print(domainName);
var addresses = <String>[
'$domainName',
@ -116,12 +116,12 @@ class AppConfigRepository {
}
Future<void> createServer(
String hetznerKey,
String? hetznerKey,
User rootUser,
String domainName,
String cloudFlareKey, {
void Function() onCancel,
Future<void> Function(HetznerServerDetails serverDetails) onSuccess,
String? domainName,
String? cloudFlareKey, {
void Function()? onCancel,
required Future<void> Function(HetznerServerDetails serverDetails) onSuccess,
}) async {
var hetznerApi = HetznerApi(hetznerKey);
@ -135,7 +135,7 @@ class AppConfigRepository {
hetznerApi.close();
onSuccess(serverDetails);
} 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>();
nav.showPopUpDialog(
BrandAlert(
@ -165,7 +165,7 @@ class AppConfigRepository {
text: 'Отменить',
onPressed: () {
hetznerApi.close();
onCancel();
onCancel!();
},
),
],
@ -176,8 +176,8 @@ class AppConfigRepository {
}
Future<void> createDnsRecords(
String cloudFlareKey,
String ip4,
String? cloudFlareKey,
String? ip4,
CloudFlareDomain cloudFlareDomain,
) async {
var cloudflareApi = CloudflareApi(cloudFlareKey);
@ -195,7 +195,7 @@ class AppConfigRepository {
cloudflareApi.close();
}
Future<bool> isHttpServerWorking(String domainName) async {
Future<bool> isHttpServerWorking(String? domainName) async {
var api = ServerApi(domainName);
var isHttpServerWorking = await api.isHttpServerWorking();
api.close();
@ -203,7 +203,7 @@ class AppConfigRepository {
}
Future<HetznerServerDetails> restart(
String hetznerKey,
String? hetznerKey,
HetznerServerDetails server,
) async {
var hetznerApi = HetznerApi(hetznerKey);

View File

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

View File

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

View File

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

View File

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

View File

@ -30,11 +30,11 @@ class CloudFlareFormCubit extends FormCubit {
final AppConfigCubit initializingCubit;
FieldCubit<String> apiKey;
late final FieldCubit<String> apiKey;
@override
FutureOr<bool> asyncValidation() async {
bool isKeyValid;
late bool isKeyValid;
try {
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> {
DomainSetupCubit(this.initializingCubit) : super(Initial()) {
var token = (initializingCubit.state.cloudFlareKey);
var token = initializingCubit.state.cloudFlareKey;
assert(token != null, 'no cloudflare token');
@ -13,11 +13,11 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
}
AppConfigCubit initializingCubit;
CloudflareApi api;
late CloudflareApi api;
Future<void> load() async {
emit(Loading(LoadingTypes.loadingDomain));
var list = await api.domainList();
var list = await (api.domainList() as Future<List<String>>);
if (list.isEmpty) {
emit(Empty());
} else if (list.length == 1) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,10 +9,10 @@ class BackblazeCredential {
BackblazeCredential({this.keyId, this.applicationKey});
@HiveField(0)
final String keyId;
final String? keyId;
@HiveField(1)
final String applicationKey;
final String? 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 encodedApiKey = base64.encode(utf8.encode(_apiKey));
return encodedApiKey;

View File

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

View File

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

View File

@ -17,8 +17,8 @@ class CloudFlareDomainAdapter extends TypeAdapter<CloudFlareDomain> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return CloudFlareDomain(
domainName: fields[0] as String,
zoneId: fields[1] as String,
domainName: fields[0] 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';
part 'dns_records.g.dart';
@ -6,17 +5,17 @@ part 'dns_records.g.dart';
@JsonSerializable(createToJson: true, createFactory: false)
class DnsRecords {
DnsRecords({
@required this.type,
@required this.name,
@required this.content,
required this.type,
required this.name,
required this.content,
this.ttl = 3600,
this.priority = 10,
this.proxied = false,
});
final String type;
final String name;
final String content;
final String? name;
final String? content;
final int ttl;
final int priority;
final bool proxied;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,6 +22,7 @@ void main() async {
Wakelock.enable();
getItSetup();
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
runApp(
Localization(
@ -35,7 +36,7 @@ void main() async {
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appSettings = context.watch<AppSettingsCubit>().state;
AppSettingsState appSettings = context.watch<AppSettingsCubit>().state;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
@ -50,12 +51,12 @@ class MyApp extends StatelessWidget {
home: appSettings.isOnbordingShowing
? OnboardingPage(nextPage: InitializingPage())
: RootPage(),
builder: (BuildContext context, Widget widget) {
builder: (BuildContext context, Widget? widget) {
Widget error = Text('...rendering error...');
if (widget is Scaffold || widget is Navigator)
error = Scaffold(body: Center(child: 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 {
const ActionButton({
Key key,
Key? key,
this.text,
this.onPressed,
this.isRed = false,
}) : super(key: key);
final VoidCallback onPressed;
final String text;
final VoidCallback? onPressed;
final String? text;
final bool isRed;
@override
@ -19,12 +19,12 @@ class ActionButton extends StatelessWidget {
return TextButton(
child: Text(
text,
text!,
style: isRed ? TextStyle(color: BrandColors.red1) : null,
),
onPressed: () {
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 {
BrandAlert({
Key key,
String title,
String contentText,
List<Widget> acitons,
Key? key,
String? title,
String? contentText,
List<Widget>? acitons,
}) : super(
key: key,
title: title != null ? Text(title) : null,
content: title != null ? Text(contentText) : null,
content: title != null ? Text(contentText!) : null,
actions: acitons,
);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/// 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.
///
/// To use this font, place it in your fonts/ directory and include the
@ -19,7 +19,7 @@ class BrandIcons {
BrandIcons._();
static const _kFontFam = 'BrandIcons';
static const _kFontPkg = null;
static const String? _kFontPkg = null;
static const IconData connection =
IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
@ -39,14 +39,22 @@ class BrandIcons {
IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData check =
IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData webcam =
IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData refresh =
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 =
IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData share =
IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData triangle =
IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData engineer =
IconData(0xe810, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData server =
IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData box =
@ -59,8 +67,16 @@ class BrandIcons {
IconData(0xe815, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData upload =
IconData(0xe816, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData github =
IconData(0xe817, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData arrow_left =
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 {
const BrandModalSheet({
Key key,
Key? key,
this.child,
}) : super(key: key);
final Widget child;
final Widget? child;
@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
@ -43,18 +43,17 @@ class BrandModalSheet extends StatelessWidget {
),
),
Container(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - 32 - 4,
maxHeight: MediaQuery.of(context).size.height,
),
decoration: BoxDecoration(
borderRadius:
BorderRadius.vertical(top: Radius.circular(20)),
color: Theme.of(context).scaffoldBackgroundColor,
),
width: double.infinity,
child: child
),
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - 132,
maxHeight: MediaQuery.of(context).size.height - 132,
),
decoration: BoxDecoration(
borderRadius:
BorderRadius.vertical(top: Radius.circular(20)),
color: Theme.of(context).scaffoldBackgroundColor,
),
width: double.infinity,
child: child),
],
),
),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
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 StackTrace stackTrace;
final Object? error;
final StackTrace? stackTrace;
@override
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';
class IconStatusMask extends StatelessWidget {
IconStatusMask({this.child, this.status});
IconStatusMask({required this.child, required this.status});
final Icon child;
final StateType status;
@override
Widget build(BuildContext context) {
List<Color> colors;
late List<Color> colors;
switch (status) {
case StateType.uninitialized:
colors = BrandColors.uninitializedGradientColors;

View File

@ -1,10 +1,12 @@
import 'package:flutter/material.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/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
class NotReadyCard extends StatelessWidget {
const NotReadyCard({Key key}) : super(key: key);
const NotReadyCard({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -16,29 +18,34 @@ class NotReadyCard extends StatelessWidget {
text: TextSpan(
children: [
TextSpan(
text: 'Завершите настройку приложения используя ',
text: 'not_ready_card.1'.tr(),
style: TextStyle(color: BrandColors.white),
),
WidgetSpan(
child: GestureDetector(
child: Text(
'Мастер подключения',
style: TextStyle(
child: Padding(
padding: const EdgeInsets.only(bottom: 0.5),
child: GestureDetector(
onTap: () => Navigator.of(context).push(
materialRoute(
InitializingPage(),
),
),
child: Text(
'not_ready_card.2'.tr(),
style: body1Style.copyWith(
color: Theme.of(context).brightness == Brightness.dark
? Colors.blueAccent
? Colors.black
: BrandColors.white,
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline),
),
onTap: () => Navigator.of(context).push(
materialRoute(
InitializingPage(),
decoration: TextDecoration.underline,
// height: 1.1,
),
),
),
),
),
TextSpan(
text: ' для продолжения работы',
text: 'not_ready_card.3'.tr(),
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 {
ProgressBar({
Key key,
@required this.steps,
@required this.activeIndex,
Key? key,
required this.steps,
required this.activeIndex,
}) : super(key: key);
final int activeIndex;
@ -102,14 +102,14 @@ class _ProgressBarState extends State<ProgressBar> {
}
Expanded _stepTitle({
int index,
TextStyle style,
String step,
required int index,
TextStyle? style,
String? step,
}) {
var isActive = 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(
flex: 2,
child: RichText(

View File

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

View File

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

View File

@ -1,48 +1,24 @@
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_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 {
const AboutPage({Key key}) : super(key: key);
const AboutPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PreferredSize(
child: BrandHeader(title: 'О проекте', hasBackButton: true),
child: BrandHeader(
title: 'more.about_project'.tr(), hasBackButton: true),
preferredSize: Size.fromHeight(52),
),
body: ListView(
padding: brandPagePadding2,
children: [
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),
],
body: Container(
child: BrandMarkdown(
fileName: 'about',
),
),
),
);

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_text/brand_text.dart';
import 'package:selfprivacy/utils/named_font_weight.dart';
import 'package:easy_localization/easy_localization.dart';
class AppSettingsPage extends StatefulWidget {
const AppSettingsPage({Key key}) : super(key: key);
const AppSettingsPage({Key? key}) : super(key: key);
@override
_AppSettingsPageState createState() => _AppSettingsPageState();
@ -20,16 +21,14 @@ class AppSettingsPage extends StatefulWidget {
class _AppSettingsPageState extends State<AppSettingsPage> {
@override
Widget build(BuildContext context) {
var appSettings = context.watch<AppSettingsCubit>();
var isDarkModeOn = appSettings.state.isDarkModeOn;
var isDarkModeOn = context.watch<AppSettingsCubit>().state.isDarkModeOn;
return SafeArea(
child: Builder(builder: (context) {
return Scaffold(
appBar: PreferredSize(
child:
BrandHeader(title: 'Настройки приложения', hasBackButton: true),
BrandHeader(title: 'more.settings'.tr(), hasBackButton: true),
preferredSize: Size.fromHeight(52),
),
body: ListView(
@ -57,8 +56,9 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
activeColor: BrandColors.green1,
activeTrackColor: BrandColors.green2,
value: Theme.of(context).brightness == Brightness.dark,
onChanged: (value) => appSettings.updateDarkMode(
isDarkModeOn: !isDarkModeOn),
onChanged: (value) => context
.read<AppSettingsCubit>()
.updateDarkMode(isDarkModeOn: !isDarkModeOn),
),
],
),
@ -80,8 +80,10 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
),
),
SizedBox(width: 5),
RaisedButton(
color: BrandColors.red1,
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: BrandColors.red1,
),
child: Text(
'Reset',
style: TextStyle(
@ -92,24 +94,26 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
onPressed: () {
showDialog(
context: context,
child: BrandAlert(
title: 'Вы уверенны',
contentText: 'Сбросить все ключи?',
acitons: [
ActionButton(
text: 'Да, сбросить',
isRed: true,
onPressed: () {
context
.read<AppConfigCubit>()
.clearAppConfig();
Navigator.of(context).pop();
}),
ActionButton(
text: 'Отмена',
),
],
),
builder: (_) {
return BrandAlert(
title: 'Вы уверенны',
contentText: 'Сбросить все ключи?',
acitons: [
ActionButton(
text: 'Да, сбросить',
isRed: true,
onPressed: () {
context
.read<AppConfigCubit>()
.clearAppConfig();
Navigator.of(context).pop();
}),
ActionButton(
text: 'Отмена',
),
],
);
},
);
},
)
@ -126,9 +130,9 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
class _TextColumn extends StatelessWidget {
const _TextColumn({
Key key,
@required this.title,
@required this.value,
Key? key,
required this.title,
required this.value,
this.hasWarning = false,
}) : 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';
class Console extends StatefulWidget {
const Console({Key key}) : super(key: key);
const Console({Key? key}) : super(key: key);
@override
_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_text/brand_text.dart';
import 'package:package_info/package_info.dart';
import 'package:easy_localization/easy_localization.dart';
class InfoPage extends StatelessWidget {
const InfoPage({Key key}) : super(key: key);
const InfoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PreferredSize(
child: BrandHeader(title: 'О приложении', hasBackButton: true),
child: BrandHeader(title: 'more.about_app'.tr(), hasBackButton: true),
preferredSize: Size.fromHeight(52),
),
body: ListView(
@ -24,8 +25,8 @@ class InfoPage extends StatelessWidget {
FutureBuilder(
future: _version(),
builder: (context, snapshot) {
return BrandText.body1(
'Тут любая служебная информация, v.${snapshot.data}');
return BrandText.body1('more.about_app_page.text'
.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/rootRoute.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
import 'about/about.dart';
import 'app_settings/app_setting.dart';
@ -16,13 +17,13 @@ import 'console/console.dart';
import 'info/info.dart';
class MorePage extends StatelessWidget {
const MorePage({Key key}) : super(key: key);
const MorePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
child: BrandHeader(title: 'Еще'),
child: BrandHeader(title: 'basis.more'.tr()),
preferredSize: Size.fromHeight(52),
),
body: ListView(
@ -33,33 +34,33 @@ class MorePage extends StatelessWidget {
children: [
BrandDivider(),
_NavItem(
title: 'Мастер Подключения',
iconData: BrandIcons.settings,
title: 'more.configuration_wizard'.tr(),
iconData: BrandIcons.triangle,
goTo: InitializingPage(),
),
_NavItem(
title: 'Настройки приложения',
title: 'more.settings'.tr(),
iconData: BrandIcons.settings,
goTo: AppSettingsPage(),
),
_NavItem(
title: 'О проекте Selfprivacy',
iconData: BrandIcons.triangle,
title: 'more.about_project'.tr(),
iconData: BrandIcons.engineer,
goTo: AboutPage(),
),
_NavItem(
title: 'О приложении',
iconData: BrandIcons.help,
title: 'more.about_app'.tr(),
iconData: BrandIcons.fire,
goTo: InfoPage(),
),
_NavItem(
title: 'Onboarding',
iconData: BrandIcons.triangle,
title: 'more.onboarding'.tr(),
iconData: BrandIcons.start,
goTo: OnboardingPage(nextPage: RootPage()),
),
_NavItem(
title: 'Console',
iconData: BrandIcons.triangle,
title: 'more.console'.tr(),
iconData: BrandIcons.terminal,
goTo: Console(),
),
],
@ -73,10 +74,10 @@ class MorePage extends StatelessWidget {
class _NavItem extends StatelessWidget {
const _NavItem({
Key key,
@required this.iconData,
@required this.goTo,
@required this.title,
Key? key,
required this.iconData,
required this.goTo,
required this.title,
}) : super(key: key);
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_text/brand_text.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
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;
@override
@ -54,10 +55,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
children: [
SizedBox(height: 30),
BrandText.h2(
'Цифровая независимость и приватность, доступная каждому'),
'onboarding.page1_title'.tr(),
),
SizedBox(height: 20),
BrandText.body2(
'Почта и мессенджер с открытым исходным кодом на вашем личном сервере под вашим полным контролем.'),
BrandText.body2('onboarding.page1_text'.tr()),
Flexible(
child: Center(
child: Image.asset(
@ -78,7 +79,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
curve: Curves.easeIn,
);
},
title: 'Далее',
title: 'basis.next'.tr(),
),
SizedBox(height: 30),
],
@ -94,10 +95,9 @@ class _OnboardingPageState extends State<OnboardingPage> {
child: Column(
children: [
SizedBox(height: 30),
BrandText.h2('Для работы понадобятся ваши аккаунты'),
BrandText.h2('onboarding.page2_title'.tr()),
SizedBox(height: 20),
BrandText.body2(
'Для максимальноей приватности и независимости SelfPrivacy не использует свои серверы. \n \n Если у вас нет домена, аккаунтов на Hetzner, AWS и Clouflare, мы поможем их создать и подключить.'),
BrandText.body2('onboarding.page2_text'.tr()),
SizedBox(height: 20),
Center(
child: Image.asset(
@ -127,7 +127,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
Navigator.of(context)
.pushReplacement(materialRoute(widget.nextPage));
},
title: 'Понял',
title: 'basis.got_it'.tr(),
),
SizedBox(height: 30),
],
@ -137,10 +137,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
}
String _fileName({
@required BuildContext context,
@required String path,
@required String fileName,
@required String fileExtention,
required BuildContext context,
required String path,
required String fileName,
required String fileExtention,
}) {
var theme = Theme.of(context);
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/pages/providers/settings/settings.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
class ProvidersPage extends StatefulWidget {
ProvidersPage({Key key}) : super(key: key);
ProvidersPage({Key? key}) : super(key: key);
@override
_ProvidersPageState createState() => _ProvidersPageState();
@ -32,7 +33,7 @@ class _ProvidersPageState extends State<ProvidersPage> {
.toList();
return Scaffold(
appBar: PreferredSize(
child: BrandHeader(title: 'Провайдеры'),
child: BrandHeader(title: 'providers.page_title'.tr()),
preferredSize: Size.fromHeight(52),
),
body: ListView(
@ -50,32 +51,31 @@ class _ProvidersPageState extends State<ProvidersPage> {
}
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;
@override
Widget build(BuildContext context) {
String title;
String message;
String stableText;
var appConfig = context.watch<AppConfigCubit>().state;
String? title;
String? message;
String? stableText;
AppConfigState appConfig = context.watch<AppConfigCubit>().state;
var domainName =
appConfig.isDomainFilled ? appConfig.cloudFlareDomain.domainName : '';
appConfig.isDomainFilled ? appConfig.cloudFlareDomain!.domainName : '';
switch (provider.type) {
case ProviderType.server:
title = 'Сервер';
title = 'providers.server.card_title'.tr();
stableText = 'В норме';
break;
case ProviderType.domain:
title = 'Домен';
title = 'providers.domain.card_title'.tr();
message = domainName;
stableText = 'Домен настроен';
break;
case ProviderType.backup:
// message = '22 янв 2021 14:30';
title = 'Резервное копирование';
title = 'providers.backup.card_title'.tr();
stableText = 'В норме';
break;
}
@ -116,28 +116,56 @@ class _Card extends StatelessWidget {
class _ProviderDetails extends StatelessWidget {
const _ProviderDetails({
Key key,
@required this.provider,
@required this.statusText,
Key? key,
required this.provider,
required this.statusText,
}) : super(key: key);
final ProviderModel provider;
final String statusText;
final String? statusText;
@override
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) {
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;
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;
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;
}
return BrandModalSheet(
@ -163,7 +191,7 @@ class _ProviderDetails extends StatelessWidget {
onSelected: (_PopupMenuItemType result) {
switch (result) {
case _PopupMenuItemType.setting:
navigatorKey.currentState
navigatorKey.currentState!
.push(materialRoute(SettingsPage()));
break;
}
@ -174,7 +202,7 @@ class _ProviderDetails extends StatelessWidget {
value: _PopupMenuItemType.setting,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('Настройки'),
child: Text('basis.settings'.tr()),
),
),
],
@ -182,11 +210,10 @@ class _ProviderDetails extends StatelessWidget {
),
),
Padding(
padding: brandPagePadding1,
padding: brandPagePadding2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 13),
IconStatusMask(
status: provider.state,
child:
@ -195,11 +222,7 @@ class _ProviderDetails extends StatelessWidget {
SizedBox(height: 10),
BrandText.h1(title),
SizedBox(height: 10),
BrandText.body1(statusText),
SizedBox(
height: 20,
),
Text('Статусы сервера и сервис провайдера и т.д.')
...children
],
),
)

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

View File

@ -1,5 +1,6 @@
import 'package:flutter/cupertino.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/pages/more/more.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';
class RootPage extends StatefulWidget {
const RootPage({Key key}) : super(key: key);
const RootPage({Key? key}) : super(key: key);
@override
_RootPageState createState() => _RootPageState();
@ -15,7 +16,7 @@ class RootPage extends StatefulWidget {
class _RootPageState extends State<RootPage>
with SingleTickerProviderStateMixin {
TabController tabController;
late TabController tabController;
@override
void initState() {
@ -33,14 +34,17 @@ class _RootPageState extends State<RootPage>
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: TabBarView(
controller: tabController,
children: [
ProvidersPage(),
ServicesPage(),
UsersPage(),
MorePage(),
],
body: Provider<ChangeTab>(
create: (_) => ChangeTab(tabController.animateTo),
child: TabBarView(
controller: tabController,
children: [
ProvidersPage(),
ServicesPage(),
UsersPage(),
MorePage(),
],
),
),
bottomNavigationBar: BrandTabBar(
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:selfprivacy/config/brand_colors.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/services/services_cubit.dart';
import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
import 'package:selfprivacy/logic/models/state_types.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_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/icon_status_mask/icon_status_mask.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 {
ServicesPage({Key key}) : super(key: key);
ServicesPage({Key? key}) : super(key: key);
@override
_ServicesPageState createState() => _ServicesPageState();
@ -21,27 +27,20 @@ class ServicesPage extends StatefulWidget {
class _ServicesPageState extends State<ServicesPage> {
@override
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;
return Scaffold(
appBar: PreferredSize(
child: BrandHeader(title: 'Сервисы'),
child: BrandHeader(title: 'basis.services'.tr()),
preferredSize: Size.fromHeight(52),
),
body: ListView(
padding: brandPagePadding2,
children: [
if (!isReady) NotReadyCard(),
BrandText.body1('services.title'.tr()),
SizedBox(height: 24),
...connected.map((service) => _Card(service: service)).toList(),
if (uninitialized.isNotEmpty) ...[
BrandText.body1('не подключены'),
SizedBox(height: 30),
],
...uninitialized.map((service) => _Card(service: service)).toList()
if (!isReady) ...[NotReadyCard(), SizedBox(height: 24)],
...ServiceTypes.values.map((t) => _Card(serviceType: t)).toList()
],
),
);
@ -49,67 +48,329 @@ class _ServicesPageState extends State<ServicesPage> {
}
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
Widget build(BuildContext context) {
String title;
IconData iconData;
String description;
String subtitle;
switch (service.type) {
case ServiceTypes.messanger:
iconData = BrandIcons.messanger;
title = 'Мессенджер';
description =
'Delta Chat. Если бы мне надо было обсудить что-то от чего зависит жизнь. Я бы выбрал Delta.Chat + свой почтовый сервер.';
break;
switch (serviceType) {
case ServiceTypes.mail:
iconData = BrandIcons.envelope;
title = 'Почта';
description = 'Электронная почта для семьи или компании ';
title = 'services.mail.title'.tr();
subtitle = 'services.mail.subtitle'.tr();
break;
case ServiceTypes.messenger:
iconData = BrandIcons.messanger;
title = 'services.messenger.title'.tr();
subtitle = 'services.messenger.subtitle'.tr();
break;
case ServiceTypes.passwordManager:
iconData = BrandIcons.key;
title = 'Менеджер паролей';
description = 'Надёжное хранилище для ваших паролей и ключей доступа';
title = 'services.password_manager.title'.tr();
subtitle = 'services.password_manager.subtitle'.tr();
break;
case ServiceTypes.github:
iconData = BrandIcons.github;
title = 'Git сервер';
description = 'Сервис для приватного хранения своих разработок';
case ServiceTypes.video:
iconData = BrandIcons.webcam;
title = 'services.video.title'.tr();
subtitle = 'services.video.subtitle'.tr();
break;
case ServiceTypes.cloud:
iconData = BrandIcons.upload;
title = 'Файловое Облако';
description = 'Сервис для доступа к вашим файлам в любой точке мира';
title = 'services.cloud.title'.tr();
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;
}
return BrandCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconStatusMask(
status: service.state,
child: Icon(iconData, size: 30, color: Colors.white),
),
SizedBox(height: 10),
BrandText.h2(title),
SizedBox(height: 10),
if (service.state == StateType.uninitialized) ...[
BrandText.body1(description),
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
var changeTab = context.read<ChangeTab>().onPress;
return GestureDetector(
onTap: () => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _ServiceDetails(
serviceType: serviceType,
status: isReady ? StateType.stable : StateType.uninitialized,
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),
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';
class _NoUsers extends StatelessWidget {
const _NoUsers({Key key, @required this.text})
: assert(text != null),
super(key: key);
const _NoUsers({Key? key, required this.text}) : super(key: key);
final String text;
@ -17,7 +15,7 @@ class _NoUsers extends StatelessWidget {
Icon(BrandIcons.users, size: 50, color: BrandColors.grey7),
SizedBox(height: 20),
BrandText.h2(
'Здесь пока никого',
'users.nobody_here'.tr(),
style: TextStyle(
color: BrandColors.grey7,
),

View File

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

View File

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

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