Merge pull request 'translations' (#3) from translations into master
Reviewed-on: https://git.selfprivacy.org/kherel/selfprivacy.org.app/pulls/3
2
.gitignore
vendored
|
@ -18,7 +18,7 @@
|
|||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
|
|
13
.vscode/launch.json
vendored
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "selfprivacy",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
}
|
||||
]
|
||||
}
|
BIN
assets/fonts/Inter-Bold.ttf
Normal file
BIN
assets/fonts/Inter-ExtraBold.ttf
Normal file
BIN
assets/fonts/Inter-Medium.ttf
Normal file
BIN
assets/fonts/Inter-Regular.ttf
Normal file
BIN
assets/fonts/Inter-SemiBold.ttf
Normal file
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 149 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 177 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 141 KiB |
12
assets/markdown/about-en.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
### О проекте
|
||||
|
||||
Всё больше организаций хотят владеть нашими данными
|
||||
А мы сами хотим распоряжаться своими **данными** на своем сервере.
|
||||
|
||||
### Миссия проекта
|
||||
|
||||
Цифровая независимость и приватность доступная каждому
|
||||
|
||||
### Цель
|
||||
|
||||
Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких
|
12
assets/markdown/about-ru.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
### О проекте
|
||||
|
||||
Всё больше организаций хотят владеть нашими данными
|
||||
А мы сами хотим распоряжаться своими **данными** на своем сервере.
|
||||
|
||||
### Миссия проекта
|
||||
|
||||
Цифровая независимость и приватность доступная каждому
|
||||
|
||||
### Цель
|
||||
|
||||
Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких
|
7
assets/markdown/how_hetzner-en.md
Normal 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, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.
|
7
assets/markdown/how_hetzner-ru.md
Normal 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, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.
|
|
@ -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": "Добавьте первого пользователя",
|
||||
"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": "\n"
|
||||
}
|
||||
}
|
|
@ -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, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет."
|
||||
}
|
||||
}
|
16
fastlane/metadata/android/en-US/full_description.txt
Normal file
|
@ -0,0 +1,16 @@
|
|||
SelfPrivacy - is a platform on your cloud hosting, that allows to deploy your own private services and control them using mobile application.
|
||||
To use this application, you'll be required to create accounts of different service providers. Please reffer to this manual: https://hugo.selfprivacy.org/posts/getting_started
|
||||
Application will do the following things for you:
|
||||
1. Create your personal server
|
||||
2. Setup NixOS
|
||||
3. Bring all services to the ready-to-use state. Services include:
|
||||
* E-mail, ready to use with DeltaChat
|
||||
* NextCloud - your personal cloud storage
|
||||
* Bitwarden - secure and private password manager
|
||||
* Pleroma - your private fediverse space for blogging
|
||||
* Jitsi — awesome Zoom alternative
|
||||
* Gitea - your own Git server
|
||||
* OpenConnect - Personal VPN server
|
||||
|
||||
!!! Project is currently in alpha state. Feel free to try it. It would be much appreciated if you would provide us with some feedback.
|
||||
!!! Only Russian localization is currently available. English is coming soon
|
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
Normal file
After Width: | Height: | Size: 195 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
Normal file
After Width: | Height: | Size: 170 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
Normal file
After Width: | Height: | Size: 103 KiB |
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Self-hosted services without pain
|
1
fastlane/metadata/android/en-US/title.txt
Normal file
|
@ -0,0 +1 @@
|
|||
SelfPrivacy
|
1
fastlane/metadata/android/en-US/video.txt
Normal file
|
@ -0,0 +1 @@
|
|||
https://fdroid.selfprivacy.org/SFPresentation.mp4
|
|
@ -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
|
||||
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
|
@ -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()),
|
||||
],
|
||||
|
|
|
@ -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(Bloc cubit, Object error, StackTrace stackTrace) {
|
||||
final navigator = getIt.get<NavigationService>().navigator!;
|
||||
|
||||
navigator.push(
|
||||
materialRoute(
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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!);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -18,9 +18,9 @@ abstract class ApiMap {
|
|||
};
|
||||
loggedClient = client;
|
||||
}
|
||||
String rootAddress;
|
||||
String? rootAddress;
|
||||
|
||||
Dio loggedClient;
|
||||
late Dio loggedClient;
|
||||
|
||||
void close() {
|
||||
loggedClient.close();
|
||||
|
@ -61,7 +61,7 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
addMessage(
|
||||
Message.warn(
|
||||
text:
|
||||
'response-uri: ${response?.request?.uri}\ncode: ${response?.statusCode}\ndata: ${response?.toString()}\n',
|
||||
'response-uri: ${response?.request.uri}\ncode: ${response?.statusCode}\ndata: ${response?.toString()}\n',
|
||||
),
|
||||
);
|
||||
return super.onError(err);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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',
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
// }
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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];
|
||||
// }
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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!;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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!();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
70
lib/ui/components/brand_md/brand_md.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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ек";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
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: brandPagePadding1,
|
||||
child: BrandMarkdown(
|
||||
fileName: 'how_hetzner',
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|