diff --git a/assets/fonts/BrandIcons.ttf b/assets/fonts/BrandIcons.ttf index d902699b..3a5b43e2 100644 Binary files a/assets/fonts/BrandIcons.ttf and b/assets/fonts/BrandIcons.ttf differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 055f6066..0633658f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,5 +1,7 @@ PODS: - Flutter (1.0.0) + - package_info (0.0.1): + - Flutter - path_provider (0.0.1): - Flutter - shared_preferences (0.0.1): @@ -9,6 +11,7 @@ PODS: DEPENDENCIES: - Flutter (from `Flutter`) + - package_info (from `.symlinks/plugins/package_info/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`) @@ -16,6 +19,8 @@ DEPENDENCIES: EXTERNAL SOURCES: Flutter: :path: Flutter + package_info: + :path: ".symlinks/plugins/package_info/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" shared_preferences: @@ -25,6 +30,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef diff --git a/lib/config/brand_colors.dart b/lib/config/brand_colors.dart index 3b7ca03c..25f5e70c 100644 --- a/lib/config/brand_colors.dart +++ b/lib/config/brand_colors.dart @@ -18,6 +18,18 @@ class BrandColors { /// ![](https://www.colorhexa.com/DDDDDD.png) static const Color gray4 = Color(0xFFDDDDDD); + /// ![](https://www.colorhexa.com/EDEEF1.png) + static const Color gray5 = Color(0xFFEDEEF1); + + /// ![](https://www.colorhexa.com/FA0E0E.png) + static const Color red = Color(0xFFFA0E0E); + + /// ![](https://www.colorhexa.com/00AF54.png) + static const Color green1 = Color(0xFF00AF54); + + /// ![](https://www.colorhexa.com/0F8849.png) + static const Color green2 = Color(0xFF0F8849); + static const primary = blue; static const headlineColor = black; static const inactive = gray2; @@ -26,6 +38,8 @@ class BrandColors { static const textColor1 = black; static const textColor2 = gray1; + static const dividerColor = gray5; + static const warning = red; static get navBackground => white.withOpacity(0.8); diff --git a/lib/config/brand_theme.dart b/lib/config/brand_theme.dart index 7c8458a3..0be8b325 100644 --- a/lib/config/brand_theme.dart +++ b/lib/config/brand_theme.dart @@ -18,4 +18,6 @@ final theme = ThemeData( ), ); -final brandPagePadding = EdgeInsets.symmetric(horizontal: 15, vertical: 30); +final brandPagePadding1 = EdgeInsets.symmetric(horizontal: 15, vertical: 30); + +final brandPagePadding2 = EdgeInsets.symmetric(horizontal: 15); diff --git a/lib/config/text_themes.dart b/lib/config/text_themes.dart index 4a3c0c84..3a6a188b 100644 --- a/lib/config/text_themes.dart +++ b/lib/config/text_themes.dart @@ -23,6 +23,12 @@ final headline2Style = GoogleFonts.inter( color: BrandColors.headlineColor, ); +final headline3Style = GoogleFonts.inter( + fontSize: 20, + fontWeight: NamedFontWeight.extraBold, + color: BrandColors.headlineColor, +); + final captionStyle = GoogleFonts.inter( fontSize: 18, fontWeight: NamedFontWeight.medium, diff --git a/lib/ui/components/brand_divider/brand_divider.dart b/lib/ui/components/brand_divider/brand_divider.dart new file mode 100644 index 00000000..00bd0ebf --- /dev/null +++ b/lib/ui/components/brand_divider/brand_divider.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/brand_colors.dart'; + +class BrandDivider extends StatelessWidget { + const BrandDivider({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + height: 1, + color: BrandColors.dividerColor, + ); + } +} + diff --git a/lib/ui/components/brand_header/brand_header.dart b/lib/ui/components/brand_header/brand_header.dart new file mode 100644 index 00000000..52a82010 --- /dev/null +++ b/lib/ui/components/brand_header/brand_header.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; +import 'package:selfprivacy/utils/extensions/text_extension.dart'; + +class BrandHeader extends StatelessWidget { + const BrandHeader({ + Key key, + @required this.title, + this.hasBackButton = false, + }) : super(key: key); + + final String title; + final bool hasBackButton; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only( + top: hasBackButton ? 4 : 17, + bottom: hasBackButton ? 7 : 20, + left: hasBackButton ? 1 : 15, + ), + child: Container( + child: Row( + children: [ + if (hasBackButton) ...[ + IconButton( + icon: Icon(BrandIcons.arrow_left), + onPressed: () => Navigator.of(context).pop(), + ), + SizedBox(width: 10), + ], + Text(title).caption, + ], + ), + ), + ); + } +} diff --git a/lib/ui/components/brand_icons/brand_icons.dart b/lib/ui/components/brand_icons/brand_icons.dart index 72be2f90..a6d3db1f 100644 --- a/lib/ui/components/brand_icons/brand_icons.dart +++ b/lib/ui/components/brand_icons/brand_icons.dart @@ -57,4 +57,6 @@ class BrandIcons { 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/pages/about/about.dart b/lib/ui/pages/about/about.dart new file mode 100644 index 00000000..2c61ea0b --- /dev/null +++ b/lib/ui/pages/about/about.dart @@ -0,0 +1,56 @@ +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/utils/extensions/text_extension.dart'; + +class AboutPage extends StatelessWidget { + const AboutPage({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: ListView( + children: [ + BrandHeader(title: 'О проекте', hasBackButton: true), + Padding( + padding: brandPagePadding2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BrandDivider(), + SizedBox(height: 20), + Text('О проекте').h3, + SizedBox(height: 10), + Text('Всё больше организаций хотят владеть нашими данными') + .body1, + SizedBox(height: 10), + Text('А мы сами хотим распоряжаться своими данными на своем сервере.') + .body1, + SizedBox(height: 20), + BrandDivider(), + SizedBox(height: 10), + Text('Миссия проекта').h3, + SizedBox(height: 10), + Text( + 'Цифровая независимость и приватность доступная каждому'), + SizedBox(height: 20), + BrandDivider(), + SizedBox(height: 10), + Text('Цель').h3, + SizedBox(height: 10), + Text( + 'Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких'), + SizedBox(height: 10), + Text( + 'Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких'), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/ui/pages/info/info.dart b/lib/ui/pages/info/info.dart new file mode 100644 index 00000000..6a897995 --- /dev/null +++ b/lib/ui/pages/info/info.dart @@ -0,0 +1,46 @@ +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/utils/extensions/text_extension.dart'; +import 'package:package_info/package_info.dart'; + +class InfoPage extends StatelessWidget { + const InfoPage({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: ListView( + children: [ + BrandHeader(title: 'О приложении', hasBackButton: true), + Padding( + padding: brandPagePadding2, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + BrandDivider(), + SizedBox(height: 10), + FutureBuilder( + future: _version(), + builder: (context, snapshot) { + return Text( + 'Тут любая служебная информация, v.${snapshot.data}') + .body1; + }), + ], + ), + ), + ], + ), + ), + ); + } + + Future _version() async { + var packageInfo = await PackageInfo.fromPlatform(); + return packageInfo.version; + } +} diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart new file mode 100644 index 00000000..2ded231c --- /dev/null +++ b/lib/ui/pages/more/more.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/brand_colors.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_icons/brand_icons.dart'; +import 'package:selfprivacy/ui/pages/about/about.dart'; +import 'package:selfprivacy/ui/pages/info/info.dart'; +import 'package:selfprivacy/ui/pages/settings/setting.dart'; +import 'package:selfprivacy/utils/extensions/text_extension.dart'; +import 'package:selfprivacy/utils/route_transitions/basic.dart'; + +class MorePage extends StatelessWidget { + const MorePage({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListView( + children: [ + BrandHeader(title: 'Еще'), + Padding( + padding: brandPagePadding2, + child: Column( + children: [ + BrandDivider(), + _NavItem( + title: 'Настройки', + iconData: BrandIcons.settings, + goTo: SettingsPage(), + ), + _NavItem( + title: 'О проекте Selfprivacy', + iconData: BrandIcons.triangle, + goTo: AboutPage(), + ), + _NavItem( + title: 'О приложении', + iconData: BrandIcons.help, + goTo: InfoPage(), + ), + ], + ), + ) + ], + ); + } +} + +class _NavItem extends StatelessWidget { + const _NavItem({ + Key key, + @required this.iconData, + @required this.goTo, + @required this.title, + }) : super(key: key); + + final IconData iconData; + final Widget goTo; + final String title; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => Navigator.of(context).push(materialRoute(goTo)), + child: Container( + padding: EdgeInsets.symmetric(vertical: 24), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: BrandColors.dividerColor, + ), + ), + ), + child: Row( + children: [ + Text(title).body1, + Spacer(), + Icon(iconData, size: 20), + SizedBox(width: 18), + ], + ), + ), + ); + } +} diff --git a/lib/ui/pages/onboarding/onboarding.dart b/lib/ui/pages/onboarding/onboarding.dart index 0838ba65..6b72eb7a 100644 --- a/lib/ui/pages/onboarding/onboarding.dart +++ b/lib/ui/pages/onboarding/onboarding.dart @@ -39,7 +39,8 @@ class OnboardingPage extends StatelessWidget { ), BrandButton.rised( onPressed: () { - Navigator.of(context).push(materialRoute(RootPage())); + Navigator.of(context) + .pushReplacement(materialRoute(RootPage())); }, title: 'Приступим!', ) diff --git a/lib/ui/pages/rootRoute.dart b/lib/ui/pages/rootRoute.dart index 0851c0d2..514b34fc 100644 --- a/lib/ui/pages/rootRoute.dart +++ b/lib/ui/pages/rootRoute.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.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/servers/servers.dart'; import 'package:selfprivacy/ui/pages/services/services.dart'; @@ -37,7 +38,7 @@ class _RootPageState extends State ServersPage(), ServicesPage(), Text('users'), - Text('more'), + MorePage(), ], ), bottomNavigationBar: BottomTabBar( diff --git a/lib/ui/pages/servers/servers.dart b/lib/ui/pages/servers/servers.dart index 486106b7..dd182e25 100644 --- a/lib/ui/pages/servers/servers.dart +++ b/lib/ui/pages/servers/servers.dart @@ -16,7 +16,7 @@ class ServersPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: ListView( - padding: brandPagePadding, + padding: brandPagePadding1, children: [ Text('Начало').caption, Text('SelfPrivacy').h1, diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index a60f5a93..20e612c1 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -23,7 +23,7 @@ class _ServicesPageState extends State { final uninitialized = serviceCubit.state.uninitialized; return Scaffold( body: ListView( - padding: brandPagePadding, + padding: brandPagePadding1, children: [ Text('Сервисы').caption, SizedBox(height: 24), diff --git a/lib/ui/pages/settings/setting.dart b/lib/ui/pages/settings/setting.dart new file mode 100644 index 00000000..a5010edf --- /dev/null +++ b/lib/ui/pages/settings/setting.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/brand_colors.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/utils/extensions/text_extension.dart'; + +class SettingsPage extends StatelessWidget { + const SettingsPage({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: ListView( + children: [ + BrandHeader(title: 'Настройки', hasBackButton: true), + Padding( + padding: brandPagePadding2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + BrandDivider(), + _SwitcherBlock( + child: _TextColumn( + title: 'Allow Auto-upgrade', + value: 'Wether to allow automatic packages upgrades', + ), + isActive: true, + ), + _SwitcherBlock( + child: _TextColumn( + title: 'Reboot after upgrade', + value: 'Reboot without prompt after applying updates', + ), + isActive: false, + ), + _Button( + onTap: () {}, + child: _TextColumn( + title: 'Server Timezone', + value: 'Europe/Kyiv', + ), + ), + _Button( + onTap: () {}, + child: _TextColumn( + title: 'Server Locale', + value: 'Default', + ), + ), + _Button( + onTap: () {}, + child: _TextColumn( + hasWarning: true, + title: 'Factory Reset', + value: 'Restore default settings on your server', + ), + ) + ], + ), + ) + ], + ), + ), + ); + } +} + +class _SwitcherBlock extends StatelessWidget { + const _SwitcherBlock({ + Key key, + @required this.child, + @required this.isActive, + }) : super(key: key); + + final Widget child; + final bool isActive; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(top: 20, bottom: 5), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(width: 1, color: BrandColors.dividerColor), + )), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible(child: child), + SizedBox(width: 5), + Switch( + activeColor: BrandColors.green1, + activeTrackColor: BrandColors.green2, + onChanged: (v) {}, + value: isActive, + ), + ], + ), + ); + } +} + +class _Button extends StatelessWidget { + const _Button({ + Key key, + @required this.onTap, + @required this.child, + }) : super(key: key); + + final Widget child; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Container( + padding: EdgeInsets.only(top: 20, bottom: 5), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(width: 1, color: BrandColors.dividerColor), + )), + child: child, + ), + ); + } +} + +class _TextColumn extends StatelessWidget { + const _TextColumn({ + Key key, + @required this.title, + @required this.value, + this.hasWarning = false, + }) : super(key: key); + + final String title; + final String value; + final bool hasWarning; + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title).body1.copyWith( + style: TextStyle(color: hasWarning ? BrandColors.warning : null)), + SizedBox(height: 5), + Text(value) + .body1 + .copyWith( + style: TextStyle( + fontSize: 13, + height: 1.53, + color: BrandColors.gray1, + ), + ) + .copyWith( + style: + TextStyle(color: hasWarning ? BrandColors.warning : null)), + ], + ); + } +} diff --git a/lib/utils/extensions/text_extension.dart b/lib/utils/extensions/text_extension.dart index 98b02bda..7def4aec 100644 --- a/lib/utils/extensions/text_extension.dart +++ b/lib/utils/extensions/text_extension.dart @@ -7,6 +7,8 @@ import 'package:selfprivacy/config/text_themes.dart'; extension TextExtension on Text { Text get h1 => copyWith(style: headline1Style); Text get h2 => copyWith(style: headline2Style); + Text get h3 => copyWith(style: headline3Style); + Text get caption => copyWith(style: captionStyle); Text get body1 => copyWith(style: body1Style); diff --git a/pubspec.lock b/pubspec.lock index 998c80bf..69f3d6fe 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -210,6 +210,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.4" + package_info: + dependency: "direct main" + description: + name: package_info + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.3+2" path: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 58875a15..a774e87b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: selfprivacy description: selfprivacy.org publish_to: 'none' -version: 1.0.0+1 +version: 0.1.0+1 environment: sdk: ">=2.7.0 <3.0.0" @@ -14,6 +14,7 @@ dependencies: equatable: ^1.2.5 flutter_bloc: ^6.1.1 google_fonts: ^1.1.1 + package_info: ^0.4.3+2 provider: ^4.3.2+2 url_launcher: ^5.7.10