Compare commits

...

30 Commits

Author SHA1 Message Date
tester.nicolai f8f43b6cee fixed RU app text 2021-09-23 01:25:47 +03:00
Illia Chub a7e7d0ff05 Merge pull request 'ssh' (#37) from ssh into master
Reviewed-on: kherel/selfprivacy.org.app#37
2021-09-16 17:38:04 +03:00
Kherel 4942f67f37 update 2021-09-15 16:37:22 +02:00
Kherel d0023e5718 update 2021-09-15 15:15:54 +02:00
Illia Chub e064598c73 Merge pull request 'services-switchers' (#32) from services-switchers into master
Reviewed-on: kherel/selfprivacy.org.app#32
2021-09-07 16:30:46 +03:00
Kherel 90d64d8f51 update 2021-09-02 21:32:07 +02:00
Kherel 26607251d9 update 2021-08-29 17:02:51 +02:00
Kherel 84e9259ec2 update 2021-08-29 15:54:28 +02:00
Kherel 94a0e22b15 update 2021-08-29 11:50:24 +02:00
Kherel 1a8a4e7270 update 2021-08-18 12:44:46 +02:00
Kherel 1202e4ad53 change password generator 2021-08-18 11:36:40 +02:00
Illia Chub 8ea1b28d73 Updated application description 2021-08-06 16:27:13 +03:00
Illia Chub 8f8714e07f Added F-Droid changelogs for the latest update 2021-07-29 16:30:13 +03:00
Illia Chub 63d3057125 Merge pull request 'fix build version' (#29) from jobs into master
Reviewed-on: kherel/selfprivacy.org.app#29
2021-07-29 16:22:34 +03:00
Kherel dfc6f67ee3 fix build version 2021-07-29 15:21:18 +02:00
Illia Chub b130960113 Merge pull request 'jobs' (#28) from jobs into master
Reviewed-on: kherel/selfprivacy.org.app#28
2021-07-29 15:10:31 +03:00
Kherel 5dea5234de fix 2021-07-29 14:09:10 +02:00
Kherel 3a5353dbf4 add validation 2021-07-29 11:34:26 +02:00
Kherel 65c6a0b870 fix translations 2021-07-29 08:06:29 +02:00
Kherel 933e8ffb90 fix 2021-07-29 07:29:33 +02:00
Kherel 1c352fd771 update 2021-07-29 07:28:46 +02:00
Kherel Kechil f53ad044c1 finish 2021-07-29 07:24:42 +02:00
Kherel 21611e63c7 commit 2021-06-20 23:08:52 +02:00
Kherel d8393f75ea update 2021-06-20 09:18:25 +02:00
Kherel 9e8fdf2965 update 2021-06-08 20:52:44 +02:00
Kherel d3f494adeb update 2021-05-25 23:53:54 +02:00
kherel e849b551fc Merge pull request 'reverse-dns' (#27) from reverse-dns into master
Reviewed-on: kherel/selfprivacy.org.app#27
2021-05-17 15:52:34 +03:00
Kherel 234515477c fix 2021-05-17 14:46:48 +02:00
Kherel e5758aa2bf fix 2021-05-17 14:40:06 +02:00
Kherel d4f315214b add reverse-dns 2021-05-17 14:38:38 +02:00
102 changed files with 2600 additions and 1164 deletions

16
.editorconfig Normal file
View File

@ -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

View File

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

View File

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

View File

@ -19,12 +19,15 @@
"connect": "Connect",
"domain": "Domain",
"saving": "Saving..",
"nickname": "nickname",
"nickname": "Nickname",
"loading": "Loading...",
"later": "I will setup it later",
"reset": "Reset",
"details": "Details",
"no_data": "No data"
"no_data": "No data",
"wait": "Wait",
"remove": "Remove",
"apply": "Apply"
},
"more": {
"_comment": "'More' tab",
@ -32,9 +35,22 @@
"about_project": "About us",
"about_app": "About application",
"onboarding": "Onboarding",
"create_ssh_key": "Create ssh key",
"generate_key": "Generate key",
"generate_key_text": "You can generate ssh key",
"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": {
"text": "Тут любая служебная информация, v.{}"
"text": "Application version v.{}"
},
"settings": {
"title": "Application settings",
@ -145,6 +161,13 @@
"bottom_sheet": {
"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": {
@ -188,8 +211,9 @@
"20": "\n",
"21": "One more restart to apply your security certificates.",
"22": "Create master account",
"23": "Enter a nickname and strong password"
"23": "Enter a nickname and strong password",
"finish": "Everything is initialized",
"checks": "Checks have been completed \n{} ouf of {}"
},
"modals": {
"_comment": "messages in modals",
@ -199,9 +223,29 @@
"4": "Purge all authentication keys?",
"5": "Yes, purge all my tokens",
"6": "Delete the server and volume?",
"7": "Yes"
"7": "Yes",
"8": "Remove task"
},
"timer": {
"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"
}
}
}

View File

@ -6,7 +6,7 @@
"providers": "Провайдеры",
"services": "Сервисы",
"users": "Пользователи",
"more": "Еще",
"more": "Ещё",
"next": "Далее",
"got_it": "Понял",
"settings": "Настройки",
@ -22,26 +22,42 @@
"nickname": "Никнейм",
"loading": "Загрузка",
"later": "Настрою потом",
"reset": "Reset",
"reset": "Сбросить",
"details": "Детальная информация",
"no_data": "Нет данных"
"no_data": "Нет данных",
"wait": "Загрузка",
"remove": "Удалить",
"apply": "Подать"
},
"more": {
"_comment": "вкладка еще",
"_comment": "вкладка ещё",
"configuration_wizard": "Мастер Подключения",
"about_project": "О проекте SelfPrivacy",
"about_app": "О приложении",
"onboarding": "Приветствие",
"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": {
"text": "Версия приложения: v.{}"
},
"settings": {
"title": "Настройки приложения",
"1": "Темная тема",
"2": "Сменить цветовую тему",
"1": "Тёмная тема",
"2": "Сменить цветовую тему.",
"3": "Сброс настроек",
"4": "Сбросить API ключи а так же root пользвателя",
"4": "Сбросить API ключи а также root пользвателя.",
"5": "Удалить сервер",
"6": "Действие приведет к удалению сервера. После чего он не будет доступен."
}
@ -49,9 +65,9 @@
"onboarding": {
"_comment": "страницы онбординга",
"page1_title": "Цифровая независимость доступна каждому",
"page1_text": "Почта, VPN, Мессенджер, социальная сеть и многое другое на вашем личном сервере, под вашим полным контролем.",
"page2_title": "SelfPrivacy — это не облако, а ваш личный дата-центр",
"page2_text": "У SelfPrivacy работает только с вашими сервис-провадерами: Hetzner, Cloudflare, Backblaze. Если у вас нет учетных записей, мы поможем их создать."
"page1_text": "Почта, VPN, Мессенджер, социальная сеть и многое другое на Вашем личном сервере, под Вашим полным контролем.",
"page2_title": "SelfPrivacy — это не облако, а Ваш личный дата-центр",
"page2_text": "SelfPrivacy работает только с вашими сервис-провайдерами: Hetzner, Cloudflare, Backblaze. Если у Вас нет учётных записей, мы поможем их создать."
},
"providers": {
"_comment": "вкладка провайдеры",
@ -60,7 +76,7 @@
"card_title": "Сервер",
"status": "Статус — в норме",
"bottom_sheet": {
"1": "Это виртульный компьютер на котором работают все ваши сервисы.",
"1": "Это виртуальный компьютер на котором работают все Ваши сервисы.",
"2": "Общая информация",
"3": "Размещение"
},
@ -74,16 +90,18 @@
"card_title": "Домен",
"status": "Статус — в норме",
"bottom_sheet": {
"1": "Это ваш личный адрес в интернете, который будет указывать на сервер и другие ваши сервисы.",
"2": "{} — продлен до {}"
"1": "Это Ваш личный адрес в интернете, который будет указывать на сервер и другие ваши сервисы.",
"2": "{} — продлен до {}."
}
},
"backup": {
"card_title": "Резервное копирование",
"status": "Статус — в норме",
"bottom_sheet": {
"1": "Выручит в любой ситуации: хакерская атака, удаление сервера и т.п.",
"2": "3Gb — бестплатно до 10Gb, последний вчера в {}"
"1": "Выручит Вас в любой ситуации: хакерская атака, удаление сервера и.т.д.",
"2": "Использовано 3Gb из бестплатых 10Gb.",
"3": "Последнее копирование была сделана вчера в {}."
}
}
},
@ -95,72 +113,72 @@
},
"services": {
"_comment": "Вкладка сервисы",
"title": "Ваши личные приватные и независимые сервисы",
"title": "Ваши личные, приватные и независимые сервисы:",
"mail": {
"title": "Почта",
"subtitle": "Электронная почта для семьи или компании",
"subtitle": "Электронная почта для семьи или компании.",
"bottom_sheet": {
"1": "Для подключения почтового ящика используйте {} и логин пароль, который вы создали. Так же приглашайте",
"2": "новых пользователей"
"1": "Для подключения почтового ящика используйте {} и профиль, который Вы создали. Так же приглашайте",
"2": "новых пользователей."
}
},
"messenger": {
"title": "Мессенджер",
"subtitle": "Telegram и Signal не так приватны, как Delta.Chat использующий ваш личный сервер.",
"subtitle": "Telegram и Signal не так приватны, как Delta.Chat — который использует Ваш личный сервер.",
"bottom_sheet": {
"1": "Для подключения используйте {} и логин пароль, который вы создали"
"1": "Для подключения используйте {} и логин пароль, который Вы создали."
}
},
"password_manager": {
"title": "Менеджер паролей",
"subtitle": "Фундамент безопасности. Создавать, хранить, копировать пароли между устройствами, вбивать их в формы поможет — Bitwarden.",
"subtitle": "Это фундамент Вашей безопасности. Создавать, хранить, копировать пароли между устройствами и вбивать их в формы поможет — Bitwarden.",
"bottom_sheet": {
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
"1": "Подключиться к серверу и создать пользователя можно по адресу:."
}
},
"video": {
"title": "Видеоконференция",
"subtitle": "Zoom и Google meet отличные инструменты, но Jitsi meet не хуже и дает уверенность, что вас никто не подслушивает.",
"subtitle": "Jitsi meet — отличный аналог Zoom и Google meet который по мимо удобства ещё и гарантирует Вам защищённые высококачественные видеоконференции.",
"bottom_sheet": {
"1": "Для использования просто перейдите по ссылке:"
"1": "Для использования просто перейдите по ссылке:."
}
},
"cloud": {
"title": "Файловое облако",
"subtitle": "Не позволяйте облачным сервисам читать ваши данные используйте NextCloud.",
"subtitle": "Не позволяйте облачным сервисам просматривать ваши данные. Используйте NextCloud — надёжный дом для всех Ваших данных.",
"bottom_sheet": {
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
"1": "Подключиться к серверу и создать пользователя можно по адресу:."
}
},
"social_network": {
"title": "Социальная сеть",
"subtitle": "Сложно поверить, но стало возможным создать свою собственную социальную сеть, со своими правилами и аудиторией.",
"bottom_sheet": {
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
"1": "Подключиться к серверу и создать пользователя можно по адресу:."
}
},
"git": {
"title": "Git-сервер",
"subtitle": "Приватная альтернатива Github, которая принадлежит вам, а не Microsoft.",
"bottom_sheet": {
"1": "Подключиться к серверу и создать пользователя можно по адресу:"
"1": "Подключиться к серверу и создать пользователя можно по адресу:."
}
}
},
"users": {
"_comment": "'Users' tab",
"add_new_user": "Добавьте первого пользователя",
"add_new_user": "Добавьте первого пользователя.",
"new_user": "Новый пользователь",
"not_ready": "Подключите сервер, домен и DNS в разделу Провайдеры, чтобы добавить первого пользователя",
"nobody_here": "'Здесь пока никого'",
"nobody_here": "Здесь будут отображаться пользователи.",
"login": "Логин",
"onboarding": "Приветствие",
"console": "Консоль разработчика",
"new_user_info_note": "Новый пользователь автоматически получит доступ ко всем сервисам. Ещё какое-то описание.",
"delete_confirm_question": "удалить учетную запись?",
"new_user_info_note": "Новый пользователь автоматически получит доступ ко всем сервисам.",
"delete_confirm_question": "Вы действительно хотите удалить учетную запись?",
"reset_password": "Сбросить пароль",
"account": "Учетная запись",
"send_regisration_data": "Отправить реквизиты для входа"
"send_regisration_data": "Поделиться реквизитами"
},
"initializing": {
"_comment": "initializing page",
@ -173,34 +191,56 @@
"6": "Подключите облачное хранилище Backblaze",
"7": "На данный момент подлюченных доменов нет",
"8": "Загружаем список доменов",
"9": "Найдено больше одного домена, для вашей безопастности, просим вам удалить не нужные домены",
"9": "Найдено больше одного домена, для вашей безопастности, просим Вам удалить не нужные домены",
"10": "Сохранить домен",
"final": "Последний шаг",
"11": "Создать сервер",
"what": "Что это значит?",
"13": "Сервер презагружен, ждем последнюю проверку",
"14": "Cервер запушен, сейчас он будет проверен и перезагружен",
"15": "Cервер создан, идет проверка ДНС адресов и запуск сервера",
"13": "Сервер презагружен, ждем последнюю проверку.",
"14": "Cервер запущен, сейчас он будет проверен и перезагружен.",
"15": "Cервер создан, идет проверка ДНС адресов и запуск сервера.",
"16": "До следующей проверки: ",
"17": "Проверка",
"18": "Как получить Hetzner API Token'",
"18": "Как получить Hetzner API Token:'",
"19": "1 Переходим по ссылке ",
"20": "\n2 Заходим в созданный нами проект. Если такового - нет, значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.",
"20": "\n2 Заходим в созданный нами проект. Если такового нет - значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же Вы используете мобильную версию сайта - в нижнем правом углу Вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description даём нашему токену название (это может быть любое название, которое Вам нравиться, сути оно не меняет.",
"21": "Сейчас будет дополнительная перезагрузка для активации сертификатов безопастности",
"22": "Создайте главную учетную запись",
"23": "Введите никнейм и сложный пароль"
"23": "Введите никнейм и сложный пароль",
"finish": "Всё инициализировано.",
"checks": "Проверок выполнено: \n{} / {}"
},
"modals": {
"_comment": "messages in modals",
"1": "Сервер с таким именем уже существует",
"2": "Уничтожить сервер и создать новый?",
"3": "Вы уверенны",
"3": "Подтвердите",
"4": "Сбросить все ключи?",
"5": "Да, сбросить",
"6": "Удалить сервер и диск?",
"7": "Да, удалить"
"7": "Да, удалить",
"8": "Удалить задачу"
},
"timer": {
"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": "Имя уже используется."
}
}
}

View File

@ -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

View File

@ -12,5 +12,4 @@ Application will do the following things for you:
* Gitea - your own Git server
* OpenConnect - Personal VPN server
!!! Project is currently in alpha state. Feel free to try it. It would be much appreciated if you would provide us with some feedback.
!!! Only Russian localization is currently available. English is coming soon
!!! 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.

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>9.0</string>
</dict>
</plist>

View File

@ -2,10 +2,14 @@ PODS:
- Flutter (1.0.0)
- flutter_secure_storage (3.3.1):
- Flutter
- local_auth (0.0.1):
- Flutter
- package_info (0.0.1):
- Flutter
- path_provider (0.0.1):
- Flutter
- share_plus (0.0.1):
- Flutter
- shared_preferences (0.0.1):
- Flutter
- url_launcher (0.0.1):
@ -16,8 +20,10 @@ PODS:
DEPENDENCIES:
- Flutter (from `Flutter`)
- 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`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
@ -27,10 +33,14 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
local_auth:
:path: ".symlinks/plugins/local_auth/ios"
package_info:
:path: ".symlinks/plugins/package_info/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios"
url_launcher:
@ -39,10 +49,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock/ios"
SPEC CHECKSUMS:
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f

View File

@ -361,6 +361,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = UVNTKR53DD;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -499,6 +500,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = UVNTKR53DD;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -529,6 +531,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = UVNTKR53DD;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 608 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

After

Width:  |  Height:  |  Size: 1007 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 907 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 608 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -2,7 +2,9 @@ import 'package:flutter/material.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_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/services/services_cubit.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
class BlocAndProviderConfig extends StatelessWidget {
@ -12,11 +14,9 @@ class BlocAndProviderConfig extends StatelessWidget {
@override
Widget build(BuildContext context) {
// var platformBrightness =
// SchedulerBinding.instance.window.platformBrightness;
// var isDark = platformBrightness == Brightness.dark;
var isDark = false;
var usersCubit = UsersCubit();
var servicesCubit = ServicesCubit();
return MultiProvider(
providers: [
BlocProvider(
@ -27,10 +27,17 @@ class BlocAndProviderConfig extends StatelessWidget {
),
BlocProvider(
lazy: false,
create: (_) => AppConfigCubit()..load(),
create: (_) => AppConfigCubit(servicesCubit)..load(),
),
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,
);

View File

@ -1,7 +0,0 @@
import 'package:flutter/material.dart';
final shadow8 = BoxShadow(
offset: Offset(0, 4),
blurRadius: 8,
color: Colors.black.withOpacity(.08),
);

View File

@ -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);

View File

@ -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/console.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';
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/timer.dart';
final getIt = GetIt.instance;
Future<void> getItSetup() async {
@ -17,6 +17,7 @@ Future<void> getItSetup() async {
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
getIt.registerSingleton<TimerModel>(TimerModel());
getIt.registerSingleton<SSHModel>(SSHModel()..init());
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
await getIt.allReady();

View File

@ -19,21 +19,25 @@ class HiveConfig {
Hive.registerAdapter(HetznerDataBaseAdapter());
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);
var sshCipher = HiveAesCipher(await getEncriptedKey(BNames.sshEnckey));
await Hive.openBox(BNames.sshConfig, encryptionCipher: sshCipher);
}
static Future<Uint8List> getEncriptedKey() async {
final FlutterSecureStorage secureStorage = FlutterSecureStorage();
var containsEncryptionKey =
await secureStorage.containsKey(key: BNames.key);
if (!containsEncryptionKey) {
static Future<Uint8List> getEncriptedKey(String encKey) async {
final secureStorage = FlutterSecureStorage();
var hasEncryptionKey = await secureStorage.containsKey(key: encKey);
if (!hasEncryptionKey) {
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!);
}
}
@ -42,10 +46,13 @@ class BNames {
static String appConfig = 'appConfig';
static String isDarkModeOn = 'isDarkModeOn';
static String isOnbordingShowing = 'isOnbordingShowing';
static String users = 'users';
static String appSettings = 'appSettings';
static String servicesState = 'servicesState';
static String key = 'key';
static String sshEnckey = 'sshEngkey';
static String cloudFlareDomain = 'cloudFlareDomain';
static String hetznerKey = 'hetznerKey';
@ -58,4 +65,7 @@ class BNames {
static String isLoading = 'isLoading';
static String isServerResetedFirstTime = 'isServerResetedFirstTime';
static String isServerResetedSecondTime = 'isServerResetedSecondTime';
static String sshConfig = 'sshConfig';
static String sshPrivateKey = "sshPrivateKey";
static String sshPublicKey = "sshPublicKey";
}

View File

@ -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/server_details.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 {
bool hasLoger;
@ -73,30 +73,28 @@ class HetznerApi extends ApiMap {
required User rootUser,
required String domainName,
}) async {
var dbPassword = getRandomString(40);
const chars =
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
var dbStorageName = getRandomString(6, chars);
var client = await getClient();
Response dbCreateResponse = await client.post(
'/volumes',
data: {
"size": 10,
"name": dbStorageName,
"name": StringGenerators.dbStorageName(),
"labels": {"labelkey": "value"},
"location": "fsn1",
"automount": false,
"format": "ext4"
},
);
var dbPassword = StringGenerators.dbPassword();
var dbId = dbCreateResponse.data['volume']['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/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-20.09 DOMAIN=$domainName LUSER=${rootUser.login} PASSWORD=${rootUser.password} HASHED_PASSWORD=${rootUser.hashPassword.hash} SALT=${rootUser.hashPassword.salt} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":true, "location": "fsn1"}''',
);
'''{"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/master/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(
'/servers',
@ -192,4 +190,20 @@ class HetznerApi extends ApiMap {
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);
}
}

View File

@ -3,6 +3,8 @@ import 'dart:io';
import 'package:dio/dio.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';
@ -31,7 +33,7 @@ class ServerApi extends ApiMap {
var client = await getClient();
try {
response = await client.get('/serviceStatus');
response = await client.get('/services/status');
res = response.statusCode == HttpStatus.ok;
} catch (e) {
res = false;
@ -40,6 +42,92 @@ class ServerApi extends ApiMap {
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":
'\$6\$${user.hashPassword.salt}\$${user.hashPassword.hash}',
},
),
);
res = response.statusCode == HttpStatus.ok;
} catch (e) {
print(e);
res = false;
}
close(client);
return res;
}
String get rootAddress =>
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();
}
}
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');
}
}
}

View File

@ -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 {
setHeznerKey,
setCloudFlareKey,
@ -10,3 +15,80 @@ enum InitializingSteps {
}
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];
}

View File

@ -2,6 +2,9 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
import 'package:selfprivacy/logic/get_it/ssh.dart';
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
@ -43,9 +46,10 @@ part 'app_config_state.dart';
/// c. if server is okay set that fully checked
class AppConfigCubit extends Cubit<AppConfigState> {
AppConfigCubit() : super(InitialAppConfigState());
AppConfigCubit(this.servicesCubit) : super(InitialAppConfigState());
final repository = AppConfigRepository();
final ServicesCubit servicesCubit;
Future<void> load() async {
var state = await repository.load();
@ -232,6 +236,7 @@ class AppConfigCubit extends Cubit<AppConfigState> {
if (isServerWorking) {
await repository.saveHasFinalChecked(true);
servicesCubit.allOn();
emit(state.copyWith(
hasFinalChecked: true,
@ -259,14 +264,19 @@ class AppConfigCubit extends Cubit<AppConfigState> {
void clearAppConfig() {
closeTimer();
servicesCubit.allOff();
repository.clearAppConfig();
emit(InitialAppConfigState());
}
Future<void> serverDelete() async {
closeTimer();
servicesCubit.allOff();
if (state.hetznerServer != null) {
await repository.deleteServer(state.cloudFlareDomain!);
await getIt<SSHModel>().clear();
}
await repository.deleteRecords();
emit(AppConfigState(
@ -316,7 +326,7 @@ class AppConfigCubit extends Cubit<AppConfigState> {
void createServerAndSetDnsRecords() async {
AppConfigState _stateCopy = state;
var onSuccess = (serverDetails) async {
var onSuccess = (HetznerServerDetails serverDetails) async {
await repository.createDnsRecords(
serverDetails.ip4,
state.cloudFlareDomain!,

View File

@ -149,7 +149,7 @@ class AppConfigRepository {
}
Future<void> createDnsRecords(
String? ip4,
String ip4,
CloudFlareDomain cloudFlareDomain,
) async {
var cloudflareApi = CloudflareApi();
@ -163,6 +163,11 @@ class AppConfigRepository {
ip4: ip4,
cloudFlareDomain: cloudFlareDomain,
);
await HetznerApi().createReverseDns(
ip4: ip4,
domainName: cloudFlareDomain.domainName,
);
}
Future<bool> isHttpServerWorking() async {

View File

@ -85,7 +85,17 @@ class AppConfigState extends Equatable {
bool get isServerCreated => hetznerServer != null;
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 {
var res = [

View File

@ -3,6 +3,7 @@ import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/backblaze.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
import 'package:easy_localization/easy_localization.dart';
class BackblazeFormCubit extends FormCubit {
BackblazeFormCubit(this.initializingCubit) {
@ -10,7 +11,7 @@ class BackblazeFormCubit extends FormCubit {
keyId = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('required'),
RequiredStringValidation('validations.required'.tr()),
//ValidationModel<String>(
//(s) => regExp.hasMatch(s), 'invalid key format'),
//LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')

View File

@ -4,6 +4,7 @@ import 'package:cubit_form/cubit_form.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/forms/validations/validations.dart';
import 'package:easy_localization/easy_localization.dart';
class CloudFlareFormCubit extends FormCubit {
CloudFlareFormCubit(this.initializingCubit) {
@ -11,10 +12,11 @@ class CloudFlareFormCubit extends FormCubit {
apiKey = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('required'),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => regExp.hasMatch(s), 'invalid key format'),
LegnthStringValidationWithLenghShowing(40, 'length is [] shoud be 40')
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
LegnthStringValidationWithLenghShowing(
40, 'validations.length'.tr(args: ["40"]))
],
);

View File

@ -4,6 +4,7 @@ import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:easy_localization/easy_localization.dart';
class HetznerFormCubit extends FormCubit {
HetznerFormCubit(this.initializingCubit) {
@ -11,10 +12,10 @@ class HetznerFormCubit extends FormCubit {
apiKey = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('required'),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => regExp.hasMatch(s), 'invalid key format'),
LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
LegnthStringValidationWithLenghShowing(64, 'validations.length'.tr(args: ["64"]))
],
);

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/user.dart';
import 'package:easy_localization/easy_localization.dart';
class RootUserFormCubit extends FormCubit {
RootUserFormCubit(this.initializingCubit) {
@ -12,18 +13,20 @@ class RootUserFormCubit extends FormCubit {
userName = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('required'),
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(
initalValue: '',
validations: [
RequiredStringValidation('required'),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => passwordRegExp.hasMatch(s), 'invalid format'),
(s) => passwordRegExp.hasMatch(s), 'validations.invalid_format'.tr()),
],
);

View File

@ -1,13 +1,16 @@
import 'dart:async';
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:easy_localization/easy_localization.dart';
import 'package:selfprivacy/utils/password_generator.dart';
class UserFormCubit extends FormCubit {
UserFormCubit({
required this.usersCubit,
required this.jobsCubit,
required List<User> users,
User? user,
}) {
var isEdit = user != null;
@ -18,18 +21,22 @@ class UserFormCubit extends FormCubit {
login = FieldCubit(
initalValue: isEdit ? user!.login : '',
validations: [
RequiredStringValidation('required'),
ValidationModel(
(login) => users.any((user) => user.login == login),
'validations.user_alredy_exist'.tr(),
),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => userRegExp.hasMatch(s), 'invalid format'),
(s) => userRegExp.hasMatch(s), 'validations.invalid_format'.tr()),
],
);
password = FieldCubit(
initalValue: isEdit ? user!.password : genPass(),
initalValue: isEdit ? user!.password : StringGenerators.userPassword(),
validations: [
RequiredStringValidation('required'),
ValidationModel<String>(
(s) => passwordRegExp.hasMatch(s), 'invalid format'),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>((s) => passwordRegExp.hasMatch(s),
'validations.invalid_format'.tr()),
],
);
@ -42,15 +49,15 @@ class UserFormCubit extends FormCubit {
login: login.state.value,
password: password.state.value,
);
usersCubit.addUser(user);
jobsCubit.addJob(CreateUserJob(user: user));
}
late FieldCubit<String> login;
late FieldCubit<String> password;
void genNewPassword() {
password.externalSetValue(genPass());
password.externalSetValue(StringGenerators.userPassword());
}
late UsersCubit usersCubit;
final JobsCubit jobsCubit;
}

View File

@ -6,7 +6,6 @@ import 'hetzner_metrics_cubit.dart';
class HetznerMetricsRepository {
Future<HetznerMetricsLoaded> getMetrics(Period period) async {
print(period);
var end = DateTime.now();
DateTime start;

View File

@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/config/text_themes.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>[];
for (var job in jobs) {
if (job is CreateUserJob) {
newUsers.add(job.user);
await api.createUser(job.user);
} else if (job is ServiceToggleJob) {
await api.switchService(job.type, job.needToTurnOn);
if (job.needToTurnOn) {
servicesCubit.turnOnist([job.type]);
} else {
servicesCubit.turnOffList([job.type]);
}
}
if (job is CreateSSHKeyJob) {
await getIt<SSHModel>().generateKeys();
api.sendSsh(getIt<SSHModel>().savedPubKey!);
}
}
usersCubit.addUsers(newUsers);
await api.apply();
emit(JobsStateEmpty());
getIt<NavigationService>().navigator!.pop();
}
}
}

View File

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

View File

@ -0,0 +1,63 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
part 'services_state.dart';
class ServicesCubit extends Cubit<ServicesState> {
ServicesCubit() : super(ServicesState.allOff());
Box box = Hive.box(BNames.servicesState);
void load() {
emit(
ServicesState(
isPasswordManagerEnable:
box.get(ServiceTypes.passwordManager.txt, defaultValue: false),
isCloudEnable: box.get(ServiceTypes.cloud.txt, defaultValue: false),
isGitEnable: box.get(ServiceTypes.git.txt, defaultValue: false),
isSocialNetworkEnable:
box.get(ServiceTypes.socialNetwork.txt, defaultValue: false),
isVpnEnable: box.get(ServiceTypes.vpn.txt, defaultValue: false),
),
);
}
void allOn() {
box.put(ServiceTypes.passwordManager.txt, true);
box.put(ServiceTypes.cloud.txt, true);
box.put(ServiceTypes.git.txt, true);
box.put(ServiceTypes.socialNetwork.txt, true);
box.put(ServiceTypes.vpn.txt, true);
emit(ServicesState.allOn());
}
void allOff() {
box.put(ServiceTypes.passwordManager.txt, false);
box.put(ServiceTypes.cloud.txt, false);
box.put(ServiceTypes.git.txt, false);
box.put(ServiceTypes.socialNetwork.txt, false);
box.put(ServiceTypes.vpn.txt, false);
emit(ServicesState.allOff());
}
void turnOffList(List<ServiceTypes> list) {
for (final service in list) {
box.put(service.txt, false);
}
emit(state.disableList(list));
}
void turnOnist(List<ServiceTypes> list) {
for (final service in list) {
box.put(service.txt, true);
}
emit(state.enableList(list));
}
}

View File

@ -0,0 +1,93 @@
part of 'services_cubit.dart';
class ServicesState extends Equatable {
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');
}
}
}

View File

@ -1,42 +1,36 @@
import 'package:bloc/bloc.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';
export 'package:provider/provider.dart';
part 'users_state.dart';
class UsersCubit extends Cubit<UsersState> {
UsersCubit() : super(UsersState([]));
UsersCubit() : super(UsersState(<User>[]));
Box<User> box = Hive.box<User>(BNames.users);
void addUser(User user) {
var users = [...state.users];
users.add(user);
emit(UsersState(users));
void load() async {
var loadedUsers = box.values.toList();
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 index = users.indexOf(user);
users.remove(user);
await box.deleteAt(index);
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()),
// ];

View File

@ -1,8 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/text_themes.dart';
class NavigationService {
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
NavigatorState? get navigator => navigatorKey.currentState;
void showPopUpDialog(AlertDialog dialog) {
@ -13,4 +18,14 @@ class NavigationService {
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);
}
}

39
lib/logic/get_it/ssh.dart Normal file
View File

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

54
lib/logic/models/job.dart Normal file
View File

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

View File

@ -4,6 +4,7 @@ import 'package:crypt/crypt.dart';
import 'package:equatable/equatable.dart';
import 'package:selfprivacy/utils/color_utils.dart';
import 'package:hive/hive.dart';
import 'package:selfprivacy/utils/password_generator.dart';
part 'user.g.dart';
@ -25,7 +26,10 @@ class User extends Equatable {
Color get color => stringToColor(login);
Crypt get hashPassword => Crypt.sha512(password);
Crypt get hashPassword => Crypt.sha512(
password,
salt: StringGenerators.passwordSalt(),
);
String toString() {
return login;

View File

@ -16,47 +16,50 @@ import 'config/localization.dart';
import 'logic/cubit/app_settings/app_settings_cubit.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await HiveConfig.init();
Bloc.observer = SimpleBlocObserver();
Wakelock.enable();
await getItSetup();
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
runApp(
Localization(
child: BlocAndProviderConfig(
child: MyApp(),
),
),
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
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>(
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
child: MaterialApp(
navigatorKey: getIt.get<NavigationService>().navigatorKey,
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
title: 'SelfPrivacy',
theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme,
home: appSettings.isOnbordingShowing
? OnboardingPage(nextPage: InitializingPage())
: RootPage(),
builder: (BuildContext context, Widget? widget) {
Widget error = Text('...rendering error...');
if (widget is Scaffold || widget is Navigator)
error = Scaffold(body: Center(child: error));
ErrorWidget.builder = (FlutterErrorDetails errorDetails) => error;
return widget!;
},
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
child: MaterialApp(
scaffoldMessengerKey:
getIt.get<NavigationService>().scaffoldMessengerKey,
navigatorKey: getIt.get<NavigationService>().navigatorKey,
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
title: 'SelfPrivacy',
theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme,
home: appSettings.isOnbordingShowing
? OnboardingPage(nextPage: InitializingPage())
: RootPage(),
builder: (BuildContext context, Widget? widget) {
Widget error = Text('...rendering error...');
if (widget is Scaffold || widget is Navigator)
error = Scaffold(body: Center(child: error));
ErrorWidget.builder =
(FlutterErrorDetails errorDetails) => error;
return widget!;
},
),
);
}),
),
);
}

View File

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

View File

@ -10,14 +10,14 @@ class BrandButton {
static rised({
Key? key,
required VoidCallback? onPressed,
String? title,
String? text,
Widget? child,
}) {
assert(title == 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');
assert(text != null || child != null, 'required title or child');
return _RisedButton(
key: key,
title: title,
title: text,
onPressed: onPressed,
child: child,
);

View File

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

View File

@ -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),
)
];

View File

@ -1,16 +1,19 @@
import 'package:flutter/material.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/pre_styled_buttons/pre_styled_buttons.dart';
class BrandHeader extends StatelessWidget {
const BrandHeader({
Key? key,
required this.title,
this.hasBackButton = false,
this.hasFlashButton = false,
}) : super(key: key);
final String title;
final bool hasBackButton;
final bool hasFlashButton;
@override
Widget build(BuildContext context) {
@ -31,6 +34,8 @@ class BrandHeader extends StatelessWidget {
SizedBox(width: 10),
],
BrandText.h4(title),
Spacer(),
if (hasFlashButton) PreStyledButtons.flash(),
],
),
),

View File

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

View File

@ -56,6 +56,7 @@ class _BrandMarkdownState extends State<BrandMarkdown> {
),
);
return Markdown(
shrinkWrap: true,
styleSheet: markdown,
onTapLink: (String text, String? href, String title) {
if (href != null) {

View File

@ -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 {
const BrandModalSheet({
Key? key,
this.child,
}) : super(key: key);
// class BrandModalSheet extends StatelessWidget {
// const BrandModalSheet({
// Key? key,
// this.child,
// }) : super(key: key);
final Widget? child;
@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
minChildSize: 0.95,
initialChildSize: 1,
maxChildSize: 1,
builder: (context, scrollController) {
return SingleChildScrollView(
controller: scrollController,
physics: ClampingScrollPhysics(),
child: Container(
child: Column(
children: [
GestureDetector(
onTap: () => Navigator.of(context).pop(),
behavior: HitTestBehavior.opaque,
child: Container(
width: double.infinity,
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 132, bottom: 6),
child: Container(
height: 4,
width: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2),
color: Color(0xFFE3E3E3).withOpacity(0.65),
),
),
),
),
),
),
Container(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - 132,
maxHeight: MediaQuery.of(context).size.height - 132,
),
decoration: BoxDecoration(
borderRadius:
BorderRadius.vertical(top: Radius.circular(20)),
color: Theme.of(context).scaffoldBackgroundColor,
),
width: double.infinity,
child: child),
],
),
),
);
});
}
}
// final Widget? child;
// @override
// Widget build(BuildContext context) {
// return DraggableScrollableSheet(
// minChildSize: 1,
// initialChildSize: 1,
// maxChildSize: 1,
// builder: (context, scrollController) {
// return SingleChildScrollView(
// controller: scrollController,
// physics: ClampingScrollPhysics(),
// child: Container(
// child: Column(
// children: [
// GestureDetector(
// onTap: () => Navigator.of(context).pop(),
// behavior: HitTestBehavior.opaque,
// child: Container(
// width: double.infinity,
// child: Center(
// child: Padding(
// padding: EdgeInsets.only(top: 132, bottom: 6),
// child: Container(
// height: 4,
// width: 30,
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(2),
// color: Color(0xFFE3E3E3).withOpacity(0.65),
// ),
// ),
// ),
// ),
// ),
// ),
// Container(
// constraints: BoxConstraints(
// minHeight: MediaQuery.of(context).size.height - 132,
// maxHeight: MediaQuery.of(context).size.height - 132,
// ),
// decoration: BoxDecoration(
// borderRadius:
// BorderRadius.vertical(top: Radius.circular(20)),
// color: Theme.of(context).scaffoldBackgroundColor,
// ),
// width: double.infinity,
// child: child),
// ],
// ),
// ),
// );
// });
// }
// }

View File

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

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/text_themes.dart';
export 'package:selfprivacy/utils/extensions/text_extensions.dart';
enum TextType {
h1, // right now only at onboarding and opened providers
@ -51,10 +52,16 @@ class BrandText extends StatelessWidget {
type: TextType.onboardingTitle,
style: style,
);
factory BrandText.h2(String? text, {TextStyle? style}) => BrandText(
factory BrandText.h2(
String? text, {
TextStyle? style,
TextAlign? textAlign,
}) =>
BrandText(
text,
type: TextType.h2,
style: style,
textAlign: textAlign,
);
factory BrandText.h3(String text, {TextStyle? style, TextAlign? textAlign}) =>
BrandText(
@ -120,7 +127,6 @@ class BrandText extends StatelessWidget {
Text build(BuildContext context) {
TextStyle style;
var isDark = Theme.of(context).brightness == Brightness.dark;
switch (type) {
case TextType.h1:
style = isDark

View File

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

View File

@ -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_text/brand_text.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 {
const OnePage({
@ -16,33 +16,31 @@ class OnePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PreferredSize(
child: Column(
children: [
Container(
height: 51,
alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 15),
child: BrandText.h4('basis.details'.tr()),
),
BrandDivider(),
],
),
preferredSize: Size.fromHeight(52),
),
body: child,
bottomNavigationBar: SafeArea(
child: Container(
decoration: BoxDecoration(boxShadow: kElevationToShadow[3]),
height: kBottomNavigationBarHeight,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
return Scaffold(
appBar: PreferredSize(
child: Column(
children: [
Container(
height: 51,
alignment: Alignment.center,
child: PreStyledButtons.close(
onPress: () => Navigator.of(context).pop()),
padding: EdgeInsets.symmetric(horizontal: 15),
child: BrandText.h4('basis.details'.tr()),
),
BrandDivider(),
],
),
preferredSize: Size.fromHeight(52),
),
body: child,
bottomNavigationBar: SafeArea(
child: Container(
decoration: BoxDecoration(boxShadow: kElevationToShadow[3]),
height: kBottomNavigationBarHeight,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
alignment: Alignment.center,
child: PreStyledButtons.close(
onPress: () => Navigator.of(context).pop()),
),
),
),

View File

@ -1,13 +1,4 @@
import 'package:flutter/material.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);
}
part of 'pre_styled_buttons.dart';
class _CloseButton extends StatelessWidget {
const _CloseButton({Key? key, required this.onPress}) : super(key: key);

View File

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

View File

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

View File

@ -53,9 +53,9 @@ class _ProgressBarState extends State<ProgressBar> {
width: 10,
),
);
even.add(
odd.add(
SizedBox(
width: 10,
width: 20,
),
);

View File

@ -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,
);

View File

@ -10,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/app_config/app_config_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_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_modal_sheet/brand_modal_sheet.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
@ -36,7 +36,7 @@ class InitializingPage extends StatelessWidget {
() => _stepCheck(cubit),
() => _stepCheck(cubit),
() => _stepCheck(cubit),
() => Container(child: Text('Everythigng is initialized'))
() => Container(child: Center(child: Text('initializing.finish'.tr())))
][cubit.state.progress]();
return BlocListener<AppConfigCubit, AppConfigState>(
listener: (context, state) {
@ -50,7 +50,7 @@ class InitializingPage extends StatelessWidget {
child: Column(
children: [
Padding(
padding: brandPagePadding2.copyWith(top: 10, bottom: 10),
padding: paddingH15V0.copyWith(top: 10, bottom: 10),
child: ProgressBar(
steps: [
'Hetzner',
@ -59,12 +59,9 @@ class InitializingPage extends StatelessWidget {
'Domain',
'User',
'Server',
'',
'',
'',
'',
'✅ Check',
],
activeIndex: cubit.state.progress,
activeIndex: cubit.state.porgressBar,
),
),
_addCard(
@ -132,7 +129,7 @@ class InitializingPage extends StatelessWidget {
onPressed: formCubitState.isSubmitting
? null
: () => context.read<HetznerFormCubit>().trySubmit(),
title: 'basis.connect'.tr(),
text: 'basis.connect'.tr(),
),
SizedBox(height: 10),
BrandButton.text(
@ -187,7 +184,7 @@ class InitializingPage extends StatelessWidget {
onPressed: formCubitState.isSubmitting
? null
: () => context.read<CloudFlareFormCubit>().trySubmit(),
title: 'basis.connect'.tr(),
text: 'basis.connect'.tr(),
),
SizedBox(height: 10),
BrandButton.text(
@ -238,7 +235,7 @@ class InitializingPage extends StatelessWidget {
onPressed: formCubitState.isSubmitting
? null
: () => context.read<BackblazeFormCubit>().trySubmit(),
title: 'basis.connect'.tr(),
text: 'basis.connect'.tr(),
),
SizedBox(height: 10),
BrandButton.text(
@ -330,7 +327,7 @@ class InitializingPage extends StatelessWidget {
SizedBox(height: 30),
BrandButton.rised(
onPressed: () => context.read<DomainSetupCubit>().saveDomain(),
title: 'initializing.10'.tr(),
text: 'initializing.10'.tr(),
),
],
SizedBox(height: 10),
@ -400,7 +397,7 @@ class InitializingPage extends StatelessWidget {
onPressed: formCubitState.isSubmitting
? null
: () => context.read<RootUserFormCubit>().trySubmit(),
title: 'basis.connect'.tr(),
text: 'basis.connect'.tr(),
),
SizedBox(height: 10),
BrandButton.text(
@ -428,7 +425,7 @@ class InitializingPage extends StatelessWidget {
onPressed: isLoading
? null
: () => appConfigCubit.createServerAndSetDnsRecords(),
title: isLoading ? 'basis.loading'.tr() : 'initializing.11'.tr(),
text: isLoading ? 'basis.loading'.tr() : 'initializing.11'.tr(),
),
Spacer(flex: 2),
BrandButton.text(
@ -443,21 +440,29 @@ class InitializingPage extends StatelessWidget {
Widget _stepCheck(AppConfigCubit appConfigCubit) {
assert(appConfigCubit.state is TimerState, 'wronge state');
var state = appConfigCubit.state as TimerState;
late int doneCount;
late String? text;
if (state.isServerResetedSecondTime) {
text = 'initializing.13'.tr();
doneCount = 3;
} else if (state.isServerResetedFirstTime) {
text = 'initializing.21'.tr();
doneCount = 2;
} else if (state.isServerStarted) {
text = 'initializing.14'.tr();
doneCount = 1;
} else if (state.isServerCreated) {
text = 'initializing.15'.tr();
doneCount = 0;
}
return Builder(builder: (context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 15),
BrandText.h4(
'initializing.checks'.tr(args: [doneCount.toString(), "4"]),
),
Spacer(flex: 2),
SizedBox(height: 10),
BrandText.body2(text),
@ -488,8 +493,8 @@ class InitializingPage extends StatelessWidget {
Widget _addCard(Widget child) {
return Container(
height: 450,
padding: brandPagePadding2,
child: BrandCard(child: child),
padding: paddingH15V0,
child: BrandCards.big(child: child),
);
}
}
@ -501,12 +506,14 @@ class _HowHetzner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BrandModalSheet(
return BrandBottomSheet(
isExpended: true,
child: Padding(
padding: brandPagePadding2.copyWith(top: 25),
child: BrandMarkdown(
fileName: 'how_hetzner',
)),
padding: paddingH15V0,
child: BrandMarkdown(
fileName: 'how_hetzner',
),
),
);
}
}

View File

@ -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_divider/brand_divider.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/utils/named_font_weight.dart';
import 'package:easy_localization/easy_localization.dart';
@ -32,7 +33,7 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
preferredSize: Size.fromHeight(52),
),
body: ListView(
padding: brandPagePadding2,
padding: paddingH15V0,
children: [
BrandDivider(),
Container(
@ -52,9 +53,7 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
),
),
SizedBox(width: 5),
Switch(
activeColor: BrandColors.green1,
activeTrackColor: BrandColors.green2,
BrandSwitch(
value: Theme.of(context).brightness == Brightness.dark,
onChanged: (value) => context
.read<AppSettingsCubit>()
@ -120,69 +119,72 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
],
),
),
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: () {
showDialog(
context: context,
builder: (_) {
return BrandAlert(
title: 'modals.3'.tr(),
contentText: 'modals.6'.tr(),
acitons: [
ActionButton(
text: 'modals.7'.tr(),
isRed: true,
onPressed: () async {
await context
.read<AppConfigCubit>()
.serverDelete();
Navigator.of(context).pop();
}),
ActionButton(
text: 'basis.cancel'.tr(),
),
],
);
},
);
},
),
],
),
)
// deleteServer(context)
],
),
);
}),
);
}
Widget deleteServer(BuildContext context) {
// todo: need to check
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: () {
showDialog(
context: context,
builder: (_) {
return BrandAlert(
title: 'modals.3'.tr(),
contentText: 'modals.6'.tr(),
acitons: [
ActionButton(
text: 'modals.7'.tr(),
isRed: true,
onPressed: () async {
await context.read<AppConfigCubit>().serverDelete();
Navigator.of(context).pop();
}),
ActionButton(
text: 'basis.cancel'.tr(),
),
],
);
},
);
},
),
],
),
);
}
}
class _TextColumn extends StatelessWidget {

View File

@ -18,7 +18,7 @@ class InfoPage extends StatelessWidget {
preferredSize: Size.fromHeight(52),
),
body: ListView(
padding: brandPagePadding2,
padding: paddingH15V0,
children: [
BrandDivider(),
SizedBox(height: 10),

View File

@ -1,15 +1,29 @@
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_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_header/brand_header.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/icon_status_mask/icon_status_mask.dart';
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
import 'package:selfprivacy/ui/pages/rootRoute.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:share_plus/share_plus.dart';
import 'about/about.dart';
import 'app_settings/app_setting.dart';
@ -21,15 +35,21 @@ class MorePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var jobsCubit = context.watch<JobsCubit>();
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
return Scaffold(
appBar: PreferredSize(
child: BrandHeader(title: 'basis.more'.tr()),
child: BrandHeader(
title: 'basis.more'.tr(),
hasFlashButton: true,
),
preferredSize: Size.fromHeight(52),
),
body: ListView(
children: [
Padding(
padding: brandPagePadding2,
padding: paddingH15V0,
child: Column(
children: [
BrandDivider(),
@ -63,6 +83,73 @@ class MorePage extends StatelessWidget {
iconData: BrandIcons.terminal,
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 {
const _NavItem({
Key? key,
@ -88,29 +319,81 @@ class _NavItem extends StatelessWidget {
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).push(materialRoute(goTo)),
child: Container(
padding: EdgeInsets.symmetric(vertical: 24),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1.0,
color: BrandColors.dividerColor,
),
),
),
child: Row(
children: [
BrandText.body1(title),
Spacer(),
SizedBox(
width: 56,
child: Icon(
iconData,
size: 20,
),
),
],
),
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),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1.0,
color: BrandColors.dividerColor,
),
),
),
child: Row(
children: [
BrandText.body1(
title,
style: TextStyle(
color: isActive ? null : Colors.grey,
),
),
Spacer(),
SizedBox(
width: 56,
child: Icon(
iconData,
size: 20,
color: isActive ? null : Colors.grey,
),
),
],
),
);
}

View File

@ -79,7 +79,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
curve: Curves.easeIn,
);
},
title: 'basis.next'.tr(),
text: 'basis.next'.tr(),
),
SizedBox(height: 30),
],
@ -129,7 +129,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
(route) => false,
);
},
title: 'basis.got_it'.tr(),
text: 'basis.got_it'.tr(),
),
SizedBox(height: 30),
],

View File

@ -1,20 +1,19 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.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/models/provider.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_modal_sheet/brand_modal_sheet.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package: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/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/utils/route_transitions/slide_bottom.dart';
import 'package:selfprivacy/utils/ui_helpers.dart';
var navigatorKey = GlobalKey<NavigatorState>();
@ -46,11 +45,14 @@ class _ProvidersPageState extends State<ProvidersPage> {
.toList();
return Scaffold(
appBar: PreferredSize(
child: BrandHeader(title: 'providers.page_title'.tr()),
child: BrandHeader(
title: 'providers.page_title'.tr(),
hasFlashButton: true,
),
preferredSize: Size.fromHeight(52),
),
body: ListView(
padding: brandPagePadding2,
padding: paddingH15V0,
children: [
if (!isReady) ...[
NotReadyCard(),
@ -73,7 +75,7 @@ class _Card extends StatelessWidget {
String? message;
late String stableText;
late VoidCallback onTap;
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
AppConfigState appConfig = context.watch<AppConfigCubit>().state;
var domainName =
@ -83,24 +85,22 @@ class _Card extends StatelessWidget {
case ProviderType.server:
title = 'providers.server.card_title'.tr();
stableText = 'providers.server.status'.tr();
onTap = () => Navigator.of(context).push(
SlideBottomRoute(
OnePage(
title: title,
child: ServerDetails(),
),
onTap = () => showBrandBottomSheet(
context: context,
builder: (context) => BrandBottomSheet(
isExpended: true,
child: ServerDetails(),
),
);
break;
case ProviderType.domain:
title = 'providers.domain.card_title'.tr();
message = domainName;
stableText = 'providers.domain.status'.tr();
onTap = () => showModalBottomSheet<void>(
onTap = () => showBrandBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _ProviderDetails(
provider: provider,
@ -127,8 +127,8 @@ class _Card extends StatelessWidget {
break;
}
return GestureDetector(
onTap: onTap,
child: BrandCard(
onTap: isReady ? onTap : null,
child: BrandCards.big(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -196,37 +196,31 @@ class _ProviderDetails extends StatelessWidget {
];
break;
}
return BrandModalSheet(
child: Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (_) {
return materialRoute(
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 40),
Padding(
padding: brandPagePadding2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconStatusMask(
status: provider.state,
child:
Icon(provider.icon, size: 40, color: Colors.white),
),
SizedBox(height: 10),
BrandText.h1(title),
SizedBox(height: 10),
...children
],
return BrandBottomSheet(
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 40),
Padding(
padding: paddingH15V0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconStatusMask(
status: provider.state,
child: Icon(provider.icon, size: 40, color: Colors.white),
),
)
],
),
);
},
SizedBox(height: 10),
BrandText.h1(title),
SizedBox(height: 10),
...children,
SizedBox(height: 30),
],
),
)
],
),
),
);
}

View File

@ -13,7 +13,7 @@ class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
padding: brandPagePadding2,
padding: paddingH15V0,
children: [
SizedBox(height: 10),
BrandHeader(title: 'basis.settings'.tr(), hasBackButton: true),

View File

@ -51,7 +51,7 @@ class CpuChart extends StatelessWidget {
interval: 20,
rotateAngle: 90.0,
showTitles: true,
getTextStyles: (value) => const TextStyle(
getTextStyles: (_, __) => const TextStyle(
fontSize: 10,
color: Colors.purple,
fontWeight: FontWeight.bold,
@ -60,7 +60,7 @@ class CpuChart extends StatelessWidget {
return bottomTitle(value.toInt());
}),
leftTitles: SideTitles(
getTextStyles: (value) => progressTextStyleLight.copyWith(
getTextStyles: (_, __) => progressTextStyleLight.copyWith(
color: Theme.of(context).brightness == Brightness.dark
? BrandColors.gray4
: null,
@ -82,7 +82,6 @@ class CpuChart extends StatelessWidget {
double appliedInterval,
double value,
) {
print(value);
if (value < 0) {
return false;
} else if (value == 0) {

View File

@ -71,7 +71,7 @@ class NetworkChart extends StatelessWidget {
interval: 20,
rotateAngle: 90.0,
showTitles: true,
getTextStyles: (value) => const TextStyle(
getTextStyles: (_, __) => const TextStyle(
fontSize: 10,
color: Colors.purple,
fontWeight: FontWeight.bold,
@ -87,7 +87,7 @@ class NetworkChart extends StatelessWidget {
].reduce(max) *
1.2 /
10,
getTextStyles: (value) => progressTextStyleLight.copyWith(
getTextStyles: (_, __) => progressTextStyleLight.copyWith(
color: Theme.of(context).brightness == Brightness.dark
? BrandColors.gray4
: null,

View File

@ -56,41 +56,58 @@ class _ServerDetailsState extends State<ServerDetails>
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
var providerState = isReady ? StateType.stable : StateType.uninitialized;
return TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: tabController,
children: [
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: brandPagePadding2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Header(
providerState: providerState,
tabController: tabController),
BrandText.body1('providers.server.bottom_sheet.1'.tr()),
SizedBox(height: 10),
BlocProvider(
create: (context) => HetznerMetricsCubit()..restart(),
child: _Chart(),
),
SizedBox(height: 20),
BlocProvider(
create: (context) => ServerDetailsCubit()..check(),
child: _TextDetails(),
),
],
),
),
],
),
return Scaffold(
appBar: PreferredSize(
child: Column(
children: [
Container(
height: 51,
alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 15),
child: BrandText.h4('basis.details'.tr()),
),
BrandDivider(),
],
),
_ServerSettings(tabController: tabController),
],
preferredSize: Size.fromHeight(52),
),
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: tabController,
children: [
SingleChildScrollView(
physics: ClampingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: paddingH15V0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Header(
providerState: providerState,
tabController: tabController),
BrandText.body1('providers.server.bottom_sheet.1'.tr()),
SizedBox(height: 10),
BlocProvider(
create: (context) => HetznerMetricsCubit()..restart(),
child: _Chart(),
),
SizedBox(height: 20),
BlocProvider(
create: (context) => ServerDetailsCubit()..check(),
child: _TextDetails(),
),
],
),
),
],
),
),
_ServerSettings(tabController: tabController),
],
),
);
}
}

View File

@ -11,7 +11,7 @@ class _ServerSettings extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
padding: brandPagePadding2,
padding: paddingH15V0,
children: [
SizedBox(height: 10),
Container(

View File

@ -27,13 +27,13 @@ class _TextDetails extends StatelessWidget {
children: [
TableRow(
children: [
getRowTitle('Last check'),
getRowTitle('Last check:'),
getRowValue(formater.format(checkTime)),
],
),
TableRow(
children: [
getRowTitle('Server Id'),
getRowTitle('Server Id:'),
getRowValue(data.id.toString()),
],
),
@ -48,7 +48,7 @@ class _TextDetails extends StatelessWidget {
),
TableRow(
children: [
getRowTitle('CPU'),
getRowTitle('CPU:'),
getRowValue(
data.serverType.cores.toString(),
),
@ -56,7 +56,7 @@ class _TextDetails extends StatelessWidget {
),
TableRow(
children: [
getRowTitle('Memory'),
getRowTitle('Memory:'),
getRowValue(
'${data.serverType.memory.toString()} GB',
),
@ -64,7 +64,7 @@ class _TextDetails extends StatelessWidget {
),
TableRow(
children: [
getRowTitle('Disk Local'),
getRowTitle('Disk Local:'),
getRowValue(
'${data.serverType.disk.toString()} GB',
),
@ -100,7 +100,7 @@ class _TextDetails extends StatelessWidget {
children: [
TableRow(
children: [
getRowTitle('Country'),
getRowTitle('Country:'),
getRowValue(
'${data.location.country}',
),
@ -108,13 +108,13 @@ class _TextDetails extends StatelessWidget {
),
TableRow(
children: [
getRowTitle('City'),
getRowTitle('City:'),
getRowValue(data.location.city),
],
),
TableRow(
children: [
getRowTitle('Description'),
getRowTitle('Description:'),
getRowValue(data.location.description),
],
),

View File

@ -1,13 +1,19 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.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/services/services_cubit.dart';
import 'package:selfprivacy/logic/models/job.dart';
import 'package:selfprivacy/logic/models/state_types.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_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.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/icon_status_mask/icon_status_mask.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
@ -17,6 +23,14 @@ import 'package:url_launcher/url_launcher.dart';
import '../rootRoute.dart';
const switchableServices = [
ServiceTypes.passwordManager,
ServiceTypes.cloud,
ServiceTypes.socialNetwork,
ServiceTypes.git,
ServiceTypes.vpn,
];
class ServicesPage extends StatefulWidget {
ServicesPage({Key? key}) : super(key: key);
@ -31,11 +45,14 @@ class _ServicesPageState extends State<ServicesPage> {
return Scaffold(
appBar: PreferredSize(
child: BrandHeader(title: 'basis.services'.tr()),
child: BrandHeader(
title: 'basis.services'.tr(),
hasFlashButton: true,
),
preferredSize: Size.fromHeight(52),
),
body: ListView(
padding: brandPagePadding2,
padding: paddingH15V0,
children: [
BrandText.body1('services.title'.tr()),
SizedBox(height: 24),
@ -60,78 +77,115 @@ class _Card extends StatelessWidget {
final ServiceTypes serviceType;
@override
Widget build(BuildContext context) {
String title;
IconData iconData;
String subtitle;
switch (serviceType) {
case ServiceTypes.mail:
iconData = BrandIcons.envelope;
title = 'services.mail.title'.tr();
subtitle = 'services.mail.subtitle'.tr();
break;
case ServiceTypes.messenger:
iconData = BrandIcons.messanger;
title = 'services.messenger.title'.tr();
subtitle = 'services.messenger.subtitle'.tr();
break;
case ServiceTypes.passwordManager:
iconData = BrandIcons.key;
title = 'services.password_manager.title'.tr();
subtitle = 'services.password_manager.subtitle'.tr();
break;
case ServiceTypes.video:
iconData = BrandIcons.webcam;
title = 'services.video.title'.tr();
subtitle = 'services.video.subtitle'.tr();
break;
case ServiceTypes.cloud:
iconData = BrandIcons.upload;
title = 'services.cloud.title'.tr();
subtitle = 'services.cloud.subtitle'.tr();
break;
case ServiceTypes.socialNetwork:
iconData = BrandIcons.social;
title = 'services.social_network.title'.tr();
subtitle = 'services.social_network.subtitle'.tr();
break;
case ServiceTypes.git:
iconData = BrandIcons.git;
title = 'services.git.title'.tr();
subtitle = 'services.git.subtitle'.tr();
break;
}
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
var changeTab = context.read<ChangeTab>().onPress;
var serviceState = context.watch<ServicesCubit>().state;
var jobsCubit = context.watch<JobsCubit>();
var jobState = jobsCubit.state;
var switchebleService = switchableServices.contains(serviceType);
var hasSwitchJob = switchebleService &&
jobState is JobsStateWithJobs &&
jobState.jobList
.any((el) => el is ServiceToggleJob && el.type == serviceType);
var isSwithOn = isReady &&
(!switchableServices.contains(serviceType) ||
serviceState.isEnableByType(serviceType));
return GestureDetector(
onTap: () => showDialog<void>(
context: context,
// isScrollControlled: true,
// backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _ServiceDetails(
serviceType: serviceType,
status: isReady ? StateType.stable : StateType.uninitialized,
title: title,
icon: iconData,
changeTab: changeTab,
);
},
),
child: BrandCard(
onTap: isSwithOn
? () => showDialog<void>(
context: context,
// isScrollControlled: true,
// backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _ServiceDetails(
serviceType: serviceType,
status:
isSwithOn ? StateType.stable : StateType.uninitialized,
title: serviceType.title,
icon: serviceType.icon,
changeTab: changeTab,
);
},
)
: null,
child: BrandCards.big(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconStatusMask(
status: isReady ? StateType.stable : StateType.uninitialized,
child: Icon(iconData, size: 30, color: Colors.white),
Row(
children: [
IconStatusMask(
status:
isSwithOn ? StateType.stable : StateType.uninitialized,
child: Icon(serviceType.icon, size: 30, color: Colors.white),
),
if (isReady && switchebleService) ...[
Spacer(),
Builder(
builder: (context) {
late bool isActive;
if (hasSwitchJob) {
isActive = ((jobState as JobsStateWithJobs)
.jobList
.firstWhere((el) =>
el is ServiceToggleJob &&
el.type == serviceType) as ServiceToggleJob)
.needToTurnOn;
} else {
isActive = serviceState.isEnableByType(serviceType);
}
return BrandSwitch(
value: isActive,
onChanged: (value) =>
jobsCubit.createOrRemoveServiceToggleJob(
ServiceToggleJob(
type: serviceType,
needToTurnOn: value,
),
),
);
},
),
]
],
),
SizedBox(height: 10),
BrandText.h2(title),
SizedBox(height: 10),
BrandText.body2(subtitle),
SizedBox(height: 10),
ClipRect(
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 10),
BrandText.h2(serviceType.title),
SizedBox(height: 10),
BrandText.body2(serviceType.subtitle),
SizedBox(height: 10),
],
),
if (hasSwitchJob)
Positioned(
bottom: 30,
left: 0,
right: 0,
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 3,
sigmaY: 2,
),
child: BrandText.h2(
'jobs.runJobs'.tr(),
textAlign: TextAlign.center,
),
),
)
],
),
)
],
),
),
@ -139,16 +193,6 @@ class _Card extends StatelessWidget {
}
}
enum ServiceTypes {
mail,
messenger,
passwordManager,
video,
cloud,
socialNetwork,
git,
}
class _ServiceDetails extends StatelessWidget {
const _ServiceDetails({
Key? key,
@ -347,7 +391,12 @@ class _ServiceDetails extends StatelessWidget {
],
));
break;
case ServiceTypes.vpn:
child = Text(
'services.vpn.bottom_sheet.1'.tr(),
);
}
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
@ -359,7 +408,7 @@ class _ServiceDetails extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: brandPagePadding1,
padding: paddingH15V30,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -376,7 +425,7 @@ class _ServiceDetails extends StatelessWidget {
child: Container(
child: BrandButton.rised(
onPressed: () => Navigator.of(context).pop(),
title: 'basis.close'.tr(),
text: 'basis.close'.tr(),
),
),
),
@ -407,252 +456,3 @@ class _ServiceDetails extends StatelessWidget {
}
}
}
// class _ServiceDetails extends StatelessWidget {
// const _ServiceDetails({
// Key? key,
// required this.serviceType,
// required this.icon,
// required this.status,
// required this.title,
// required this.changeTab,
// }) : super(key: key);
// final ServiceTypes serviceType;
// final IconData icon;
// final StateType status;
// final String title;
// final ValueChanged<int> changeTab;
// @override
// Widget build(BuildContext context) {
// late Widget child;
// var config = context.watch<AppConfigCubit>().state;
// var domainName = UiHelpers.getDomainName(config);
// var linksStyle = body1Style.copyWith(
// fontSize: 15,
// color: Theme.of(context).brightness == Brightness.dark
// ? Colors.white
// : BrandColors.black,
// fontWeight: FontWeight.bold,
// decoration: TextDecoration.underline,
// // height: 1.1,
// );
// var textStyle = body1Style.copyWith(
// color: Theme.of(context).brightness == Brightness.dark
// ? Colors.white
// : BrandColors.black,
// );
// switch (serviceType) {
// case ServiceTypes.mail:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.mail.bottom_sheet.1'.tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// child: Text(
// 'services.mail.bottom_sheet.2'.tr(),
// style: linksStyle,
// ),
// onTap: () {
// Navigator.of(context).pop();
// changeTab(2);
// },
// ),
// ),
// ),
// ],
// ));
// break;
// case ServiceTypes.messenger:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.messenger.bottom_sheet.1'.tr(args: [domainName]),
// style: textStyle,
// )
// ],
// ));
// break;
// case ServiceTypes.passwordManager:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.password_manager.bottom_sheet.1'
// .tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// onTap: () => _launchURL('https://password.$domainName'),
// child: Text(
// 'password.$domainName',
// style: linksStyle,
// ),
// ),
// ),
// ),
// ],
// ));
// break;
// case ServiceTypes.video:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.video.bottom_sheet.1'.tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// onTap: () => _launchURL('https://meet.$domainName'),
// child: Text(
// 'meet.$domainName',
// style: linksStyle,
// ),
// ),
// ),
// ),
// ],
// ));
// break;
// case ServiceTypes.cloud:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.cloud.bottom_sheet.1'.tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// onTap: () => _launchURL('https://cloud.$domainName'),
// child: Text(
// 'cloud.$domainName',
// style: linksStyle,
// ),
// ),
// ),
// ),
// ],
// ));
// break;
// case ServiceTypes.socialNetwork:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.social_network.bottom_sheet.1'
// .tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// onTap: () => _launchURL('https://social.$domainName'),
// child: Text(
// 'social.$domainName',
// style: linksStyle,
// ),
// ),
// ),
// ),
// ],
// ));
// break;
// case ServiceTypes.git:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.git.bottom_sheet.1'.tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// onTap: () => _launchURL('https://git.$domainName'),
// child: Text(
// 'git.$domainName',
// style: linksStyle,
// ),
// ),
// ),
// ),
// ],
// ));
// break;
// }
// return BrandModalSheet(
// child: Navigator(
// key: navigatorKey,
// initialRoute: '/',
// onGenerateRoute: (_) {
// return materialRoute(
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Padding(
// padding: brandPagePadding1,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// SizedBox(height: 13),
// IconStatusMask(
// status: status,
// child: Icon(icon, size: 40, color: Colors.white),
// ),
// SizedBox(height: 10),
// BrandText.h1(title),
// SizedBox(height: 10),
// child,
// ],
// ),
// )
// ],
// ),
// );
// },
// ),
// );
// }
// void _launchURL(url) async {
// var _possible = await canLaunch(url);
// if (_possible) {
// try {
// await launch(
// url,
// forceSafariVC: true,
// enableJavaScript: true,
// );
// } catch (e) {
// print(e);
// }
// } else {
// throw 'Could not launch $url';
// }
// }
// }

View File

@ -7,10 +7,26 @@ class _NewUser extends StatelessWidget {
var domainName = UiHelpers.getDomainName(config);
return BrandModalSheet(
return BrandBottomSheet(
child: BlocProvider(
create: (context) =>
UserFormCubit(usersCubit: context.read<UsersCubit>()),
create: (context) {
var jobCubit = context.read<JobsCubit>();
var jobState = jobCubit.state;
var users = <User>[];
users.addAll(context.read<UsersCubit>().state.users);
if (jobState is JobsStateWithJobs) {
var jobs = jobState.jobList;
jobs.forEach((job) {
if (job is CreateUserJob) {
users.add(job.user);
}
});
}
return UserFormCubit(
jobsCubit: jobCubit,
users: users,
);
},
child: Builder(builder: (context) {
var formCubitState = context.watch<UserFormCubit>().state;
@ -22,20 +38,24 @@ class _NewUser extends StatelessWidget {
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
BrandHeader(
title: 'users.new_user'.tr(),
),
SizedBox(width: 14),
Padding(
padding: brandPagePadding2,
padding: paddingH15V0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CubitFormTextField(
formFieldCubit: context.read<UserFormCubit>().login,
decoration: InputDecoration(
labelText: 'users.login'.tr(),
suffixText: '@$domainName',
IntrinsicHeight(
child: CubitFormTextField(
formFieldCubit: context.read<UserFormCubit>().login,
decoration: InputDecoration(
labelText: 'users.login'.tr(),
suffixText: '@$domainName',
),
),
),
SizedBox(height: 20),
@ -62,7 +82,7 @@ class _NewUser extends StatelessWidget {
onPressed: formCubitState.isSubmitting
? null
: () => context.read<UserFormCubit>().trySubmit(),
title: 'basis.create'.tr(),
text: 'basis.create'.tr(),
),
SizedBox(height: 40),
Text('users.new_user_info_note'.tr()),

View File

@ -1,24 +1,22 @@
part of 'users.dart';
class _User extends StatelessWidget {
const _User({Key? key, this.user}) : super(key: key);
const _User({Key? key, required this.user}) : super(key: key);
final User? user;
final User user;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
showModalBottomSheet<void>(
showBrandBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _UserDetails(user: user);
},
);
},
child: Container(
padding: brandPagePadding2,
padding: paddingH15V0,
height: 48,
child: Row(
children: [
@ -26,12 +24,12 @@ class _User extends StatelessWidget {
width: 17,
height: 17,
decoration: BoxDecoration(
color: user!.color,
color: user.color,
shape: BoxShape.circle,
),
),
SizedBox(width: 20),
BrandText.h4(user!.login),
BrandText.h4(user.login),
],
),
),

View File

@ -3,10 +3,10 @@ part of 'users.dart';
class _UserDetails extends StatelessWidget {
const _UserDetails({
Key? key,
this.user,
required this.user,
}) : super(key: key);
final User? user;
final User user;
@override
Widget build(BuildContext context) {
@ -14,14 +14,16 @@ class _UserDetails extends StatelessWidget {
var domainName = UiHelpers.getDomainName(config);
return BrandModalSheet(
return BrandBottomSheet(
isExpended: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 200,
decoration: BoxDecoration(
color: user!.color,
color: user.color,
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
@ -114,7 +116,7 @@ class _UserDetails extends StatelessWidget {
horizontal: 15,
),
child: BrandText.h1(
user!.login,
user.login,
softWrap: true,
overflow: TextOverflow.ellipsis,
)),
@ -123,7 +125,7 @@ class _UserDetails extends StatelessWidget {
),
SizedBox(height: 20),
Padding(
padding: brandPagePadding2.copyWith(bottom: 20),
padding: paddingH15V0.copyWith(bottom: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -131,14 +133,14 @@ class _UserDetails extends StatelessWidget {
Container(
height: 40,
alignment: Alignment.centerLeft,
child: BrandText.h4('${user!.login}@$domainName'),
child: BrandText.h4('${user.login}@$domainName'),
),
SizedBox(height: 14),
BrandText.small('basis.password'.tr()),
Container(
height: 40,
alignment: Alignment.centerLeft,
child: BrandText.h4(user!.password),
child: BrandText.h4(user.password),
),
SizedBox(height: 24),
BrandDivider(),
@ -146,7 +148,10 @@ class _UserDetails extends StatelessWidget {
BrandButton.emptyWithIconText(
title: 'users.send_regisration_data'.tr(),
icon: Icon(BrandIcons.share),
onPressed: () {},
onPressed: () {
Share.share(
'login: ${user.login}, password: ${user.password}');
},
),
SizedBox(height: 20),
],

View File

@ -4,17 +4,21 @@ import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
import 'package:selfprivacy/logic/models/job.dart';
import 'package:selfprivacy/logic/models/user.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_divider/brand_divider.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/ui/helpers/modals.dart';
import 'package:selfprivacy/utils/ui_helpers.dart';
import 'package:share_plus/share_plus.dart';
part 'fab.dart';
part 'new_user.dart';
@ -31,7 +35,6 @@ class UsersPage extends StatelessWidget {
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
final users = usersCubitState.users;
final isEmpty = usersCubitState.isEmpty;
Widget child;
if (!isReady) {
@ -46,14 +49,17 @@ class UsersPage extends StatelessWidget {
)
: ListView(
children: [
...users.map((user) => _User(user: user)),
...users.map((user) => _User(user: user)).toList(),
],
);
}
return Scaffold(
appBar: PreferredSize(
child: BrandHeader(title: 'basis.users'.tr()),
child: BrandHeader(
title: 'basis.users'.tr(),
hasFlashButton: true,
),
preferredSize: Size.fromHeight(52),
),
floatingActionButton: isReady ? _Fab() : null,

View File

@ -2,10 +2,8 @@ library elevation_extension;
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:selfprivacy/config/brand_shadow.dart';
extension ElevationExtension on BoxDecoration {
BoxDecoration get ev8 => copyWith(boxShadow: [shadow8]);
BoxDecoration copyWith({
Color? color,

View File

@ -0,0 +1,51 @@
import 'dart:ui';
import 'package:flutter/cupertino.dart';
extension TextExtension on Text {
Text withColor(Color color) => Text(
data!,
key: this.key,
strutStyle: this.strutStyle,
textAlign: this.textAlign,
textDirection: this.textDirection,
locale: this.locale,
softWrap: this.softWrap,
overflow: this.overflow,
textScaleFactor: this.textScaleFactor,
maxLines: this.maxLines,
semanticsLabel: this.semanticsLabel,
textWidthBasis: textWidthBasis ?? this.textWidthBasis,
style: this.style != null
? this.style!.copyWith(color: color)
: TextStyle(color: color),
);
Text copyWith({
Key? key,
StrutStyle? strutStyle,
TextAlign? textAlign,
TextDirection? textDirection,
Locale? locale,
bool? softWrap,
TextOverflow? overflow,
double? textScaleFactor,
int? maxLines,
String? semanticsLabel,
TextWidthBasis? textWidthBasis,
TextStyle? style,
}) {
return Text(data!,
key: key ?? this.key,
strutStyle: strutStyle ?? this.strutStyle,
textAlign: textAlign ?? this.textAlign,
textDirection: textDirection ?? this.textDirection,
locale: locale ?? this.locale,
softWrap: softWrap ?? this.softWrap,
overflow: overflow ?? this.overflow,
textScaleFactor: textScaleFactor ?? this.textScaleFactor,
maxLines: maxLines ?? this.maxLines,
semanticsLabel: semanticsLabel ?? this.semanticsLabel,
textWidthBasis: textWidthBasis ?? this.textWidthBasis,
style: style != null ? this.style?.merge(style) ?? style : this.style);
}
}

View File

@ -1,138 +1,99 @@
import 'dart:math';
var generator = PasswordGenerator();
Random _rnd = Random();
String genPass() {
generator.generate(8);
return generator.getGeneratedValue();
}
///Generates a password.
///
///The password [_generatedValue] is of a specified length, including letters [_letterGen] of mixed cases,
///numbers [_numGen], and symbols[_symGen] depending on user choice.
class PasswordGenerator {
late bool _letterGen;
late bool _numGen;
late bool _symGen;
late String _generatedValue;
///Constructor.
///
///[_letterGen] is true to make password generation possible from the opening of the application, and
///[_generatedValue] is intialized to the value below so the text containing it can be first generated
///upon users request
PasswordGenerator() {
_letterGen = true;
_numGen = true;
_symGen = false;
_generatedValue = "Press Generate";
}
///Call to generate a value, of [n] length
void generate(int n) {
//Discards the old value
_generatedValue = "";
///Cannot generate a value without any character types selected
if (!_letterGen && !_numGen && !_symGen) {
_generatedValue = "No character type selected";
return;
}
///'Randomly' selectes caracter type to generate and append [toAppend] to [_generatedValue]
// ignore: unnecessary_statements
for (n; n > 0; n--) {
String? toAppend;
var random = new Random();
///loops until a valid character is generated, meaning the user has to check the character value
///to be generated. 'Randomly' picks a character type.
while (toAppend == null) {
int selector = random.nextInt(3);
if (selector == 0) {
toAppend = _generateLetter();
} else if (selector == 1) {
toAppend = _generateNumber();
} else {
toAppend = _generateSymbol();
}
}
_generatedValue += toAppend;
toAppend = null;
}
}
///Generates a letter when called.
String _generateLetter() {
if (!_letterGen) return '';
///Finds the integer value for the range between a-z and A-Z, with [base] UTF-16 value for lowercase letters and
///[baseUpper] UTF-16 value for uppercase letters
int base = "a".codeUnitAt(0);
int baseUpper = "A".codeUnitAt(0);
int maxRand = ("z".codeUnitAt(0) - base) + 1;
Random random = new Random();
///Randomly selects between upper and lower case generation, randomly generates value from [maxRand], then adding base,
///which creates a UTF-16 encoded character to be converted into a string of one character between a-z/A-Z.
///This string is then returned.
if (random.nextInt(2) == 0) {
return String.fromCharCodes([random.nextInt(maxRand) + base]);
} else {
return String.fromCharCodes([random.nextInt(maxRand) + baseUpper]);
}
}
///Generates a number when called
String? _generateNumber() {
if (!_numGen) return null;
///Finds the integer value for the range between 0-9
int base = "0".codeUnitAt(0);
int maxRand = ("9".codeUnitAt(0) - base) + 1;
Random random = new Random();
///Randomly generates value from [maxRand], then adding base, which creates a UTF-16 encoded character to be converted into a
///string of one character between 0-9. This string is then returned.
return String.fromCharCodes([random.nextInt(maxRand) + base]);
}
///Generates a symbol when called
String? _generateSymbol() {
if (!_symGen) return null;
///Finds the integer value for the range between symbols !-.
///(note) which includes symbols !"#$%&'()*+,=.
int base = "!".codeUnitAt(0);
int maxRand = (".".codeUnitAt(0) - base) + 1;
Random random = new Random();
///Randomly generates value from [maxRand], then adding base, which creates a UTF-16 encoded character to be
///converted into a string of one character between !-. . This string is then returned.
return String.fromCharCodes([random.nextInt(maxRand) + base]);
}
///Toggles letter generation
void checkLetterGen(bool value) {
_letterGen = value;
}
///Toggles number generation
void checkNumGen(bool value) {
_numGen = value;
}
///Toggles symbol generation
void checkSymGen(bool value) {
_symGen = value;
}
///Returns the generated value to be used by generator app
String getGeneratedValue() {
return _generatedValue;
}
typedef StringGeneratorFunction = String Function();
class StringGenerators {
static const letters = 'abcdefghijklmnopqrstuvwxyz';
static const numbers = '1234567890';
static const symbols = '_';
static String getRandomString(
int length, {
hasLowercaseLetters = false,
hasUppercaseLetters = false,
hasNumbers = false,
hasSymbols = false,
isStrict = false,
}) {
var chars = '';
if (hasLowercaseLetters) chars += letters;
if (hasUppercaseLetters) chars += letters.toUpperCase();
if (hasNumbers) chars += numbers;
if (hasSymbols) chars += symbols;
assert(chars.isNotEmpty, 'chart empty');
if (!isStrict) {
return genString(length, chars);
}
var res = '';
var loose = length;
if (hasLowercaseLetters) {
loose -= 1;
res += genString(1, letters);
}
if (hasUppercaseLetters) {
loose -= 1;
res += genString(1, letters.toUpperCase());
}
if (hasNumbers) {
loose -= 1;
res += genString(1, numbers.toUpperCase());
}
if (hasSymbols) {
loose -= 1;
res += genString(1, symbols);
}
res += genString(loose, chars);
var shuffledlist = res.split('')..shuffle();
return shuffledlist.join();
}
static String genString(int length, String chars) {
return String.fromCharCodes(
Iterable.generate(
length,
(_) => chars.codeUnitAt(
_rnd.nextInt(chars.length),
),
),
);
}
static StringGeneratorFunction userPassword = () => getRandomString(
8,
hasLowercaseLetters: true,
hasUppercaseLetters: true,
hasNumbers: true,
isStrict: true,
);
static StringGeneratorFunction passwordSalt = () => getRandomString(
8,
hasLowercaseLetters: true,
);
static StringGeneratorFunction simpleId = () => getRandomString(
5,
hasLowercaseLetters: true,
);
static StringGeneratorFunction dbPassword = () => getRandomString(
40,
hasLowercaseLetters: true,
hasUppercaseLetters: true,
hasNumbers: true,
hasSymbols: true,
);
static StringGeneratorFunction dbStorageName = () => getRandomString(
6,
hasLowercaseLetters: true,
hasUppercaseLetters: true,
hasNumbers: true,
);
}

View File

@ -1,14 +0,0 @@
import 'dart:math';
const _chars =
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890_';
Random _rnd = Random();
String getRandomString(int length, [chars = _chars]) => String.fromCharCodes(
Iterable.generate(
length,
(_) => chars.codeUnitAt(
_rnd.nextInt(chars.length),
),
),
);

View File

@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "19.0.0"
version: "22.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.7.2"
archive:
dependency: transitive
description:
@ -28,28 +28,35 @@ packages:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.2.0"
asn1lib:
dependency: transitive
description:
name: asn1lib
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
version: "2.8.1"
basic_utils:
dependency: "direct dev"
dependency: "direct main"
description:
name: basic_utils
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0-nullsafety.3"
version: "3.5.0"
bloc:
dependency: transitive
description:
name: bloc
url: "https://pub.dartlang.org"
source: hosted
version: "7.0.0"
version: "7.1.0"
boolean_selector:
dependency: transitive
description:
@ -63,56 +70,56 @@ packages:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.0"
build_config:
dependency: transitive
description:
name: build_config
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.7"
version: "1.0.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.10"
version: "3.0.0"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.4"
build_runner:
dependency: "direct dev"
description:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "1.12.2"
version: "2.1.1"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.12"
version: "7.1.0"
built_collection:
dependency: transitive
description:
name: built_collection
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
version: "5.1.0"
built_value:
dependency: transitive
description:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.4"
version: "8.1.2"
characters:
dependency: transitive
description:
@ -126,7 +133,7 @@ packages:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
checked_yaml:
dependency: transitive
description:
@ -140,7 +147,7 @@ packages:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
version: "0.3.3"
clock:
dependency: transitive
description:
@ -154,7 +161,7 @@ packages:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "3.7.0"
version: "4.1.0"
collection:
dependency: transitive
description:
@ -168,14 +175,14 @@ packages:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.0.1"
coverage:
dependency: transitive
description:
name: coverage
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
version: "1.0.3"
crypt:
dependency: "direct main"
description:
@ -184,33 +191,33 @@ packages:
source: hosted
version: "4.0.1"
crypto:
dependency: "direct main"
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.0.1"
cubit_form:
dependency: "direct main"
description:
name: cubit_form
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2-nullsafety.0"
version: "1.0.18"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
version: "1.0.3"
dart_style:
dependency: transitive
description:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.3"
dio:
dependency: "direct main"
description:
@ -252,7 +259,14 @@ packages:
name: equatable
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.3"
extended_masked_text:
dependency: transitive
description:
name: extended_masked_text
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.1"
fake_async:
dependency: transitive
description:
@ -266,14 +280,14 @@ packages:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.1.2"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.0"
version: "6.1.2"
fixnum:
dependency: transitive
description:
@ -287,7 +301,7 @@ packages:
name: fl_chart
url: "https://pub.dartlang.org"
source: hosted
version: "0.35.0"
version: "0.40.0"
flutter:
dependency: "direct main"
description: flutter
@ -299,14 +313,14 @@ packages:
name: flutter_bloc
url: "https://pub.dartlang.org"
source: hosted
version: "7.0.0"
version: "7.2.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.0"
version: "0.9.2"
flutter_localizations:
dependency: transitive
description: flutter
@ -318,14 +332,21 @@ packages:
name: flutter_markdown
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.1"
version: "0.6.5"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
version: "4.2.1"
flutter_test:
dependency: "direct dev"
description: flutter
@ -336,13 +357,20 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
get_it:
dependency: "direct main"
description:
name: get_it
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.0"
version: "7.2.0"
glob:
dependency: transitive
description:
@ -356,42 +384,42 @@ packages:
name: graphs
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "2.0.0"
hive:
dependency: "direct main"
description:
name: hive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
version: "2.0.4"
hive_flutter:
dependency: "direct main"
description:
name: hive_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.1.0"
hive_generator:
dependency: "direct dev"
description:
name: hive_generator
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "1.1.0"
http:
dependency: transitive
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.1"
version: "0.13.3"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.0.1"
http_parser:
dependency: transitive
description:
@ -419,7 +447,14 @@ packages:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.0.3"
ionicons:
dependency: "direct main"
description:
name: ionicons
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2"
js:
dependency: transitive
description:
@ -440,7 +475,14 @@ packages:
name: json_serializable
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
version: "4.1.4"
local_auth:
dependency: "direct main"
description:
name: local_auth
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.7"
logging:
dependency: transitive
description:
@ -455,13 +497,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
mask_text_input_formatter:
dependency: transitive
description:
name: mask_text_input_formatter
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0-nullsafety.2"
matcher:
dependency: transitive
description:
@ -475,7 +510,7 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.7.0"
mime:
dependency: transitive
description:
@ -483,6 +518,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
modal_bottom_sheet:
dependency: "direct main"
description:
name: modal_bottom_sheet
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
nanoid:
dependency: "direct main"
description:
name: nanoid
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
nested:
dependency: transitive
description:
@ -496,7 +545,7 @@ packages:
name: node_preamble
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.13"
version: "2.0.1"
package_config:
dependency: transitive
description:
@ -510,7 +559,7 @@ packages:
name: package_info
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.2"
path:
dependency: transitive
description:
@ -524,21 +573,21 @@ packages:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
version: "2.0.2"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.2"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.2"
path_provider_platform_interface:
dependency: transitive
description:
@ -552,42 +601,42 @@ packages:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.3"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.0"
version: "1.11.1"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.2"
version: "4.1.0"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.0.2"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.1"
pointycastle:
dependency: transitive
dependency: "direct main"
description:
name: pointycastle
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.3.2"
pool:
dependency: transitive
description:
@ -608,14 +657,14 @@ packages:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.1"
version: "4.2.3"
provider:
dependency: "direct main"
description:
name: provider
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
version: "6.0.0"
pub_semver:
dependency: transitive
description:
@ -630,27 +679,83 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
rsa_encrypt:
dependency: "direct main"
description:
name: rsa_encrypt
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
share_plus:
dependency: "direct main"
description:
name: share_plus
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
share_plus_linux:
dependency: transitive
description:
name: share_plus_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
share_plus_macos:
dependency: transitive
description:
name: share_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
share_plus_web:
dependency: transitive
description:
name: share_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
share_plus_windows:
dependency: transitive
description:
name: share_plus_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
version: "2.0.7"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.2"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.2"
shared_preferences_platform_interface:
dependency: transitive
description:
@ -664,21 +769,21 @@ packages:
name: shared_preferences_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.2"
shelf:
dependency: transitive
description:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.2.0"
shelf_packages_handler:
dependency: transitive
description:
@ -692,7 +797,7 @@ packages:
name: shelf_static
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.1.0"
shelf_web_socket:
dependency: transitive
description:
@ -711,7 +816,14 @@ packages:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.0.3"
source_helper:
dependency: transitive
description:
name: source_helper
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
source_map_stack_trace:
dependency: transitive
description:
@ -732,7 +844,14 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.1"
ssh_key:
dependency: "direct main"
description:
name: ssh_key
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.0"
stack_trace:
dependency: transitive
description:
@ -774,21 +893,21 @@ packages:
name: test
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.5"
version: "1.17.10"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.19"
version: "0.4.2"
test_core:
dependency: transitive
description:
name: test_core
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.15"
version: "0.4.0"
timing:
dependency: transitive
description:
@ -796,6 +915,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
tuple:
dependency: transitive
description:
name: tuple
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
typed_data:
dependency: transitive
description:
@ -803,55 +929,48 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
unicons:
dependency: "direct main"
description:
name: unicons
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.3"
version: "6.0.9"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.1"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "2.0.4"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.4"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.2"
vector_math:
dependency: transitive
description:
@ -865,42 +984,42 @@ packages:
name: vm_service
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.0+1"
version: "6.2.0"
wakelock:
dependency: "direct main"
description:
name: wakelock
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0+2"
version: "0.5.3+3"
wakelock_macos:
dependency: transitive
description:
name: wakelock_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0"
version: "0.1.0+2"
wakelock_platform_interface:
dependency: transitive
description:
name: wakelock_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
version: "0.2.1+2"
wakelock_web:
dependency: transitive
description:
name: wakelock_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
version: "0.2.0+2"
wakelock_windows:
dependency: transitive
description:
name: wakelock_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0"
version: "0.1.0+1"
watcher:
dependency: transitive
description:
@ -914,7 +1033,7 @@ packages:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.0"
webkit_inspection_protocol:
dependency: transitive
description:
@ -928,7 +1047,7 @@ packages:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
version: "2.2.7"
xdg_directories:
dependency: transitive
description:
@ -942,7 +1061,7 @@ packages:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.2"
version: "5.1.2"
yaml:
dependency: transitive
description:
@ -951,5 +1070,5 @@ packages:
source: hosted
version: "3.1.0"
sdks:
dart: ">=2.12.0 <3.0.0"
flutter: ">=2.0.0"
dart: ">=2.13.4 <3.0.0"
flutter: ">=2.2.3"

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