From a112d873eba526c4b45b872854ed0b4573feea38 Mon Sep 17 00:00:00 2001 From: Kherel Date: Sun, 6 Dec 2020 08:28:31 +0100 Subject: [PATCH] update onboarding and providers --- lib/logic/models/provider.dart | 40 ++ lib/logic/models/service.dart | 1 - lib/main.dart | 2 +- .../components/brand_button/brand_button.dart | 4 +- .../pages/dots_indicator/dots_indicator.dart | 33 ++ lib/ui/pages/onboarding/onboarding copy.dart | 228 ++++++++++ lib/ui/pages/onboarding/onboarding.dart | 416 +++++++++++------- lib/ui/pages/providers/providers.dart | 206 ++++++--- lib/ui/pages/services/services.dart | 6 +- lib/ui/pages/users/users.dart | 61 ++- 10 files changed, 762 insertions(+), 235 deletions(-) create mode 100644 lib/logic/models/provider.dart create mode 100644 lib/ui/pages/dots_indicator/dots_indicator.dart create mode 100644 lib/ui/pages/onboarding/onboarding copy.dart diff --git a/lib/logic/models/provider.dart b/lib/logic/models/provider.dart new file mode 100644 index 0000000000..2034330ab8 --- /dev/null +++ b/lib/logic/models/provider.dart @@ -0,0 +1,40 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/widgets.dart'; +import 'package:selfprivacy/logic/models/service.dart'; +import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; + +enum ProviderTypes { + server, + domain, + backup, +} + +class ProviderModel extends Equatable { + const ProviderModel({this.state, this.type}); + + final ServiceStateType state; + final ProviderTypes type; + + ProviderModel updateState(ServiceStateType newState) => ProviderModel( + state: newState, + type: type, + ); + + @override + List get props => [state, type]; + + IconData get icon { + switch (type) { + case ProviderTypes.server: + return BrandIcons.server; + + case ProviderTypes.domain: + return BrandIcons.globe; + + break; + case ProviderTypes.backup: + return BrandIcons.save; + } + return null; + } +} diff --git a/lib/logic/models/service.dart b/lib/logic/models/service.dart index 5167eeaa4b..4d78b47978 100644 --- a/lib/logic/models/service.dart +++ b/lib/logic/models/service.dart @@ -5,7 +5,6 @@ enum ServiceTypes { messanger, mail, passwordManager, - backup, github, cloud, } diff --git a/lib/main.dart b/lib/main.dart index d6f113bf7d..64cbb21d82 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,7 +20,7 @@ void main() { ); } -var _showOnbording = false; +var _showOnbording = true; class MyApp extends StatelessWidget { @override diff --git a/lib/ui/components/brand_button/brand_button.dart b/lib/ui/components/brand_button/brand_button.dart index b594553d56..c3cc5728ea 100644 --- a/lib/ui/components/brand_button/brand_button.dart +++ b/lib/ui/components/brand_button/brand_button.dart @@ -99,7 +99,9 @@ class _RisedButton extends StatelessWidget { return ClipRRect( borderRadius: BorderRadius.circular(24), child: ColoredBox( - color: Theme.of(context).primaryColor, + color: onPressed == null + ? BrandColors.gray2 + : Theme.of(context).primaryColor, child: Material( color: Colors.transparent, child: InkWell( diff --git a/lib/ui/pages/dots_indicator/dots_indicator.dart b/lib/ui/pages/dots_indicator/dots_indicator.dart new file mode 100644 index 0000000000..e95c308fed --- /dev/null +++ b/lib/ui/pages/dots_indicator/dots_indicator.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/brand_colors.dart'; + +class DotsIndicator extends StatelessWidget { + const DotsIndicator({ + Key key, + @required this.activeIndex, + @required this.count, + }) : super(key: key); + + final int activeIndex; + final int count; + + @override + Widget build(BuildContext context) { + var dots = List.generate( + count, + (index) => Container( + margin: EdgeInsets.symmetric(horizontal: 5, vertical: 10), + height: 10, + width: 10, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: index == activeIndex ? BrandColors.blue : BrandColors.gray2, + ), + ), + ); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: dots, + ); + } +} diff --git a/lib/ui/pages/onboarding/onboarding copy.dart b/lib/ui/pages/onboarding/onboarding copy.dart new file mode 100644 index 0000000000..1a4685fb1d --- /dev/null +++ b/lib/ui/pages/onboarding/onboarding copy.dart @@ -0,0 +1,228 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/brand_theme.dart'; +import 'package:selfprivacy/config/text_themes.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_modal_sheet/brand_modal_sheet.dart'; +import 'package:selfprivacy/ui/components/brand_span_button/brand_span_button.dart'; +import 'package:selfprivacy/utils/extensions/text_extension.dart'; + +class OnboardingPage extends StatelessWidget { + const OnboardingPage({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView( + padding: brandPagePadding1, + children: [ + Text('Начало').caption, + Text('SelfPrivacy').h1, + SizedBox( + height: 10, + ), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: + 'Для устойчивости и приватности требует много учёток. Полная инструкция на ', + style: body2Style, + ), + BrandSpanButton.link( + text: 'selfprivacy.org/start', + urlString: 'https://selfprivacy.org/start', + ), + ], + ), + ), + SizedBox(height: 50), + BrandCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset('assets/images/logos/hetzner.png'), + SizedBox(height: 10), + Text('1. Подключите сервер Hetzner').h2, + SizedBox(height: 10), + Text('Здесь будут жить наши данные и SelfPrivacy-сервисы') + .body2, + _MockForm( + hintText: 'Hetzner API Token', + ), + SizedBox(height: 20), + BrandButton.text( + onPressed: () => _showModal(context, _HowHetzner()), + title: 'Как получить API Token', + ), + ], + ), + ), + BrandCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset('assets/images/logos/namecheap.png'), + SizedBox(height: 10), + Text('2. Настройте домен ').h2, + SizedBox(height: 10), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Зарегистрируйте домен в ', + style: body2Style, + ), + BrandSpanButton.link( + text: 'NameCheap', + urlString: 'https://www.namecheap.com', + ), + TextSpan( + text: + ' или у любого другого регистратора. После этого настройте его на DNS-сервер CloudFlare', + style: body2Style, + ), + ], + ), + ), + _MockForm( + hintText: 'Домен, например, selfprivacy.org', + submitButtonText: 'Проверить DNS', + ), + SizedBox(height: 20), + BrandButton.text( + onPressed: () {}, + title: 'Как настроить DNS CloudFlare', + ), + ], + ), + ), + BrandCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset('assets/images/logos/cloudflare.png'), + SizedBox(height: 10), + Text('3. Подключите CloudFlare DNS').h2, + SizedBox(height: 10), + Text('Для управления DNS вашего домена').body2, + _MockForm( + hintText: 'CloudFlare API Token', + ), + SizedBox(height: 20), + BrandButton.text( + onPressed: () {}, + title: 'Как получить API Token', + ), + ], + ), + ), + BrandCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset('assets/images/logos/aws.png'), + SizedBox(height: 10), + Text('4. Подключите Amazon AWS для бекапа').h2, + SizedBox(height: 10), + Text('IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде') + .body2, + _MockForm( + hintText: 'Amazon AWS Access Key', + ), + SizedBox(height: 20), + BrandButton.text( + onPressed: () {}, + title: 'Как получить API Token', + ), + ], + ), + ) + ], + ), + ); + } + + void _showModal(BuildContext context, Widget widget) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return widget; + }, + ); + } +} + +class _HowHetzner extends StatelessWidget { + const _HowHetzner({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BrandModalSheet( + child: Column( + children: [ + SizedBox(height: 40), + Text('Как получить Hetzner API Token').h2, + SizedBox(height: 20), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: '1 Переходим по ссылке ', + style: body1Style, + ), + BrandSpanButton.link( + text: 'hetzner.com/sdfsdfsdfsdf', + urlString: 'https://hetzner.com/sdfsdfsdfsdf', + ), + TextSpan( + text: ''' + +2 Заходим в созданный нами проект. Если такового - нет, значит создаём. + +3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика). + +4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему. + +5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку. + +6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет. + + ''', + style: body1Style, + ), + ], + ), + ), + ], + ), + ); + } +} + +class _MockForm extends StatelessWidget { + const _MockForm({ + Key key, + @required this.hintText, + this.submitButtonText = 'Подключить', + }) : super(key: key); + + final String hintText; + final String submitButtonText; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SizedBox(height: 20), + TextField(decoration: InputDecoration(hintText: hintText)), + SizedBox(height: 20), + BrandButton.rised(onPressed: () {}, title: submitButtonText), + ], + ); + } +} diff --git a/lib/ui/pages/onboarding/onboarding.dart b/lib/ui/pages/onboarding/onboarding.dart index 1a4685fb1d..67f775c9d3 100644 --- a/lib/ui/pages/onboarding/onboarding.dart +++ b/lib/ui/pages/onboarding/onboarding.dart @@ -5,144 +5,225 @@ 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_modal_sheet/brand_modal_sheet.dart'; import 'package:selfprivacy/ui/components/brand_span_button/brand_span_button.dart'; +import 'package:selfprivacy/ui/pages/dots_indicator/dots_indicator.dart'; +import 'package:selfprivacy/ui/pages/rootRoute.dart'; import 'package:selfprivacy/utils/extensions/text_extension.dart'; +import 'package:selfprivacy/utils/route_transitions/basic.dart'; -class OnboardingPage extends StatelessWidget { +class OnboardingPage extends StatefulWidget { const OnboardingPage({Key key}) : super(key: key); + @override + _OnboardingPageState createState() => _OnboardingPageState(); +} + +class _OnboardingPageState extends State { + PageController controller; + var currentPage = 0; + + @override + void initState() { + controller = PageController( + initialPage: 0, + )..addListener(() { + if (currentPage != controller.page.toInt()) { + setState(() { + currentPage = controller.page.toInt(); + }); + } + }); + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) {}); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Scaffold( - body: ListView( - padding: brandPagePadding1, - children: [ - Text('Начало').caption, - Text('SelfPrivacy').h1, - SizedBox( - height: 10, - ), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: - 'Для устойчивости и приватности требует много учёток. Полная инструкция на ', - style: body2Style, - ), - BrandSpanButton.link( - text: 'selfprivacy.org/start', - urlString: 'https://selfprivacy.org/start', - ), - ], - ), - ), - SizedBox(height: 50), - BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset('assets/images/logos/hetzner.png'), - SizedBox(height: 10), - Text('1. Подключите сервер Hetzner').h2, - SizedBox(height: 10), - Text('Здесь будут жить наши данные и SelfPrivacy-сервисы') - .body2, - _MockForm( - hintText: 'Hetzner API Token', - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () => _showModal(context, _HowHetzner()), - title: 'Как получить API Token', - ), - ], - ), - ), - BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset('assets/images/logos/namecheap.png'), - SizedBox(height: 10), - Text('2. Настройте домен ').h2, - SizedBox(height: 10), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: 'Зарегистрируйте домен в ', - style: body2Style, - ), - BrandSpanButton.link( - text: 'NameCheap', - urlString: 'https://www.namecheap.com', - ), - TextSpan( - text: - ' или у любого другого регистратора. После этого настройте его на DNS-сервер CloudFlare', - style: body2Style, - ), - ], + var steps = getSteps(); + + return SafeArea( + child: Scaffold( + body: ListView( + shrinkWrap: true, + children: [ + Padding( + padding: brandPagePadding1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Начало').caption, + Text('SelfPrivacy').h1, + SizedBox( + height: 10, ), - ), - _MockForm( - hintText: 'Домен, например, selfprivacy.org', - submitButtonText: 'Проверить DNS', - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как настроить DNS CloudFlare', - ), - ], + RichText( + text: TextSpan( + children: [ + TextSpan( + text: + 'Для устойчивости и приватности требует много учёток. Полная инструкция на ', + style: body2Style, + ), + BrandSpanButton.link( + text: 'selfprivacy.org/start', + urlString: 'https://selfprivacy.org/start', + ), + ], + ), + ), + ], + ), ), - ), - BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset('assets/images/logos/cloudflare.png'), - SizedBox(height: 10), - Text('3. Подключите CloudFlare DNS').h2, - SizedBox(height: 10), - Text('Для управления DNS вашего домена').body2, - _MockForm( - hintText: 'CloudFlare API Token', - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как получить API Token', - ), - ], + Container( + height: 480, + child: PageView.builder( + physics: NeverScrollableScrollPhysics(), + allowImplicitScrolling: false, + controller: controller, + itemBuilder: (_, index) { + return Padding( + padding: brandPagePadding2, + child: steps[index], + ); + }, + itemCount: 4, + ), ), - ), - BrandCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset('assets/images/logos/aws.png'), - SizedBox(height: 10), - Text('4. Подключите Amazon AWS для бекапа').h2, - SizedBox(height: 10), - Text('IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде') - .body2, - _MockForm( - hintText: 'Amazon AWS Access Key', - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как получить API Token', - ), - ], + DotsIndicator( + activeIndex: currentPage, + count: steps.length, ), - ) - ], + SizedBox(height: 50), + ], + ), ), ); } + List getSteps() => [ + BrandCard( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset('assets/images/logos/hetzner.png'), + SizedBox(height: 10), + Text('1. Подключите сервер Hetzner').h2, + SizedBox(height: 10), + Text('Здесь будут жить наши данные и SelfPrivacy-сервисы').body2, + _MockForm( + onPressed: _nextPage, + hintText: 'Hetzner API Token', + length: 2, + ), + SizedBox(height: 20), + Spacer(), + BrandButton.text( + onPressed: () => _showModal(context, _HowHetzner()), + title: 'Как получить API Token', + ), + ], + ), + ), + BrandCard( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset('assets/images/logos/namecheap.png'), + SizedBox(height: 10), + Text('2. Настройте домен ').h2, + SizedBox(height: 10), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Зарегистрируйте домен в ', + style: body2Style, + ), + BrandSpanButton.link( + text: 'NameCheap', + urlString: 'https://www.namecheap.com', + ), + TextSpan( + text: + ' или у любого другого регистратора. После этого настройте его на DNS-сервер CloudFlare', + style: body2Style, + ), + ], + ), + ), + _MockForm( + onPressed: _nextPage, + hintText: 'Домен, например, selfprivacy.org', + submitButtonText: 'Проверить DNS', + length: 2, + ), + Spacer(), + BrandButton.text( + onPressed: () {}, + title: 'Как настроить DNS CloudFlare', + ), + ], + ), + ), + BrandCard( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset('assets/images/logos/cloudflare.png'), + SizedBox(height: 10), + Text('3. Подключите CloudFlare DNS').h2, + SizedBox(height: 10), + Text('Для управления DNS вашего домена').body2, + _MockForm( + onPressed: _nextPage, + hintText: 'CloudFlare API Token', + length: 2, + ), + Spacer(), + BrandButton.text( + onPressed: () {}, + title: 'Как получить API Token', + ), + ], + ), + ), + BrandCard( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset('assets/images/logos/aws.png'), + SizedBox(height: 10), + Text('4. Подключите Amazon AWS для бекапа').h2, + SizedBox(height: 10), + Text('IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде') + .body2, + _MockForm( + onPressed: () { + Navigator.of(context) + .pushReplacement(materialRoute(RootPage())); + }, + hintText: 'Amazon AWS Access Key', + length: 2, + ), + Spacer(), + BrandButton.text( + onPressed: () {}, + title: 'Как получить API Token', + ), + ], + ), + ), + ]; + void _showModal(BuildContext context, Widget widget) { showModalBottomSheet( context: context, @@ -153,6 +234,11 @@ class OnboardingPage extends StatelessWidget { }, ); } + + void _nextPage() => controller.nextPage( + duration: Duration(milliseconds: 300), + curve: Curves.easeIn, + ); } class _HowHetzner extends StatelessWidget { @@ -163,25 +249,27 @@ class _HowHetzner extends StatelessWidget { @override Widget build(BuildContext context) { return BrandModalSheet( - child: Column( - children: [ - SizedBox(height: 40), - Text('Как получить Hetzner API Token').h2, - SizedBox(height: 20), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: '1 Переходим по ссылке ', - style: body1Style, - ), - BrandSpanButton.link( - text: 'hetzner.com/sdfsdfsdfsdf', - urlString: 'https://hetzner.com/sdfsdfsdfsdf', - ), - TextSpan( - text: ''' - + child: Padding( + padding: brandPagePadding2, + child: Column( + children: [ + SizedBox(height: 40), + Text('Как получить Hetzner API Token').h2, + SizedBox(height: 20), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: '1 Переходим по ссылке ', + style: body1Style, + ), + BrandSpanButton.link( + text: 'hetzner.com/sdfsdfsdfsdf', + urlString: 'https://hetzner.com/sdfsdfsdfsdf', + ), + TextSpan( + text: ''' + 2 Заходим в созданный нами проект. Если такового - нет, значит создаём. 3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика). @@ -192,36 +280,60 @@ class _HowHetzner extends StatelessWidget { 6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет. - ''', - style: body1Style, - ), - ], + ''', + style: body1Style, + ), + ], + ), ), - ), - ], + ], + ), ), ); } } -class _MockForm extends StatelessWidget { +class _MockForm extends StatefulWidget { const _MockForm({ Key key, @required this.hintText, this.submitButtonText = 'Подключить', + @required this.onPressed, + @required this.length, }) : super(key: key); final String hintText; final String submitButtonText; + final int length; + + final VoidCallback onPressed; + + @override + __MockFormState createState() => __MockFormState(); +} + +class __MockFormState extends State<_MockForm> { + String text = ''; @override Widget build(BuildContext context) { return Column( children: [ SizedBox(height: 20), - TextField(decoration: InputDecoration(hintText: hintText)), + TextField( + onChanged: (value) => { + setState(() { + text = value; + }) + }, + decoration: InputDecoration(hintText: widget.hintText), + ), SizedBox(height: 20), - BrandButton.rised(onPressed: () {}, title: submitButtonText), + BrandButton.rised( + onPressed: + text.length == widget.length ? widget.onPressed ?? () {} : null, + title: widget.submitButtonText, + ), ], ); } diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 0ee08f07de..19e33002ca 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/models/provider.dart'; import 'package:selfprivacy/logic/models/service.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_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/icon_status_mask/icon_status_mask.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 ProvidersPage extends StatefulWidget { ProvidersPage({Key key}) : super(key: key); @@ -19,9 +20,11 @@ class ProvidersPage extends StatefulWidget { class _ProvidersPageState extends State { @override Widget build(BuildContext context) { - final serviceCubit = context.watch(); - final connected = serviceCubit.state.connected; - final uninitialized = serviceCubit.state.uninitialized; + final cards = ProviderTypes.values + .map((type) => _Card( + provider: + ProviderModel(state: ServiceStateType.stable, type: type))) + .toList(); return Scaffold( appBar: PreferredSize( child: BrandHeader(title: 'Провайдеры'), @@ -29,86 +32,161 @@ class _ProvidersPageState extends State { ), body: ListView( padding: brandPagePadding2, - children: [ - SizedBox(height: 24), - ...connected.map((service) => _Card(service: service)).toList(), - if (uninitialized.isNotEmpty) ...[ - Text('не подключены').body1, - SizedBox(height: 30), - ], - ...uninitialized.map((service) => _Card(service: service)).toList() - ], + children: cards, ), ); } } class _Card extends StatelessWidget { - const _Card({Key key, @required this.service}) : super(key: key); + const _Card({Key key, @required this.provider}) : super(key: key); - final Service service; + final ProviderModel provider; @override Widget build(BuildContext context) { String title; - IconData iconData; - String description; + String message; + String stableText; - switch (service.type) { - case ServiceTypes.messanger: - iconData = BrandIcons.messanger; - title = 'Мессенджер'; - description = - 'Delta Chat срфеТекст-текст описание. Если бы мне надо было обсудить что-то от чего зависит жизнь. Я бы выбрал Delta.Chat + свой почтовый сервер.'; + switch (provider.type) { + case ProviderTypes.server: + title = 'Сервер'; + stableText = 'В норме'; break; - case ServiceTypes.mail: - iconData = BrandIcons.envelope; - title = 'Почта'; - description = 'Электронная почта для семьи или компании '; + case ProviderTypes.domain: + title = 'Домен'; + message = 'example.com'; + stableText = 'Домен настроен'; break; - case ServiceTypes.passwordManager: - iconData = BrandIcons.key; - title = 'Менеджер паролей'; - description = 'Надёжное хранилище для ваших паролей и ключей доступа'; - break; - case ServiceTypes.github: - iconData = BrandIcons.github; - title = 'Git сервер'; - description = 'Сервис для приватного хранения своих разработок'; - break; - case ServiceTypes.backup: - iconData = BrandIcons.save; + case ProviderTypes.backup: + message = '22 янв 2021 14:30'; title = 'Резервное копирование'; - description = 'Обеспеченье целосности и сохранности ваших данных'; - break; - case ServiceTypes.cloud: - iconData = BrandIcons.upload; - title = 'Файловое Облако'; - description = 'Сервис для доступа к вашим файлам в любой точке мира'; + stableText = 'В норме'; break; } - return BrandCard( + return GestureDetector( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return _ProviderDetails( + provider: provider, + statusText: stableText, + ); + }, + ), + child: BrandCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconStatusMaks( + status: provider.state, + child: Icon(provider.icon, size: 30, color: Colors.white), + ), + SizedBox(height: 10), + Text(title).h2, + SizedBox(height: 10), + if (message != null) ...[ + Text(message).body2, + SizedBox(height: 10), + ], + if (provider.state == ServiceStateType.stable) + Text(stableText).body2, + ], + ), + ), + ); + } +} + +class _ProviderDetails extends StatelessWidget { + const _ProviderDetails({ + Key key, + @required this.provider, + @required this.statusText, + }) : super(key: key); + + final ProviderModel provider; + final String statusText; + + @override + Widget build(BuildContext context) { + String title; + + switch (provider.type) { + case ProviderTypes.server: + title = 'Сервер'; + break; + case ProviderTypes.domain: + title = 'Домен'; + + break; + case ProviderTypes.backup: + title = 'Резервное копирование'; + break; + } + return BrandModalSheet( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - IconStatusMaks( - status: service.state, - child: Icon(iconData, size: 30, color: Colors.white), + Align( + alignment: Alignment.centerRight, + child: Padding( + padding: EdgeInsets.symmetric( + vertical: 4, + horizontal: 2, + ), + child: PopupMenuButton<_PopupMenuItemType>( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + onSelected: (_PopupMenuItemType result) { + switch (result) { + case _PopupMenuItemType.setting: + Navigator.of(context) + .pushReplacement(materialRoute(SettingsPage())); + break; + } + }, + icon: Icon(Icons.more_vert), + itemBuilder: (BuildContext context) => [ + PopupMenuItem<_PopupMenuItemType>( + value: _PopupMenuItemType.setting, + child: Container( + padding: EdgeInsets.only(left: 5), + child: Text('Настройки'), + ), + ), + ], + ), + ), ), - SizedBox(height: 10), - Text(title).h2, - SizedBox(height: 10), - if (service.state == ServiceStateType.uninitialized) ...[ - Text(description).body1, - SizedBox(height: 10), - BrandButton.text( - title: 'Подключить', - onPressed: () { - context.read().connect(service); - }) - ], - if (service.state == ServiceStateType.stable) Text('Подключен').body1, + Padding( + padding: brandPagePadding1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 13), + IconStatusMaks( + status: provider.state, + child: Icon(provider.icon, size: 40, color: Colors.white), + ), + SizedBox(height: 10), + Text(title).h1, + SizedBox(height: 10), + Text(statusText).body1, + SizedBox( + height: 20, + ), + Text('Статусы сервера и сервис провайдера и т.д.') + ], + ), + ) ], ), ); } } + +enum _PopupMenuItemType { setting } diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index 22c4d89bed..71dbf51fc0 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -75,11 +75,7 @@ class _Card extends StatelessWidget { title = 'Git сервер'; description = 'Сервис для приватного хранения своих разработок'; break; - case ServiceTypes.backup: - iconData = BrandIcons.save; - title = 'Резервное копирование'; - description = 'Обеспеченье целосности и сохранности ваших данных'; - break; + case ServiceTypes.cloud: iconData = BrandIcons.upload; title = 'Файловое Облако'; diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index 151e998dc3..7209c6face 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -199,26 +199,60 @@ class _UserDetails extends StatelessWidget { vertical: 4, horizontal: 2, ), - child: PopupMenuButton( + child: PopupMenuButton( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.0), ), - // onSelected: (WhyFarther result) { - // setState(() { - // _selection = result; - // }); - // }, + onSelected: (PopupMenuItemType result) { + switch (result) { + case PopupMenuItemType.reset: + break; + case PopupMenuItemType.delete: + showDialog( + context: context, + child: AlertDialog( + title: Text('Подтверждение '), + content: SingleChildScrollView( + child: ListBody( + children: [ + Text('удалить учетную запись?'), + ], + ), + ), + actions: [ + TextButton( + child: Text('Отменить'), + onPressed: () { + Navigator.of(context)..pop(); + }, + ), + TextButton( + child: Text( + 'Удалить', + style: TextStyle( + color: BrandColors.red, + ), + ), + onPressed: () { + Navigator.of(context)..pop()..pop(); + }, + ), + ], + )); + break; + } + }, icon: Icon(Icons.more_vert), itemBuilder: (BuildContext context) => [ - PopupMenuItem( - value: 1, + PopupMenuItem( + value: PopupMenuItemType.reset, child: Container( padding: EdgeInsets.only(left: 5), child: Text('Сбросить пароль'), ), ), - PopupMenuItem( - value: 2, + PopupMenuItem( + value: PopupMenuItemType.delete, child: Container( padding: EdgeInsets.only(left: 5), child: Text( @@ -277,7 +311,7 @@ class _UserDetails extends StatelessWidget { BrandDivider(), SizedBox(height: 20), Text( - 'Вам был создан доступ к сервисам с логином и паролем к сервисам:
- E-mail с адресом 
- Менеджер паролей: 
- Файловое облако: 
- Видеоконференция 
- Git сервер '), + 'Вам был создан доступ к сервисам с логином и паролем к сервисам:- E-mail с адресом - Менеджер паролей: - Файловое облако: - Видеоконференция - Git сервер '), ], ), ) @@ -286,3 +320,8 @@ class _UserDetails extends StatelessWidget { ); } } + +enum PopupMenuItemType { + reset, + delete, +}