mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-23 09:16:54 +00:00
chore: Merge master into digital-ocean-dns
This commit is contained in:
commit
6767b679a0
40
.drone.yml
40
.drone.yml
|
@ -97,16 +97,16 @@ steps:
|
|||
GOOGLE_KEYSTORE_PASS:
|
||||
from_secret: GOOGLE_KEYSTORE_PASS
|
||||
|
||||
# - name: Build Intermediate Android Release Artifact (Bundle)
|
||||
# commands:
|
||||
# - ./ci.py --build-bundle
|
||||
# environment:
|
||||
# STANDALONE_KEYSTORE_PASS:
|
||||
# from_secret: STANDALONE_KEYSTORE_PASS
|
||||
# FDROID_KEYSTORE_PASS:
|
||||
# from_secret: FDROID_KEYSTORE_PASS
|
||||
# GOOGLE_KEYSTORE_PASS:
|
||||
# from_secret: GOOGLE_KEYSTORE_PASS
|
||||
- name: Build Intermediate Android Release Artifact (Bundle)
|
||||
commands:
|
||||
- ./ci.py --build-bundle
|
||||
environment:
|
||||
STANDALONE_KEYSTORE_PASS:
|
||||
from_secret: STANDALONE_KEYSTORE_PASS
|
||||
FDROID_KEYSTORE_PASS:
|
||||
from_secret: FDROID_KEYSTORE_PASS
|
||||
GOOGLE_KEYSTORE_PASS:
|
||||
from_secret: GOOGLE_KEYSTORE_PASS
|
||||
|
||||
- name: Sign Android Release Artifact (.APK) for Standalone Use
|
||||
commands:
|
||||
|
@ -132,16 +132,16 @@ steps:
|
|||
GOOGLE_KEYSTORE_PASS:
|
||||
from_secret: GOOGLE_KEYSTORE_PASS
|
||||
|
||||
# - name: Sign Android Release Artifact (Bundle) for Google Play
|
||||
# commands:
|
||||
# - ./ci.py --sign-bundle
|
||||
# environment:
|
||||
# STANDALONE_KEYSTORE_PASS:
|
||||
# from_secret: STANDALONE_KEYSTORE_PASS
|
||||
# FDROID_KEYSTORE_PASS:
|
||||
# from_secret: FDROID_KEYSTORE_PASS
|
||||
# GOOGLE_KEYSTORE_PASS:
|
||||
# from_secret: GOOGLE_KEYSTORE_PASS
|
||||
- name: Sign Android Release Artifact (Bundle) for Google Play
|
||||
commands:
|
||||
- ./ci.py --sign-bundle
|
||||
environment:
|
||||
STANDALONE_KEYSTORE_PASS:
|
||||
from_secret: STANDALONE_KEYSTORE_PASS
|
||||
FDROID_KEYSTORE_PASS:
|
||||
from_secret: FDROID_KEYSTORE_PASS
|
||||
GOOGLE_KEYSTORE_PASS:
|
||||
from_secret: GOOGLE_KEYSTORE_PASS
|
||||
|
||||
- name: Package Linux AppImage Artifact
|
||||
commands:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.bundle.enableUncompressedNativeLibs=false
|
||||
|
|
6
assets/images/logos/digital_ocean.svg
Normal file
6
assets/images/logos/digital_ocean.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.0013 24.0128V19.3592C16.927 19.3592 20.7505 14.4743 18.8591 9.2901C18.1652 7.37152 16.6276 5.83395 14.709 5.14C9.52482 3.26224 4.63995 7.07217 4.63995 11.9979H0C0 4.14669 7.59264 -1.97641 15.8248 0.595295C19.417 1.72467 22.2881 4.58211 23.4038 8.17433C25.9755 16.4201 19.8661 24.0128 12.0013 24.0128Z" fill="white"/>
|
||||
<path d="M12.0149 19.3729H7.38855V14.7466H12.0149V19.3729Z" fill="white"/>
|
||||
<path d="M7.38861 22.9376H3.82361V19.3726H7.38861V22.9376Z" fill="white"/>
|
||||
<path d="M3.82354 19.373H0.843628V16.3931H3.82354V19.373Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 657 B |
10
assets/images/logos/hetzner.svg
Normal file
10
assets/images/logos/hetzner.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="24" height="23" viewBox="0 0 24 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_51804_9018)">
|
||||
<path d="M22.5704 0H19.3252C18.5948 0 18.2817 0.302609 18.2817 1.04348V9.25565H5.35304V1.04348C5.35304 0.313043 5.05044 0 4.30957 0H1.04348C0.302609 0 0 0.302609 0 1.04348V22.1739C0 22.9148 0.302609 23.2174 1.04348 23.2174H4.30957C5.04 23.2174 5.35304 22.9252 5.35304 22.1739V13.8261H18.2922V22.1739C18.2922 22.9043 18.5948 23.2174 19.3357 23.2174H22.5809C23.3113 23.2174 23.6243 22.9148 23.6243 22.1739V1.04348C23.6035 0.333913 23.3009 0 22.5704 0Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_51804_9018">
|
||||
<rect width="24" height="22.9565" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 722 B |
12
assets/markdown/how_digital_ocean-en.md
Normal file
12
assets/markdown/how_digital_ocean-en.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
### How to get Digital Ocean API Token
|
||||
1. Visit the following [link](https://cloud.digitalocean.com/) and sign
|
||||
into newly created account.
|
||||
2. Enter into previously created project. If you haven't created one,
|
||||
then please proceed.
|
||||
3. Go to the "API" link on the left bar.
|
||||
4. Click on the "Generate New Token".
|
||||
5. Enter any name for the token.
|
||||
6. Put expiration time to "No expiry".
|
||||
7. Check the "Write (optional)" checkbox.
|
||||
8. Now click on the "Generate Token" button.
|
||||
9. After that, the token will be shown. Store it in any reliable place, preferably a password manager.
|
10
assets/markdown/how_digital_ocean-ru.md
Normal file
10
assets/markdown/how_digital_ocean-ru.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
### How to get Digital Ocean API Token
|
||||
1. Перейдите по [ссылке](https://cloud.digitalocean.com/) и войдите в ваш аккаунт.
|
||||
2. Перейдите в новый проект, либо создайте проект, если ещё этого не сделали.
|
||||
3. Перейдите в "API" раздел в меню слева.
|
||||
4. Нажмите на "Generate New Token".
|
||||
5. Введите какое-нибудь имя для токена.
|
||||
6. Установите время истощения на "No expiry".
|
||||
7. Проставьте галочку в пункте "Write (optional)".
|
||||
8. Теперь нажмите на "Generate Token" кнопку внизу.
|
||||
9. После этого появится ваш токен. Скопируйте его в надёжное место, лучше в ваш собственный менеджер паролей.
|
|
@ -4,6 +4,7 @@
|
|||
"basis": {
|
||||
"providers": "Providers",
|
||||
"providers_title": "Your Data Center",
|
||||
"select": "Select",
|
||||
"services": "Services",
|
||||
"services_title": "Your personal, private and independent services.",
|
||||
"users": "Users",
|
||||
|
@ -53,7 +54,8 @@
|
|||
"about_application_page": {
|
||||
"title": "About",
|
||||
"application_version_text": "Application version v.{}",
|
||||
"api_version_text": "Server API version v.{}"
|
||||
"api_version_text": "Server API version v.{}",
|
||||
"privacy_policy": "Privacy policy"
|
||||
},
|
||||
"application_settings": {
|
||||
"title": "Application settings",
|
||||
|
@ -79,8 +81,14 @@
|
|||
"onboarding": {
|
||||
"page1_title": "Digital independence, available to all of us",
|
||||
"page1_text": "Mail, VPN, Messenger, social network and much more on your private server, under your control.",
|
||||
"page2_title": "SelfPrivacy — it's not a cloud, but your personal datacenter",
|
||||
"page2_text": "SelfPrivacy works only with your provider accounts: Hetzner, Cloudflare, Backblaze. If you do not own those, we'll help you to create them."
|
||||
"page2_title": "SelfPrivacy is not a cloud, it's Your personal datacenter",
|
||||
"page2_text": "SelfPrivacy only works with providers that you choose. If you do not have required accounts in those, we'll help you to create them.",
|
||||
"page2_server_provider_title": "Server provider",
|
||||
"page2_server_provider_text": "A server provider maintains your server in its own data center. SelfPrivacy will automatically connect to the provider and setup all necessary things.",
|
||||
"page2_dns_provider_title": "DNS provider",
|
||||
"page2_dns_provider_text": "You need a domain to have a place in the Internet. And you also need a reliable DNS provider to have the domain pointed to your server. We will suggest you pick a supported DNS provider to automatically setup networking.",
|
||||
"page2_backup_provider_title": "Backup provider",
|
||||
"page2_backup_provider_text": "What if something happens to your server? Imagine a hacker attack, an accidental data deletion or denial of service? Your data will be kept safe in your provider of backups. They will be securely encrypted and anytime accessible to restore your server with."
|
||||
},
|
||||
"resource_chart": {
|
||||
"month": "Month",
|
||||
|
@ -268,21 +276,47 @@
|
|||
"no_ssh_notice": "Only email and SSH accounts are created for this user. Single Sign On for all services is coming soon."
|
||||
},
|
||||
"initializing": {
|
||||
"connect_to_server": "Connect the server provider",
|
||||
"select_provider": "Select your provider",
|
||||
"server_provider_description": "A place where your data and SelfPrivacy services will reside:",
|
||||
"dns_provider_description": "A service which lets your IP point towards domain names:",
|
||||
"connect_to_server": "Let's start with a server.",
|
||||
"select_provider": "Pick any provider from the following list, they all support SelfPrivacy",
|
||||
"select_provider_notice": "By 'Relatively small' we mean a machine with 2 cores of CPU and 2 gigabytes of RAM.",
|
||||
"select_provider_countries_title": "Available countries",
|
||||
"select_provider_countries_text_hetzner": "Germany, Finland, USA",
|
||||
"select_provider_countries_text_do": "USA, Netherlands, Singapore, UK, Germany, Canada, India, Australia",
|
||||
"select_provider_price_title": "Average price",
|
||||
"select_provider_price_text_hetzner": "€8 per month for a relatively small server and 50GB of disk storage",
|
||||
"select_provider_price_text_do": "$17 per month for a relatively small server and 50GB of disk storage",
|
||||
"select_provider_payment_title": "Payment methods",
|
||||
"select_provider_payment_text_hetzner": "Credit cards, SWIFT, SEPA, PayPal",
|
||||
"select_provider_payment_text_do": "Credit cards, Google Pay, PayPal",
|
||||
"select_provider_email_notice": "E-mail hosting won't be available for new clients. Nevertheless it will be unlocked as soon as you complete your first payment.",
|
||||
"select_provider_site_button": "Visit site",
|
||||
"connect_to_server_provider": "Now log in ",
|
||||
"connect_to_server_provider_text": "With API token SelfPrivacy will be able to rent a machine and setup your server on it",
|
||||
"how": "How to obtain API token",
|
||||
"provider_bad_key_error": "Provider API key is invalid",
|
||||
"could_not_connect": "Counldn't connect to the provider.",
|
||||
"choose_location_type": "Choose your server location and type:",
|
||||
"back_to_locations": "Go back to available locations!",
|
||||
"no_locations_found": "No available locations found. Make sure your account is accessible.",
|
||||
"choose_location_type": "Where do you want to order your server?",
|
||||
"choose_location_type_text": "Different locations provide different server configurations, prices and connection speed.",
|
||||
"locations_not_found": "Oops!",
|
||||
"locations_not_found_text": "There are no available servers to rent",
|
||||
"back_to_locations": "Select something else",
|
||||
"no_locations_found": "No available locations found, make sure your account is accessible",
|
||||
"choose_server_type": "What type of server do you need?",
|
||||
"choose_server_type_text": "Different resource capabilities support different services. Don't worry, you can expand your server anytime",
|
||||
"choose_server_type_notice": "The important things to look at are the CPU and RAM. The data of your services will be stored on a mounted volume which is easily explandable and gets paid for separately.",
|
||||
"choose_server_type_ram": "{} GB of RAM",
|
||||
"choose_server_type_storage": "{} GB of system storage",
|
||||
"choose_server_type_payment_per_month": "{} per month",
|
||||
"no_server_types_found": "No available server types found. Make sure your account is accessible and try to change your server location.",
|
||||
"cloudflare_bad_key_error": "Cloudflare API key is invalid",
|
||||
"backblaze_bad_key_error": "Backblaze storage information is invalid",
|
||||
"connect_to_dns": "Connect the DNS provider",
|
||||
"select_dns": "Now let's select a DNS provider",
|
||||
"manage_domain_dns": "To manage your domain's DNS",
|
||||
"use_this_domain": "Use this domain?",
|
||||
"use_this_domain_text": "The token you provided gives access to the following domain",
|
||||
"cloudflare_api_token": "CloudFlare API Token",
|
||||
"connect_backblaze_storage": "Connect Backblaze storage",
|
||||
"no_connected_domains": "No connected domains at the moment",
|
||||
|
@ -393,6 +427,8 @@
|
|||
"generation_error": "Couldn't generate a recovery key. {}"
|
||||
},
|
||||
"modals": {
|
||||
"dns_removal_error": "Couldn't remove DNS records.",
|
||||
"server_deletion_error": "Couldn't delete active server.",
|
||||
"server_validators_error": "Couldn't fetch available servers.",
|
||||
"already_exists": "Such server already exists.",
|
||||
"unexpected_error": "Unexpected error during placement from the provider side.",
|
||||
|
@ -443,4 +479,4 @@
|
|||
"length_not_equal": "Length is [], should be {}",
|
||||
"length_longer": "Length is [], should be shorter than or equal to {}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"locale": "ru",
|
||||
"basis": {
|
||||
"providers": "Провайдеры",
|
||||
"select": "Выбрать",
|
||||
"providers_title": "Ваш Дата Центр",
|
||||
"services": "Сервисы",
|
||||
"services_title": "Ваши личные, приватные и независимые сервисы.",
|
||||
|
@ -53,7 +54,8 @@
|
|||
"about_application_page": {
|
||||
"title": "О приложении",
|
||||
"application_version_text": "Версия приложения v.{}",
|
||||
"api_version_text": "Версия API сервера v.{}"
|
||||
"api_version_text": "Версия API сервера v.{}",
|
||||
"privacy_policy": "Политика конфиденциальности"
|
||||
},
|
||||
"application_settings": {
|
||||
"title": "Настройки приложения",
|
||||
|
@ -80,7 +82,13 @@
|
|||
"page1_title": "Цифровая независимость доступна каждому",
|
||||
"page1_text": "Почта, VPN, Мессенджер, социальная сеть и многое другое на Вашем личном сервере, под Вашим полным контролем.",
|
||||
"page2_title": "SelfPrivacy — это не облако, а Ваш личный дата-центр",
|
||||
"page2_text": "SelfPrivacy работает только с вашими сервис-провайдерами: Hetzner, Cloudflare, Backblaze. Если у Вас нет учётных записей, мы поможем их создать."
|
||||
"page2_text": "SelfPrivacy работает только с сервис-провайдерами на ваш выбор. Если у Вас нет учётных записей, мы поможем их создать.",
|
||||
"page2_server_provider_title": "Сервер-провайдер",
|
||||
"page2_server_provider_text": "Сервер-провайдер будет обслуживать ваш сервер в своём дата-центре. SelfPrivacy автоматически подключится к нему и настроит вам сервер.",
|
||||
"page2_dns_provider_title": "DNS-провайдер",
|
||||
"page2_dns_provider_text": "Чтобы быть в интернете, нужен домен. Чтобы домен указывал на ваш сервер, нужен надёжный DNS сервер. Мы предложим вам выбрать один из поддерживаемых DNS серверов автоматически настроим все записи. Хотите настроить их вручную? Так тоже можно.",
|
||||
"page2_backup_provider_title": "Бэкап-провайдер",
|
||||
"page2_backup_provider_text": "Что если с сервером что-то случится? Хакерская атака, отказ в обслуживании или просто случайное удаление данных? Ваши данные будут в сохранности в другом месте, у провайдера хранилища ваших резервных копий. Все они надёжно шифруются, и вы сможете восстановить свой сервер."
|
||||
},
|
||||
"resource_chart": {
|
||||
"month": "Месяц",
|
||||
|
@ -268,20 +276,45 @@
|
|||
"no_ssh_notice": "Для этого пользователя созданы только SSH и Email аккаунты. Единая авторизация для всех сервисов ещё не реализована."
|
||||
},
|
||||
"initializing": {
|
||||
"connect_to_server": "Подключите сервер",
|
||||
"server_provider_description": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:",
|
||||
"dns_provider_description": "Это позволит связать ваш домен с IP адресом:",
|
||||
"connect_to_server": "Начнём с сервера.",
|
||||
"select_provider": "Ниже подборка провайдеров, которых поддерживает SelfPrivacy",
|
||||
"select_provider_notice": "Под 'Небольшим сервером' имеется ввиду сервер с двумя потоками процессора и двумя гигабайтами оперативной памяти.",
|
||||
"select_provider_countries_title": "Доступные страны",
|
||||
"select_provider_countries_text_hetzner": "Германия, Финляндия, США",
|
||||
"select_provider_countries_text_do": "США, Нидерланды, Сингапур, Великобритания, Германия, Канада, Индия, Австралия",
|
||||
"select_provider_price_title": "Средняя цена",
|
||||
"select_provider_price_text_hetzner": "€8 в месяц за небольшой сервер и 50GB места на диске",
|
||||
"select_provider_price_text_do": "$17 в месяц за небольшой сервер и 50GB места на диске",
|
||||
"select_provider_payment_title": "Методы оплаты",
|
||||
"select_provider_payment_text_hetzner": "Банковские карты, SWIFT, SEPA, PayPal",
|
||||
"select_provider_payment_text_do": "Банковские карты, Google Pay, PayPal",
|
||||
"select_provider_email_notice": "Хостинг электронной почты недоступен для новых клиентов. Разблокировать можно будет после первой оплаты.",
|
||||
"select_provider_site_button": "Посетить сайт",
|
||||
"connect_to_server_provider": "Авторизоваться в ",
|
||||
"connect_to_server_provider_text": "С помощью API токена приложение SelfPrivacy сможет от вашего имени заказать и настроить сервер",
|
||||
"how": "Как получить API Token",
|
||||
"provider_bad_key_error": "API ключ провайдера неверен",
|
||||
"could_not_connect": "Не удалось соединиться с провайдером.",
|
||||
"choose_location_type": "Выберите локацию и тип вашего сервера:",
|
||||
"back_to_locations": "Назад к доступным локациям!",
|
||||
"choose_location_type": "Где заказать сервер?",
|
||||
"choose_location_type_text": "От выбора локации будут зависеть доступные конфигурации, цены и скорость вашего соединения с сервером.",
|
||||
"locations_not_found": "Упс!",
|
||||
"locations_not_found_text": "В этом месте не оказалось доступных серверов для аренды",
|
||||
"back_to_locations": "Выберем другой",
|
||||
"no_locations_found": "Не найдено локаций. Убедитесь, что ваш аккаунт доступен.",
|
||||
"no_server_types_found": "Не удалось получить список серверов. Убедитесь, что ваш аккаунт доступен и попытайтесь сменить локацию сервера.",
|
||||
"choose_server_type": "Какой выбрать тип сервера?",
|
||||
"choose_server_type_text": "От ресурсов сервера зависит, какие сервисы смогут запуститься. Расширить сервер можно будет в любое время",
|
||||
"choose_server_type_notice": "Главное, на что стоит обратить внимание — количество потоков процессора и объём оперативной памяти. Данные сервисов будут размещены на отдельном диске, который оплачивается отдельно и легко расширяем!",
|
||||
"choose_server_type_ram": "{} GB у RAM",
|
||||
"choose_server_type_storage": "{} GB системного хранилища",
|
||||
"choose_server_type_payment_per_month": "{} в месяц",
|
||||
"no_server_types_found": "Не найдено доступных типов сервера! Пожалуйста, убедитесь, что у вас есть доступ к провайдеру сервера...",
|
||||
"cloudflare_bad_key_error": "Cloudflare API ключ неверен",
|
||||
"backblaze_bad_key_error": "Информация о Backblaze хранилище неверна",
|
||||
"connect_to_dns": "Подключите DNS провайдер",
|
||||
"manage_domain_dns": "Для управления DNS вашего домена",
|
||||
"use_this_domain": "Используем этот домен?",
|
||||
"use_this_domain_text": "Указанный вами токен даёт контроль над этим доменом",
|
||||
"cloudflare_api_token": "CloudFlare API ключ",
|
||||
"connect_backblaze_storage": "Подключите облачное хранилище Backblaze",
|
||||
"no_connected_domains": "На данный момент подлюченных доменов нет",
|
||||
|
@ -393,6 +426,8 @@
|
|||
"generation_error": "Не удалось сгенерировать ключ. {}"
|
||||
},
|
||||
"modals": {
|
||||
"dns_removal_error": "Невозможно удалить DNS записи.",
|
||||
"server_deletion_error": "Невозможно удалить сервер.",
|
||||
"server_validators_error": "Не удалось получить список серверов.",
|
||||
"already_exists": "Такой сервер уже существует.",
|
||||
"unexpected_error": "Непредвиденная ошибка со стороны провайдера.",
|
||||
|
@ -443,4 +478,4 @@
|
|||
"length_not_equal": "Длина строки [], должно быть равно {}",
|
||||
"length_longer": "Длина строки [], должно быть меньше либо равно {}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1
ci.py
1
ci.py
|
@ -149,6 +149,7 @@ def package_linux_archive():
|
|||
def deploy_gitea_release():
|
||||
gitea_upload_attachment(f"{HOST_MOUNTED_VOLUME}/standalone_{APP_NAME}-{APP_SEMVER}.apk")
|
||||
gitea_upload_attachment(f"{HOST_MOUNTED_VOLUME}/standalone_{APP_NAME}-{APP_SEMVER}.apk.idsig")
|
||||
gitea_upload_attachment(f"{HOST_MOUNTED_VOLUME}/{APP_NAME}-{APP_SEMVER}.aab")
|
||||
gitea_upload_attachment(f"{HOST_MOUNTED_VOLUME}/SelfPrivacy-{APP_SEMVER}-x86_64.AppImage")
|
||||
gitea_upload_attachment(f"{HOST_MOUNTED_VOLUME}/SelfPrivacy-{APP_SEMVER}-x86_64.AppImage.zsync")
|
||||
gitea_upload_attachment(f"{HOST_MOUNTED_VOLUME}/{APP_NAME}-{APP_SEMVER}.flatpak")
|
||||
|
|
33
fastlane/metadata/android/en-US/changelogs/0.8.0.txt
Normal file
33
fastlane/metadata/android/en-US/changelogs/0.8.0.txt
Normal file
|
@ -0,0 +1,33 @@
|
|||
Server setup:
|
||||
- Added support for Digital Ocean as server provider
|
||||
- You can now choose server region
|
||||
- You can now choose server tier
|
||||
- Server installation UI has been refreshed
|
||||
- Fields now have more specific error messages
|
||||
|
||||
Common UI:
|
||||
- New app bar used in most of the screens
|
||||
|
||||
Services:
|
||||
- Services are now sorted by their status
|
||||
|
||||
Server settings:
|
||||
- Timezone search screen now has a search bar
|
||||
- Fixed job creation when switching the setting multiple times
|
||||
- Server destruction now works
|
||||
|
||||
Jobs:
|
||||
- Jobs panel now should take slightly less space
|
||||
|
||||
Auth:
|
||||
- Recovery key page can now be reloaded by dragging down
|
||||
|
||||
Logging:
|
||||
- Log console now has a limit of 500 lines
|
||||
- GraphQL API requests are now logged in the console
|
||||
- Networks errors are better handled
|
||||
|
||||
For developers:
|
||||
- App now only uses GraphQL API to communicate with the server. All REST API calls have been removed.
|
||||
- Server can now be deployed with staging ACME certificates
|
||||
- Language assets have been reorganized
|
2895
lib/illustrations/stray_deer.dart
Normal file
2895
lib/illustrations/stray_deer.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -90,7 +90,8 @@ class BackblazeApi extends ApiMap {
|
|||
),
|
||||
);
|
||||
if (response.statusCode == HttpStatus.ok) {
|
||||
isTokenValid = response.data['allowed']['capabilities'].contains('listBuckets');
|
||||
isTokenValid =
|
||||
response.data['allowed']['capabilities'].contains('listBuckets');
|
||||
} else if (response.statusCode == HttpStatus.unauthorized) {
|
||||
isTokenValid = false;
|
||||
} else {
|
||||
|
|
|
@ -18,6 +18,7 @@ import 'package:selfprivacy/logic/models/server_metadata.dart';
|
|||
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||
import 'package:selfprivacy/utils/extensions/string_extensions.dart';
|
||||
import 'package:selfprivacy/utils/network_utils.dart';
|
||||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
|
||||
class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
|
||||
|
@ -325,23 +326,6 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
|
|||
return success;
|
||||
}
|
||||
|
||||
static String getHostnameFromDomain(final String domain) {
|
||||
// Replace all non-alphanumeric characters with an underscore
|
||||
String hostname =
|
||||
domain.split('.')[0].replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
|
||||
if (hostname.endsWith('-')) {
|
||||
hostname = hostname.substring(0, hostname.length - 1);
|
||||
}
|
||||
if (hostname.startsWith('-')) {
|
||||
hostname = hostname.substring(1);
|
||||
}
|
||||
if (hostname.isEmpty) {
|
||||
hostname = 'selfprivacy-server';
|
||||
}
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<APIGenericResult<ServerHostingDetails?>> createServer({
|
||||
required final String dnsApiToken,
|
||||
|
@ -431,17 +415,42 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteServer({
|
||||
Future<APIGenericResult<bool>> deleteServer({
|
||||
required final String domainName,
|
||||
}) async {
|
||||
final Dio client = await getClient();
|
||||
|
||||
final ServerBasicInfo serverToRemove = (await getServers()).firstWhere(
|
||||
(final el) => el.name == domainName,
|
||||
);
|
||||
final ServerVolume volumeToRemove = (await getVolumes()).firstWhere(
|
||||
(final el) => el.serverId == serverToRemove.id,
|
||||
);
|
||||
final String hostname = getHostnameFromDomain(domainName);
|
||||
final servers = await getServers();
|
||||
final ServerBasicInfo serverToRemove;
|
||||
try {
|
||||
serverToRemove = servers.firstWhere(
|
||||
(final el) => el.name == hostname,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return APIGenericResult(
|
||||
data: false,
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
final volumes = await getVolumes();
|
||||
final ServerVolume volumeToRemove;
|
||||
try {
|
||||
volumeToRemove = volumes.firstWhere(
|
||||
(final el) => el.serverId == serverToRemove.id,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return APIGenericResult(
|
||||
data: false,
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
final List<Future> laterFutures = <Future>[];
|
||||
|
||||
await detachVolume(volumeToRemove);
|
||||
|
@ -449,13 +458,23 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
|
|||
|
||||
try {
|
||||
laterFutures.add(deleteVolume(volumeToRemove));
|
||||
laterFutures.add(client.delete('/droplets/$serverToRemove.id'));
|
||||
laterFutures.add(client.delete('/droplets/${serverToRemove.id}'));
|
||||
await Future.wait(laterFutures);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return APIGenericResult(
|
||||
success: false,
|
||||
data: false,
|
||||
message: e.toString(),
|
||||
);
|
||||
} finally {
|
||||
close(client);
|
||||
}
|
||||
|
||||
return APIGenericResult(
|
||||
success: true,
|
||||
data: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -19,6 +19,7 @@ import 'package:selfprivacy/logic/models/server_metadata.dart';
|
|||
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||
import 'package:selfprivacy/utils/extensions/string_extensions.dart';
|
||||
import 'package:selfprivacy/utils/network_utils.dart';
|
||||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
|
||||
class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||
|
@ -461,49 +462,47 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
|||
);
|
||||
}
|
||||
|
||||
static String getHostnameFromDomain(final String domain) {
|
||||
// Replace all non-alphanumeric characters with an underscore
|
||||
String hostname =
|
||||
domain.split('.')[0].replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
|
||||
if (hostname.endsWith('-')) {
|
||||
hostname = hostname.substring(0, hostname.length - 1);
|
||||
}
|
||||
if (hostname.startsWith('-')) {
|
||||
hostname = hostname.substring(1);
|
||||
}
|
||||
if (hostname.isEmpty) {
|
||||
hostname = 'selfprivacy-server';
|
||||
}
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteServer({
|
||||
Future<APIGenericResult<bool>> deleteServer({
|
||||
required final String domainName,
|
||||
}) async {
|
||||
final Dio client = await getClient();
|
||||
try {
|
||||
final String hostname = getHostnameFromDomain(domainName);
|
||||
|
||||
final String hostname = getHostnameFromDomain(domainName);
|
||||
final Response serversReponse = await client.get('/servers');
|
||||
final List servers = serversReponse.data['servers'];
|
||||
final Map server =
|
||||
servers.firstWhere((final el) => el['name'] == hostname);
|
||||
final List volumes = server['volumes'];
|
||||
final List<Future> laterFutures = <Future>[];
|
||||
|
||||
final Response serversReponse = await client.get('/servers');
|
||||
final List servers = serversReponse.data['servers'];
|
||||
final Map server = servers.firstWhere((final el) => el['name'] == hostname);
|
||||
final List volumes = server['volumes'];
|
||||
final List<Future> laterFutures = <Future>[];
|
||||
for (final volumeId in volumes) {
|
||||
await client.post('/volumes/$volumeId/actions/detach');
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
|
||||
for (final volumeId in volumes) {
|
||||
await client.post('/volumes/$volumeId/actions/detach');
|
||||
for (final volumeId in volumes) {
|
||||
laterFutures.add(client.delete('/volumes/$volumeId'));
|
||||
}
|
||||
laterFutures.add(client.delete('/servers/${server['id']}'));
|
||||
|
||||
await Future.wait(laterFutures);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return APIGenericResult(
|
||||
success: false,
|
||||
data: false,
|
||||
message: e.toString(),
|
||||
);
|
||||
} finally {
|
||||
close(client);
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
|
||||
for (final volumeId in volumes) {
|
||||
laterFutures.add(client.delete('/volumes/$volumeId'));
|
||||
}
|
||||
laterFutures.add(client.delete('/servers/${server['id']}'));
|
||||
|
||||
await Future.wait(laterFutures);
|
||||
close(client);
|
||||
return APIGenericResult(
|
||||
success: true,
|
||||
data: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -31,7 +31,9 @@ abstract class ServerProviderApi extends ApiMap {
|
|||
Future<ServerHostingDetails> restart();
|
||||
Future<ServerHostingDetails> powerOn();
|
||||
|
||||
Future<void> deleteServer({required final String domainName});
|
||||
Future<APIGenericResult<bool>> deleteServer({
|
||||
required final String domainName,
|
||||
});
|
||||
Future<APIGenericResult<ServerHostingDetails?>> createServer({
|
||||
required final String dnsApiToken,
|
||||
required final User rootUser,
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:material_color_utilities/material_color_utilities.dart'
|
||||
as color_utils;
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
|
@ -20,7 +25,7 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
|
|||
|
||||
Box box = Hive.box(BNames.appSettingsBox);
|
||||
|
||||
void load() {
|
||||
void load() async {
|
||||
final bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
|
||||
final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing);
|
||||
emit(
|
||||
|
@ -29,6 +34,14 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
|
|||
isOnboardingShowing: isOnboardingShowing,
|
||||
),
|
||||
);
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final color_utils.CorePalette? colorPalette =
|
||||
await AppThemeFactory.getCorePalette();
|
||||
emit(
|
||||
state.copyWith(
|
||||
corePalette: colorPalette,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void updateDarkMode({required final bool isDarkModeOn}) {
|
||||
|
|
|
@ -4,20 +4,27 @@ class AppSettingsState extends Equatable {
|
|||
const AppSettingsState({
|
||||
required this.isDarkModeOn,
|
||||
required this.isOnboardingShowing,
|
||||
this.corePalette,
|
||||
});
|
||||
|
||||
final bool isDarkModeOn;
|
||||
final bool isOnboardingShowing;
|
||||
final color_utils.CorePalette? corePalette;
|
||||
|
||||
AppSettingsState copyWith({
|
||||
final bool? isDarkModeOn,
|
||||
final bool? isOnboardingShowing,
|
||||
final color_utils.CorePalette? corePalette,
|
||||
}) =>
|
||||
AppSettingsState(
|
||||
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
|
||||
isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing,
|
||||
corePalette: corePalette ?? this.corePalette,
|
||||
);
|
||||
|
||||
color_utils.CorePalette get corePaletteOrDefault =>
|
||||
corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value);
|
||||
|
||||
@override
|
||||
List<Object> get props => [isDarkModeOn, isOnboardingShowing];
|
||||
List<dynamic> get props => [isDarkModeOn, isOnboardingShowing, corePalette];
|
||||
}
|
||||
|
|
|
@ -755,7 +755,11 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
|||
closeTimer();
|
||||
|
||||
if (state.serverDetails != null) {
|
||||
await repository.deleteServer(state.serverDomain!);
|
||||
final bool deletionResult =
|
||||
await repository.deleteServer(state.serverDomain!);
|
||||
if (!deletionResult) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await repository.deleteServerRelatedRecords();
|
||||
emit(
|
||||
|
|
|
@ -763,13 +763,26 @@ class ServerInstallationRepository {
|
|||
await box.put(BNames.hasFinalChecked, value);
|
||||
}
|
||||
|
||||
Future<void> deleteServer(final ServerDomain serverDomain) async {
|
||||
await ApiController.currentServerProviderApiFactory!
|
||||
Future<bool> deleteServer(final ServerDomain serverDomain) async {
|
||||
final APIGenericResult<bool> deletionResult = await ApiController
|
||||
.currentServerProviderApiFactory!
|
||||
.getServerProvider()
|
||||
.deleteServer(
|
||||
domainName: serverDomain.domainName,
|
||||
);
|
||||
|
||||
if (!deletionResult.success) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('modals.server_validators_error'.tr());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!deletionResult.data) {
|
||||
getIt<NavigationService>()
|
||||
.showSnackBar('modals.server_deletion_error'.tr());
|
||||
return false;
|
||||
}
|
||||
|
||||
await box.put(BNames.hasFinalChecked, false);
|
||||
await box.put(BNames.isServerStarted, false);
|
||||
await box.put(BNames.isServerResetedFirstTime, false);
|
||||
|
@ -777,9 +790,15 @@ class ServerInstallationRepository {
|
|||
await box.put(BNames.isLoading, false);
|
||||
await box.put(BNames.serverDetails, null);
|
||||
|
||||
await ApiController.currentDnsProviderApiFactory!
|
||||
final APIGenericResult<void> removalResult = await ApiController
|
||||
.currentDnsProviderApiFactory!
|
||||
.getDnsProvider()
|
||||
.removeSimilarRecords(domain: serverDomain);
|
||||
|
||||
if (!removalResult.success) {
|
||||
getIt<NavigationService>().showSnackBar('modals.dns_removal_error'.tr());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> deleteServerRelatedRecords() async {
|
||||
|
|
|
@ -9,5 +9,9 @@ class ConsoleModel extends ChangeNotifier {
|
|||
void addMessage(final Message message) {
|
||||
messages.add(message);
|
||||
notifyListeners();
|
||||
// Make sure we don't have too many messages
|
||||
if (messages.length > 500) {
|
||||
messages.removeAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,4 +95,15 @@ enum ServerProvider {
|
|||
return unknown;
|
||||
}
|
||||
}
|
||||
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case ServerProvider.hetzner:
|
||||
return 'Hetzner Cloud';
|
||||
case ServerProvider.digitalOcean:
|
||||
return 'Digital Ocean';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,13 +105,13 @@ class ServiceStorageUsage {
|
|||
}
|
||||
|
||||
enum ServiceStatus {
|
||||
failed,
|
||||
reloading,
|
||||
activating,
|
||||
active,
|
||||
deactivating,
|
||||
failed,
|
||||
inactive,
|
||||
off,
|
||||
reloading;
|
||||
off;
|
||||
|
||||
factory ServiceStatus.fromGraphQL(final Enum$ServiceStatusEnum graphQL) {
|
||||
switch (graphQL) {
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:system_theme/system_theme.dart';
|
||||
import 'package:gtk_theme_fl/gtk_theme_fl.dart';
|
||||
import 'package:material_color_utilities/palettes/core_palette.dart';
|
||||
|
||||
abstract class AppThemeFactory {
|
||||
AppThemeFactory._();
|
||||
|
@ -22,40 +19,17 @@ abstract class AppThemeFactory {
|
|||
required final Color fallbackColor,
|
||||
final bool isDark = false,
|
||||
}) async {
|
||||
ColorScheme? gtkColorsScheme;
|
||||
final Brightness brightness = isDark ? Brightness.dark : Brightness.light;
|
||||
|
||||
final ColorScheme? dynamicColorsScheme =
|
||||
await _getDynamicColors(brightness);
|
||||
|
||||
if (Platform.isLinux) {
|
||||
final GtkThemeData themeData = await GtkThemeData.initialize();
|
||||
final bool isGtkDark =
|
||||
Color(themeData.theme_bg_color).computeLuminance() < 0.5;
|
||||
final bool isInverseNeeded = isGtkDark != isDark;
|
||||
gtkColorsScheme = ColorScheme.fromSeed(
|
||||
seedColor: Color(themeData.theme_selected_bg_color),
|
||||
brightness: brightness,
|
||||
background: isInverseNeeded ? null : Color(themeData.theme_bg_color),
|
||||
surface: isInverseNeeded ? null : Color(themeData.theme_base_color),
|
||||
);
|
||||
}
|
||||
|
||||
final SystemAccentColor accentColor = SystemAccentColor(fallbackColor);
|
||||
|
||||
try {
|
||||
await accentColor.load();
|
||||
} on MissingPluginException catch (e) {
|
||||
print('_createAppTheme: ${e.message}');
|
||||
}
|
||||
|
||||
final ColorScheme fallbackColorScheme = ColorScheme.fromSeed(
|
||||
seedColor: accentColor.accent,
|
||||
seedColor: fallbackColor,
|
||||
brightness: brightness,
|
||||
);
|
||||
|
||||
final ColorScheme colorScheme =
|
||||
dynamicColorsScheme ?? gtkColorsScheme ?? fallbackColorScheme;
|
||||
final ColorScheme colorScheme = dynamicColorsScheme ?? fallbackColorScheme;
|
||||
|
||||
final Typography appTypography = Typography.material2021();
|
||||
|
||||
|
@ -80,4 +54,12 @@ abstract class AppThemeFactory {
|
|||
return Future.value(null);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<CorePalette?> getCorePalette() async {
|
||||
try {
|
||||
return await DynamicColorPlugin.getCorePalette();
|
||||
} on PlatformException {
|
||||
return Future.value(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class BrandBottomSheet extends StatelessWidget {
|
|||
Widget build(final BuildContext context) {
|
||||
final double mainHeight = MediaQuery.of(context).size.height -
|
||||
MediaQuery.of(context).padding.top -
|
||||
100;
|
||||
300;
|
||||
late Widget innerWidget;
|
||||
if (isExpended) {
|
||||
innerWidget = Scaffold(
|
||||
|
@ -29,31 +29,28 @@ class BrandBottomSheet extends StatelessWidget {
|
|||
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,
|
||||
),
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
height: 4,
|
||||
width: 30,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
color: BrandColors.gray4,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: mainHeight),
|
||||
child: innerWidget,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: mainHeight),
|
||||
child: innerWidget,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/text_themes.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
|
||||
class ProgressBar extends StatefulWidget {
|
||||
const ProgressBar({
|
||||
|
@ -63,13 +62,6 @@ class _ProgressBarState extends State<ProgressBar> {
|
|||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BrandText.h2('Progress'),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: even,
|
||||
),
|
||||
const SizedBox(height: 7),
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
decoration: BoxDecoration(
|
||||
|
@ -98,11 +90,6 @@ class _ProgressBarState extends State<ProgressBar> {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: odd,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
|||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AboutApplicationPage extends StatelessWidget {
|
||||
const AboutApplicationPage({super.key});
|
||||
|
@ -37,6 +38,26 @@ class AboutApplicationPage extends StatelessWidget {
|
|||
.tr(args: [snapshot.data.toString()]),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Button to call showAboutDialog
|
||||
TextButton(
|
||||
onPressed: () => showAboutDialog(
|
||||
context: context,
|
||||
applicationName: 'SelfPrivacy',
|
||||
applicationLegalese: '© 2022 SelfPrivacy',
|
||||
// Link to privacy policy
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => launchUrl(
|
||||
Uri.parse('https://selfprivacy.ru/privacy-policy'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: Text('about_application_page.privacy_policy'.tr()),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Text('Show about dialog'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
|
@ -49,11 +48,16 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
BrandText.h2(
|
||||
Text(
|
||||
'onboarding.page1_title'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
BrandText.body2('onboarding.page1_text'.tr()),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'onboarding.page1_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Flexible(
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
|
@ -86,34 +90,49 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
maxHeight: MediaQuery.of(context).size.height,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
BrandText.h2('onboarding.page2_title'.tr()),
|
||||
const SizedBox(height: 20),
|
||||
BrandText.body2('onboarding.page2_text'.tr()),
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
child: Image.asset(
|
||||
_fileName(
|
||||
context: context,
|
||||
path: 'assets/images/onboarding',
|
||||
fileExtention: 'png',
|
||||
fileName: 'logos_line',
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'onboarding.page2_title'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
Flexible(
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
_fileName(
|
||||
context: context,
|
||||
path: 'assets/images/onboarding',
|
||||
fileExtention: 'png',
|
||||
fileName: 'onboarding2',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'onboarding.page2_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'onboarding.page2_server_provider_title'.tr(),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'onboarding.page2_server_provider_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'onboarding.page2_dns_provider_title'.tr(),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'onboarding.page2_dns_provider_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'onboarding.page2_backup_provider_title'.tr(),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'onboarding.page2_backup_provider_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.rised(
|
||||
onPressed: () {
|
||||
context.read<AppSettingsCubit>().turnOffOnboarding();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
|
@ -82,7 +84,10 @@ class CpuChart extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
minY: 0,
|
||||
maxY: 100,
|
||||
// Maximal value of data by 100 step
|
||||
maxY:
|
||||
((data.map((final e) => e.value).reduce(max) - 1) / 100).ceil() *
|
||||
100.0,
|
||||
minX: 0,
|
||||
titlesData: FlTitlesData(
|
||||
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:selfprivacy/logic/models/service.dart';
|
|||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart';
|
||||
import 'package:selfprivacy/utils/network_utils.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
|
@ -59,7 +60,7 @@ class _ServicePageState extends State<ServicePage> {
|
|||
if (service.url != null)
|
||||
ListTile(
|
||||
iconColor: Theme.of(context).colorScheme.onBackground,
|
||||
onTap: () => _launchURL(service.url),
|
||||
onTap: () => launchURL(service.url),
|
||||
leading: const Icon(Icons.open_in_browser),
|
||||
title: Text(
|
||||
'service_page.open_in_browser'.tr(),
|
||||
|
@ -232,15 +233,3 @@ class ServiceStatusCard extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _launchURL(final url) async {
|
||||
try {
|
||||
final Uri uri = Uri.parse(url);
|
||||
await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,22 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/job.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.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_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';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/ui/pages/services/service_page.dart';
|
||||
import 'package:selfprivacy/utils/network_utils.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
import 'package:selfprivacy/utils/ui_helpers.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
const switchableServices = [
|
||||
'bitwarden',
|
||||
'nextcloud',
|
||||
'pleroma',
|
||||
'gitea',
|
||||
'ocserv',
|
||||
];
|
||||
|
||||
class ServicesPage extends StatefulWidget {
|
||||
const ServicesPage({super.key});
|
||||
|
||||
|
@ -36,24 +24,16 @@ class ServicesPage extends StatefulWidget {
|
|||
State<ServicesPage> createState() => _ServicesPageState();
|
||||
}
|
||||
|
||||
void _launchURL(final url) async {
|
||||
try {
|
||||
final Uri uri = Uri.parse(url);
|
||||
await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
class _ServicesPageState extends State<ServicesPage> {
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
|
||||
final services = [...context.watch<ServicesCubit>().state.services];
|
||||
services
|
||||
.sort((final a, final b) => a.status.index.compareTo(b.status.index));
|
||||
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
|
@ -71,10 +51,7 @@ class _ServicesPageState extends State<ServicesPage> {
|
|||
BrandText.body1('basis.services_title'.tr()),
|
||||
const SizedBox(height: 24),
|
||||
if (!isReady) ...[const NotReadyCard(), const SizedBox(height: 24)],
|
||||
...context
|
||||
.read<ServicesCubit>()
|
||||
.state
|
||||
.services
|
||||
...services
|
||||
.map(
|
||||
(final service) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
|
@ -100,24 +77,28 @@ class _Card extends StatelessWidget {
|
|||
final isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
|
||||
final serviceState = context.watch<ServicesCubit>().state;
|
||||
final jobsCubit = context.watch<JobsCubit>();
|
||||
final jobState = jobsCubit.state;
|
||||
|
||||
final switchableService = switchableServices.contains(service.id);
|
||||
final hasSwitchJob = switchableService &&
|
||||
jobState is JobsStateWithJobs &&
|
||||
jobState.clientJobList.any(
|
||||
(final el) => el is ServiceToggleJob && el.id == service.id,
|
||||
);
|
||||
|
||||
final isSwitchOn = isReady &&
|
||||
(!switchableServices.contains(service.id) ||
|
||||
serviceState.isEnableByType(service));
|
||||
|
||||
final config = context.watch<ServerInstallationCubit>().state;
|
||||
final domainName = UiHelpers.getDomainName(config);
|
||||
|
||||
StateType getStatus(final ServiceStatus status) {
|
||||
switch (status) {
|
||||
case ServiceStatus.active:
|
||||
return StateType.stable;
|
||||
case ServiceStatus.activating:
|
||||
return StateType.stable;
|
||||
case ServiceStatus.deactivating:
|
||||
return StateType.uninitialized;
|
||||
case ServiceStatus.inactive:
|
||||
return StateType.uninitialized;
|
||||
case ServiceStatus.failed:
|
||||
return StateType.error;
|
||||
case ServiceStatus.off:
|
||||
return StateType.uninitialized;
|
||||
case ServiceStatus.reloading:
|
||||
return StateType.stable;
|
||||
}
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: isReady
|
||||
? () => Navigator.of(context)
|
||||
|
@ -130,8 +111,7 @@ class _Card extends StatelessWidget {
|
|||
Row(
|
||||
children: [
|
||||
IconStatusMask(
|
||||
status:
|
||||
isSwitchOn ? StateType.stable : StateType.uninitialized,
|
||||
status: getStatus(service.status),
|
||||
icon: SvgPicture.string(
|
||||
service.svgIcon,
|
||||
width: 30.0,
|
||||
|
@ -139,33 +119,6 @@ class _Card extends StatelessWidget {
|
|||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
if (isReady && switchableService) ...[
|
||||
const Spacer(),
|
||||
Builder(
|
||||
builder: (final context) {
|
||||
late bool isActive;
|
||||
if (hasSwitchJob) {
|
||||
isActive = (jobState.clientJobList.firstWhere(
|
||||
(final el) =>
|
||||
el is ServiceToggleJob && el.id == service.id,
|
||||
) as ServiceToggleJob)
|
||||
.needToTurnOn;
|
||||
} else {
|
||||
isActive = serviceState.isEnableByType(service);
|
||||
}
|
||||
|
||||
return BrandSwitch(
|
||||
value: isActive,
|
||||
onChanged: (final value) => jobsCubit.addJob(
|
||||
ServiceToggleJob(
|
||||
service: service,
|
||||
needToTurnOn: value,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
ClipRect(
|
||||
|
@ -181,7 +134,7 @@ class _Card extends StatelessWidget {
|
|||
Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => _launchURL(
|
||||
onTap: () => launchURL(
|
||||
'https://${service.url}',
|
||||
),
|
||||
child: Text(
|
||||
|
@ -215,22 +168,6 @@ class _Card extends StatelessWidget {
|
|||
const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
if (hasSwitchJob)
|
||||
Positioned(
|
||||
bottom: 24,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 3,
|
||||
sigmaY: 2,
|
||||
),
|
||||
child: BrandText.h2(
|
||||
'jobs.run_jobs'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/filled_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||
import 'package:selfprivacy/utils/network_utils.dart';
|
||||
|
||||
class DnsProviderPicker extends StatefulWidget {
|
||||
const DnsProviderPicker({
|
||||
|
@ -60,7 +63,7 @@ class _DnsProviderPickerState extends State<DnsProviderPicker> {
|
|||
providerCubit: widget.formCubit,
|
||||
providerInfo: ProviderPageInfo(
|
||||
providerType: DnsProvider.digitalOcean,
|
||||
pathToHow: 'how_cloudflare',
|
||||
pathToHow: 'how_digital_ocean_dns',
|
||||
image: Image.asset(
|
||||
'assets/images/logos/digital_ocean.png',
|
||||
width: 150,
|
||||
|
@ -155,51 +158,175 @@ class ProviderSelectionPage extends StatelessWidget {
|
|||
final ServerInstallationCubit serverInstallationCubit;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Column(
|
||||
children: [
|
||||
Text(
|
||||
'initializing.select_provider'.tr(),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'initializing.dns_provider_description'.tr(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 320,
|
||||
Widget build(final BuildContext context) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.connect_to_server'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
serverInstallationCubit
|
||||
.setDnsProviderType(DnsProvider.cloudflare);
|
||||
callback(DnsProvider.cloudflare);
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/images/logos/cloudflare.png',
|
||||
width: 150,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
serverInstallationCubit
|
||||
.setDnsProviderType(DnsProvider.digitalOcean);
|
||||
callback(DnsProvider.digitalOcean);
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/images/logos/digital_ocean.png',
|
||||
width: 150,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'initializing.select_provider'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
OutlinedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
color: const Color(0xFFD50C2D),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
'assets/images/logos/hetzner.svg',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Hetzner Cloud',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_countries_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_countries_text_hetzner'
|
||||
.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_price_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_price_text_hetzner'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_payment_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_payment_text_hetzner'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_email_notice'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
title: 'basis.select'.tr(),
|
||||
onPressed: () {
|
||||
serverInstallationCubit
|
||||
.setDnsProviderType(DnsProvider.cloudflare);
|
||||
callback(DnsProvider.cloudflare);
|
||||
},
|
||||
),
|
||||
// Outlined button that will open website
|
||||
BrandOutlinedButton(
|
||||
onPressed: () =>
|
||||
launchURL('https://cloud.digitalocean.com/'),
|
||||
title: 'initializing.select_provider_site_button'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
OutlinedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
color: const Color(0xFF0080FF),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
'assets/images/logos/digital_ocean.svg',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Digital Ocean',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_countries_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_countries_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_price_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_price_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_payment_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_payment_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
title: 'basis.select'.tr(),
|
||||
onPressed: () {
|
||||
serverInstallationCubit
|
||||
.setDnsProviderType(DnsProvider.digitalOcean);
|
||||
callback(DnsProvider.digitalOcean);
|
||||
},
|
||||
),
|
||||
// Outlined button that will open website
|
||||
BrandOutlinedButton(
|
||||
onPressed: () =>
|
||||
launchURL('https://www.digitalocean.com'),
|
||||
title: 'initializing.select_provider_site_button'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_setup_cu
|
|||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_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_cards/brand_cards.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
|
||||
|
@ -57,88 +56,92 @@ class InitializingPage extends StatelessWidget {
|
|||
.pushReplacement(materialRoute(const RootPage()));
|
||||
}
|
||||
},
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: paddingH15V0.copyWith(top: 10, bottom: 10),
|
||||
child: cubit.state is ServerInstallationFinished
|
||||
? const SizedBox(
|
||||
height: 80,
|
||||
)
|
||||
: ProgressBar(
|
||||
steps: const [
|
||||
'Hosting',
|
||||
'Server Type',
|
||||
'CloudFlare',
|
||||
'Backblaze',
|
||||
'Domain',
|
||||
'User',
|
||||
'Server',
|
||||
'Installation',
|
||||
],
|
||||
activeIndex: cubit.state.porgressBar,
|
||||
),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
actions: [
|
||||
if (cubit.state is ServerInstallationFinished)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.check),
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.pushReplacement(materialRoute(const RootPage()));
|
||||
},
|
||||
)
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: ProgressBar(
|
||||
steps: const [
|
||||
'Hosting',
|
||||
'Server Type',
|
||||
'CloudFlare',
|
||||
'Backblaze',
|
||||
'Domain',
|
||||
'User',
|
||||
'Server',
|
||||
'Installation',
|
||||
],
|
||||
activeIndex: cubit.state.porgressBar,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16.0, 0, 16.0, 0.0),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: actualInitializingPage,
|
||||
),
|
||||
if (cubit.state.porgressBar ==
|
||||
ServerSetupProgress.serverProviderFilled.index)
|
||||
BrandText.h2(
|
||||
'initializing.choose_location_type'.tr(),
|
||||
),
|
||||
_addCard(
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: actualInitializingPage,
|
||||
),
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: MediaQuery.of(context).size.height -
|
||||
MediaQuery.of(context).padding.top -
|
||||
MediaQuery.of(context).padding.bottom -
|
||||
566,
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: MediaQuery.of(context).size.height -
|
||||
MediaQuery.of(context).padding.top -
|
||||
MediaQuery.of(context).padding.bottom -
|
||||
566,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: BrandButton.text(
|
||||
title: cubit.state is ServerInstallationFinished
|
||||
? 'basis.close'.tr()
|
||||
: 'basis.later'.tr(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(const RootPage()),
|
||||
(final predicate) => false,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (cubit.state is ServerInstallationEmpty ||
|
||||
cubit.state is ServerInstallationNotFinished)
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: BrandButton.text(
|
||||
title: cubit.state is ServerInstallationFinished
|
||||
? 'basis.close'.tr()
|
||||
: 'basis.later'.tr(),
|
||||
title: 'basis.connect_to_existing'.tr(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(const RootPage()),
|
||||
(final predicate) => false,
|
||||
Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const RecoveryRouting(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (cubit.state is ServerInstallationFinished)
|
||||
Container()
|
||||
else
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: BrandButton.text(
|
||||
title: 'basis.connect_to_existing'.tr(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const RecoveryRouting(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -210,14 +213,11 @@ class InitializingPage extends StatelessWidget {
|
|||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/logos/backblaze.png',
|
||||
height: 50,
|
||||
Text(
|
||||
'${'initializing.connect_to_server_provider'.tr()}Backblaze',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BrandText.h2('initializing.connect_backblaze_storage'.tr()),
|
||||
const SizedBox(height: 10),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 32),
|
||||
CubitFormTextField(
|
||||
formFieldCubit: context.read<BackblazeFormCubit>().keyId,
|
||||
textAlign: TextAlign.center,
|
||||
|
@ -226,7 +226,7 @@ class InitializingPage extends StatelessWidget {
|
|||
hintText: 'KeyID',
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 16),
|
||||
CubitFormTextField(
|
||||
formFieldCubit:
|
||||
context.read<BackblazeFormCubit>().applicationKey,
|
||||
|
@ -236,7 +236,7 @@ class InitializingPage extends StatelessWidget {
|
|||
hintText: 'Master Application Key',
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 32),
|
||||
BrandButton.rised(
|
||||
onPressed: formCubitState.isSubmitting
|
||||
? null
|
||||
|
@ -266,91 +266,85 @@ class InitializingPage extends StatelessWidget {
|
|||
builder: (final context) {
|
||||
final DomainSetupState state =
|
||||
context.watch<DomainSetupCubit>().state;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/logos/cloudflare.png',
|
||||
width: 150,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
BrandText.h2('basis.domain'.tr()),
|
||||
const SizedBox(height: 10),
|
||||
if (state is Empty)
|
||||
BrandText.body2('initializing.no_connected_domains'.tr()),
|
||||
if (state is Loading)
|
||||
BrandText.body2(
|
||||
state.type == LoadingTypes.loadingDomain
|
||||
? 'initializing.loading_domain_list'.tr()
|
||||
: 'basis.saving'.tr(),
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.use_this_domain'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
if (state is MoreThenOne)
|
||||
BrandText.body2(
|
||||
'initializing.found_more_domains'.tr(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.use_this_domain_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
if (state is Loaded) ...[
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: BrandText.h3(
|
||||
const SizedBox(height: 32),
|
||||
if (state is Empty)
|
||||
Text(
|
||||
'initializing.no_connected_domains'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
if (state is Loading)
|
||||
Text(
|
||||
state.type == LoadingTypes.loadingDomain
|
||||
? 'initializing.loading_domain_list'.tr()
|
||||
: 'basis.saving'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
if (state is MoreThenOne)
|
||||
Text(
|
||||
'initializing.found_more_domains'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
if (state is Loaded) ...[
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
state.domain,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineMedium
|
||||
?.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 56,
|
||||
child: BrandButton.rised(
|
||||
onPressed: () =>
|
||||
context.read<DomainSetupCubit>().load(),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.refresh,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
if (state is Empty) ...[
|
||||
const SizedBox(height: 30),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<DomainSetupCubit>().load(),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.refresh,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
BrandText.buttonTitleText('domain.update_list'.tr()),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
if (state is Empty) ...[
|
||||
const SizedBox(height: 30),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<DomainSetupCubit>().load(),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.refresh,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
BrandText.buttonTitleText('domain.update_list'.tr()),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
if (state is Loaded) ...[
|
||||
const SizedBox(height: 32),
|
||||
BrandButton.rised(
|
||||
onPressed: () =>
|
||||
context.read<DomainSetupCubit>().saveDomain(),
|
||||
text: 'initializing.save_domain'.tr(),
|
||||
),
|
||||
],
|
||||
],
|
||||
if (state is Loaded) ...[
|
||||
const SizedBox(height: 30),
|
||||
BrandButton.rised(
|
||||
onPressed: () =>
|
||||
context.read<DomainSetupCubit>().saveDomain(),
|
||||
text: 'initializing.save_domain'.tr(),
|
||||
),
|
||||
],
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
width: double.infinity,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -367,12 +361,16 @@ class InitializingPage extends StatelessWidget {
|
|||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BrandText.h2('initializing.create_master_account'.tr()),
|
||||
const SizedBox(height: 10),
|
||||
BrandText.body2(
|
||||
'initializing.enter_username_and_password'.tr(),
|
||||
Text(
|
||||
'initializing.create_master_account'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.enter_username_and_password'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
if (formCubitState.isErrorShown) const SizedBox(height: 16),
|
||||
if (formCubitState.isErrorShown)
|
||||
Text(
|
||||
'users.username_rule'.tr(),
|
||||
|
@ -380,7 +378,7 @@ class InitializingPage extends StatelessWidget {
|
|||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const SizedBox(height: 32),
|
||||
CubitFormTextField(
|
||||
formFieldCubit: context.read<RootUserFormCubit>().userName,
|
||||
textAlign: TextAlign.center,
|
||||
|
@ -389,7 +387,7 @@ class InitializingPage extends StatelessWidget {
|
|||
hintText: 'basis.username'.tr(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const SizedBox(height: 16),
|
||||
BlocBuilder<FieldCubit<bool>, FieldCubitState<bool>>(
|
||||
bloc: context.read<RootUserFormCubit>().isVisible,
|
||||
builder: (final context, final state) {
|
||||
|
@ -420,7 +418,7 @@ class InitializingPage extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 32),
|
||||
BrandButton.rised(
|
||||
onPressed: formCubitState.isSubmitting
|
||||
? null
|
||||
|
@ -440,11 +438,16 @@ class InitializingPage extends StatelessWidget {
|
|||
builder: (final context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Spacer(flex: 2),
|
||||
BrandText.h2('initializing.final'.tr()),
|
||||
const SizedBox(height: 10),
|
||||
BrandText.body2('initializing.create_server'.tr()),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'initializing.final'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.create_server'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 128),
|
||||
BrandButton.rised(
|
||||
onPressed:
|
||||
isLoading ? null : appConfigCubit.createServerAndSetDnsRecords,
|
||||
|
@ -479,55 +482,64 @@ class InitializingPage extends StatelessWidget {
|
|||
doneCount = 0;
|
||||
}
|
||||
return Builder(
|
||||
builder: (final context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 15),
|
||||
BrandText.h4(
|
||||
'initializing.checks'.tr(args: [doneCount.toString(), '4']),
|
||||
),
|
||||
const Spacer(flex: 2),
|
||||
const SizedBox(height: 10),
|
||||
BrandText.body2(text),
|
||||
const SizedBox(height: 10),
|
||||
if (doneCount == 0 && state.dnsMatches != null)
|
||||
Column(
|
||||
children: state.dnsMatches!.entries.map((final entry) {
|
||||
final String domain = entry.key;
|
||||
final bool isCorrect = entry.value;
|
||||
return Row(
|
||||
children: [
|
||||
if (isCorrect) const Icon(Icons.check, color: Colors.green),
|
||||
if (!isCorrect)
|
||||
const Icon(Icons.schedule, color: Colors.amber),
|
||||
const SizedBox(width: 10),
|
||||
Text(domain),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
builder: (final context) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.checks'.tr(args: [doneCount.toString(), '4']),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (!state.isLoading)
|
||||
Row(
|
||||
children: [
|
||||
BrandText.body2('initializing.until_the_next_check'.tr()),
|
||||
BrandTimer(
|
||||
startDateTime: state.timerStart!,
|
||||
duration: state.duration!,
|
||||
)
|
||||
],
|
||||
),
|
||||
if (state.isLoading) BrandText.body2('initializing.check'.tr()),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
if (text != null)
|
||||
Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 128),
|
||||
const SizedBox(height: 10),
|
||||
if (doneCount == 0 && state.dnsMatches != null)
|
||||
Column(
|
||||
children: state.dnsMatches!.entries.map((final entry) {
|
||||
final String domain = entry.key;
|
||||
final bool isCorrect = entry.value;
|
||||
return Row(
|
||||
children: [
|
||||
if (isCorrect)
|
||||
const Icon(Icons.check, color: Colors.green),
|
||||
if (!isCorrect)
|
||||
const Icon(Icons.schedule, color: Colors.amber),
|
||||
const SizedBox(width: 10),
|
||||
Text(domain),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (!state.isLoading)
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'initializing.until_the_next_check'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
BrandTimer(
|
||||
startDateTime: state.timerStart!,
|
||||
duration: state.duration!,
|
||||
)
|
||||
],
|
||||
),
|
||||
if (state.isLoading)
|
||||
Text(
|
||||
'initializing.check'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _addCard(final Widget child) => Container(
|
||||
height: 450,
|
||||
padding: paddingH15V0,
|
||||
child: BrandCards.big(child: child),
|
||||
);
|
||||
}
|
||||
|
||||
class _HowTo extends StatelessWidget {
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/filled_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||
import 'package:selfprivacy/utils/network_utils.dart';
|
||||
|
||||
class ServerProviderPicker extends StatefulWidget {
|
||||
const ServerProviderPicker({
|
||||
|
@ -46,7 +51,7 @@ class _ServerProviderPickerState extends State<ServerProviderPicker> {
|
|||
providerCubit: widget.formCubit,
|
||||
providerInfo: ProviderPageInfo(
|
||||
providerType: ServerProvider.hetzner,
|
||||
pathToHow: 'hetzner_how',
|
||||
pathToHow: 'how_hetzner',
|
||||
image: Image.asset(
|
||||
'assets/images/logos/hetzner.png',
|
||||
width: 150,
|
||||
|
@ -59,7 +64,7 @@ class _ServerProviderPickerState extends State<ServerProviderPicker> {
|
|||
providerCubit: widget.formCubit,
|
||||
providerInfo: ProviderPageInfo(
|
||||
providerType: ServerProvider.digitalOcean,
|
||||
pathToHow: 'hetzner_how',
|
||||
pathToHow: 'how_digital_ocean',
|
||||
image: Image.asset(
|
||||
'assets/images/logos/digital_ocean.png',
|
||||
width: 150,
|
||||
|
@ -96,13 +101,16 @@ class ProviderInputDataPage extends StatelessWidget {
|
|||
Widget build(final BuildContext context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
providerInfo.image,
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'initializing.connect_to_server'.tr(),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
"${'initializing.connect_to_server_provider'.tr()}${providerInfo.providerType.displayName}",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.connect_to_server_provider_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
CubitFormTextField(
|
||||
formFieldCubit: providerCubit.apiKey,
|
||||
textAlign: TextAlign.center,
|
||||
|
@ -111,13 +119,13 @@ class ProviderInputDataPage extends StatelessWidget {
|
|||
hintText: 'Provider API Token',
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 32),
|
||||
FilledButton(
|
||||
title: 'basis.connect'.tr(),
|
||||
onPressed: () => providerCubit.trySubmit(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
OutlinedButton(
|
||||
BrandOutlinedButton(
|
||||
child: Text('initializing.how'.tr()),
|
||||
onPressed: () => showModalBottomSheet<void>(
|
||||
context: context,
|
||||
|
@ -154,51 +162,177 @@ class ProviderSelectionPage extends StatelessWidget {
|
|||
final ServerInstallationCubit serverInstallationCubit;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Column(
|
||||
children: [
|
||||
Text(
|
||||
'initializing.select_provider'.tr(),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'initializing.server_provider_description'.tr(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 320,
|
||||
Widget build(final BuildContext context) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.connect_to_server'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
serverInstallationCubit
|
||||
.setServerProviderType(ServerProvider.hetzner);
|
||||
callback(ServerProvider.hetzner);
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/images/logos/hetzner.png',
|
||||
width: 150,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
serverInstallationCubit
|
||||
.setServerProviderType(ServerProvider.digitalOcean);
|
||||
callback(ServerProvider.digitalOcean);
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/images/logos/digital_ocean.png',
|
||||
width: 150,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'initializing.select_provider'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
OutlinedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
color: const Color(0xFFD50C2D),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
'assets/images/logos/hetzner.svg',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Hetzner Cloud',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_countries_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_countries_text_hetzner'
|
||||
.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_price_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_price_text_hetzner'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_payment_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_payment_text_hetzner'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_email_notice'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
title: 'basis.select'.tr(),
|
||||
onPressed: () {
|
||||
serverInstallationCubit
|
||||
.setServerProviderType(ServerProvider.hetzner);
|
||||
callback(ServerProvider.hetzner);
|
||||
},
|
||||
),
|
||||
// Outlined button that will open website
|
||||
BrandOutlinedButton(
|
||||
onPressed: () =>
|
||||
launchURL('https://www.hetzner.com/cloud'),
|
||||
title: 'initializing.select_provider_site_button'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
OutlinedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
color: const Color(0xFF0080FF),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
'assets/images/logos/digital_ocean.svg',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Digital Ocean',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_countries_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_countries_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_price_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_price_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_payment_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_payment_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
title: 'basis.select'.tr(),
|
||||
onPressed: () {
|
||||
serverInstallationCubit
|
||||
.setServerProviderType(ServerProvider.digitalOcean);
|
||||
callback(ServerProvider.digitalOcean);
|
||||
},
|
||||
),
|
||||
// Outlined button that will open website
|
||||
BrandOutlinedButton(
|
||||
onPressed: () =>
|
||||
launchURL('https://www.digitalocean.com'),
|
||||
title: 'initializing.select_provider_site_button'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
InfoBox(text: 'initializing.select_provider_notice'.tr()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/illustrations/stray_deer.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||
|
||||
class ServerTypePicker extends StatefulWidget {
|
||||
const ServerTypePicker({
|
||||
|
@ -68,27 +70,43 @@ class SelectLocationPage extends StatelessWidget {
|
|||
if ((snapshot.data as List<ServerProviderLocation>).isEmpty) {
|
||||
return Text('initializing.no_locations_found'.tr());
|
||||
}
|
||||
return ListView(
|
||||
padding: paddingH15V0,
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
'initializing.choose_location_type'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.choose_location_type_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...(snapshot.data! as List<ServerProviderLocation>).map(
|
||||
(final location) => InkWell(
|
||||
onTap: () {
|
||||
callback(location);
|
||||
},
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (location.flag != null) Text(location.flag!),
|
||||
const SizedBox(height: 8),
|
||||
Text(location.title),
|
||||
const SizedBox(height: 8),
|
||||
if (location.description != null)
|
||||
Text(location.description!),
|
||||
],
|
||||
(final location) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
callback(location);
|
||||
},
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${location.flag ?? ''} ${location.title}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (location.description != null)
|
||||
Text(
|
||||
location.description!,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -126,11 +144,33 @@ class SelectTypePage extends StatelessWidget {
|
|||
if (snapshot.hasData) {
|
||||
if ((snapshot.data as List<ServerType>).isEmpty) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.no_server_types_found'.tr(),
|
||||
'initializing.locations_not_found'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.locations_not_found_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (final context, final constraints) => CustomPaint(
|
||||
size: Size(
|
||||
constraints.maxWidth,
|
||||
(constraints.maxWidth * 1).toDouble(),
|
||||
),
|
||||
painter: StrayDeerPainter(
|
||||
colorScheme: Theme.of(context).colorScheme,
|
||||
colorPalette: context
|
||||
.read<AppSettingsCubit>()
|
||||
.state
|
||||
.corePaletteOrDefault,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.rised(
|
||||
onPressed: () {
|
||||
backToLocationPickingCallback();
|
||||
|
@ -140,51 +180,120 @@ class SelectTypePage extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
}
|
||||
return ListView(
|
||||
padding: paddingH15V0,
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.choose_server_type'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.choose_server_type_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...(snapshot.data! as List<ServerType>).map(
|
||||
(final type) => InkWell(
|
||||
onTap: () {
|
||||
serverInstallationCubit.setServerType(type);
|
||||
},
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
type.title,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'cores: ${type.cores.toString()}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'ram: ${type.ram.toString()}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'disk: ${type.disk.gibibyte.toString()}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'price: ${type.price.value.toString()} ${type.price.currency}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
(final type) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
serverInstallationCubit.setServerType(type);
|
||||
},
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
type.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.memory_outlined,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'server.core_count'.plural(type.cores),
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.memory_outlined,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'initializing.choose_server_type_ram'
|
||||
.tr(args: [type.ram.toString()]),
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.sd_card_outlined,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'initializing.choose_server_type_storage'
|
||||
.tr(
|
||||
args: [type.disk.gibibyte.toString()],
|
||||
),
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 8),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.payments_outlined,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'initializing.choose_server_type_payment_per_month'
|
||||
.tr(
|
||||
args: [
|
||||
'${type.price.value.toString()} ${type.price.currency}'
|
||||
],
|
||||
),
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const SizedBox(height: 16),
|
||||
InfoBox(text: 'initializing.choose_server_type_notice'.tr()),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
enum DnsRecordsCategory {
|
||||
services,
|
||||
|
@ -133,3 +134,32 @@ DnsRecord? extractDkimRecord(final List<DnsRecord> records) {
|
|||
|
||||
return dkimRecord;
|
||||
}
|
||||
|
||||
String getHostnameFromDomain(final String domain) {
|
||||
// Replace all non-alphanumeric characters with an underscore
|
||||
String hostname =
|
||||
domain.split('.')[0].replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
|
||||
if (hostname.endsWith('-')) {
|
||||
hostname = hostname.substring(0, hostname.length - 1);
|
||||
}
|
||||
if (hostname.startsWith('-')) {
|
||||
hostname = hostname.substring(1);
|
||||
}
|
||||
if (hostname.isEmpty) {
|
||||
hostname = 'selfprivacy-server';
|
||||
}
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
void launchURL(final url) async {
|
||||
try {
|
||||
final Uri uri = Uri.parse(url);
|
||||
await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <gtk_theme_fl/gtk_theme_fl_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
|
||||
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) gtk_theme_fl_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkThemeFlPlugin");
|
||||
gtk_theme_fl_plugin_register_with_registrar(gtk_theme_fl_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
flutter_secure_storage_linux
|
||||
gtk_theme_fl
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
|
|
27
pubspec.lock
27
pubspec.lock
|
@ -308,7 +308,7 @@ packages:
|
|||
name: dynamic_color
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.5.4"
|
||||
easy_localization:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -602,13 +602,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
gtk_theme_fl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: gtk_theme_fl
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.1"
|
||||
hive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -764,7 +757,7 @@ packages:
|
|||
source: hosted
|
||||
version: "0.12.12"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
|
@ -1188,20 +1181,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
system_theme:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: system_theme
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
system_theme_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: system_theme_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.2"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1414,4 +1393,4 @@ packages:
|
|||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.17.0 <3.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
flutter: ">=3.3.0"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: selfprivacy
|
||||
description: selfprivacy.org
|
||||
publish_to: 'none'
|
||||
version: 0.7.0+16
|
||||
version: 0.8.0+17
|
||||
|
||||
environment:
|
||||
sdk: '>=2.17.0 <3.0.0'
|
||||
|
@ -14,7 +14,7 @@ dependencies:
|
|||
cubit_form: ^2.0.1
|
||||
device_info_plus: ^4.0.1
|
||||
dio: ^4.0.4
|
||||
dynamic_color: ^1.4.0
|
||||
dynamic_color: ^1.5.4
|
||||
easy_localization: ^3.0.0
|
||||
either_option: ^2.0.1-dev.1
|
||||
equatable: ^2.0.3
|
||||
|
@ -30,7 +30,6 @@ dependencies:
|
|||
graphql: ^5.1.1
|
||||
graphql_codegen: ^0.10.2
|
||||
graphql_flutter: ^5.1.0
|
||||
gtk_theme_fl: ^0.0.1
|
||||
hive: ^2.2.3
|
||||
hive_flutter: ^1.1.0
|
||||
http: ^0.13.5
|
||||
|
@ -38,6 +37,7 @@ dependencies:
|
|||
ionicons: ^0.1.2
|
||||
json_annotation: ^4.6.0
|
||||
local_auth: ^2.0.2
|
||||
material_color_utilities: ^0.1.5
|
||||
modal_bottom_sheet: ^2.0.1
|
||||
nanoid: ^1.0.0
|
||||
package_info: ^2.0.2
|
||||
|
@ -45,7 +45,6 @@ dependencies:
|
|||
provider: ^6.0.2
|
||||
pub_semver: ^2.1.1
|
||||
share_plus: ^4.0.4
|
||||
system_theme: ^2.0.0
|
||||
timezone: ^0.8.0
|
||||
url_launcher: ^6.0.20
|
||||
wakelock: ^0.6.1+1
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
|
||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <system_theme/system_theme_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
|
@ -19,8 +18,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
SystemThemePluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SystemThemePlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
connectivity_plus_windows
|
||||
dynamic_color
|
||||
flutter_secure_storage_windows
|
||||
system_theme
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue