diff --git a/assets/fonts/BrandIcons.ttf b/assets/fonts/BrandIcons.ttf index c4ce1ac820..d902699b56 100644 Binary files a/assets/fonts/BrandIcons.ttf and b/assets/fonts/BrandIcons.ttf differ diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart new file mode 100644 index 0000000000..88bc3903c9 --- /dev/null +++ b/lib/config/bloc_config.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; + +class BlocAndProviderConfig extends StatelessWidget { + const BlocAndProviderConfig({Key key, this.child}) : super(key: key); + + final Widget child; + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + BlocProvider( + create: (BuildContext context) => ServicesCubit(), + ), + ], + child: child, + ); + } +} diff --git a/lib/config/brand_colors.dart b/lib/config/brand_colors.dart index 028d5c2c3a..3b7ca03c1f 100644 --- a/lib/config/brand_colors.dart +++ b/lib/config/brand_colors.dart @@ -28,4 +28,17 @@ class BrandColors { static const textColor2 = gray1; static get navBackground => white.withOpacity(0.8); + + static const List uninitializedGradientColors = [ + Color(0xFF555555), + Color(0xFFABABAB), + ]; + static const List stableGradientColors = [ + Color(0xFF093CEF), + Color(0xFF14A1CB), + ]; + static const List warningGradientColors = [ + Color(0xFFEF4E09), + Color(0xFFEFD135), + ]; } diff --git a/lib/config/brand_theme.dart b/lib/config/brand_theme.dart index e08c09d1a0..7c8458a3e1 100644 --- a/lib/config/brand_theme.dart +++ b/lib/config/brand_theme.dart @@ -4,7 +4,7 @@ import 'package:selfprivacy/config/text_themes.dart'; import 'brand_colors.dart'; -var theme = ThemeData( +final theme = ThemeData( primaryColor: BrandColors.primary, brightness: Brightness.light, scaffoldBackgroundColor: BrandColors.scaffoldBackground, @@ -13,7 +13,9 @@ var theme = ThemeData( headline1: headline1Style, headline2: headline2Style, caption: captionStyle, - bodyText1: bodyText1Style, + bodyText1: body1Style, ), ), ); + +final brandPagePadding = EdgeInsets.symmetric(horizontal: 15, vertical: 30); diff --git a/lib/config/text_themes.dart b/lib/config/text_themes.dart index a871d0e8ae..4a3c0c84f2 100644 --- a/lib/config/text_themes.dart +++ b/lib/config/text_themes.dart @@ -29,7 +29,7 @@ final captionStyle = GoogleFonts.inter( color: BrandColors.headlineColor, ); -final bodyText1Style = defaultTextStyle; -final body2TextStyle = defaultTextStyle.copyWith( +final body1Style = defaultTextStyle; +final body2Style = defaultTextStyle.copyWith( color: BrandColors.textColor2, ); diff --git a/lib/logic/cubit/services/services_cubit.dart b/lib/logic/cubit/services/services_cubit.dart new file mode 100644 index 0000000000..95c1169ae0 --- /dev/null +++ b/lib/logic/cubit/services/services_cubit.dart @@ -0,0 +1,24 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:selfprivacy/logic/models/service.dart'; +export 'package:provider/provider.dart'; + +part 'services_state.dart'; + +class ServicesCubit extends Cubit { + ServicesCubit() : super(ServicesState(all)); + + void connect(Service service) { + var newState = state.updateElement(service, ServiceStateType.stable); + emit(newState); + } +} + +final all = ServiceTypes.values + .map( + (type) => Service( + state: ServiceStateType.uninitialized, + type: type, + ), + ) + .toList(); diff --git a/lib/logic/cubit/services/services_state.dart b/lib/logic/cubit/services/services_state.dart new file mode 100644 index 0000000000..4057b3e339 --- /dev/null +++ b/lib/logic/cubit/services/services_state.dart @@ -0,0 +1,23 @@ +part of 'services_cubit.dart'; + +@immutable +class ServicesState { + ServicesState(this.all); + + final List all; + + ServicesState updateElement(Service service, ServiceStateType 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 != ServiceStateType.uninitialized) + .toList(); + + List get uninitialized => all + .where((service) => service.state == ServiceStateType.uninitialized) + .toList(); +} diff --git a/lib/logic/models/service.dart b/lib/logic/models/service.dart new file mode 100644 index 0000000000..5167eeaa4b --- /dev/null +++ b/lib/logic/models/service.dart @@ -0,0 +1,26 @@ +import 'package:equatable/equatable.dart'; + +enum ServiceStateType { uninitialized, stable, warning } +enum ServiceTypes { + messanger, + mail, + passwordManager, + backup, + github, + cloud, +} + +class Service extends Equatable { + const Service({this.state, this.type}); + + final ServiceStateType state; + final ServiceTypes type; + + Service updateState(ServiceStateType newState) => Service( + state: newState, + type: type, + ); + + @override + List get props => [state, type]; +} diff --git a/lib/main.dart b/lib/main.dart index 3b3a6ab857..e2c11ca208 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'config/bloc_config.dart'; import 'config/brand_theme.dart'; import 'config/localization.dart'; @@ -11,7 +12,9 @@ void main() { runApp( Localization( - child: MyApp(), + child: BlocAndProviderConfig( + child: MyApp(), + ), ), ); } diff --git a/lib/ui/components/brand_icons/brand_icons.dart b/lib/ui/components/brand_icons/brand_icons.dart index dc4bcf5810..72be2f904b 100644 --- a/lib/ui/components/brand_icons/brand_icons.dart +++ b/lib/ui/components/brand_icons/brand_icons.dart @@ -23,32 +23,26 @@ class BrandIcons { static const IconData connection = IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData envelope = + IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData document = IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData envelope = + static const IconData key = IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData github = + static const IconData save = IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData globe = IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData help = IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData key = - IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData messenger = - IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData refresh = IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData save = - IconData(0xe80b, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData settings = IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData share = IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData triangle = IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData upload = - IconData(0xe810, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData server = IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData box = @@ -57,4 +51,10 @@ class BrandIcons { IconData(0xe813, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData users = IconData(0xe814, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData messanger = + 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); } diff --git a/lib/ui/components/icon_status_mask/icon_status_mask.dart b/lib/ui/components/icon_status_mask/icon_status_mask.dart new file mode 100644 index 0000000000..91ce636984 --- /dev/null +++ b/lib/ui/components/icon_status_mask/icon_status_mask.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/brand_colors.dart'; +import 'package:selfprivacy/logic/models/service.dart'; + +class IconStatusMaks extends StatelessWidget { + IconStatusMaks({this.child, this.status}); + final Icon child; + + final ServiceStateType status; + + @override + Widget build(BuildContext context) { + List colors; + switch (status) { + case ServiceStateType.uninitialized: + colors = BrandColors.uninitializedGradientColors; + break; + case ServiceStateType.stable: + colors = BrandColors.stableGradientColors; + break; + case ServiceStateType.warning: + colors = BrandColors.warningGradientColors; + break; + } + return ShaderMask( + shaderCallback: (bounds) => LinearGradient( + begin: Alignment(-1, -0.8), + end: Alignment(0.9, 0.9), + colors: colors, + tileMode: TileMode.mirror, + ).createShader(bounds), + child: child, + ); + } +} diff --git a/lib/ui/pages/rootRoute.dart b/lib/ui/pages/rootRoute.dart index a7cfaa8210..0851c0d286 100644 --- a/lib/ui/pages/rootRoute.dart +++ b/lib/ui/pages/rootRoute.dart @@ -2,6 +2,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/servers/servers.dart'; +import 'package:selfprivacy/ui/pages/services/services.dart'; class RootPage extends StatefulWidget { const RootPage({Key key}) : super(key: key); @@ -34,7 +35,7 @@ class _RootPageState extends State controller: tabController, children: [ ServersPage(), - Text('services'), + ServicesPage(), Text('users'), Text('more'), ], diff --git a/lib/ui/pages/servers/servers.dart b/lib/ui/pages/servers/servers.dart index 924ba6b385..486106b744 100644 --- a/lib/ui/pages/servers/servers.dart +++ b/lib/ui/pages/servers/servers.dart @@ -1,11 +1,13 @@ 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/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'; +export 'package:bloc/bloc.dart'; class ServersPage extends StatelessWidget { const ServersPage({Key key}) : super(key: key); @@ -13,71 +15,70 @@ class ServersPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - body: Container( - child: ListView( - padding: EdgeInsets.symmetric(horizontal: 15, vertical: 30), - children: [ - Text('Начало').caption, - Text('SelfPrivacy').h1, - SizedBox( - height: 10, + body: ListView( + padding: brandPagePadding, + 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', + ), + ], ), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: - 'Для устойчивости и приватности требует много учёток. Полная инструкция на ', - style: body2TextStyle, - ), - 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: () => showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (BuildContext context) { - return BrandModalSheet( - child: Column( - children: [ - Text('Как получить Hetzner API Token').h2, - SizedBox(height: 20), - RichText( - text: TextSpan( - children: [ - TextSpan( - text: '1 Переходим по ссылке ', - style: bodyText1Style, - ), - BrandSpanButton.link( - text: 'hetzner.com/sdfsdfsdfsdf', - urlString: - 'https://hetzner.com/sdfsdfsdfsdf', - ), - TextSpan( - text: ''' - + ), + 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: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return BrandModalSheet( + child: Column( + children: [ + 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 (с иконкой ключика). @@ -88,104 +89,103 @@ class ServersPage extends StatelessWidget { 6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет. - ''', - style: bodyText1Style, - ), - ], - ), + ''', + style: body1Style, + ), + ], ), - ], - ), - ); - }, - ), - 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: body2TextStyle, + ), + ], ), - BrandSpanButton.link( - text: 'NameCheap', - urlString: 'https://www.namecheap.com', - ), - TextSpan( - text: - ' или у любого другого регистратора. После этого настройте его на DNS-сервер CloudFlare', - style: body2TextStyle, - ), - ], - ), + ); + }, ), - _MockForm( - hintText: 'Домен, например, selfprivacy.org', - submitButtonText: 'Проверить DNS', - ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как настроить DNS CloudFlare', - ), - ], - ), + title: 'Как получить API Token', + ), + ], ), - 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', + ), + 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, + ), + ], ), - SizedBox(height: 20), - BrandButton.text( - onPressed: () {}, - title: 'Как получить API Token', - ), - ], - ), + ), + _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/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', - ), - ], - ), - ) - ], - ), + ), + 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', + ), + ], + ), + ) + ], ), ); } diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart new file mode 100644 index 0000000000..a60f5a9388 --- /dev/null +++ b/lib/ui/pages/services/services.dart @@ -0,0 +1,110 @@ +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/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_icons/brand_icons.dart'; +import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; +import 'package:selfprivacy/utils/extensions/text_extension.dart'; + +class ServicesPage extends StatefulWidget { + ServicesPage({Key key}) : super(key: key); + + @override + _ServicesPageState createState() => _ServicesPageState(); +} + +class _ServicesPageState extends State { + @override + Widget build(BuildContext context) { + final serviceCubit = context.watch(); + final connected = serviceCubit.state.connected; + final uninitialized = serviceCubit.state.uninitialized; + return Scaffold( + body: ListView( + padding: brandPagePadding, + children: [ + Text('Сервисы').caption, + 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() + ], + ), + ); + } +} + +class _Card extends StatelessWidget { + const _Card({Key key, @required this.service}) : super(key: key); + + final Service service; + @override + Widget build(BuildContext context) { + String title; + IconData iconData; + String description; + + switch (service.type) { + case ServiceTypes.messanger: + iconData = BrandIcons.messanger; + title = 'Мессенджер'; + description = + 'Delta Chat срфеТекст-текст описание. Если бы мне надо было обсудить что-то от чего зависит жизнь. Я бы выбрал Delta.Chat + свой почтовый сервер.'; + break; + case ServiceTypes.mail: + iconData = BrandIcons.envelope; + title = 'Почта'; + description = 'Электронная почта для семьи или компании '; + 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; + title = 'Резервное копирование'; + description = 'Обеспеченье целосности и сохранности ваших данных'; + break; + case ServiceTypes.cloud: + iconData = BrandIcons.upload; + title = 'Файловое Облако'; + description = 'Сервис для доступа к вашим файлам в любой точке мира'; + break; + } + return BrandCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconStatusMaks( + status: service.state, + child: Icon(iconData, size: 30, color: Colors.white), + ), + 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, + ], + ), + ); + } +} diff --git a/lib/utils/extensions/text_extension.dart b/lib/utils/extensions/text_extension.dart index 6094bcf4c1..98b02bda9e 100644 --- a/lib/utils/extensions/text_extension.dart +++ b/lib/utils/extensions/text_extension.dart @@ -9,7 +9,8 @@ extension TextExtension on Text { Text get h2 => copyWith(style: headline2Style); Text get caption => copyWith(style: captionStyle); - Text get body2 => copyWith(style: body2TextStyle); + Text get body1 => copyWith(style: body1Style); + Text get body2 => copyWith(style: body2Style); Text setKey(Key key) => copyWith(key: key); diff --git a/pubspec.lock b/pubspec.lock index f3b1f207ba..998c80bf27 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,6 +22,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.5.0-nullsafety.1" + bloc: + dependency: transitive + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" boolean_selector: dependency: transitive description: @@ -85,6 +92,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.3.3" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.5" fake_async: dependency: transitive description: @@ -111,6 +125,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.1" flutter_launcher_icons: dependency: "direct dev" description: @@ -182,6 +203,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0-nullsafety.3" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" path: dependency: transitive description: @@ -259,6 +287,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.13" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.2+2" shared_preferences: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ad190df3e1..58875a1517 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,10 @@ dependencies: sdk: flutter cupertino_icons: ^1.0.0 easy_localization: ^2.3.3 + equatable: ^1.2.5 + flutter_bloc: ^6.1.1 google_fonts: ^1.1.1 + provider: ^4.3.2+2 url_launcher: ^5.7.10 dev_dependencies: