Compare commits
60 Commits
Author | SHA1 | Date |
---|---|---|
Illia Chub | 39cbabe9ca | |
Illia Chub | 3c03a3dbd4 | |
Kherel | 46efb6ac94 | |
Kherel | c9aa513e29 | |
Kherel | 2eab3fb8e4 | |
kherel | bdf5a3b1cc | |
kherel | d414b22251 | |
kherel | 43732fa866 | |
Kherel | eac9078fad | |
Kherel | ba8b19193d | |
Kherel | 3bc9b23e8f | |
kherel | 05e4f7e2f8 | |
Kherel | 4ae77f33c8 | |
tester.nicolai | 5c01d6a375 | |
kherel | e700ea2e4c | |
Kherel | 2c4d0ea7d1 | |
kherel | 3e7d003f21 | |
Illia Chub | a7e7d0ff05 | |
Kherel | 4942f67f37 | |
Kherel | d0023e5718 | |
Illia Chub | e064598c73 | |
Kherel | 90d64d8f51 | |
Kherel | 26607251d9 | |
Kherel | 84e9259ec2 | |
Kherel | 94a0e22b15 | |
Kherel | 1a8a4e7270 | |
Kherel | 1202e4ad53 | |
Illia Chub | 8ea1b28d73 | |
Illia Chub | 8f8714e07f | |
Illia Chub | 63d3057125 | |
Kherel | dfc6f67ee3 | |
Illia Chub | b130960113 | |
Kherel | 5dea5234de | |
Kherel | 3a5353dbf4 | |
Kherel | 65c6a0b870 | |
Kherel | 933e8ffb90 | |
Kherel | 1c352fd771 | |
Kherel Kechil | f53ad044c1 | |
Kherel | 21611e63c7 | |
Kherel | d8393f75ea | |
Kherel | 9e8fdf2965 | |
Kherel | d3f494adeb | |
kherel | e849b551fc | |
Kherel | 234515477c | |
Kherel | e5758aa2bf | |
Kherel | d4f315214b | |
Illia Chub | 5d8b953e0a | |
ilchub | c3eed056b5 | |
Illia Chub | eaa059f964 | |
ilchub | f30b54f564 | |
ilchub | 739ff1aadf | |
Kherel | 8ccb4f18f6 | |
Kherel | 5b83b493f4 | |
Kherel | 536ef4b717 | |
ilchub | 695c91cba7 | |
Kherel | 3676dc50f0 | |
Kherel | 6726c87de7 | |
Kherel | cd49f9fb45 | |
Illia Chub | 039449d3f5 | |
Illia Chub | 160bdd51a3 |
|
@ -0,0 +1,16 @@
|
||||||
|
# editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.dart]
|
||||||
|
max_line_length = 150
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
|
@ -1,3 +1,4 @@
|
||||||
|
ios/build
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
*.class
|
*.class
|
||||||
*.log
|
*.log
|
||||||
|
@ -29,7 +30,7 @@
|
||||||
.packages
|
.packages
|
||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.pub/
|
||||||
/build/
|
**/build/
|
||||||
|
|
||||||
# Web related
|
# Web related
|
||||||
lib/generated_plugin_registrant.dart
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
### О проекте
|
### О проекте
|
||||||
|
|
||||||
Всё больше организаций хотят владеть нашими данными
|
Всё больше организаций хотят владеть нашими данными
|
||||||
А мы сами хотим распоряжаться своими **данными** на своем сервере.
|
Проект позволяет только Вам в полной мере распоряжаться собственными **данными** на своём сервере.
|
||||||
|
|
||||||
### Миссия проекта
|
### Миссия проекта
|
||||||
|
|
||||||
Цифровая независимость и приватность доступная каждому
|
Цифровая независимость и приватность доступная каждому.
|
||||||
|
|
||||||
### Цель
|
### Цель
|
||||||
|
|
||||||
Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких
|
Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких.
|
|
@ -3,5 +3,5 @@
|
||||||
2. Заходим в созданный нами проект. Если такового - нет, значит создаём.
|
2. Заходим в созданный нами проект. Если такового - нет, значит создаём.
|
||||||
3. Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).
|
3. Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).
|
||||||
4. Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.
|
4. Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.
|
||||||
5. В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.
|
5. В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же Вы используете мобильную версию сайта, в нижнем правом углу Вы увидите красный плюсик. Нажимаем на эту кнопку.
|
||||||
6. В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.
|
6. В поле Description, даём нашему токену название (это может быть любое название, которые Вам нравиться. Сути оно не меняет.
|
|
@ -19,12 +19,15 @@
|
||||||
"connect": "Connect",
|
"connect": "Connect",
|
||||||
"domain": "Domain",
|
"domain": "Domain",
|
||||||
"saving": "Saving..",
|
"saving": "Saving..",
|
||||||
"nickname": "nickname",
|
"nickname": "Nickname",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"later": "I will setup it later",
|
"later": "I will setup it later",
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"details": "Details",
|
"details": "Details",
|
||||||
"no_data": "No data"
|
"no_data": "No data",
|
||||||
|
"wait": "Wait",
|
||||||
|
"remove": "Remove",
|
||||||
|
"apply": "Apply"
|
||||||
},
|
},
|
||||||
"more": {
|
"more": {
|
||||||
"_comment": "'More' tab",
|
"_comment": "'More' tab",
|
||||||
|
@ -32,16 +35,31 @@
|
||||||
"about_project": "About us",
|
"about_project": "About us",
|
||||||
"about_app": "About application",
|
"about_app": "About application",
|
||||||
"onboarding": "Onboarding",
|
"onboarding": "Onboarding",
|
||||||
|
"create_ssh_key": "Create ssh key",
|
||||||
|
"generate_key": "Generate key",
|
||||||
|
"generate_key_text": "You can generate ssh key",
|
||||||
"console": "Console",
|
"console": "Console",
|
||||||
|
"remove": "Remove",
|
||||||
|
"enable": "Enable",
|
||||||
|
"ok": "ok",
|
||||||
|
"continue": "Continue",
|
||||||
|
"ssh_key_exist_text": "You have generated ssh key",
|
||||||
|
"yes_delete": "Yes, delete my SSH key",
|
||||||
|
"share": "Share",
|
||||||
|
"copy_buffer": "Copy to buffer",
|
||||||
|
"copied_ssh": "SSH copied to clipboard",
|
||||||
|
"delete_ssh_text": "Delete SSH key?",
|
||||||
"about_app_page": {
|
"about_app_page": {
|
||||||
"text": "Тут любая служебная информация, v.{}"
|
"text": "Application version v.{}"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Application settings",
|
"title": "Application settings",
|
||||||
"1": "Dark Theme",
|
"1": "Dark Theme",
|
||||||
"2": "Change your the app theme",
|
"2": "Change your the app theme",
|
||||||
"3": "Reset app config",
|
"3": "Reset app config",
|
||||||
"4": "Reset api keys and root user"
|
"4": "Reset api keys and root user",
|
||||||
|
"5": "Delete Server",
|
||||||
|
"6": "This removes the Server. It will be no longer accessible"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"onboarding": {
|
"onboarding": {
|
||||||
|
@ -61,14 +79,18 @@
|
||||||
"1": "It's a virtual computer, where all your services live.",
|
"1": "It's a virtual computer, where all your services live.",
|
||||||
"2": "General information",
|
"2": "General information",
|
||||||
"3": "Location"
|
"3": "Location"
|
||||||
|
},
|
||||||
|
"chart": {
|
||||||
|
"month": "Month",
|
||||||
|
"day": "Day",
|
||||||
|
"hour": "Hour"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain": {
|
"domain": {
|
||||||
"card_title": "Domain",
|
"card_title": "Domain",
|
||||||
"status": "Status — Good",
|
"status": "Status — Good",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "It's your personal internet address that will point to the server and other services of yours.",
|
"1": "It's your personal internet address that will point to the server and other services of yours."
|
||||||
"2": "{} — expires on {}"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"backup": {
|
"backup": {
|
||||||
|
@ -138,6 +160,13 @@
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "You can connect and create a new user here:"
|
"1": "You can connect and create a new user here:"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"vpn": {
|
||||||
|
"title": "VPN Server",
|
||||||
|
"subtitle": "Private VPN server",
|
||||||
|
"bottom_sheet": {
|
||||||
|
"1": "Openconnect VPN Server. Engine for secure and scalable VPN infrastructure"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"users": {
|
"users": {
|
||||||
|
@ -157,7 +186,7 @@
|
||||||
},
|
},
|
||||||
"initializing": {
|
"initializing": {
|
||||||
"_comment": "initializing page",
|
"_comment": "initializing page",
|
||||||
"1": "Connect Hetzner server",
|
"1": "Connect a server",
|
||||||
"2": "Here, your data and SelfPrivacy services wiil reside",
|
"2": "Here, your data and SelfPrivacy services wiil reside",
|
||||||
"how": "How to obtain API token",
|
"how": "How to obtain API token",
|
||||||
"3": "Connect CloudFlare",
|
"3": "Connect CloudFlare",
|
||||||
|
@ -179,7 +208,11 @@
|
||||||
"18": "How to obtain Hetzner API Token",
|
"18": "How to obtain Hetzner API Token",
|
||||||
"19": "1 Go via this link ",
|
"19": "1 Go via this link ",
|
||||||
"20": "\n",
|
"20": "\n",
|
||||||
"21": "One more restart to apply your security certificates."
|
"21": "One more restart to apply your security certificates.",
|
||||||
|
"22": "Create master account",
|
||||||
|
"23": "Enter a nickname and strong password",
|
||||||
|
"finish": "Everything is initialized",
|
||||||
|
"checks": "Checks have been completed \n{} ouf of {}"
|
||||||
},
|
},
|
||||||
"modals": {
|
"modals": {
|
||||||
"_comment": "messages in modals",
|
"_comment": "messages in modals",
|
||||||
|
@ -187,9 +220,31 @@
|
||||||
"2": "Destroy server and create a new one?",
|
"2": "Destroy server and create a new one?",
|
||||||
"3": "Are you sure?",
|
"3": "Are you sure?",
|
||||||
"4": "Purge all authentication keys?",
|
"4": "Purge all authentication keys?",
|
||||||
"5": "Yes, purge all my tokens"
|
"5": "Yes, purge all my tokens",
|
||||||
|
"6": "Delete the server and volume?",
|
||||||
|
"7": "Yes",
|
||||||
|
"8": "Remove task"
|
||||||
},
|
},
|
||||||
"timer": {
|
"timer": {
|
||||||
"sec": "{} sec"
|
"sec": "{} sec"
|
||||||
|
},
|
||||||
|
"jobs": {
|
||||||
|
"_comment": "Jobs list",
|
||||||
|
"title": "Jobs list",
|
||||||
|
"start": "Start",
|
||||||
|
"empty": "No jobs",
|
||||||
|
"createUser": "Create",
|
||||||
|
"serviceTurnOff": "Turn off",
|
||||||
|
"serviceTurnOn": "Turn on",
|
||||||
|
"jobAdded": "Job added",
|
||||||
|
"runJobs": "Run jobs"
|
||||||
|
},
|
||||||
|
"validations": {
|
||||||
|
"required": "Required",
|
||||||
|
"invalid_format": "Invalid format",
|
||||||
|
"root_name": "User name cannot be 'root'",
|
||||||
|
"key_format": "Invalid key format",
|
||||||
|
"length": "Length is [] shoud be {}",
|
||||||
|
"user_alredy_exist": "Already exists"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
"providers": "Провайдеры",
|
"providers": "Провайдеры",
|
||||||
"services": "Сервисы",
|
"services": "Сервисы",
|
||||||
"users": "Пользователи",
|
"users": "Пользователи",
|
||||||
"more": "Еще",
|
"more": "Ещё",
|
||||||
"next": "Далее",
|
"next": "Далее",
|
||||||
"got_it": "Понял",
|
"got_it": "Понял",
|
||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
|
@ -22,34 +22,52 @@
|
||||||
"nickname": "Никнейм",
|
"nickname": "Никнейм",
|
||||||
"loading": "Загрузка",
|
"loading": "Загрузка",
|
||||||
"later": "Настрою потом",
|
"later": "Настрою потом",
|
||||||
"reset": "Reset",
|
"reset": "Сбросить",
|
||||||
"details": "Детальная информация",
|
"details": "Детальная информация",
|
||||||
"no_data": "Нет данных"
|
"no_data": "Нет данных",
|
||||||
|
"wait": "Загрузка",
|
||||||
|
"remove": "Удалить",
|
||||||
|
"apply": "Подать"
|
||||||
},
|
},
|
||||||
"more": {
|
"more": {
|
||||||
"_comment": "вкладка еще",
|
"_comment": "вкладка ещё",
|
||||||
"configuration_wizard": "Мастер Подключения",
|
"configuration_wizard": "Мастер Подключения",
|
||||||
"about_project": "О проекте SelfPrivacy",
|
"about_project": "О проекте SelfPrivacy",
|
||||||
"about_app": "О приложении",
|
"about_app": "О приложении",
|
||||||
"onboarding": "Приветствие",
|
"onboarding": "Приветствие",
|
||||||
"console": "Консоль",
|
"console": "Консоль",
|
||||||
|
"create_ssh_key": "Создать ssh ключ",
|
||||||
|
"generate_key": "Сгенерировать ключ",
|
||||||
|
"generate_key_text": "Вы сможете сгенерировать ключ",
|
||||||
|
"remove": "Отключить",
|
||||||
|
"enable": "Включить",
|
||||||
|
"ok": "ok",
|
||||||
|
"continue": "Продолжить",
|
||||||
|
"ssh_key_exist_text": "У Вас уже есть сгенерированный ssk ключ",
|
||||||
|
"yes_delete": "Да, удалить",
|
||||||
|
"share": "Поделиться",
|
||||||
|
"copy_buffer": "Копировать в буфер",
|
||||||
|
"copied_ssh": "SSH копировано в буфер",
|
||||||
|
"delete_ssh_text": "Удалить SSH ключ?",
|
||||||
"about_app_page": {
|
"about_app_page": {
|
||||||
"text": "Версия приложения: v.{}"
|
"text": "Версия приложения: v.{}"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Настройки приложения",
|
"title": "Настройки приложения",
|
||||||
"1": "Темная тема",
|
"1": "Тёмная тема",
|
||||||
"2": "Сменить цветовую тему",
|
"2": "Сменить цветовую тему.",
|
||||||
"3": "Сброс настроек",
|
"3": "Сброс настроек",
|
||||||
"4": "Сбросить API ключи а так же root пользвателя"
|
"4": "Сбросить API ключи а также root пользвателя.",
|
||||||
|
"5": "Удалить сервер",
|
||||||
|
"6": "Действие приведет к удалению сервера. После чего он не будет доступен."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"onboarding": {
|
"onboarding": {
|
||||||
"_comment": "страницы онбординга",
|
"_comment": "страницы онбординга",
|
||||||
"page1_title": "Цифровая независимость доступна каждому",
|
"page1_title": "Цифровая независимость доступна каждому",
|
||||||
"page1_text": "Почта, VPN, Мессенджер, социальная сеть и многое другое на вашем личном сервере, под вашим полным контролем.",
|
"page1_text": "Почта, VPN, Мессенджер, социальная сеть и многое другое на Вашем личном сервере, под Вашим полным контролем.",
|
||||||
"page2_title": "SelfPrivacy — это не облако, а ваш личный дата-центр",
|
"page2_title": "SelfPrivacy — это не облако, а Ваш личный дата-центр",
|
||||||
"page2_text": "У SelfPrivacy работает только с вашими сервис-провадерами: Hetzner, Cloudflare, Backblaze. Если у вас нет учетных записей, мы поможем их создать."
|
"page2_text": "SelfPrivacy работает только с вашими сервис-провайдерами: Hetzner, Cloudflare, Backblaze. Если у Вас нет учётных записей, мы поможем их создать."
|
||||||
},
|
},
|
||||||
"providers": {
|
"providers": {
|
||||||
"_comment": "вкладка провайдеры",
|
"_comment": "вкладка провайдеры",
|
||||||
|
@ -58,25 +76,29 @@
|
||||||
"card_title": "Сервер",
|
"card_title": "Сервер",
|
||||||
"status": "Статус — в норме",
|
"status": "Статус — в норме",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Это виртульный компьютер на котором работают все ваши сервисы.",
|
"1": "Это виртуальный компьютер на котором работают все Ваши сервисы.",
|
||||||
"2": "Общая информация",
|
"2": "Общая информация",
|
||||||
"3": "Размещение"
|
"3": "Размещение"
|
||||||
|
},
|
||||||
|
"chart": {
|
||||||
|
"month": "Месяц",
|
||||||
|
"day": "День",
|
||||||
|
"hour": "Час"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain": {
|
"domain": {
|
||||||
"card_title": "Домен",
|
"card_title": "Домен",
|
||||||
"status": "Статус — в норме",
|
"status": "Статус — в норме",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Это ваш личный адрес в интернете, который будет указывать на сервер и другие ваши сервисы.",
|
"1": "Это ваш личный адрес в интернете, который будет указывать на сервер и другие ваши сервисы."
|
||||||
"2": "{} — продлен до {}"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"backup": {
|
"backup": {
|
||||||
"card_title": "Резервное копирование",
|
"card_title": "Резервное копирование",
|
||||||
"status": "Статус — в норме",
|
"status": "Статус — в норме",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Выручит в любой ситуации: хакерская атака, удаление сервера и т.п.",
|
"1": "Выручит Вас в любой ситуации: хакерская атака, удаление сервера и.т.д.",
|
||||||
"2": "3Gb — бестплатно до 10Gb, последний вчера в {}"
|
"2": "Использовано 3Gb из бестплатых 10Gb. Последнее копирование была сделана вчера в {}."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -88,108 +110,141 @@
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"_comment": "Вкладка сервисы",
|
"_comment": "Вкладка сервисы",
|
||||||
"title": "Ваши личные приватные и независимые сервисы",
|
"title": "Ваши личные, приватные и независимые сервисы:",
|
||||||
"mail": {
|
"mail": {
|
||||||
"title": "Почта",
|
"title": "Почта",
|
||||||
"subtitle": "Электронная почта для семьи или компании",
|
"subtitle": "Электронная почта для семьи или компании.",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Для подключения почтового ящика используйте {} и логин пароль, который вы создали. Так же приглашайте",
|
"1": "Для подключения почтового ящика используйте {} и профиль, который Вы создали. Так же приглашайте",
|
||||||
"2": "новых пользователей"
|
"2": "новых пользователей."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"messenger": {
|
"messenger": {
|
||||||
"title": "Мессенджер",
|
"title": "Мессенджер",
|
||||||
"subtitle": "Telegram и Signal не так приватны, как Delta.Chat использующий ваш личный сервер.",
|
"subtitle": "Telegram и Signal не так приватны, как Delta.Chat — который использует Ваш личный сервер.",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Для подключения используйте {} и логин пароль, который вы создали"
|
"1": "Для подключения используйте {} и логин пароль, который Вы создали."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"password_manager": {
|
"password_manager": {
|
||||||
"title": "Менеджер паролей",
|
"title": "Менеджер паролей",
|
||||||
"subtitle": "Фундамент безопасности. Создавать, хранить, копировать пароли между устройствами, вбивать их в формы поможет — Bitwarden.",
|
"subtitle": "Это фундамент Вашей безопасности. Создавать, хранить, копировать пароли между устройствами и вбивать их в формы поможет — Bitwarden.",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
|
"1": "Подключиться к серверу и создать пользователя можно по адресу:."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"video": {
|
"video": {
|
||||||
"title": "Видеоконференция",
|
"title": "Видеоконференция",
|
||||||
"subtitle": "Zoom и Google meet отличные инструменты, но Jitsi meet не хуже и дает уверенность, что вас никто не подслушивает.",
|
"subtitle": "Jitsi meet — отличный аналог Zoom и Google meet который по мимо удобства ещё и гарантирует Вам защищённые высококачественные видеоконференции.",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Для использования просто перейдите по ссылке:"
|
"1": "Для использования просто перейдите по ссылке:."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cloud": {
|
"cloud": {
|
||||||
"title": "Файловое облако",
|
"title": "Файловое облако",
|
||||||
"subtitle": "Не позволяйте облачным сервисам читать ваши данные используйте NextCloud.",
|
"subtitle": "Не позволяйте облачным сервисам просматривать ваши данные. Используйте NextCloud — надёжный дом для всех Ваших данных.",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
|
"1": "Подключиться к серверу и создать пользователя можно по адресу:."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"social_network": {
|
"social_network": {
|
||||||
"title": "Социальная сеть",
|
"title": "Социальная сеть",
|
||||||
"subtitle": "Сложно поверить, но стало возможным создать свою собственную социальную сеть, со своими правилами и аудиторией.",
|
"subtitle": "Сложно поверить, но стало возможным создать свою собственную социальную сеть, со своими правилами и аудиторией.",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
|
"1": "Подключиться к серверу и создать пользователя можно по адресу:."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
"title": "Git-сервер",
|
"title": "Git-сервер",
|
||||||
"subtitle": "Приватная альтернатива Github, которая принадлежит вам, а не Microsoft.",
|
"subtitle": "Приватная альтернатива Github, которая принадлежит вам, а не Microsoft.",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
|
"1": "Подключиться к серверу и создать пользователя можно по адресу:."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vpn": {
|
||||||
|
"title": "VPN сервер",
|
||||||
|
"subtitle": "Закрытый VPN сервер",
|
||||||
|
"bottom_sheet": {
|
||||||
|
"1": "Создать подключиться к VPN-серверу. Движок для безопасной и масштабируемой инфраструктуры VPN"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"users": {
|
"users": {
|
||||||
"_comment": "'Users' tab",
|
"_comment": "'Users' tab",
|
||||||
"add_new_user": "Добавьте первого пользователя",
|
"add_new_user": "Добавьте первого пользователя.",
|
||||||
"new_user": "Новый пользователь",
|
"new_user": "Новый пользователь",
|
||||||
"not_ready": "Подключите сервер, домен и DNS в разделу Провайдеры, чтобы добавить первого пользователя",
|
"not_ready": "Подключите сервер, домен и DNS в разделу Провайдеры, чтобы добавить первого пользователя",
|
||||||
"nobody_here": "'Здесь пока никого'",
|
"nobody_here": "Здесь будут отображаться пользователи.",
|
||||||
"login": "Логин",
|
"login": "Логин",
|
||||||
"onboarding": "Приветствие",
|
"onboarding": "Приветствие",
|
||||||
"console": "Консоль разработчика",
|
"console": "Консоль разработчика",
|
||||||
"new_user_info_note": "Новый пользователь автоматически получит доступ ко всем сервисам. Ещё какое-то описание.",
|
"new_user_info_note": "Новый пользователь автоматически получит доступ ко всем сервисам.",
|
||||||
"delete_confirm_question": "удалить учетную запись?",
|
"delete_confirm_question": "Вы действительно хотите удалить учетную запись?",
|
||||||
"reset_password": "Сбросить пароль",
|
"reset_password": "Сбросить пароль",
|
||||||
"account": "Учетная запись",
|
"account": "Учетная запись",
|
||||||
"send_regisration_data": "Отправить реквизиты для входа"
|
"send_regisration_data": "Поделиться реквизитами"
|
||||||
},
|
},
|
||||||
"initializing": {
|
"initializing": {
|
||||||
"_comment": "initializing page",
|
"_comment": "initializing page",
|
||||||
"1": "Подключите сервер Hetzner",
|
"1": "Подключите сервер",
|
||||||
"2": "Здесь будут жить наши данные и SelfPrivacy-сервисы",
|
"2": "Здесь будут жить наши данные и SelfPrivacy-сервисы",
|
||||||
"how": "Как получить API Token",
|
"how": "Как получить API Token",
|
||||||
"3": "'Подключите CloudFlare'",
|
"3": "Подключите CloudFlare",
|
||||||
"4": "Для управления DNS вашего домена",
|
"4": "Для управления DNS вашего домена",
|
||||||
"5": "CloudFlare API Token",
|
"5": "CloudFlare API Token",
|
||||||
"6": "Подключите облачное хранилище Backblaze",
|
"6": "Подключите облачное хранилище Backblaze",
|
||||||
"7": "На данный момент подлюченных доменов нет",
|
"7": "На данный момент подлюченных доменов нет",
|
||||||
"8": "Загружаем список доменов",
|
"8": "Загружаем список доменов",
|
||||||
"9": "Найдено больше одного домена, для вашей безопастности, просим вам удалить не нужные домены",
|
"9": "Найдено больше одного домена, для вашей безопастности, просим Вам удалить не нужные домены",
|
||||||
"10": "Сохранить домен",
|
"10": "Сохранить домен",
|
||||||
"final": "Последний шаг",
|
"final": "Последний шаг",
|
||||||
"11": "Создать сервер",
|
"11": "Создать сервер",
|
||||||
"what": "Что это значит?",
|
"what": "Что это значит?",
|
||||||
"13": "Сервер презагружен, ждем последнюю проверку",
|
"13": "Сервер презагружен, ждем последнюю проверку.",
|
||||||
"14": "Cервер запушен, сейчас он будет проверен и перезагружен",
|
"14": "Cервер запущен, сейчас он будет проверен и перезагружен.",
|
||||||
"15": "Cервер создан, идет проверка ДНС адресов и запуск сервера",
|
"15": "Cервер создан, идет проверка ДНС адресов и запуск сервера.",
|
||||||
"16": "До следующей проверки: ",
|
"16": "До следующей проверки: ",
|
||||||
"17": "Проверка",
|
"17": "Проверка",
|
||||||
"18": "Как получить Hetzner API Token'",
|
"18": "Как получить Hetzner API Token:'",
|
||||||
"19": "1 Переходим по ссылке ",
|
"19": "1 Переходим по ссылке ",
|
||||||
"20": "\n2 Заходим в созданный нами проект. Если такового - нет, значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.",
|
"20": "\n2 Заходим в созданный нами проект. Если такового нет - значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же Вы используете мобильную версию сайта - в нижнем правом углу Вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description даём нашему токену название (это может быть любое название, которое Вам нравиться, сути оно не меняет.",
|
||||||
"21": "Сейчас будет дополнительная перезагрузка для активации сертификатов безопастности"
|
"21": "Сейчас будет дополнительная перезагрузка для активации сертификатов безопастности",
|
||||||
|
"22": "Создайте главную учетную запись",
|
||||||
|
"23": "Введите никнейм и сложный пароль",
|
||||||
|
"finish": "Всё инициализировано.",
|
||||||
|
"checks": "Проверок выполнено: \n{} / {}"
|
||||||
},
|
},
|
||||||
"modals": {
|
"modals": {
|
||||||
"_comment": "messages in modals",
|
"_comment": "messages in modals",
|
||||||
"1": "Сервер с таким именем уже существует",
|
"1": "Сервер с таким именем уже существует",
|
||||||
"2": "Уничтожить сервер и создать новый?",
|
"2": "Уничтожить сервер и создать новый?",
|
||||||
"3": "Вы уверенны",
|
"3": "Подтвердите",
|
||||||
"4": "Сбросить все ключи?",
|
"4": "Сбросить все ключи?",
|
||||||
"5": "Да, сбросить"
|
"5": "Да, сбросить",
|
||||||
|
"6": "Удалить сервер и диск?",
|
||||||
|
"7": "Да, удалить",
|
||||||
|
"8": "Удалить задачу"
|
||||||
},
|
},
|
||||||
"timer": {
|
"timer": {
|
||||||
"sec": "{} сек"
|
"sec": "{} сек"
|
||||||
|
},
|
||||||
|
"jobs": {
|
||||||
|
"_comment": "Jobs list",
|
||||||
|
"title": "Задачи",
|
||||||
|
"start": "Начать выполенение",
|
||||||
|
"empty": "Пусто.",
|
||||||
|
"createUser": "Создать запись",
|
||||||
|
"serviceTurnOff": "Остановить",
|
||||||
|
"serviceTurnOn": "Запустить",
|
||||||
|
"jobAdded": "Задача добавленна",
|
||||||
|
"runJobs": "Запустите задачи"
|
||||||
|
},
|
||||||
|
"validations": {
|
||||||
|
"required": "Обязательное поле.",
|
||||||
|
"invalid_format": "Неверный формат.",
|
||||||
|
"root_name": "Имя пользователя не может быть'root'.",
|
||||||
|
"key_format": "Неверный формат.",
|
||||||
|
"length": "Длина строки [] должна быть {}.",
|
||||||
|
"user_alredy_exist": "Имя уже используется."
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
- Updated Pleroma OTP to v2.3.0
|
||||||
|
- Updated Nextcloud to v21
|
||||||
|
- Fixed Bad Gateway HTTP response on social.$DOMAIN
|
||||||
|
- Fixed broken initial reboot chain
|
||||||
|
- Fixed password generation for PAM
|
||||||
|
- Various translation fixes
|
|
@ -0,0 +1,7 @@
|
||||||
|
- Added users creation possibility
|
||||||
|
- Fixed reverse DNS issue during server creation
|
||||||
|
- Updated NixOS to 21.05
|
||||||
|
- Defined application users as system ones
|
||||||
|
- Minor bugfixes
|
||||||
|
- Translation fixes
|
||||||
|
- en_GB is now a separate locale
|
|
@ -1,5 +1,5 @@
|
||||||
SelfPrivacy - is a platform on your cloud hosting, that allows to deploy your own private services and control them using mobile application.
|
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
|
To use this application, you'll be required to create accounts of different service providers. Please reffer to this manual: https://selfprivacy.org/en/second.html
|
||||||
Application will do the following things for you:
|
Application will do the following things for you:
|
||||||
1. Create your personal server
|
1. Create your personal server
|
||||||
2. Setup NixOS
|
2. Setup NixOS
|
||||||
|
@ -12,5 +12,4 @@ Application will do the following things for you:
|
||||||
* Gitea - your own Git server
|
* Gitea - your own Git server
|
||||||
* OpenConnect - Personal VPN 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.
|
!!! Project is currently in open beta 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
|
|
|
@ -21,6 +21,6 @@
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>8.0</string>
|
<string>9.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -2,10 +2,14 @@ PODS:
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_secure_storage (3.3.1):
|
- flutter_secure_storage (3.3.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- local_auth (0.0.1):
|
||||||
|
- Flutter
|
||||||
- package_info (0.0.1):
|
- package_info (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider (0.0.1):
|
- path_provider (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- share_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- shared_preferences (0.0.1):
|
- shared_preferences (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- url_launcher (0.0.1):
|
- url_launcher (0.0.1):
|
||||||
|
@ -16,8 +20,10 @@ PODS:
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
|
- local_auth (from `.symlinks/plugins/local_auth/ios`)
|
||||||
- package_info (from `.symlinks/plugins/package_info/ios`)
|
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||||
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
||||||
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||||
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
||||||
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
||||||
|
@ -27,10 +33,14 @@ EXTERNAL SOURCES:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
|
local_auth:
|
||||||
|
:path: ".symlinks/plugins/local_auth/ios"
|
||||||
package_info:
|
package_info:
|
||||||
:path: ".symlinks/plugins/package_info/ios"
|
:path: ".symlinks/plugins/package_info/ios"
|
||||||
path_provider:
|
path_provider:
|
||||||
:path: ".symlinks/plugins/path_provider/ios"
|
:path: ".symlinks/plugins/path_provider/ios"
|
||||||
|
share_plus:
|
||||||
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
:path: ".symlinks/plugins/shared_preferences/ios"
|
:path: ".symlinks/plugins/shared_preferences/ios"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
|
@ -39,10 +49,12 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/wakelock/ios"
|
:path: ".symlinks/plugins/wakelock/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
|
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||||
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
|
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
|
||||||
|
local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd
|
||||||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
||||||
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
|
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
|
||||||
|
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||||
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
||||||
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
|
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
|
||||||
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
||||||
|
|
|
@ -345,7 +345,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
@ -360,7 +360,8 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
|
DEVELOPMENT_TEAM = UVNTKR53DD;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -379,6 +380,7 @@
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
};
|
};
|
||||||
name = Profile;
|
name = Profile;
|
||||||
|
@ -431,7 +433,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
@ -481,7 +483,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
@ -498,7 +500,8 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
|
DEVELOPMENT_TEAM = UVNTKR53DD;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -518,6 +521,7 @@
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
@ -528,7 +532,8 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
|
DEVELOPMENT_TEAM = UVNTKR53DD;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -547,6 +552,7 @@
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 829 B |
Before Width: | Height: | Size: 608 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 906 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 525 B After Width: | Height: | Size: 1007 B |
Before Width: | Height: | Size: 907 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 608 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.8 KiB |
|
@ -2,11 +2,6 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleLocalizations</key>
|
|
||||||
<array>
|
|
||||||
<string>ru</string>
|
|
||||||
<string>en</string>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
|
@ -15,6 +10,11 @@
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
|
<key>CFBundleLocalizations</key>
|
||||||
|
<array>
|
||||||
|
<string>ru</string>
|
||||||
|
<string>en</string>
|
||||||
|
</array>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>selfprivacy</string>
|
<string>selfprivacy</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
|
@ -34,8 +34,6 @@
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
|
|
|
@ -2,7 +2,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||||
|
|
||||||
class BlocAndProviderConfig extends StatelessWidget {
|
class BlocAndProviderConfig extends StatelessWidget {
|
||||||
|
@ -12,11 +14,10 @@ class BlocAndProviderConfig extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// var platformBrightness =
|
|
||||||
// SchedulerBinding.instance.window.platformBrightness;
|
|
||||||
// var isDark = platformBrightness == Brightness.dark;
|
|
||||||
var isDark = false;
|
var isDark = false;
|
||||||
|
var usersCubit = UsersCubit();
|
||||||
|
var appConfigCubit = AppConfigCubit()..load();
|
||||||
|
var servicesCubit = ServicesCubit(appConfigCubit);
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
|
@ -25,12 +26,14 @@ class BlocAndProviderConfig extends StatelessWidget {
|
||||||
isOnbordingShowing: true,
|
isOnbordingShowing: true,
|
||||||
)..load(),
|
)..load(),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(lazy: false, create: (_) => appConfigCubit),
|
||||||
lazy: false,
|
|
||||||
create: (_) => AppConfigCubit()..load(),
|
|
||||||
),
|
|
||||||
BlocProvider(create: (_) => ProvidersCubit()),
|
BlocProvider(create: (_) => ProvidersCubit()),
|
||||||
BlocProvider(create: (_) => UsersCubit()),
|
BlocProvider(create: (_) => usersCubit..load(), lazy: false),
|
||||||
|
BlocProvider(create: (_) => servicesCubit..load(), lazy: false),
|
||||||
|
BlocProvider(
|
||||||
|
create: (_) =>
|
||||||
|
JobsCubit(usersCubit: usersCubit, servicesCubit: servicesCubit),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
final shadow8 = BoxShadow(
|
|
||||||
offset: Offset(0, 4),
|
|
||||||
blurRadius: 8,
|
|
||||||
color: Colors.black.withOpacity(.08),
|
|
||||||
);
|
|
|
@ -76,6 +76,6 @@ var darkTheme = ligtTheme.copyWith(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final brandPagePadding1 = EdgeInsets.symmetric(horizontal: 15, vertical: 30);
|
final paddingH15V30 = EdgeInsets.symmetric(horizontal: 15, vertical: 30);
|
||||||
|
|
||||||
final brandPagePadding2 = EdgeInsets.symmetric(horizontal: 15);
|
final paddingH15V0 = EdgeInsets.symmetric(horizontal: 15);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:get_it/get_it.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/api_config.dart';
|
import 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/console.dart';
|
import 'package:selfprivacy/logic/get_it/console.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/navigation.dart';
|
import 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||||
|
import 'package:selfprivacy/logic/get_it/ssh.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/timer.dart';
|
import 'package:selfprivacy/logic/get_it/timer.dart';
|
||||||
|
|
||||||
export 'package:selfprivacy/logic/get_it/api_config.dart';
|
export 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||||
|
@ -9,7 +10,6 @@ export 'package:selfprivacy/logic/get_it/console.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/timer.dart';
|
export 'package:selfprivacy/logic/get_it/timer.dart';
|
||||||
|
|
||||||
|
|
||||||
final getIt = GetIt.instance;
|
final getIt = GetIt.instance;
|
||||||
|
|
||||||
Future<void> getItSetup() async {
|
Future<void> getItSetup() async {
|
||||||
|
@ -17,6 +17,7 @@ Future<void> getItSetup() async {
|
||||||
|
|
||||||
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
||||||
getIt.registerSingleton<TimerModel>(TimerModel());
|
getIt.registerSingleton<TimerModel>(TimerModel());
|
||||||
|
getIt.registerSingleton<SSHModel>(SSHModel()..init());
|
||||||
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
||||||
|
|
||||||
await getIt.allReady();
|
await getIt.allReady();
|
||||||
|
|
|
@ -19,21 +19,25 @@ class HiveConfig {
|
||||||
Hive.registerAdapter(HetznerDataBaseAdapter());
|
Hive.registerAdapter(HetznerDataBaseAdapter());
|
||||||
|
|
||||||
await Hive.openBox(BNames.appSettings);
|
await Hive.openBox(BNames.appSettings);
|
||||||
var cipher = HiveAesCipher(await getEncriptedKey());
|
await Hive.openBox<User>(BNames.users);
|
||||||
|
await Hive.openBox(BNames.servicesState);
|
||||||
|
|
||||||
|
var cipher = HiveAesCipher(await getEncriptedKey(BNames.key));
|
||||||
await Hive.openBox(BNames.appConfig, encryptionCipher: cipher);
|
await Hive.openBox(BNames.appConfig, encryptionCipher: cipher);
|
||||||
|
|
||||||
|
var sshCipher = HiveAesCipher(await getEncriptedKey(BNames.sshEnckey));
|
||||||
|
await Hive.openBox(BNames.sshConfig, encryptionCipher: sshCipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Uint8List> getEncriptedKey() async {
|
static Future<Uint8List> getEncriptedKey(String encKey) async {
|
||||||
final FlutterSecureStorage secureStorage = FlutterSecureStorage();
|
final secureStorage = FlutterSecureStorage();
|
||||||
var containsEncryptionKey =
|
var hasEncryptionKey = await secureStorage.containsKey(key: encKey);
|
||||||
await secureStorage.containsKey(key: BNames.key);
|
if (!hasEncryptionKey) {
|
||||||
if (!containsEncryptionKey) {
|
|
||||||
var key = Hive.generateSecureKey();
|
var key = Hive.generateSecureKey();
|
||||||
await secureStorage.write(key: BNames.key, value: base64UrlEncode(key));
|
await secureStorage.write(key: encKey, value: base64UrlEncode(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
String? string = await secureStorage.read(key: BNames.key);
|
String? string = await secureStorage.read(key: encKey);
|
||||||
return base64Url.decode(string!);
|
return base64Url.decode(string!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,10 +46,13 @@ class BNames {
|
||||||
static String appConfig = 'appConfig';
|
static String appConfig = 'appConfig';
|
||||||
static String isDarkModeOn = 'isDarkModeOn';
|
static String isDarkModeOn = 'isDarkModeOn';
|
||||||
static String isOnbordingShowing = 'isOnbordingShowing';
|
static String isOnbordingShowing = 'isOnbordingShowing';
|
||||||
|
static String users = 'users';
|
||||||
|
|
||||||
static String appSettings = 'appSettings';
|
static String appSettings = 'appSettings';
|
||||||
|
static String servicesState = 'servicesState';
|
||||||
|
|
||||||
static String key = 'key';
|
static String key = 'key';
|
||||||
|
static String sshEnckey = 'sshEngkey';
|
||||||
|
|
||||||
static String cloudFlareDomain = 'cloudFlareDomain';
|
static String cloudFlareDomain = 'cloudFlareDomain';
|
||||||
static String hetznerKey = 'hetznerKey';
|
static String hetznerKey = 'hetznerKey';
|
||||||
|
@ -58,4 +65,7 @@ class BNames {
|
||||||
static String isLoading = 'isLoading';
|
static String isLoading = 'isLoading';
|
||||||
static String isServerResetedFirstTime = 'isServerResetedFirstTime';
|
static String isServerResetedFirstTime = 'isServerResetedFirstTime';
|
||||||
static String isServerResetedSecondTime = 'isServerResetedSecondTime';
|
static String isServerResetedSecondTime = 'isServerResetedSecondTime';
|
||||||
|
static String sshConfig = 'sshConfig';
|
||||||
|
static String sshPrivateKey = "sshPrivateKey";
|
||||||
|
static String sshPublicKey = "sshPublicKey";
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,13 @@ final headline4Style = defaultTextStyle.copyWith(
|
||||||
color: BrandColors.headlineColor,
|
color: BrandColors.headlineColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final headline4UnderlinedStyle = defaultTextStyle.copyWith(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: NamedFontWeight.medium,
|
||||||
|
color: BrandColors.headlineColor,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
);
|
||||||
|
|
||||||
final headline5Style = defaultTextStyle.copyWith(
|
final headline5Style = defaultTextStyle.copyWith(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: NamedFontWeight.medium,
|
fontWeight: NamedFontWeight.medium,
|
||||||
|
|
|
@ -15,13 +15,23 @@ abstract class ApiMap {
|
||||||
if (hasLoger) {
|
if (hasLoger) {
|
||||||
dio.interceptors.add(PrettyDioLogger());
|
dio.interceptors.add(PrettyDioLogger());
|
||||||
}
|
}
|
||||||
dio..interceptors.add(ConsoleInterceptor());
|
dio.interceptors.add(ConsoleInterceptor());
|
||||||
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
|
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
|
||||||
(HttpClient client) {
|
(HttpClient client) {
|
||||||
client.badCertificateCallback =
|
client.badCertificateCallback =
|
||||||
(X509Certificate cert, String host, int port) => true;
|
(X509Certificate cert, String host, int port) => true;
|
||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dio.interceptors.add(InterceptorsWrapper(onError: (DioError e, handler) {
|
||||||
|
print(e.requestOptions.path);
|
||||||
|
print(e.requestOptions.data);
|
||||||
|
|
||||||
|
print(e.message);
|
||||||
|
print(e.response);
|
||||||
|
|
||||||
|
return handler.next(e);
|
||||||
|
}));
|
||||||
return dio;
|
return dio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
||||||
import 'package:selfprivacy/logic/models/hetzner_server_info.dart';
|
import 'package:selfprivacy/logic/models/hetzner_server_info.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_details.dart';
|
import 'package:selfprivacy/logic/models/server_details.dart';
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator2.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
class HetznerApi extends ApiMap {
|
class HetznerApi extends ApiMap {
|
||||||
bool hasLoger;
|
bool hasLoger;
|
||||||
|
@ -68,24 +68,13 @@ class HetznerApi extends ApiMap {
|
||||||
return server == null;
|
return server == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<HetznerServerDetails> createServer({
|
Future<HetznerDataBase> createVolume() async {
|
||||||
required String cloudFlareKey,
|
|
||||||
required User rootUser,
|
|
||||||
required String domainName,
|
|
||||||
}) async {
|
|
||||||
var dbPassword = getRandomString(40);
|
|
||||||
|
|
||||||
const chars =
|
|
||||||
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
|
|
||||||
|
|
||||||
var dbStorageName = getRandomString(6, chars);
|
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
|
|
||||||
Response dbCreateResponse = await client.post(
|
Response dbCreateResponse = await client.post(
|
||||||
'/volumes',
|
'/volumes',
|
||||||
data: {
|
data: {
|
||||||
"size": 10,
|
"size": 10,
|
||||||
"name": dbStorageName,
|
"name": StringGenerators.dbStorageName(),
|
||||||
"labels": {"labelkey": "value"},
|
"labels": {"labelkey": "value"},
|
||||||
"location": "fsn1",
|
"location": "fsn1",
|
||||||
"automount": false,
|
"automount": false,
|
||||||
|
@ -93,23 +82,53 @@ class HetznerApi extends ApiMap {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
var dbId = dbCreateResponse.data['volume']['id'];
|
var dbId = dbCreateResponse.data['volume']['id'];
|
||||||
var data = jsonDecode(
|
return HetznerDataBase(
|
||||||
'''{"name":"$domainName","server_type":"cx11","start_after_create":false,"image":"ubuntu-20.04", "volumes":[$dbId], "networks":[],"user_data":"#cloud-config\\nruncmd:\\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/preproduction/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-20.09 DOMAIN=$domainName LUSER=${rootUser.login} PASSWORD=${rootUser.password} HASHED_PASSWORD=${rootUser.hashPassword} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":true, "location": "fsn1"}''',
|
id: dbId,
|
||||||
|
name: dbCreateResponse.data['volume']['name'],
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<HetznerServerDetails> createServer({
|
||||||
|
required String cloudFlareKey,
|
||||||
|
required User rootUser,
|
||||||
|
required String domainName,
|
||||||
|
required HetznerDataBase dataBase,
|
||||||
|
}) async {
|
||||||
|
var client = await getClient();
|
||||||
|
|
||||||
|
// Response dbCreateResponse = await client.post(
|
||||||
|
// '/volumes',
|
||||||
|
// data: {
|
||||||
|
// "size": 10,
|
||||||
|
// "name": StringGenerators.dbStorageName(),
|
||||||
|
// "labels": {"labelkey": "value"},
|
||||||
|
// "location": "fsn1",
|
||||||
|
// "automount": false,
|
||||||
|
// "format": "ext4"
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
|
||||||
|
var dbPassword = StringGenerators.dbPassword();
|
||||||
|
// var dbId = dbCreateResponse.data['volume']['id'];
|
||||||
|
var dbId = dataBase.id;
|
||||||
|
|
||||||
|
/// add ssh key when you need it: e.g. "ssh_keys":["kherel"]
|
||||||
|
/// check the branch name, it could be "development" or "master".
|
||||||
|
|
||||||
|
var data = jsonDecode(
|
||||||
|
'''{"name":"$domainName","server_type":"cx11","start_after_create":false,"image":"ubuntu-20.04", "volumes":[$dbId], "networks":[], "user_data":"#cloud-config\\nruncmd:\\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/development/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-21.05 DOMAIN=$domainName LUSER=${rootUser.login} PASSWORD=${rootUser.password} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":true, "location": "fsn1"}''');
|
||||||
|
|
||||||
Response serverCreateResponse = await client.post(
|
Response serverCreateResponse = await client.post(
|
||||||
'/servers',
|
'/servers',
|
||||||
data: data,
|
data: data,
|
||||||
);
|
);
|
||||||
|
|
||||||
client.close();
|
client.close();
|
||||||
return HetznerServerDetails(
|
return HetznerServerDetails(
|
||||||
id: serverCreateResponse.data['server']['id'],
|
id: serverCreateResponse.data['server']['id'],
|
||||||
ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'],
|
ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'],
|
||||||
createTime: DateTime.now(),
|
createTime: DateTime.now(),
|
||||||
dataBase: HetznerDataBase(
|
dataBase: dataBase,
|
||||||
id: dbId,
|
|
||||||
name: dbCreateResponse.data['volume']['name'],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,28 +139,22 @@ class HetznerApi extends ApiMap {
|
||||||
|
|
||||||
Response serversReponse = await client.get('/servers');
|
Response serversReponse = await client.get('/servers');
|
||||||
List servers = serversReponse.data['servers'];
|
List servers = serversReponse.data['servers'];
|
||||||
var server = servers.firstWhere((el) => el['name'] == domainName);
|
Map server = servers.firstWhere((el) => el['name'] == domainName);
|
||||||
await client.delete('/servers/${server['id']}');
|
List volumes = server['volumes'];
|
||||||
|
|
||||||
Response volumesReponse = await client.get('/volumes');
|
|
||||||
List volumes = volumesReponse.data['volumes'];
|
|
||||||
|
|
||||||
var laterFutures = <Future>[];
|
var laterFutures = <Future>[];
|
||||||
for (var volume in volumes) {
|
|
||||||
if (volume['server'] == null) {
|
|
||||||
await client.delete('/volumes/${volume['id']}');
|
|
||||||
} else {
|
|
||||||
laterFutures.add(Future.delayed(Duration(seconds: 60)).then(
|
|
||||||
(_) => client.delete('/volumes/${volume['id']}'),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (laterFutures.isEmpty) {
|
for (var volumeId in volumes) {
|
||||||
close(client);
|
await client.post('/volumes/$volumeId/actions/detach');
|
||||||
} else {
|
|
||||||
Future.wait(laterFutures).then((value) => close(client));
|
|
||||||
}
|
}
|
||||||
|
await Future.delayed(Duration(seconds: 10));
|
||||||
|
|
||||||
|
for (var volumeId in volumes) {
|
||||||
|
laterFutures.add(client.delete('/volumes/$volumeId'));
|
||||||
|
}
|
||||||
|
laterFutures.add(client.delete('/servers/${server['id']}'));
|
||||||
|
|
||||||
|
await Future.wait(laterFutures);
|
||||||
|
close(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<HetznerServerDetails> reset() async {
|
Future<HetznerServerDetails> reset() async {
|
||||||
|
@ -164,11 +177,22 @@ class HetznerApi extends ApiMap {
|
||||||
return server.copyWith(startTime: DateTime.now());
|
return server.copyWith(startTime: DateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics() async {
|
Future<Map<String, dynamic>> getMetrics(
|
||||||
|
DateTime start, DateTime end, String type) async {
|
||||||
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
|
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
await client.post('/servers/${hetznerServer!.id}/metrics');
|
|
||||||
|
Map<String, dynamic> queryParameters = {
|
||||||
|
"start": start.toUtc().toIso8601String(),
|
||||||
|
"end": end.toUtc().toIso8601String(),
|
||||||
|
"type": type
|
||||||
|
};
|
||||||
|
var res = await client.get(
|
||||||
|
'/servers/${hetznerServer!.id}/metrics',
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
);
|
||||||
close(client);
|
close(client);
|
||||||
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<HetznerServerInfo> getInfo() async {
|
Future<HetznerServerInfo> getInfo() async {
|
||||||
|
@ -179,4 +203,20 @@ class HetznerApi extends ApiMap {
|
||||||
|
|
||||||
return HetznerServerInfo.fromJson(response.data!['server']);
|
return HetznerServerInfo.fromJson(response.data!['server']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> createReverseDns({
|
||||||
|
required String ip4,
|
||||||
|
required String domainName,
|
||||||
|
}) async {
|
||||||
|
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
|
||||||
|
var client = await getClient();
|
||||||
|
await client.post(
|
||||||
|
'/servers/${hetznerServer!.id}/actions/change_dns_ptr',
|
||||||
|
data: {
|
||||||
|
"ip": ip4,
|
||||||
|
"dns_ptr": domainName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
|
||||||
import 'api_map.dart';
|
import 'api_map.dart';
|
||||||
|
|
||||||
|
@ -31,7 +33,7 @@ class ServerApi extends ApiMap {
|
||||||
|
|
||||||
var client = await getClient();
|
var client = await getClient();
|
||||||
try {
|
try {
|
||||||
response = await client.get('/serviceStatus');
|
response = await client.get('/services/status');
|
||||||
res = response.statusCode == HttpStatus.ok;
|
res = response.statusCode == HttpStatus.ok;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res = false;
|
res = false;
|
||||||
|
@ -40,6 +42,106 @@ class ServerApi extends ApiMap {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> createUser(User user) async {
|
||||||
|
bool res;
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
var client = await getClient();
|
||||||
|
try {
|
||||||
|
response = await client.post(
|
||||||
|
'/users/create',
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
"X-User": user.login,
|
||||||
|
"X-Password": user.password,
|
||||||
|
"X-Domain": getIt<ApiConfigModel>().cloudFlareDomain!.domainName
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
res = response.statusCode == HttpStatus.ok;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(client);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
String get rootAddress =>
|
String get rootAddress =>
|
||||||
throw UnimplementedError('not used in with implementation');
|
throw UnimplementedError('not used in with implementation');
|
||||||
|
|
||||||
|
Future<bool> apply() async {
|
||||||
|
bool res;
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
var client = await getClient();
|
||||||
|
try {
|
||||||
|
response = await client.get(
|
||||||
|
'/system/configuration/apply',
|
||||||
|
);
|
||||||
|
|
||||||
|
res = response.statusCode == HttpStatus.ok;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(client);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> switchService(ServiceTypes type, bool needToTurnOn) async {
|
||||||
|
var client = await getClient();
|
||||||
|
client.post('/services/${type.url}/${needToTurnOn ? 'enable' : 'disable'}');
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sendSsh(String ssh) async {
|
||||||
|
var client = await getClient();
|
||||||
|
client.post(
|
||||||
|
'/services/ssh/enable',
|
||||||
|
data: {"public_key": ssh},
|
||||||
|
);
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<ServiceTypes, bool>> servicesPowerCheck() async {
|
||||||
|
var client = await getClient();
|
||||||
|
Response response = await client.get('/services/status');
|
||||||
|
|
||||||
|
close(client);
|
||||||
|
return {
|
||||||
|
ServiceTypes.passwordManager: response.data['bitwarden'] == 0,
|
||||||
|
ServiceTypes.git: response.data['gitea'] == 0,
|
||||||
|
ServiceTypes.cloud: response.data['nextcloud'] == 0,
|
||||||
|
ServiceTypes.vpn: response.data['ocserv'] == 0,
|
||||||
|
ServiceTypes.socialNetwork: response.data['pleroma'] == 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UrlServerExt on ServiceTypes {
|
||||||
|
String get url {
|
||||||
|
switch (this) {
|
||||||
|
// case ServiceTypes.mail:
|
||||||
|
// return ''; // cannot be swithch off
|
||||||
|
// case ServiceTypes.messenger:
|
||||||
|
// return ''; // external service
|
||||||
|
// case ServiceTypes.video:
|
||||||
|
// return ''; // jeetsu meet not working
|
||||||
|
case ServiceTypes.passwordManager:
|
||||||
|
return 'bitwarden';
|
||||||
|
case ServiceTypes.cloud:
|
||||||
|
return 'nextcloud';
|
||||||
|
case ServiceTypes.socialNetwork:
|
||||||
|
return 'pleroma';
|
||||||
|
case ServiceTypes.git:
|
||||||
|
return 'gitea';
|
||||||
|
case ServiceTypes.vpn:
|
||||||
|
return 'ocserv';
|
||||||
|
default:
|
||||||
|
throw Exception('wrong state');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:ionicons/ionicons.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
|
|
||||||
enum InitializingSteps {
|
enum InitializingSteps {
|
||||||
setHeznerKey,
|
setHeznerKey,
|
||||||
setCloudFlareKey,
|
setCloudFlareKey,
|
||||||
|
@ -8,3 +13,82 @@ enum InitializingSteps {
|
||||||
startServer,
|
startServer,
|
||||||
checkSystemDnsAndDkimSet,
|
checkSystemDnsAndDkimSet,
|
||||||
}
|
}
|
||||||
|
enum Period { hour, day, month }
|
||||||
|
|
||||||
|
enum ServiceTypes {
|
||||||
|
mail,
|
||||||
|
messenger,
|
||||||
|
passwordManager,
|
||||||
|
video,
|
||||||
|
cloud,
|
||||||
|
socialNetwork,
|
||||||
|
git,
|
||||||
|
vpn,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ServiceTypesExt on ServiceTypes {
|
||||||
|
String get title {
|
||||||
|
switch (this) {
|
||||||
|
case ServiceTypes.mail:
|
||||||
|
return 'services.mail.title'.tr();
|
||||||
|
case ServiceTypes.messenger:
|
||||||
|
return 'services.messenger.title'.tr();
|
||||||
|
case ServiceTypes.passwordManager:
|
||||||
|
return 'services.password_manager.title'.tr();
|
||||||
|
case ServiceTypes.video:
|
||||||
|
return 'services.video.title'.tr();
|
||||||
|
case ServiceTypes.cloud:
|
||||||
|
return 'services.cloud.title'.tr();
|
||||||
|
case ServiceTypes.socialNetwork:
|
||||||
|
return 'services.social_network.title'.tr();
|
||||||
|
case ServiceTypes.git:
|
||||||
|
return 'services.git.title'.tr();
|
||||||
|
case ServiceTypes.vpn:
|
||||||
|
return 'services.vpn.title'.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get subtitle {
|
||||||
|
switch (this) {
|
||||||
|
case ServiceTypes.mail:
|
||||||
|
return 'services.mail.subtitle'.tr();
|
||||||
|
case ServiceTypes.messenger:
|
||||||
|
return 'services.messenger.subtitle'.tr();
|
||||||
|
case ServiceTypes.passwordManager:
|
||||||
|
return 'services.password_manager.subtitle'.tr();
|
||||||
|
case ServiceTypes.video:
|
||||||
|
return 'services.video.subtitle'.tr();
|
||||||
|
case ServiceTypes.cloud:
|
||||||
|
return 'services.cloud.subtitle'.tr();
|
||||||
|
case ServiceTypes.socialNetwork:
|
||||||
|
return 'services.social_network.subtitle'.tr();
|
||||||
|
case ServiceTypes.git:
|
||||||
|
return 'services.git.subtitle'.tr();
|
||||||
|
case ServiceTypes.vpn:
|
||||||
|
return 'services.vpn.subtitle'.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData get icon {
|
||||||
|
switch (this) {
|
||||||
|
case ServiceTypes.mail:
|
||||||
|
return BrandIcons.envelope;
|
||||||
|
case ServiceTypes.messenger:
|
||||||
|
return BrandIcons.messanger;
|
||||||
|
case ServiceTypes.passwordManager:
|
||||||
|
return BrandIcons.key;
|
||||||
|
case ServiceTypes.video:
|
||||||
|
return BrandIcons.webcam;
|
||||||
|
case ServiceTypes.cloud:
|
||||||
|
return BrandIcons.upload;
|
||||||
|
case ServiceTypes.socialNetwork:
|
||||||
|
return BrandIcons.social;
|
||||||
|
case ServiceTypes.git:
|
||||||
|
return BrandIcons.git;
|
||||||
|
case ServiceTypes.vpn:
|
||||||
|
return Ionicons.shield_checkmark_outline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get txt => this.toString().split('.')[1];
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/get_it/ssh.dart';
|
||||||
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
|
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
|
||||||
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||||
|
|
||||||
|
@ -43,16 +45,17 @@ part 'app_config_state.dart';
|
||||||
/// c. if server is okay set that fully checked
|
/// c. if server is okay set that fully checked
|
||||||
|
|
||||||
class AppConfigCubit extends Cubit<AppConfigState> {
|
class AppConfigCubit extends Cubit<AppConfigState> {
|
||||||
AppConfigCubit() : super(InitialAppConfigState());
|
AppConfigCubit() : super(AppConfigEmpty());
|
||||||
|
|
||||||
final repository = AppConfigRepository();
|
final repository = AppConfigRepository();
|
||||||
|
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
var state = await repository.load();
|
var state = await repository.load();
|
||||||
|
|
||||||
if (state.progress < 6 || state.isFullyInitilized) {
|
if (state is AppConfigFinished) {
|
||||||
emit(state);
|
emit(state);
|
||||||
} else if (state.progress == 6) {
|
} else if (state is AppConfigNotFinished) {
|
||||||
|
if (state.progress == 6) {
|
||||||
startServerIfDnsIsOkay(state: state, isImmediate: true);
|
startServerIfDnsIsOkay(state: state, isImmediate: true);
|
||||||
} else if (state.progress == 7) {
|
} else if (state.progress == 7) {
|
||||||
resetServerIfServerIsOkay(state: state, isImmediate: true);
|
resetServerIfServerIsOkay(state: state, isImmediate: true);
|
||||||
|
@ -61,13 +64,16 @@ class AppConfigCubit extends Cubit<AppConfigState> {
|
||||||
} else if (state.progress == 9) {
|
} else if (state.progress == 9) {
|
||||||
finishCheckIfServerIsOkay(state: state, isImmediate: true);
|
finishCheckIfServerIsOkay(state: state, isImmediate: true);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw 'wrong state';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void startServerIfDnsIsOkay({
|
void startServerIfDnsIsOkay({
|
||||||
AppConfigState? state,
|
AppConfigNotFinished? state,
|
||||||
bool isImmediate = false,
|
bool isImmediate = false,
|
||||||
}) async {
|
}) async {
|
||||||
state = state ?? this.state;
|
state = state ?? this.state as AppConfigNotFinished;
|
||||||
|
|
||||||
final work = () async {
|
final work = () async {
|
||||||
emit(TimerState(dataState: state!, isLoading: true));
|
emit(TimerState(dataState: state!, isLoading: true));
|
||||||
|
@ -112,10 +118,10 @@ class AppConfigCubit extends Cubit<AppConfigState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void oneMoreReset({
|
void oneMoreReset({
|
||||||
AppConfigState? state,
|
AppConfigNotFinished? state,
|
||||||
bool isImmediate = false,
|
bool isImmediate = false,
|
||||||
}) async {
|
}) async {
|
||||||
var dataState = state ?? this.state;
|
var dataState = state ?? this.state as AppConfigNotFinished;
|
||||||
|
|
||||||
var work = () async {
|
var work = () async {
|
||||||
emit(TimerState(dataState: dataState, isLoading: true));
|
emit(TimerState(dataState: dataState, isLoading: true));
|
||||||
|
@ -165,10 +171,10 @@ class AppConfigCubit extends Cubit<AppConfigState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetServerIfServerIsOkay({
|
void resetServerIfServerIsOkay({
|
||||||
AppConfigState? state,
|
AppConfigNotFinished? state,
|
||||||
bool isImmediate = false,
|
bool isImmediate = false,
|
||||||
}) async {
|
}) async {
|
||||||
var dataState = state ?? this.state;
|
var dataState = state ?? this.state as AppConfigNotFinished;
|
||||||
|
|
||||||
var work = () async {
|
var work = () async {
|
||||||
emit(TimerState(dataState: dataState, isLoading: true));
|
emit(TimerState(dataState: dataState, isLoading: true));
|
||||||
|
@ -220,10 +226,10 @@ class AppConfigCubit extends Cubit<AppConfigState> {
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
|
|
||||||
void finishCheckIfServerIsOkay({
|
void finishCheckIfServerIsOkay({
|
||||||
AppConfigState? state,
|
AppConfigNotFinished? state,
|
||||||
bool isImmediate = false,
|
bool isImmediate = false,
|
||||||
}) async {
|
}) async {
|
||||||
state = state ?? this.state;
|
state = state ?? this.state as AppConfigNotFinished;
|
||||||
|
|
||||||
var work = () async {
|
var work = () async {
|
||||||
emit(TimerState(dataState: state!, isLoading: true));
|
emit(TimerState(dataState: state!, isLoading: true));
|
||||||
|
@ -233,10 +239,7 @@ class AppConfigCubit extends Cubit<AppConfigState> {
|
||||||
if (isServerWorking) {
|
if (isServerWorking) {
|
||||||
await repository.saveHasFinalChecked(true);
|
await repository.saveHasFinalChecked(true);
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.finish());
|
||||||
hasFinalChecked: true,
|
|
||||||
isLoading: false,
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
finishCheckIfServerIsOkay();
|
finishCheckIfServerIsOkay();
|
||||||
}
|
}
|
||||||
|
@ -259,18 +262,42 @@ class AppConfigCubit extends Cubit<AppConfigState> {
|
||||||
|
|
||||||
void clearAppConfig() {
|
void clearAppConfig() {
|
||||||
closeTimer();
|
closeTimer();
|
||||||
|
|
||||||
repository.clearAppConfig();
|
repository.clearAppConfig();
|
||||||
emit(InitialAppConfigState());
|
emit(AppConfigEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> serverDelete() async {
|
||||||
|
closeTimer();
|
||||||
|
|
||||||
|
if (state.hetznerServer != null) {
|
||||||
|
await repository.deleteServer(state.cloudFlareDomain!);
|
||||||
|
await getIt<SSHModel>().clear();
|
||||||
|
}
|
||||||
|
await repository.deleteRecords();
|
||||||
|
emit(AppConfigNotFinished(
|
||||||
|
hetznerKey: state.hetznerKey,
|
||||||
|
cloudFlareDomain: state.cloudFlareDomain,
|
||||||
|
cloudFlareKey: state.cloudFlareKey,
|
||||||
|
backblazeCredential: state.backblazeCredential,
|
||||||
|
rootUser: state.rootUser,
|
||||||
|
hetznerServer: null,
|
||||||
|
isServerStarted: false,
|
||||||
|
isServerResetedFirstTime: false,
|
||||||
|
isServerResetedSecondTime: false,
|
||||||
|
isLoading: false,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setHetznerKey(String hetznerKey) async {
|
void setHetznerKey(String hetznerKey) async {
|
||||||
await repository.saveHetznerKey(hetznerKey);
|
await repository.saveHetznerKey(hetznerKey);
|
||||||
emit(state.copyWith(hetznerKey: hetznerKey));
|
emit((state as AppConfigNotFinished).copyWith(hetznerKey: hetznerKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCloudflareKey(String cloudFlareKey) async {
|
void setCloudflareKey(String cloudFlareKey) async {
|
||||||
await repository.saveCloudFlareKey(cloudFlareKey);
|
await repository.saveCloudFlareKey(cloudFlareKey);
|
||||||
emit(state.copyWith(cloudFlareKey: cloudFlareKey));
|
emit(
|
||||||
|
(state as AppConfigNotFinished).copyWith(cloudFlareKey: cloudFlareKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBackblazeKey(String keyId, String applicationKey) async {
|
void setBackblazeKey(String keyId, String applicationKey) async {
|
||||||
|
@ -279,38 +306,41 @@ class AppConfigCubit extends Cubit<AppConfigState> {
|
||||||
applicationKey: applicationKey,
|
applicationKey: applicationKey,
|
||||||
);
|
);
|
||||||
await repository.saveBackblazeKey(backblazeCredential);
|
await repository.saveBackblazeKey(backblazeCredential);
|
||||||
emit(state.copyWith(backblazeCredential: backblazeCredential));
|
emit((state as AppConfigNotFinished)
|
||||||
|
.copyWith(backblazeCredential: backblazeCredential));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDomain(CloudFlareDomain cloudFlareDomain) async {
|
void setDomain(CloudFlareDomain cloudFlareDomain) async {
|
||||||
await repository.saveDomain(cloudFlareDomain);
|
await repository.saveDomain(cloudFlareDomain);
|
||||||
emit(state.copyWith(cloudFlareDomain: cloudFlareDomain));
|
emit((state as AppConfigNotFinished)
|
||||||
|
.copyWith(cloudFlareDomain: cloudFlareDomain));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRootUser(User rootUser) async {
|
void setRootUser(User rootUser) async {
|
||||||
await repository.saveRootUser(rootUser);
|
await repository.saveRootUser(rootUser);
|
||||||
emit(state.copyWith(rootUser: rootUser));
|
emit((state as AppConfigNotFinished).copyWith(rootUser: rootUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
void createServerAndSetDnsRecords() async {
|
void createServerAndSetDnsRecords() async {
|
||||||
AppConfigState _stateCopy = state;
|
AppConfigNotFinished _stateCopy = state as AppConfigNotFinished;
|
||||||
var onSuccess = (serverDetails) async {
|
var onSuccess = (HetznerServerDetails serverDetails) async {
|
||||||
await repository.createDnsRecords(
|
await repository.createDnsRecords(
|
||||||
serverDetails.ip4,
|
serverDetails.ip4,
|
||||||
state.cloudFlareDomain!,
|
state.cloudFlareDomain!,
|
||||||
);
|
);
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit((state as AppConfigNotFinished).copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
hetznerServer: serverDetails,
|
hetznerServer: serverDetails,
|
||||||
));
|
));
|
||||||
startServerIfDnsIsOkay();
|
startServerIfDnsIsOkay();
|
||||||
};
|
};
|
||||||
|
|
||||||
var onCancel = () => emit(state.copyWith(isLoading: false));
|
var onCancel =
|
||||||
|
() => emit((state as AppConfigNotFinished).copyWith(isLoading: false));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
emit(state.copyWith(isLoading: true));
|
emit((state as AppConfigNotFinished).copyWith(isLoading: true));
|
||||||
await repository.createServer(
|
await repository.createServer(
|
||||||
state.rootUser!,
|
state.rootUser!,
|
||||||
state.cloudFlareDomain!.domainName,
|
state.cloudFlareDomain!.domainName,
|
||||||
|
|
|
@ -22,7 +22,23 @@ class AppConfigRepository {
|
||||||
Box box = Hive.box(BNames.appConfig);
|
Box box = Hive.box(BNames.appConfig);
|
||||||
|
|
||||||
Future<AppConfigState> load() async {
|
Future<AppConfigState> load() async {
|
||||||
var res = AppConfigState(
|
late AppConfigState res;
|
||||||
|
if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
|
||||||
|
res = AppConfigFinished(
|
||||||
|
hetznerKey: getIt<ApiConfigModel>().hetznerKey!,
|
||||||
|
cloudFlareKey: getIt<ApiConfigModel>().cloudFlareKey!,
|
||||||
|
cloudFlareDomain: getIt<ApiConfigModel>().cloudFlareDomain!,
|
||||||
|
backblazeCredential: getIt<ApiConfigModel>().backblazeCredential!,
|
||||||
|
hetznerServer: getIt<ApiConfigModel>().hetznerServer!,
|
||||||
|
rootUser: box.get(BNames.rootUser),
|
||||||
|
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
|
||||||
|
isServerResetedFirstTime:
|
||||||
|
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
|
||||||
|
isServerResetedSecondTime:
|
||||||
|
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
res = AppConfigNotFinished(
|
||||||
hetznerKey: getIt<ApiConfigModel>().hetznerKey,
|
hetznerKey: getIt<ApiConfigModel>().hetznerKey,
|
||||||
cloudFlareKey: getIt<ApiConfigModel>().cloudFlareKey,
|
cloudFlareKey: getIt<ApiConfigModel>().cloudFlareKey,
|
||||||
cloudFlareDomain: getIt<ApiConfigModel>().cloudFlareDomain,
|
cloudFlareDomain: getIt<ApiConfigModel>().cloudFlareDomain,
|
||||||
|
@ -34,10 +50,9 @@ class AppConfigRepository {
|
||||||
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
|
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
|
||||||
isServerResetedSecondTime:
|
isServerResetedSecondTime:
|
||||||
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
|
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
|
||||||
hasFinalChecked: box.get(BNames.hasFinalChecked, defaultValue: false),
|
|
||||||
error: null,
|
|
||||||
isLoading: box.get(BNames.isLoading, defaultValue: false),
|
isLoading: box.get(BNames.isLoading, defaultValue: false),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -101,12 +116,16 @@ class AppConfigRepository {
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}) async {
|
}) async {
|
||||||
var hetznerApi = HetznerApi();
|
var hetznerApi = HetznerApi();
|
||||||
|
late HetznerDataBase dataBase;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
dataBase = await hetznerApi.createVolume();
|
||||||
|
|
||||||
var serverDetails = await hetznerApi.createServer(
|
var serverDetails = await hetznerApi.createServer(
|
||||||
cloudFlareKey: cloudFlareKey,
|
cloudFlareKey: cloudFlareKey,
|
||||||
rootUser: rootUser,
|
rootUser: rootUser,
|
||||||
domainName: domainName,
|
domainName: domainName,
|
||||||
|
dataBase: dataBase,
|
||||||
);
|
);
|
||||||
saveServerDetails(serverDetails);
|
saveServerDetails(serverDetails);
|
||||||
onSuccess(serverDetails);
|
onSuccess(serverDetails);
|
||||||
|
@ -129,6 +148,7 @@ class AppConfigRepository {
|
||||||
cloudFlareKey: cloudFlareKey,
|
cloudFlareKey: cloudFlareKey,
|
||||||
rootUser: rootUser,
|
rootUser: rootUser,
|
||||||
domainName: domainName,
|
domainName: domainName,
|
||||||
|
dataBase: dataBase,
|
||||||
);
|
);
|
||||||
|
|
||||||
await saveServerDetails(serverDetails);
|
await saveServerDetails(serverDetails);
|
||||||
|
@ -149,7 +169,7 @@ class AppConfigRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createDnsRecords(
|
Future<void> createDnsRecords(
|
||||||
String? ip4,
|
String ip4,
|
||||||
CloudFlareDomain cloudFlareDomain,
|
CloudFlareDomain cloudFlareDomain,
|
||||||
) async {
|
) async {
|
||||||
var cloudflareApi = CloudflareApi();
|
var cloudflareApi = CloudflareApi();
|
||||||
|
@ -163,6 +183,11 @@ class AppConfigRepository {
|
||||||
ip4: ip4,
|
ip4: ip4,
|
||||||
cloudFlareDomain: cloudFlareDomain,
|
cloudFlareDomain: cloudFlareDomain,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await HetznerApi().createReverseDns(
|
||||||
|
ip4: ip4,
|
||||||
|
domainName: cloudFlareDomain.domainName,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isHttpServerWorking() async {
|
Future<bool> isHttpServerWorking() async {
|
||||||
|
@ -220,4 +245,27 @@ class AppConfigRepository {
|
||||||
Future<void> saveHasFinalChecked(bool value) async {
|
Future<void> saveHasFinalChecked(bool value) async {
|
||||||
await box.put(BNames.hasFinalChecked, value);
|
await box.put(BNames.hasFinalChecked, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteServer(CloudFlareDomain cloudFlareDomain) async {
|
||||||
|
var hetznerApi = HetznerApi();
|
||||||
|
var cloudFlare = CloudflareApi();
|
||||||
|
|
||||||
|
await hetznerApi.deleteSelfprivacyServerAndAllVolumes(
|
||||||
|
domainName: cloudFlareDomain.domainName,
|
||||||
|
);
|
||||||
|
|
||||||
|
await cloudFlare.removeSimilarRecords(cloudFlareDomain: cloudFlareDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteRecords() async {
|
||||||
|
await box.deleteAll([
|
||||||
|
BNames.hetznerServer,
|
||||||
|
BNames.isServerStarted,
|
||||||
|
BNames.isServerResetedFirstTime,
|
||||||
|
BNames.isServerResetedSecondTime,
|
||||||
|
BNames.hasFinalChecked,
|
||||||
|
BNames.isLoading,
|
||||||
|
]);
|
||||||
|
getIt<ApiConfigModel>().init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
part of 'app_config_cubit.dart';
|
part of 'app_config_cubit.dart';
|
||||||
|
|
||||||
class AppConfigState extends Equatable {
|
abstract class AppConfigState extends Equatable {
|
||||||
const AppConfigState({
|
const AppConfigState({
|
||||||
required this.hetznerKey,
|
required this.hetznerKey,
|
||||||
required this.cloudFlareKey,
|
required this.cloudFlareKey,
|
||||||
|
@ -11,9 +11,6 @@ class AppConfigState extends Equatable {
|
||||||
required this.isServerStarted,
|
required this.isServerStarted,
|
||||||
required this.isServerResetedFirstTime,
|
required this.isServerResetedFirstTime,
|
||||||
required this.isServerResetedSecondTime,
|
required this.isServerResetedSecondTime,
|
||||||
required this.hasFinalChecked,
|
|
||||||
required this.isLoading,
|
|
||||||
required this.error,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -26,9 +23,6 @@ class AppConfigState extends Equatable {
|
||||||
hetznerServer,
|
hetznerServer,
|
||||||
isServerStarted,
|
isServerStarted,
|
||||||
isServerResetedFirstTime,
|
isServerResetedFirstTime,
|
||||||
hasFinalChecked,
|
|
||||||
isLoading,
|
|
||||||
error,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
final String? hetznerKey;
|
final String? hetznerKey;
|
||||||
|
@ -41,42 +35,6 @@ class AppConfigState extends Equatable {
|
||||||
final bool isServerResetedFirstTime;
|
final bool isServerResetedFirstTime;
|
||||||
final bool isServerResetedSecondTime;
|
final bool isServerResetedSecondTime;
|
||||||
|
|
||||||
final bool hasFinalChecked;
|
|
||||||
|
|
||||||
final bool? isLoading;
|
|
||||||
final Exception? error;
|
|
||||||
|
|
||||||
AppConfigState copyWith({
|
|
||||||
String? hetznerKey,
|
|
||||||
String? cloudFlareKey,
|
|
||||||
BackblazeCredential? backblazeCredential,
|
|
||||||
CloudFlareDomain? cloudFlareDomain,
|
|
||||||
User? rootUser,
|
|
||||||
HetznerServerDetails? hetznerServer,
|
|
||||||
bool? isServerStarted,
|
|
||||||
bool? isServerResetedFirstTime,
|
|
||||||
bool? isServerResetedSecondTime,
|
|
||||||
bool? hasFinalChecked,
|
|
||||||
bool? isLoading,
|
|
||||||
Exception? error,
|
|
||||||
}) =>
|
|
||||||
AppConfigState(
|
|
||||||
hetznerKey: hetznerKey ?? this.hetznerKey,
|
|
||||||
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
|
|
||||||
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
|
|
||||||
cloudFlareDomain: cloudFlareDomain ?? this.cloudFlareDomain,
|
|
||||||
rootUser: rootUser ?? this.rootUser,
|
|
||||||
hetznerServer: hetznerServer ?? this.hetznerServer,
|
|
||||||
isServerStarted: isServerStarted ?? this.isServerStarted,
|
|
||||||
isServerResetedFirstTime:
|
|
||||||
isServerResetedFirstTime ?? this.isServerResetedFirstTime,
|
|
||||||
isServerResetedSecondTime:
|
|
||||||
isServerResetedSecondTime ?? this.isServerResetedSecondTime,
|
|
||||||
hasFinalChecked: hasFinalChecked ?? this.hasFinalChecked,
|
|
||||||
isLoading: isLoading ?? this.isLoading,
|
|
||||||
error: error ?? this.error,
|
|
||||||
);
|
|
||||||
|
|
||||||
bool get isHetznerFilled => hetznerKey != null;
|
bool get isHetznerFilled => hetznerKey != null;
|
||||||
bool get isCloudFlareFilled => cloudFlareKey != null;
|
bool get isCloudFlareFilled => cloudFlareKey != null;
|
||||||
bool get isBackblazeFilled => backblazeCredential != null;
|
bool get isBackblazeFilled => backblazeCredential != null;
|
||||||
|
@ -84,9 +42,19 @@ class AppConfigState extends Equatable {
|
||||||
bool get isUserFilled => rootUser != null;
|
bool get isUserFilled => rootUser != null;
|
||||||
bool get isServerCreated => hetznerServer != null;
|
bool get isServerCreated => hetznerServer != null;
|
||||||
|
|
||||||
bool get isFullyInitilized => _fulfilementList.every((el) => el!);
|
// bool get isFullyInitilized => _fulfilementList.every((el) => el!);
|
||||||
int get progress => _fulfilementList.where((el) => el!).length;
|
int get progress => _fulfilementList.where((el) => el!).length;
|
||||||
|
|
||||||
|
int get porgressBar {
|
||||||
|
if (progress < 6) {
|
||||||
|
return progress;
|
||||||
|
} else if (progress < 10) {
|
||||||
|
return 6;
|
||||||
|
} else {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<bool?> get _fulfilementList {
|
List<bool?> get _fulfilementList {
|
||||||
var res = [
|
var res = [
|
||||||
isHetznerFilled,
|
isHetznerFilled,
|
||||||
|
@ -98,32 +66,13 @@ class AppConfigState extends Equatable {
|
||||||
isServerStarted,
|
isServerStarted,
|
||||||
isServerResetedFirstTime,
|
isServerResetedFirstTime,
|
||||||
isServerResetedSecondTime,
|
isServerResetedSecondTime,
|
||||||
hasFinalChecked,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InitialAppConfigState extends AppConfigState {
|
class TimerState extends AppConfigNotFinished {
|
||||||
InitialAppConfigState()
|
|
||||||
: super(
|
|
||||||
hetznerKey: null,
|
|
||||||
cloudFlareKey: null,
|
|
||||||
backblazeCredential: null,
|
|
||||||
cloudFlareDomain: null,
|
|
||||||
rootUser: null,
|
|
||||||
hetznerServer: null,
|
|
||||||
isServerStarted: false,
|
|
||||||
isServerResetedFirstTime: false,
|
|
||||||
isServerResetedSecondTime: false,
|
|
||||||
hasFinalChecked: false,
|
|
||||||
isLoading: false,
|
|
||||||
error: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class TimerState extends AppConfigState {
|
|
||||||
TimerState({
|
TimerState({
|
||||||
required this.dataState,
|
required this.dataState,
|
||||||
this.timerStart,
|
this.timerStart,
|
||||||
|
@ -139,12 +88,10 @@ class TimerState extends AppConfigState {
|
||||||
isServerStarted: dataState.isServerStarted,
|
isServerStarted: dataState.isServerStarted,
|
||||||
isServerResetedFirstTime: dataState.isServerResetedFirstTime,
|
isServerResetedFirstTime: dataState.isServerResetedFirstTime,
|
||||||
isServerResetedSecondTime: dataState.isServerResetedSecondTime,
|
isServerResetedSecondTime: dataState.isServerResetedSecondTime,
|
||||||
hasFinalChecked: dataState.hasFinalChecked,
|
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
error: dataState.error,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final AppConfigState dataState;
|
final AppConfigNotFinished dataState;
|
||||||
final DateTime? timerStart;
|
final DateTime? timerStart;
|
||||||
final Duration? duration;
|
final Duration? duration;
|
||||||
|
|
||||||
|
@ -155,3 +102,134 @@ class TimerState extends AppConfigState {
|
||||||
duration,
|
duration,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AppConfigNotFinished extends AppConfigState {
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
AppConfigNotFinished({
|
||||||
|
String? hetznerKey,
|
||||||
|
String? cloudFlareKey,
|
||||||
|
BackblazeCredential? backblazeCredential,
|
||||||
|
CloudFlareDomain? cloudFlareDomain,
|
||||||
|
User? rootUser,
|
||||||
|
HetznerServerDetails? hetznerServer,
|
||||||
|
required bool isServerStarted,
|
||||||
|
required bool isServerResetedFirstTime,
|
||||||
|
required bool isServerResetedSecondTime,
|
||||||
|
required this.isLoading,
|
||||||
|
}) : super(
|
||||||
|
hetznerKey: hetznerKey,
|
||||||
|
cloudFlareKey: cloudFlareKey,
|
||||||
|
backblazeCredential: backblazeCredential,
|
||||||
|
cloudFlareDomain: cloudFlareDomain,
|
||||||
|
rootUser: rootUser,
|
||||||
|
hetznerServer: hetznerServer,
|
||||||
|
isServerStarted: isServerStarted,
|
||||||
|
isServerResetedFirstTime: isServerResetedFirstTime,
|
||||||
|
isServerResetedSecondTime: isServerResetedSecondTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
hetznerKey,
|
||||||
|
cloudFlareKey,
|
||||||
|
backblazeCredential,
|
||||||
|
cloudFlareDomain,
|
||||||
|
rootUser,
|
||||||
|
hetznerServer,
|
||||||
|
isServerStarted,
|
||||||
|
isServerResetedFirstTime,
|
||||||
|
isLoading
|
||||||
|
];
|
||||||
|
|
||||||
|
AppConfigNotFinished copyWith({
|
||||||
|
String? hetznerKey,
|
||||||
|
String? cloudFlareKey,
|
||||||
|
BackblazeCredential? backblazeCredential,
|
||||||
|
CloudFlareDomain? cloudFlareDomain,
|
||||||
|
User? rootUser,
|
||||||
|
HetznerServerDetails? hetznerServer,
|
||||||
|
bool? isServerStarted,
|
||||||
|
bool? isServerResetedFirstTime,
|
||||||
|
bool? isServerResetedSecondTime,
|
||||||
|
bool? isLoading,
|
||||||
|
}) =>
|
||||||
|
AppConfigNotFinished(
|
||||||
|
hetznerKey: hetznerKey ?? this.hetznerKey,
|
||||||
|
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
|
||||||
|
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
|
||||||
|
cloudFlareDomain: cloudFlareDomain ?? this.cloudFlareDomain,
|
||||||
|
rootUser: rootUser ?? this.rootUser,
|
||||||
|
hetznerServer: hetznerServer ?? this.hetznerServer,
|
||||||
|
isServerStarted: isServerStarted ?? this.isServerStarted,
|
||||||
|
isServerResetedFirstTime:
|
||||||
|
isServerResetedFirstTime ?? this.isServerResetedFirstTime,
|
||||||
|
isServerResetedSecondTime:
|
||||||
|
isServerResetedSecondTime ?? this.isServerResetedSecondTime,
|
||||||
|
isLoading: isLoading ?? this.isLoading,
|
||||||
|
);
|
||||||
|
|
||||||
|
AppConfigFinished finish() => AppConfigFinished(
|
||||||
|
hetznerKey: hetznerKey!,
|
||||||
|
cloudFlareKey: cloudFlareKey!,
|
||||||
|
backblazeCredential: backblazeCredential!,
|
||||||
|
cloudFlareDomain: cloudFlareDomain!,
|
||||||
|
rootUser: rootUser!,
|
||||||
|
hetznerServer: hetznerServer!,
|
||||||
|
isServerStarted: isServerStarted,
|
||||||
|
isServerResetedFirstTime: isServerResetedFirstTime,
|
||||||
|
isServerResetedSecondTime: isServerResetedSecondTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppConfigEmpty extends AppConfigNotFinished {
|
||||||
|
AppConfigEmpty()
|
||||||
|
: super(
|
||||||
|
hetznerKey: null,
|
||||||
|
cloudFlareKey: null,
|
||||||
|
backblazeCredential: null,
|
||||||
|
cloudFlareDomain: null,
|
||||||
|
rootUser: null,
|
||||||
|
hetznerServer: null,
|
||||||
|
isServerStarted: false,
|
||||||
|
isServerResetedFirstTime: false,
|
||||||
|
isServerResetedSecondTime: false,
|
||||||
|
isLoading: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppConfigFinished extends AppConfigState {
|
||||||
|
const AppConfigFinished({
|
||||||
|
required String hetznerKey,
|
||||||
|
required String cloudFlareKey,
|
||||||
|
required BackblazeCredential backblazeCredential,
|
||||||
|
required CloudFlareDomain cloudFlareDomain,
|
||||||
|
required User rootUser,
|
||||||
|
required HetznerServerDetails hetznerServer,
|
||||||
|
required bool isServerStarted,
|
||||||
|
required bool isServerResetedFirstTime,
|
||||||
|
required bool isServerResetedSecondTime,
|
||||||
|
}) : super(
|
||||||
|
hetznerKey: hetznerKey,
|
||||||
|
cloudFlareKey: cloudFlareKey,
|
||||||
|
backblazeCredential: backblazeCredential,
|
||||||
|
cloudFlareDomain: cloudFlareDomain,
|
||||||
|
rootUser: rootUser,
|
||||||
|
hetznerServer: hetznerServer,
|
||||||
|
isServerStarted: isServerStarted,
|
||||||
|
isServerResetedFirstTime: isServerResetedFirstTime,
|
||||||
|
isServerResetedSecondTime: isServerResetedSecondTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
hetznerKey,
|
||||||
|
cloudFlareKey,
|
||||||
|
backblazeCredential,
|
||||||
|
cloudFlareDomain,
|
||||||
|
rootUser,
|
||||||
|
hetznerServer,
|
||||||
|
isServerStarted,
|
||||||
|
isServerResetedFirstTime,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
|
||||||
|
export 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
|
||||||
|
part 'authentication_dependend_state.dart';
|
||||||
|
|
||||||
|
abstract class AppConfigDependendCubit<T extends AppConfigDependendState>
|
||||||
|
extends Cubit<T> {
|
||||||
|
AppConfigDependendCubit(
|
||||||
|
this.appConfigCubit,
|
||||||
|
T initState,
|
||||||
|
) : super(initState) {
|
||||||
|
authCubitSubscription = appConfigCubit.stream.listen(checkAuthStatus);
|
||||||
|
checkAuthStatus(appConfigCubit.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkAuthStatus(AppConfigState state) {
|
||||||
|
if (state is AppConfigFinished) {
|
||||||
|
load();
|
||||||
|
} else if (state is AppConfigEmpty) {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
late StreamSubscription authCubitSubscription;
|
||||||
|
final AppConfigCubit appConfigCubit;
|
||||||
|
|
||||||
|
void load();
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
authCubitSubscription.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
part of 'authentication_dependend_cubit.dart';
|
||||||
|
|
||||||
|
abstract class AppConfigDependendState extends Equatable {
|
||||||
|
const AppConfigDependendState();
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/backblaze.dart';
|
import 'package:selfprivacy/logic/api_maps/backblaze.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
|
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
class BackblazeFormCubit extends FormCubit {
|
class BackblazeFormCubit extends FormCubit {
|
||||||
BackblazeFormCubit(this.initializingCubit) {
|
BackblazeFormCubit(this.initializingCubit) {
|
||||||
|
@ -10,7 +11,7 @@ class BackblazeFormCubit extends FormCubit {
|
||||||
keyId = FieldCubit(
|
keyId = FieldCubit(
|
||||||
initalValue: '',
|
initalValue: '',
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('required'),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
//ValidationModel<String>(
|
//ValidationModel<String>(
|
||||||
//(s) => regExp.hasMatch(s), 'invalid key format'),
|
//(s) => regExp.hasMatch(s), 'invalid key format'),
|
||||||
//LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
|
//LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
|
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
class CloudFlareFormCubit extends FormCubit {
|
class CloudFlareFormCubit extends FormCubit {
|
||||||
CloudFlareFormCubit(this.initializingCubit) {
|
CloudFlareFormCubit(this.initializingCubit) {
|
||||||
|
@ -11,10 +12,11 @@ class CloudFlareFormCubit extends FormCubit {
|
||||||
apiKey = FieldCubit(
|
apiKey = FieldCubit(
|
||||||
initalValue: '',
|
initalValue: '',
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('required'),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>(
|
ValidationModel<String>(
|
||||||
(s) => regExp.hasMatch(s), 'invalid key format'),
|
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
|
||||||
LegnthStringValidationWithLenghShowing(40, 'length is [] shoud be 40')
|
LegnthStringValidationWithLenghShowing(
|
||||||
|
40, 'validations.length'.tr(args: ["40"]))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
class HetznerFormCubit extends FormCubit {
|
class HetznerFormCubit extends FormCubit {
|
||||||
HetznerFormCubit(this.initializingCubit) {
|
HetznerFormCubit(this.initializingCubit) {
|
||||||
|
@ -11,10 +12,10 @@ class HetznerFormCubit extends FormCubit {
|
||||||
apiKey = FieldCubit(
|
apiKey = FieldCubit(
|
||||||
initalValue: '',
|
initalValue: '',
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('required'),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>(
|
ValidationModel<String>(
|
||||||
(s) => regExp.hasMatch(s), 'invalid key format'),
|
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
|
||||||
LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
|
LegnthStringValidationWithLenghShowing(64, 'validations.length'.tr(args: ["64"]))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
class RootUserFormCubit extends FormCubit {
|
class RootUserFormCubit extends FormCubit {
|
||||||
RootUserFormCubit(this.initializingCubit) {
|
RootUserFormCubit(this.initializingCubit) {
|
||||||
|
@ -12,18 +13,20 @@ class RootUserFormCubit extends FormCubit {
|
||||||
userName = FieldCubit(
|
userName = FieldCubit(
|
||||||
initalValue: '',
|
initalValue: '',
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('required'),
|
|
||||||
ValidationModel<String>(
|
ValidationModel<String>(
|
||||||
(s) => userRegExp.hasMatch(s), 'invalid format'),
|
(s) => s.toLowerCase() == 'root', 'validations.root_name'.tr()),
|
||||||
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
|
ValidationModel<String>(
|
||||||
|
(s) => userRegExp.hasMatch(s), 'validations.invalid_format'.tr()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
password = FieldCubit(
|
password = FieldCubit(
|
||||||
initalValue: '',
|
initalValue: '',
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('required'),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>(
|
ValidationModel<String>(
|
||||||
(s) => passwordRegExp.hasMatch(s), 'invalid format'),
|
(s) => passwordRegExp.hasMatch(s), 'validations.invalid_format'.tr()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
class UserFormCubit extends FormCubit {
|
class UserFormCubit extends FormCubit {
|
||||||
UserFormCubit({
|
UserFormCubit({
|
||||||
required this.usersCubit,
|
required this.jobsCubit,
|
||||||
|
required List<User> users,
|
||||||
User? user,
|
User? user,
|
||||||
}) {
|
}) {
|
||||||
var isEdit = user != null;
|
var isEdit = user != null;
|
||||||
|
@ -18,18 +21,22 @@ class UserFormCubit extends FormCubit {
|
||||||
login = FieldCubit(
|
login = FieldCubit(
|
||||||
initalValue: isEdit ? user!.login : '',
|
initalValue: isEdit ? user!.login : '',
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('required'),
|
ValidationModel(
|
||||||
|
(login) => users.any((user) => user.login == login),
|
||||||
|
'validations.user_alredy_exist'.tr(),
|
||||||
|
),
|
||||||
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>(
|
ValidationModel<String>(
|
||||||
(s) => userRegExp.hasMatch(s), 'invalid format'),
|
(s) => userRegExp.hasMatch(s), 'validations.invalid_format'.tr()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
password = FieldCubit(
|
password = FieldCubit(
|
||||||
initalValue: isEdit ? user!.password : genPass(),
|
initalValue: isEdit ? user!.password : StringGenerators.userPassword(),
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('required'),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>(
|
ValidationModel<String>((s) => passwordRegExp.hasMatch(s),
|
||||||
(s) => passwordRegExp.hasMatch(s), 'invalid format'),
|
'validations.invalid_format'.tr()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -42,15 +49,15 @@ class UserFormCubit extends FormCubit {
|
||||||
login: login.state.value,
|
login: login.state.value,
|
||||||
password: password.state.value,
|
password: password.state.value,
|
||||||
);
|
);
|
||||||
usersCubit.addUser(user);
|
jobsCubit.addJob(CreateUserJob(user: user));
|
||||||
}
|
}
|
||||||
|
|
||||||
late FieldCubit<String> login;
|
late FieldCubit<String> login;
|
||||||
late FieldCubit<String> password;
|
late FieldCubit<String> password;
|
||||||
|
|
||||||
void genNewPassword() {
|
void genNewPassword() {
|
||||||
password.externalSetValue(genPass());
|
password.externalSetValue(StringGenerators.userPassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
late UsersCubit usersCubit;
|
final JobsCubit jobsCubit;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
|
||||||
|
|
||||||
|
import 'hetzner_metrics_repository.dart';
|
||||||
|
|
||||||
|
part 'hetzner_metrics_state.dart';
|
||||||
|
|
||||||
|
class HetznerMetricsCubit extends Cubit<HetznerMetricsState> {
|
||||||
|
HetznerMetricsCubit() : super(HetznerMetricsLoading(Period.day));
|
||||||
|
|
||||||
|
final repository = HetznerMetricsRepository();
|
||||||
|
|
||||||
|
Timer? timer;
|
||||||
|
|
||||||
|
close() {
|
||||||
|
closeTimer();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeTimer() {
|
||||||
|
if (timer != null && timer!.isActive) {
|
||||||
|
timer!.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void changePeriod(Period period) async {
|
||||||
|
closeTimer();
|
||||||
|
emit(HetznerMetricsLoading(period));
|
||||||
|
load(period);
|
||||||
|
}
|
||||||
|
|
||||||
|
void restart() async {
|
||||||
|
load(state.period);
|
||||||
|
}
|
||||||
|
|
||||||
|
void load(Period period) async {
|
||||||
|
var newState = await repository.getMetrics(period);
|
||||||
|
timer = Timer(
|
||||||
|
Duration(seconds: newState.stepInSeconds.toInt()),
|
||||||
|
() => load(newState.period),
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(newState);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
||||||
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
|
||||||
|
|
||||||
|
import 'hetzner_metrics_cubit.dart';
|
||||||
|
|
||||||
|
class HetznerMetricsRepository {
|
||||||
|
Future<HetznerMetricsLoaded> getMetrics(Period period) async {
|
||||||
|
var end = DateTime.now();
|
||||||
|
DateTime start;
|
||||||
|
|
||||||
|
switch (period) {
|
||||||
|
case Period.hour:
|
||||||
|
start = end.subtract(Duration(hours: 1));
|
||||||
|
break;
|
||||||
|
case Period.day:
|
||||||
|
start = end.subtract(Duration(days: 1));
|
||||||
|
break;
|
||||||
|
case Period.month:
|
||||||
|
start = end.subtract(Duration(days: 15));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var api = HetznerApi(hasLoger: true);
|
||||||
|
|
||||||
|
var results = await Future.wait([
|
||||||
|
api.getMetrics(start, end, 'cpu'),
|
||||||
|
api.getMetrics(start, end, 'network'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var cpuMetricsData = results[0]["metrics"];
|
||||||
|
var networkMetricsData = results[1]["metrics"];
|
||||||
|
|
||||||
|
return HetznerMetricsLoaded(
|
||||||
|
period: period,
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
stepInSeconds: cpuMetricsData["step"],
|
||||||
|
cpu: timeSeriesSerializer(cpuMetricsData, 'cpu'),
|
||||||
|
ppsIn: timeSeriesSerializer(networkMetricsData, 'network.0.pps.in'),
|
||||||
|
ppsOut: timeSeriesSerializer(networkMetricsData, 'network.0.pps.out'),
|
||||||
|
bandwidthIn:
|
||||||
|
timeSeriesSerializer(networkMetricsData, 'network.0.bandwidth.in'),
|
||||||
|
bandwidthOut: timeSeriesSerializer(
|
||||||
|
networkMetricsData,
|
||||||
|
'network.0.bandwidth.out',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TimeSeriesData> timeSeriesSerializer(
|
||||||
|
Map<String, dynamic> json, String type) {
|
||||||
|
List list = json["time_series"][type]["values"];
|
||||||
|
return list.map((el) => TimeSeriesData(el[0], double.parse(el[1]))).toList();
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
part of 'hetzner_metrics_cubit.dart';
|
||||||
|
|
||||||
|
abstract class HetznerMetricsState extends Equatable {
|
||||||
|
const HetznerMetricsState();
|
||||||
|
|
||||||
|
abstract final Period period;
|
||||||
|
}
|
||||||
|
|
||||||
|
class HetznerMetricsLoading extends HetznerMetricsState {
|
||||||
|
HetznerMetricsLoading(this.period);
|
||||||
|
final Period period;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [period];
|
||||||
|
}
|
||||||
|
|
||||||
|
class HetznerMetricsLoaded extends HetznerMetricsState {
|
||||||
|
HetznerMetricsLoaded({
|
||||||
|
required this.period,
|
||||||
|
required this.start,
|
||||||
|
required this.end,
|
||||||
|
required this.stepInSeconds,
|
||||||
|
required this.cpu,
|
||||||
|
required this.ppsIn,
|
||||||
|
required this.ppsOut,
|
||||||
|
required this.bandwidthIn,
|
||||||
|
required this.bandwidthOut,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Period period;
|
||||||
|
final DateTime start;
|
||||||
|
final DateTime end;
|
||||||
|
final num stepInSeconds;
|
||||||
|
|
||||||
|
final List<TimeSeriesData> cpu;
|
||||||
|
final List<TimeSeriesData> ppsIn;
|
||||||
|
final List<TimeSeriesData> ppsOut;
|
||||||
|
final List<TimeSeriesData> bandwidthIn;
|
||||||
|
final List<TimeSeriesData> bandwidthOut;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [period, start, end];
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/get_it/ssh.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
export 'package:provider/provider.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
part 'jobs_state.dart';
|
||||||
|
|
||||||
|
class JobsCubit extends Cubit<JobsState> {
|
||||||
|
JobsCubit({
|
||||||
|
required this.usersCubit,
|
||||||
|
required this.servicesCubit,
|
||||||
|
}) : super(JobsStateEmpty());
|
||||||
|
|
||||||
|
final api = ServerApi();
|
||||||
|
final UsersCubit usersCubit;
|
||||||
|
final ServicesCubit servicesCubit;
|
||||||
|
|
||||||
|
void addJob(Job job) {
|
||||||
|
var newJobsList = <Job>[];
|
||||||
|
if (state is JobsStateWithJobs) {
|
||||||
|
newJobsList.addAll((state as JobsStateWithJobs).jobList);
|
||||||
|
}
|
||||||
|
newJobsList.add(job);
|
||||||
|
getIt<NavigationService>().showSnackBar('jobs.jobAdded'.tr());
|
||||||
|
emit(JobsStateWithJobs(newJobsList));
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeJob(String id) {
|
||||||
|
final newState = (state as JobsStateWithJobs).removeById(id);
|
||||||
|
emit(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void createOrRemoveServiceToggleJob(ServiceToggleJob job) {
|
||||||
|
var newJobsList = <Job>[];
|
||||||
|
if (state is JobsStateWithJobs) {
|
||||||
|
newJobsList.addAll((state as JobsStateWithJobs).jobList);
|
||||||
|
}
|
||||||
|
var needToRemoveJob =
|
||||||
|
newJobsList.any((el) => el is ServiceToggleJob && el.type == job.type);
|
||||||
|
if (needToRemoveJob) {
|
||||||
|
var removingJob = newJobsList
|
||||||
|
.firstWhere(((el) => el is ServiceToggleJob && el.type == job.type));
|
||||||
|
removeJob(removingJob.id);
|
||||||
|
} else {
|
||||||
|
newJobsList.add(job);
|
||||||
|
getIt<NavigationService>().showSnackBar('jobs.jobAdded'.tr());
|
||||||
|
emit(JobsStateWithJobs(newJobsList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void createShhJobIfNotExist(CreateSSHKeyJob job) {
|
||||||
|
var newJobsList = <Job>[];
|
||||||
|
if (state is JobsStateWithJobs) {
|
||||||
|
newJobsList.addAll((state as JobsStateWithJobs).jobList);
|
||||||
|
}
|
||||||
|
var isExistInJobList = newJobsList.any((el) => el is CreateSSHKeyJob);
|
||||||
|
if (!isExistInJobList) {
|
||||||
|
newJobsList.add(job);
|
||||||
|
getIt<NavigationService>().showSnackBar('jobs.jobAdded'.tr());
|
||||||
|
emit(JobsStateWithJobs(newJobsList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> applyAll() async {
|
||||||
|
if (state is JobsStateWithJobs) {
|
||||||
|
var jobs = (state as JobsStateWithJobs).jobList;
|
||||||
|
emit(JobsStateLoading());
|
||||||
|
var newUsers = <User>[];
|
||||||
|
var hasServiceJobs = false;
|
||||||
|
for (var job in jobs) {
|
||||||
|
if (job is CreateUserJob) {
|
||||||
|
newUsers.add(job.user);
|
||||||
|
await api.createUser(job.user);
|
||||||
|
} else if (job is ServiceToggleJob) {
|
||||||
|
hasServiceJobs = true;
|
||||||
|
await api.switchService(job.type, job.needToTurnOn);
|
||||||
|
}
|
||||||
|
if (job is CreateSSHKeyJob) {
|
||||||
|
await getIt<SSHModel>().generateKeys();
|
||||||
|
api.sendSsh(getIt<SSHModel>().savedPubKey!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usersCubit.addUsers(newUsers);
|
||||||
|
await api.apply();
|
||||||
|
if (hasServiceJobs) {
|
||||||
|
await servicesCubit.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(JobsStateEmpty());
|
||||||
|
|
||||||
|
getIt<NavigationService>().navigator!.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
part of 'jobs_cubit.dart';
|
||||||
|
|
||||||
|
abstract class JobsState extends Equatable {
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class JobsStateLoading extends JobsState {}
|
||||||
|
|
||||||
|
class JobsStateEmpty extends JobsState {}
|
||||||
|
|
||||||
|
class JobsStateWithJobs extends JobsState {
|
||||||
|
JobsStateWithJobs(this.jobList);
|
||||||
|
final List<Job> jobList;
|
||||||
|
|
||||||
|
JobsState removeById(String id) {
|
||||||
|
var newJobsList = jobList.where((element) => element.id != id).toList();
|
||||||
|
|
||||||
|
if (newJobsList.isEmpty) {
|
||||||
|
return JobsStateEmpty();
|
||||||
|
}
|
||||||
|
return JobsStateWithJobs(newJobsList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => jobList;
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||||
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
|
|
||||||
|
part 'services_state.dart';
|
||||||
|
|
||||||
|
class ServicesCubit extends AppConfigDependendCubit<ServicesState> {
|
||||||
|
ServicesCubit(AppConfigCubit appConfigCubit)
|
||||||
|
: super(appConfigCubit, ServicesState.allOff());
|
||||||
|
|
||||||
|
Box box = Hive.box(BNames.servicesState);
|
||||||
|
final api = ServerApi();
|
||||||
|
Future<void> load() async {
|
||||||
|
if (appConfigCubit.state is AppConfigFinished) {
|
||||||
|
var statuses = await api.servicesPowerCheck();
|
||||||
|
emit(
|
||||||
|
ServicesState(
|
||||||
|
isPasswordManagerEnable: statuses[ServiceTypes.passwordManager]!,
|
||||||
|
isCloudEnable: statuses[ServiceTypes.cloud]!,
|
||||||
|
isGitEnable: statuses[ServiceTypes.git]!,
|
||||||
|
isSocialNetworkEnable: statuses[ServiceTypes.socialNetwork]!,
|
||||||
|
isVpnEnable: statuses[ServiceTypes.vpn]!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clear() async {
|
||||||
|
box.clear();
|
||||||
|
emit(ServicesState.allOff());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
part of 'services_cubit.dart';
|
||||||
|
|
||||||
|
class ServicesState extends AppConfigDependendState {
|
||||||
|
const ServicesState({
|
||||||
|
required this.isPasswordManagerEnable,
|
||||||
|
required this.isCloudEnable,
|
||||||
|
required this.isGitEnable,
|
||||||
|
required this.isSocialNetworkEnable,
|
||||||
|
required this.isVpnEnable,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isPasswordManagerEnable;
|
||||||
|
final bool isCloudEnable;
|
||||||
|
final bool isGitEnable;
|
||||||
|
final bool isSocialNetworkEnable;
|
||||||
|
final bool isVpnEnable;
|
||||||
|
|
||||||
|
factory ServicesState.allOff() => ServicesState(
|
||||||
|
isPasswordManagerEnable: false,
|
||||||
|
isCloudEnable: false,
|
||||||
|
isGitEnable: false,
|
||||||
|
isSocialNetworkEnable: false,
|
||||||
|
isVpnEnable: false,
|
||||||
|
);
|
||||||
|
factory ServicesState.allOn() => ServicesState(
|
||||||
|
isPasswordManagerEnable: true,
|
||||||
|
isCloudEnable: true,
|
||||||
|
isGitEnable: true,
|
||||||
|
isSocialNetworkEnable: true,
|
||||||
|
isVpnEnable: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
ServicesState enableList(
|
||||||
|
List<ServiceTypes> list,
|
||||||
|
) =>
|
||||||
|
ServicesState(
|
||||||
|
isPasswordManagerEnable: list.contains(ServiceTypes.passwordManager)
|
||||||
|
? true
|
||||||
|
: isPasswordManagerEnable,
|
||||||
|
isCloudEnable: list.contains(ServiceTypes.cloud) ? true : isCloudEnable,
|
||||||
|
isGitEnable:
|
||||||
|
list.contains(ServiceTypes.git) ? true : isPasswordManagerEnable,
|
||||||
|
isSocialNetworkEnable: list.contains(ServiceTypes.socialNetwork)
|
||||||
|
? true
|
||||||
|
: isPasswordManagerEnable,
|
||||||
|
isVpnEnable:
|
||||||
|
list.contains(ServiceTypes.vpn) ? true : isPasswordManagerEnable,
|
||||||
|
);
|
||||||
|
|
||||||
|
ServicesState disableList(
|
||||||
|
List<ServiceTypes> list,
|
||||||
|
) =>
|
||||||
|
ServicesState(
|
||||||
|
isPasswordManagerEnable: list.contains(ServiceTypes.passwordManager)
|
||||||
|
? false
|
||||||
|
: isPasswordManagerEnable,
|
||||||
|
isCloudEnable:
|
||||||
|
list.contains(ServiceTypes.cloud) ? false : isCloudEnable,
|
||||||
|
isGitEnable:
|
||||||
|
list.contains(ServiceTypes.git) ? false : isPasswordManagerEnable,
|
||||||
|
isSocialNetworkEnable: list.contains(ServiceTypes.socialNetwork)
|
||||||
|
? false
|
||||||
|
: isPasswordManagerEnable,
|
||||||
|
isVpnEnable:
|
||||||
|
list.contains(ServiceTypes.vpn) ? false : isPasswordManagerEnable,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [
|
||||||
|
isPasswordManagerEnable,
|
||||||
|
isCloudEnable,
|
||||||
|
isGitEnable,
|
||||||
|
isSocialNetworkEnable,
|
||||||
|
isVpnEnable
|
||||||
|
];
|
||||||
|
|
||||||
|
bool isEnableByType(ServiceTypes type) {
|
||||||
|
switch (type) {
|
||||||
|
case ServiceTypes.passwordManager:
|
||||||
|
return isPasswordManagerEnable;
|
||||||
|
case ServiceTypes.cloud:
|
||||||
|
return isCloudEnable;
|
||||||
|
case ServiceTypes.socialNetwork:
|
||||||
|
return isSocialNetworkEnable;
|
||||||
|
case ServiceTypes.git:
|
||||||
|
return isGitEnable;
|
||||||
|
case ServiceTypes.vpn:
|
||||||
|
return isVpnEnable;
|
||||||
|
default:
|
||||||
|
throw Exception('wrong state');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,42 +1,37 @@
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
export 'package:provider/provider.dart';
|
export 'package:provider/provider.dart';
|
||||||
|
|
||||||
part 'users_state.dart';
|
part 'users_state.dart';
|
||||||
|
|
||||||
class UsersCubit extends Cubit<UsersState> {
|
class UsersCubit extends Cubit<UsersState> {
|
||||||
UsersCubit() : super(UsersState([]));
|
UsersCubit() : super(UsersState(<User>[]));
|
||||||
|
Box<User> box = Hive.box<User>(BNames.users);
|
||||||
|
|
||||||
void addUser(User user) {
|
void load() async {
|
||||||
var users = [...state.users];
|
var loadedUsers = box.values.toList();
|
||||||
users.add(user);
|
|
||||||
|
|
||||||
emit(UsersState(users));
|
if (loadedUsers.isNotEmpty) {
|
||||||
|
emit(UsersState(loadedUsers));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(User? user) {
|
void addUsers(List<User> users) async {
|
||||||
|
var newUserList = <User>[...state.users, ...users];
|
||||||
|
|
||||||
|
await box.addAll(users);
|
||||||
|
emit(UsersState(newUserList));
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(User user) async {
|
||||||
var users = [...state.users];
|
var users = [...state.users];
|
||||||
|
var index = users.indexOf(user);
|
||||||
users.remove(user);
|
users.remove(user);
|
||||||
|
await box.deleteAt(index);
|
||||||
|
|
||||||
emit(UsersState(users));
|
emit(UsersState(users));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// final initMockUsers = <User>[
|
|
||||||
// User(login: 'Heartbreaking.Goose', password: genPass()),
|
|
||||||
// User(login: 'Alma.lawson', password: genPass()),
|
|
||||||
// User(login: 'Bee.gees', password: genPass()),
|
|
||||||
// User(login: 'Bim.jennings', password: genPass()),
|
|
||||||
// User(login: 'Debra.holt', password: genPass()),
|
|
||||||
// User(login: 'Georgia.young', password: genPass()),
|
|
||||||
// User(login: 'Kenzi.lawson', password: genPass()),
|
|
||||||
// User(login: 'Le.jennings', password: genPass()),
|
|
||||||
// User(login: 'Kirill.Zh', password: genPass()),
|
|
||||||
// User(login: 'Tina.Bolton', password: genPass()),
|
|
||||||
// User(login: 'Rebekah.Lynn', password: genPass()),
|
|
||||||
// User(login: 'Aleena.Armstrong', password: genPass()),
|
|
||||||
// User(login: 'Rosemary.Williams', password: genPass()),
|
|
||||||
// User(login: 'Sullivan.Nixon', password: genPass()),
|
|
||||||
// User(login: 'Aleena.Armstrong', password: genPass()),
|
|
||||||
// ];
|
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
|
import 'package:selfprivacy/config/text_themes.dart';
|
||||||
|
|
||||||
class NavigationService {
|
class NavigationService {
|
||||||
|
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
|
||||||
|
GlobalKey<ScaffoldMessengerState>();
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
NavigatorState? get navigator => navigatorKey.currentState;
|
NavigatorState? get navigator => navigatorKey.currentState;
|
||||||
|
|
||||||
void showPopUpDialog(AlertDialog dialog) {
|
void showPopUpDialog(AlertDialog dialog) {
|
||||||
|
@ -13,4 +18,14 @@ class NavigationService {
|
||||||
builder: (_) => dialog,
|
builder: (_) => dialog,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showSnackBar(String text) {
|
||||||
|
final state = scaffoldMessengerKey.currentState!;
|
||||||
|
final snack = SnackBar(
|
||||||
|
backgroundColor: BrandColors.black.withOpacity(0.8),
|
||||||
|
content: Text(text, style: buttonTitleText),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
);
|
||||||
|
state.showSnackBar(snack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pointycastle/pointycastle.dart';
|
||||||
|
import 'package:rsa_encrypt/rsa_encrypt.dart';
|
||||||
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
|
import 'package:pointycastle/api.dart' as crypto;
|
||||||
|
import 'package:ssh_key/ssh_key.dart' as ssh_key;
|
||||||
|
|
||||||
|
class SSHModel {
|
||||||
|
Box _box = Hive.box(BNames.sshConfig);
|
||||||
|
String? savedPrivateKey;
|
||||||
|
String? savedPubKey;
|
||||||
|
|
||||||
|
Future<void> generateKeys() async {
|
||||||
|
var helper = RsaKeyHelper();
|
||||||
|
crypto.AsymmetricKeyPair pair =
|
||||||
|
await helper.computeRSAKeyPair(helper.getSecureRandom());
|
||||||
|
var privateKey = pair.privateKey as RSAPrivateKey;
|
||||||
|
var publicKey = pair.publicKey as RSAPublicKey;
|
||||||
|
|
||||||
|
savedPrivateKey = helper.encodePrivateKeyToPemPKCS1(privateKey);
|
||||||
|
savedPubKey = publicKey.encode(ssh_key.PubKeyEncoding.openSsh);
|
||||||
|
|
||||||
|
await _box.put(BNames.sshPrivateKey, savedPrivateKey);
|
||||||
|
await _box.put(BNames.sshPublicKey, savedPubKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() async {
|
||||||
|
savedPrivateKey = _box.get(BNames.sshPrivateKey);
|
||||||
|
savedPubKey = _box.get(BNames.sshPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isSSHKeyGenerated => savedPrivateKey != null && savedPubKey != null;
|
||||||
|
|
||||||
|
Future<void> clear() async {
|
||||||
|
savedPrivateKey = null;
|
||||||
|
savedPubKey = null;
|
||||||
|
await _box.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
class TimeSeriesData {
|
||||||
|
TimeSeriesData(
|
||||||
|
this.secondsSinceEpoch,
|
||||||
|
this.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
final int secondsSinceEpoch;
|
||||||
|
DateTime get time =>
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(secondsSinceEpoch * 1000);
|
||||||
|
final double value;
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
import 'user.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class Job extends Equatable {
|
||||||
|
Job({
|
||||||
|
String? id,
|
||||||
|
required this.title,
|
||||||
|
}) : id = id ?? StringGenerators.simpleId();
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [id, title];
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateUserJob extends Job {
|
||||||
|
CreateUserJob({
|
||||||
|
required this.user,
|
||||||
|
}) : super(title: '${"jobs.createUser".tr()} ${user.login}');
|
||||||
|
|
||||||
|
final User user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [id, title, user];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceToggleJob extends Job {
|
||||||
|
ServiceToggleJob({
|
||||||
|
required this.type,
|
||||||
|
required this.needToTurnOn,
|
||||||
|
}) : super(
|
||||||
|
title:
|
||||||
|
'${needToTurnOn ? "jobs.serviceTurnOn".tr() : "jobs.serviceTurnOff".tr()} ${type.title}');
|
||||||
|
|
||||||
|
final ServiceTypes type;
|
||||||
|
final bool needToTurnOn;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [id, title, type, needToTurnOn];
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateSSHKeyJob extends Job {
|
||||||
|
CreateSSHKeyJob() : super(title: '${"more.create_ssh_key".tr()}');
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [id, title];
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:crypt/crypt.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:selfprivacy/utils/color_utils.dart';
|
import 'package:selfprivacy/utils/color_utils.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:selfprivacy/utils/crypto.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
part 'user.g.dart';
|
part 'user.g.dart';
|
||||||
|
|
||||||
|
@ -25,7 +26,10 @@ class User extends Equatable {
|
||||||
|
|
||||||
Color get color => stringToColor(login);
|
Color get color => stringToColor(login);
|
||||||
|
|
||||||
String get hashPassword => convertToSha512Hash(password);
|
Crypt get hashPassword => Crypt.sha512(
|
||||||
|
password,
|
||||||
|
salt: StringGenerators.passwordSalt(),
|
||||||
|
);
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
return login;
|
return login;
|
||||||
|
|
|
@ -16,30 +16,30 @@ import 'config/localization.dart';
|
||||||
import 'logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await HiveConfig.init();
|
await HiveConfig.init();
|
||||||
|
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
Bloc.observer = SimpleBlocObserver();
|
Bloc.observer = SimpleBlocObserver();
|
||||||
Wakelock.enable();
|
Wakelock.enable();
|
||||||
await getItSetup();
|
await getItSetup();
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
|
|
||||||
runApp(
|
runApp(MyApp());
|
||||||
Localization(
|
|
||||||
child: BlocAndProviderConfig(
|
|
||||||
child: MyApp(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
AppSettingsState appSettings = context.watch<AppSettingsCubit>().state;
|
return Localization(
|
||||||
|
child: BlocAndProviderConfig(
|
||||||
|
child: Builder(builder: (context) {
|
||||||
|
var appSettings = context.watch<AppSettingsCubit>().state;
|
||||||
|
|
||||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
|
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
|
scaffoldMessengerKey:
|
||||||
|
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||||
navigatorKey: getIt.get<NavigationService>().navigatorKey,
|
navigatorKey: getIt.get<NavigationService>().navigatorKey,
|
||||||
localizationsDelegates: context.localizationDelegates,
|
localizationsDelegates: context.localizationDelegates,
|
||||||
supportedLocales: context.supportedLocales,
|
supportedLocales: context.supportedLocales,
|
||||||
|
@ -54,10 +54,14 @@ class MyApp extends StatelessWidget {
|
||||||
Widget error = Text('...rendering error...');
|
Widget error = Text('...rendering error...');
|
||||||
if (widget is Scaffold || widget is Navigator)
|
if (widget is Scaffold || widget is Navigator)
|
||||||
error = Scaffold(body: Center(child: error));
|
error = Scaffold(body: Center(child: error));
|
||||||
ErrorWidget.builder = (FlutterErrorDetails errorDetails) => error;
|
ErrorWidget.builder =
|
||||||
|
(FlutterErrorDetails errorDetails) => error;
|
||||||
return widget!;
|
return widget!;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
|
|
||||||
|
class BrandBottomSheet extends StatelessWidget {
|
||||||
|
const BrandBottomSheet({
|
||||||
|
Key? key,
|
||||||
|
required this.child,
|
||||||
|
this.isExpended = false,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final bool isExpended;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var mainHeight = MediaQuery.of(context).size.height -
|
||||||
|
MediaQuery.of(context).padding.top -
|
||||||
|
100;
|
||||||
|
late Widget innerWidget;
|
||||||
|
if (isExpended) {
|
||||||
|
innerWidget = Scaffold(
|
||||||
|
body: child,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final ThemeData themeData = Theme.of(context);
|
||||||
|
|
||||||
|
innerWidget = Material(
|
||||||
|
color: themeData.scaffoldBackgroundColor,
|
||||||
|
child: IntrinsicHeight(child: child),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxHeight: mainHeight + 4 + 6),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
height: 4,
|
||||||
|
width: 30,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
color: BrandColors.gray4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 6),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxHeight: mainHeight),
|
||||||
|
child: innerWidget,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,14 +10,14 @@ class BrandButton {
|
||||||
static rised({
|
static rised({
|
||||||
Key? key,
|
Key? key,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
String? title,
|
String? text,
|
||||||
Widget? child,
|
Widget? child,
|
||||||
}) {
|
}) {
|
||||||
assert(title == null || child == null, 'required title or child');
|
assert(text == null || child == null, 'required title or child');
|
||||||
assert(title != null || child != null, 'required title or child');
|
assert(text != null || child != null, 'required title or child');
|
||||||
return _RisedButton(
|
return _RisedButton(
|
||||||
key: key,
|
key: key,
|
||||||
title: title,
|
title: text,
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
import 'package:selfprivacy/utils/extensions/elevation_extension.dart';
|
|
||||||
|
|
||||||
class BrandCard extends StatelessWidget {
|
|
||||||
const BrandCard({
|
|
||||||
Key? key,
|
|
||||||
this.child,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final Widget? child;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? BrandColors.black
|
|
||||||
: BrandColors.white,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
).ev8,
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 20,
|
|
||||||
vertical: 15,
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
|
|
||||||
|
class BrandCards {
|
||||||
|
static Widget big({required Widget child}) => _BrandCard(
|
||||||
|
child: child,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 15,
|
||||||
|
),
|
||||||
|
shadow: bigShadow,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
);
|
||||||
|
static Widget small({required Widget child}) => _BrandCard(
|
||||||
|
child: child,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
shadow: bigShadow,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BrandCard extends StatelessWidget {
|
||||||
|
const _BrandCard({
|
||||||
|
Key? key,
|
||||||
|
required this.child,
|
||||||
|
required this.padding,
|
||||||
|
required this.shadow,
|
||||||
|
required this.borderRadius,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
final List<BoxShadow> shadow;
|
||||||
|
final BorderRadius borderRadius;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? BrandColors.black
|
||||||
|
: BrandColors.white,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
boxShadow: shadow,
|
||||||
|
),
|
||||||
|
padding: padding,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final bigShadow = [
|
||||||
|
BoxShadow(
|
||||||
|
offset: Offset(0, 4),
|
||||||
|
blurRadius: 8,
|
||||||
|
color: Colors.black.withOpacity(.08),
|
||||||
|
)
|
||||||
|
];
|
|
@ -1,16 +1,19 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/pre_styled_buttons/pre_styled_buttons.dart';
|
||||||
|
|
||||||
class BrandHeader extends StatelessWidget {
|
class BrandHeader extends StatelessWidget {
|
||||||
const BrandHeader({
|
const BrandHeader({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.hasBackButton = false,
|
this.hasBackButton = false,
|
||||||
|
this.hasFlashButton = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final bool hasBackButton;
|
final bool hasBackButton;
|
||||||
|
final bool hasFlashButton;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -31,6 +34,8 @@ class BrandHeader extends StatelessWidget {
|
||||||
SizedBox(width: 10),
|
SizedBox(width: 10),
|
||||||
],
|
],
|
||||||
BrandText.h4(title),
|
BrandText.h4(title),
|
||||||
|
Spacer(),
|
||||||
|
if (hasFlashButton) PreStyledButtons.flash(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
class BrandLoader {
|
||||||
|
static horizontal() => _HorizontalLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HorizontalLoader extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text('basis.wait'.tr()),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
LinearProgressIndicator(minHeight: 3),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,9 @@ class _BrandMarkdownState extends State<BrandMarkdown> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var isDark = Theme.of(context).brightness == Brightness.dark;
|
var isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
var markdown = MarkdownStyleSheet(
|
var markdown = MarkdownStyleSheet(
|
||||||
p: defaultTextStyle,
|
p: defaultTextStyle.copyWith(
|
||||||
|
color: isDark ? BrandColors.white : null,
|
||||||
|
),
|
||||||
h1: headline1Style.copyWith(
|
h1: headline1Style.copyWith(
|
||||||
color: isDark ? BrandColors.white : null,
|
color: isDark ? BrandColors.white : null,
|
||||||
),
|
),
|
||||||
|
@ -54,6 +56,7 @@ class _BrandMarkdownState extends State<BrandMarkdown> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return Markdown(
|
return Markdown(
|
||||||
|
shrinkWrap: true,
|
||||||
styleSheet: markdown,
|
styleSheet: markdown,
|
||||||
onTapLink: (String text, String? href, String title) {
|
onTapLink: (String text, String? href, String title) {
|
||||||
if (href != null) {
|
if (href != null) {
|
||||||
|
|
|
@ -1,63 +1,63 @@
|
||||||
import 'package:flutter/material.dart';
|
// import 'package:flutter/material.dart';
|
||||||
|
|
||||||
var navigatorKey = GlobalKey<NavigatorState>();
|
// var navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
class BrandModalSheet extends StatelessWidget {
|
// class BrandModalSheet extends StatelessWidget {
|
||||||
const BrandModalSheet({
|
// const BrandModalSheet({
|
||||||
Key? key,
|
// Key? key,
|
||||||
this.child,
|
// this.child,
|
||||||
}) : super(key: key);
|
// }) : super(key: key);
|
||||||
|
|
||||||
final Widget? child;
|
// final Widget? child;
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context) {
|
// Widget build(BuildContext context) {
|
||||||
return DraggableScrollableSheet(
|
// return DraggableScrollableSheet(
|
||||||
minChildSize: 0.95,
|
// minChildSize: 1,
|
||||||
initialChildSize: 1,
|
// initialChildSize: 1,
|
||||||
maxChildSize: 1,
|
// maxChildSize: 1,
|
||||||
builder: (context, scrollController) {
|
// builder: (context, scrollController) {
|
||||||
return SingleChildScrollView(
|
// return SingleChildScrollView(
|
||||||
controller: scrollController,
|
// controller: scrollController,
|
||||||
physics: ClampingScrollPhysics(),
|
// physics: ClampingScrollPhysics(),
|
||||||
child: Container(
|
// child: Container(
|
||||||
child: Column(
|
// child: Column(
|
||||||
children: [
|
// children: [
|
||||||
GestureDetector(
|
// GestureDetector(
|
||||||
onTap: () => Navigator.of(context).pop(),
|
// onTap: () => Navigator.of(context).pop(),
|
||||||
behavior: HitTestBehavior.opaque,
|
// behavior: HitTestBehavior.opaque,
|
||||||
child: Container(
|
// child: Container(
|
||||||
width: double.infinity,
|
// width: double.infinity,
|
||||||
child: Center(
|
// child: Center(
|
||||||
child: Padding(
|
// child: Padding(
|
||||||
padding: EdgeInsets.only(top: 132, bottom: 6),
|
// padding: EdgeInsets.only(top: 132, bottom: 6),
|
||||||
child: Container(
|
// child: Container(
|
||||||
height: 4,
|
// height: 4,
|
||||||
width: 30,
|
// width: 30,
|
||||||
decoration: BoxDecoration(
|
// decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(2),
|
// borderRadius: BorderRadius.circular(2),
|
||||||
color: Color(0xFFE3E3E3).withOpacity(0.65),
|
// color: Color(0xFFE3E3E3).withOpacity(0.65),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
Container(
|
// Container(
|
||||||
constraints: BoxConstraints(
|
// constraints: BoxConstraints(
|
||||||
minHeight: MediaQuery.of(context).size.height - 132,
|
// minHeight: MediaQuery.of(context).size.height - 132,
|
||||||
maxHeight: MediaQuery.of(context).size.height - 132,
|
// maxHeight: MediaQuery.of(context).size.height - 132,
|
||||||
),
|
// ),
|
||||||
decoration: BoxDecoration(
|
// decoration: BoxDecoration(
|
||||||
borderRadius:
|
// borderRadius:
|
||||||
BorderRadius.vertical(top: Radius.circular(20)),
|
// BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
// color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
),
|
// ),
|
||||||
width: double.infinity,
|
// width: double.infinity,
|
||||||
child: child),
|
// child: child),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
|
|
||||||
|
class BrandRadio extends StatelessWidget {
|
||||||
|
BrandRadio({
|
||||||
|
Key? key,
|
||||||
|
required this.isChecked,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final bool isChecked;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: EdgeInsets.all(2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: _getBorder(),
|
||||||
|
),
|
||||||
|
child: isChecked
|
||||||
|
? Container(
|
||||||
|
height: 10,
|
||||||
|
width: 10,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: BrandColors.primary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxBorder? _getBorder() {
|
||||||
|
return Border.all(
|
||||||
|
color: isChecked ? BrandColors.primary : BrandColors.gray1,
|
||||||
|
width: 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_radio/brand_radio.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
|
|
||||||
|
class BrandRadioTile extends StatelessWidget {
|
||||||
|
const BrandRadioTile({
|
||||||
|
Key? key,
|
||||||
|
required this.isChecked,
|
||||||
|
required this.text,
|
||||||
|
required this.onPress,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final bool isChecked;
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
final VoidCallback onPress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onPress,
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(2),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
BrandRadio(
|
||||||
|
isChecked: isChecked,
|
||||||
|
),
|
||||||
|
SizedBox(width: 9),
|
||||||
|
BrandText.h5(text)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
|
|
||||||
|
class BrandSwitch extends StatelessWidget {
|
||||||
|
const BrandSwitch({
|
||||||
|
Key? key,
|
||||||
|
required this.onChanged,
|
||||||
|
required this.value,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final ValueChanged<bool> onChanged;
|
||||||
|
final bool value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Switch(
|
||||||
|
activeColor: BrandColors.green1,
|
||||||
|
activeTrackColor: BrandColors.green2,
|
||||||
|
value: value,
|
||||||
|
onChanged: onChanged,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/text_themes.dart';
|
import 'package:selfprivacy/config/text_themes.dart';
|
||||||
|
export 'package:selfprivacy/utils/extensions/text_extensions.dart';
|
||||||
|
|
||||||
enum TextType {
|
enum TextType {
|
||||||
h1, // right now only at onboarding and opened providers
|
h1, // right now only at onboarding and opened providers
|
||||||
|
@ -12,7 +13,8 @@ enum TextType {
|
||||||
medium,
|
medium,
|
||||||
small,
|
small,
|
||||||
onboardingTitle,
|
onboardingTitle,
|
||||||
buttonTitleText // risen button title text,
|
buttonTitleText, // risen button title text,
|
||||||
|
h4Underlined,
|
||||||
}
|
}
|
||||||
|
|
||||||
class BrandText extends StatelessWidget {
|
class BrandText extends StatelessWidget {
|
||||||
|
@ -24,6 +26,7 @@ class BrandText extends StatelessWidget {
|
||||||
this.overflow,
|
this.overflow,
|
||||||
this.softWrap,
|
this.softWrap,
|
||||||
this.textAlign,
|
this.textAlign,
|
||||||
|
this.maxLines,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String? text;
|
final String? text;
|
||||||
|
@ -32,6 +35,7 @@ class BrandText extends StatelessWidget {
|
||||||
final TextOverflow? overflow;
|
final TextOverflow? overflow;
|
||||||
final bool? softWrap;
|
final bool? softWrap;
|
||||||
final TextAlign? textAlign;
|
final TextAlign? textAlign;
|
||||||
|
final int? maxLines;
|
||||||
|
|
||||||
factory BrandText.h1(
|
factory BrandText.h1(
|
||||||
String? text, {
|
String? text, {
|
||||||
|
@ -51,10 +55,16 @@ class BrandText extends StatelessWidget {
|
||||||
type: TextType.onboardingTitle,
|
type: TextType.onboardingTitle,
|
||||||
style: style,
|
style: style,
|
||||||
);
|
);
|
||||||
factory BrandText.h2(String? text, {TextStyle? style}) => BrandText(
|
factory BrandText.h2(
|
||||||
|
String? text, {
|
||||||
|
TextStyle? style,
|
||||||
|
TextAlign? textAlign,
|
||||||
|
}) =>
|
||||||
|
BrandText(
|
||||||
text,
|
text,
|
||||||
type: TextType.h2,
|
type: TextType.h2,
|
||||||
style: style,
|
style: style,
|
||||||
|
textAlign: textAlign,
|
||||||
);
|
);
|
||||||
factory BrandText.h3(String text, {TextStyle? style, TextAlign? textAlign}) =>
|
factory BrandText.h3(String text, {TextStyle? style, TextAlign? textAlign}) =>
|
||||||
BrandText(
|
BrandText(
|
||||||
|
@ -73,6 +83,24 @@ class BrandText extends StatelessWidget {
|
||||||
text,
|
text,
|
||||||
type: TextType.h4,
|
type: TextType.h4,
|
||||||
style: style,
|
style: style,
|
||||||
|
softWrap: true,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
|
textAlign: textAlign,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory BrandText.h4Underlined(
|
||||||
|
String? text, {
|
||||||
|
TextStyle? style,
|
||||||
|
TextAlign? textAlign,
|
||||||
|
}) =>
|
||||||
|
BrandText(
|
||||||
|
text,
|
||||||
|
type: TextType.h4Underlined,
|
||||||
|
style: style,
|
||||||
|
softWrap: true,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -120,7 +148,6 @@ class BrandText extends StatelessWidget {
|
||||||
Text build(BuildContext context) {
|
Text build(BuildContext context) {
|
||||||
TextStyle style;
|
TextStyle style;
|
||||||
var isDark = Theme.of(context).brightness == Brightness.dark;
|
var isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TextType.h1:
|
case TextType.h1:
|
||||||
style = isDark
|
style = isDark
|
||||||
|
@ -142,6 +169,11 @@ class BrandText extends StatelessWidget {
|
||||||
? headline4Style.copyWith(color: Colors.white)
|
? headline4Style.copyWith(color: Colors.white)
|
||||||
: headline4Style;
|
: headline4Style;
|
||||||
break;
|
break;
|
||||||
|
case TextType.h4Underlined:
|
||||||
|
style = isDark
|
||||||
|
? headline4UnderlinedStyle.copyWith(color: Colors.white)
|
||||||
|
: headline4UnderlinedStyle;
|
||||||
|
break;
|
||||||
case TextType.h5:
|
case TextType.h5:
|
||||||
style = isDark
|
style = isDark
|
||||||
? headline5Style.copyWith(color: Colors.white)
|
? headline5Style.copyWith(color: Colors.white)
|
||||||
|
@ -179,6 +211,7 @@ class BrandText extends StatelessWidget {
|
||||||
return Text(
|
return Text(
|
||||||
text!,
|
text!,
|
||||||
style: style,
|
style: style,
|
||||||
|
maxLines: maxLines,
|
||||||
overflow: overflow,
|
overflow: overflow,
|
||||||
softWrap: softWrap,
|
softWrap: softWrap,
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
|
|
|
@ -70,9 +70,9 @@ class _BrandTimerState extends State<BrandTimer> {
|
||||||
_durationToString(DateTime.now().difference(widget.startDateTime));
|
_durationToString(DateTime.now().difference(widget.startDateTime));
|
||||||
|
|
||||||
String _durationToString(Duration duration) {
|
String _durationToString(Duration duration) {
|
||||||
|
var timeLeft = widget.duration - duration;
|
||||||
String twoDigits(int n) => n.toString().padLeft(2, "0");
|
String twoDigits(int n) => n.toString().padLeft(2, "0");
|
||||||
String twoDigitSeconds =
|
String twoDigitSeconds = twoDigits(timeLeft.inSeconds);
|
||||||
twoDigits(widget.duration.inSeconds - duration.inSeconds.remainder(60));
|
|
||||||
|
|
||||||
return "timer.sec".tr(args: [twoDigitSeconds]);
|
return "timer.sec".tr(args: [twoDigitSeconds]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.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/logic/cubit/jobs/jobs_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
|
|
||||||
|
class JobsContent extends StatelessWidget {
|
||||||
|
const JobsContent({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<JobsCubit, JobsState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
late final List<Widget> widgets;
|
||||||
|
if (state is JobsStateEmpty) {
|
||||||
|
widgets = [
|
||||||
|
SizedBox(height: 80),
|
||||||
|
Center(child: BrandText.body1('jobs.empty'.tr())),
|
||||||
|
];
|
||||||
|
} else if (state is JobsStateLoading) {
|
||||||
|
widgets = [
|
||||||
|
SizedBox(height: 80),
|
||||||
|
BrandLoader.horizontal(),
|
||||||
|
];
|
||||||
|
} else if (state is JobsStateWithJobs) {
|
||||||
|
widgets = [
|
||||||
|
...state.jobList
|
||||||
|
.map(
|
||||||
|
(j) => Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: BrandCards.small(
|
||||||
|
child: Text(j.title),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
primary: BrandColors.red1,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
context.read<JobsCubit>().removeJob(j.id),
|
||||||
|
child: Text('basis.remove'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
BrandButton.rised(
|
||||||
|
onPressed: () => context.read<JobsCubit>().applyAll(),
|
||||||
|
text: 'jobs.start'.tr(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return ListView(
|
||||||
|
padding: paddingH15V0,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 15),
|
||||||
|
Center(
|
||||||
|
child: BrandText.h2(
|
||||||
|
'jobs.title'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
...widgets
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,9 +33,7 @@ class NotReadyCard extends StatelessWidget {
|
||||||
child: Text(
|
child: Text(
|
||||||
'not_ready_card.2'.tr(),
|
'not_ready_card.2'.tr(),
|
||||||
style: body1Style.copyWith(
|
style: body1Style.copyWith(
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: Colors.white,
|
||||||
? Colors.black
|
|
||||||
: BrandColors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
// height: 1.1,
|
// height: 1.1,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
|
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/ui/components/pre_styled_buttons.dart';
|
import 'package:selfprivacy/ui/components/pre_styled_buttons/pre_styled_buttons.dart';
|
||||||
|
|
||||||
class OnePage extends StatelessWidget {
|
class OnePage extends StatelessWidget {
|
||||||
const OnePage({
|
const OnePage({
|
||||||
|
@ -16,8 +16,7 @@ class OnePage extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return Scaffold(
|
||||||
child: Scaffold(
|
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -45,7 +44,6 @@ class OnePage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
import 'package:flutter/material.dart';
|
part of 'pre_styled_buttons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
|
|
||||||
class PreStyledButtons {
|
|
||||||
static Widget close({
|
|
||||||
required VoidCallback onPress,
|
|
||||||
}) =>
|
|
||||||
_CloseButton(onPress: onPress);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CloseButton extends StatelessWidget {
|
class _CloseButton extends StatelessWidget {
|
||||||
const _CloseButton({Key? key, required this.onPress}) : super(key: key);
|
const _CloseButton({Key? key, required this.onPress}) : super(key: key);
|
|
@ -0,0 +1,86 @@
|
||||||
|
part of 'pre_styled_buttons.dart';
|
||||||
|
|
||||||
|
class _BrandFlashButton extends StatefulWidget {
|
||||||
|
_BrandFlashButton({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_BrandFlashButtonState createState() => _BrandFlashButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BrandFlashButtonState extends State<_BrandFlashButton>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation _colorTween;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_animationController =
|
||||||
|
AnimationController(vsync: this, duration: Duration(milliseconds: 800));
|
||||||
|
_colorTween = ColorTween(
|
||||||
|
begin: BrandColors.black,
|
||||||
|
end: BrandColors.primary,
|
||||||
|
).animate(_animationController);
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance!.addPostFrameCallback(_afterLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _afterLayout(_) {
|
||||||
|
if (Theme.of(context).brightness == Brightness.dark) {
|
||||||
|
setState(() {
|
||||||
|
_colorTween = ColorTween(
|
||||||
|
begin: BrandColors.white,
|
||||||
|
end: BrandColors.primary,
|
||||||
|
).animate(_animationController);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wasPrevStateIsEmpty = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocListener<JobsCubit, JobsState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (wasPrevStateIsEmpty && state is! JobsStateEmpty) {
|
||||||
|
wasPrevStateIsEmpty = false;
|
||||||
|
_animationController.forward();
|
||||||
|
} else if (!wasPrevStateIsEmpty && state is JobsStateEmpty) {
|
||||||
|
wasPrevStateIsEmpty = true;
|
||||||
|
|
||||||
|
_animationController.reverse();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showBrandBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => BrandBottomSheet(
|
||||||
|
isExpended: true,
|
||||||
|
child: JobsContent(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: AnimatedBuilder(
|
||||||
|
animation: _colorTween,
|
||||||
|
builder: (context, child) {
|
||||||
|
var v = _animationController.value;
|
||||||
|
var icon = v > 0.5 ? Ionicons.flash : Ionicons.flash_outline;
|
||||||
|
return Transform.scale(
|
||||||
|
scale: 1 + (v < 0.5 ? v : 1 - v) * 2,
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
color: _colorTween.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:ionicons/ionicons.dart';
|
||||||
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/jobs_content/jobs_content.dart';
|
||||||
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
|
|
||||||
|
part 'close.dart';
|
||||||
|
part 'flash.dart';
|
||||||
|
|
||||||
|
class PreStyledButtons {
|
||||||
|
static Widget close({
|
||||||
|
required VoidCallback onPress,
|
||||||
|
}) =>
|
||||||
|
_CloseButton(onPress: onPress);
|
||||||
|
|
||||||
|
static Widget flash() => _BrandFlashButton();
|
||||||
|
}
|
|
@ -53,9 +53,9 @@ class _ProgressBarState extends State<ProgressBar> {
|
||||||
width: 10,
|
width: 10,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
even.add(
|
odd.add(
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 10,
|
width: 20,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||||
|
|
||||||
|
Future<T?> showBrandBottomSheet<T>({
|
||||||
|
required BuildContext context,
|
||||||
|
required WidgetBuilder builder,
|
||||||
|
}) =>
|
||||||
|
showCupertinoModalBottomSheet<T>(
|
||||||
|
builder: builder,
|
||||||
|
barrierColor: Colors.black45,
|
||||||
|
context: context,
|
||||||
|
shadow: BoxShadow(color: Colors.transparent),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
);
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/initializing/backblaze_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/initializing/backblaze_form_cubit.dart';
|
||||||
|
@ -9,10 +10,10 @@ import 'package:selfprivacy/logic/cubit/forms/initializing/hetzner_form_cubit.da
|
||||||
import 'package:selfprivacy/logic/cubit/forms/initializing/root_user_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/initializing/root_user_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_card/brand_card.dart';
|
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.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_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
|
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
|
||||||
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
||||||
|
@ -35,20 +36,21 @@ class InitializingPage extends StatelessWidget {
|
||||||
() => _stepCheck(cubit),
|
() => _stepCheck(cubit),
|
||||||
() => _stepCheck(cubit),
|
() => _stepCheck(cubit),
|
||||||
() => _stepCheck(cubit),
|
() => _stepCheck(cubit),
|
||||||
() => Container(child: Text('Everythigng is initialized'))
|
() => Container(child: Center(child: Text('initializing.finish'.tr())))
|
||||||
][cubit.state.progress]();
|
][cubit.state.progress]();
|
||||||
return BlocListener<AppConfigCubit, AppConfigState>(
|
return BlocListener<AppConfigCubit, AppConfigState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.isFullyInitilized) {
|
if (cubit.state is AppConfigFinished) {
|
||||||
Navigator.of(context).pushReplacement(materialRoute(RootPage()));
|
Navigator.of(context).pushReplacement(materialRoute(RootPage()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: ListView(
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: brandPagePadding2.copyWith(top: 10, bottom: 10),
|
padding: paddingH15V0.copyWith(top: 10, bottom: 10),
|
||||||
child: ProgressBar(
|
child: ProgressBar(
|
||||||
steps: [
|
steps: [
|
||||||
'Hetzner',
|
'Hetzner',
|
||||||
|
@ -57,12 +59,9 @@ class InitializingPage extends StatelessWidget {
|
||||||
'Domain',
|
'Domain',
|
||||||
'User',
|
'User',
|
||||||
'Server',
|
'Server',
|
||||||
' ✅',
|
'✅ Check',
|
||||||
' ✅',
|
|
||||||
' ✅',
|
|
||||||
' ✅',
|
|
||||||
],
|
],
|
||||||
activeIndex: cubit.state.progress,
|
activeIndex: cubit.state.porgressBar,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_addCard(
|
_addCard(
|
||||||
|
@ -71,8 +70,17 @@ class InitializingPage extends StatelessWidget {
|
||||||
child: actualPage,
|
child: actualPage,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BrandButton.text(
|
ConstrainedBox(
|
||||||
title: cubit.state.isFullyInitilized
|
constraints: BoxConstraints(
|
||||||
|
minHeight: MediaQuery.of(context).size.height -
|
||||||
|
MediaQuery.of(context).padding.top -
|
||||||
|
MediaQuery.of(context).padding.bottom -
|
||||||
|
566,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: BrandButton.text(
|
||||||
|
title: cubit.state is AppConfigFinished
|
||||||
? 'basis.close'.tr()
|
? 'basis.close'.tr()
|
||||||
: 'basis.later'.tr(),
|
: 'basis.later'.tr(),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -80,11 +88,14 @@ class InitializingPage extends StatelessWidget {
|
||||||
materialRoute(RootPage()),
|
materialRoute(RootPage()),
|
||||||
(predicate) => false,
|
(predicate) => false,
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +129,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
onPressed: formCubitState.isSubmitting
|
onPressed: formCubitState.isSubmitting
|
||||||
? null
|
? null
|
||||||
: () => context.read<HetznerFormCubit>().trySubmit(),
|
: () => context.read<HetznerFormCubit>().trySubmit(),
|
||||||
title: 'basis.connect'.tr(),
|
text: 'basis.connect'.tr(),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandButton.text(
|
BrandButton.text(
|
||||||
|
@ -173,7 +184,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
onPressed: formCubitState.isSubmitting
|
onPressed: formCubitState.isSubmitting
|
||||||
? null
|
? null
|
||||||
: () => context.read<CloudFlareFormCubit>().trySubmit(),
|
: () => context.read<CloudFlareFormCubit>().trySubmit(),
|
||||||
title: 'basis.connect'.tr(),
|
text: 'basis.connect'.tr(),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandButton.text(
|
BrandButton.text(
|
||||||
|
@ -224,7 +235,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
onPressed: formCubitState.isSubmitting
|
onPressed: formCubitState.isSubmitting
|
||||||
? null
|
? null
|
||||||
: () => context.read<BackblazeFormCubit>().trySubmit(),
|
: () => context.read<BackblazeFormCubit>().trySubmit(),
|
||||||
title: 'basis.connect'.tr(),
|
text: 'basis.connect'.tr(),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandButton.text(
|
BrandButton.text(
|
||||||
|
@ -316,7 +327,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
SizedBox(height: 30),
|
SizedBox(height: 30),
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed: () => context.read<DomainSetupCubit>().saveDomain(),
|
onPressed: () => context.read<DomainSetupCubit>().saveDomain(),
|
||||||
title: 'initializing.10'.tr(),
|
text: 'initializing.10'.tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
|
@ -341,8 +352,10 @@ class InitializingPage extends StatelessWidget {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Spacer(),
|
BrandText.h2('initializing.22'.tr()),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
|
BrandText.body2('initializing.23'.tr()),
|
||||||
|
Spacer(),
|
||||||
CubitFormTextField(
|
CubitFormTextField(
|
||||||
formFieldCubit: context.read<RootUserFormCubit>().userName,
|
formFieldCubit: context.read<RootUserFormCubit>().userName,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
@ -384,7 +397,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
onPressed: formCubitState.isSubmitting
|
onPressed: formCubitState.isSubmitting
|
||||||
? null
|
? null
|
||||||
: () => context.read<RootUserFormCubit>().trySubmit(),
|
: () => context.read<RootUserFormCubit>().trySubmit(),
|
||||||
title: 'basis.connect'.tr(),
|
text: 'basis.connect'.tr(),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandButton.text(
|
BrandButton.text(
|
||||||
|
@ -398,7 +411,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _stepServer(AppConfigCubit appConfigCubit) {
|
Widget _stepServer(AppConfigCubit appConfigCubit) {
|
||||||
var isLoading = appConfigCubit.state.isLoading;
|
var isLoading = (appConfigCubit.state as AppConfigNotFinished).isLoading;
|
||||||
return Builder(builder: (context) {
|
return Builder(builder: (context) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -409,10 +422,10 @@ class InitializingPage extends StatelessWidget {
|
||||||
BrandText.body2('initializing.11'.tr()),
|
BrandText.body2('initializing.11'.tr()),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed: isLoading!
|
onPressed: isLoading
|
||||||
? null
|
? null
|
||||||
: () => appConfigCubit.createServerAndSetDnsRecords(),
|
: () => appConfigCubit.createServerAndSetDnsRecords(),
|
||||||
title: isLoading ? 'basis.loading'.tr() : 'initializing.11'.tr(),
|
text: isLoading ? 'basis.loading'.tr() : 'initializing.11'.tr(),
|
||||||
),
|
),
|
||||||
Spacer(flex: 2),
|
Spacer(flex: 2),
|
||||||
BrandButton.text(
|
BrandButton.text(
|
||||||
|
@ -425,28 +438,36 @@ class InitializingPage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _stepCheck(AppConfigCubit appConfigCubit) {
|
Widget _stepCheck(AppConfigCubit appConfigCubit) {
|
||||||
assert(appConfigCubit.state is TimerState, 'wronge state');
|
assert(appConfigCubit.state is AppConfigNotFinished, 'wronge state');
|
||||||
var state = appConfigCubit.state as TimerState;
|
var state = appConfigCubit.state as TimerState;
|
||||||
|
late int doneCount;
|
||||||
late String? text;
|
late String? text;
|
||||||
if (state.isServerResetedSecondTime) {
|
if (state.isServerResetedSecondTime) {
|
||||||
text = 'initializing.13'.tr();
|
text = 'initializing.13'.tr();
|
||||||
|
doneCount = 3;
|
||||||
} else if (state.isServerResetedFirstTime) {
|
} else if (state.isServerResetedFirstTime) {
|
||||||
text = 'initializing.21'.tr();
|
text = 'initializing.21'.tr();
|
||||||
|
doneCount = 2;
|
||||||
} else if (state.isServerStarted) {
|
} else if (state.isServerStarted) {
|
||||||
text = 'initializing.14'.tr();
|
text = 'initializing.14'.tr();
|
||||||
|
doneCount = 1;
|
||||||
} else if (state.isServerCreated) {
|
} else if (state.isServerCreated) {
|
||||||
text = 'initializing.15'.tr();
|
text = 'initializing.15'.tr();
|
||||||
|
doneCount = 0;
|
||||||
}
|
}
|
||||||
return Builder(builder: (context) {
|
return Builder(builder: (context) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
SizedBox(height: 15),
|
||||||
|
BrandText.h4(
|
||||||
|
'initializing.checks'.tr(args: [doneCount.toString(), "4"]),
|
||||||
|
),
|
||||||
Spacer(flex: 2),
|
Spacer(flex: 2),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandText.body2(text),
|
BrandText.body2(text),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
if (!state.isLoading!)
|
if (!state.isLoading)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
BrandText.body2('initializing.16'.tr()),
|
BrandText.body2('initializing.16'.tr()),
|
||||||
|
@ -456,7 +477,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (state.isLoading!) BrandText.body2('initializing.17'.tr()),
|
if (state.isLoading) BrandText.body2('initializing.17'.tr()),
|
||||||
Spacer(
|
Spacer(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
),
|
),
|
||||||
|
@ -472,8 +493,8 @@ class InitializingPage extends StatelessWidget {
|
||||||
Widget _addCard(Widget child) {
|
Widget _addCard(Widget child) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 450,
|
height: 450,
|
||||||
padding: brandPagePadding2,
|
padding: paddingH15V0,
|
||||||
child: BrandCard(child: child),
|
child: BrandCards.big(child: child),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -485,12 +506,14 @@ class _HowHetzner extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BrandModalSheet(
|
return BrandBottomSheet(
|
||||||
|
isExpended: true,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: brandPagePadding2.copyWith(top: 25),
|
padding: paddingH15V0,
|
||||||
child: BrandMarkdown(
|
child: BrandMarkdown(
|
||||||
fileName: 'how_hetzner',
|
fileName: 'how_hetzner',
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
|
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_switch/brand_switch.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
import 'package:selfprivacy/utils/named_font_weight.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
@ -27,12 +28,12 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||||
child: Builder(builder: (context) {
|
child: Builder(builder: (context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
child:
|
child: BrandHeader(
|
||||||
BrandHeader(title: 'more.settings.title'.tr(), hasBackButton: true),
|
title: 'more.settings.title'.tr(), hasBackButton: true),
|
||||||
preferredSize: Size.fromHeight(52),
|
preferredSize: Size.fromHeight(52),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: brandPagePadding2,
|
padding: paddingH15V0,
|
||||||
children: [
|
children: [
|
||||||
BrandDivider(),
|
BrandDivider(),
|
||||||
Container(
|
Container(
|
||||||
|
@ -52,9 +53,7 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 5),
|
SizedBox(width: 5),
|
||||||
Switch(
|
BrandSwitch(
|
||||||
activeColor: BrandColors.green1,
|
|
||||||
activeTrackColor: BrandColors.green2,
|
|
||||||
value: Theme.of(context).brightness == Brightness.dark,
|
value: Theme.of(context).brightness == Brightness.dark,
|
||||||
onChanged: (value) => context
|
onChanged: (value) => context
|
||||||
.read<AppSettingsCubit>()
|
.read<AppSettingsCubit>()
|
||||||
|
@ -116,16 +115,89 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
deleteServer(context)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget deleteServer(BuildContext context) {
|
||||||
|
var isDisabled =
|
||||||
|
context.watch<AppConfigCubit>().state.hetznerServer == null;
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(top: 20, bottom: 5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
|
||||||
|
)),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: _TextColumn(
|
||||||
|
title: 'more.settings.5'.tr(),
|
||||||
|
value: 'more.settings.6'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
primary: BrandColors.red1,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'basis.delete'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: BrandColors.white,
|
||||||
|
fontWeight: NamedFontWeight.demiBold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: isDisabled
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) {
|
||||||
|
return BrandAlert(
|
||||||
|
title: 'modals.3'.tr(),
|
||||||
|
contentText: 'modals.6'.tr(),
|
||||||
|
acitons: [
|
||||||
|
ActionButton(
|
||||||
|
text: 'modals.7'.tr(),
|
||||||
|
isRed: true,
|
||||||
|
onPressed: () async {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await context
|
||||||
|
.read<AppConfigCubit>()
|
||||||
|
.serverDelete();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}),
|
||||||
|
ActionButton(
|
||||||
|
text: 'basis.cancel'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TextColumn extends StatelessWidget {
|
class _TextColumn extends StatelessWidget {
|
||||||
|
|
|
@ -18,7 +18,7 @@ class InfoPage extends StatelessWidget {
|
||||||
preferredSize: Size.fromHeight(52),
|
preferredSize: Size.fromHeight(52),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: brandPagePadding2,
|
padding: paddingH15V0,
|
||||||
children: [
|
children: [
|
||||||
BrandDivider(),
|
BrandDivider(),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
|
|
|
@ -1,15 +1,29 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:ionicons/ionicons.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/config/text_themes.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/get_it/ssh.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
|
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
||||||
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
||||||
import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import 'about/about.dart';
|
import 'about/about.dart';
|
||||||
import 'app_settings/app_setting.dart';
|
import 'app_settings/app_setting.dart';
|
||||||
|
@ -21,15 +35,21 @@ class MorePage extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var jobsCubit = context.watch<JobsCubit>();
|
||||||
|
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
child: BrandHeader(title: 'basis.more'.tr()),
|
child: BrandHeader(
|
||||||
|
title: 'basis.more'.tr(),
|
||||||
|
hasFlashButton: true,
|
||||||
|
),
|
||||||
preferredSize: Size.fromHeight(52),
|
preferredSize: Size.fromHeight(52),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: brandPagePadding2,
|
padding: paddingH15V0,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
BrandDivider(),
|
BrandDivider(),
|
||||||
|
@ -63,6 +83,73 @@ class MorePage extends StatelessWidget {
|
||||||
iconData: BrandIcons.terminal,
|
iconData: BrandIcons.terminal,
|
||||||
goTo: Console(),
|
goTo: Console(),
|
||||||
),
|
),
|
||||||
|
_MoreMenuTapItem(
|
||||||
|
title: 'more.create_ssh_key'.tr(),
|
||||||
|
iconData: Ionicons.key_outline,
|
||||||
|
onTap: isReady
|
||||||
|
? () {
|
||||||
|
if (getIt<SSHModel>().isSSHKeyGenerated) {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return _SSHExitsDetails(
|
||||||
|
onShareTap: () {
|
||||||
|
Share.share(
|
||||||
|
getIt<SSHModel>().savedPrivateKey!);
|
||||||
|
},
|
||||||
|
onDeleteTap: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) {
|
||||||
|
return BrandAlert(
|
||||||
|
title: 'modals.3'.tr(),
|
||||||
|
contentText:
|
||||||
|
'more.delete_ssh_text'.tr(),
|
||||||
|
acitons: [
|
||||||
|
ActionButton(
|
||||||
|
text: 'more.yes_delete'.tr(),
|
||||||
|
isRed: true,
|
||||||
|
onPressed: () {
|
||||||
|
getIt<SSHModel>().clear();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}),
|
||||||
|
ActionButton(
|
||||||
|
text: 'basis.cancel'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onCopyTap: () {
|
||||||
|
Clipboard.setData(ClipboardData(
|
||||||
|
text: getIt<SSHModel>()
|
||||||
|
.savedPrivateKey!));
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar('more.copied_ssh'.tr());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return _MoreDetails(
|
||||||
|
title: 'more.create_ssh_key'.tr(),
|
||||||
|
icon: Ionicons.key_outline,
|
||||||
|
onTap: () {
|
||||||
|
jobsCubit.createShhJobIfNotExist(
|
||||||
|
CreateSSHKeyJob());
|
||||||
|
},
|
||||||
|
text: 'more.generate_key_text'.tr(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -72,6 +159,150 @@ class MorePage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _SSHExitsDetails extends StatelessWidget {
|
||||||
|
const _SSHExitsDetails({
|
||||||
|
Key? key,
|
||||||
|
required this.onDeleteTap,
|
||||||
|
required this.onShareTap,
|
||||||
|
required this.onCopyTap,
|
||||||
|
}) : super(key: key);
|
||||||
|
final Function onDeleteTap;
|
||||||
|
final Function onShareTap;
|
||||||
|
final Function onCopyTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var textStyle = body1Style.copyWith(
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? Colors.white
|
||||||
|
: BrandColors.black);
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
width: 350,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: paddingH15V30,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
'more.ssh_key_exist_text'.tr(),
|
||||||
|
style: textStyle,
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Container(
|
||||||
|
child: BrandButton.text(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
onShareTap();
|
||||||
|
},
|
||||||
|
title: 'more.share'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: BrandButton.text(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
onDeleteTap();
|
||||||
|
},
|
||||||
|
title: 'basis.delete'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
child: BrandButton.text(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
onCopyTap();
|
||||||
|
},
|
||||||
|
title: 'more.copy_buffer'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoreDetails extends StatelessWidget {
|
||||||
|
const _MoreDetails({
|
||||||
|
Key? key,
|
||||||
|
required this.icon,
|
||||||
|
required this.title,
|
||||||
|
required this.onTap,
|
||||||
|
required this.text,
|
||||||
|
}) : super(key: key);
|
||||||
|
final String title;
|
||||||
|
final IconData icon;
|
||||||
|
final Function onTap;
|
||||||
|
final String text;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var textStyle = body1Style.copyWith(
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? Colors.white
|
||||||
|
: BrandColors.black);
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
width: 350,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: paddingH15V30,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
IconStatusMask(
|
||||||
|
status: StateType.stable,
|
||||||
|
child: Icon(icon, size: 40, color: Colors.white),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
BrandText.h2(title),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style: textStyle,
|
||||||
|
),
|
||||||
|
SizedBox(height: 40),
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
child: BrandButton.rised(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
onTap();
|
||||||
|
},
|
||||||
|
text: 'more.generate_key'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _NavItem extends StatelessWidget {
|
class _NavItem extends StatelessWidget {
|
||||||
const _NavItem({
|
const _NavItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -88,7 +319,54 @@ class _NavItem extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => Navigator.of(context).push(materialRoute(goTo)),
|
onTap: () => Navigator.of(context).push(materialRoute(goTo)),
|
||||||
child: Container(
|
child: _MoreMenuItem(
|
||||||
|
iconData: iconData,
|
||||||
|
title: title,
|
||||||
|
isActive: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoreMenuTapItem extends StatelessWidget {
|
||||||
|
const _MoreMenuTapItem({
|
||||||
|
Key? key,
|
||||||
|
required this.iconData,
|
||||||
|
required this.onTap,
|
||||||
|
required this.title,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final IconData iconData;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
final String title;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: _MoreMenuItem(
|
||||||
|
isActive: onTap != null,
|
||||||
|
iconData: iconData,
|
||||||
|
title: title,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoreMenuItem extends StatelessWidget {
|
||||||
|
const _MoreMenuItem({
|
||||||
|
Key? key,
|
||||||
|
required this.iconData,
|
||||||
|
required this.title,
|
||||||
|
required this.isActive,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final IconData iconData;
|
||||||
|
final String title;
|
||||||
|
final bool isActive;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
padding: EdgeInsets.symmetric(vertical: 24),
|
padding: EdgeInsets.symmetric(vertical: 24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
|
@ -100,18 +378,23 @@ class _NavItem extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
BrandText.body1(title),
|
BrandText.body1(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: isActive ? null : Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 56,
|
width: 56,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
iconData,
|
iconData,
|
||||||
size: 20,
|
size: 20,
|
||||||
|
color: isActive ? null : Colors.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
curve: Curves.easeIn,
|
curve: Curves.easeIn,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
title: 'basis.next'.tr(),
|
text: 'basis.next'.tr(),
|
||||||
),
|
),
|
||||||
SizedBox(height: 30),
|
SizedBox(height: 30),
|
||||||
],
|
],
|
||||||
|
@ -129,7 +129,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
(route) => false,
|
(route) => false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
title: 'basis.got_it'.tr(),
|
text: 'basis.got_it'.tr(),
|
||||||
),
|
),
|
||||||
SizedBox(height: 30),
|
SizedBox(height: 30),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/provider.dart';
|
import 'package:selfprivacy/logic/models/provider.dart';
|
||||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_card/brand_card.dart';
|
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
||||||
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/one_page/one_page.dart';
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/server_details.dart';
|
import 'package:selfprivacy/ui/pages/server_details/server_details.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/slide_bottom.dart';
|
|
||||||
import 'package:selfprivacy/utils/ui_helpers.dart';
|
import 'package:selfprivacy/utils/ui_helpers.dart';
|
||||||
|
|
||||||
var navigatorKey = GlobalKey<NavigatorState>();
|
var navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
@ -29,7 +28,7 @@ class ProvidersPage extends StatefulWidget {
|
||||||
class _ProvidersPageState extends State<ProvidersPage> {
|
class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
|
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
|
||||||
|
|
||||||
final cards = ProviderType.values
|
final cards = ProviderType.values
|
||||||
.map(
|
.map(
|
||||||
|
@ -46,11 +45,14 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
.toList();
|
.toList();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
child: BrandHeader(title: 'providers.page_title'.tr()),
|
child: BrandHeader(
|
||||||
|
title: 'providers.page_title'.tr(),
|
||||||
|
hasFlashButton: true,
|
||||||
|
),
|
||||||
preferredSize: Size.fromHeight(52),
|
preferredSize: Size.fromHeight(52),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: brandPagePadding2,
|
padding: paddingH15V0,
|
||||||
children: [
|
children: [
|
||||||
if (!isReady) ...[
|
if (!isReady) ...[
|
||||||
NotReadyCard(),
|
NotReadyCard(),
|
||||||
|
@ -73,7 +75,7 @@ class _Card extends StatelessWidget {
|
||||||
String? message;
|
String? message;
|
||||||
late String stableText;
|
late String stableText;
|
||||||
late VoidCallback onTap;
|
late VoidCallback onTap;
|
||||||
|
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
|
||||||
AppConfigState appConfig = context.watch<AppConfigCubit>().state;
|
AppConfigState appConfig = context.watch<AppConfigCubit>().state;
|
||||||
|
|
||||||
var domainName =
|
var domainName =
|
||||||
|
@ -82,27 +84,23 @@ class _Card extends StatelessWidget {
|
||||||
switch (provider.type) {
|
switch (provider.type) {
|
||||||
case ProviderType.server:
|
case ProviderType.server:
|
||||||
title = 'providers.server.card_title'.tr();
|
title = 'providers.server.card_title'.tr();
|
||||||
stableText = 'providers.domain.status'.tr();
|
|
||||||
|
|
||||||
stableText = 'providers.server.status'.tr();
|
stableText = 'providers.server.status'.tr();
|
||||||
onTap = () => Navigator.of(context).push(
|
onTap = () => showBrandBottomSheet(
|
||||||
SlideBottomRoute(
|
context: context,
|
||||||
OnePage(
|
builder: (context) => BrandBottomSheet(
|
||||||
title: title,
|
isExpended: true,
|
||||||
child: ServerDetails(),
|
child: ServerDetails(),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ProviderType.domain:
|
case ProviderType.domain:
|
||||||
title = 'providers.domain.card_title'.tr();
|
title = 'providers.domain.card_title'.tr();
|
||||||
message = domainName;
|
message = domainName;
|
||||||
stableText = 'providers.domain.status'.tr();
|
stableText = 'providers.domain.status'.tr();
|
||||||
|
|
||||||
onTap = () => showModalBottomSheet<void>(
|
onTap = () => showBrandBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return _ProviderDetails(
|
return _ProviderDetails(
|
||||||
provider: provider,
|
provider: provider,
|
||||||
|
@ -129,8 +127,8 @@ class _Card extends StatelessWidget {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: isReady ? onTap : null,
|
||||||
child: BrandCard(
|
child: BrandCards.big(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
@ -180,8 +178,7 @@ class _ProviderDetails extends StatelessWidget {
|
||||||
children = [
|
children = [
|
||||||
BrandText.body1('providers.domain.bottom_sheet.1'.tr()),
|
BrandText.body1('providers.domain.bottom_sheet.1'.tr()),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandText.body1(
|
BrandText.body1(domainName),
|
||||||
'providers.domain.bottom_sheet.2'.tr(args: [domainName, 'Date'])),
|
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandText.body1('providers.domain.status'.tr()),
|
BrandText.body1('providers.domain.status'.tr()),
|
||||||
];
|
];
|
||||||
|
@ -198,37 +195,31 @@ class _ProviderDetails extends StatelessWidget {
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return BrandModalSheet(
|
return BrandBottomSheet(
|
||||||
child: Navigator(
|
child: SafeArea(
|
||||||
key: navigatorKey,
|
child: Column(
|
||||||
initialRoute: '/',
|
|
||||||
onGenerateRoute: (_) {
|
|
||||||
return materialRoute(
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 40),
|
SizedBox(height: 40),
|
||||||
Padding(
|
Padding(
|
||||||
padding: brandPagePadding2,
|
padding: paddingH15V0,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
IconStatusMask(
|
IconStatusMask(
|
||||||
status: provider.state,
|
status: provider.state,
|
||||||
child:
|
child: Icon(provider.icon, size: 40, color: Colors.white),
|
||||||
Icon(provider.icon, size: 40, color: Colors.white),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandText.h1(title),
|
BrandText.h1(title),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
...children
|
...children,
|
||||||
|
SizedBox(height: 30),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ class SettingsPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: brandPagePadding2,
|
padding: paddingH15V0,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandHeader(title: 'basis.settings'.tr(), hasBackButton: true),
|
BrandHeader(title: 'basis.settings'.tr(), hasBackButton: true),
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
part of 'server_details.dart';
|
||||||
|
|
||||||
|
class _Chart extends StatelessWidget {
|
||||||
|
const _Chart({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
|
var cubit = context.watch<HetznerMetricsCubit>();
|
||||||
|
var period = cubit.state.period;
|
||||||
|
var state = cubit.state;
|
||||||
|
List<Widget> charts;
|
||||||
|
if (state is HetznerMetricsLoading) {
|
||||||
|
charts = [
|
||||||
|
Container(
|
||||||
|
height: 200,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text('basis.loading'.tr()),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
} else if (state is HetznerMetricsLoaded) {
|
||||||
|
charts = [
|
||||||
|
Legend(color: Colors.red, text: 'CPU %'),
|
||||||
|
getCpuChart(state),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
BrandText.small('Public Network interface packets per sec'),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Legend(color: Colors.red, text: 'IN'),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Legend(color: Colors.green, text: 'OUT'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
getPpsChart(state),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
BrandText.small('Public Network interface bytes per sec'),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Legend(color: Colors.red, text: 'IN'),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Legend(color: Colors.green, text: 'OUT'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
getBandwidthChart(state),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw 'wrong state';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
BrandRadioTile(
|
||||||
|
isChecked: period == Period.month,
|
||||||
|
text: 'providers.server.chart.month'.tr(),
|
||||||
|
onPress: () => cubit.changePeriod(Period.month),
|
||||||
|
),
|
||||||
|
BrandRadioTile(
|
||||||
|
isChecked: period == Period.day,
|
||||||
|
text: 'providers.server.chart.day'.tr(),
|
||||||
|
onPress: () => cubit.changePeriod(Period.day),
|
||||||
|
),
|
||||||
|
BrandRadioTile(
|
||||||
|
isChecked: period == Period.hour,
|
||||||
|
text: 'providers.server.chart.hour'.tr(),
|
||||||
|
onPress: () => cubit.changePeriod(Period.hour),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...charts,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getCpuChart(HetznerMetricsLoaded state) {
|
||||||
|
var data = state.cpu;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 200,
|
||||||
|
child: CpuChart(data, state.period, state.start),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getPpsChart(HetznerMetricsLoaded state) {
|
||||||
|
var ppsIn = state.ppsIn;
|
||||||
|
var ppsOut = state.ppsOut;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 200,
|
||||||
|
child: NetworkChart(
|
||||||
|
[ppsIn, ppsOut],
|
||||||
|
state.period,
|
||||||
|
state.start,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getBandwidthChart(HetznerMetricsLoaded state) {
|
||||||
|
var ppsIn = state.bandwidthIn;
|
||||||
|
var ppsOut = state.bandwidthOut;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 200,
|
||||||
|
child: NetworkChart(
|
||||||
|
[ppsIn, ppsOut],
|
||||||
|
state.period,
|
||||||
|
state.start,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Legend extends StatelessWidget {
|
||||||
|
const Legend({
|
||||||
|
Key? key,
|
||||||
|
required this.color,
|
||||||
|
required this.text,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
final Color color;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
_ColoredBox(color: color),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
BrandText.small(text),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ColoredBox extends StatelessWidget {
|
||||||
|
const _ColoredBox({
|
||||||
|
Key? key,
|
||||||
|
required this.color,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.3),
|
||||||
|
border: Border.all(
|
||||||
|
color: color,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|