diff --git a/assets/fonts/BrandIcons.ttf b/assets/fonts/BrandIcons.ttf index bf40d009..404489f3 100644 Binary files a/assets/fonts/BrandIcons.ttf and b/assets/fonts/BrandIcons.ttf differ diff --git a/assets/fonts/Inter-Bold.ttf b/assets/fonts/Inter-Bold.ttf new file mode 100644 index 00000000..e98b84ce Binary files /dev/null and b/assets/fonts/Inter-Bold.ttf differ diff --git a/assets/fonts/Inter-ExtraBold.ttf b/assets/fonts/Inter-ExtraBold.ttf new file mode 100644 index 00000000..7f16a0f0 Binary files /dev/null and b/assets/fonts/Inter-ExtraBold.ttf differ diff --git a/assets/fonts/Inter-Medium.ttf b/assets/fonts/Inter-Medium.ttf new file mode 100644 index 00000000..721147d8 Binary files /dev/null and b/assets/fonts/Inter-Medium.ttf differ diff --git a/assets/fonts/Inter-Regular.ttf b/assets/fonts/Inter-Regular.ttf new file mode 100644 index 00000000..96fd6a12 Binary files /dev/null and b/assets/fonts/Inter-Regular.ttf differ diff --git a/assets/fonts/Inter-SemiBold.ttf b/assets/fonts/Inter-SemiBold.ttf new file mode 100644 index 00000000..ddb27929 Binary files /dev/null and b/assets/fonts/Inter-SemiBold.ttf differ diff --git a/assets/translations/en.json b/assets/translations/en.json index 70371bbf..c1ca5eee 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,46 +1,167 @@ { "test": "en-test", "basis": { - "_comment": "базовые элементы интерфейса", - "providers": "Провайдеры", - "services": "Сервисы", - "users": "Пользователи", - "more": "Еще", - "next": "Далее", - "got_it": "Понял" + "_comment": "Basic interface elements", + "providers": "Providers", + "services": "Services", + "users": "Users", + "more": "More", + "next": "Next", + "got_it": "Got it", + "settings": "Settings", + "password": "Password", + "create": "Add new", + "confirmation": "Confirmation", + "cancel": "Cancel", + "delete": "Delete", + "close": "Close", + "connect": "Connect", + "domain": "Domain", + "saving": "Saving..", + "nickname": "nickname", + "loading": "loading" }, "more": { - "_comment": "Элементы на странице еще", - "configuration_wizard": "Мастер Подключения", - "settings": "Настройки приложения", - "about_project": "О проекте SelfPrivacy", - "about_app": "О приложении", + "_comment": "'More' tab", + "configuration_wizard": "Setup wizard", + "settings": "Application settings", + "about_project": "About us", + "about_app": "About application", "onboarding": "Onboarding", "console": "Console" }, "onboarding": { - "_comment": "страницы онбординга", - "page1_title": "Digital independence, available to all of us.", + "_comment": "Onboarding pages", + "page1_title": "Digital independence, available to all of us", "page1_text": "Mail, VPN, Messenger, social network and much more on your private server, under your control.", - "page2_title": "SelfPrivacy is 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 — it's not a cloud, but your personal datacenter", + "page2_text": "SelfPrivacy works only with your provider accounts: Hetzner, Cloudflare, Backblaze. If you do not own those, we'll help you to create them" }, "providers": { - "_comment": "вкладка провайдеры", + "_comment": "'Providers' tab", "page_title": "Your Data Center", "server": { - "card_title": "Сервер" + "card_title": "Server", + "bottom_sheet": { + "1": "It's a virtual computer, where all your services live.", + "2": "1 CPU, RAM 4Gb, 40Gb — $5 per month", + "3": "Status — Good" + } }, "domain": { - "card_title": "Домен" + "card_title": "Domain", + "bottom_sheet": { + "1": "It's your personal internet address that will point to the server and other services of yours.", + "2": "{} — expires on {}", + "3": "Status — Good" + } }, "backup": { - "card_title": "Резервное копирование" + "card_title": "Backup", + "bottom_sheet": { + "1": "Will save your day in case of incident: hackers attack, server deletion, etc.", + "2": "3Gb/10Gb, last backup was yesterday {}", + "3": "Status — Good" + } } }, "not_ready_card": { - "1": "Завершите настройку приложения используя ", + "_comment": "Card shown when user skips initial setup", + "1": "Please finish application setup using ", "2": "@:more.configuration_wizard", - "3": " для продолжения работы" + "3": " for further work" + }, + "services": { + "_comment": "Вкладка сервисы", + "title": "Your personal, private and independent services.", + "mail": { + "title": "E-Mail", + "subtitle": "E-Mail for company and family.", + "bottom_sheet": { + "1": "To connect to the mailserver, please use {} domain alongside with username and password, that you created. Also feel free to invite", + "2": "new users" + } + }, + "messenger": { + "title": "Messenger", + "subtitle": "Telegram or Signal not so private as Delta.Chat that uses your private server.", + "bottom_sheet": { + "1": "For connection, please use {} domain and credentials that you created." + } + }, + "password_manager": { + "title": "Password Manager", + "subtitle": "Base of your security. Bitwarden will help you to create, store and move passwords between devices, as well as input them, when requested using autocompletion.", + "bottom_sheet": { + "1": "You can connect to the service and create a user via this link:" + } + }, + "video": { + "title": "Videomeet", + "subtitle": "Zoom and Google Meet are good, but Jitsi Meet is a worth alternative that also gives you confidence that you're not being listened.", + "bottom_sheet": { + "1": "Using Jitsi as simple as just visiting this link:" + } + }, + "cloud": { + "title": "Cloud Storage", + "subtitle": "Do not allow cloud services to read your data by using NextCloud.", + "bottom_sheet": { + "1": "You can connect and create a new user here:" + } + }, + "social_network": { + "title": "Social Network", + "subtitle": "It's hard to believe, but it became possible to create your own social network, with your own rules and target audience.", + "bottom_sheet": { + "1": "You can connect and create new social user here:" + } + }, + "git": { + "title": "Git-server", + "subtitle": "Private alternative to the Github, that belongs to you, but not a Microsoft.", + "bottom_sheet": { + "1": "You can connect and create a new user here:" + } + } + }, + "users": { + "_comment": "'Users' tab", + "add_new_user": "Добавьте первого пользователя", + "new_user": "Новый пользователь", + "not_ready": "Подключите сервер, домен и DNS в разделу Провайдеры, чтобы добавить первого пользователя", + "nobody_here": "'Здесь пока никого'", + "login": "Логин", + "onboarding": "Onboarding", + "console": "Console", + "new_user_info_note": "Новый пользователь автоматически получит доступ ко всем сервисам. Ещё какое-то описание.", + "delete_confirm_question": "удалить учетную запись?", + "reset_password": "Сбросить пароль", + "account": "Учетная запись", + "send_regisration_data": "Отправить реквизиты для входа" + }, + "initializing": { + "_comment": "initializing page", + "1": "Подключите сервер Hetzner", + "2": "Здесь будут жить наши данные и SelfPrivacy-сервисы", + "how": "Как получить API Token", + "3": "'Подключите CloudFlare'", + "4": "Для управления DNS вашего домена", + "5": "CloudFlare API Token", + "6": "Подключите облачное хранилище Backblaze", + "7": "На данный момент подлюченных доменов нет", + "8": "Загружаем список доменов", + "9": "Найдено больше одного домена, для вашей безопастности, просим вам удалить не нужные домены", + "10": "Сохранить домен", + "11": "Создать сервер", + "what": "Что это значит?", + "13": "Сервер презагружен, ждем последнюю проверку", + "14": "Cервер запушен, сейчас он будет проверен и перезагружен", + "15": "Cервер создан, идет проверка ДНС адресов и запуск сервера", + "16": "До следующей проверки: ", + "17": "Проверка", + "18": "Как получить Hetzner API Token'", + "19": "1 Переходим по ссылке ", + "20": "\n2 Заходим в созданный нами проект. Если такового - нет, значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет." } } \ No newline at end of file diff --git a/assets/translations/ru.json b/assets/translations/ru.json index a84f981f..c26efa35 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -7,10 +7,22 @@ "users": "Пользователи", "more": "Еще", "next": "Далее", - "got_it": "Понял" + "got_it": "Понял", + "settings": "Настройки", + "password": "Пароль", + "create": "Создать", + "confirmation": "Подтверждение", + "cancel": "Отменить", + "delete": "Удалить", + "close": "Закрыть", + "connect": "Подключить", + "domain": "Домен", + "saving": "Сохранение..", + "nickname": "Никнейм", + "loading": "Загрузка" }, "more": { - "_comment": "Элементы на странице еще", + "_comment": "вкладка еще", "configuration_wizard": "Мастер Подключения", "settings": "Настройки приложения", "about_project": "О проекте SelfPrivacy", @@ -29,13 +41,28 @@ "_comment": "вкладка провайдеры", "page_title": "Ваш Дата-центр", "server": { - "card_title": "Сервер" + "card_title": "Сервер", + "bottom_sheet": { + "1": "Это виртульный компьютер на котором работают все ваши сервисы.", + "2": "1 CPU, RAM 4Gb, 40Gb — $5 в месяц", + "3": "Статус — в норме" + } }, "domain": { - "card_title": "Домен" + "card_title": "Домен", + "bottom_sheet": { + "1": "Это ваш личный адрес в интернете, который будет указывать на сервер и другие ваши сервисы.", + "2": "{} — продлен до {}", + "3": "Статус — в норме" + } }, "backup": { - "card_title": "Резервное копирование" + "card_title": "Резервное копирование", + "bottom_sheet": { + "1": "Выручит в любой ситуации: хакерская атака, удаление сервера и т.п.", + "2": "3Gb — бестплатно до 10Gb, последний вчера в {}", + "3": "Статус — в норме" + } } }, "not_ready_card": { @@ -43,5 +70,98 @@ "1": "Завершите настройку приложения используя ", "2": "@:more.configuration_wizard", "3": " для продолжения работы" + }, + "services": { + "_comment": "Вкладка сервисы", + "title": "Ваши личные приватные и независимые сервисы", + "mail": { + "title": "Почта", + "subtitle": "Электронная почта для семьи или компании", + "bottom_sheet": { + "1": "Для подключения почтового ящика используйте {} и логин пароль, который вы создали. Так же приглашайте", + "2": "новых пользователей" + } + }, + "messenger": { + "title": "Мессенджер", + "subtitle": "Telegram и Signal не так приватны, как Delta.Chat использующий ваш личный сервер.", + "bottom_sheet": { + "1": "Для подключения используйте {} и логин пароль, который вы создали" + } + }, + "password_manager": { + "title": "Менеджер паролей", + "subtitle": "Фундамент безопасности. Создавать, хранить, копировать пароли между устройствами, вбивать их в формы поможет — Bitwarden.", + "bottom_sheet": { + "1": "Подключиться к серверу и создать пользователя можно по адресу:" + } + }, + "video": { + "title": "Видеоконференция", + "subtitle": "Zoom и Google meet отличные инструменты, но Jitsi meet не хуже и дает уверенность, что вас никто не подслушивает.", + "bottom_sheet": { + "1": "Для использования просто перейдите по ссылке:" + } + }, + "cloud": { + "title": "Файловое облако", + "subtitle": "Не позволяйте облачным сервисам читать ваши данные используйте NextCloud.", + "bottom_sheet": { + "1": "Подключиться к серверу и создать пользователя можно по адресу:" + } + }, + "social_network": { + "title": "Социальная сеть", + "subtitle": "Сложно поверить, но стало возможным создать свою собственную социальную сеть, со своими правилами и аудиторией.", + "bottom_sheet": { + "1": "Подключиться к серверу и создать пользователя можно по адресу:" + } + }, + "git": { + "title": "Git-сервер", + "subtitle": "Приватная альтернатива Github, которая принадлежит вам, а не Microsoft.", + "bottom_sheet": { + "1": "Подключиться к серверу и создать пользователя можно по адресу:" + } + } + }, + "users": { + "_comment": "'Users' tab", + "add_new_user": "Добавьте первого пользователя", + "new_user": "Новый пользователь", + "not_ready": "Подключите сервер, домен и DNS в разделу Провайдеры, чтобы добавить первого пользователя", + "nobody_here": "'Здесь пока никого'", + "login": "Логин", + "onboarding": "Onboarding", + "console": "Console", + "new_user_info_note": "Новый пользователь автоматически получит доступ ко всем сервисам. Ещё какое-то описание.", + "delete_confirm_question": "удалить учетную запись?", + "reset_password": "Сбросить пароль", + "account": "Учетная запись", + "send_regisration_data": "Отправить реквизиты для входа" + }, + "initializing": { + "_comment": "initializing page", + "1": "Подключите сервер Hetzner", + "2": "Здесь будут жить наши данные и SelfPrivacy-сервисы", + "how": "Как получить API Token", + "3": "'Подключите CloudFlare'", + "4": "Для управления DNS вашего домена", + "5": "CloudFlare API Token", + "6": "Подключите облачное хранилище Backblaze", + "7": "На данный момент подлюченных доменов нет", + "8": "Загружаем список доменов", + "9": "Найдено больше одного домена, для вашей безопастности, просим вам удалить не нужные домены", + "10": "Сохранить домен", + "11": "Создать сервер", + "what": "Что это значит?", + "13": "Сервер презагружен, ждем последнюю проверку", + "14": "Cервер запушен, сейчас он будет проверен и перезагружен", + "15": "Cервер создан, идет проверка ДНС адресов и запуск сервера", + "16": "До следующей проверки: ", + "17": "Проверка", + "18": "Как получить Hetzner API Token'", + "19": "1 Переходим по ссылке ", + "20": "\n2 Заходим в созданный нами проект. Если такового - нет, значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет." } } \ No newline at end of file diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 55dd6b3f..af289892 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; class BlocAndProviderConfig extends StatelessWidget { @@ -30,7 +29,6 @@ class BlocAndProviderConfig extends StatelessWidget { lazy: false, create: (_) => AppConfigCubit()..load(), ), - BlocProvider(create: (_) => ServicesCubit()), BlocProvider(create: (_) => ProvidersCubit()), BlocProvider(create: (_) => UsersCubit()), ], diff --git a/lib/config/brand_theme.dart b/lib/config/brand_theme.dart index c8fd246e..9f76cc0c 100644 --- a/lib/config/brand_theme.dart +++ b/lib/config/brand_theme.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:selfprivacy/config/text_themes.dart'; import 'brand_colors.dart'; final ligtTheme = ThemeData( primaryColor: BrandColors.primary, + fontFamily: 'Inter', brightness: Brightness.light, scaffoldBackgroundColor: BrandColors.scaffoldBackground, inputDecorationTheme: InputDecorationTheme( @@ -33,21 +33,17 @@ final ligtTheme = ThemeData( color: BrandColors.red1, ), ), - errorStyle: GoogleFonts.inter( - textStyle: TextStyle( - fontSize: 12, - color: BrandColors.red1, - ), + errorStyle: TextStyle( + fontSize: 12, + color: BrandColors.red1, ), ), - textTheme: GoogleFonts.interTextTheme( - TextTheme( - headline1: headline1Style, - headline2: headline2Style, - caption: headline4Style, - bodyText1: body1Style, - subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style - ), + textTheme: TextTheme( + headline1: headline1Style, + headline2: headline2Style, + caption: headline4Style, + bodyText1: body1Style, + subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style ), ); @@ -57,14 +53,12 @@ var darkTheme = ligtTheme.copyWith( iconTheme: IconThemeData(color: BrandColors.gray3), cardColor: BrandColors.gray1, dialogBackgroundColor: Color(0xFF202120), - textTheme: GoogleFonts.interTextTheme( - TextTheme( - headline1: headline1Style.copyWith(color: BrandColors.white), - headline2: headline2Style.copyWith(color: BrandColors.white), - caption: headline4Style.copyWith(color: BrandColors.white), - bodyText1: body1Style.copyWith(color: BrandColors.white), - subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style - ), + textTheme: TextTheme( + headline1: headline1Style.copyWith(color: BrandColors.white), + headline2: headline2Style.copyWith(color: BrandColors.white), + caption: headline4Style.copyWith(color: BrandColors.white), + bodyText1: body1Style.copyWith(color: BrandColors.white), + subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style ), inputDecorationTheme: InputDecorationTheme( labelStyle: TextStyle(color: BrandColors.white), diff --git a/lib/config/text_themes.dart b/lib/config/text_themes.dart index 1c8c0fbf..b64ab1a2 100644 --- a/lib/config/text_themes.dart +++ b/lib/config/text_themes.dart @@ -1,41 +1,38 @@ import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:selfprivacy/utils/named_font_weight.dart'; import 'brand_colors.dart'; -final defaultTextStyle = GoogleFonts.inter( - textStyle: TextStyle( - fontSize: 15, - color: BrandColors.textColor1, - ), +final defaultTextStyle = TextStyle( + fontSize: 15, + color: BrandColors.textColor1, ); -final headline1Style = GoogleFonts.inter( +final headline1Style = defaultTextStyle.copyWith( fontSize: 40, fontWeight: NamedFontWeight.extraBold, color: BrandColors.headlineColor, ); -final headline2Style = GoogleFonts.inter( +final headline2Style = defaultTextStyle.copyWith( fontSize: 24, fontWeight: NamedFontWeight.extraBold, color: BrandColors.headlineColor, ); -final onboardingTitle = GoogleFonts.inter( +final onboardingTitle = defaultTextStyle.copyWith( fontSize: 30, fontWeight: NamedFontWeight.extraBold, color: BrandColors.headlineColor, ); -final headline3Style = GoogleFonts.inter( +final headline3Style = defaultTextStyle.copyWith( fontSize: 20, fontWeight: NamedFontWeight.extraBold, color: BrandColors.headlineColor, ); -final headline4Style = GoogleFonts.inter( +final headline4Style = defaultTextStyle.copyWith( fontSize: 18, fontWeight: NamedFontWeight.medium, color: BrandColors.headlineColor, @@ -59,16 +56,12 @@ final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45); final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue); -final progressTextStyleLight = GoogleFonts.inter( - textStyle: TextStyle( - fontSize: 11, - color: BrandColors.textColor1, - ), +final progressTextStyleLight = TextStyle( + fontSize: 11, + color: BrandColors.textColor1, ); -final progressTextStyleDark = GoogleFonts.inter( - textStyle: TextStyle( - fontSize: 11, - color: BrandColors.white, - ), +final progressTextStyleDark = TextStyle( + fontSize: 11, + color: BrandColors.white, ); diff --git a/lib/logic/cubit/app_config/app_config_cubit.dart b/lib/logic/cubit/app_config/app_config_cubit.dart index dbe64180..4d8dd8a5 100644 --- a/lib/logic/cubit/app_config/app_config_cubit.dart +++ b/lib/logic/cubit/app_config/app_config_cubit.dart @@ -9,6 +9,7 @@ import 'package:selfprivacy/logic/models/server_details.dart'; import 'package:selfprivacy/logic/models/user.dart'; import 'app_config_repository.dart'; +export 'package:provider/provider.dart'; part 'app_config_state.dart'; @@ -281,49 +282,3 @@ class AppConfigCubit extends Cubit { } } } - -// void checkDnsAndStartServer() async { -// var ip4 = state.hetznerServer.ip4; -// var domainName = state.cloudFlareDomain.domainName; - -// var isMatch = await repository.isDnsAddressesMatch(domainName, ip4); - -// if (isMatch) { -// var server = await repository.startServer( -// state.hetznerKey, -// state.hetznerServer, -// ); -// repository.saveServerDetails(server); -// emit( -// state.copyWith( -// hasFinalChecked: true, -// isServerStarted: true, -// isLoading: false, -// hetznerServer: server, -// ), -// ); -// } else { -// emit(state.copyWith(lastDnsCheckTime: DateTime.now())); -// } -// } - -// void serverReset() async { -// var callBack = () async { -// var isServerWorking = await repository.isHttpServerWorking( -// state.cloudFlareDomain.domainName, -// ); -// if (!isServerWorking) { -// var last = DateTime.now(); -// // emit(state.copyWith(lastServerStatusCheckTime: last)); -// return; -// } - -// var hetznerServerDetails = await repository.restart( -// state.hetznerKey, -// state.hetznerServer, -// ); -// emit(state.copyWith(hetznerServer: hetznerServerDetails)); -// }; - -// _tryOrAddError(state, callBack); -// } diff --git a/lib/logic/cubit/services/services_cubit.dart b/lib/logic/cubit/services/services_cubit.dart index 96e76686..a23b70e7 100644 --- a/lib/logic/cubit/services/services_cubit.dart +++ b/lib/logic/cubit/services/services_cubit.dart @@ -1,28 +1,28 @@ -import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; -import 'package:selfprivacy/logic/models/service.dart'; -import 'package:selfprivacy/logic/models/state_types.dart'; +// import 'package:bloc/bloc.dart'; +// import 'package:equatable/equatable.dart'; +// import 'package:meta/meta.dart'; +// import 'package:selfprivacy/logic/models/service.dart'; +// import 'package:selfprivacy/logic/models/state_types.dart'; -export 'package:provider/provider.dart'; -export 'package:selfprivacy/logic/models/state_types.dart'; +// export 'package:provider/provider.dart'; +// export 'package:selfprivacy/logic/models/state_types.dart'; -part 'services_state.dart'; +// part 'services_state.dart'; -class ServicesCubit extends Cubit { - ServicesCubit() : super(ServicesState(all)); +// class ServicesCubit extends Cubit { +// ServicesCubit() : super(ServicesState(all)); - void connect(Service service) { - var newState = state.updateElement(service, StateType.stable); - emit(newState); - } -} +// void connect(Service service) { +// var newState = state.updateElement(service, StateType.stable); +// emit(newState); +// } +// } -final all = ServiceTypes.values - .map( - (type) => Service( - state: StateType.uninitialized, - type: type, - ), - ) - .toList(); +// final all = ServiceTypes.values +// .map( +// (type) => Service( +// state: StateType.uninitialized, +// type: type, +// ), +// ) +// .toList(); diff --git a/lib/logic/cubit/services/services_state.dart b/lib/logic/cubit/services/services_state.dart index 0cc0fb96..d20a63a8 100644 --- a/lib/logic/cubit/services/services_state.dart +++ b/lib/logic/cubit/services/services_state.dart @@ -1,26 +1,26 @@ -part of 'services_cubit.dart'; +// part of 'services_cubit.dart'; -@immutable -class ServicesState extends Equatable{ - ServicesState(this.all); +// @immutable +// class ServicesState extends Equatable{ +// ServicesState(this.all); - final List all; +// final List all; - ServicesState updateElement(Service service, StateType newState) { - var newList = [...all]; - var index = newList.indexOf(service); - newList[index] = service.updateState(newState); - return ServicesState(newList); - } +// ServicesState updateElement(Service service, StateType newState) { +// var newList = [...all]; +// var index = newList.indexOf(service); +// newList[index] = service.updateState(newState); +// return ServicesState(newList); +// } - List get connected => all - .where((service) => service.state != StateType.uninitialized) - .toList(); +// List get connected => all +// .where((service) => service.state != StateType.uninitialized) +// .toList(); - List get uninitialized => all - .where((service) => service.state == StateType.uninitialized) - .toList(); +// List get uninitialized => all +// .where((service) => service.state == StateType.uninitialized) +// .toList(); - @override - List get props => all; -} +// @override +// List get props => all; +// } diff --git a/lib/logic/models/service.dart b/lib/logic/models/service.dart index a799c682..8ce6e310 100644 --- a/lib/logic/models/service.dart +++ b/lib/logic/models/service.dart @@ -1,25 +1,25 @@ -import 'package:equatable/equatable.dart'; -import 'package:selfprivacy/logic/models/state_types.dart'; +// import 'package:equatable/equatable.dart'; +// import 'package:selfprivacy/logic/models/state_types.dart'; -enum ServiceTypes { - messanger, - mail, - passwordManager, - github, - cloud, -} +// enum ServiceTypes { +// messanger, +// mail, +// passwordManager, +// github, +// cloud, +// } -class Service extends Equatable { - const Service({required this.state, required this.type}); +// class Service extends Equatable { +// const Service({required this.state, required this.type}); - final StateType state; - final ServiceTypes type; +// final StateType state; +// final ServiceTypes type; - Service updateState(StateType newState) => Service( - state: newState, - type: type, - ); +// Service updateState(StateType newState) => Service( +// state: newState, +// type: type, +// ); - @override - List get props => [state, type]; -} +// @override +// List get props => [state, type]; +// } diff --git a/lib/ui/components/brand_icons/brand_icons.dart b/lib/ui/components/brand_icons/brand_icons.dart index 9cd84941..ba5cc19d 100644 --- a/lib/ui/components/brand_icons/brand_icons.dart +++ b/lib/ui/components/brand_icons/brand_icons.dart @@ -1,5 +1,5 @@ /// Flutter icons BrandIcons -/// Copyright (C) 2020 by original authors @ fluttericon.com, fontello.com +/// Copyright (C) 2021 by original authors @ fluttericon.com, fontello.com /// This font was generated by FlutterIcon.com, which is derived from Fontello. /// /// To use this font, place it in your fonts/ directory and include the @@ -19,7 +19,7 @@ class BrandIcons { BrandIcons._(); static const _kFontFam = 'BrandIcons'; - static const dynamic _kFontPkg = null; + static const String? _kFontPkg = null; static const IconData connection = IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); @@ -39,8 +39,14 @@ class BrandIcons { IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData check = IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData webcam = + IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData refresh = IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData git = + IconData(0xe80b, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData social = + IconData(0xe80c, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData settings = IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData share = @@ -59,8 +65,6 @@ class BrandIcons { IconData(0xe815, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData upload = IconData(0xe816, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData github = - IconData(0xe817, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData arrow_left = IconData(0xe818, fontFamily: _kFontFam, fontPackage: _kFontPkg); } diff --git a/lib/ui/components/brand_text/brand_text.dart b/lib/ui/components/brand_text/brand_text.dart index 3efde9e2..9dda8549 100644 --- a/lib/ui/components/brand_text/brand_text.dart +++ b/lib/ui/components/brand_text/brand_text.dart @@ -99,7 +99,7 @@ class BrandText extends StatelessWidget { ); @override Text build(BuildContext context) { - TextStyle? style; + TextStyle style; var isDark = Theme.of(context).brightness == Brightness.dark; switch (type) { diff --git a/lib/ui/components/not_ready_card/not_ready_card.dart b/lib/ui/components/not_ready_card/not_ready_card.dart index cd4a2fed..d1db3047 100644 --- a/lib/ui/components/not_ready_card/not_ready_card.dart +++ b/lib/ui/components/not_ready_card/not_ready_card.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_colors.dart'; +import 'package:selfprivacy/config/text_themes.dart'; import 'package:selfprivacy/ui/pages/initializing/initializing.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -21,19 +22,24 @@ class NotReadyCard extends StatelessWidget { style: TextStyle(color: BrandColors.white), ), WidgetSpan( - child: GestureDetector( - child: Text( - 'not_ready_card.2'.tr(), - style: TextStyle( + child: Padding( + padding: const EdgeInsets.only(bottom: 0.5), + child: GestureDetector( + onTap: () => Navigator.of(context).push( + materialRoute( + InitializingPage(), + ), + ), + child: Text( + 'not_ready_card.2'.tr(), + style: body1Style.copyWith( color: Theme.of(context).brightness == Brightness.dark - ? Colors.blueAccent + ? Colors.black : BrandColors.white, fontWeight: FontWeight.bold, - decoration: TextDecoration.underline), - ), - onTap: () => Navigator.of(context).push( - materialRoute( - InitializingPage(), + decoration: TextDecoration.underline, + // height: 1.1, + ), ), ), ), diff --git a/lib/ui/pages/initializing/initializing.dart b/lib/ui/pages/initializing/initializing.dart index 01d47c8e..5f39d543 100644 --- a/lib/ui/pages/initializing/initializing.dart +++ b/lib/ui/pages/initializing/initializing.dart @@ -20,6 +20,7 @@ import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart'; import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart'; import 'package:selfprivacy/ui/pages/rootRoute.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; +import 'package:easy_localization/easy_localization.dart'; class InitializingPage extends StatelessWidget { @override @@ -60,9 +61,9 @@ class InitializingPage extends StatelessWidget { 'Domain', 'User', 'Server', - 'Check1', - 'Check2', - 'Check3' + ' ✅', + ' ✅', + ' ✅' ], activeIndex: cubit.state.progress, ), @@ -76,8 +77,9 @@ class InitializingPage extends StatelessWidget { ), ), BrandButton.text( - title: - cubit.state.isFullyInitilized ? 'Close' : 'Настрою потом', + title: cubit.state.isFullyInitilized + ? 'basis.close'.tr() + : 'Настрою потом', onPressed: () { Navigator.of(context).pushAndRemoveUntil( materialRoute(RootPage()), @@ -105,10 +107,9 @@ class InitializingPage extends StatelessWidget { width: 150, ), SizedBox(height: 10), - BrandText.h2('Подключите сервер Hetzner'), + BrandText.h2('initializing.1'.tr()), SizedBox(height: 10), - BrandText.body2( - 'Здесь будут жить наши данные и SelfPrivacy-сервисы'), + BrandText.body2('initializing.2'.tr()), Spacer(), CubitFormTextField( formFieldCubit: context.read().apiKey, @@ -123,12 +124,12 @@ class InitializingPage extends StatelessWidget { onPressed: formCubitState.isSubmitting ? null : () => context.read().trySubmit(), - title: 'Подключить', + title: 'basis.connect'.tr(), ), SizedBox(height: 10), BrandButton.text( onPressed: () => _showModal(context, _HowHetzner()), - title: 'Как получить API Token', + title: 'initializing.how'.tr(), ), ], ); @@ -161,16 +162,16 @@ class InitializingPage extends StatelessWidget { width: 150, ), SizedBox(height: 10), - BrandText.h2('Подключите CloudFlare'), + BrandText.h2('initializing.3'.tr()), SizedBox(height: 10), - BrandText.body2('Для управления DNS вашего домена'), + BrandText.body2('initializing.4'.tr()), Spacer(), CubitFormTextField( formFieldCubit: context.read().apiKey, textAlign: TextAlign.center, scrollPadding: EdgeInsets.only(bottom: 70), decoration: InputDecoration( - hintText: 'CloudFlare API Token', + hintText: 'initializing.5'.tr(), ), ), Spacer(), @@ -178,12 +179,12 @@ class InitializingPage extends StatelessWidget { onPressed: formCubitState.isSubmitting ? null : () => context.read().trySubmit(), - title: 'Подключить', + title: 'basis.connect'.tr(), ), SizedBox(height: 10), BrandButton.text( onPressed: () {}, - title: 'Как получить API Token', + title: 'initializing.how'.tr(), ), ], ); @@ -204,7 +205,7 @@ class InitializingPage extends StatelessWidget { height: 50, ), SizedBox(height: 10), - BrandText.h2('Подключите облачное хранилище Backblaze'), + BrandText.h2('initializing.6'.tr()), SizedBox(height: 10), Spacer(), CubitFormTextField( @@ -229,12 +230,12 @@ class InitializingPage extends StatelessWidget { onPressed: formCubitState.isSubmitting ? null : () => context.read().trySubmit(), - title: 'Подключить', + title: 'basis.connect'.tr(), ), SizedBox(height: 10), BrandButton.text( onPressed: () => _showModal(context, _HowHetzner()), - title: 'Как получить API Token', + title: 'initializing.how'.tr(), ), ], ); @@ -255,19 +256,18 @@ class InitializingPage extends StatelessWidget { width: 150, ), SizedBox(height: 30), - BrandText.h2('Домен'), + BrandText.h2('basis.domain'.tr()), SizedBox(height: 10), - if (state is Empty) - BrandText.body2('На данный момент подлюченных доменов нет'), + if (state is Empty) BrandText.body2('initializing.7'.tr()), if (state is Loading) BrandText.body2( state.type == LoadingTypes.loadingDomain - ? 'Загружаем список доменов' - : 'Сохранение..', + ? 'initializing.8'.tr() + : 'basis.saving'.tr(), ), if (state is MoreThenOne) BrandText.body2( - 'Найдено больше одного домена, для вашей безопастности, просим вам удалить не нужные домены', + 'initializing.9'.tr(), ), if (state is Loaded) ...[ SizedBox(height: 10), @@ -322,7 +322,7 @@ class InitializingPage extends StatelessWidget { SizedBox(height: 30), BrandButton.rised( onPressed: () => context.read().saveDomain(), - title: 'Сохранить домен', + title: 'initializing.10'.tr(), ), ], SizedBox(height: 10), @@ -330,7 +330,7 @@ class InitializingPage extends StatelessWidget { SizedBox(height: 10), BrandButton.text( onPressed: () => _showModal(context, _HowHetzner()), - title: 'Как получить API Token', + title: 'initializing.how'.tr(), ), ], ); @@ -354,7 +354,7 @@ class InitializingPage extends StatelessWidget { textAlign: TextAlign.center, scrollPadding: EdgeInsets.only(bottom: 70), decoration: InputDecoration( - hintText: 'Никнейм', + hintText: 'basis.nickname'.tr(), ), ), SizedBox(height: 10), @@ -368,7 +368,7 @@ class InitializingPage extends StatelessWidget { textAlign: TextAlign.center, scrollPadding: EdgeInsets.only(bottom: 70), decoration: InputDecoration( - hintText: 'Пароль', + hintText: 'basis.password'.tr(), suffixIcon: IconButton( icon: Icon( isVisible ? Icons.visibility : Icons.visibility_off, @@ -390,12 +390,12 @@ class InitializingPage extends StatelessWidget { onPressed: formCubitState.isSubmitting ? null : () => context.read().trySubmit(), - title: 'Подключить', + title: 'basis.connect'.tr(), ), SizedBox(height: 10), BrandButton.text( onPressed: () => _showModal(context, _HowHetzner()), - title: 'Как получить API Token', + title: 'initializing.how'.tr(), ), ], ); @@ -410,19 +410,19 @@ class InitializingPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Spacer(flex: 2), - BrandText.h2('Создать сервер'), + BrandText.h2('initializing.how'.tr()), SizedBox(height: 10), - BrandText.body2('Создать сервер'), + BrandText.body2('initializing.11'.tr()), Spacer(), BrandButton.rised( onPressed: isLoading! ? null : appConfigCubit.createServerAndSetDnsRecords, - title: isLoading ? 'loading' : 'Создать сервер', + title: isLoading ? 'loading' : 'initializing.11'.tr(), ), Spacer(flex: 2), BrandButton.text( onPressed: () => _showModal(context, _HowHetzner()), - title: 'Что это значит?', + title: 'initializing.what'.tr(), ), ], ); @@ -435,11 +435,11 @@ class InitializingPage extends StatelessWidget { String? text; if (state.isServerReseted!) { - text = 'Сервер презагружен, ждем последнюю проверку'; + text = 'initializing.13'.tr(); } else if (state.isServerStarted!) { - text = 'Cервер запушен, сейчас он будет проверен и перезагружен'; + text = 'initializing.14'.tr(); } else if (state.isServerCreated) { - text = 'Cервер создан, идет проверка ДНС адресов и запуск сервера'; + text = 'initializing.15'.tr(); } return Builder(builder: (context) { return Column( @@ -452,20 +452,20 @@ class InitializingPage extends StatelessWidget { if (!state.isLoading!) Row( children: [ - BrandText.body2('До следующей проверки: '), + BrandText.body2('initializing.16'.tr()), BrandTimer( startDateTime: state.timerStart, duration: state.duration, ) ], ), - if (state.isLoading!) BrandText.body2('Проверка'), + if (state.isLoading!) BrandText.body2('initializing.17'.tr()), Spacer( flex: 2, ), BrandButton.text( onPressed: () => _showModal(context, _HowHetzner()), - title: 'Что это значит?', + title: 'initializing.what'.tr(), ), ], ); @@ -496,13 +496,13 @@ class _HowHetzner extends StatelessWidget { child: Column( children: [ SizedBox(height: 40), - BrandText.h2('Как получить Hetzner API Token'), + BrandText.h2('initializing.18'.tr()), SizedBox(height: 20), RichText( text: TextSpan( children: [ TextSpan( - text: '1 Переходим по ссылке ', + text: 'initializing.19'.tr(), style: body1Style.copyWith( color: isDark ? BrandColors.white : BrandColors.black, ), @@ -512,19 +512,7 @@ class _HowHetzner extends StatelessWidget { urlString: 'https://hetzner.com', ), TextSpan( - text: ''' - -2 Заходим в созданный нами проект. Если такового - нет, значит создаём. - -3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика). - -4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему. - -5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку. - -6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет. - - ''', + text: 'initializing.20'.tr(), style: body1Style.copyWith( color: isDark ? BrandColors.white : BrandColors.black, ), diff --git a/lib/ui/pages/onboarding/onboarding.dart b/lib/ui/pages/onboarding/onboarding.dart index 07790f60..c0418d15 100644 --- a/lib/ui/pages/onboarding/onboarding.dart +++ b/lib/ui/pages/onboarding/onboarding.dart @@ -58,7 +58,7 @@ class _OnboardingPageState extends State { 'onboarding.page1_title'.tr(), ), SizedBox(height: 20), - BrandText.body2('onboarding.page1_text'.tr()), + BrandText.body2('services.page1_text'.tr()), Flexible( child: Center( child: Image.asset( diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 16afa6d9..f4db4a53 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -24,8 +24,7 @@ class ProvidersPage extends StatefulWidget { class _ProvidersPageState extends State { @override Widget build(BuildContext context) { - // var isReady = context.watch().state.isFullyInitilized; - var isReady = true; + var isReady = context.watch().state.isFullyInitilized; final cards = ProviderType.values .map((type) => _Card( @@ -128,18 +127,45 @@ class _ProviderDetails extends StatelessWidget { @override Widget build(BuildContext context) { late String title; + late List children; + var config = context.watch().state; + + var domainName = config.isDomainFilled + ? config.cloudFlareDomain!.domainName! + : 'example.com'; switch (provider.type) { case ProviderType.server: title = 'providers.server.card_title'.tr(); + children = [ + BrandText.body1('providers.server.bottom_sheet.1'.tr()), + SizedBox(height: 10), + BrandText.body1('providers.server.bottom_sheet.2'.tr()), + SizedBox(height: 10), + BrandText.body1('providers.server.bottom_sheet.3'.tr()), + ]; break; case ProviderType.domain: title = 'providers.domain.card_title'.tr(); - + children = [ + BrandText.body1('providers.domain.bottom_sheet.1'.tr()), + SizedBox(height: 10), + BrandText.body1( + 'providers.domain.bottom_sheet.2'.tr(args: [domainName, 'Date'])), + SizedBox(height: 10), + BrandText.body1('providers.domain.bottom_sheet.3'.tr()), + ]; break; case ProviderType.backup: title = 'providers.backup.card_title'.tr(); - + children = [ + BrandText.body1('providers.backup.bottom_sheet.1'.tr()), + SizedBox(height: 10), + BrandText.body1( + 'providers.backup.bottom_sheet.2'.tr(args: [domainName, 'Time'])), + SizedBox(height: 10), + BrandText.body1('providers.backup.bottom_sheet.3'.tr()), + ]; break; } return BrandModalSheet( @@ -176,7 +202,7 @@ class _ProviderDetails extends StatelessWidget { value: _PopupMenuItemType.setting, child: Container( padding: EdgeInsets.only(left: 5), - child: Text('Настройки'), + child: Text('basis.settings'.tr()), ), ), ], @@ -184,11 +210,10 @@ class _ProviderDetails extends StatelessWidget { ), ), Padding( - padding: brandPagePadding1, + padding: brandPagePadding2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 13), IconStatusMask( status: provider.state, child: @@ -197,11 +222,7 @@ class _ProviderDetails extends StatelessWidget { SizedBox(height: 10), BrandText.h1(title), SizedBox(height: 10), - BrandText.body1(statusText), - SizedBox( - height: 20, - ), - Text('Статусы сервера и сервис провайдера и т.д.') + ...children ], ), ) diff --git a/lib/ui/pages/providers/settings/settings.dart b/lib/ui/pages/providers/settings/settings.dart index 48460786..e2e2294e 100644 --- a/lib/ui/pages/providers/settings/settings.dart +++ b/lib/ui/pages/providers/settings/settings.dart @@ -5,6 +5,7 @@ import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/switch_block/switch_bloc.dart'; +import 'package:easy_localization/easy_localization.dart'; class SettingsPage extends StatelessWidget { const SettingsPage({Key? key}) : super(key: key); @@ -15,7 +16,7 @@ class SettingsPage extends StatelessWidget { padding: brandPagePadding2, children: [ SizedBox(height: 10), - BrandHeader(title: 'Настройки', hasBackButton: true), + BrandHeader(title: 'basis.settings'.tr(), hasBackButton: true), BrandDivider(), SwitcherBlock( onChange: (_) {}, diff --git a/lib/ui/pages/rootRoute.dart b/lib/ui/pages/rootRoute.dart index f11dc737..426c3627 100644 --- a/lib/ui/pages/rootRoute.dart +++ b/lib/ui/pages/rootRoute.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart'; import 'package:selfprivacy/ui/pages/more/more.dart'; import 'package:selfprivacy/ui/pages/providers/providers.dart'; @@ -15,7 +16,7 @@ class RootPage extends StatefulWidget { class _RootPageState extends State with SingleTickerProviderStateMixin { - TabController? tabController; + late TabController tabController; @override void initState() { @@ -26,21 +27,24 @@ class _RootPageState extends State @override void dispose() { super.dispose(); - tabController!.dispose(); + tabController.dispose(); } @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( - body: TabBarView( - controller: tabController, - children: [ - ProvidersPage(), - ServicesPage(), - UsersPage(), - MorePage(), - ], + body: Provider( + create: (_) => ChangeTab(tabController.animateTo), + child: TabBarView( + controller: tabController, + children: [ + ProvidersPage(), + ServicesPage(), + UsersPage(), + MorePage(), + ], + ), ), bottomNavigationBar: BrandTabBar( controller: tabController, @@ -49,3 +53,9 @@ class _RootPageState extends State ); } } + +class ChangeTab { + final ValueChanged onPress; + + ChangeTab(this.onPress); +} diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index 704e8d25..ef2284a8 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -1,15 +1,21 @@ import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_theme.dart'; +import 'package:selfprivacy/config/text_themes.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; -import 'package:selfprivacy/logic/models/service.dart'; -import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; +import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/brand_card/brand_card.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; +import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/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/utils/route_transitions/basic.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../rootRoute.dart'; class ServicesPage extends StatefulWidget { ServicesPage({Key? key}) : super(key: key); @@ -21,28 +27,20 @@ class ServicesPage extends StatefulWidget { class _ServicesPageState extends State { @override Widget build(BuildContext context) { - final serviceCubitState = context.watch().state; - - final connected = serviceCubitState.connected; - final uninitialized = serviceCubitState.uninitialized; var isReady = context.watch().state.isFullyInitilized; return Scaffold( appBar: PreferredSize( - child: BrandHeader(title: 'Сервисы'), + child: BrandHeader(title: 'basis.services'.tr()), preferredSize: Size.fromHeight(52), ), body: ListView( padding: brandPagePadding2, children: [ - if (!isReady) NotReadyCard(), + BrandText.body1('services.title'.tr()), SizedBox(height: 24), - ...connected.map((service) => _Card(service: service)).toList(), - if (uninitialized.isNotEmpty) ...[ - BrandText.body1('не подключены'), - SizedBox(height: 30), - ], - ...uninitialized.map((service) => _Card(service: service)).toList() + if (!isReady) ...[NotReadyCard(), SizedBox(height: 24)], + ...ServiceTypes.values.map((t) => _Card(serviceType: t)).toList() ], ), ); @@ -50,67 +48,329 @@ class _ServicesPageState extends State { } class _Card extends StatelessWidget { - const _Card({Key? key, required this.service}) : super(key: key); + const _Card({Key? key, required this.serviceType}) : super(key: key); - final Service service; + final ServiceTypes serviceType; @override Widget build(BuildContext context) { - String? title; - IconData? iconData; - String? description; + String title; + IconData iconData; + String subtitle; - switch (service.type) { - case ServiceTypes.messanger: - iconData = BrandIcons.messanger; - title = 'Мессенджер'; - description = - 'Delta Chat. Если бы мне надо было обсудить что-то от чего зависит жизнь. Я бы выбрал Delta.Chat + свой почтовый сервер.'; - break; + switch (serviceType) { case ServiceTypes.mail: iconData = BrandIcons.envelope; - title = 'Почта'; - description = 'Электронная почта для семьи или компании '; + title = 'services.mail.title'.tr(); + subtitle = 'services.mail.subtitle'.tr(); + break; + case ServiceTypes.messenger: + iconData = BrandIcons.messanger; + title = 'services.messenger.title'.tr(); + subtitle = 'services.messenger.subtitle'.tr(); break; case ServiceTypes.passwordManager: iconData = BrandIcons.key; - title = 'Менеджер паролей'; - description = 'Надёжное хранилище для ваших паролей и ключей доступа'; + title = 'services.password_manager.title'.tr(); + subtitle = 'services.password_manager.subtitle'.tr(); break; - case ServiceTypes.github: - iconData = BrandIcons.github; - title = 'Git сервер'; - description = 'Сервис для приватного хранения своих разработок'; + case ServiceTypes.video: + iconData = BrandIcons.webcam; + title = 'services.video.title'.tr(); + subtitle = 'services.video.subtitle'.tr(); break; - case ServiceTypes.cloud: iconData = BrandIcons.upload; - title = 'Файловое Облако'; - description = 'Сервис для доступа к вашим файлам в любой точке мира'; + title = 'services.cloud.title'.tr(); + subtitle = 'services.cloud.subtitle'.tr(); + break; + case ServiceTypes.socialNetwork: + iconData = BrandIcons.social; + title = 'services.social_network.title'.tr(); + subtitle = 'services.social_network.subtitle'.tr(); + break; + case ServiceTypes.git: + iconData = BrandIcons.git; + title = 'services.git.title'.tr(); + subtitle = 'services.git.subtitle'.tr(); break; } - return BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - IconStatusMask( - status: service.state, - child: Icon(iconData, size: 30, color: Colors.white), - ), - SizedBox(height: 10), - BrandText.h2(title), - SizedBox(height: 10), - if (service.state == StateType.uninitialized) ...[ - BrandText.body1(description), + + var isReady = context.watch().state.isFullyInitilized; + var changeTab = context.read().onPress; + return GestureDetector( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return _ServiceDetails( + serviceType: serviceType, + status: isReady ? StateType.stable : StateType.uninitialized, + title: title, + icon: iconData, + changeTab: changeTab, + ); + }, + ), + child: BrandCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconStatusMask( + status: isReady ? StateType.stable : StateType.uninitialized, + child: Icon(iconData, size: 30, color: Colors.white), + ), + SizedBox(height: 10), + BrandText.h2(title), + SizedBox(height: 10), + BrandText.body2(subtitle), SizedBox(height: 10), - BrandButton.text( - title: 'Подключить', - onPressed: () { - context.read().connect(service); - }) ], - if (service.state == StateType.stable) BrandText.body2('Подключен'), - ], + ), ), ); } } + +enum ServiceTypes { + mail, + messenger, + passwordManager, + video, + cloud, + socialNetwork, + git, +} + +class _ServiceDetails extends StatelessWidget { + const _ServiceDetails({ + Key? key, + required this.serviceType, + required this.icon, + required this.status, + required this.title, + required this.changeTab, + }) : super(key: key); + + final ServiceTypes serviceType; + final IconData icon; + final StateType status; + final String title; + final ValueChanged changeTab; + + @override + Widget build(BuildContext context) { + late Widget child; + + var config = context.watch().state; + var domainName = config.isDomainFilled + ? config.cloudFlareDomain!.domainName! + : 'example.com'; + + var linksStyle = body1Style.copyWith( + fontSize: 15, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : BrandColors.black, + fontWeight: FontWeight.bold, + decoration: TextDecoration.underline, + // height: 1.1, + ); + + var textStyle = body1Style.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : BrandColors.black, + ); + switch (serviceType) { + case ServiceTypes.mail: + child = RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'services.mail.bottom_sheet.1'.tr(args: [domainName]), + style: textStyle, + ), + WidgetSpan( + child: Padding( + padding: EdgeInsets.only(bottom: 0.8, left: 5), + child: GestureDetector( + child: Text( + 'services.mail.bottom_sheet.2'.tr(), + style: linksStyle, + ), + onTap: () { + Navigator.of(context).pop(); + changeTab(2); + }, + ), + ), + ), + ], + )); + break; + case ServiceTypes.messenger: + child = RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'services.messenger.bottom_sheet.1'.tr(args: [domainName]), + style: textStyle, + ) + ], + )); + break; + case ServiceTypes.passwordManager: + child = RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'services.password_manager.bottom_sheet.1' + .tr(args: [domainName]), + style: textStyle, + ), + WidgetSpan( + child: Padding( + padding: EdgeInsets.only(bottom: 0.8, left: 5), + child: GestureDetector( + onTap: () => _launchURL('https://password.$domainName'), + child: Text( + 'password.$domainName', + style: linksStyle, + ), + ), + ), + ), + ], + )); + break; + case ServiceTypes.video: + child = RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'services.video.bottom_sheet.1'.tr(args: [domainName]), + style: textStyle, + ), + WidgetSpan( + child: Padding( + padding: EdgeInsets.only(bottom: 0.8, left: 5), + child: GestureDetector( + onTap: () => _launchURL('https://meet.$domainName'), + child: Text( + 'meet.$domainName', + style: linksStyle, + ), + ), + ), + ), + ], + )); + break; + case ServiceTypes.cloud: + child = RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'services.cloud.bottom_sheet.1'.tr(args: [domainName]), + style: textStyle, + ), + WidgetSpan( + child: Padding( + padding: EdgeInsets.only(bottom: 0.8, left: 5), + child: GestureDetector( + onTap: () => _launchURL('https://cloud.$domainName'), + child: Text( + 'cloud.$domainName', + style: linksStyle, + ), + ), + ), + ), + ], + )); + break; + case ServiceTypes.socialNetwork: + child = RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'services.social_network.bottom_sheet.1' + .tr(args: [domainName]), + style: textStyle, + ), + WidgetSpan( + child: Padding( + padding: EdgeInsets.only(bottom: 0.8, left: 5), + child: GestureDetector( + onTap: () => _launchURL('https://social_network.$domainName'), + child: Text( + 'social.$domainName', + style: linksStyle, + ), + ), + ), + ), + ], + )); + break; + case ServiceTypes.git: + child = RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'services.git.bottom_sheet.1'.tr(args: [domainName]), + style: textStyle, + ), + WidgetSpan( + child: Padding( + padding: EdgeInsets.only(bottom: 0.8, left: 5), + child: GestureDetector( + onTap: () => _launchURL('https://git.$domainName'), + child: Text( + 'git.$domainName', + style: linksStyle, + ), + ), + ), + ), + ], + )); + break; + } + return BrandModalSheet( + child: Navigator( + key: navigatorKey, + initialRoute: '/', + onGenerateRoute: (_) { + return materialRoute( + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: brandPagePadding1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 13), + IconStatusMask( + status: status, + child: Icon(icon, size: 40, color: Colors.white), + ), + SizedBox(height: 10), + BrandText.h1(title), + child, + ], + ), + ) + ], + ), + ); + }, + ), + ); + } + + void _launchURL(url) async => + await canLaunch(url) ? await launch(url) : throw 'Could not launch $url'; +} diff --git a/lib/ui/pages/users/empty.dart b/lib/ui/pages/users/empty.dart index 9a7c3688..e9623403 100644 --- a/lib/ui/pages/users/empty.dart +++ b/lib/ui/pages/users/empty.dart @@ -15,7 +15,7 @@ class _NoUsers extends StatelessWidget { Icon(BrandIcons.users, size: 50, color: BrandColors.grey7), SizedBox(height: 20), BrandText.h2( - 'Здесь пока никого', + 'users.nobody_here'.tr(), style: TextStyle( color: BrandColors.grey7, ), diff --git a/lib/ui/pages/users/new_user.dart b/lib/ui/pages/users/new_user.dart index 2255a1f5..53636e74 100644 --- a/lib/ui/pages/users/new_user.dart +++ b/lib/ui/pages/users/new_user.dart @@ -3,6 +3,12 @@ part of 'users.dart'; class _NewUser extends StatelessWidget { @override Widget build(BuildContext context) { + var config = context.watch().state; + + var domainName = config.isDomainFilled + ? config.cloudFlareDomain!.domainName! + : 'example.com'; + return BrandModalSheet( child: BlocProvider( create: (context) => @@ -19,7 +25,9 @@ class _NewUser extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - BrandHeader(title: 'Новый пользователь'), + BrandHeader( + title: 'users.new_user'.tr(), + ), SizedBox(width: 14), Padding( padding: brandPagePadding2, @@ -28,8 +36,8 @@ class _NewUser extends StatelessWidget { CubitFormTextField( formFieldCubit: context.read().login, decoration: InputDecoration( - labelText: 'Логин', - suffixText: '@example', + labelText: 'users.login'.tr(), + suffixText: '@$domainName', ), ), SizedBox(height: 20), @@ -37,7 +45,7 @@ class _NewUser extends StatelessWidget { formFieldCubit: context.read().password, decoration: InputDecoration( alignLabelWithHint: false, - labelText: 'Пароль', + labelText: 'basis.password'.tr(), suffixIcon: Padding( padding: const EdgeInsets.only(right: 8), child: IconButton( @@ -56,11 +64,10 @@ class _NewUser extends StatelessWidget { onPressed: formCubitState.isSubmitting ? null : () => context.read().trySubmit(), - title: 'Создать', + title: 'basis.create'.tr(), ), SizedBox(height: 40), - Text( - 'Новый пользователь автоматически получит доступ ко всем сервисам. Ещё какое-то описание.'), + Text('users.new_user_info_note'.tr()), SizedBox(height: 30), ], ), diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index ceafcbd2..99f43315 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -10,6 +10,12 @@ class _UserDetails extends StatelessWidget { @override Widget build(BuildContext context) { + var config = context.watch().state; + + var domainName = config.isDomainFilled + ? config.cloudFlareDomain!.domainName! + : 'example.com'; + return BrandModalSheet( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -45,24 +51,25 @@ class _UserDetails extends StatelessWidget { context: context, builder: (context) { return AlertDialog( - title: Text('Подтверждение '), + title: Text('basis.confirmation'.tr()), content: SingleChildScrollView( child: ListBody( children: [ - Text('удалить учетную запись?'), + Text('users.delete_confirm_question' + .tr()), ], ), ), actions: [ TextButton( - child: Text('Отменить'), + child: Text('basis.cancel'.tr()), onPressed: () { Navigator.of(context)..pop(); }, ), TextButton( child: Text( - 'Удалить', + 'basis.delete'.tr(), style: TextStyle( color: BrandColors.red1, ), @@ -85,7 +92,7 @@ class _UserDetails extends StatelessWidget { value: PopupMenuItemType.reset, child: Container( padding: EdgeInsets.only(left: 5), - child: Text('Сбросить пароль'), + child: Text('users.reset_password'.tr()), ), ), PopupMenuItem( @@ -93,7 +100,7 @@ class _UserDetails extends StatelessWidget { child: Container( padding: EdgeInsets.only(left: 5), child: Text( - 'Удалить', + 'basis.delete'.tr(), style: TextStyle(color: BrandColors.red1), ), ), @@ -122,14 +129,14 @@ class _UserDetails extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - BrandText.small('Учетная запись'), + BrandText.small('users.account'.tr()), Container( height: 40, alignment: Alignment.centerLeft, - child: BrandText.h4('${user!.login}@example.com'), + child: BrandText.h4('${user!.login}@$domainName'), ), SizedBox(height: 14), - BrandText.small('Пароль'), + BrandText.small('basis.password'.tr()), Container( height: 40, alignment: Alignment.centerLeft, @@ -139,15 +146,11 @@ class _UserDetails extends StatelessWidget { BrandDivider(), SizedBox(height: 20), BrandButton.iconText( - title: 'Отправить реквизиты для входа', + title: 'users.send_regisration_data'.tr(), icon: Icon(BrandIcons.share), onPressed: () {}, ), SizedBox(height: 20), - BrandDivider(), - SizedBox(height: 20), - Text( - 'Вам был создан доступ к сервисам с логином и паролем к сервисам:- E-mail с адресом - Менеджер паролей: - Файловое облако: - Видеоконференция - Git сервер '), ], ), ) diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index 003d7768..8acb4016 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -13,6 +13,7 @@ import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; +import 'package:easy_localization/easy_localization.dart'; part 'fab.dart'; part 'new_user.dart'; @@ -39,7 +40,7 @@ class UsersPage extends StatelessWidget { ? Container( alignment: Alignment.center, child: _NoUsers( - text: 'Добавьте первого пользователя', + text: 'users.add_new_user'.tr(), ), ) : ListView( @@ -51,7 +52,7 @@ class UsersPage extends StatelessWidget { return Scaffold( appBar: PreferredSize( - child: BrandHeader(title: 'Пользователи'), + child: BrandHeader(title: 'basis.users'.tr()), preferredSize: Size.fromHeight(52), ), floatingActionButton: isReady ? _Fab() : null, @@ -72,8 +73,7 @@ class UsersPage extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 15), child: Center( child: _NoUsers( - text: - 'Подключите сервер, домен и DNS в разеде Провайдеры, чтобы добавить первого пользователя', + text: 'users.not_ready'.tr(), ), ), ), diff --git a/pubspec.lock b/pubspec.lock index 26040d5a..bb1f548e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -329,13 +329,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" - google_fonts: - dependency: "direct main" - description: - name: google_fonts - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" graphs: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0e312fd3..215913e7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,6 @@ dependencies: flutter_bloc: ^7.0.0-nullsafety.5 flutter_secure_storage: ^4.1.0 get_it: ^6.0.0 - google_fonts: ^2.0.0 hive: ^2.0.0 hive_flutter: ^1.0.0 json_annotation: ^4.0.0 @@ -54,3 +53,15 @@ flutter: - family: BrandIcons fonts: - asset: assets/fonts/BrandIcons.ttf + - family: Inter + fonts: + - asset: assets/fonts/Inter-Regular.ttf + - asset: assets/fonts/Inter-Medium.ttf + weight: 500 + - asset: assets/fonts/Inter-SemiBold.ttf + weight: 600 + - asset: assets/fonts/Inter-Bold.ttf + weight: 700 + - asset: assets/fonts/Inter-ExtraBold.ttf + weight: 800 + \ No newline at end of file