forked from SelfPrivacy/selfprivacy.org.app
Compare commits
5 Commits
571e32ecff
...
2fc20f43c3
Author | SHA1 | Date |
---|---|---|
Inex Code | 2fc20f43c3 | |
Inex Code | 8aaf62ca5c | |
Inex Code | 53e8ae5ce8 | |
Inex Code | 408b359a2a | |
Inex Code | 129eb76a04 |
|
@ -19,9 +19,9 @@
|
|||
"close": "Close",
|
||||
"connect": "Connect",
|
||||
"domain": "Domain",
|
||||
"saving": "Saving..",
|
||||
"saving": "Saving…",
|
||||
"nickname": "Nickname",
|
||||
"loading": "Loading...",
|
||||
"loading": "Loading…",
|
||||
"later": "Skip to setup later",
|
||||
"connect_to_existing": "Connect to an existing server",
|
||||
"reset": "Reset",
|
||||
|
@ -38,13 +38,13 @@
|
|||
"about_project": "About us",
|
||||
"about_application": "About",
|
||||
"onboarding": "Onboarding",
|
||||
"create_ssh_key": "Create SSH key",
|
||||
"create_ssh_key": "Superuser SSH keys",
|
||||
"console": "Console",
|
||||
"application_settings": "Application settings"
|
||||
},
|
||||
"console_page": {
|
||||
"title": "Console",
|
||||
"waiting": "Waiting for initialization..."
|
||||
"waiting": "Waiting for initialization…"
|
||||
},
|
||||
"about_us_page": {
|
||||
"title": "About us"
|
||||
|
@ -61,7 +61,7 @@
|
|||
"reset_config_title": "Reset application config",
|
||||
"reset_config_description": "Reset api keys and root user",
|
||||
"delete_server_title": "Delete server",
|
||||
"delete_server_description": "This removes your server. It will be no longer accessible"
|
||||
"delete_server_description": "This removes your server. It will be no longer accessible."
|
||||
},
|
||||
"ssh": {
|
||||
"title": "SSH keys",
|
||||
|
@ -79,7 +79,7 @@
|
|||
"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_text": "SelfPrivacy works only with your provider accounts: Hetzner, Cloudflare, Backblaze. If you do not own those, we'll help you to create them."
|
||||
},
|
||||
"resource_chart": {
|
||||
"month": "Month",
|
||||
|
@ -133,7 +133,7 @@
|
|||
"ok": "Records are OK",
|
||||
"error": "Problems found",
|
||||
"error_subtitle": "Tap here to fix them",
|
||||
"refreshing": "Refreshing status...",
|
||||
"refreshing": "Refreshing status…",
|
||||
"uninitialized": "Data is not retrieved yet",
|
||||
"services_title": "Services",
|
||||
"services_subtitle": "Type “A” records required for each service.",
|
||||
|
@ -177,7 +177,7 @@
|
|||
"data_migration_title": "Data migration",
|
||||
"data_migration_notice": "During migration all services will be turned off.",
|
||||
"start_migration_button": "Start migration",
|
||||
"migration_process": "Migrating...",
|
||||
"migration_process": "Migrating…",
|
||||
"migration_done": "Finish"
|
||||
},
|
||||
"not_ready_card": {
|
||||
|
@ -276,9 +276,9 @@
|
|||
"final": "Final step",
|
||||
"create_server": "Create server",
|
||||
"what": "What does it mean?",
|
||||
"server_rebooted": "Server rebooted. Waiting for the last verification...",
|
||||
"server_started": "Server started. It will be validated and rebooted now...",
|
||||
"server_created": "Server created. DNS checks and server boot in progress...",
|
||||
"server_rebooted": "Server rebooted. Waiting for the last verification…",
|
||||
"server_started": "Server started. It will be validated and rebooted now…",
|
||||
"server_created": "Server created. DNS checks and server boot in progress…",
|
||||
"until_the_next_check": "Until the next check: ",
|
||||
"check": "Check",
|
||||
"one_more_restart": "One more restart to apply your security certificates.",
|
||||
|
@ -311,7 +311,7 @@
|
|||
"hetzner_connected_description": "Communication established. Enter Hetzner token with access to {}:",
|
||||
"hetzner_connected_placeholder": "Hetzner token",
|
||||
"confirm_server": "Confirm server",
|
||||
"confirm_server_description": "Found your server! Confirm it is correct.",
|
||||
"confirm_server_description": "Found your server! Confirm it is the right one:",
|
||||
"confirm_server_accept": "Yes! That's it",
|
||||
"confirm_server_decline": "Choose a different server",
|
||||
"choose_server": "Choose your server",
|
||||
|
@ -336,7 +336,7 @@
|
|||
"this_device": "This device",
|
||||
"other_devices": "Other devices",
|
||||
"authorize_new_device": "Authorize new device",
|
||||
"access_granted_on" : "Access granted on {}",
|
||||
"access_granted_on": "Access granted on {}",
|
||||
"tip": "Press on the device to revoke access."
|
||||
},
|
||||
"add_new_device_screen": {
|
||||
|
@ -356,7 +356,7 @@
|
|||
},
|
||||
"recovery_key": {
|
||||
"key_connection_error": "Couldn't connect to the server.",
|
||||
"key_synchronizing": "Synchronizing...",
|
||||
"key_synchronizing": "Synchronizing…",
|
||||
"key_main_header": "Recovery key",
|
||||
"key_main_description": "Is needed for SelfPrivacy authorization when all your other authorized devices aren't available.",
|
||||
"key_amount_toggle": "Limit by number of uses",
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"domain": "Домен",
|
||||
"saving": "Сохранение…",
|
||||
"nickname": "Никнейм",
|
||||
"loading": "Загрузка",
|
||||
"loading": "Загрузка…",
|
||||
"later": "Пропустить и настроить потом",
|
||||
"connect_to_existing": "Подключиться к существующему серверу",
|
||||
"reset": "Сбросить",
|
||||
|
@ -29,22 +29,22 @@
|
|||
"no_data": "Нет данных",
|
||||
"wait": "Загрузка",
|
||||
"remove": "Удалить",
|
||||
"apply": "Подать",
|
||||
"apply": "Применить",
|
||||
"done": "Готово",
|
||||
"continue": "Продолжить"
|
||||
},
|
||||
"more_page": {
|
||||
"configuration_wizard": "Мастер Подключения",
|
||||
"configuration_wizard": "Мастер настройки",
|
||||
"about_project": "О проекте SelfPrivacy",
|
||||
"about_application": "О приложении",
|
||||
"onboarding": "Приветствие",
|
||||
"console": "Консоль",
|
||||
"create_ssh_key": "Создать ssh ключ",
|
||||
"create_ssh_key": "SSH ключи администратора",
|
||||
"application_settings": "Настройки приложения"
|
||||
},
|
||||
"console_page": {
|
||||
"title": "Консоль",
|
||||
"waiting": "Ждём инициализации..."
|
||||
"waiting": "Ждём инициализации…"
|
||||
},
|
||||
"about_us_page": {
|
||||
"title": "О проекте SelfPrivacy"
|
||||
|
@ -57,17 +57,17 @@
|
|||
"application_settings": {
|
||||
"title": "Настройки приложения",
|
||||
"dark_theme_title": "Тёмная тема",
|
||||
"dark_theme_description": "Сменить цветовую тему.",
|
||||
"dark_theme_description": "Сменить цветовую тему",
|
||||
"reset_config_title": "Сброс настроек",
|
||||
"reset_config_description": "Сбросить API ключи а также root пользвателя.",
|
||||
"reset_config_description": "Сбросить API ключи, а также root пользователя",
|
||||
"delete_server_title": "Удалить сервер",
|
||||
"delete_server_description": "Действие приведет к удалению сервера. После этого он будет недоступен."
|
||||
"delete_server_description": "Действие приведёт к удалению сервера. После этого он будет недоступен."
|
||||
},
|
||||
"ssh": {
|
||||
"title": "SSH ключи",
|
||||
"create": "Добавить SSH ключ",
|
||||
"delete": "Удалить SSH ключ",
|
||||
"delete_confirm_question": "Вы уверены что хотите удалить следующий ключ?",
|
||||
"delete_confirm_question": "Вы уверены, что хотите удалить следующий ключ?",
|
||||
"subtitle_with_keys": "Ключей: {}",
|
||||
"subtitle_without_keys": "Ключей нет",
|
||||
"no_key_name": "Безымянный ключ",
|
||||
|
@ -92,11 +92,11 @@
|
|||
},
|
||||
"server": {
|
||||
"card_title": "Сервер",
|
||||
"description": "Это виртуальный компьютер на котором работают все Ваши сервисы.",
|
||||
"description": "Это виртуальный компьютер на котором работают все ваши сервисы",
|
||||
"general_information": "Общая информация",
|
||||
"resource_usage": "Потребление ресурсов",
|
||||
"allow_autoupgrade": "Разрешить авто-обноления",
|
||||
"allow_autoupgrade_hint": "Разрешить автоматичесую установку обновлений на сервер",
|
||||
"allow_autoupgrade": "Разрешить авто-обновления",
|
||||
"allow_autoupgrade_hint": "Разрешить автоматическую установку обновлений на сервер",
|
||||
"reboot_after_upgrade": "Перезагружать после обновлений",
|
||||
"reboot_after_upgrade_hint": "Автоматически перезагружать сервер после применения обновлений",
|
||||
"server_timezone": "Часовой пояс сервера",
|
||||
|
@ -136,7 +136,7 @@
|
|||
"ok": "Записи в норме",
|
||||
"error": "Обнаружены проблемы",
|
||||
"error_subtitle": "Нажмите здесь, чтобы исправить",
|
||||
"refreshing": "Обновление данных...",
|
||||
"refreshing": "Обновление данных…",
|
||||
"uninitialized": "Данные ещё не получены",
|
||||
"services_title": "Сервисы",
|
||||
"services_subtitle": "Записи типа “A” необходимые для работы сервисов.",
|
||||
|
@ -155,7 +155,7 @@
|
|||
"create_new": "Создать новую копию",
|
||||
"creating": "Создание копии: {}%",
|
||||
"restoring": "Восстановление из копии",
|
||||
"error_pending": "Сервер вернул ошибку: проверьте её ниже.",
|
||||
"error_pending": "Сервер вернул ошибку: проверьте её ниже",
|
||||
"restore_alert": "Вы собираетесь восстановить из копии созданной {}. Все текущие данные будут потеряны. Вы уверены?",
|
||||
"refresh": "Обновить статус",
|
||||
"refetch_backups": "Обновить список копий",
|
||||
|
@ -180,7 +180,7 @@
|
|||
"data_migration_title": "Миграция данных",
|
||||
"data_migration_notice": "На время миграции данных все сервисы будут выключены.",
|
||||
"start_migration_button": "Начать миграцию",
|
||||
"migration_process": "Мигрируем...",
|
||||
"migration_process": "Мигрируем…",
|
||||
"migration_done": "Завершить"
|
||||
},
|
||||
"not_ready_card": {
|
||||
|
@ -195,7 +195,16 @@
|
|||
"disable": "Выключить сервис",
|
||||
"enable": "Включить сервис",
|
||||
"move": "Переместить на другой диск",
|
||||
"uses": "Использует {usage} на {volume}"
|
||||
"uses": "Использует {usage} на {volume}",
|
||||
"status": {
|
||||
"active": "Включено и работает",
|
||||
"inactive": "Остановлено",
|
||||
"failed": "Не удалось запустить",
|
||||
"off": "Отключено",
|
||||
"activating": "Включается",
|
||||
"deactivating": "Выключается",
|
||||
"reloading": "Перезапускается"
|
||||
}
|
||||
},
|
||||
"mail": {
|
||||
"title": "Почта",
|
||||
|
@ -232,13 +241,13 @@
|
|||
"subtitle": "Закрытый VPN сервер"
|
||||
},
|
||||
"users": {
|
||||
"add_new_user": "Добавьте первого пользователя.",
|
||||
"add_new_user": "Добавьте первого пользователя",
|
||||
"new_user": "Новый пользователь",
|
||||
"delete_user": "Удалить пользователя",
|
||||
"not_ready": "Подключите сервер, домен и DNS в разделе Провайдеры чтобы добавить первого пользователя",
|
||||
"nobody_here": "Здесь будут отображаться пользователи.",
|
||||
"nobody_here": "Здесь будут отображаться пользователи",
|
||||
"login": "Логин",
|
||||
"new_user_info_note": "Новый пользователь автоматически получит доступ ко всем сервисам.",
|
||||
"new_user_info_note": "Новый пользователь автоматически получит доступ ко всем сервисам",
|
||||
"delete_confirm_question": "Вы действительно хотите удалить учетную запись?",
|
||||
"reset_password": "Сбросить пароль",
|
||||
"account": "Учетная запись",
|
||||
|
@ -254,14 +263,14 @@
|
|||
},
|
||||
"initializing": {
|
||||
"connect_to_server": "Подключите сервер",
|
||||
"place_where_data": "Здесь будут жить наши данные и SelfPrivacy-сервисы",
|
||||
"place_where_data": "Здесь будут жить ваши данные и SelfPrivacy-сервисы:",
|
||||
"how": "Как получить API Token",
|
||||
"hetzner_bad_key_error": "Hetzner API ключ неверен",
|
||||
"cloudflare_bad_key_error": "Cloudflare API ключ неверен",
|
||||
"backblaze_bad_key_error": "Информация о Backblaze хранилище неверна",
|
||||
"connect_cloudflare": "Подключите CloudFlare",
|
||||
"manage_domain_dns": "Для управления DNS вашего домена",
|
||||
"cloudflare_api_token": "CloudFlare API Token",
|
||||
"cloudflare_api_token": "CloudFlare API ключ",
|
||||
"connect_backblaze_storage": "Подключите облачное хранилище Backblaze",
|
||||
"no_connected_domains": "На данный момент подлюченных доменов нет",
|
||||
"loading_domain_list": "Загружаем список доменов",
|
||||
|
@ -270,15 +279,15 @@
|
|||
"final": "Последний шаг",
|
||||
"create_server": "Создать сервер",
|
||||
"what": "Что это значит?",
|
||||
"server_rebooted": "Сервер презагружен, ждем последнюю проверку.",
|
||||
"server_started": "Cервер запущен, сейчас он будет проверен и перезагружен.",
|
||||
"server_created": "Cервер создан, идет проверка ДНС адресов и запуск сервера.",
|
||||
"server_rebooted": "Сервер перезагружен, ждём последнюю проверку…",
|
||||
"server_started": "Сервер запущен. Сейчас он будет проверен и перезагружен…",
|
||||
"server_created": "Сервер создан. Идёт проверка DNS адресов и запуск сервера…",
|
||||
"until_the_next_check": "До следующей проверки: ",
|
||||
"check": "Проверка",
|
||||
"one_more_restart": "Сейчас будет дополнительная перезагрузка для активации сертификатов безопастности",
|
||||
"one_more_restart": "Сейчас будет дополнительная перезагрузка для активации сертификатов безопасности.",
|
||||
"create_master_account": "Создайте главную учетную запись",
|
||||
"enter_nickname_and_password": "Введите никнейм и сложный пароль",
|
||||
"finish": "Всё инициализировано.",
|
||||
"finish": "Всё инициализировано",
|
||||
"checks": "Проверок выполнено: \n{} / {}"
|
||||
},
|
||||
"recovering": {
|
||||
|
@ -321,7 +330,8 @@
|
|||
"confirm_cloudflare": "Подключение к Cloudflare",
|
||||
"confirm_cloudflare_description": "Введите токен Cloudflare, который имеет права на {}:",
|
||||
"confirm_backblze": "Подключение к Backblaze",
|
||||
"confirm_backblaze_description": "Введите токен Backblaze, который имеет права на хранилище резервных копий:"
|
||||
"confirm_backblaze_description": "Введите токен Backblaze, который имеет права на хранилище резервных копий:",
|
||||
"confirm_backblaze": "Подключение к Backblaze"
|
||||
},
|
||||
"devices": {
|
||||
"main_screen": {
|
||||
|
@ -330,7 +340,7 @@
|
|||
"this_device": "Это устройство",
|
||||
"other_devices": "Другие устройства",
|
||||
"authorize_new_device": "Авторизовать новое устройство",
|
||||
"access_granted_on" : "Доступ выдан {}",
|
||||
"access_granted_on": "Доступ выдан {}",
|
||||
"tip": "Нажмите на устройство, чтобы отозвать доступ."
|
||||
},
|
||||
"add_new_device_screen": {
|
||||
|
@ -349,8 +359,8 @@
|
|||
}
|
||||
},
|
||||
"recovery_key": {
|
||||
"key_connection_error": "Не удалось соединиться с сервером",
|
||||
"key_synchronizing": "Синхронизация...",
|
||||
"key_connection_error": "Не удалось соединиться с сервером.",
|
||||
"key_synchronizing": "Синхронизация…",
|
||||
"key_main_header": "Ключ восстановления",
|
||||
"key_main_description": "Требуется для авторизации SelfPrivacy, когда авторизованные устройства недоступны.",
|
||||
"key_amount_toggle": "Ограничить использования",
|
||||
|
@ -376,7 +386,7 @@
|
|||
"try_again": "Попробовать ещё раз?",
|
||||
"are_you_sure": "Вы уверены?",
|
||||
"purge_all_keys": "Стереть все ключи авторизации?",
|
||||
"purge_all_keys_confirm": "Да, стереть все ключи!",
|
||||
"purge_all_keys_confirm": "Да, стереть все ключи",
|
||||
"delete_server_volume": "Удалить сервер и хранилище?",
|
||||
"reboot": "Перезагрузить",
|
||||
"you_cant_use_this_api": "Нельзя использовать этот API для доменом с подобным TLD.",
|
||||
|
@ -389,7 +399,7 @@
|
|||
"jobs": {
|
||||
"title": "Задачи",
|
||||
"start": "Начать выполенение",
|
||||
"empty": "Пусто.",
|
||||
"empty": "Задач нет",
|
||||
"create_user": "Создать пользователя",
|
||||
"delete_user": "Удалить пользователя",
|
||||
"service_turn_off": "Остановить",
|
||||
|
@ -397,7 +407,7 @@
|
|||
"job_added": "Задача добавленна",
|
||||
"run_jobs": "Запустите задачи",
|
||||
"reboot_success": "Сервер перезагружается",
|
||||
"reboot_failed": "Не удалось перезагрузить сервер, проверьте логи",
|
||||
"reboot_failed": "Не удалось перезагрузить сервер, проверьте логи.",
|
||||
"config_pull_failed": "Не удалось обновить конфигурацию сервера. Обновление ПО запущено.",
|
||||
"upgrade_success": "Запущено обновление сервера",
|
||||
"upgrade_failed": "Обновить сервер не вышло",
|
||||
|
|
|
@ -66,11 +66,6 @@ abstract class AppThemeFactory {
|
|||
typography: appTypography,
|
||||
useMaterial3: true,
|
||||
scaffoldBackgroundColor: colorScheme.background,
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
backgroundColor: colorScheme.primary,
|
||||
foregroundColor: colorScheme.onPrimary,
|
||||
),
|
||||
);
|
||||
|
||||
return materialThemeData;
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
|
||||
class BrandHeader extends StatelessWidget {
|
||||
const BrandHeader({
|
||||
|
@ -15,25 +13,17 @@ class BrandHeader extends StatelessWidget {
|
|||
final VoidCallback? onBackButtonPressed;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Container(
|
||||
height: 52,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: EdgeInsets.only(
|
||||
left: hasBackButton ? 1 : 15,
|
||||
Widget build(final BuildContext context) => AppBar(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Text(title),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (hasBackButton) ...[
|
||||
IconButton(
|
||||
icon: const Icon(BrandIcons.arrowLeft),
|
||||
leading: hasBackButton
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed:
|
||||
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
BrandText.h4(title),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,72 +1,145 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||
import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart';
|
||||
import 'package:selfprivacy/ui/helpers/widget_size.dart';
|
||||
|
||||
class BrandHeroScreen extends StatelessWidget {
|
||||
const BrandHeroScreen({
|
||||
required this.children,
|
||||
final super.key,
|
||||
this.headerTitle = '',
|
||||
this.hasBackButton = true,
|
||||
this.hasFlashButton = true,
|
||||
this.heroIcon,
|
||||
this.heroTitle,
|
||||
this.heroIconWidget,
|
||||
this.heroTitle = '',
|
||||
this.heroSubtitle,
|
||||
this.onBackButtonPressed,
|
||||
});
|
||||
|
||||
final List<Widget> children;
|
||||
final String headerTitle;
|
||||
final bool hasBackButton;
|
||||
final bool hasFlashButton;
|
||||
final IconData? heroIcon;
|
||||
final String? heroTitle;
|
||||
final Widget? heroIconWidget;
|
||||
final String heroTitle;
|
||||
final String? heroSubtitle;
|
||||
final VoidCallback? onBackButtonPressed;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52.0),
|
||||
child: BrandHeader(
|
||||
title: headerTitle,
|
||||
Widget build(final BuildContext context) {
|
||||
final Widget heroIconWidget = this.heroIconWidget ??
|
||||
Icon(
|
||||
heroIcon ?? Icons.help,
|
||||
size: 48.0,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
);
|
||||
final bool hasHeroIcon = heroIcon != null || this.heroIconWidget != null;
|
||||
|
||||
return Scaffold(
|
||||
floatingActionButton: hasFlashButton ? const BrandFab() : null,
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
HeroSliverAppBar(
|
||||
heroTitle: heroTitle,
|
||||
hasHeroIcon: hasHeroIcon,
|
||||
hasBackButton: hasBackButton,
|
||||
onBackButtonPressed: onBackButtonPressed,
|
||||
heroIconWidget: heroIconWidget,
|
||||
),
|
||||
),
|
||||
floatingActionButton: hasFlashButton ? const BrandFab() : null,
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
children: <Widget>[
|
||||
if (heroIcon != null)
|
||||
Container(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Icon(
|
||||
heroIcon,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
if (heroTitle != null)
|
||||
Text(
|
||||
heroTitle!,
|
||||
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
if (heroSubtitle != null)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 4.0,
|
||||
),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
Text(
|
||||
heroSubtitle!,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
textAlign: hasHeroIcon ? TextAlign.center : TextAlign.start,
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
...children,
|
||||
]),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate(children),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HeroSliverAppBar extends StatefulWidget {
|
||||
const HeroSliverAppBar({
|
||||
required this.heroTitle,
|
||||
required this.hasHeroIcon,
|
||||
required this.hasBackButton,
|
||||
required this.onBackButtonPressed,
|
||||
required this.heroIconWidget,
|
||||
final super.key,
|
||||
});
|
||||
|
||||
final String heroTitle;
|
||||
final bool hasHeroIcon;
|
||||
final bool hasBackButton;
|
||||
final VoidCallback? onBackButtonPressed;
|
||||
final Widget heroIconWidget;
|
||||
|
||||
@override
|
||||
State<HeroSliverAppBar> createState() => _HeroSliverAppBarState();
|
||||
}
|
||||
|
||||
class _HeroSliverAppBarState extends State<HeroSliverAppBar> {
|
||||
Size _size = Size.zero;
|
||||
@override
|
||||
Widget build(final BuildContext context) => SliverAppBar(
|
||||
expandedHeight:
|
||||
widget.hasHeroIcon ? 148.0 + _size.height : 72.0 + _size.height,
|
||||
primary: true,
|
||||
pinned: true,
|
||||
stretch: true,
|
||||
leading: widget.hasBackButton
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: widget.onBackButtonPressed ??
|
||||
() => Navigator.of(context).pop(),
|
||||
)
|
||||
: null,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
title: LayoutBuilder(
|
||||
builder: (final context, final constraints) => SizedBox(
|
||||
width: constraints.maxWidth - 72.0,
|
||||
child: WidgetSize(
|
||||
onChange: (final Size size) => setState(() => _size = size),
|
||||
child: Text(
|
||||
widget.heroTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
overflow: TextOverflow.fade,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
expandedTitleScale: 1.2,
|
||||
centerTitle: true,
|
||||
collapseMode: CollapseMode.pin,
|
||||
titlePadding: const EdgeInsets.only(
|
||||
bottom: 12.0,
|
||||
top: 16.0,
|
||||
),
|
||||
background: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 72.0),
|
||||
if (widget.hasHeroIcon) widget.heroIconWidget,
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
class WidgetSize extends StatefulWidget {
|
||||
const WidgetSize({
|
||||
required this.onChange,
|
||||
required this.child,
|
||||
final super.key,
|
||||
});
|
||||
final Widget child;
|
||||
final Function onChange;
|
||||
|
||||
@override
|
||||
State<WidgetSize> createState() => _WidgetSizeState();
|
||||
}
|
||||
|
||||
class _WidgetSizeState extends State<WidgetSize> {
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
|
||||
return Container(
|
||||
key: widgetKey,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
var widgetKey = GlobalKey();
|
||||
Size? oldSize;
|
||||
|
||||
void postFrameCallback(_) {
|
||||
final context = widgetKey.currentContext;
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
;
|
||||
|
||||
final newSize = context.size;
|
||||
if (oldSize == newSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
oldSize = newSize;
|
||||
widget.onChange(newSize);
|
||||
}
|
||||
}
|
|
@ -91,7 +91,6 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
|||
if (!isReady) {
|
||||
return BrandHeroScreen(
|
||||
hasBackButton: true,
|
||||
headerTitle: '',
|
||||
heroIcon: BrandIcons.globe,
|
||||
heroTitle: 'domain.screen_title'.tr(),
|
||||
heroSubtitle: 'not_ready_card.in_menu'.tr(),
|
||||
|
|
|
@ -16,7 +16,8 @@ class AboutApplicationPage extends StatelessWidget {
|
|||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'about_application_page.title'.tr(),
|
||||
hasBackButton: true),
|
||||
hasBackButton: true,
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
padding: paddingH15V0,
|
||||
|
|
|
@ -12,7 +12,6 @@ import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
|||
import 'package:selfprivacy/logic/models/job.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/segmented_buttons.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
||||
|
|
|
@ -47,14 +47,18 @@ class _SelectTimezoneState extends State<SelectTimezone> {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'server.select_timezone'.tr(),
|
||||
hasBackButton: true,
|
||||
appBar: AppBar(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Text('server.select_timezone'.tr()),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
controller: controller,
|
||||
children: locations
|
||||
.asMap()
|
||||
|
@ -124,5 +128,6 @@ class _SelectTimezoneState extends State<SelectTimezone> {
|
|||
.values
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -47,25 +47,14 @@ class _ServicePageState extends State<ServicePage> {
|
|||
|
||||
return BrandHeroScreen(
|
||||
hasBackButton: true,
|
||||
children: [
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: SvgPicture.string(
|
||||
heroIconWidget: SvgPicture.string(
|
||||
service.svgIcon,
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
service.displayName,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
heroTitle: service.displayName,
|
||||
children: [
|
||||
ServiceStatusCard(status: service.status),
|
||||
const SizedBox(height: 16),
|
||||
if (service.url != null)
|
||||
|
|
|
@ -415,7 +415,8 @@ class InitializingPage extends StatelessWidget {
|
|||
BrandText.h2('initializing.create_master_account'.tr()),
|
||||
const SizedBox(height: 10),
|
||||
BrandText.body2(
|
||||
'initializing.enter_nickname_and_password'.tr()),
|
||||
'initializing.enter_nickname_and_password'.tr(),
|
||||
),
|
||||
const Spacer(),
|
||||
CubitFormTextField(
|
||||
formFieldCubit: context.read<RootUserFormCubit>().userName,
|
||||
|
|
Loading…
Reference in New Issue