diff --git a/assets/fonts/BrandIcons.ttf b/assets/fonts/BrandIcons.ttf index 404489f3..4d4e0f28 100644 Binary files a/assets/fonts/BrandIcons.ttf and b/assets/fonts/BrandIcons.ttf differ diff --git a/assets/markdown/about-en.md b/assets/markdown/about-en.md new file mode 100644 index 00000000..3ee32766 --- /dev/null +++ b/assets/markdown/about-en.md @@ -0,0 +1,12 @@ +### О проекте + +Всё больше организаций хотят владеть нашими данными +А мы сами хотим распоряжаться своими **данными** на своем сервере. + +### Миссия проекта + +Цифровая независимость и приватность доступная каждому + +### Цель + +Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких \ No newline at end of file diff --git a/assets/markdown/about-ru.md b/assets/markdown/about-ru.md new file mode 100644 index 00000000..3ee32766 --- /dev/null +++ b/assets/markdown/about-ru.md @@ -0,0 +1,12 @@ +### О проекте + +Всё больше организаций хотят владеть нашими данными +А мы сами хотим распоряжаться своими **данными** на своем сервере. + +### Миссия проекта + +Цифровая независимость и приватность доступная каждому + +### Цель + +Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких \ No newline at end of file diff --git a/assets/markdown/how_hetzner-en.md b/assets/markdown/how_hetzner-en.md new file mode 100644 index 00000000..4c0c344b --- /dev/null +++ b/assets/markdown/how_hetzner-en.md @@ -0,0 +1,7 @@ +### Как получить Hetzner API Token +1. Переходим по ссылке https://hetzner.com +2. Заходим в созданный нами проект. Если такового - нет, значит создаём. +3. Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика). +4. Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему. +5. В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку. +6. В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет. \ No newline at end of file diff --git a/assets/markdown/how_hetzner-ru.md b/assets/markdown/how_hetzner-ru.md new file mode 100644 index 00000000..4c0c344b --- /dev/null +++ b/assets/markdown/how_hetzner-ru.md @@ -0,0 +1,7 @@ +### Как получить Hetzner API Token +1. Переходим по ссылке https://hetzner.com +2. Заходим в созданный нами проект. Если такового - нет, значит создаём. +3. Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика). +4. Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему. +5. В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку. +6. В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет. \ No newline at end of file diff --git a/assets/translations/en.json b/assets/translations/en.json index c1ca5eee..c8562456 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,5 +1,6 @@ { "test": "en-test", + "locale": "en", "basis": { "_comment": "Basic interface elements", "providers": "Providers", @@ -28,7 +29,10 @@ "about_project": "About us", "about_app": "About application", "onboarding": "Onboarding", - "console": "Console" + "console": "Console", + "about_app_page": { + "text": "Тут любая служебная информация, v.{}" + } }, "onboarding": { "_comment": "Onboarding pages", @@ -162,6 +166,6 @@ "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, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет." + "20": "\n" } } \ No newline at end of file diff --git a/assets/translations/ru.json b/assets/translations/ru.json index c26efa35..d3b19c1b 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -1,5 +1,6 @@ { "test": "ru-test", + "locale": "ru", "basis": { "_comment": "базовые элементы интерфейса", "providers": "Провайдеры", @@ -28,7 +29,10 @@ "about_project": "О проекте SelfPrivacy", "about_app": "О приложении", "onboarding": "Onboarding", - "console": "Console" + "console": "Console", + "about_app_page": { + "text": "Тут любая служебная информация, v.{}" + } }, "onboarding": { "_comment": "страницы онбординга", diff --git a/lib/config/md_files.dart b/lib/config/md_files.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/ui/components/brand_icons/brand_icons.dart b/lib/ui/components/brand_icons/brand_icons.dart index ba5cc19d..b66f8bf6 100644 --- a/lib/ui/components/brand_icons/brand_icons.dart +++ b/lib/ui/components/brand_icons/brand_icons.dart @@ -53,6 +53,8 @@ class BrandIcons { IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData triangle = IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData engineer = + IconData(0xe810, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData server = IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData box = @@ -67,4 +69,14 @@ class BrandIcons { IconData(0xe816, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData arrow_left = IconData(0xe818, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData shape = + IconData(0xe819, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData keyhole = + IconData(0xe81a, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData terminal = + IconData(0xe81b, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData fire = + IconData(0xe81c, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData start = + IconData(0xe81d, fontFamily: _kFontFam, fontPackage: _kFontPkg); } diff --git a/lib/ui/components/brand_md/brand_md.dart b/lib/ui/components/brand_md/brand_md.dart new file mode 100644 index 00000000..75985286 --- /dev/null +++ b/lib/ui/components/brand_md/brand_md.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter/services.dart' show rootBundle; +import 'package:easy_localization/easy_localization.dart'; +import 'package:selfprivacy/config/brand_colors.dart'; +import 'package:selfprivacy/config/text_themes.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class BrandMarkdown extends StatefulWidget { + const BrandMarkdown({ + Key? key, + required this.fileName, + }) : super(key: key); + + final String fileName; + + @override + _BrandMarkdownState createState() => _BrandMarkdownState(); +} + +class _BrandMarkdownState extends State { + String _mdContent = ''; + + @override + void initState() { + super.initState(); + _loadMdFile(); + } + + void _loadMdFile() async { + String mdFromFile = await rootBundle + .loadString('assets/markdown/${widget.fileName}-${'locale'.tr()}.md'); + setState(() { + _mdContent = mdFromFile; + }); + } + + @override + Widget build(BuildContext context) { + var isDark = Theme.of(context).brightness == Brightness.dark; + var markdown = MarkdownStyleSheet( + p: defaultTextStyle, + h1: headline1Style.copyWith( + color: isDark ? BrandColors.white : null, + ), + h2: headline2Style.copyWith( + color: isDark ? BrandColors.white : null, + ), + h3: headline3Style.copyWith( + color: isDark ? BrandColors.white : null, + ), + h4: headline4Style.copyWith( + color: isDark ? BrandColors.white : null, + ), + ); + return Markdown( + styleSheet: markdown, + onTapLink: (String text, String? href, String title) { + if (href != null) { + canLaunch(href).then((canLaunchURL) { + if (canLaunchURL) { + launch(href); + } + }); + } + }, + data: _mdContent, + ); + } +} diff --git a/lib/ui/pages/initializing/initializing.dart b/lib/ui/pages/initializing/initializing.dart index 5f39d543..4375f422 100644 --- a/lib/ui/pages/initializing/initializing.dart +++ b/lib/ui/pages/initializing/initializing.dart @@ -1,9 +1,7 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/config/text_themes.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/backblaze_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/cloudflare_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/domain_cloudflare.dart'; @@ -13,8 +11,8 @@ import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_card/brand_card.dart'; +import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; -import 'package:selfprivacy/ui/components/brand_span_button/brand_span_button.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart'; import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart'; @@ -488,41 +486,12 @@ class _HowHetzner extends StatelessWidget { @override Widget build(BuildContext context) { - var isDark = Theme.of(context).brightness == Brightness.dark; - return BrandModalSheet( child: Padding( - padding: brandPagePadding2, - child: Column( - children: [ - SizedBox(height: 40), - BrandText.h2('initializing.18'.tr()), - SizedBox(height: 20), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: 'initializing.19'.tr(), - style: body1Style.copyWith( - color: isDark ? BrandColors.white : BrandColors.black, - ), - ), - BrandSpanButton.link( - text: 'hetzner.com', - urlString: 'https://hetzner.com', - ), - TextSpan( - text: 'initializing.20'.tr(), - style: body1Style.copyWith( - color: isDark ? BrandColors.white : BrandColors.black, - ), - ), - ], - ), - ), - ], - ), - ), + padding: brandPagePadding1, + child: BrandMarkdown( + fileName: 'how_hetzner', + )), ); } } diff --git a/lib/ui/pages/more/about/about.dart b/lib/ui/pages/more/about/about.dart index ac31338f..b927c785 100644 --- a/lib/ui/pages/more/about/about.dart +++ b/lib/ui/pages/more/about/about.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; -import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; class AboutPage extends StatelessWidget { const AboutPage({Key? key}) : super(key: key); @@ -12,37 +11,14 @@ class AboutPage extends StatelessWidget { return SafeArea( child: Scaffold( appBar: PreferredSize( - child: BrandHeader(title: 'О проекте', hasBackButton: true), + child: BrandHeader( + title: 'more.about_project'.tr(), hasBackButton: true), preferredSize: Size.fromHeight(52), ), - body: ListView( - padding: brandPagePadding2, - children: [ - BrandDivider(), - SizedBox(height: 20), - BrandText.h3('О проекте'), - SizedBox(height: 10), - BrandText.body1( - 'Всё больше организаций хотят владеть нашими данными'), - SizedBox(height: 10), - BrandText.body1( - 'А мы сами хотим распоряжаться своими данными на своем сервере.'), - SizedBox(height: 20), - BrandDivider(), - SizedBox(height: 10), - BrandText.h3('Миссия проекта'), - SizedBox(height: 10), - BrandText.body1( - 'Цифровая независимость и приватность доступная каждому'), - SizedBox(height: 20), - BrandDivider(), - SizedBox(height: 10), - BrandText.h3('Цель'), - SizedBox(height: 10), - BrandText.body1( - 'Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких'), - SizedBox(height: 10), - ], + body: Container( + child: BrandMarkdown( + fileName: 'about', + ), ), ), ); diff --git a/lib/ui/pages/more/app_settings/app_setting.dart b/lib/ui/pages/more/app_settings/app_setting.dart index 279b6443..8d3d33c1 100644 --- a/lib/ui/pages/more/app_settings/app_setting.dart +++ b/lib/ui/pages/more/app_settings/app_setting.dart @@ -9,6 +9,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/utils/named_font_weight.dart'; +import 'package:easy_localization/easy_localization.dart'; class AppSettingsPage extends StatefulWidget { const AppSettingsPage({Key? key}) : super(key: key); @@ -27,7 +28,7 @@ class _AppSettingsPageState extends State { return Scaffold( appBar: PreferredSize( child: - BrandHeader(title: 'Настройки приложения', hasBackButton: true), + BrandHeader(title: 'more.settings'.tr(), hasBackButton: true), preferredSize: Size.fromHeight(52), ), body: ListView( diff --git a/lib/ui/pages/more/info/info.dart b/lib/ui/pages/more/info/info.dart index 21906e5d..1ed6aea4 100644 --- a/lib/ui/pages/more/info/info.dart +++ b/lib/ui/pages/more/info/info.dart @@ -4,6 +4,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:package_info/package_info.dart'; +import 'package:easy_localization/easy_localization.dart'; class InfoPage extends StatelessWidget { const InfoPage({Key? key}) : super(key: key); @@ -13,7 +14,7 @@ class InfoPage extends StatelessWidget { return SafeArea( child: Scaffold( appBar: PreferredSize( - child: BrandHeader(title: 'О приложении', hasBackButton: true), + child: BrandHeader(title: 'more.about_app'.tr(), hasBackButton: true), preferredSize: Size.fromHeight(52), ), body: ListView( @@ -24,8 +25,8 @@ class InfoPage extends StatelessWidget { FutureBuilder( future: _version(), builder: (context, snapshot) { - return BrandText.body1( - 'Тут любая служебная информация, v.${snapshot.data}'); + return BrandText.body1('more.about_app_page.text' + .tr(args: [snapshot.data.toString()])); }), ], ), diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index acd7bb0a..8959d87a 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.dart @@ -35,7 +35,7 @@ class MorePage extends StatelessWidget { BrandDivider(), _NavItem( title: 'more.configuration_wizard'.tr(), - iconData: BrandIcons.settings, + iconData: BrandIcons.triangle, goTo: InitializingPage(), ), _NavItem( @@ -45,22 +45,22 @@ class MorePage extends StatelessWidget { ), _NavItem( title: 'more.about_project'.tr(), - iconData: BrandIcons.triangle, + iconData: BrandIcons.engineer, goTo: AboutPage(), ), _NavItem( title: 'more.about_app'.tr(), - iconData: BrandIcons.help, + iconData: BrandIcons.fire, goTo: InfoPage(), ), _NavItem( title: 'more.onboarding'.tr(), - iconData: BrandIcons.triangle, + iconData: BrandIcons.start, goTo: OnboardingPage(nextPage: RootPage()), ), _NavItem( title: 'more.console'.tr(), - iconData: BrandIcons.triangle, + iconData: BrandIcons.terminal, goTo: Console(), ), ], diff --git a/lib/ui/pages/onboarding/onboarding.dart b/lib/ui/pages/onboarding/onboarding.dart index c0418d15..07790f60 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('services.page1_text'.tr()), + BrandText.body2('onboarding.page1_text'.tr()), Flexible( child: Center( child: Image.asset( diff --git a/pubspec.lock b/pubspec.lock index bb1f548e..a7267e00 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -298,6 +298,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.0" flutter_secure_storage: dependency: "direct main" description: @@ -420,6 +427,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + markdown: + dependency: transitive + description: + name: markdown + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" mask_text_input_formatter: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 215913e7..b891badd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: either_option: ^2.0.1-dev.1 equatable: ^2.0.0 flutter_bloc: ^7.0.0-nullsafety.5 + flutter_markdown: ^0.6.0 flutter_secure_storage: ^4.1.0 get_it: ^6.0.0 hive: ^2.0.0 @@ -49,6 +50,7 @@ flutter: - assets/images/onboarding/ - assets/images/logos/ - assets/translations/ + - assets/markdown/ fonts: - family: BrandIcons fonts: @@ -64,4 +66,3 @@ flutter: weight: 700 - asset: assets/fonts/Inter-ExtraBold.ttf weight: 800 - \ No newline at end of file