From 52cdcfa3d0b8456a0bc9f8fbc532870e559adbee Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sun, 12 May 2019 18:16:10 +0200 Subject: [PATCH 01/17] [TASK] Added element widgets --- .gitignore | 2 +- config.json.dist | 5 + lib/main.dart | 33 +- lib/src/app.dart | 215 ++++++++---- lib/src/blocs/bloc_provider.dart | 40 --- lib/src/blocs/main_bloc.dart | 32 -- lib/src/commons/colors.dart | 38 --- lib/src/commons/dimensions.dart | 27 -- .../managers/local_configuration_manager.dart | 36 +++ .../managers/shared_preferences_manager.dart} | 6 +- .../repositories/repositories_provider.dart | 14 +- .../blocs/configuration/configuration.dart | 4 + .../configuration/configuration_bloc.dart | 68 ++++ .../configuration/configuration_event.dart | 7 + .../configuration/configuration_state.dart | 25 ++ lib/src/models/view_models.dart | 2 - lib/src/pages/account_page.dart | 119 ------- lib/src/pages/entry_page.dart | 100 ------ lib/src/pages/group_page.dart | 89 ----- lib/src/pages/main_page.dart | 131 -------- lib/src/pages/part_page.dart | 89 ----- lib/src/pages/profile_page.dart | 255 --------------- .../local_secrets_repository.dart | 50 --- lib/src/routes.dart | 65 ++-- lib/src/{ => ui}/commons/api_values.dart | 8 +- lib/src/ui/commons/colors.dart | 40 +++ lib/src/{ => ui}/commons/defaults.dart | 0 lib/src/ui/commons/dimensions.dart | 32 ++ lib/src/{ => ui}/commons/paths.dart | 0 lib/src/{ => ui}/commons/tags.dart | 0 .../localizations/cv_localization.dart | 11 +- .../localizations/cv_localization_en.dart | 14 +- .../localizations/cv_localization_fr.dart | 12 +- lib/src/ui/models/view_models.dart | 2 + lib/src/ui/pages/account_page.dart | 124 +++++++ lib/src/{ => ui}/pages/auth_page.dart | 162 ++++------ .../ui/pages/elements/entry_profile_page.dart | 65 ++++ .../ui/pages/elements/group_profile_page.dart | 58 ++++ .../ui/pages/elements/part_profile_page.dart | 57 ++++ .../pages/elements/profile_profile_page.dart | 218 +++++++++++++ lib/src/{ => ui}/pages/home_page.dart | 2 +- lib/src/ui/pages/main_page.dart | 90 ++++++ lib/src/{ => ui}/pages/search_page.dart | 39 ++- lib/src/{ => ui}/pages/settings_page.dart | 4 +- lib/src/ui/pages/splash_page.dart | 24 ++ lib/src/ui/widgets/account_tile_widget.dart | 99 ++++++ .../widgets/arc_banner_image_widget.dart | 0 .../ui/widgets/elements/element_widget.dart | 17 + .../widgets/elements}/entry_list_widget.dart | 58 ++-- .../elements/entry_profile_widget.dart} | 106 +++--- lib/src/ui/widgets/elements/entry_widget.dart | 42 +++ .../widgets/elements}/group_list_widget.dart | 97 +++--- .../elements/group_profile_widget.dart | 134 ++++++++ lib/src/ui/widgets/elements/group_widget.dart | 42 +++ .../widgets/elements}/part_list_widget.dart | 66 ++-- .../widgets/elements/part_profile_widget.dart | 147 +++++++++ lib/src/ui/widgets/elements/part_widget.dart | 42 +++ .../elements}/profile_list_widget.dart | 80 ++--- .../widgets/elements/profile_tile_widget.dart | 28 ++ .../ui/widgets/elements/profile_widget.dart | 43 +++ lib/src/{ => ui}/widgets/error_widget.dart | 4 +- .../widgets/initial_circle_avatar_widget.dart | 0 lib/src/{ => ui}/widgets/loading_widget.dart | 1 + lib/src/ui/widgets/login_form_widget.dart | 303 +++++++++++++++++ .../widgets/menu_bottom_sheet_widget.dart | 6 +- lib/src/ui/widgets/menu_button_widget.dart | 86 +++++ .../widgets/profile_image_widget.dart | 2 +- lib/src/ui/widgets/register_form_widget.dart | 303 +++++++++++++++++ lib/src/{ => ui}/widgets/rounded_modal.dart | 0 lib/src/{ => ui}/widgets/sort_box_widget.dart | 0 .../{ => ui}/widgets/sort_dialog_widget.dart | 12 +- .../widgets/sort_list_tile_widget.dart | 2 +- .../ui/widgets/theme_switch_tile_widget.dart | 47 +++ lib/src/utils/logging_service.dart | 4 - lib/src/utils/navigation.dart | 40 ++- lib/src/utils/utils.dart | 4 +- lib/src/widgets/account_tile_widget.dart | 71 ---- lib/src/widgets/group_widget.dart | 127 -------- lib/src/widgets/login_form_widget.dart | 306 ------------------ lib/src/widgets/menu_button_widget.dart | 65 ---- lib/src/widgets/part_widget.dart | 159 --------- lib/src/widgets/profile_widget.dart | 28 -- lib/src/widgets/register_form_widget.dart | 235 -------------- lib/src/widgets/theme_switch_tile_widget.dart | 35 -- pubspec.lock | 47 ++- pubspec.yaml | 21 +- secrets.json.dist | 4 - 87 files changed, 2844 insertions(+), 2483 deletions(-) create mode 100644 config.json.dist delete mode 100644 lib/src/blocs/bloc_provider.dart delete mode 100644 lib/src/blocs/main_bloc.dart delete mode 100644 lib/src/commons/colors.dart delete mode 100644 lib/src/commons/dimensions.dart create mode 100644 lib/src/data/managers/local_configuration_manager.dart rename lib/src/{repositories/shared_preferences_repository.dart => data/managers/shared_preferences_manager.dart} (96%) rename lib/src/{ => data}/repositories/repositories_provider.dart (56%) create mode 100644 lib/src/domain/blocs/configuration/configuration.dart create mode 100644 lib/src/domain/blocs/configuration/configuration_bloc.dart create mode 100644 lib/src/domain/blocs/configuration/configuration_event.dart create mode 100644 lib/src/domain/blocs/configuration/configuration_state.dart delete mode 100644 lib/src/models/view_models.dart delete mode 100644 lib/src/pages/account_page.dart delete mode 100644 lib/src/pages/entry_page.dart delete mode 100644 lib/src/pages/group_page.dart delete mode 100644 lib/src/pages/main_page.dart delete mode 100644 lib/src/pages/part_page.dart delete mode 100644 lib/src/pages/profile_page.dart delete mode 100644 lib/src/repositories/local_secrets_repository.dart rename lib/src/{ => ui}/commons/api_values.dart (69%) create mode 100644 lib/src/ui/commons/colors.dart rename lib/src/{ => ui}/commons/defaults.dart (100%) create mode 100644 lib/src/ui/commons/dimensions.dart rename lib/src/{ => ui}/commons/paths.dart (100%) rename lib/src/{ => ui}/commons/tags.dart (100%) rename lib/src/{ => ui}/localizations/cv_localization.dart (95%) rename lib/src/{ => ui}/localizations/cv_localization_en.dart (97%) rename lib/src/{ => ui}/localizations/cv_localization_fr.dart (97%) create mode 100644 lib/src/ui/models/view_models.dart create mode 100644 lib/src/ui/pages/account_page.dart rename lib/src/{ => ui}/pages/auth_page.dart (54%) create mode 100644 lib/src/ui/pages/elements/entry_profile_page.dart create mode 100644 lib/src/ui/pages/elements/group_profile_page.dart create mode 100644 lib/src/ui/pages/elements/part_profile_page.dart create mode 100644 lib/src/ui/pages/elements/profile_profile_page.dart rename lib/src/{ => ui}/pages/home_page.dart (90%) create mode 100644 lib/src/ui/pages/main_page.dart rename lib/src/{ => ui}/pages/search_page.dart (55%) rename lib/src/{ => ui}/pages/settings_page.dart (79%) create mode 100644 lib/src/ui/pages/splash_page.dart create mode 100644 lib/src/ui/widgets/account_tile_widget.dart rename lib/src/{ => ui}/widgets/arc_banner_image_widget.dart (100%) create mode 100644 lib/src/ui/widgets/elements/element_widget.dart rename lib/src/{widgets => ui/widgets/elements}/entry_list_widget.dart (77%) rename lib/src/{widgets/entry_widget.dart => ui/widgets/elements/entry_profile_widget.dart} (55%) create mode 100644 lib/src/ui/widgets/elements/entry_widget.dart rename lib/src/{widgets => ui/widgets/elements}/group_list_widget.dart (65%) create mode 100644 lib/src/ui/widgets/elements/group_profile_widget.dart create mode 100644 lib/src/ui/widgets/elements/group_widget.dart rename lib/src/{widgets => ui/widgets/elements}/part_list_widget.dart (74%) create mode 100644 lib/src/ui/widgets/elements/part_profile_widget.dart create mode 100644 lib/src/ui/widgets/elements/part_widget.dart rename lib/src/{widgets => ui/widgets/elements}/profile_list_widget.dart (74%) create mode 100644 lib/src/ui/widgets/elements/profile_tile_widget.dart create mode 100644 lib/src/ui/widgets/elements/profile_widget.dart rename lib/src/{ => ui}/widgets/error_widget.dart (93%) rename lib/src/{ => ui}/widgets/initial_circle_avatar_widget.dart (100%) rename lib/src/{ => ui}/widgets/loading_widget.dart (99%) create mode 100644 lib/src/ui/widgets/login_form_widget.dart rename lib/src/{ => ui}/widgets/menu_bottom_sheet_widget.dart (84%) create mode 100644 lib/src/ui/widgets/menu_button_widget.dart rename lib/src/{ => ui}/widgets/profile_image_widget.dart (83%) create mode 100644 lib/src/ui/widgets/register_form_widget.dart rename lib/src/{ => ui}/widgets/rounded_modal.dart (100%) rename lib/src/{ => ui}/widgets/sort_box_widget.dart (100%) rename lib/src/{ => ui}/widgets/sort_dialog_widget.dart (81%) rename lib/src/{ => ui}/widgets/sort_list_tile_widget.dart (94%) create mode 100644 lib/src/ui/widgets/theme_switch_tile_widget.dart delete mode 100644 lib/src/widgets/account_tile_widget.dart delete mode 100644 lib/src/widgets/group_widget.dart delete mode 100644 lib/src/widgets/login_form_widget.dart delete mode 100644 lib/src/widgets/menu_button_widget.dart delete mode 100644 lib/src/widgets/part_widget.dart delete mode 100644 lib/src/widgets/profile_widget.dart delete mode 100644 lib/src/widgets/register_form_widget.dart delete mode 100644 lib/src/widgets/theme_switch_tile_widget.dart delete mode 100644 secrets.json.dist diff --git a/.gitignore b/.gitignore index 7b790f8..0cfb168 100644 --- a/.gitignore +++ b/.gitignore @@ -328,4 +328,4 @@ packages ios/.symlinks/ # Plugins -secrets.json \ No newline at end of file +config.json \ No newline at end of file diff --git a/config.json.dist b/config.json.dist new file mode 100644 index 0000000..1c7ee7d --- /dev/null +++ b/config.json.dist @@ -0,0 +1,5 @@ +{ + "apiServerUrl": "", + "clientId": "", + "clientSecret": "" +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 0a80b16..298eb28 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:social_cv_client_dart_common/managers.dart'; import 'package:social_cv_client_dart_common/repositories.dart'; import 'package:social_cv_client_flutter/src/app.dart'; -import 'package:social_cv_client_flutter/src/repositories/shared_preferences_repository.dart'; -import 'package:social_cv_client_flutter/src/repositories/repositories_provider.dart'; -import 'package:social_cv_client_flutter/src/repositories/local_secrets_repository.dart'; +import 'package:social_cv_client_flutter/src/data/managers/local_configuration_manager.dart'; +import 'package:social_cv_client_flutter/src/data/managers/shared_preferences_manager.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; @@ -19,26 +20,30 @@ Future main() async { void run() async { initLogger(package: "CV App"); - SecretsRepository secretsRepository = LocalSecretsRepository(); - PreferencesRepository preferencesRepository = SharedPreferencesRepository(); + ConfigRepository configRepository = LocalConfigManager(); + PreferencesRepository preferencesRepository = SharedPreferencesManager(); - CVClient cvClient = CVClientImpl( - accessToken: await preferencesRepository.getAccessToken(), - refreshToken: await preferencesRepository.getRefreshToken(), + CVApiManager cvClient = DefaultCVApiManager( + apiBaseUrl: await configRepository.getApiServerUrl(), + apiInterceptor: ApiInterceptor( + accessToken: await preferencesRepository.getAccessToken(), + refreshToken: await preferencesRepository.getRefreshToken(), + ), ); - CVCache cvCache = CVCacheImpl(); - CVRepository cvRepository = CVRepositoryImpl( - client: cvClient, - cache: cvCache, + CVCacheManager cacheManager = DefaultCVCacheManager(); + + CVRepository cvRepository = DefaultCloudCVRepository( + cvApiManager: cvClient, + cvCacheManager: cacheManager, ); runApp( RepositoriesProvider( cvRepository: cvRepository, preferencesRepository: preferencesRepository, - secretsRepository: secretsRepository, - child: CVApp(), + configRepository: configRepository, + child: ConfigWrapperApp(), ), ); } diff --git a/lib/src/app.dart b/lib/src/app.dart index 84bd603..0f84701 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,87 +1,176 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/blocs/main_bloc.dart'; -import 'package:social_cv_client_flutter/src/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/pages/main_page.dart'; -import 'package:social_cv_client_flutter/src/repositories/repositories_provider.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; import 'package:social_cv_client_flutter/src/routes.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/splash_page.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; -class CVApp extends StatelessWidget { - const CVApp({ - Key key, - }) : super(key: key); +import 'domain/blocs/configuration/configuration.dart'; + +class ConfigWrapperApp extends StatefulWidget { + const ConfigWrapperApp({Key key}) : super(key: key); @override - Widget build(BuildContext context) { - logger.info('Building App'); + State createState() => _ConfigWrapperAppState(); +} - RepositoriesProvider repositories = RepositoriesProvider.of(context); +class _ConfigWrapperAppState extends State { + ConfigurationBloc _configBloc; - return BlocProvider( - bloc: ApplicationBloc( - preferencesRepository: repositories.preferencesRepository, - ), - child: BlocProvider( - bloc: AccountBloc( - cvRepository: repositories.cvRepository, - preferencesRepository: repositories.preferencesRepository, - secretRepository: repositories.secretsRepository, - ), - child: _CVThemedApp(), - ), + @override + void initState() { + super.initState(); + _configBloc = ConfigurationBloc(); + _configBloc.dispatch(AppLaunched()); + } + + @override + void dispose() { + _configBloc?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: _configBloc, + builder: (BuildContext context, ConfigurationState state) { + if (state is ConfigLoading) { + return WidgetsApp( + home: SplashPage(), + color: AppColors.primaryColor, + ); + } else if (state is ConfigLoaded) { + return _CVApp(state); + } + return Container(); + }, ); } } -class _CVThemedApp extends StatelessWidget { +class _CVApp extends StatefulWidget { + final ConfigLoaded state; + + _CVApp(this.state); + + @override + State createState() => _CVAppState(); +} + +class _CVAppState extends State<_CVApp> { + final String _tag = "_CVAppState"; + + AppBloc _appBloc; + AccountBloc _accountBloc; + AuthenticationBloc _authBloc; + LoginBloc _loginBloc; + RegisterBloc _registerBloc; + + ConfigLoaded get _state => widget.state; + + @override + void initState() { + super.initState(); + _appBloc = AppBloc(preferencesRepository: _state.preferencesRepository); + + _accountBloc = AccountBloc( + cvRepository: _state.cvRepository, + preferencesRepository: _state.preferencesRepository, + ); + + _authBloc = AuthenticationBloc( + cvRepository: _state.cvRepository, + preferencesRepository: _state.preferencesRepository, + configRepository: _state.configRepository, + accountBloc: _accountBloc, + ); + + _loginBloc = LoginBloc( + cvRepository: _state.cvRepository, + authBloc: _authBloc, + ); + + _registerBloc = RegisterBloc(authenticationBloc: _authBloc); + } + + @override + void dispose() { + _appBloc?.dispose(); + _accountBloc?.dispose(); + _authBloc?.dispose(); + _loginBloc?.dispose(); + _registerBloc?.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - RepositoriesProvider repositories = RepositoriesProvider.of(context); + logger.info('$_tag:build'); + + return BlocBuilder( + bloc: _appBloc, + builder: (BuildContext context, AppState state) { + if (state is AppUninitialized) Text('App Uninitialized'); + + if (state is AppInitialized) return _CVInitializedApp(state: state); + + if (state is AppFailure) return ErrorCard(message: 'Error Setup App'); - BlocProvider _mainPageProvider = BlocProvider( - bloc: MainBloc(), - child: MainPage(), + if (state is AppLoading) + return Center(child: CircularProgressIndicator()); + }, ); + } +} + +class _CVInitializedApp extends StatelessWidget { + final String _tag = "_CVInitializedApp"; + + const _CVInitializedApp({this.state}); + + final AppInitialized state; + + @override + Widget build(BuildContext context) { + logger.info('$_tag:build'); + + RepositoriesProvider repositories = RepositoriesProvider.of(context); ///Routes Routes routes = Routes( - mainPageProvider: _mainPageProvider, cvRepository: repositories.cvRepository, preferencesRepository: repositories.preferencesRepository, - secretsRepository: repositories.secretsRepository, + configRepository: repositories.configRepository, ); - ApplicationBloc _appBloc = BlocProvider.of(context); - - return StreamBuilder( - stream: _appBloc.themeStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - return MaterialApp( - onGenerateTitle: (BuildContext context) => - CVLocalizations.of(context).appName, - theme: _buildCVTheme(snapshot.data), - home: _mainPageProvider, - onGenerateRoute: routes.router.generator, - - ///Use Fluro routes - localizationsDelegates: [ - const CVLocalizationsDelegate(), - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - supportedLocales: [ - const Locale('en'), - const Locale('fr'), - ], - debugShowCheckedModeBanner: false, + return MaterialApp( + onGenerateTitle: (BuildContext context) => + CVLocalizations.of(context).appName, + theme: _buildCVTheme(state.theme), + home: MainPage(), + onGenerateRoute: routes.router.generator, + + ///Use Fluro routes + localizationsDelegates: [ + const CVLocalizationsDelegate(), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: [ + const Locale('en'), + const Locale('fr'), + ], + debugShowCheckedModeBanner: false, // showSemanticsDebugger: true, - ); - }); + ); } ThemeData _buildCVTheme(String theme) { @@ -93,13 +182,13 @@ class _CVThemedApp extends StatelessWidget { } return base.copyWith( - primaryColor: AppColors.kCVPrimaryColor, - primaryColorLight: AppColors.kCVPrimaryColorLight, - primaryColorDark: AppColors.kCVPrimaryColorDark, - accentColor: AppColors.kCVAccentColor, + primaryColor: AppColors.primaryColor, + primaryColorLight: AppColors.primaryColorLight, + primaryColorDark: AppColors.primaryColorDark, + accentColor: AppColors.accentColor, buttonColor: (theme != ThemeType.DARK) - ? AppColors.kCVWhite - : AppColors.kCVPrimaryColorDark, + ? AppColors.white + : AppColors.primaryColorDark, inputDecorationTheme: InputDecorationTheme( border: OutlineInputBorder(), ), diff --git a/lib/src/blocs/bloc_provider.dart b/lib/src/blocs/bloc_provider.dart deleted file mode 100644 index 13dfe0c..0000000 --- a/lib/src/blocs/bloc_provider.dart +++ /dev/null @@ -1,40 +0,0 @@ -///Generic Interface for all BLoCs -import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; - -///Generic BLoC provider -class BlocProvider extends StatefulWidget { - BlocProvider({ - Key key, - @required this.child, - @required this.bloc, - }) : super(key: key); - - final T bloc; - final Widget child; - - @override - _BlocProviderState createState() => _BlocProviderState(); - - static T of(BuildContext context) { - final type = _typeOf>(); - BlocProvider provider = context.ancestorWidgetOfExactType(type); - - return provider.bloc; - } - - static Type _typeOf() => T; -} - -class _BlocProviderState extends State> { - @override - void dispose() { - widget.bloc.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return widget.child; - } -} diff --git a/lib/src/blocs/main_bloc.dart b/lib/src/blocs/main_bloc.dart deleted file mode 100644 index d279c4d..0000000 --- a/lib/src/blocs/main_bloc.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:rxdart/rxdart.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; - -enum TabType { - HOME_TAB, - ACCOUNT_TAB, -} - -class MainBloc extends BlocBase { - MainBloc() : super() { - _tabController.add(TabType.HOME_TAB); - } - - ///Reactive variables - final _tabController = BehaviorSubject(); - - ///Streams - Observable get tabStream => _tabController.stream; - - ///Sinks - Sink get tab => _tabController.sink; - - /* Functions */ - - ///Human function - Function(TabType) get changeTab => tab.add; - - @override - void dispose() { - _tabController.close(); - } -} diff --git a/lib/src/commons/colors.dart b/lib/src/commons/colors.dart deleted file mode 100644 index e8f87eb..0000000 --- a/lib/src/commons/colors.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; - -class AppColors { - ///Colors - static const Color kCVBlue = Colors.blue; - static const Color kCVOrange = Colors.deepOrange; - static const Color kCVPink = Colors.pink; - static const Color kCVWhite = Colors.white; - static const Color kCVBlack = Colors.black; - - ///Basics - static const Color kCVPrimaryColor = const Color(0xFF2196f3); - static const Color kCVPrimaryColorLight = const Color(0xFF6ec6ff); - static const Color kCVPrimaryColorDark = const Color(0xFF0069c0); - static const Color kCVTextOnPrimary = const Color(0xFFFFFFFF); - static const Color kCVAccentColor = const Color(0xFFFF5722); - static const Color kCVAccentColorLight = const Color(0xFFff8a50); - static const Color kCVAccentColorDark = const Color(0xFFc41c00); - static const Color kCVTextOnAccent = const Color(0xFFFFFFFF); - - static const Color kCVBackgroundColor = const Color(0xFFFFFFFF); - static const Color kCVBackgroundColorLight = kCVBackgroundColor; - static const Color kCVBackgroundColorDark = const Color(0xFF353A3A); - - ///Cards - static const Color kCVCardBackgroundColor = const Color(0xFFFFFFFF); - static const Color kCVCardBackgroundColorLight = kCVBackgroundColor; - static const Color kCVCardBackgroundColorDark = const Color(0xFF353A3A); - - /// Misc - static const Color kCVErrorRed = Colors.red; - - /// Auth Stuff - static const Color loginGradientEnd = kCVPrimaryColorLight; - static const Color loginGradientStart = kCVPrimaryColorDark; -} diff --git a/lib/src/commons/dimensions.dart b/lib/src/commons/dimensions.dart deleted file mode 100644 index c5a1786..0000000 --- a/lib/src/commons/dimensions.dart +++ /dev/null @@ -1,27 +0,0 @@ -class AppDimensions { - ///Group - static const double kCVGroupPadding = 5.0; - - ///Entry - static const double kCVEntryPadding = 10.0; - static const double kCVEntryTagSpacing = 4.0; - static const double kCVEntryCardElevation = 2.0; - static const double kCVEntryEventHeight = 200.0; - static const double kCVEntryEventHWidth = 300.0; - - static const double kCVHorizontalEntryListHeight = kCVEntryEventHeight; - static const double kCVHorizontalGroupListHeight = 300.0; - - ///Sort Dialog - static const double kCVSortDialogWidth = 200.0; - static const double kCVSortDialogHeight = 300.0; - - ///List - static const double kCVListHeaderDefaultHeightMax = 40.0; - static const double kCVListHeaderDefaultHeightMin = 40.0; - - ///Profile - static const double kCVProfileAvatarMin = 5.0; - static const double kCVProfileAvatarMax = 50.0; - static const double kCVProfileAvatarElevation = 2.0; -} diff --git a/lib/src/data/managers/local_configuration_manager.dart b/lib/src/data/managers/local_configuration_manager.dart new file mode 100644 index 0000000..91f56ea --- /dev/null +++ b/lib/src/data/managers/local_configuration_manager.dart @@ -0,0 +1,36 @@ +import 'dart:async' show Future; +import 'dart:convert' show json; + +import 'package:flutter/services.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; + +/// From https://medium.com/@sokrato/storing-your-secret-keys-in-flutter-c0b9af1c0f69 + +class LocalConfigManager implements ConfigRepository { + static const secretPath = 'config.json'; + + String _apiServerUrl; + String _clientId; + String _clientSecret; + + LocalConfigManager() { + rootBundle.loadStructuredData( + secretPath, + (jsonStr) { + Map jsonMap = json.decode(jsonStr); + _apiServerUrl = jsonMap["serverUrl"]; + _clientId = jsonMap["clientId"]; + _clientSecret = jsonMap["clientSecret"]; + }, + ); + } + + @override + Future getApiServerUrl() => Future.value(_apiServerUrl); + + @override + Future getClientId() => Future.value(_clientId); + + @override + Future getClientSecret() => Future.value(_clientSecret); +} diff --git a/lib/src/repositories/shared_preferences_repository.dart b/lib/src/data/managers/shared_preferences_manager.dart similarity index 96% rename from lib/src/repositories/shared_preferences_repository.dart rename to lib/src/data/managers/shared_preferences_manager.dart index 9edc4b6..bc38ab7 100644 --- a/lib/src/repositories/shared_preferences_repository.dart +++ b/lib/src/data/managers/shared_preferences_manager.dart @@ -1,7 +1,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:social_cv_client_dart_common/repositories.dart'; -class SharedPreferencesRepository implements PreferencesRepository { +class SharedPreferencesManager implements PreferencesRepository { static const String KEY_OAUTH_ACCESS_TOKEN = 'OAUTH_ACCESS_TOKEN'; static const String KEY_OAUTH_ACCESS_TOKEN_EXPIRATION = 'OAUTH_ACCESS_TOKEN_EXPIRATION'; @@ -70,9 +70,9 @@ class SharedPreferencesRepository implements PreferencesRepository { return prefs.getString(KEY_OAUTH_REFRESH_TOKEN_EXPIRATION) ?? ''; } - Future setRefreshTokenExpiration(String refreshTokenExpiration) async { + Future setRefreshTokenExpiration(int refreshTokenExpiration) async { final SharedPreferences prefs = await _prefs; - return prefs.setString( + return prefs.setInt( KEY_OAUTH_REFRESH_TOKEN_EXPIRATION, refreshTokenExpiration); } diff --git a/lib/src/repositories/repositories_provider.dart b/lib/src/data/repositories/repositories_provider.dart similarity index 56% rename from lib/src/repositories/repositories_provider.dart rename to lib/src/data/repositories/repositories_provider.dart index b8ecd14..5182987 100644 --- a/lib/src/repositories/repositories_provider.dart +++ b/lib/src/data/repositories/repositories_provider.dart @@ -5,13 +5,17 @@ class RepositoriesProvider extends InheritedWidget { const RepositoriesProvider({ Key key, Widget child, - this.cvRepository, - this.secretsRepository, - this.preferencesRepository, - }) : super(key: key, child: child); + @required this.cvRepository, + @required this.configRepository, + @required this.preferencesRepository, + }) : assert(cvRepository != null, 'No $CVRepository given'), + assert(configRepository != null, 'No $ConfigRepository given'), + assert( + preferencesRepository != null, 'No $PreferencesRepository given'), + super(key: key, child: child); final CVRepository cvRepository; - final SecretsRepository secretsRepository; + final ConfigRepository configRepository; final PreferencesRepository preferencesRepository; @override diff --git a/lib/src/domain/blocs/configuration/configuration.dart b/lib/src/domain/blocs/configuration/configuration.dart new file mode 100644 index 0000000..37baa13 --- /dev/null +++ b/lib/src/domain/blocs/configuration/configuration.dart @@ -0,0 +1,4 @@ +export 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration_state.dart'; + +export './configuration_bloc.dart'; +export './configuration_event.dart'; diff --git a/lib/src/domain/blocs/configuration/configuration_bloc.dart b/lib/src/domain/blocs/configuration/configuration_bloc.dart new file mode 100644 index 0000000..3b43659 --- /dev/null +++ b/lib/src/domain/blocs/configuration/configuration_bloc.dart @@ -0,0 +1,68 @@ +import 'package:bloc/bloc.dart'; +import 'package:social_cv_client_dart_common/managers.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/src/data/managers/local_configuration_manager.dart'; +import 'package:social_cv_client_flutter/src/data/managers/shared_preferences_manager.dart'; +import 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration.dart'; + +class ConfigurationBloc extends Bloc { + final String _tag = '$ConfigurationBloc'; + + ConfigurationBloc() : super(); + + /// Interceptors + ApiInterceptor _apiInterceptor; + + /// Managers + CVApiManager _cvApiManager; + CVCacheManager _cvCacheManager; + + /// Repositories + CVRepository _cvRepository; + PreferencesRepository _preferencesRepository; + ConfigRepository _configRepository; + + @override + ConfigurationState get initialState => ConfigLoading(); + + @override + Stream mapEventToState(ConfigurationEvent event) async* { + if (event is AppLaunched) { + yield* _mapAppLaunchedEventToState(); + } + } + + Stream _mapAppLaunchedEventToState() async* { + try { + yield ConfigLoading(); + _preferencesRepository = SharedPreferencesManager(); + + /// Interceptors + _apiInterceptor = ApiInterceptor( + accessToken: await _preferencesRepository.getAccessToken(), + ); + + /// Managers + _cvApiManager = DefaultCVApiManager( + apiInterceptor: _apiInterceptor, + apiBaseUrl: await _configRepository.getApiServerUrl(), + ); + _cvCacheManager = DefaultCVCacheManager(); + + /// Repositories + _configRepository = LocalConfigManager(); + _cvRepository = DefaultCloudCVRepository( + cvApiManager: _cvApiManager, + cvCacheManager: _cvCacheManager, + ); + + yield ConfigLoaded( + cvRepository: _cvRepository, + preferencesRepository: _preferencesRepository, + configRepository: _configRepository, + ); + } catch (error) { + print(error.toString()); + } + } +} diff --git a/lib/src/domain/blocs/configuration/configuration_event.dart b/lib/src/domain/blocs/configuration/configuration_event.dart new file mode 100644 index 0000000..053ba50 --- /dev/null +++ b/lib/src/domain/blocs/configuration/configuration_event.dart @@ -0,0 +1,7 @@ +import 'package:equatable/equatable.dart'; + +abstract class ConfigurationEvent extends Equatable { + ConfigurationEvent([List props = const []]) : super(props); +} + +class AppLaunched extends ConfigurationEvent {} diff --git a/lib/src/domain/blocs/configuration/configuration_state.dart b/lib/src/domain/blocs/configuration/configuration_state.dart new file mode 100644 index 0000000..8389d38 --- /dev/null +++ b/lib/src/domain/blocs/configuration/configuration_state.dart @@ -0,0 +1,25 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; + +abstract class ConfigurationState extends Equatable { + ConfigurationState([List props = const []]) : super(props); +} + +class ConfigLoading extends ConfigurationState {} + +class ConfigLoaded extends ConfigurationState { + final CVRepository cvRepository; + final PreferencesRepository preferencesRepository; + final ConfigRepository configRepository; + + ConfigLoaded({ + @required this.cvRepository, + @required this.preferencesRepository, + @required this.configRepository, + }) : assert(cvRepository != null, 'No $CVRepository given'), + assert( + preferencesRepository != null, 'No $PreferencesRepository given'), + assert(configRepository != null, 'No $ConfigRepository given'), + super([cvRepository, preferencesRepository, configRepository]); +} diff --git a/lib/src/models/view_models.dart b/lib/src/models/view_models.dart deleted file mode 100644 index 021e072..0000000 --- a/lib/src/models/view_models.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// TODO : Added all ViewModels -/// TODO : Repositories have to be base on these ViewModels diff --git a/lib/src/pages/account_page.dart b/lib/src/pages/account_page.dart deleted file mode 100644 index ba60af4..0000000 --- a/lib/src/pages/account_page.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/repositories/repositories_provider.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/profile_list_widget.dart'; - -class AccountPage extends StatelessWidget { - const AccountPage({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - logger.info('Building AccountPage'); - - AccountBloc _accountBloc = BlocProvider.of(context); - - return SafeArea( - left: false, - right: false, - child: Stack( - children: [ - StreamBuilder( - stream: _accountBloc.isFetchingAccountDetailsStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.data == true) { - return LinearProgressIndicator(); - } - return Container(); - }, - ), - _AccountPageDetails(), - ], - ), - ); - } -} - -class _AccountPageDetails extends StatelessWidget { - @override - Widget build(BuildContext context) { - AccountBloc _accountBloc = BlocProvider.of(context); - - return StreamBuilder( - stream: _accountBloc.isAuthenticatedStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - if (snapshot.data == true) return _AccountPageDetailsConnected(); - if (snapshot.data == false) return _AccountPageDetailsNotConnected(); - } else if (snapshot.hasError) { - return Container( - child: - ErrorContent(message: translateError(context, snapshot.error)), - ); - } - return Container(); - }, - ); - } -} - -class _AccountPageDetailsNotConnected extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Center( - child: RaisedButton( - child: Text(CVLocalizations.of(context).authSignInCTA), - onPressed: () => navigateToLogin(context), - ), - ); - } -} - -class _AccountPageDetailsConnected extends StatelessWidget { - @override - Widget build(BuildContext context) { - AccountBloc _accountBloc = BlocProvider.of(context); - RepositoriesProvider _repositories = RepositoriesProvider.of(context); - - return StreamBuilder( - stream: _accountBloc.accountDetailsStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - return ListView( - children: [ - ExpansionTile( - leading: Icon(MdiIcons.accountBoxMultiple), - title: Text(CVLocalizations.of(context).accountMyProfile), - children: [ - BlocProvider( - bloc: ProfileListBloc( - cvRepository: _repositories.cvRepository, - preferencesRepository: - _repositories.preferencesRepository, - ), - child: ProfileListWidget( - fromUserModel: snapshot.data, - showOptions: false, - shrinkWrap: true, - physics: ClampingScrollPhysics(), - ), - ), - ], - ), - ], - ); - } else if (snapshot.hasError) { - return ErrorCard(message: translateError(context, snapshot.error)); - } - return Container(); - }, - ); - } -} diff --git a/lib/src/pages/entry_page.dart b/lib/src/pages/entry_page.dart deleted file mode 100644 index 3dbabbb..0000000 --- a/lib/src/pages/entry_page.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/repositories/repositories_provider.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/loading_widget.dart'; - -class EntryPage extends StatelessWidget { - const EntryPage({ - Key key, - @required this.entryId, - }) : assert(entryId != null), - super(key: key); - - final String entryId; - - @override - Widget build(BuildContext context) { - EntryBloc _entryBloc = BlocProvider.of(context); - RepositoriesProvider _repositories = RepositoriesProvider.of(context); - - _entryBloc.fetchEntry(entryId); - - return Scaffold( - appBar: AppBar( - title: StreamBuilder( - stream: _entryBloc.entryStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return Text('Error : ${snapshot.error.toString()}'); - } else if (snapshot.hasData) { - EntryModel entryModel = snapshot.data; - return Text(entryModel.name); - } - return LoadingShadowContent( - numberOfTitleLines: 1, - numberOfContentLines: 0, - ); - }, - ), - ), - body: _EntryPageEntryBody(), - ); - } -} - -class _EntryPageEntryBody extends StatelessWidget { - @override - Widget build(BuildContext context) { - EntryBloc _entryBloc = BlocProvider.of(context); - - return Stack( - children: [ - StreamBuilder( - stream: _entryBloc.isFetchingEntryStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.data == true) { - return LinearProgressIndicator(); - } - return Container(); - }, - ), - StreamBuilder( - stream: _entryBloc.entryStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return ErrorCard( - message: translateError(context, snapshot.error)); - } else if (snapshot.hasData) { - EntryModel entryModel = snapshot.data; - - return ListView( - children: [ - ListTile( - title: Text('name'), - subtitle: Text(entryModel.name), - ), - ListTile( - title: Text('type'), - subtitle: Text(entryModel.type), - ), - ListTile( - title: Text('content'), - subtitle: Text(entryModel.content), - ), - ], - ); - } - return LoadingShadowContent( - numberOfContentLines: 2, - padding: EdgeInsets.all(10.0), - ); - }, - ), - ], - ); - } -} diff --git a/lib/src/pages/group_page.dart b/lib/src/pages/group_page.dart deleted file mode 100644 index fed30db..0000000 --- a/lib/src/pages/group_page.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/entry_list_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/loading_widget.dart'; - -class GroupPage extends StatelessWidget { - const GroupPage({ - Key key, - @required this.groupId, - }) : assert(groupId != null), - super(key: key); - - final String groupId; - - @override - Widget build(BuildContext context) { - GroupBloc _groupBloc = BlocProvider.of(context); - _groupBloc.fetchGroup(groupId); - - return Scaffold( - appBar: AppBar( - title: StreamBuilder( - stream: _groupBloc.groupStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return Text('Error : ${snapshot.error.toString()}'); - } else if (snapshot.hasData) { - GroupModel groupModel = snapshot.data; - return Text(groupModel.name); - } - return LoadingShadowContent( - numberOfTitleLines: 1, - numberOfContentLines: 0, - ); - }, - ), - ), - body: _GroupPageGroupBody(), - ); - } -} - -class _GroupPageGroupBody extends StatelessWidget { - @override - Widget build(BuildContext context) { - GroupBloc _profileGroupBloc = BlocProvider.of(context); - - return Stack( - children: [ - StreamBuilder( - stream: _profileGroupBloc.isFetchingGroupStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.data == true) { - return LinearProgressIndicator(); - } - return Container(); - }, - ), - StreamBuilder( - stream: _profileGroupBloc.groupStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return ErrorCard( - message: translateError(context, snapshot.error), - ); - } else if (snapshot.hasData) { - return BlocProvider( - bloc: EntryListBloc(), - child: EntryListWidget( - fromGroupModel: snapshot.data, - showOptions: true, - ), - ); - } - return LoadingShadowContent( - numberOfTitleLines: 0, - numberOfContentLines: 2, - padding: EdgeInsets.all(10.0), - ); - }, - ), - ], - ); - } -} diff --git a/lib/src/pages/main_page.dart b/lib/src/pages/main_page.dart deleted file mode 100644 index bb6652a..0000000 --- a/lib/src/pages/main_page.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/blocs/main_bloc.dart'; -import 'package:social_cv_client_flutter/src/commons/tags.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/pages/account_page.dart'; -import 'package:social_cv_client_flutter/src/pages/home_page.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/widgets/menu_button_widget.dart'; - -class MainPage extends StatelessWidget { - const MainPage({ - Key key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - logger.info('Building MainPage'); - return Scaffold( - appBar: AppBar( - title: Text(CVLocalizations.of(context).appName), - centerTitle: true, - actions: [ - MenuButton(), - ], - ), - body: _MainPageBody(), - floatingActionButton: FloatingActionButton.extended( - heroTag: kHeroSearchFAB, - icon: Icon(Icons.search), - label: Text(CVLocalizations.of(context).search), - foregroundColor: Colors.white, - onPressed: () => navigateToSearch(context), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, - bottomNavigationBar: _MainPageBottomNavigationBar(), - ); - } -} - -class _MainPageBody extends StatelessWidget { - @override - Widget build(BuildContext context) { - HomePage _homePage = HomePage(); - AccountPage _accountPage = AccountPage(); - - MainBloc _mainBloc = BlocProvider.of(context); - return StreamBuilder( - stream: _mainBloc.tabStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - return Container( - child: Stack( - children: [ - Offstage( - offstage: snapshot.data != TabType.HOME_TAB, - child: _homePage, - ), - Offstage( - offstage: snapshot.data != TabType.ACCOUNT_TAB, - child: _accountPage, - ), - ], - ), - ); - } else { - return Container(); - } - }, - ); - } -} - -class _MainPageBottomNavigationBar extends StatelessWidget { - @override - Widget build(BuildContext context) { - MainBloc _mainBloc = BlocProvider.of(context); - return Theme( - data: Theme.of(context).copyWith( - ///sets the background color of the `BottomNavigationBar` - canvasColor: Theme.of(context).primaryColor, - - ///sets the active color of the `BottomNavigationBar` if `Brightness` is light - primaryColor: Theme.of(context).selectedRowColor, - textTheme: Theme.of(context).primaryTextTheme, - ), - - ///sets the inactive color of the `BottomNavigationBar` - child: StreamBuilder( - stream: _mainBloc.tabStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - var index = 0; - if (snapshot.hasData) { - if (snapshot.data == TabType.HOME_TAB) index = 0; - if (snapshot.data == TabType.ACCOUNT_TAB) index = 2; - } - return BottomNavigationBar( - currentIndex: index, - onTap: (index) { - if (index == 0) { - _mainBloc.changeTab(TabType.HOME_TAB); - } else if (index == 2) { - _mainBloc.changeTab(TabType.ACCOUNT_TAB); - } - }, - type: BottomNavigationBarType.fixed, - items: [ - BottomNavigationBarItem( - icon: Icon(MdiIcons.homeOutline), - activeIcon: Icon(MdiIcons.home), - title: Text(CVLocalizations.of(context).home), - ), - const BottomNavigationBarItem( - ///Fake item - icon: SizedBox(), - title: SizedBox(), - ), - BottomNavigationBarItem( - icon: Icon(MdiIcons.accountOutline), - activeIcon: Icon(MdiIcons.account), - title: Text(CVLocalizations.of(context).account), - ), - ], - ); - }, - ), - ); - } -} diff --git a/lib/src/pages/part_page.dart b/lib/src/pages/part_page.dart deleted file mode 100644 index 7c93d18..0000000 --- a/lib/src/pages/part_page.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/group_list_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/loading_widget.dart'; - -class PartPage extends StatelessWidget { - const PartPage({ - Key key, - @required this.partId, - }) : assert(partId != null), - super(key: key); - - final String partId; - - @override - Widget build(BuildContext context) { - PartBloc _partBloc = BlocProvider.of(context); - _partBloc.fetchPart(partId); - - return Scaffold( - appBar: AppBar( - title: StreamBuilder( - stream: _partBloc.partStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return Text('Error : ${snapshot.error.toString()}'); - } else if (snapshot.hasData) { - PartModel partModel = snapshot.data; - return Text(partModel.name); - } - return LoadingShadowContent( - numberOfTitleLines: 1, - numberOfContentLines: 0, - ); - }, - ), - ), - body: _PartPagePartBody(), - ); - } -} - -class _PartPagePartBody extends StatelessWidget { - @override - Widget build(BuildContext context) { - PartBloc _partBloc = BlocProvider.of(context); - - return Stack( - children: [ - StreamBuilder( - stream: _partBloc.isFetchingPartStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.data == true) { - return LinearProgressIndicator(); - } - return Container(); - }, - ), - StreamBuilder( - stream: _partBloc.partStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return ErrorCard( - message: translateError(context, snapshot.error), - ); - } else if (snapshot.hasData) { - return BlocProvider( - bloc: GroupListBloc(), - child: GroupListWidget( - fromPartModel: snapshot.data, - showOptions: true, - ), - ); - } - return LoadingShadowContent( - numberOfTitleLines: 0, - numberOfContentLines: 2, - padding: EdgeInsets.all(10.0), - ); - }, - ), - ], - ); - } -} diff --git a/lib/src/pages/profile_page.dart b/lib/src/pages/profile_page.dart deleted file mode 100644 index 75447ba..0000000 --- a/lib/src/pages/profile_page.dart +++ /dev/null @@ -1,255 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/commons/api_values.dart'; -import 'package:social_cv_client_flutter/src/commons/dimensions.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/initial_circle_avatar_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/part_widget.dart'; - -/// TODO : Build owner interaction with ProfileModel.owner - -class ProfilePage extends StatelessWidget { - const ProfilePage({ - Key key, - @required this.profileId, - }) : assert(profileId != null), - super(key: key); - - final String profileId; - - @override - Widget build(BuildContext context) { - logger.info('Building ProfilePage'); - - ProfileBloc _profileBloc = BlocProvider.of(context); - _profileBloc.fetchProfileDetails(profileId); - - return Scaffold( - body: StreamBuilder( - stream: _profileBloc.profileStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - List slivers = []; - slivers.add(_ProfilePageAppBar()); - - if (snapshot.hasError) { - slivers.add( - SliverToBoxAdapter( - child: - ErrorCard(message: translateError(context, snapshot.error)), - ), - ); - } else if (snapshot.hasData) { - ProfileModel profileModel = snapshot.data; - if (profileModel.type == kCVProfileType1) { - slivers.addAll([ - _ProfilePageSliver( - partId: profileModel.parts['main'], - ) - ]); - } else if (profileModel.type == kCVProfileType2) { - slivers.addAll([ - _ProfilePageSliver( - partId: profileModel.parts['header'], - ), - _ProfilePageSliver( - partId: profileModel.parts['main'], - ) - ]); - } else if (profileModel.type == kCVProfileType3) { - slivers.addAll([ - _ProfilePageSliver( - partId: profileModel.parts['side'], - ), - _ProfilePageSliver( - partId: profileModel.parts['main'], - ) - ]); - } else if (profileModel.type == kCVProfileType4) { - slivers.addAll([ - _ProfilePageSliver( - partId: profileModel.parts['header'], - ), - _ProfilePageSliver( - partId: profileModel.parts['side'], - ), - _ProfilePageSliver( - partId: profileModel.parts['main'], - ) - ]); - } else { - slivers.add( - SliverToBoxAdapter( - child: ErrorCard( - message: CVLocalizations.of(context).notSupported, - ), - ), - ); - } - } else { - slivers.add( - SliverToBoxAdapter( - child: LoadingShadowContent( - numberOfContentLines: 3, - ), - ), - ); - } - - return CustomScrollView( - slivers: slivers, - ); - }, - ), - ); - } -} - -class _ProfilePageAppBar extends StatelessWidget { - @override - Widget build(BuildContext context) { - ProfileBloc _profileBloc = BlocProvider.of(context); - - return SliverAppBar( - expandedHeight: 200, - pinned: true, - elevation: 2.0, - floating: false, - bottom: PreferredSize( - preferredSize: Size.fromHeight(6.0), - child: StreamBuilder( - stream: _profileBloc.isFetchingProfileStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.data == true) { - return LinearProgressIndicator(); - } - return Container(); - }, - ), - ), - flexibleSpace: StreamBuilder( - stream: _profileBloc.profileStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - Widget titleWidget = LoadingShadowContent( - numberOfTitleLines: 1, - numberOfContentLines: 0, - ); - - Widget subtitleWidget = LoadingShadowContent( - numberOfTitleLines: 1, - numberOfContentLines: 0, - ); - - Widget avatarWidget = InitialCircleAvatar( - elevation: AppDimensions.kCVProfileAvatarElevation, - maxRadius: AppDimensions.kCVProfileAvatarMax, - minRadius: AppDimensions.kCVProfileAvatarMin, - backgroundImage: AssetImage('images/default-avatar.png'), - ); - - Widget bannerWidget = Image.asset( - 'images/default-banner.jpg', - fit: BoxFit.cover, - ); - - if (snapshot.hasData) { - ProfileModel profileModel = snapshot.data; - titleWidget = Text( - profileModel.title, - ); - subtitleWidget = Text( - profileModel.subtitle, - ); - avatarWidget = InitialCircleAvatar( - text: profileModel.title, - elevation: AppDimensions.kCVProfileAvatarElevation, - maxRadius: AppDimensions.kCVProfileAvatarMax, - minRadius: AppDimensions.kCVProfileAvatarMin, - backgroundImage: NetworkImage(profileModel.picture), - ); - - bannerWidget = Image.network( - profileModel.cover, - fit: BoxFit.cover, - ); - } - - Widget profileInfo = Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - titleWidget, - subtitleWidget, - ], - ); - - Widget backgroundWidget = Stack( - children: [ - Stack( - fit: StackFit.expand, - children: [ - bannerWidget, - - ///This gradient ensures that the toolbar icons are distinct - ///against the background image. - const DecoratingBackground(), - ], - ), - Center(heightFactor: 2, child: avatarWidget), - ], - ); - return FlexibleSpaceBar( - background: backgroundWidget, - collapseMode: CollapseMode.parallax, - centerTitle: true, - title: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - profileInfo, - ], - ), - ), - ); - }, - ), - ); - } -} - -class _ProfilePageSliver extends StatelessWidget { - const _ProfilePageSliver({@required this.partId}) : assert(partId != null); - - final String partId; - - @override - Widget build(BuildContext context) { - return SliverToBoxAdapter( - child: BlocProvider( - bloc: PartBloc(), - child: PartWidget(fromId: partId), - ), - ); - } -} - -class DecoratingBackground extends StatelessWidget { - const DecoratingBackground({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return DecoratedBox( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment(0.0, -1.0), - end: Alignment(0.0, 5), - colors: [Color(0x60000000), Color(0x00000000)], - ), - ), - ); - } -} diff --git a/lib/src/repositories/local_secrets_repository.dart b/lib/src/repositories/local_secrets_repository.dart deleted file mode 100644 index e3b71b9..0000000 --- a/lib/src/repositories/local_secrets_repository.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:async' show Future; -import 'dart:convert' show json; - -import 'package:flutter/services.dart' show rootBundle; -import 'package:social_cv_client_dart_common/repositories.dart'; - -/// From https://medium.com/@sokrato/storing-your-secret-keys-in-flutter-c0b9af1c0f69 - -class LocalSecretsRepository implements SecretsRepository { - static const secretPath = 'secrets.json'; - - @override - Future loadclientId() async { - return (await _load()).clientId; - } - - @override - Future loadclientSecret() async { - return (await _load()).clientSecret; - } - - static Future _load() { - return rootBundle.loadStructuredData( - secretPath, - (jsonStr) async { - final secret = Secret.fromJson(json.decode(jsonStr)); - return secret; - }, - ); - } -} - -/// TODO : Remove 'Secret' class -class Secret { - Secret({ - this.clientId = '', - this.clientSecret = '', - }) : assert(clientId != null), - assert(clientSecret != null); - - final String clientId; - final String clientSecret; - - factory Secret.fromJson(Map jsonMap) { - return new Secret( - clientId: jsonMap['clientId'], - clientSecret: jsonMap['clientSecret'], - ); - } -} diff --git a/lib/src/routes.dart b/lib/src/routes.dart index 45f0116..1bb5c2d 100644 --- a/lib/src/routes.dart +++ b/lib/src/routes.dart @@ -1,43 +1,44 @@ import 'package:fluro/fluro.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/repositories.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/blocs/main_bloc.dart'; -import 'package:social_cv_client_flutter/src/commons/paths.dart'; -import 'package:social_cv_client_flutter/src/pages/entry_page.dart'; -import 'package:social_cv_client_flutter/src/pages/group_page.dart'; -import 'package:social_cv_client_flutter/src/pages/auth_page.dart'; -import 'package:social_cv_client_flutter/src/pages/part_page.dart'; -import 'package:social_cv_client_flutter/src/pages/profile_page.dart'; -import 'package:social_cv_client_flutter/src/pages/search_page.dart'; -import 'package:social_cv_client_flutter/src/pages/settings_page.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/paths.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/auth_page.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/elements/entry_profile_page.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/elements/group_profile_page.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/elements/part_profile_page.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/search_page.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/settings_page.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; class Routes { + final String _TAG = 'Routes'; final Router router = Router(); Routes({ - this.mainPageProvider, - this.cvRepository, - this.secretsRepository, - this.preferencesRepository, - }) { + @required this.cvRepository, + @required this.configRepository, + @required this.preferencesRepository, + }) : assert(cvRepository != null, 'No CV repository given'), + assert(configRepository != null, 'No config repository given'), + assert(preferencesRepository != null, + 'No preferences repositories given') { _defineRoutes(); } - final BlocProvider mainPageProvider; final CVRepository cvRepository; - final SecretsRepository secretsRepository; + final ConfigRepository configRepository; final PreferencesRepository preferencesRepository; void _defineRoutes() { + logger.info('$_TAG:_defineRoutes'); + router.define( AppPaths.kPathHome, handler: Handler( handlerFunc: (BuildContext context, Map params) { logger.info('Navigate to ${AppPaths.kPathHome}'); - return mainPageProvider; + return MainPage(); }, ), ); @@ -47,13 +48,11 @@ class Routes { handler: Handler( handlerFunc: (BuildContext context, Map params) { logger.info('Navigate to ${AppPaths.kPathAccount}'); - return mainPageProvider; + return MainPage(); }, ), ); - ///TODO : Check other solution to avoid LoginBloc recreation when - ///LoginPage rebuild (caused by input change) router.define( AppPaths.kPathLogin, handler: Handler( @@ -74,8 +73,6 @@ class Routes { ), ); - ///TODO : Check other solution to avoid SearchBloc recreation when - ///SearchPage rebuild (caused by input change) router.define( AppPaths.kPathSearch, handler: Handler( @@ -95,10 +92,7 @@ class Routes { logger.info('Navigate to ${AppPaths.kPathProfiles}/$profileId'); - return BlocProvider( - bloc: ProfileBloc(cvRepository: cvRepository), - child: ProfilePage(profileId: profileId), - ); + return ProfileProfilePage(profileId: profileId); }, ), ); @@ -111,10 +105,7 @@ class Routes { logger.info('Navigate to ${AppPaths.kPathParts}/$partId'); - return BlocProvider( - bloc: PartBloc(cvRepository: cvRepository), - child: PartPage(partId: partId), - ); + return PartProfilePage(partId: partId); }, ), ); @@ -127,10 +118,7 @@ class Routes { logger.info('Navigate to ${AppPaths.kPathGroups}/$groupId'); - return BlocProvider( - bloc: GroupBloc(cvRepository: cvRepository), - child: GroupPage(groupId: groupId), - ); + return GroupPage(groupId: groupId); }, ), ); @@ -143,10 +131,7 @@ class Routes { logger.info('Navigate to ${AppPaths.kPathEntries}/$entryId'); - return BlocProvider( - bloc: EntryBloc(cvRepository: cvRepository), - child: EntryPage(entryId: entryId), - ); + return EntryPage(entryId: entryId); }, ), ); diff --git a/lib/src/commons/api_values.dart b/lib/src/ui/commons/api_values.dart similarity index 69% rename from lib/src/commons/api_values.dart rename to lib/src/ui/commons/api_values.dart index 5997d89..331c088 100644 --- a/lib/src/commons/api_values.dart +++ b/lib/src/ui/commons/api_values.dart @@ -1,8 +1,8 @@ ///Profile Types -const kCVProfileType1 = 'PROFILE_TYPE_MAIN'; -const kCVProfileType2 = 'PROFILE_TYPE_HEADER_MAIN'; -const kCVProfileType3 = 'PROFILE_TYPE_MAIN_SIDE'; -const kCVProfileType4 = 'PROFILE_TYPE_HEADER_MAIN_SIDE'; +const kCVProfileTypeMain = 'PROFILE_TYPE_MAIN'; +const kCVProfileTypeHeaderMain = 'PROFILE_TYPE_HEADER_MAIN'; +const kCVProfileTypeMainSide = 'PROFILE_TYPE_MAIN_SIDE'; +const kCVProfileTypeHeaderMainSide = 'PROFILE_TYPE_HEADER_MAIN_SIDE'; ///Part Types const kCVPartTypeListHorizontal = 'PART_TYPE_LIST_HORIZONTAL'; diff --git a/lib/src/ui/commons/colors.dart b/lib/src/ui/commons/colors.dart new file mode 100644 index 0000000..54f744b --- /dev/null +++ b/lib/src/ui/commons/colors.dart @@ -0,0 +1,40 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class AppColors { + ///Colors + static const Color blue = Colors.blue; + static const Color orange = Colors.deepOrange; + static const Color pink = Colors.pink; + static const Color white = Colors.white; + static const Color black = Colors.black; + + /// Misc + static const Color errorColor = Colors.red; + static const Color warningColor = Colors.yellow; + static const Color successColor = Colors.green; + + ///Basics + static const Color primaryColor = const Color(0xFF2196f3); + static const Color primaryColorLight = const Color(0xFF6ec6ff); + static const Color primaryColorDark = const Color(0xFF0069c0); + static const Color textOnPrimary = const Color(0xFFFFFFFF); + static const Color accentColor = const Color(0xFFFF5722); + static const Color accentColorLight = const Color(0xFFff8a50); + static const Color accentColorDark = const Color(0xFFc41c00); + static const Color textOnAccent = const Color(0xFFFFFFFF); + + static const Color backgroundColor = const Color(0xFFFFFFFF); + static const Color backgroundColorLight = backgroundColor; + static const Color backgroundColorDark = const Color(0xFF353A3A); + + ///Cards + static const Color cardBackgroundColor = const Color(0xFFFFFFFF); + static const Color cardBackgroundColorLight = backgroundColor; + static const Color cardBackgroundColorDark = const Color(0xFF353A3A); + + /// Auth Stuff + static const Color loginGradientEnd = primaryColorLight; + static const Color loginGradientStart = primaryColorDark; +} diff --git a/lib/src/commons/defaults.dart b/lib/src/ui/commons/defaults.dart similarity index 100% rename from lib/src/commons/defaults.dart rename to lib/src/ui/commons/defaults.dart diff --git a/lib/src/ui/commons/dimensions.dart b/lib/src/ui/commons/dimensions.dart new file mode 100644 index 0000000..2567a33 --- /dev/null +++ b/lib/src/ui/commons/dimensions.dart @@ -0,0 +1,32 @@ +class AppDimensions { + ///Profile + + static const double profileAvatarMin = 5.0; + static const double profileAvatarMax = 50.0; + static const double profileAvatarElevation = 2.0; + + ///Group + + static const double groupPadding = 5.0; + + ///Entry + + static const double entryPadding = 10.0; + static const double entryTagSpacing = 4.0; + static const double entryCardElevation = 2.0; + static const double entryEventHeight = 200.0; + static const double entryEventHWidth = 300.0; + + static const double horizontalEntryListHeight = entryEventHeight; + static const double horizontalGroupListHeight = 300.0; + + ///Sort Dialog + + static const double sortDialogWidth = 200.0; + static const double sortDialogHeight = 300.0; + + ///List + + static const double listHeaderDefaultHeightMax = 40.0; + static const double listHeaderDefaultHeightMin = 40.0; +} diff --git a/lib/src/commons/paths.dart b/lib/src/ui/commons/paths.dart similarity index 100% rename from lib/src/commons/paths.dart rename to lib/src/ui/commons/paths.dart diff --git a/lib/src/commons/tags.dart b/lib/src/ui/commons/tags.dart similarity index 100% rename from lib/src/commons/tags.dart rename to lib/src/ui/commons/tags.dart diff --git a/lib/src/localizations/cv_localization.dart b/lib/src/ui/localizations/cv_localization.dart similarity index 95% rename from lib/src/localizations/cv_localization.dart rename to lib/src/ui/localizations/cv_localization.dart index b7d24b9..f2fd2c8 100644 --- a/lib/src/localizations/cv_localization.dart +++ b/lib/src/ui/localizations/cv_localization.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization_en.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization_fr.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization_en.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization_fr.dart'; abstract class CVLocalizations { static CVLocalizations of(BuildContext context) { @@ -74,9 +74,16 @@ abstract class CVLocalizations { /// Home Page String get homeTitle; + String get homeCTA; + String get homeWelcome; /// Account Page + + String get accountTitle; + + String get accountCTA; + String get accountMyProfile; /// Profile Page diff --git a/lib/src/localizations/cv_localization_en.dart b/lib/src/ui/localizations/cv_localization_en.dart similarity index 97% rename from lib/src/localizations/cv_localization_en.dart rename to lib/src/ui/localizations/cv_localization_en.dart index 4427901..79a1f44 100644 --- a/lib/src/localizations/cv_localization_en.dart +++ b/lib/src/ui/localizations/cv_localization_en.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; class CVLocalizationsEN implements CVLocalizations { const CVLocalizationsEN(); @@ -97,14 +97,24 @@ class CVLocalizationsEN implements CVLocalizations { @override String get authOr => 'OR'; - ///Home Page + /// Home Page @override String get homeTitle => 'Social CV'; + @override + String get homeCTA => 'Home'; + @override String get homeWelcome => 'Welcome on our new resume social network !'; /// Account Page + + @override + String get accountTitle => 'Account'; + + @override + String get accountCTA => 'Account'; + @override String get accountMyProfile => 'My profiles'; diff --git a/lib/src/localizations/cv_localization_fr.dart b/lib/src/ui/localizations/cv_localization_fr.dart similarity index 97% rename from lib/src/localizations/cv_localization_fr.dart rename to lib/src/ui/localizations/cv_localization_fr.dart index f510c52..97315b0 100644 --- a/lib/src/localizations/cv_localization_fr.dart +++ b/lib/src/ui/localizations/cv_localization_fr.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; class CVLocalizationsFR implements CVLocalizations { const CVLocalizationsFR(); @@ -100,10 +100,20 @@ class CVLocalizationsFR implements CVLocalizations { @override String get homeTitle => 'Social CV'; + @override + String get homeCTA => 'Accueil'; + @override String get homeWelcome => 'Bienvenue sur notre nouveau réseau social de CV !'; /// Account Page + + @override + String get accountTitle => 'Compte'; + + @override + String get accountCTA => 'Compte'; + @override String get accountMyProfile => 'Mes profils'; diff --git a/lib/src/ui/models/view_models.dart b/lib/src/ui/models/view_models.dart new file mode 100644 index 0000000..dbca130 --- /dev/null +++ b/lib/src/ui/models/view_models.dart @@ -0,0 +1,2 @@ +/// This is a placeholder +/// Most of needed view models are in the common library diff --git a/lib/src/ui/pages/account_page.dart b/lib/src/ui/pages/account_page.dart new file mode 100644 index 0000000..0acd440 --- /dev/null +++ b/lib/src/ui/pages/account_page.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/src/utils/utils.dart'; + +class AccountPage extends StatefulWidget { + const AccountPage({Key key}) : super(key: key); + + @override + State createState() => _AccountPageState(); +} + +class _AccountPageState extends State { + final String _TAG = '_AccountPageState'; + + // TODO: Add AuthenticationBloc + AuthenticationBloc get _authBloc => null; + + @override + Widget build(BuildContext context) { + logger.info('$_TAG:build'); + + return SafeArea( + left: false, + right: false, + child: BlocBuilder( + bloc: _authBloc, + builder: (BuildContext context, AuthenticationState state) { + if (state is AuthenticationUninitialized) return Container(); + if (state is AuthenticationUnauthenticated) + return _AccountPageDetailsNotConnected(); + if (state is AuthenticationAuthenticated) + return _AccountPageDetailsConnected(); + if (state is AuthenticationLoading) + return Center(child: CircularProgressIndicator()); + }, + ), + ); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// // +// _AccountPageDetailsNotConnected // +// // +//////////////////////////////////////////////////////////////////////////////// +class _AccountPageDetailsNotConnected extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Center( + child: RaisedButton( + child: Text(CVLocalizations.of(context).authSignInCTA), + onPressed: () => navigateToLogin(context), + ), + ); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// // +// _AccountPageDetailsConnected // +// // +//////////////////////////////////////////////////////////////////////////////// +class _AccountPageDetailsConnected extends StatefulWidget { + _AccountPageDetailsConnected({Key key}) : super(key: key); + + @override + State createState() => _AccountPageDetailsConnectedState(); +} + +class _AccountPageDetailsConnectedState + extends State<_AccountPageDetailsConnected> { + // TODO: Add AccountBloc + AccountBloc get _accountBloc => null; + + @override + Widget build(BuildContext context) { + RepositoriesProvider _repositories = RepositoriesProvider.of(context); + + return BlocBuilder( + bloc: _accountBloc, + builder: (BuildContext context, AccountState state) { + if (state is AccountUninitialized) { + return Container(); + } + if (state is AccountLoaded) { + return ListView( + children: [ + ExpansionTile( + leading: Icon(MdiIcons.accountBoxMultiple), + title: Text(CVLocalizations.of(context).accountMyProfile), + children: [ +// BlocProvider( +// bloc: ElementListBloc( +// cvRepository: _repositories.cvRepository, +// preferencesRepository: +// _repositories.preferencesRepository, +// ), +// child: ProfileListWidget( +// fromUserViewModel: snapshot.data, +// showOptions: false, +// shrinkWrap: true, +// physics: ClampingScrollPhysics(), +// ), +// ), + ], + ), + ], + ); + } + if (state is AccountFailed) { + return ErrorCard(message: translateError(context, state.error)); + } + return Container(); + }, + ); + } +} diff --git a/lib/src/pages/auth_page.dart b/lib/src/ui/pages/auth_page.dart similarity index 54% rename from lib/src/pages/auth_page.dart rename to lib/src/ui/pages/auth_page.dart index 6d014ff..6b1d94c 100644 --- a/lib/src/pages/auth_page.dart +++ b/lib/src/ui/pages/auth_page.dart @@ -1,15 +1,15 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/login_form_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/register_form_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/widgets/login_form_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/register_form_widget.dart'; class AuthPage extends StatefulWidget { + AuthPage({Key key}) : super(key: key); + @override State createState() => _AuthPageState(); } @@ -29,98 +29,78 @@ class _AuthPageState extends State Widget build(BuildContext context) { logger.info('$_TAG:build'); - AccountBloc _accountBloc = BlocProvider.of(context); - return Scaffold( key: _scaffoldKey, - -// appBar: AppBar( -// title: Text(CVLocalizations.of(context).authTitle), -// centerTitle: true, -// ), - body: Stack( - children: [ - StreamBuilder( - stream: _accountBloc.isLoggingStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.data == true) { - return LinearProgressIndicator(); - } - return Container(); - }, - ), - NotificationListener( - onNotification: (overscroll) { - overscroll.disallowGlow(); - }, - child: SingleChildScrollView( - child: Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height >= 775.0 - ? MediaQuery.of(context).size.height - : 775.0, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - AppColors.loginGradientStart, - AppColors.loginGradientEnd - ], - begin: const FractionalOffset(0.0, 0.0), - end: const FractionalOffset(1.0, 1.0), - stops: [0.0, 1.0], - tileMode: TileMode.clamp, - ), + body: NotificationListener( + onNotification: (overscroll) { + overscroll.disallowGlow(); + }, + child: SingleChildScrollView( + child: Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height >= 775.0 + ? MediaQuery.of(context).size.height + : 775.0, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.loginGradientStart, + AppColors.loginGradientEnd + ], + begin: const FractionalOffset(0.0, 0.0), + end: const FractionalOffset(1.0, 1.0), + stops: [0.0, 1.0], + tileMode: TileMode.clamp, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsets.only(top: 75.0), + child: Image( + width: 250.0, + height: 191.0, + fit: BoxFit.fill, + image: new AssetImage('assets/img/login_logo.png')), + ), + Padding( + padding: EdgeInsets.only(top: 20.0), + child: _buildMenuBar(context), ), - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Padding( - padding: EdgeInsets.only(top: 75.0), - child: Image( - width: 250.0, - height: 191.0, - fit: BoxFit.fill, - image: new AssetImage('assets/img/login_logo.png')), - ), - Padding( - padding: EdgeInsets.only(top: 20.0), - child: _buildMenuBar(context), - ), - Expanded( - flex: 2, - child: PageView( - controller: _pageController, - onPageChanged: (i) { - if (i == 0) { - setState(() { - right = Colors.white; - left = Colors.black; - }); - } else if (i == 1) { - setState(() { - right = Colors.black; - left = Colors.white; - }); - } - }, - children: [ - new ConstrainedBox( - constraints: const BoxConstraints.expand(), - child: LoginFormWidget(), - ), - new ConstrainedBox( - constraints: const BoxConstraints.expand(), - child: RegisterFormWidget(), - ), - ], + Expanded( + flex: 2, + child: PageView( + controller: _pageController, + onPageChanged: (i) { + if (i == 0) { + setState(() { + right = Colors.white; + left = Colors.black; + }); + } else if (i == 1) { + setState(() { + right = Colors.black; + left = Colors.white; + }); + } + }, + children: [ + new ConstrainedBox( + constraints: const BoxConstraints.expand(), + child: LoginFormWidget(), ), - ), - ], + new ConstrainedBox( + constraints: const BoxConstraints.expand(), + child: RegisterFormWidget(), + ), + ], + ), ), - ), + ], ), ), - ], + ), ), ); } diff --git a/lib/src/ui/pages/elements/entry_profile_page.dart b/lib/src/ui/pages/elements/entry_profile_page.dart new file mode 100644 index 0000000..d1bbfc8 --- /dev/null +++ b/lib/src/ui/pages/elements/entry_profile_page.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; + +class EntryPage extends EntryWidget { + EntryPage( + {Key key, String entryId, EntryViewModel entry, EntryBloc entryBloc}) + : super(key: key, entryId: entryId, entry: entry, entryBloc: entryBloc); + + @override + State createState() => _EntryPageState(); +} + +class _EntryPageState extends EntryWidgetState { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: entryBloc, + builder: (BuildContext context, EntryState state) { + if (state is EntryLoading) { + return Scaffold( + appBar: AppBar( + title: LoadingShadowContent( + numberOfTitleLines: 1, + numberOfContentLines: 0, + ), + ), + body: SingleChildScrollView( + child: LoadingShadowContent( + numberOfContentLines: 2, + padding: EdgeInsets.all(10.0), + ), + ), + ); + } else if (state is EntryLoaded) { + EntryViewModel model = state.element; + + return Scaffold( + appBar: AppBar(title: Text(model.name)), + body: ListView( + children: [ + ListTile( + title: Text('name'), + subtitle: Text(model.name), + ), + ListTile( + title: Text('type'), + subtitle: Text(model.type), + ), + ListTile( + title: Text('content'), + subtitle: Text(model.content), + ), + ], + ), + ); + } + return Container(); + }, + ); + } +} diff --git a/lib/src/ui/pages/elements/group_profile_page.dart b/lib/src/ui/pages/elements/group_profile_page.dart new file mode 100644 index 0000000..1a7fae5 --- /dev/null +++ b/lib/src/ui/pages/elements/group_profile_page.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; + +class GroupPage extends GroupWidget { + GroupPage( + {Key key, String groupId, GroupViewModel group, GroupBloc groupBloc}) + : super(key: key, groupId: groupId, group: group, groupBloc: groupBloc); + + @override + State createState() => _GroupPageState(); +} + +class _GroupPageState extends GroupWidgetState { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: groupBloc, + builder: (BuildContext context, GroupState state) { + if (state is GroupLoading) { + return Scaffold( + appBar: AppBar( + title: LoadingShadowContent( + numberOfTitleLines: 1, + numberOfContentLines: 0, + ), + ), + body: SingleChildScrollView( + child: SingleChildScrollView( + child: LoadingShadowContent( + numberOfContentLines: 2, + padding: EdgeInsets.all(10.0), + ), + ), + ), + ); + } else if (state is GroupLoaded) { + GroupViewModel model = state.element; + + return Scaffold( + appBar: AppBar(title: Text(model.name)), + body: SingleChildScrollView( + child: EntryListWidget( + fromGroupViewModel: model, + showOptions: true, + ), + ), + ); + } + return Container(); + }, + ); + } +} diff --git a/lib/src/ui/pages/elements/part_profile_page.dart b/lib/src/ui/pages/elements/part_profile_page.dart new file mode 100644 index 0000000..693ccb6 --- /dev/null +++ b/lib/src/ui/pages/elements/part_profile_page.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; + +class PartProfilePage extends PartWidget { + PartProfilePage( + {Key key, String partId, PartViewModel part, PartBloc partBloc}) + : super(key: key, partId: partId, part: part, partBloc: partBloc); + + @override + State createState() => _PartProfilePageState(); +} + +class _PartProfilePageState extends PartWidgetState { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: partBloc, + builder: (BuildContext context, PartState state) { + if (state is PartLoading) { + return Scaffold( + appBar: AppBar( + title: LoadingShadowContent( + numberOfTitleLines: 1, + numberOfContentLines: 0, + ), + ), + body: SingleChildScrollView( + child: SingleChildScrollView( + child: LoadingShadowContent( + numberOfContentLines: 2, + padding: EdgeInsets.all(10.0), + ), + ), + ), + ); + } else if (state is PartLoaded) { + PartViewModel model = state.element; + + return Scaffold( + appBar: AppBar(title: Text(model.name)), + body: SingleChildScrollView( + child: GroupListWidget( + fromPartViewModel: model, + showOptions: true, + ), + ), + ); + } + }, + ); + } +} diff --git a/lib/src/ui/pages/elements/profile_profile_page.dart b/lib/src/ui/pages/elements/profile_profile_page.dart new file mode 100644 index 0000000..92194e0 --- /dev/null +++ b/lib/src/ui/pages/elements/profile_profile_page.dart @@ -0,0 +1,218 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_profile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/utils.dart'; + +/// TODO : Build owner interaction with ProfileViewModel.owner + +class ProfileProfilePage extends ProfileWidget { + ProfileProfilePage( + {Key key, + String profileId, + ProfileViewModel profile, + ProfileBloc profileBloc}) + : super( + key: key, + profileId: profileId, + profile: profile, + profileBloc: profileBloc); + + @override + State createState() => _ProfileProfilePageState(); +} + +class _ProfileProfilePageState extends ProfileWidgetState { + @override + Widget build(BuildContext context) { + logger.info('Building ProfilePage'); + + return BlocBuilder( + bloc: profileBloc, + builder: (BuildContext context, ProfileState state) { + List slivers = []; + + if (state is ProfileLoaded) { + slivers.add(_ProfilePageAppBar(profile: state.element)); + ProfileViewModel profile = state.element; + if (profile.type == kCVProfileTypeMain) { + slivers.addAll([ + PartProfileWidget( + partId: profile.partIds.elementAt(0), + ) + ]); + } else if (profile.type == kCVProfileTypeHeaderMain) { + slivers.addAll([ + PartProfileWidget( + partId: profile.partIds.elementAt(0), + ), + PartProfileWidget( + partId: profile.partIds.elementAt(1), + ) + ]); + } else if (profile.type == kCVProfileTypeMainSide) { + slivers.addAll([ + PartProfileWidget( + partId: profile.partIds.elementAt(0), + ), + PartProfileWidget( + partId: profile.partIds.elementAt(1), + ) + ]); + } else if (profile.type == kCVProfileTypeHeaderMainSide) { + slivers.addAll([ + PartProfileWidget( + partId: profile.partIds.elementAt(0), + ), + PartProfileWidget( + partId: profile.partIds.elementAt(1), + ), + PartProfileWidget( + partId: profile.partIds.elementAt(2), + ) + ]); + } else { + slivers.add( + SliverToBoxAdapter( + child: ErrorCard( + message: CVLocalizations.of(context).notSupported, + ), + ), + ); + } + } else if (state is ProfileFailure) { + slivers.add( + SliverToBoxAdapter( + child: ErrorCard(message: translateError(context, state.error)), + ), + ); + } + return Scaffold( + body: CustomScrollView( + slivers: slivers, + )); + }, + ); + } +} + +class _ProfilePageAppBar extends StatelessWidget { + final ProfileViewModel profile; + + _ProfilePageAppBar({@required this.profile}); + + @override + Widget build(BuildContext context) { + Widget titleWidget = LoadingShadowContent( + numberOfTitleLines: 1, + numberOfContentLines: 0, + ); + + Widget subtitleWidget = LoadingShadowContent( + numberOfTitleLines: 1, + numberOfContentLines: 0, + ); + + Widget avatarWidget = InitialCircleAvatar( + elevation: AppDimensions.profileAvatarElevation, + maxRadius: AppDimensions.profileAvatarMax, + minRadius: AppDimensions.profileAvatarMin, + backgroundImage: AssetImage('images/default-avatar.png'), + ); + + Widget bannerWidget = Image.asset( + 'images/default-banner.jpg', + fit: BoxFit.cover, + ); + + if (this.profile != null) { + titleWidget = Text( + profile.title, + ); + subtitleWidget = Text( + profile.subtitle, + ); + avatarWidget = InitialCircleAvatar( + text: profile.title, + elevation: AppDimensions.profileAvatarElevation, + maxRadius: AppDimensions.profileAvatarMax, + minRadius: AppDimensions.profileAvatarMin, + backgroundImage: NetworkImage(profile.picture.toString()), + ); + + bannerWidget = Image.network( + profile.cover.toString(), + fit: BoxFit.cover, + ); + } + + Widget profileInfo = Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + titleWidget, + subtitleWidget, + ], + ); + + Widget backgroundWidget = Stack( + children: [ + Stack( + fit: StackFit.expand, + children: [ + bannerWidget, + const DecoratingBackground(), + ], + ), + Center(heightFactor: 2, child: avatarWidget), + ], + ); + + return SliverAppBar( + expandedHeight: 200, + pinned: true, + elevation: 2.0, + floating: false, + flexibleSpace: FlexibleSpaceBar( + background: backgroundWidget, + collapseMode: CollapseMode.parallax, + centerTitle: true, + title: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + profileInfo, + ], + ), + ), + ), + ); + } +} + +class DecoratingBackground extends StatelessWidget { + const DecoratingBackground({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + /// This gradient ensures that the toolbar icons are distinct + /// against the background image. + begin: Alignment(0.0, -1.0), + end: Alignment(0.0, 5), + colors: [Color(0x60000000), Color(0x00000000)], + ), + ), + ); + } +} diff --git a/lib/src/pages/home_page.dart b/lib/src/ui/pages/home_page.dart similarity index 90% rename from lib/src/pages/home_page.dart rename to lib/src/ui/pages/home_page.dart index a52cc57..840779a 100644 --- a/lib/src/pages/home_page.dart +++ b/lib/src/ui/pages/home_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; class HomePage extends StatelessWidget { diff --git a/lib/src/ui/pages/main_page.dart b/lib/src/ui/pages/main_page.dart new file mode 100644 index 0000000..7b24e0a --- /dev/null +++ b/lib/src/ui/pages/main_page.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/tags.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/account_page.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/home_page.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/menu_button_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/navigation.dart'; + +class MainPage extends StatefulWidget { + const MainPage({Key key}) : super(key: key); + + @override + State createState() => _MainPageState(); +} + +class _MainPageState extends State { + final String _TAG = '_MainPageState'; + int _currentIndex = 0; + + final List _children = [ + HomePage(), + AccountPage(), + ]; + + @override + Widget build(BuildContext context) { + logger.info('$_TAG:build'); + + return Scaffold( + appBar: AppBar( + title: Text(CVLocalizations.of(context).appName), + centerTitle: true, + actions: [ + MenuButton(), + ], + ), + body: _children.elementAt(_currentIndex), + floatingActionButton: FloatingActionButton.extended( + heroTag: kHeroSearchFAB, + icon: Icon(Icons.search), + label: Text(CVLocalizations.of(context).search), + foregroundColor: Colors.white, + onPressed: () => navigateToSearch(context), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, + bottomNavigationBar: Theme( + data: Theme.of(context).copyWith( + ///sets the background color of the `BottomNavigationBar` + canvasColor: Theme.of(context).primaryColor, + + ///sets the active color of the `BottomNavigationBar` if `Brightness` is light + primaryColor: Theme.of(context).selectedRowColor, + textTheme: Theme.of(context).primaryTextTheme, + ), + + ///sets the inactive color of the `BottomNavigationBar` + child: BottomNavigationBar( + currentIndex: _currentIndex, + onTap: _onTabTapped, + type: BottomNavigationBarType.fixed, + items: [ + BottomNavigationBarItem( + icon: Icon(MdiIcons.homeOutline), + activeIcon: Icon(MdiIcons.home), + title: Text(CVLocalizations.of(context).homeCTA), + ), + const BottomNavigationBarItem( + ///Fake item + icon: SizedBox(), + title: SizedBox(), + ), + BottomNavigationBarItem( + icon: Icon(MdiIcons.accountOutline), + activeIcon: Icon(MdiIcons.account), + title: Text(CVLocalizations.of(context).accountCTA), + ), + ], + ), + ), + ); + } + + void _onTabTapped(int index) { + setState(() { + _currentIndex = index; + }); + } +} diff --git a/lib/src/pages/search_page.dart b/lib/src/ui/pages/search_page.dart similarity index 55% rename from lib/src/pages/search_page.dart rename to lib/src/ui/pages/search_page.dart index ae8c2b0..b804d0f 100644 --- a/lib/src/pages/search_page.dart +++ b/lib/src/ui/pages/search_page.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/commons/tags.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/repositories/repositories_provider.dart'; -import 'package:social_cv_client_flutter/src/widgets/profile_list_widget.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/tags.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +/// Add controller for the input class SearchPage extends StatelessWidget { const SearchPage({ Key key, @@ -40,20 +38,21 @@ class SearchPage extends StatelessWidget { ), ), ), - SliverToBoxAdapter( - child: BlocProvider( - bloc: ProfileListBloc( - cvRepository: _repositories.cvRepository, - preferencesRepository: _repositories.preferencesRepository, - ), - child: ProfileListWidget( - fromSearch: '', - showOptions: true, - shrinkWrap: true, - physics: ClampingScrollPhysics(), - ), - ), - ), + + /// TODO: Add search result +// SliverToBoxAdapter( +// child: BlocProvider>( +// bloc: ElementBloc( +// cvRepository: _repositories.cvRepository, +// ), +// child: ProfileListWidget( +// fromSearch: '', +// showOptions: true, +// shrinkWrap: true, +// physics: ClampingScrollPhysics(), +// ), +// ), +// ), ], ), ); diff --git a/lib/src/pages/settings_page.dart b/lib/src/ui/pages/settings_page.dart similarity index 79% rename from lib/src/pages/settings_page.dart rename to lib/src/ui/pages/settings_page.dart index 22ed282..c8099c8 100644 --- a/lib/src/pages/settings_page.dart +++ b/lib/src/ui/pages/settings_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_tile_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/widgets/theme_switch_tile_widget.dart'; class SettingsPage extends StatelessWidget { const SettingsPage({ diff --git a/lib/src/ui/pages/splash_page.dart b/lib/src/ui/pages/splash_page.dart new file mode 100644 index 0000000..1f5747c --- /dev/null +++ b/lib/src/ui/pages/splash_page.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; + +class SplashPage extends StatelessWidget { + final String _tag = 'SplashPage'; + + @override + Widget build(BuildContext context) { + print('$_tag:build'); + + return Scaffold( + backgroundColor: AppColors.primaryColor, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + Text("Social CV"), + ], + ), + ), + ); + } +} diff --git a/lib/src/ui/widgets/account_tile_widget.dart b/lib/src/ui/widgets/account_tile_widget.dart new file mode 100644 index 0000000..ea19297 --- /dev/null +++ b/lib/src/ui/widgets/account_tile_widget.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/navigation.dart'; + +class AccountTile extends StatefulWidget { + const AccountTile({Key key}) : super(key: key); + + @override + State createState() => _AccountTitleState(); +} + +class _AccountTitleState extends State { + // TODO: Add AuthenticationBloc + AuthenticationBloc get _authBloc => null; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: _authBloc, + builder: (BuildContext context, AuthenticationState state) { + if (state is AuthenticationAuthenticated) + return _AccountTileConnected(); + if (state is AuthenticationUnauthenticated) + return _AccountTileNotConnected(); + return Container(); + }, + ); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// // +// _AccountTileConnected // +// // +//////////////////////////////////////////////////////////////////////////////// + +class _AccountTileConnected extends StatefulWidget { + _AccountTileConnected({Key key}) : super(key: key); + + @override + State createState() => _AccountTileConnectedState(); +} + +class _AccountTileConnectedState extends State<_AccountTileConnected> { + // TODO: Add AccountBloc + AccountBloc get _accountBloc => null; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: _accountBloc, + builder: (BuildContext context, AccountState state) { + if (state is AccountUninitialized) { + return Container(); + } + if (state is AccountLoaded) { + var userModel = state.userModel; + return ListTile( + leading: InitialCircleAvatar( + text: userModel.username, + backgroundImage: NetworkImage(userModel.picture), + ), + title: Text(userModel.username), + subtitle: Text(userModel.email), + trailing: IconButton( + icon: Icon(MdiIcons.logout), + onPressed: () => null, // TODO: Add logout + ), + ); + } + if (state is AccountFailed) { + return Container(child: Text('${state.error}')); + } + }, + ); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// // +// _AccountTileNotConnected // +// // +//////////////////////////////////////////////////////////////////////////////// +class _AccountTileNotConnected extends StatelessWidget { + @override + Widget build(BuildContext context) { + return GestureDetector( + child: ListTile( + title: Center(child: Text(CVLocalizations.of(context).authSignInCTA)), + trailing: Icon(MdiIcons.login), + ), + onTap: () => navigateToLogin(context), + ); + } +} diff --git a/lib/src/widgets/arc_banner_image_widget.dart b/lib/src/ui/widgets/arc_banner_image_widget.dart similarity index 100% rename from lib/src/widgets/arc_banner_image_widget.dart rename to lib/src/ui/widgets/arc_banner_image_widget.dart diff --git a/lib/src/ui/widgets/elements/element_widget.dart b/lib/src/ui/widgets/elements/element_widget.dart new file mode 100644 index 0000000..274a1e8 --- /dev/null +++ b/lib/src/ui/widgets/elements/element_widget.dart @@ -0,0 +1,17 @@ +import 'package:flutter/widgets.dart'; +import 'package:social_cv_client_dart_common/models.dart'; + +abstract class ElementWidget + extends StatefulWidget { + final String elementId; + final T element; + + ElementWidget({Key key, this.elementId, this.element}) : super(key: key); +} + +abstract class ElementWidgetState extends State { + @override + void initState() { + super.initState(); + } +} diff --git a/lib/src/widgets/entry_list_widget.dart b/lib/src/ui/widgets/elements/entry_list_widget.dart similarity index 77% rename from lib/src/widgets/entry_list_widget.dart rename to lib/src/ui/widgets/elements/entry_list_widget.dart index f7666ee..df52872 100644 --- a/lib/src/widgets/entry_list_widget.dart +++ b/lib/src/ui/widgets/elements/entry_list_widget.dart @@ -1,29 +1,29 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_profile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/entry_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_list_tile_widget.dart'; class EntryListWidget extends StatelessWidget { const EntryListWidget({ Key key, - this.fromGroupModel, + this.fromGroupViewModel, this.fromSearch, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(fromGroupModel != null || fromSearch != null), + }) : assert(fromGroupViewModel != null || fromSearch != null), super(key: key); - final GroupModel fromGroupModel; + final GroupViewModel fromGroupViewModel; final Object fromSearch; final bool showOptions; @@ -34,9 +34,9 @@ class EntryListWidget extends StatelessWidget { @override Widget build(BuildContext context) { - if (fromGroupModel != null) { + if (fromGroupViewModel != null) { return _EntryListFromGroup( - groupModel: fromGroupModel, + groupViewModel: fromGroupViewModel, showOptions: showOptions, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, @@ -62,14 +62,14 @@ class EntryListWidget extends StatelessWidget { class _EntryListFromGroup extends StatelessWidget { _EntryListFromGroup({ - @required this.groupModel, + @required this.groupViewModel, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(groupModel != null); + }) : assert(groupViewModel != null); - final GroupModel groupModel; + final GroupViewModel groupViewModel; final bool showOptions; @@ -80,12 +80,12 @@ class _EntryListFromGroup extends StatelessWidget { @override Widget build(BuildContext context) { EntryListBloc entryListBloc = BlocProvider.of(context); - entryListBloc.fetchGroupEntries(groupModel.id); + entryListBloc.fetchGroupEntries(groupViewModel.id); - return StreamBuilder>( + return StreamBuilder>( stream: entryListBloc.entriesStream, builder: - (BuildContext context, AsyncSnapshot> snapshot) { + (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasError) { return ErrorList( error: snapshot.error, @@ -95,7 +95,7 @@ class _EntryListFromGroup extends StatelessWidget { ); } else if (snapshot.hasData) { return _EntryList( - entryModels: snapshot.data, + EntryViewModels: snapshot.data, showOptions: showOptions, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, @@ -103,7 +103,7 @@ class _EntryListFromGroup extends StatelessWidget { ); } else { return LoadingList( - count: groupModel.entryIds.length, + count: groupViewModel.entryIds.length, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, physics: this.physics, @@ -144,14 +144,14 @@ class _EntryListFromSearch extends StatelessWidget { class _EntryList extends StatelessWidget { _EntryList({ - @required this.entryModels, + @required this.EntryViewModels, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(entryModels != null); + }) : assert(EntryViewModels != null); - final List entryModels; + final List EntryViewModels; final bool showOptions; @@ -161,7 +161,8 @@ class _EntryList extends StatelessWidget { @override Widget build(BuildContext context) { - EntryListBloc _entryListBloc = BlocProvider.of(context); + ElementListBloc _entryListBloc = + BlocProvider.of>(context); final List sortItems = [ SortListItem(field: 'name', title: 'Name', value: SortState.NoSort) @@ -171,7 +172,8 @@ class _EntryList extends StatelessWidget { scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, physics: this.physics, - itemCount: showOptions ? entryModels.length + 2 : entryModels.length, + itemCount: + showOptions ? EntryViewModels.length + 2 : EntryViewModels.length, itemBuilder: (BuildContext context, int i) { if (showOptions) { if (i == 0) { @@ -212,7 +214,7 @@ class _EntryList extends StatelessWidget { ); } i--; - if (i == entryModels.length) { + if (i == EntryViewModels.length) { return Center( child: FlatButton( onPressed: null, @@ -221,7 +223,7 @@ class _EntryList extends StatelessWidget { ); } } - return EntryWidget(entryModel: entryModels[i]); + return EntryProfileWidget(entry: EntryViewModels[i]); }, ); } diff --git a/lib/src/widgets/entry_widget.dart b/lib/src/ui/widgets/elements/entry_profile_widget.dart similarity index 55% rename from lib/src/widgets/entry_widget.dart rename to lib/src/ui/widgets/elements/entry_profile_widget.dart index 8e50047..265aa99 100644 --- a/lib/src/widgets/entry_widget.dart +++ b/lib/src/ui/widgets/elements/entry_profile_widget.dart @@ -1,57 +1,71 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/commons/api_values.dart'; -import 'package:social_cv_client_flutter/src/commons/dimensions.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -class EntryWidget extends StatelessWidget { - const EntryWidget({ - Key key, - @required this.entryModel, - }) : assert(entryModel != null), - super(key: key); +class EntryProfileWidget extends EntryWidget { + EntryProfileWidget( + {Key key, String entryId, EntryViewModel entry, EntryBloc entryBloc}) + : super(key: key, entryId: entryId, entry: entry, entryBloc: entryBloc); - final EntryModel entryModel; + @override + State createState() => _EntryProfileWidgetState(); +} +class _EntryProfileWidgetState extends EntryWidgetState { @override Widget build(BuildContext context) { - if (entryModel.type == kCVEntryTypeMap) { - return _EntryWidgetMap(entryModel: entryModel); - } else if (entryModel.type == kCVEntryTypeEvent) { - return _EntryWidgetEvent(entryModel: entryModel); - } else if (entryModel.type == kCVEntryTypeTag) { - return _EntryWidgetTag(entryModel); - } else { - return ErrorContent(message: CVLocalizations.of(context).notSupported); - } + return BlocBuilder( + bloc: this.entryBloc, + builder: (BuildContext context, EntryState state) { + if (state is EntryLoaded) { + EntryViewModel entryViewModel = state.element; + if (entryViewModel.type == kCVEntryTypeMap) { + return _EntryWidgetMap(entry: entryViewModel); + } else if (entryViewModel.type == kCVEntryTypeEvent) { + return _EntryWidgetEvent(entry: entryViewModel); + } else if (entryViewModel.type == kCVEntryTypeTag) { + return _EntryWidgetTag(entryViewModel); + } else { + return ErrorContent( + message: CVLocalizations.of(context).notSupported); + } + } + return Container(); + }, + ); } } class _EntryWidgetMap extends StatelessWidget { _EntryWidgetMap({ - @required this.entryModel, - }) : assert(entryModel != null); + @required this.entry, + }) : assert(EntryViewModel != null); - final EntryModel entryModel; + final EntryViewModel entry; @override Widget build(BuildContext context) { return InkWell( - onTap: () => navigateToEntry(context, entryModel.id), + onTap: () => navigateToEntry(context, entry: entry), child: Container( - padding: EdgeInsets.all(AppDimensions.kCVEntryPadding), + padding: EdgeInsets.all(AppDimensions.entryPadding), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '${entryModel.name ?? ''}', + '${entry.name ?? ''}', style: TextStyle(fontWeight: FontWeight.bold), ), Expanded( child: Text( - '${entryModel.content ?? ''}', + '${entry.content ?? ''}', textAlign: TextAlign.end, ), ) @@ -64,19 +78,19 @@ class _EntryWidgetMap extends StatelessWidget { class _EntryWidgetEvent extends StatelessWidget { _EntryWidgetEvent({ - @required this.entryModel, - }) : assert(entryModel != null); + @required this.entry, + }) : assert(EntryViewModel != null); - final EntryModel entryModel; + final EntryViewModel entry; @override Widget build(BuildContext context) { return Card( - elevation: AppDimensions.kCVEntryCardElevation, + elevation: AppDimensions.entryCardElevation, child: Container( - height: AppDimensions.kCVEntryEventHeight, - width: AppDimensions.kCVEntryEventHWidth, - padding: const EdgeInsets.all(AppDimensions.kCVEntryPadding), + height: AppDimensions.entryEventHeight, + width: AppDimensions.entryEventHWidth, + padding: const EdgeInsets.all(AppDimensions.entryPadding), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -86,7 +100,7 @@ class _EntryWidgetEvent extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '${entryModel.startDate} ${entryModel.endDate}', + '${entry.startDate} ${entry.endDate}', textAlign: TextAlign.start, style: TextStyle( color: Theme.of(context).accentColor, @@ -95,7 +109,7 @@ class _EntryWidgetEvent extends StatelessWidget { ), Expanded( child: Text( - entryModel.location ?? '', + entry.location ?? '', textAlign: TextAlign.end, style: TextStyle(fontStyle: FontStyle.italic), ), @@ -103,13 +117,13 @@ class _EntryWidgetEvent extends StatelessWidget { ], ), Text( - entryModel.name ?? '', + entry.name ?? '', textAlign: TextAlign.start, style: TextStyle(fontWeight: FontWeight.bold), ), Expanded( child: Text( - entryModel.content, + entry.content, textAlign: TextAlign.justify, ), ), @@ -118,7 +132,7 @@ class _EntryWidgetEvent extends StatelessWidget { children: [ FlatButton( child: Text(CVLocalizations.of(context).entryWidgetDetails), - onPressed: () => navigateToEntry(context, entryModel.id), + onPressed: () => navigateToEntry(context, entry: entry), ) ], ) @@ -130,20 +144,20 @@ class _EntryWidgetEvent extends StatelessWidget { } class _EntryWidgetTag extends StatelessWidget { - _EntryWidgetTag(this.entryModel); + _EntryWidgetTag(this.entry); - final EntryModel entryModel; + final EntryViewModel entry; @override Widget build(BuildContext context) { - List tags = entryModel.content; + List tags = entry.content; List _tagWidgets = []; tags.forEach((dynamic tag) { _tagWidgets.add(Chip(label: Text(tag as String))); }); return Container( - padding: EdgeInsets.all(AppDimensions.kCVEntryPadding), + padding: EdgeInsets.all(AppDimensions.entryPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -151,7 +165,7 @@ class _EntryWidgetTag extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - entryModel.name.toUpperCase() ?? '', + entry.name.toUpperCase() ?? '', style: TextStyle( color: Theme.of(context).accentColor, fontWeight: FontWeight.bold, @@ -159,13 +173,13 @@ class _EntryWidgetTag extends StatelessWidget { ), FlatButton( child: Text(CVLocalizations.of(context).entryWidgetDetails), - onPressed: () => navigateToEntry(context, entryModel.id), + onPressed: () => navigateToEntry(context, entry: entry), ) ], ), Wrap( alignment: WrapAlignment.start, - spacing: AppDimensions.kCVEntryTagSpacing, + spacing: AppDimensions.entryTagSpacing, runSpacing: 0.0, children: _tagWidgets, ) diff --git a/lib/src/ui/widgets/elements/entry_widget.dart b/lib/src/ui/widgets/elements/entry_widget.dart new file mode 100644 index 0000000..c6dca68 --- /dev/null +++ b/lib/src/ui/widgets/elements/entry_widget.dart @@ -0,0 +1,42 @@ +import 'package:flutter/widgets.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/element_widget.dart'; + +/// If [entryBloc] given we assume that it have been already initialized +abstract class EntryWidget extends ElementWidget { + final EntryBloc entryBloc; + + EntryWidget({Key key, String entryId, EntryViewModel entry, this.entryBloc}) + : assert(entryId != null && entry == null && entryBloc == null), + assert(entryId == null && entry != null && entryBloc == null), + assert(entryId == null && entry == null && entryBloc != null), + super(key: key, elementId: entryId, element: entry); +} + +/// If [widget.profileBloc] exists the lifecycle of it will be managed by its creator +abstract class EntryWidgetState + extends ElementWidgetState { + EntryBloc entryBloc; + + @override + void initState() { + super.initState(); + + entryBloc = widget.entryBloc; + + if (entryBloc == null) { + entryBloc = EntryBloc(); + entryBloc.dispatch(EntryInitialized( + withId: widget.elementId, + withEntry: widget.element, + )); + } + } + + @override + void dispose() { + if (widget.entryBloc == null) entryBloc.dispose(); + super.dispose(); + } +} diff --git a/lib/src/widgets/group_list_widget.dart b/lib/src/ui/widgets/elements/group_list_widget.dart similarity index 65% rename from lib/src/widgets/group_list_widget.dart rename to lib/src/ui/widgets/elements/group_list_widget.dart index 5c6d966..5d184b2 100644 --- a/lib/src/widgets/group_list_widget.dart +++ b/lib/src/ui/widgets/elements/group_list_widget.dart @@ -1,30 +1,31 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/group_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_list_tile_widget.dart'; - -/// A widget to list all [GroupModel] from [PartModel] or from a search +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_profile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; + +/// A widget to list all [GroupViewModel] from [PartViewModel] or from a search class GroupListWidget extends StatelessWidget { GroupListWidget({ Key key, - this.fromPartModel, + this.fromPartViewModel, this.fromSearch, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(fromPartModel != null || fromSearch != null), + }) + : assert(fromPartViewModel != null || fromSearch != null), super(key: key); - final PartModel fromPartModel; + final PartViewModel fromPartViewModel; final Object fromSearch; final bool showOptions; @@ -35,9 +36,9 @@ class GroupListWidget extends StatelessWidget { @override Widget build(BuildContext context) { - if (fromPartModel != null) { - return _GroupListFromPartModel( - partModel: fromPartModel, + if (fromPartViewModel != null) { + return _GroupListFromPartViewModel( + partViewModel: fromPartViewModel, showOptions: showOptions, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, @@ -53,7 +54,9 @@ class GroupListWidget extends StatelessWidget { ); } else { return ErrorList( - error: CVLocalizations.of(context).notYetImplemented, + error: CVLocalizations + .of(context) + .notYetImplemented, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, physics: this.physics, @@ -62,17 +65,17 @@ class GroupListWidget extends StatelessWidget { } } -/// A widget to list all [GroupModel] from [PartModel] -class _GroupListFromPartModel extends StatelessWidget { - _GroupListFromPartModel({ - @required this.partModel, +/// A widget to list all [GroupViewModel] from [PartViewModel] +class _GroupListFromPartViewModel extends StatelessWidget { + _GroupListFromPartViewModel({ + @required this.partViewModel, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(partModel != null); + }) : assert(partViewModel != null); - final PartModel partModel; + final PartViewModel partViewModel; final bool showOptions; @@ -82,13 +85,15 @@ class _GroupListFromPartModel extends StatelessWidget { @override Widget build(BuildContext context) { - GroupListBloc _groupListBloc = BlocProvider.of(context); - _groupListBloc.fetchPartGroups(partModel.id); + ElementListBloc _groupListBloc = + BlocProvider.of>(context); + _groupListBloc.dispatch( + ElementListFetchFromParent(parentId: partViewModel.id); - return StreamBuilder>( + return StreamBuilder>( stream: _groupListBloc.groupsStream, builder: - (BuildContext context, AsyncSnapshot> snapshot) { + (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasError) { return ErrorList( error: snapshot.error, @@ -98,7 +103,7 @@ class _GroupListFromPartModel extends StatelessWidget { ); } else if (snapshot.hasData) { return _GroupList( - groupModels: snapshot.data, + groupViewModels: snapshot.data, showOptions: showOptions, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, @@ -106,7 +111,7 @@ class _GroupListFromPartModel extends StatelessWidget { ); } return LoadingList( - count: partModel.groupIds.length, + count: partViewModel.groupIds.length, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, physics: this.physics, @@ -137,7 +142,9 @@ class _GroupListFromSearch extends StatelessWidget { @override Widget build(BuildContext context) { return ErrorList( - error: CVLocalizations.of(context).notYetImplemented, + error: CVLocalizations + .of(context) + .notYetImplemented, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, physics: this.physics, @@ -145,17 +152,17 @@ class _GroupListFromSearch extends StatelessWidget { } } -/// A widget to list all [GroupModel] +/// A widget to list all [GroupViewModel] class _GroupList extends StatelessWidget { _GroupList({ - @required this.groupModels, + @required this.groupViewModels, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(groupModels != null); + }) : assert(groupViewModels != null); - final List groupModels; + final List groupViewModels; final bool showOptions; @@ -165,7 +172,8 @@ class _GroupList extends StatelessWidget { @override Widget build(BuildContext context) { - GroupListBloc _groupListBloc = BlocProvider.of(context); + ElementListBloc _groupListBloc = BlocProvider.of< + ElementListBloc>(context); final List sortItems = [ SortListItem(field: 'title', title: 'Title', value: SortState.NoSort) @@ -175,7 +183,8 @@ class _GroupList extends StatelessWidget { scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, physics: this.physics, - itemCount: showOptions ? groupModels.length + 2 : groupModels.length, + itemCount: + showOptions ? groupViewModels.length + 2 : groupViewModels.length, itemBuilder: (BuildContext context, int i) { if (showOptions) { if (i == 0) { @@ -190,7 +199,9 @@ class _GroupList extends StatelessWidget { builder: (BuildContext context) { return SortDialog( title: - Text(CVLocalizations.of(context).partListSorting), + Text(CVLocalizations + .of(context) + .partListSorting), sortItems: sortItems, ); }, @@ -204,7 +215,9 @@ class _GroupList extends StatelessWidget { return DropdownButton( value: snapshot.data, hint: - Text(CVLocalizations.of(context).partListItemPerPage), + Text(CVLocalizations + .of(context) + .partListItemPerPage), items: getDropDownMenuElementPerPage(), onChanged: (value) { _groupListBloc.setItemsPerPage(value); @@ -216,16 +229,18 @@ class _GroupList extends StatelessWidget { ); } i--; - if (i == groupModels.length) { + if (i == groupViewModels.length) { return Center( child: FlatButton( onPressed: null, - child: Text(CVLocalizations.of(context).groupListLoadMore), + child: Text(CVLocalizations + .of(context) + .groupListLoadMore), ), ); } } - return GroupWidget(groupModel: groupModels[i]); + return GroupProfileWidget(group: groupViewModels[i]); }, ); } diff --git a/lib/src/ui/widgets/elements/group_profile_widget.dart b/lib/src/ui/widgets/elements/group_profile_widget.dart new file mode 100644 index 0000000..cddf795 --- /dev/null +++ b/lib/src/ui/widgets/elements/group_profile_widget.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/navigation.dart'; + +class GroupProfileWidget extends GroupWidget { + GroupProfileWidget( + {Key key, String groupId, GroupViewModel group, GroupBloc groupBloc}) + : super(key: key, groupId: groupId, group: group, groupBloc: groupBloc); + + @override + State createState() => _GroupProfileWidgetState(); +} + +class _GroupProfileWidgetState extends GroupWidgetState { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: groupBloc, + builder: (BuildContext context, GroupState state) { + if (state is GroupLoaded) { + GroupViewModel groupViewModel; + if (groupViewModel.type == kCVGroupTypeListHorizontal) { + return _GroupHorizontal(group: groupViewModel); + } else if (groupViewModel.type == kCVGroupTypeListVertical) { + return _GroupVertical(group: groupViewModel); + } else { + return ErrorContent( + message: CVLocalizations.of(context).notSupported); + } + } + return Container(); + }, + ); + } +} + +class _GroupHorizontal extends StatelessWidget { + const _GroupHorizontal({ + @required this.group, + }) : assert(group != null); + + final GroupViewModel group; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: AppDimensions.groupPadding), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + group.name.toUpperCase(), + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold), + ), + FlatButton( + child: Text(CVLocalizations.of(context).groupWidgetDetails), + onPressed: () => navigateToGroup(context, group: group), + ), + ], + ), + ), + Container( + height: AppDimensions.horizontalEntryListHeight, + child: EntryListWidget( + fromGroupViewModel: group, + showOptions: false, + scrollDirection: Axis.horizontal, + shrinkWrap: true, + ), + ), + ], + ); + } +} + +class _GroupVertical extends StatelessWidget { + const _GroupVertical({ + @required this.group, + }) : assert(group != null); + + final GroupViewModel group; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: AppDimensions.groupPadding), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + group.name.toUpperCase(), + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold), + ), + FlatButton( + child: Text(CVLocalizations.of(context).groupWidgetDetails), + onPressed: () => navigateToGroup(context, group: group), + ), + ], + ), + ), + Card( + elevation: 2.0, + child: EntryListWidget( + fromGroupViewModel: group, + showOptions: false, + scrollDirection: Axis.vertical, + shrinkWrap: true, + physics: ClampingScrollPhysics(), + ), + ), + ], + ); + } +} diff --git a/lib/src/ui/widgets/elements/group_widget.dart b/lib/src/ui/widgets/elements/group_widget.dart new file mode 100644 index 0000000..1036f32 --- /dev/null +++ b/lib/src/ui/widgets/elements/group_widget.dart @@ -0,0 +1,42 @@ +import 'package:flutter/widgets.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/element_widget.dart'; + +/// If [groupBloc] given we assume that it have been already initialized and +abstract class GroupWidget extends ElementWidget { + final GroupBloc groupBloc; + + GroupWidget({Key key, String groupId, GroupViewModel group, this.groupBloc}) + : assert(groupId != null && group == null && groupBloc == null), + assert(groupId == null && group != null && groupBloc == null), + assert(groupId == null && group == null && groupBloc != null), + super(key: key, elementId: groupId, element: group); +} + +/// If [widget.profileBloc] exists the lifecycle of it will be managed by its creator +abstract class GroupWidgetState + extends ElementWidgetState { + GroupBloc groupBloc; + + @override + void initState() { + super.initState(); + + groupBloc = widget.groupBloc; + + if (groupBloc == null) { + groupBloc = GroupBloc(); + groupBloc.dispatch(GroupInitialized( + withId: widget.elementId, + withGroup: widget.element, + )); + } + } + + @override + void dispose() { + if (widget.groupBloc == null) groupBloc.dispose(); + super.dispose(); + } +} diff --git a/lib/src/widgets/part_list_widget.dart b/lib/src/ui/widgets/elements/part_list_widget.dart similarity index 74% rename from lib/src/widgets/part_list_widget.dart rename to lib/src/ui/widgets/elements/part_list_widget.dart index 03f364b..b11c710 100644 --- a/lib/src/widgets/part_list_widget.dart +++ b/lib/src/ui/widgets/elements/part_list_widget.dart @@ -1,31 +1,31 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_profile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/part_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_list_tile_widget.dart'; - -/// A widget to list all parts from [PartModel] or from a search + +/// A widget to list all parts from [PartViewModel] or from a search class PartListWidget extends StatelessWidget { PartListWidget({ Key key, - this.fromProfileModel, + this.fromProfileViewModel, this.fromSearch, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(fromProfileModel != null || fromSearch != null), + }) : assert(fromProfileViewModel != null || fromSearch != null), super(key: key); - final ProfileModel fromProfileModel; + final ProfileViewModel fromProfileViewModel; final Object fromSearch; final bool showOptions; @@ -36,9 +36,9 @@ class PartListWidget extends StatelessWidget { @override Widget build(BuildContext context) { - if (fromProfileModel != null) { + if (fromProfileViewModel != null) { return _PartListFromProfile( - profileModel: fromProfileModel, + profileViewModel: fromProfileViewModel, showOptions: showOptions, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, @@ -64,14 +64,14 @@ class PartListWidget extends StatelessWidget { class _PartListFromProfile extends StatelessWidget { _PartListFromProfile({ - @required this.profileModel, + @required this.profileViewModel, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(profileModel != null); + }) : assert(profileViewModel != null); - final ProfileModel profileModel; + final ProfileViewModel profileViewModel; final bool showOptions; @@ -81,12 +81,14 @@ class _PartListFromProfile extends StatelessWidget { @override Widget build(BuildContext context) { - PartListBloc _partListBloc = BlocProvider.of(context); - _partListBloc.fetchProfileParts(profileModel.id); + ElementListBloc _partListBloc = + BlocProvider.of>(context); + _partListBloc.fetchProfileParts(profileViewModel.id); - return StreamBuilder>( + return StreamBuilder>( stream: _partListBloc.partsStream, - builder: (BuildContext context, AsyncSnapshot> snapshot) { + builder: + (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasError) { return ErrorList( error: snapshot.error, @@ -96,7 +98,7 @@ class _PartListFromProfile extends StatelessWidget { ); } else if (snapshot.hasData) { return _PartList( - partModels: snapshot.data, + partViewModels: snapshot.data, showOptions: showOptions, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, @@ -104,7 +106,7 @@ class _PartListFromProfile extends StatelessWidget { ); } else { return LoadingList( - count: profileModel.parts.length, + count: profileViewModel.parts.length, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, physics: this.physics, @@ -145,14 +147,14 @@ class _PartListFromSearch extends StatelessWidget { class _PartList extends StatelessWidget { _PartList({ - @required this.partModels, + @required this.partViewModels, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(partModels != null); + }) : assert(partViewModels != null); - final List partModels; + final List partViewModels; final bool showOptions; @@ -162,7 +164,8 @@ class _PartList extends StatelessWidget { @override Widget build(BuildContext context) { - PartListBloc _partListBloc = BlocProvider.of(context); + ElementListBloc _partListBloc = + BlocProvider.of>(context); final List sortItems = [ SortListItem(field: 'order', title: 'Order', value: SortState.NoSort), @@ -173,7 +176,8 @@ class _PartList extends StatelessWidget { scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, physics: this.physics, - itemCount: showOptions ? partModels.length + 2 : partModels.length, + itemCount: + showOptions ? partViewModels.length + 2 : partViewModels.length, itemBuilder: (BuildContext context, int i) { if (showOptions) { if (i == 0) { @@ -214,7 +218,7 @@ class _PartList extends StatelessWidget { ); } i--; - if (i == partModels.length) { + if (i == partViewModels.length) { return Center( child: FlatButton( onPressed: null, @@ -223,7 +227,7 @@ class _PartList extends StatelessWidget { ); } } - return PartWidget(fromPartModel: partModels[i]); + return PartProfileWidget(part: partViewModels[i]); }, ); } diff --git a/lib/src/ui/widgets/elements/part_profile_widget.dart b/lib/src/ui/widgets/elements/part_profile_widget.dart new file mode 100644 index 0000000..559d14a --- /dev/null +++ b/lib/src/ui/widgets/elements/part_profile_widget.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/src/utils/utils.dart'; + +class PartProfileWidget extends PartWidget { + PartProfileWidget( + {Key key, String partId, PartViewModel part, PartBloc partBloc}) + : super(key: key, partId: partId, part: part, partBloc: partBloc); + + @override + State createState() => _PartProfileWidgetState(); +} + +class _PartProfileWidgetState extends PartWidgetState { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: partBloc, + builder: (BuildContext context, PartState state) { + if (state is PartLoading) { + return LoadingShadowContent( + numberOfTitleLines: 1, + numberOfContentLines: 2, + ); + } + if (state is PartLoaded) { + return _PartWidgetFromModel(partViewModel: state.element); + } else if (state is PartFailure) { + return ErrorContent( + message: translateError(context, state.error), + ); + } + return ErrorContent( + message: CVLocalizations.of(context).notYetImplemented); + }, + ); + } +} + +class _PartWidgetFromModel extends StatelessWidget { + _PartWidgetFromModel({ + @required this.partViewModel, + }) : assert(PartViewModel != null); + + final PartViewModel partViewModel; + + @override + Widget build(BuildContext context) { + if (partViewModel.type == kCVPartTypeListHorizontal) { + return _PartWidgetFromModelHorizontal( + partViewModel: partViewModel, + ); + } else if (partViewModel.type == kCVPartTypeListVertical) { + return _PartWidgetFromModelVertical( + partViewModel: partViewModel, + ); + } else { + return ErrorContent(message: CVLocalizations.of(context).notSupported); + } + } +} + +class _PartWidgetFromModelHorizontal extends StatelessWidget { + _PartWidgetFromModelHorizontal({ + @required this.partViewModel, + }) : assert(PartViewModel != null); + + final PartViewModel partViewModel; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + partViewModel.name.toUpperCase(), + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold), + ), + FlatButton( + child: Text(CVLocalizations.of(context).partWidgetDetails), + onPressed: () => navigateToPart(context, partViewModel.id), + ), + ], + ), + Container( + height: AppDimensions.horizontalGroupListHeight, + child: GroupListWidget( + fromPartViewModel: partViewModel, + scrollDirection: Axis.horizontal, + shrinkWrap: true, + ), + ), + ], + ); + } +} + +class _PartWidgetFromModelVertical extends StatelessWidget { + _PartWidgetFromModelVertical({ + @required this.partViewModel, + }) : assert(PartViewModel != null); + + final PartViewModel partViewModel; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + partViewModel.name.toUpperCase(), + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold), + ), + FlatButton( + child: Text(CVLocalizations.of(context).partWidgetDetails), + onPressed: () => navigateToPart(context, partViewModel.id), + ), + ], + ), + GroupListWidget( + fromPartViewModel: partViewModel, + scrollDirection: Axis.vertical, + shrinkWrap: true, + physics: ClampingScrollPhysics(), + ), + ], + ); + } +} diff --git a/lib/src/ui/widgets/elements/part_widget.dart b/lib/src/ui/widgets/elements/part_widget.dart new file mode 100644 index 0000000..a0fe892 --- /dev/null +++ b/lib/src/ui/widgets/elements/part_widget.dart @@ -0,0 +1,42 @@ +import 'package:flutter/widgets.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/element_widget.dart'; + +/// If [partBloc] given we assume that it have been already initialized +abstract class PartWidget extends ElementWidget { + final PartBloc partBloc; + + PartWidget({Key key, String partId, PartViewModel part, this.partBloc}) + : assert(partId != null && part == null && partBloc == null), + assert(partId == null && part != null && partBloc == null), + assert(partId == null && part == null && partBloc != null), + super(key: key, elementId: partId, element: part); +} + +/// If [widget.profileBloc] exists the lifecycle of it will be managed by its creator +abstract class PartWidgetState + extends ElementWidgetState { + PartBloc partBloc; + + @override + void initState() { + super.initState(); + + partBloc = widget.partBloc; + + if (partBloc == null) { + partBloc = PartBloc(); + partBloc.dispatch(PartInitialized( + withId: widget.elementId, + withPart: widget.element, + )); + } + } + + @override + void dispose() { + if (widget.partBloc == null) partBloc.dispose(); + super.dispose(); + } +} diff --git a/lib/src/widgets/profile_list_widget.dart b/lib/src/ui/widgets/elements/profile_list_widget.dart similarity index 74% rename from lib/src/widgets/profile_list_widget.dart rename to lib/src/ui/widgets/elements/profile_list_widget.dart index 3dd5cef..4107bc1 100644 --- a/lib/src/widgets/profile_list_widget.dart +++ b/lib/src/ui/widgets/elements/profile_list_widget.dart @@ -1,32 +1,32 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/commons/dimensions.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/profile_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_list_tile_widget.dart'; -/// A widget to list all profiles from [UserModel] or from a search +/// A widget to list all profiles from [UserViewModel] or from a search class ProfileListWidget extends StatelessWidget { const ProfileListWidget({ Key key, - this.fromUserModel, + this.fromUserViewModel, this.fromSearch, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(fromUserModel != null || fromSearch != null), + }) : assert(fromUserViewModel != null || fromSearch != null), super(key: key); - final UserModel fromUserModel; + final UserViewModel fromUserViewModel; final Object fromSearch; final bool showOptions; @@ -37,9 +37,9 @@ class ProfileListWidget extends StatelessWidget { @override Widget build(BuildContext context) { - if (fromUserModel != null) { - return _ProfileListFromUserModel( - fromUserModel, + if (fromUserViewModel != null) { + return _ProfileListFromUserViewModel( + fromUserViewModel, showOptions: showOptions, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, @@ -64,16 +64,16 @@ class ProfileListWidget extends StatelessWidget { } } -class _ProfileListFromUserModel extends StatelessWidget { - _ProfileListFromUserModel( - this.userModel, { +class _ProfileListFromUserViewModel extends StatelessWidget { + _ProfileListFromUserViewModel( + this.userViewModel, { this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, }); - final UserModel userModel; + final UserViewModel userViewModel; final bool showOptions; @@ -83,14 +83,15 @@ class _ProfileListFromUserModel extends StatelessWidget { @override Widget build(BuildContext context) { - ProfileListBloc profileListBloc = BlocProvider.of(context); + ElementListBloc profileListBloc = + BlocProvider.of>(context); profileListBloc.fetchAccountProfiles(); - return StreamBuilder>( + return StreamBuilder>( stream: profileListBloc.profilesStream, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { + builder: (BuildContext context, + AsyncSnapshot> snapshot) { if (snapshot.hasError) { return ErrorList( error: snapshot.error, @@ -100,7 +101,7 @@ class _ProfileListFromUserModel extends StatelessWidget { ); } else if (snapshot.hasData) { return _ProfileList( - profileModels: snapshot.data, + profileViewModels: snapshot.data, showOptions: showOptions, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, @@ -108,7 +109,7 @@ class _ProfileListFromUserModel extends StatelessWidget { ); } else { return LoadingList( - count: userModel.profileIds.length, + count: userViewModel.profileIds.length, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, physics: this.physics, @@ -141,10 +142,10 @@ class _ProfileListFromSearch extends StatelessWidget { ProfileListBloc profileListBloc = BlocProvider.of(context); profileListBloc.fetchProfiles(search); - return StreamBuilder>( + return StreamBuilder>( stream: profileListBloc.profilesStream, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { + builder: (BuildContext context, + AsyncSnapshot> snapshot) { if (snapshot.hasError) { return ErrorList( error: snapshot.error, @@ -154,7 +155,7 @@ class _ProfileListFromSearch extends StatelessWidget { ); } else if (snapshot.hasData) { return _ProfileList( - profileModels: snapshot.data, + profileViewModels: snapshot.data, showOptions: showOptions, scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, @@ -175,14 +176,14 @@ class _ProfileListFromSearch extends StatelessWidget { class _ProfileList extends StatelessWidget { _ProfileList({ - @required this.profileModels, + @required this.profileViewModels, this.showOptions = false, this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(profileModels != null); + }) : assert(profileViewModels != null); - final List profileModels; + final List profileViewModels; final bool showOptions; @@ -192,8 +193,8 @@ class _ProfileList extends StatelessWidget { @override Widget build(BuildContext context) { - ProfileListBloc _profileListBloc = - BlocProvider.of(context); + ElementListBloc _profileListBloc = + BlocProvider.of>(context); final List sortItems = [ SortListItem(field: 'title', title: 'Title', value: SortState.NoSort) @@ -203,12 +204,13 @@ class _ProfileList extends StatelessWidget { scrollDirection: this.scrollDirection, shrinkWrap: this.shrinkWrap, physics: this.physics, - itemCount: showOptions ? profileModels.length + 2 : profileModels.length, + itemCount: + showOptions ? profileViewModels.length + 2 : profileViewModels.length, itemBuilder: (BuildContext context, int i) { if (showOptions) { if (i == 0) { return Container( - height: AppDimensions.kCVListHeaderDefaultHeightMax, + height: AppDimensions.listHeaderDefaultHeightMax, color: Colors.transparent, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -249,7 +251,7 @@ class _ProfileList extends StatelessWidget { ); } i--; - if (i == profileModels.length) { + if (i == profileViewModels.length) { return Center( child: FlatButton( onPressed: null, @@ -258,7 +260,7 @@ class _ProfileList extends StatelessWidget { ); } } - return ProfileWidget(profileModel: profileModels[i]); + return ProfileTile(profileViewModel: profileViewModels[i]); }, ); } diff --git a/lib/src/ui/widgets/elements/profile_tile_widget.dart b/lib/src/ui/widgets/elements/profile_tile_widget.dart new file mode 100644 index 0000000..ebc3583 --- /dev/null +++ b/lib/src/ui/widgets/elements/profile_tile_widget.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/navigation.dart'; + +class ProfileTile extends StatelessWidget { + final ProfileViewModel profileViewModel; + + const ProfileTile({ + Key key, + this.profileViewModel, + }) : assert(profileViewModel != null), + super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: InitialCircleAvatar( + backgroundImage: NetworkImage(profileViewModel.picture ?? ''), + ), + title: Text(profileViewModel.title ?? ''), + subtitle: Text(profileViewModel.subtitle ?? ''), + onTap: () => navigateToProfile(context, profileViewModel.id), + trailing: Icon(MdiIcons.accountDetails), + ); + } +} diff --git a/lib/src/ui/widgets/elements/profile_widget.dart b/lib/src/ui/widgets/elements/profile_widget.dart new file mode 100644 index 0000000..6fa88a2 --- /dev/null +++ b/lib/src/ui/widgets/elements/profile_widget.dart @@ -0,0 +1,43 @@ +import 'package:flutter/widgets.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/element_widget.dart'; + +/// If [profileBloc] given we assume that it have been already initialized +abstract class ProfileWidget extends ElementWidget { + final ProfileBloc profileBloc; + + ProfileWidget( + {Key key, String profileId, ProfileViewModel profile, this.profileBloc}) + : assert(profileId != null && profile == null && profileBloc == null), + assert(profileId == null && profile != null && profileBloc == null), + assert(profileId == null && profile == null && profileBloc != null), + super(key: key, elementId: profileId, element: profile); +} + +/// If [widget.profileBloc] exists the lifecycle of it will be managed by its creator +abstract class ProfileWidgetState + extends ElementWidgetState { + ProfileBloc profileBloc; + + @override + void initState() { + super.initState(); + + profileBloc = widget.profileBloc; + + if (profileBloc == null) { + profileBloc = ProfileBloc(); + profileBloc.dispatch(ProfileInitialized( + withId: widget.elementId, + withProfile: widget.element, + )); + } + } + + @override + void dispose() { + if (widget.profileBloc == null) profileBloc.dispose(); + super.dispose(); + } +} diff --git a/lib/src/widgets/error_widget.dart b/lib/src/ui/widgets/error_widget.dart similarity index 93% rename from lib/src/widgets/error_widget.dart rename to lib/src/ui/widgets/error_widget.dart index c207aa1..5522423 100644 --- a/lib/src/widgets/error_widget.dart +++ b/lib/src/ui/widgets/error_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; class ErrorContent extends StatelessWidget { @@ -17,7 +17,7 @@ class ErrorContent extends StatelessWidget { children: [ Icon( Icons.error, - color: AppColors.kCVErrorRed, + color: AppColors.errorColor, ), Expanded( child: Text(message, textAlign: TextAlign.center), diff --git a/lib/src/widgets/initial_circle_avatar_widget.dart b/lib/src/ui/widgets/initial_circle_avatar_widget.dart similarity index 100% rename from lib/src/widgets/initial_circle_avatar_widget.dart rename to lib/src/ui/widgets/initial_circle_avatar_widget.dart diff --git a/lib/src/widgets/loading_widget.dart b/lib/src/ui/widgets/loading_widget.dart similarity index 99% rename from lib/src/widgets/loading_widget.dart rename to lib/src/ui/widgets/loading_widget.dart index a518913..03454b1 100644 --- a/lib/src/widgets/loading_widget.dart +++ b/lib/src/ui/widgets/loading_widget.dart @@ -79,6 +79,7 @@ class _LoadingShadowContentState extends State child: Container( height: 13.0, width: MediaQuery.of(context).size.width / _divideFactor, + ///constraints: BoxConstraints.expand(), decoration: BoxDecoration( color: Colors.grey.withOpacity(_opacity.value), diff --git a/lib/src/ui/widgets/login_form_widget.dart b/lib/src/ui/widgets/login_form_widget.dart new file mode 100644 index 0000000..97fdc5f --- /dev/null +++ b/lib/src/ui/widgets/login_form_widget.dart @@ -0,0 +1,303 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/utils.dart'; + +class LoginFormWidget extends StatefulWidget { + const LoginFormWidget({ + Key key, + }) : super(key: key); + + @override + State createState() => _LoginFormWidgetState(); +} + +class _LoginFormWidgetState extends State { + static const _TAG = '_LoginFormWidgetState'; + + _LoginFormWidgetState(); + + final FocusNode myFocusNodeEmailLogin = FocusNode(); + final FocusNode myFocusNodePasswordLogin = FocusNode(); + + TextEditingController loginEmailController = new TextEditingController(); + TextEditingController loginPasswordController = new TextEditingController(); + + bool _obscureTextLogin = true; + + String errorText; + + // TODO: Add loginbloc + LoginBloc get _loginBloc => null; + + @override + Widget build(BuildContext context) { + logger.info('$_TAG:build'); + + return BlocBuilder( + bloc: _loginBloc, + builder: (BuildContext context, LoginState state) { + return Container( + padding: EdgeInsets.only(top: 23.0), + child: Column( + children: [ + Stack( + alignment: Alignment.topCenter, + overflow: Overflow.visible, + children: [ + Card( + elevation: 2.0, + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + child: Container( + width: 300.0, + height: 190.0, + child: Column( + children: [ + Padding( + padding: EdgeInsets.only( + top: 20.0, + bottom: 20.0, + left: 25.0, + right: 25.0), + child: TextField( + focusNode: myFocusNodeEmailLogin, + controller: loginEmailController, + keyboardType: TextInputType.emailAddress, + style: TextStyle( + fontSize: 16.0, color: Colors.black), + decoration: InputDecoration( + border: InputBorder.none, + icon: Icon( + MdiIcons.email, + color: Colors.black, + size: 22.0, + ), + hintText: CVLocalizations.of(context).email, + hintStyle: TextStyle(fontSize: 17.0), + ), + ), + ), + Padding( + padding: EdgeInsets.only( + top: 20.0, + bottom: 20.0, + left: 25.0, + right: 25.0), + child: TextField( + focusNode: myFocusNodePasswordLogin, + controller: loginPasswordController, + obscureText: _obscureTextLogin, + style: TextStyle( + fontSize: 16.0, color: Colors.black), + decoration: InputDecoration( + border: InputBorder.none, + icon: Icon( + MdiIcons.lock, + size: 22.0, + color: Colors.black, + ), + hintText: CVLocalizations.of(context).password, + hintStyle: TextStyle(fontSize: 17.0), + suffixIcon: GestureDetector( + onTap: _toggleLogin, + child: Icon( + MdiIcons.eye, + size: 15.0, + color: Colors.black, + ), + ), + ), + ), + ), + (state is LoginFailure) + ? ErrorContent( + message: translateError(context, state.error)) + : Container(), + ], + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 170.0), + decoration: new BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(5.0)), + boxShadow: [ + BoxShadow( + color: AppColors.loginGradientStart, + offset: Offset(1.0, 6.0), + blurRadius: 20.0, + ), + BoxShadow( + color: AppColors.loginGradientEnd, + offset: Offset(1.0, 6.0), + blurRadius: 20.0, + ), + ], + gradient: new LinearGradient( + colors: [ + AppColors.loginGradientEnd, + AppColors.loginGradientStart + ], + begin: const FractionalOffset(0.2, 0.2), + end: const FractionalOffset(1.0, 1.0), + stops: [0.0, 1.0], + tileMode: TileMode.clamp), + ), + child: MaterialButton( + highlightColor: Colors.transparent, + splashColor: AppColors.loginGradientEnd, + //shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 10.0, horizontal: 42.0), + child: Text( + CVLocalizations.of(context).authSignInCTA, + style: TextStyle( + color: Colors.white, + fontSize: 25.0, + ), + ), + ), + onPressed: state is! RegisterLoading + ? _onLoginButtonPressed + : null, + ), + ), + ], + ), + Padding( + padding: EdgeInsets.only(top: 10.0), + child: FlatButton( + onPressed: () {}, + child: Text( + 'Forgot Password?', + style: TextStyle( + decoration: TextDecoration.underline, + color: Colors.white, + fontSize: 16.0, + ), + )), + ), + Padding( + padding: EdgeInsets.only(top: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + gradient: new LinearGradient( + colors: [ + Colors.white10, + Colors.white, + ], + begin: const FractionalOffset(0.0, 0.0), + end: const FractionalOffset(1.0, 1.0), + stops: [0.0, 1.0], + tileMode: TileMode.clamp), + ), + width: 100.0, + height: 1.0, + ), + Padding( + padding: EdgeInsets.only(left: 15.0, right: 15.0), + child: Text( + 'Or', + style: TextStyle( + color: Colors.white, + fontSize: 16.0, + ), + ), + ), + Container( + decoration: BoxDecoration( + gradient: new LinearGradient( + colors: [ + Colors.white, + Colors.white10, + ], + begin: const FractionalOffset(0.0, 0.0), + end: const FractionalOffset(1.0, 1.0), + stops: [0.0, 1.0], + tileMode: TileMode.clamp), + ), + width: 100.0, + height: 1.0, + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only(top: 10.0, right: 40.0), + child: GestureDetector( + onTap: () => print('Facebook button pressed'), + child: Container( + padding: const EdgeInsets.all(15.0), + decoration: new BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + ), + child: new Icon( + MdiIcons.facebook, + color: Color(0xFF0084ff), + ), + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 10.0), + child: GestureDetector( + onTap: () => print('Google button pressed'), + child: Container( + padding: const EdgeInsets.all(15.0), + decoration: new BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + ), + child: new Icon( + MdiIcons.google, + color: Color(0xFF0084ff), + ), + ), + ), + ), + ], + ), + ], + ), + ); + }, + ); + } + + @override + void dispose() { + myFocusNodeEmailLogin.dispose(); + myFocusNodePasswordLogin.dispose(); + super.dispose(); + } + + void _toggleLogin() { + setState(() { + _obscureTextLogin = !_obscureTextLogin; + }); + } + + _onLoginButtonPressed() { + _loginBloc.dispatch(LoginButtonPressed( + email: loginEmailController.text, + password: loginPasswordController.text, + )); + } +} diff --git a/lib/src/widgets/menu_bottom_sheet_widget.dart b/lib/src/ui/widgets/menu_bottom_sheet_widget.dart similarity index 84% rename from lib/src/widgets/menu_bottom_sheet_widget.dart rename to lib/src/ui/widgets/menu_bottom_sheet_widget.dart index 05edd1b..0344bf9 100644 --- a/lib/src/widgets/menu_bottom_sheet_widget.dart +++ b/lib/src/ui/widgets/menu_bottom_sheet_widget.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/account_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_tile_widget.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/widgets/account_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/theme_switch_tile_widget.dart'; class MenuBottomSheet extends StatelessWidget { const MenuBottomSheet({ diff --git a/lib/src/ui/widgets/menu_button_widget.dart b/lib/src/ui/widgets/menu_button_widget.dart new file mode 100644 index 0000000..4a31456 --- /dev/null +++ b/lib/src/ui/widgets/menu_button_widget.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/navigation.dart'; + +class MenuButton extends StatefulWidget { + const MenuButton({ + Key key, + }) : super(key: key); + + @override + State createState() => _MenuButtonState(); +} + +class _MenuButtonState extends State { + // TODO: Add AuthBloc + AuthenticationBloc get _authBloc => null; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: _authBloc, + builder: (BuildContext context, AuthenticationState state) { + if (state is AuthenticationAuthenticated) return _MenuButtonConnected(); + if (state is AuthenticationUnauthenticated) + return _MenuButtonNotConnected(); + return Container(); + }, + ); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// // +// _MenuButtonNotConnected // +// // +//////////////////////////////////////////////////////////////////////////////// + +class _MenuButtonNotConnected extends StatelessWidget { + @override + Widget build(BuildContext context) { + return IconButton( + onPressed: () => openMenuBottomSheet(context), + icon: Icon(Icons.menu), + ); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// // +// _MenuButtonConnected // +// // +//////////////////////////////////////////////////////////////////////////////// + +class _MenuButtonConnected extends StatefulWidget { + _MenuButtonConnected({Key key}) : super(key: key); + + @override + State createState() => _MenuButtonConnectedState(); +} + +class _MenuButtonConnectedState extends State<_MenuButtonConnected> { + AccountBloc get _accountBloc => null; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: _accountBloc, + builder: (BuildContext context, AccountState state) { + if (state is AccountLoaded) { + return Padding( + padding: EdgeInsets.only(top: 3.0, bottom: 3.0), + child: IconButton( + onPressed: () => openMenuBottomSheet(context), + icon: InitialCircleAvatar( + text: state.userModel.username, + backgroundImage: NetworkImage(state.userModel.picture), + ), + ), + ); + } + }, + ); + } +} diff --git a/lib/src/widgets/profile_image_widget.dart b/lib/src/ui/widgets/profile_image_widget.dart similarity index 83% rename from lib/src/widgets/profile_image_widget.dart rename to lib/src/ui/widgets/profile_image_widget.dart index 09c8ed7..533c8f7 100644 --- a/lib/src/widgets/profile_image_widget.dart +++ b/lib/src/ui/widgets/profile_image_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/widgets/initial_circle_avatar_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; class ProfileImage extends StatelessWidget { const ProfileImage({ diff --git a/lib/src/ui/widgets/register_form_widget.dart b/lib/src/ui/widgets/register_form_widget.dart new file mode 100644 index 0000000..44dbf41 --- /dev/null +++ b/lib/src/ui/widgets/register_form_widget.dart @@ -0,0 +1,303 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; + +class RegisterFormWidget extends StatefulWidget { + @override + State createState() => _RegisterFormWidgetState(); +} + +class _RegisterFormWidgetState extends State { + final FocusNode myFocusNodePassword = FocusNode(); + final FocusNode myFocusNodeEmail = FocusNode(); + final FocusNode myFocusNodeFirstName = FocusNode(); + final FocusNode myFocusNodeLastName = FocusNode(); + + bool _obscureTextSignup = true; + bool _obscureTextSignupConfirm = true; + + TextEditingController signupEmailController = new TextEditingController(); + TextEditingController signupFisrtNameController = new TextEditingController(); + TextEditingController signupLastNameController = new TextEditingController(); + TextEditingController signupPasswordController = new TextEditingController(); + TextEditingController signupConfirmPasswordController = + new TextEditingController(); + + // TODO : add register bloc + RegisterBloc get _registerBloc => null; + + @override + Widget build(BuildContext context) { + RegisterBloc _registerBloc = BlocProvider.of(context); + + return BlocBuilder( + bloc: _registerBloc, + builder: (BuildContext context, RegisterState state) { + Container( + padding: EdgeInsets.only(top: 23.0), + child: Column( + children: [ + Stack( + alignment: Alignment.topCenter, + overflow: Overflow.visible, + children: [ + Card( + elevation: 2.0, + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + child: Container( + width: 300.0, + height: 360.0, + child: Column( + children: [ + Padding( + padding: EdgeInsets.only( + top: 20.0, + bottom: 20.0, + left: 25.0, + right: 25.0), + child: TextField( + focusNode: myFocusNodeFirstName, + controller: signupFisrtNameController, + keyboardType: TextInputType.text, + textCapitalization: TextCapitalization.words, + style: TextStyle( + fontSize: 16.0, + color: Colors.black, + ), + decoration: InputDecoration( + border: InputBorder.none, + icon: Icon( + MdiIcons.account, + color: Colors.black, + ), + hintText: 'Firstname', + hintStyle: TextStyle(fontSize: 16.0), + ), + ), + ), + Container( + width: 250.0, + height: 1.0, + color: Colors.grey[400], + ), + Padding( + padding: EdgeInsets.only( + top: 20.0, + bottom: 20.0, + left: 25.0, + right: 25.0), + child: TextField( + focusNode: myFocusNodeFirstName, + controller: signupLastNameController, + keyboardType: TextInputType.text, + textCapitalization: TextCapitalization.words, + style: TextStyle( + fontSize: 16.0, + color: Colors.black, + ), + decoration: InputDecoration( + border: InputBorder.none, + icon: Icon( + MdiIcons.account, + color: Colors.black, + ), + hintText: 'Lastname', + hintStyle: TextStyle(fontSize: 16.0), + ), + ), + ), + Container( + width: 250.0, + height: 1.0, + color: Colors.grey[400], + ), + Padding( + padding: EdgeInsets.only( + top: 20.0, + bottom: 20.0, + left: 25.0, + right: 25.0), + child: TextField( + focusNode: myFocusNodeEmail, + controller: signupEmailController, + keyboardType: TextInputType.emailAddress, + style: TextStyle( + fontSize: 16.0, color: Colors.black), + decoration: InputDecoration( + border: InputBorder.none, + icon: Icon( + MdiIcons.email, + color: Colors.black, + ), + hintText: CVLocalizations.of(context).email, + hintStyle: TextStyle(fontSize: 16.0), + ), + ), + ), + Container( + width: 250.0, + height: 1.0, + color: Colors.grey[400], + ), + Padding( + padding: EdgeInsets.only( + top: 20.0, + bottom: 20.0, + left: 25.0, + right: 25.0), + child: TextField( + focusNode: myFocusNodePassword, + controller: signupPasswordController, + obscureText: _obscureTextSignup, + style: TextStyle( + fontSize: 16.0, color: Colors.black), + decoration: InputDecoration( + border: InputBorder.none, + icon: Icon( + MdiIcons.lock, + color: Colors.black, + ), + hintText: + CVLocalizations.of(context).password, + hintStyle: TextStyle(fontSize: 16.0), + suffixIcon: GestureDetector( + onTap: _toggleSignup, + child: Icon( + MdiIcons.eye, + size: 15.0, + color: Colors.black, + ), + ), + ), + ), + ), + Container( + width: 250.0, + height: 1.0, + color: Colors.grey[400], + ), + Padding( + padding: EdgeInsets.only( + top: 20.0, + bottom: 20.0, + left: 25.0, + right: 25.0), + child: TextField( + controller: signupConfirmPasswordController, + obscureText: _obscureTextSignupConfirm, + style: TextStyle( + fontSize: 16.0, color: Colors.black), + decoration: InputDecoration( + border: InputBorder.none, + icon: Icon( + MdiIcons.lock, + color: Colors.black, + ), + hintText: CVLocalizations.of(context) + .passwordRepeat, + hintStyle: TextStyle(fontSize: 16.0), + suffixIcon: GestureDetector( + onTap: _toggleSignupConfirm, + child: Icon( + MdiIcons.eye, + size: 15.0, + color: Colors.black, + ), + ), + ), + ), + ), + ], + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 340.0), + decoration: new BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(5.0)), + boxShadow: [ + BoxShadow( + color: AppColors.loginGradientStart, + offset: Offset(1.0, 6.0), + blurRadius: 20.0, + ), + BoxShadow( + color: AppColors.loginGradientEnd, + offset: Offset(1.0, 6.0), + blurRadius: 20.0, + ), + ], + gradient: new LinearGradient( + colors: [ + AppColors.loginGradientEnd, + AppColors.loginGradientStart + ], + begin: const FractionalOffset(0.2, 0.2), + end: const FractionalOffset(1.0, 1.0), + stops: [0.0, 1.0], + tileMode: TileMode.clamp), + ), + child: MaterialButton( + highlightColor: Colors.transparent, + splashColor: AppColors.loginGradientEnd, + //shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 10.0, horizontal: 42.0), + child: Text( + CVLocalizations.of(context).authSignUpCTA, + style: TextStyle( + color: Colors.white, + fontSize: 25.0, + ), + ), + ), + onPressed: state is! RegisterLoading + ? _onRegisterButtonPressed + : null, + ), + ), + ], + ), + ], + ), + ); + }); + } + + @override + void dispose() { + myFocusNodeFirstName.dispose(); + myFocusNodeEmail.dispose(); + myFocusNodePassword.dispose(); + super.dispose(); + } + + void _toggleSignup() { + setState(() { + _obscureTextSignup = !_obscureTextSignup; + }); + } + + void _toggleSignupConfirm() { + setState(() { + _obscureTextSignupConfirm = !_obscureTextSignupConfirm; + }); + } + + _onRegisterButtonPressed() { + _registerBloc.dispatch(RegisterButtonPressed( + fName: signupFisrtNameController.text, + lName: signupLastNameController.text, + email: signupEmailController.text, + password: signupPasswordController.text, + )); + } +} diff --git a/lib/src/widgets/rounded_modal.dart b/lib/src/ui/widgets/rounded_modal.dart similarity index 100% rename from lib/src/widgets/rounded_modal.dart rename to lib/src/ui/widgets/rounded_modal.dart diff --git a/lib/src/widgets/sort_box_widget.dart b/lib/src/ui/widgets/sort_box_widget.dart similarity index 100% rename from lib/src/widgets/sort_box_widget.dart rename to lib/src/ui/widgets/sort_box_widget.dart diff --git a/lib/src/widgets/sort_dialog_widget.dart b/lib/src/ui/widgets/sort_dialog_widget.dart similarity index 81% rename from lib/src/widgets/sort_dialog_widget.dart rename to lib/src/ui/widgets/sort_dialog_widget.dart index 7eb6d4f..a8fe671 100644 --- a/lib/src/widgets/sort_dialog_widget.dart +++ b/lib/src/ui/widgets/sort_dialog_widget.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/commons/dimensions.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_list_tile_widget.dart'; class SortDialog extends StatefulWidget { const SortDialog({ @@ -42,8 +42,8 @@ class _SortDialogState extends State { contentPadding: EdgeInsets.all(0.0), title: widget.title, content: Container( - width: AppDimensions.kCVSortDialogWidth, - height: AppDimensions.kCVSortDialogHeight, + width: AppDimensions.sortDialogWidth, + height: AppDimensions.sortDialogHeight, child: ReorderableListView( onReorder: _onReorder, children: _listTiles, diff --git a/lib/src/widgets/sort_list_tile_widget.dart b/lib/src/ui/widgets/sort_list_tile_widget.dart similarity index 94% rename from lib/src/widgets/sort_list_tile_widget.dart rename to lib/src/ui/widgets/sort_list_tile_widget.dart index 69f01ae..08baf47 100644 --- a/lib/src/widgets/sort_list_tile_widget.dart +++ b/lib/src/ui/widgets/sort_list_tile_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; class SortListItem { SortListItem({ diff --git a/lib/src/ui/widgets/theme_switch_tile_widget.dart b/lib/src/ui/widgets/theme_switch_tile_widget.dart new file mode 100644 index 0000000..c1af522 --- /dev/null +++ b/lib/src/ui/widgets/theme_switch_tile_widget.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; + +class ThemeSwitchTile extends StatelessWidget { + const ThemeSwitchTile({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + AppBloc _appBloc = BlocProvider.of(context); + + return BlocBuilder( + bloc: _appBloc, + builder: (BuildContext context, AppState state) { + if (state is AppUninitialized) { + return ListTile( + title: Text(CVLocalizations.of(context).settingsThemeCTA), + ); + } + if (state is AppLoading) { + return ListTile( + title: Text(CVLocalizations.of(context).settingsThemeCTA), + trailing: CircularProgressIndicator(), + ); + } + if (state is AppInitialized) { + return SwitchListTile( + secondary: Icon( + state.theme == ThemeType.DARK + ? MdiIcons.weatherSunny + : MdiIcons.whiteBalanceSunny, + ), + title: Text(CVLocalizations.of(context).settingsThemeCTA), + value: state.theme == ThemeType.DARK ? true : false, + onChanged: (bool enable) { + if (enable) + _appBloc.dispatch(AppThemeChanged(theme: ThemeType.DARK)); + else + _appBloc.dispatch(AppThemeChanged(theme: ThemeType.LIGHT)); + }); + } + }, + ); + } +} diff --git a/lib/src/utils/logging_service.dart b/lib/src/utils/logging_service.dart index bc8e73d..3ca85e6 100644 --- a/lib/src/utils/logging_service.dart +++ b/lib/src/utils/logging_service.dart @@ -14,7 +14,6 @@ enum LogType { warning, error, fatal, - action, } enum FatalErrorHandling { @@ -114,9 +113,6 @@ class LoggingService { // Initialization // ----------------------------------------------------------------------- - @override - void initEventHandlers() {} - /// /// Init local log file /// diff --git a/lib/src/utils/navigation.dart b/lib/src/utils/navigation.dart index 25ad6ee..53f3cbf 100644 --- a/lib/src/utils/navigation.dart +++ b/lib/src/utils/navigation.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/commons/paths.dart'; -import 'package:social_cv_client_flutter/src/widgets/menu_bottom_sheet_widget.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/paths.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/elements/entry_profile_page.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/elements/group_profile_page.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/menu_bottom_sheet_widget.dart'; void navigateToLogin(BuildContext context) { Navigator.of(context).pushNamed(AppPaths.kPathLogin); @@ -24,12 +27,37 @@ void navigateToPart(BuildContext context, String partId) { Navigator.of(context).pushNamed(AppPaths.kPathParts + '/${partId ?? ''}'); } -void navigateToGroup(BuildContext context, String groupId) { - Navigator.of(context).pushNamed(AppPaths.kPathGroups + '/${groupId ?? ''}'); +void navigateToGroup(BuildContext context, + {String groupId, GroupViewModel group}) { + assert(groupId != null || group != null); + if (group != null) { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => GroupPage(group: group))); +// Navigator.of(context).pushNamed( +// AppPaths.kPathGroups + '/${group.id ?? ''}', +// arguments: group, +// ); + } else if (groupId != null) { + Navigator.of(context).pushNamed( + AppPaths.kPathGroups + '/${groupId ?? ''}', + ); + } } -void navigateToEntry(BuildContext context, String entryId) { - Navigator.of(context).pushNamed(AppPaths.kPathEntries + '/${entryId ?? ''}'); +void navigateToEntry(BuildContext context, + {String entryId, EntryViewModel entry}) { + assert(entryId != null || entry != null); + if (entry != null) { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => EntryPage(entry: entry))); +// Navigator.of(context).pushNamed( +// AppPaths.kPathEntries + '/${entry.id ?? ''}', +// arguments: entry, +// ); + } else if (entryId != null) { + Navigator.of(context) + .pushNamed(AppPaths.kPathEntries + '/${entryId ?? ''}'); + } } void openMenuBottomSheet(BuildContext context) { diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 7a31085..13c0d7b 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_flutter/src/commons/defaults.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/defaults.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; String getInitials(String nameString) { diff --git a/lib/src/widgets/account_tile_widget.dart b/lib/src/widgets/account_tile_widget.dart deleted file mode 100644 index 798b4c4..0000000 --- a/lib/src/widgets/account_tile_widget.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/widgets/initial_circle_avatar_widget.dart'; - -class AccountTile extends StatelessWidget { - const AccountTile({ - Key key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - AccountBloc _accountBloc = BlocProvider.of(context); - return StreamBuilder( - stream: _accountBloc.isAuthenticatedStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - if (snapshot.data == true) return _AccountTileConnected(); - if (snapshot.data == false) return _AccountTileNotConnected(); - } - return Container(); - }, - ); - } -} - -class _AccountTileConnected extends StatelessWidget { - @override - Widget build(BuildContext context) { - AccountBloc _accountBloc = BlocProvider.of(context); - return StreamBuilder( - stream: _accountBloc.accountDetailsStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - UserModel userModel = snapshot.data; - return ListTile( - leading: InitialCircleAvatar( - text: userModel.username, - backgroundImage: NetworkImage(userModel.picture)), - title: Text(userModel.username), - subtitle: Text(userModel.email), - trailing: IconButton( - icon: Icon(MdiIcons.logout), - onPressed: () => _accountBloc.logout(), - ), - ); - } else if (snapshot.hasError) { - return Container(child: Text('${snapshot.error}')); - } - return Container(); - }, - ); - } -} - -class _AccountTileNotConnected extends StatelessWidget { - @override - Widget build(BuildContext context) { - return GestureDetector( - child: ListTile( - title: Center(child: Text(CVLocalizations.of(context).authSignInCTA)), - trailing: Icon(MdiIcons.login), - ), - onTap: () => navigateToLogin(context), - ); - } -} diff --git a/lib/src/widgets/group_widget.dart b/lib/src/widgets/group_widget.dart deleted file mode 100644 index f35bdda..0000000 --- a/lib/src/widgets/group_widget.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/commons/api_values.dart'; -import 'package:social_cv_client_flutter/src/commons/dimensions.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/widgets/entry_list_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; - -class GroupWidget extends StatelessWidget { - const GroupWidget({ - Key key, - @required this.groupModel, - }) : super(key: key); - - final GroupModel groupModel; - - @override - Widget build(BuildContext context) { - if (groupModel.type == kCVGroupTypeListHorizontal) { - return _GroupHorizontal(groupModel: groupModel); - } else if (groupModel.type == kCVGroupTypeListVertical) { - return _GroupVertical(groupModel: groupModel); - } else { - return ErrorContent(message: CVLocalizations.of(context).notSupported); - } - } -} - -class _GroupHorizontal extends StatelessWidget { - const _GroupHorizontal({ - @required this.groupModel, - }) : assert(groupModel != null); - - final GroupModel groupModel; - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: AppDimensions.kCVGroupPadding), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - groupModel.name.toUpperCase(), - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold), - ), - FlatButton( - child: Text(CVLocalizations.of(context).groupWidgetDetails), - onPressed: () => navigateToGroup(context, groupModel.id), - ), - ], - ), - ), - Container( - height: AppDimensions.kCVHorizontalEntryListHeight, - child: BlocProvider( - bloc: EntryListBloc(), - child: EntryListWidget( - fromGroupModel: groupModel, - showOptions: false, - scrollDirection: Axis.horizontal, - shrinkWrap: true, - ), - ), - ), - ], - ); - } -} - -class _GroupVertical extends StatelessWidget { - const _GroupVertical({ - @required this.groupModel, - }) : assert(groupModel != null); - - final GroupModel groupModel; - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: AppDimensions.kCVGroupPadding), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - groupModel.name.toUpperCase(), - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold), - ), - FlatButton( - child: Text(CVLocalizations.of(context).groupWidgetDetails), - onPressed: () => navigateToGroup(context, groupModel.id), - ), - ], - ), - ), - Card( - elevation: 2.0, - child: BlocProvider( - bloc: EntryListBloc(), - child: EntryListWidget( - fromGroupModel: groupModel, - showOptions: false, - scrollDirection: Axis.vertical, - shrinkWrap: true, - physics: ClampingScrollPhysics(), - ), - ), - ), - ], - ); - } -} diff --git a/lib/src/widgets/login_form_widget.dart b/lib/src/widgets/login_form_widget.dart deleted file mode 100644 index 5f476d0..0000000 --- a/lib/src/widgets/login_form_widget.dart +++ /dev/null @@ -1,306 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; - -class LoginFormWidget extends StatefulWidget { - const LoginFormWidget({ - Key key, - }) : super(key: key); - - @override - State createState() => _LoginFormWidgetState(); -} - -class _LoginFormWidgetState extends State { - static const _TAG = '_LoginFormWidgetState'; - - _LoginFormWidgetState(); - - final FocusNode myFocusNodeEmailLogin = FocusNode(); - final FocusNode myFocusNodePasswordLogin = FocusNode(); - - TextEditingController loginEmailController = new TextEditingController(); - TextEditingController loginPasswordController = new TextEditingController(); - - bool _obscureTextLogin = true; - - String errorText; - - @override - Widget build(BuildContext context) { - logger.info('$_TAG:build'); - - AccountBloc _accountBloc = BlocProvider.of(context); - - return Container( - padding: EdgeInsets.only(top: 23.0), - child: Column( - children: [ - Stack( - alignment: Alignment.topCenter, - overflow: Overflow.visible, - children: [ - Card( - elevation: 2.0, - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), - ), - child: Container( - width: 300.0, - height: 190.0, - child: Column( - children: [ - Padding( - padding: EdgeInsets.only( - top: 20.0, bottom: 20.0, left: 25.0, right: 25.0), - child: TextField( - focusNode: myFocusNodeEmailLogin, - controller: loginEmailController, - keyboardType: TextInputType.emailAddress, - style: TextStyle(fontSize: 16.0, color: Colors.black), - decoration: InputDecoration( - border: InputBorder.none, - icon: Icon( - MdiIcons.email, - color: Colors.black, - size: 22.0, - ), - hintText: CVLocalizations.of(context).email, - hintStyle: TextStyle(fontSize: 17.0), - ), - ), - ), - Container( - width: 250.0, - height: 1.0, - color: Colors.grey[400], - ), - Padding( - padding: EdgeInsets.only( - top: 20.0, bottom: 20.0, left: 25.0, right: 25.0), - child: TextField( - focusNode: myFocusNodePasswordLogin, - controller: loginPasswordController, - obscureText: _obscureTextLogin, - style: TextStyle(fontSize: 16.0, color: Colors.black), - decoration: InputDecoration( - border: InputBorder.none, - icon: Icon( - MdiIcons.lock, - size: 22.0, - color: Colors.black, - ), - hintText: CVLocalizations.of(context).password, - hintStyle: TextStyle(fontSize: 17.0), - suffixIcon: GestureDetector( - onTap: _toggleLogin, - child: Icon( - MdiIcons.eye, - size: 15.0, - color: Colors.black, - ), - ), - ), - ), - ), - ], - ), - ), - ), - Container( - margin: EdgeInsets.only(top: 170.0), - decoration: new BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - boxShadow: [ - BoxShadow( - color: AppColors.loginGradientStart, - offset: Offset(1.0, 6.0), - blurRadius: 20.0, - ), - BoxShadow( - color: AppColors.loginGradientEnd, - offset: Offset(1.0, 6.0), - blurRadius: 20.0, - ), - ], - gradient: new LinearGradient( - colors: [ - AppColors.loginGradientEnd, - AppColors.loginGradientStart - ], - begin: const FractionalOffset(0.2, 0.2), - end: const FractionalOffset(1.0, 1.0), - stops: [0.0, 1.0], - tileMode: TileMode.clamp), - ), - child: MaterialButton( - highlightColor: Colors.transparent, - splashColor: AppColors.loginGradientEnd, - //shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 42.0), - child: Text( - CVLocalizations.of(context).authSignInCTA, - style: TextStyle( - color: Colors.white, - fontSize: 25.0, - ), - ), - ), - onPressed: () => _accountBloc.login( - loginEmailController.text, loginPasswordController.text), - ), - ), - ], - ), - Padding( - padding: EdgeInsets.only(top: 10.0), - child: FlatButton( - onPressed: () {}, - child: Text( - 'Forgot Password?', - style: TextStyle( - decoration: TextDecoration.underline, - color: Colors.white, - fontSize: 16.0, - ), - )), - ), - Padding( - padding: EdgeInsets.only(top: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - decoration: BoxDecoration( - gradient: new LinearGradient( - colors: [ - Colors.white10, - Colors.white, - ], - begin: const FractionalOffset(0.0, 0.0), - end: const FractionalOffset(1.0, 1.0), - stops: [0.0, 1.0], - tileMode: TileMode.clamp), - ), - width: 100.0, - height: 1.0, - ), - Padding( - padding: EdgeInsets.only(left: 15.0, right: 15.0), - child: Text( - 'Or', - style: TextStyle( - color: Colors.white, - fontSize: 16.0, - ), - ), - ), - Container( - decoration: BoxDecoration( - gradient: new LinearGradient( - colors: [ - Colors.white, - Colors.white10, - ], - begin: const FractionalOffset(0.0, 0.0), - end: const FractionalOffset(1.0, 1.0), - stops: [0.0, 1.0], - tileMode: TileMode.clamp), - ), - width: 100.0, - height: 1.0, - ), - ], - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.only(top: 10.0, right: 40.0), - child: GestureDetector( - onTap: () => print('Facebook button pressed'), - child: Container( - padding: const EdgeInsets.all(15.0), - decoration: new BoxDecoration( - shape: BoxShape.circle, - color: Colors.white, - ), - child: new Icon( - MdiIcons.facebook, - color: Color(0xFF0084ff), - ), - ), - ), - ), - Padding( - padding: EdgeInsets.only(top: 10.0), - child: GestureDetector( - onTap: () => print('Google button pressed'), - child: Container( - padding: const EdgeInsets.all(15.0), - decoration: new BoxDecoration( - shape: BoxShape.circle, - color: Colors.white, - ), - child: new Icon( - MdiIcons.google, - color: Color(0xFF0084ff), - ), - ), - ), - ), - ], - ), - ], - ), - ); - } - - @override - void dispose() { - myFocusNodeEmailLogin.dispose(); - myFocusNodePasswordLogin.dispose(); - super.dispose(); - } - - void _toggleLogin() { - setState(() { - _obscureTextLogin = !_obscureTextLogin; - }); - } -} - -class _LoginFromMessage extends StatelessWidget { - @override - Widget build(BuildContext context) { - AccountBloc _accountBloc = BlocProvider.of(context); - return StreamBuilder( - stream: _accountBloc.accountDetailsStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return ErrorCard(message: translateError(context, snapshot.error)); - } else if (snapshot.hasData) { - return Card( - child: Container( - padding: EdgeInsets.all(10.0), - child: Text('Hello ${snapshot.data.username}'), - ), - ); - } - return Container(); - }, - ); - } -} diff --git a/lib/src/widgets/menu_button_widget.dart b/lib/src/widgets/menu_button_widget.dart deleted file mode 100644 index 632fce8..0000000 --- a/lib/src/widgets/menu_button_widget.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/widgets/initial_circle_avatar_widget.dart'; - -class MenuButton extends StatelessWidget { - const MenuButton({ - Key key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - AccountBloc _accountBloc = BlocProvider.of(context); - - return StreamBuilder( - stream: _accountBloc.isAuthenticatedStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - if (snapshot.data == true) return _MenuButtonConnected(); - if (snapshot.data == false) return _MenuButtonNotConnected(); - } - return Container(); - }, - ); - } -} - -class _MenuButtonNotConnected extends StatelessWidget { - @override - Widget build(BuildContext context) { - return IconButton( - onPressed: () => openMenuBottomSheet(context), - icon: Icon(Icons.menu), - ); - } -} - -class _MenuButtonConnected extends StatelessWidget { - @override - Widget build(BuildContext context) { - AccountBloc _accountBloc = BlocProvider.of(context); - - return StreamBuilder( - stream: _accountBloc.accountDetailsStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - UserModel userModel = snapshot.data; - return Padding( - padding: EdgeInsets.only(top: 3.0, bottom: 3.0), - child: IconButton( - onPressed: () => openMenuBottomSheet(context), - icon: InitialCircleAvatar( - text: userModel.username, - backgroundImage: NetworkImage(userModel.picture)), - )); - } else if (snapshot.hasError) { - return Container(child: Text('Error ${snapshot.error}')); - } - return Container(); - }, - ); - } -} diff --git a/lib/src/widgets/part_widget.dart b/lib/src/widgets/part_widget.dart deleted file mode 100644 index 3b49fb7..0000000 --- a/lib/src/widgets/part_widget.dart +++ /dev/null @@ -1,159 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/commons/api_values.dart'; -import 'package:social_cv_client_flutter/src/commons/dimensions.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/group_list_widget.dart'; -import 'package:social_cv_client_flutter/src/widgets/loading_widget.dart'; - -class PartWidget extends StatelessWidget { - PartWidget({ - Key key, - this.fromPartModel, - this.fromId, - }) : assert(fromPartModel != null || fromId != null), - super(key: key); - - final PartModel fromPartModel; - final String fromId; - - @override - Widget build(BuildContext context) { - if (fromPartModel != null) { - return _PartWidgetFromModel(partModel: fromPartModel); - } else if (fromId != null) { - PartBloc _partBloc = BlocProvider.of(context); - _partBloc.fetchPart(fromId); - - return StreamBuilder( - stream: _partBloc.partStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return ErrorContent( - message: translateError(context, snapshot.error), - ); - } else if (snapshot.hasData) { - return _PartWidgetFromModel(partModel: snapshot.data); - } - return LoadingShadowContent( - numberOfTitleLines: 1, - numberOfContentLines: 2, - ); - }, - ); - } else { - return ErrorContent( - message: CVLocalizations.of(context).notYetImplemented); - } - } -} - -class _PartWidgetFromModel extends StatelessWidget { - _PartWidgetFromModel({ - @required this.partModel, - }) : assert(partModel != null); - - final PartModel partModel; - - @override - Widget build(BuildContext context) { - if (partModel.type == kCVPartTypeListHorizontal) { - return _PartWidgetFromModelHorizontal( - partModel: partModel, - ); - } else if (partModel.type == kCVPartTypeListVertical) { - return _PartWidgetFromModelVertical( - partModel: partModel, - ); - } else { - return ErrorContent(message: CVLocalizations.of(context).notSupported); - } - } -} - -class _PartWidgetFromModelHorizontal extends StatelessWidget { - _PartWidgetFromModelHorizontal({ - @required this.partModel, - }) : assert(partModel != null); - - final PartModel partModel; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - partModel.name.toUpperCase(), - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold), - ), - FlatButton( - child: Text(CVLocalizations.of(context).partWidgetDetails), - onPressed: () => navigateToPart(context, partModel.id), - ), - ], - ), - Container( - height: AppDimensions.kCVHorizontalGroupListHeight, - child: BlocProvider( - bloc: GroupListBloc(), - child: GroupListWidget( - fromPartModel: partModel, - scrollDirection: Axis.horizontal, - shrinkWrap: true, - ), - ), - ), - ], - ); - } -} - -class _PartWidgetFromModelVertical extends StatelessWidget { - _PartWidgetFromModelVertical({ - @required this.partModel, - }) : assert(partModel != null); - - final PartModel partModel; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - partModel.name.toUpperCase(), - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold), - ), - FlatButton( - child: Text(CVLocalizations.of(context).partWidgetDetails), - onPressed: () => navigateToPart(context, partModel.id), - ), - ], - ), - BlocProvider( - bloc: GroupListBloc(), - child: GroupListWidget( - fromPartModel: partModel, - scrollDirection: Axis.vertical, - shrinkWrap: true, - physics: ClampingScrollPhysics(), - ), - ), - ], - ); - } -} diff --git a/lib/src/widgets/profile_widget.dart b/lib/src/widgets/profile_widget.dart deleted file mode 100644 index 17138bf..0000000 --- a/lib/src/widgets/profile_widget.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/widgets/initial_circle_avatar_widget.dart'; - -class ProfileWidget extends StatelessWidget { - const ProfileWidget({ - Key key, - this.profileModel, - }) : assert(profileModel != null), - super(key: key); - - final ProfileModel profileModel; - - @override - Widget build(BuildContext context) { - return ListTile( - leading: InitialCircleAvatar( - backgroundImage: NetworkImage(profileModel.picture ?? ''), - ), - title: Text(profileModel.title ?? ''), - subtitle: Text(profileModel.subtitle ?? ''), - onTap: () => navigateToProfile(context, profileModel.id), - trailing: Icon(MdiIcons.accountDetails), - ); - } -} diff --git a/lib/src/widgets/register_form_widget.dart b/lib/src/widgets/register_form_widget.dart deleted file mode 100644 index 1d50f19..0000000 --- a/lib/src/widgets/register_form_widget.dart +++ /dev/null @@ -1,235 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; - -class RegisterFormWidget extends StatefulWidget { - @override - State createState() => _RegisterFormWidgetState(); -} - -class _RegisterFormWidgetState extends State { - final FocusNode myFocusNodePassword = FocusNode(); - final FocusNode myFocusNodeEmail = FocusNode(); - final FocusNode myFocusNodeName = FocusNode(); - - bool _obscureTextSignup = true; - bool _obscureTextSignupConfirm = true; - - TextEditingController signupEmailController = new TextEditingController(); - TextEditingController signupNameController = new TextEditingController(); - TextEditingController signupPasswordController = new TextEditingController(); - TextEditingController signupConfirmPasswordController = - new TextEditingController(); - - @override - Widget build(BuildContext context) { - AccountBloc _accountBloc = BlocProvider.of(context); - - return Container( - padding: EdgeInsets.only(top: 23.0), - child: Column( - children: [ - Stack( - alignment: Alignment.topCenter, - overflow: Overflow.visible, - children: [ - Card( - elevation: 2.0, - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), - ), - child: Container( - width: 300.0, - height: 360.0, - child: Column( - children: [ - Padding( - padding: EdgeInsets.only( - top: 20.0, bottom: 20.0, left: 25.0, right: 25.0), - child: TextField( - focusNode: myFocusNodeName, - controller: signupNameController, - keyboardType: TextInputType.text, - textCapitalization: TextCapitalization.words, - style: TextStyle( - fontSize: 16.0, - color: Colors.black, - ), - decoration: InputDecoration( - border: InputBorder.none, - icon: Icon( - MdiIcons.account, - color: Colors.black, - ), - hintText: 'Name', - hintStyle: TextStyle(fontSize: 16.0), - ), - ), - ), - Container( - width: 250.0, - height: 1.0, - color: Colors.grey[400], - ), - Padding( - padding: EdgeInsets.only( - top: 20.0, bottom: 20.0, left: 25.0, right: 25.0), - child: TextField( - focusNode: myFocusNodeEmail, - controller: signupEmailController, - keyboardType: TextInputType.emailAddress, - style: TextStyle(fontSize: 16.0, color: Colors.black), - decoration: InputDecoration( - border: InputBorder.none, - icon: Icon( - MdiIcons.email, - color: Colors.black, - ), - hintText: CVLocalizations.of(context).email, - hintStyle: TextStyle(fontSize: 16.0), - ), - ), - ), - Container( - width: 250.0, - height: 1.0, - color: Colors.grey[400], - ), - Padding( - padding: EdgeInsets.only( - top: 20.0, bottom: 20.0, left: 25.0, right: 25.0), - child: TextField( - focusNode: myFocusNodePassword, - controller: signupPasswordController, - obscureText: _obscureTextSignup, - style: TextStyle(fontSize: 16.0, color: Colors.black), - decoration: InputDecoration( - border: InputBorder.none, - icon: Icon( - MdiIcons.lock, - color: Colors.black, - ), - hintText: CVLocalizations.of(context).password, - hintStyle: TextStyle(fontSize: 16.0), - suffixIcon: GestureDetector( - onTap: _toggleSignup, - child: Icon( - MdiIcons.eye, - size: 15.0, - color: Colors.black, - ), - ), - ), - ), - ), - Container( - width: 250.0, - height: 1.0, - color: Colors.grey[400], - ), - Padding( - padding: EdgeInsets.only( - top: 20.0, bottom: 20.0, left: 25.0, right: 25.0), - child: TextField( - controller: signupConfirmPasswordController, - obscureText: _obscureTextSignupConfirm, - style: TextStyle(fontSize: 16.0, color: Colors.black), - decoration: InputDecoration( - border: InputBorder.none, - icon: Icon( - MdiIcons.lock, - color: Colors.black, - ), - hintText: - CVLocalizations.of(context).passwordRepeat, - hintStyle: TextStyle(fontSize: 16.0), - suffixIcon: GestureDetector( - onTap: _toggleSignupConfirm, - child: Icon( - MdiIcons.eye, - size: 15.0, - color: Colors.black, - ), - ), - ), - ), - ), - ], - ), - ), - ), - Container( - margin: EdgeInsets.only(top: 340.0), - decoration: new BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - boxShadow: [ - BoxShadow( - color: AppColors.loginGradientStart, - offset: Offset(1.0, 6.0), - blurRadius: 20.0, - ), - BoxShadow( - color: AppColors.loginGradientEnd, - offset: Offset(1.0, 6.0), - blurRadius: 20.0, - ), - ], - gradient: new LinearGradient( - colors: [ - AppColors.loginGradientEnd, - AppColors.loginGradientStart - ], - begin: const FractionalOffset(0.2, 0.2), - end: const FractionalOffset(1.0, 1.0), - stops: [0.0, 1.0], - tileMode: TileMode.clamp), - ), - child: MaterialButton( - highlightColor: Colors.transparent, - splashColor: AppColors.loginGradientEnd, - //shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 42.0), - child: Text( - CVLocalizations.of(context).authSignUpCTA, - style: TextStyle( - color: Colors.white, - fontSize: 25.0, - ), - ), - ), - onPressed: () {}), - ), - ], - ), - ], - ), - ); - } - - @override - void dispose() { - myFocusNodeName.dispose(); - myFocusNodeEmail.dispose(); - myFocusNodePassword.dispose(); - super.dispose(); - } - - void _toggleSignup() { - setState(() { - _obscureTextSignup = !_obscureTextSignup; - }); - } - - void _toggleSignupConfirm() { - setState(() { - _obscureTextSignupConfirm = !_obscureTextSignupConfirm; - }); - } -} diff --git a/lib/src/widgets/theme_switch_tile_widget.dart b/lib/src/widgets/theme_switch_tile_widget.dart deleted file mode 100644 index b551e70..0000000 --- a/lib/src/widgets/theme_switch_tile_widget.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart'; -import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart'; - -class ThemeSwitchTile extends StatelessWidget { - const ThemeSwitchTile({ - Key key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - ApplicationBloc _appBloc = BlocProvider.of(context); - return StreamBuilder( - stream: _appBloc.themeStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - return SwitchListTile( - secondary: Icon( - snapshot.data == ThemeType.DARK - ? MdiIcons.weatherSunny - : MdiIcons.whiteBalanceSunny, - ), - title: Text(CVLocalizations.of(context).settingsThemeCTA), - value: snapshot.data == ThemeType.DARK ? true : false, - onChanged: (bool enable) { - if (enable) - _appBloc.setTheme(ThemeType.DARK); - else - _appBloc.setTheme(ThemeType.LIGHT); - }); - }, - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index 1f425ec..3371f72 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,6 +22,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + bloc: + dependency: transitive + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0" boolean_selector: dependency: transitive description: @@ -63,14 +70,14 @@ packages: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "1.3.3" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "3.0.2" + version: "3.0.3" built_collection: dependency: transitive description: @@ -119,7 +126,7 @@ packages: name: cookie_jar url: "https://pub.dartlang.org" source: hosted - version: "0.0.8" + version: "1.0.0" crypto: dependency: transitive description: @@ -135,12 +142,19 @@ packages: source: hosted version: "1.2.4" dio: - dependency: "direct main" + dependency: transitive description: name: dio url: "https://pub.dartlang.org" source: hosted - version: "1.0.17" + version: "2.1.3" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.10" fixnum: dependency: transitive description: @@ -160,6 +174,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: "0.11.1" flutter_localizations: dependency: "direct main" description: flutter @@ -192,7 +213,7 @@ packages: source: hosted version: "0.2.0" http: - dependency: "direct main" + dependency: transitive description: name: http url: "https://pub.dartlang.org" @@ -358,7 +379,7 @@ packages: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.20.0" + version: "0.21.0" shared_preferences: dependency: "direct main" description: @@ -379,7 +400,7 @@ packages: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.2+5" + version: "0.2.3" sky_engine: dependency: transitive description: flutter @@ -388,12 +409,10 @@ packages: social_cv_client_dart_common: dependency: "direct main" description: - path: "." - ref: HEAD - resolved-ref: "794bb245f287348c34e417f720d0b54f2aabe268" - url: "https://github.com/axellebot/Social-CV-Client-Dart-common" - source: git - version: "1.1.0" + path: "..\\Social-CV-Client-Dart-common" + relative: true + source: path + version: "1.0.0" source_span: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f8e6bf2..46a85ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,8 +8,10 @@ dependencies: # Common Social CV Logic social_cv_client_dart_common: - git: https://github.com/axellebot/Social-CV-Client-Dart-common - version: ^1.0.0 + path: ../Social-CV-Client-Dart-common +# git: +# url: https://github.com/axellebot/Social-CV-Client-Dart-common +# ref: develop flutter: sdk: flutter @@ -18,21 +20,24 @@ dependencies: logging: ^0.11.3+2 + # Routes fluro: ^1.3.7 - async: ^2.0.8 - rxdart: ^0.20.0 + # Bloc Pattern + flutter_bloc: ^0.11.1 - # HTTP Client - http: ^0.12.0 - dio: ^1.0.13 + # Rx + async: ^2.0.8 + rxdart: ^0.21.0 + # Storage shared_preferences: ^0.4.3 # Translations intl: ^0.15.7 intl_translation: ^0.17.0 + # UI Widgets transparent_image: ^0.1.0 # Icons @@ -61,7 +66,7 @@ flutter: - assets/images/default-avatar.png - assets/images/default-banner.jpg - - secrets.json # secret data + - config.json # secret data fonts: diff --git a/secrets.json.dist b/secrets.json.dist deleted file mode 100644 index e417eb8..0000000 --- a/secrets.json.dist +++ /dev/null @@ -1,4 +0,0 @@ -{ - "clientId":"", - "clientSecret":"" -} \ No newline at end of file From 9e0cf483912b7130823bb8e2dfa577880aa6c435 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sat, 18 May 2019 11:38:46 +0200 Subject: [PATCH 02/17] [FEAT] Added element list widget - Edited element widget --- .../configuration/configuration_bloc.dart | 2 +- lib/src/routes.dart | 1 + .../ui/pages/elements/group_profile_page.dart | 14 +- .../ui/pages/elements/part_profile_page.dart | 9 +- .../pages/elements/profile_profile_page.dart | 58 +--- lib/src/ui/widgets/account_tile_widget.dart | 2 +- .../ui/widgets/elements/element_widget.dart | 15 - .../elements/entry_list_profile_widget.dart | 179 +++++++++++ .../widgets/elements/entry_list_widget.dart | 256 +++------------- lib/src/ui/widgets/elements/entry_widget.dart | 22 +- .../elements/group_list_profile_widget.dart | 179 +++++++++++ .../widgets/elements/group_list_widget.dart | 267 +++------------- .../elements/group_profile_widget.dart | 22 +- lib/src/ui/widgets/elements/group_widget.dart | 22 +- .../elements/part_list_profile_widget.dart | 180 +++++++++++ .../ui/widgets/elements/part_list_widget.dart | 251 ++------------- .../widgets/elements/part_profile_widget.dart | 34 +-- lib/src/ui/widgets/elements/part_widget.dart | 22 +- .../elements/profile_list_tile_widget.dart | 181 +++++++++++ .../widgets/elements/profile_list_widget.dart | 288 +++--------------- .../widgets/elements/profile_tile_widget.dart | 55 +++- .../ui/widgets/elements/profile_widget.dart | 21 +- lib/src/ui/widgets/error_widget.dart | 28 +- lib/src/ui/widgets/loading_widget.dart | 12 +- lib/src/ui/widgets/menu_button_widget.dart | 4 +- pubspec.lock | 46 ++- pubspec.yaml | 2 +- 27 files changed, 1055 insertions(+), 1117 deletions(-) create mode 100644 lib/src/ui/widgets/elements/entry_list_profile_widget.dart create mode 100644 lib/src/ui/widgets/elements/group_list_profile_widget.dart create mode 100644 lib/src/ui/widgets/elements/part_list_profile_widget.dart create mode 100644 lib/src/ui/widgets/elements/profile_list_tile_widget.dart diff --git a/lib/src/domain/blocs/configuration/configuration_bloc.dart b/lib/src/domain/blocs/configuration/configuration_bloc.dart index 3b43659..b5d54ee 100644 --- a/lib/src/domain/blocs/configuration/configuration_bloc.dart +++ b/lib/src/domain/blocs/configuration/configuration_bloc.dart @@ -62,7 +62,7 @@ class ConfigurationBloc extends Bloc { configRepository: _configRepository, ); } catch (error) { - print(error.toString()); + print(error.runtimeType); } } } diff --git a/lib/src/routes.dart b/lib/src/routes.dart index 1bb5c2d..9e0eae2 100644 --- a/lib/src/routes.dart +++ b/lib/src/routes.dart @@ -6,6 +6,7 @@ import 'package:social_cv_client_flutter/src/ui/pages/auth_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/elements/entry_profile_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/elements/group_profile_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/elements/part_profile_page.dart'; +import 'package:social_cv_client_flutter/src/ui/pages/elements/profile_profile_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/search_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/settings_page.dart'; diff --git a/lib/src/ui/pages/elements/group_profile_page.dart b/lib/src/ui/pages/elements/group_profile_page.dart index 1a7fae5..e28a08d 100644 --- a/lib/src/ui/pages/elements/group_profile_page.dart +++ b/lib/src/ui/pages/elements/group_profile_page.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; @@ -39,15 +39,13 @@ class _GroupPageState extends GroupWidgetState { ), ); } else if (state is GroupLoaded) { - GroupViewModel model = state.element; - + var model = state.element; return Scaffold( appBar: AppBar(title: Text(model.name)), - body: SingleChildScrollView( - child: EntryListWidget( - fromGroupViewModel: model, - showOptions: true, - ), + body: ListView.builder( + itemCount: model.entryIds.length, + itemBuilder: (BuildContext context, int index) => + EntryProfileWidget(entryId: model.entryIds[index]), ), ); } diff --git a/lib/src/ui/pages/elements/part_profile_page.dart b/lib/src/ui/pages/elements/part_profile_page.dart index 693ccb6..f071ba3 100644 --- a/lib/src/ui/pages/elements/part_profile_page.dart +++ b/lib/src/ui/pages/elements/part_profile_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_list_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; @@ -26,7 +25,6 @@ class _PartProfilePageState extends PartWidgetState { appBar: AppBar( title: LoadingShadowContent( numberOfTitleLines: 1, - numberOfContentLines: 0, ), ), body: SingleChildScrollView( @@ -43,11 +41,8 @@ class _PartProfilePageState extends PartWidgetState { return Scaffold( appBar: AppBar(title: Text(model.name)), - body: SingleChildScrollView( - child: GroupListWidget( - fromPartViewModel: model, - showOptions: true, - ), + body: ListView.builder( + itemBuilder: (BuildContext context, int index) {}, ), ); } diff --git a/lib/src/ui/pages/elements/profile_profile_page.dart b/lib/src/ui/pages/elements/profile_profile_page.dart index 92194e0..42aa357 100644 --- a/lib/src/ui/pages/elements/profile_profile_page.dart +++ b/lib/src/ui/pages/elements/profile_profile_page.dart @@ -2,9 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; @@ -44,51 +42,10 @@ class _ProfileProfilePageState extends ProfileWidgetState { if (state is ProfileLoaded) { slivers.add(_ProfilePageAppBar(profile: state.element)); ProfileViewModel profile = state.element; - if (profile.type == kCVProfileTypeMain) { - slivers.addAll([ - PartProfileWidget( - partId: profile.partIds.elementAt(0), - ) - ]); - } else if (profile.type == kCVProfileTypeHeaderMain) { - slivers.addAll([ - PartProfileWidget( - partId: profile.partIds.elementAt(0), - ), - PartProfileWidget( - partId: profile.partIds.elementAt(1), - ) - ]); - } else if (profile.type == kCVProfileTypeMainSide) { - slivers.addAll([ - PartProfileWidget( - partId: profile.partIds.elementAt(0), - ), - PartProfileWidget( - partId: profile.partIds.elementAt(1), - ) - ]); - } else if (profile.type == kCVProfileTypeHeaderMainSide) { - slivers.addAll([ - PartProfileWidget( - partId: profile.partIds.elementAt(0), - ), - PartProfileWidget( - partId: profile.partIds.elementAt(1), - ), - PartProfileWidget( - partId: profile.partIds.elementAt(2), - ) - ]); - } else { - slivers.add( - SliverToBoxAdapter( - child: ErrorCard( - message: CVLocalizations.of(context).notSupported, - ), - ), - ); - } + slivers.addAll(profile.partIds + .map((partId) => + SliverToBoxAdapter(child: PartProfileWidget(partId: partId))) + .toList()); } else if (state is ProfileFailure) { slivers.add( SliverToBoxAdapter( @@ -97,9 +54,10 @@ class _ProfileProfilePageState extends ProfileWidgetState { ); } return Scaffold( - body: CustomScrollView( - slivers: slivers, - )); + body: CustomScrollView( + slivers: slivers, + ), + ); }, ); } diff --git a/lib/src/ui/widgets/account_tile_widget.dart b/lib/src/ui/widgets/account_tile_widget.dart index ea19297..c6ac7c6 100644 --- a/lib/src/ui/widgets/account_tile_widget.dart +++ b/lib/src/ui/widgets/account_tile_widget.dart @@ -58,7 +58,7 @@ class _AccountTileConnectedState extends State<_AccountTileConnected> { return Container(); } if (state is AccountLoaded) { - var userModel = state.userModel; + var userModel = state.user; return ListTile( leading: InitialCircleAvatar( text: userModel.username, diff --git a/lib/src/ui/widgets/elements/element_widget.dart b/lib/src/ui/widgets/elements/element_widget.dart index 274a1e8..fe166d6 100644 --- a/lib/src/ui/widgets/elements/element_widget.dart +++ b/lib/src/ui/widgets/elements/element_widget.dart @@ -1,17 +1,2 @@ import 'package:flutter/widgets.dart'; import 'package:social_cv_client_dart_common/models.dart'; - -abstract class ElementWidget - extends StatefulWidget { - final String elementId; - final T element; - - ElementWidget({Key key, this.elementId, this.element}) : super(key: key); -} - -abstract class ElementWidgetState extends State { - @override - void initState() { - super.initState(); - } -} diff --git a/lib/src/ui/widgets/elements/entry_list_profile_widget.dart b/lib/src/ui/widgets/elements/entry_list_profile_widget.dart new file mode 100644 index 0000000..0f5ef26 --- /dev/null +++ b/lib/src/ui/widgets/elements/entry_list_profile_widget.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_profile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/utils.dart'; + +/// [SimpleEntryListProfile] is a dummy widget that use [entryIds] or [entries] or [entryBlocs] to create a list of [EntryProfileWidget] +class SimpleEntryListProfile extends StatelessWidget { + final List entryIds; + final List entries; + final List entryBlocs; + + /// List behaviors + final Axis scrollDirection; + final bool shrinkWrap; + final ScrollPhysics physics; + + const SimpleEntryListProfile({ + Key key, + this.entryIds, + this.entries, + this.entryBlocs, + + /// List behaviors + this.scrollDirection = Axis.vertical, + this.shrinkWrap = false, + this.physics, + }) : assert(entryIds != null && entries == null && entryBlocs == null), + assert(entryIds == null && entries != null && entryBlocs == null), + assert(entryIds == null && entries == null && entryBlocs != null), + super(key: key); + + @override + Widget build(BuildContext context) { + int itemCount; + IndexedWidgetBuilder itemBuilder; + + if (entryIds != null && entryIds.isNotEmpty) { + itemCount = entryIds.length; + itemBuilder = (BuildContext context, int index) => + EntryProfileWidget(entryId: entryIds[index]); + } else if (entries != null && entries.isNotEmpty) { + itemCount = entries.length; + itemBuilder = (BuildContext context, int index) => + EntryProfileWidget(entry: entries[index]); + } else if (entryBlocs != null && entryBlocs.isNotEmpty) { + itemCount = entryBlocs.length; + itemBuilder = (BuildContext context, int index) => + EntryProfileWidget(entryBloc: entryBlocs[index]); + } + + return ListView.builder( + scrollDirection: scrollDirection, + shrinkWrap: shrinkWrap, + physics: physics, + itemCount: itemCount, + itemBuilder: itemBuilder, + ); + } +} + +/// [ComplexEntryListProfile] is a clever widget that use [parentGroupId] or [ownerId] or [entryListBloc] to display a list of [EntryProfileWidget] +class ComplexEntryListProfile extends EntryListWidget { + /// Search, filter and sort options + final bool showOptions; + + /// List behaviors + final Axis scrollDirection; + final bool shrinkWrap; + final ScrollPhysics physics; + + ComplexEntryListProfile({ + Key key, + String parentGroupId, + String ownerId, + EntryListBloc entryListBloc, + + /// Options + this.showOptions = false, + + /// List behaviors + this.scrollDirection = Axis.vertical, + this.shrinkWrap = false, + this.physics, + }) : super( + key: key, + parentGroupId: parentGroupId, + ownerId: ownerId, + entryListBloc: entryListBloc, + ); + + @override + State createState() => _EntryListProfileState(); +} + +class _EntryListProfileState + extends ComplexEntryListState { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: widget.entryListBloc, + builder: (BuildContext context, EntryListState state) { + if (state is EntryListLoading) { + return LoadingList( + count: state.count, + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); + } else if (state is EntryListLoaded) { + final List sortItems = [ + SortListItem(field: 'name', title: 'Name', value: SortState.NoSort) + ]; + + final sortRow = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon(Icons.sort_by_alpha), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return SortDialog( + title: + Text(CVLocalizations.of(context).entryListSorting), + sortItems: sortItems, + ); + }, + ); + }, + ), + DropdownButton( + value: null, + hint: Text(CVLocalizations.of(context).partListItemPerPage), + items: getDropDownMenuElementPerPage(), + onChanged: (value) {}, + ) + ], + ); + + return ListView( + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + children: [ + if (widget.showOptions) sortRow, + for (var entry in state.elements) + EntryProfileWidget(entry: entry), + if (widget.showOptions) + Center( + child: FlatButton( + onPressed: null, + child: Text(CVLocalizations.of(context).entryListLoadMore), + ), + ), + ], + ); + } else if (state is EntryListFailure) { + return ErrorList( + error: CVLocalizations.of(context).notSupported, + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); + } + return Container(); + }, + ); + } +} diff --git a/lib/src/ui/widgets/elements/entry_list_widget.dart b/lib/src/ui/widgets/elements/entry_list_widget.dart index df52872..6127dca 100644 --- a/lib/src/ui/widgets/elements/entry_list_widget.dart +++ b/lib/src/ui/widgets/elements/entry_list_widget.dart @@ -1,230 +1,52 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/widgets.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; -class EntryListWidget extends StatelessWidget { - const EntryListWidget({ +/// [EntryListWidget] is a clever widget that use an [EntryListBloc] +/// based on [parentGroupId] or [ownerId] or [entryListBloc] +abstract class EntryListWidget extends StatefulWidget { + final String parentGroupId; + final String ownerId; + final EntryListBloc entryListBloc; + + EntryListWidget({ Key key, - this.fromGroupViewModel, - this.fromSearch, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(fromGroupViewModel != null || fromSearch != null), + this.parentGroupId, + this.ownerId, + this.entryListBloc, + }) : assert( + parentGroupId != null && ownerId == null && entryListBloc == null), + assert( + parentGroupId == null && ownerId != null && entryListBloc == null), + assert( + parentGroupId == null && ownerId == null && entryListBloc != null), super(key: key); - - final GroupViewModel fromGroupViewModel; - final Object fromSearch; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; - - @override - Widget build(BuildContext context) { - if (fromGroupViewModel != null) { - return _EntryListFromGroup( - groupViewModel: fromGroupViewModel, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else if (fromSearch != null) { - return _EntryListFromSearch( - search: fromSearch, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } - return ErrorList( - error: CVLocalizations.of(context).notSupported, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } -} - -class _EntryListFromGroup extends StatelessWidget { - _EntryListFromGroup({ - @required this.groupViewModel, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(groupViewModel != null); - - final GroupViewModel groupViewModel; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; - - @override - Widget build(BuildContext context) { - EntryListBloc entryListBloc = BlocProvider.of(context); - entryListBloc.fetchGroupEntries(groupViewModel.id); - - return StreamBuilder>( - stream: entryListBloc.entriesStream, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return ErrorList( - error: snapshot.error, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else if (snapshot.hasData) { - return _EntryList( - EntryViewModels: snapshot.data, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else { - return LoadingList( - count: groupViewModel.entryIds.length, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } - }, - ); - } } -class _EntryListFromSearch extends StatelessWidget { - _EntryListFromSearch({ - @required this.search, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(search != null); - - final Object search; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; +/// If [widget.entryListBloc] exists the lifecycle of it will be managed by its creator +abstract class ComplexEntryListState + extends State { + EntryListBloc entryListBloc; @override - Widget build(BuildContext context) { - return ErrorList( - error: CVLocalizations.of(context).notYetImplemented, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); + void initState() { + super.initState(); + + entryListBloc = widget.entryListBloc; + + if (entryListBloc == null) { + var provider = RepositoriesProvider.of(context); + entryListBloc = EntryListBloc(cvRepository: provider.cvRepository); + entryListBloc.dispatch(EntryListInitialized( + parentGroupId: widget.parentGroupId, + ownerId: widget.ownerId, + )); + } } -} - -class _EntryList extends StatelessWidget { - _EntryList({ - @required this.EntryViewModels, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(EntryViewModels != null); - - final List EntryViewModels; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; @override - Widget build(BuildContext context) { - ElementListBloc _entryListBloc = - BlocProvider.of>(context); - - final List sortItems = [ - SortListItem(field: 'name', title: 'Name', value: SortState.NoSort) - ]; - - return ListView.builder( - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - itemCount: - showOptions ? EntryViewModels.length + 2 : EntryViewModels.length, - itemBuilder: (BuildContext context, int i) { - if (showOptions) { - if (i == 0) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: Icon(Icons.sort_by_alpha), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return SortDialog( - title: Text( - CVLocalizations.of(context).entryListSorting), - sortItems: sortItems, - ); - }, - ); - }, - ), - StreamBuilder( - stream: _entryListBloc.entryPerPage, - builder: - (BuildContext context, AsyncSnapshot snapshot) { - return DropdownButton( - value: snapshot.data, - hint: - Text(CVLocalizations.of(context).partListItemPerPage), - items: getDropDownMenuElementPerPage(), - onChanged: (value) { - _entryListBloc.setItemsPerPage(value); - }, - ); - }, - ), - ], - ); - } - i--; - if (i == EntryViewModels.length) { - return Center( - child: FlatButton( - onPressed: null, - child: Text(CVLocalizations.of(context).entryListLoadMore), - ), - ); - } - } - return EntryProfileWidget(entry: EntryViewModels[i]); - }, - ); + void dispose() { + if (widget.entryListBloc == null) entryListBloc.dispose(); + super.dispose(); } } diff --git a/lib/src/ui/widgets/elements/entry_widget.dart b/lib/src/ui/widgets/elements/entry_widget.dart index c6dca68..649fe12 100644 --- a/lib/src/ui/widgets/elements/entry_widget.dart +++ b/lib/src/ui/widgets/elements/entry_widget.dart @@ -1,22 +1,23 @@ import 'package:flutter/widgets.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/element_widget.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; /// If [entryBloc] given we assume that it have been already initialized -abstract class EntryWidget extends ElementWidget { +abstract class EntryWidget extends StatefulWidget { + final String entryId; + final EntryViewModel entry; final EntryBloc entryBloc; - EntryWidget({Key key, String entryId, EntryViewModel entry, this.entryBloc}) + EntryWidget({Key key, this.entryId, this.entry, this.entryBloc}) : assert(entryId != null && entry == null && entryBloc == null), assert(entryId == null && entry != null && entryBloc == null), assert(entryId == null && entry == null && entryBloc != null), - super(key: key, elementId: entryId, element: entry); + super(key: key); } -/// If [widget.profileBloc] exists the lifecycle of it will be managed by its creator -abstract class EntryWidgetState - extends ElementWidgetState { +/// If [widget.entryBloc] exists the lifecycle of it will be managed by its creator +abstract class EntryWidgetState extends State { EntryBloc entryBloc; @override @@ -26,10 +27,11 @@ abstract class EntryWidgetState entryBloc = widget.entryBloc; if (entryBloc == null) { - entryBloc = EntryBloc(); + var provider = RepositoriesProvider.of(context); + entryBloc = EntryBloc(cvRepository: provider.cvRepository); entryBloc.dispatch(EntryInitialized( - withId: widget.elementId, - withEntry: widget.element, + entryId: widget.entryId, + entry: widget.entry, )); } } diff --git a/lib/src/ui/widgets/elements/group_list_profile_widget.dart b/lib/src/ui/widgets/elements/group_list_profile_widget.dart new file mode 100644 index 0000000..133670b --- /dev/null +++ b/lib/src/ui/widgets/elements/group_list_profile_widget.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_profile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/utils.dart'; + +/// [SimpleGroupListProfile] is a dummy widget that use [groupIds] or [groups] or [groupBlocs] to create a list of [GroupProfileWidget] +class SimpleGroupListProfile extends StatelessWidget { + final List groupIds; + final List groups; + final List groupBlocs; + + /// List behaviors + final Axis scrollDirection; + final bool shrinkWrap; + final ScrollPhysics physics; + + const SimpleGroupListProfile({ + Key key, + this.groupIds, + this.groups, + this.groupBlocs, + + /// List behaviors + this.scrollDirection = Axis.vertical, + this.shrinkWrap = false, + this.physics, + }) : assert(groupIds != null && groups == null && groupBlocs == null), + assert(groupIds == null && groups != null && groupBlocs == null), + assert(groupIds == null && groups == null && groupBlocs != null), + super(key: key); + + @override + Widget build(BuildContext context) { + int itemCount; + IndexedWidgetBuilder itemBuilder; + + if (groupIds != null && groupIds.isNotEmpty) { + itemCount = groupIds.length; + itemBuilder = (BuildContext context, int index) => + GroupProfileWidget(groupId: groupIds[index]); + } else if (groups != null && groups.isNotEmpty) { + itemCount = groups.length; + itemBuilder = (BuildContext context, int index) => + GroupProfileWidget(group: groups[index]); + } else if (groupBlocs != null && groupBlocs.isNotEmpty) { + itemCount = groupBlocs.length; + itemBuilder = (BuildContext context, int index) => + GroupProfileWidget(groupBloc: groupBlocs[index]); + } + + return ListView.builder( + scrollDirection: scrollDirection, + shrinkWrap: shrinkWrap, + physics: physics, + itemCount: itemCount, + itemBuilder: itemBuilder, + ); + } +} + +/// [ComplexGroupListProfile] is a clever widget that use [parentPartId] or [ownerId] or [groupListBloc] to display a list of [GroupProfileWidget] +class ComplexGroupListProfile extends GroupListWidget { + /// Search, filter and sort options + final bool showOptions; + + /// List behaviors + final Axis scrollDirection; + final bool shrinkWrap; + final ScrollPhysics physics; + + ComplexGroupListProfile({ + Key key, + String parentPartId, + String ownerId, + GroupListBloc groupListBloc, + + /// Options + this.showOptions = false, + + /// List behaviors + this.scrollDirection = Axis.vertical, + this.shrinkWrap = false, + this.physics, + }) : super( + key: key, + parentPartId: parentPartId, + ownerId: ownerId, + groupListBloc: groupListBloc, + ); + + @override + State createState() => _GroupListProfileState(); +} + +class _GroupListProfileState + extends GroupListWidgetState { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: widget.groupListBloc, + builder: (BuildContext context, GroupListState state) { + if (state is GroupListLoading) { + return LoadingList( + count: state.count, + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); + } else if (state is GroupListLoaded) { + final List sortItems = [ + SortListItem(field: 'name', title: 'Name', value: SortState.NoSort) + ]; + + final sortRow = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon(Icons.sort_by_alpha), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return SortDialog( + title: + Text(CVLocalizations.of(context).groupListSorting), + sortItems: sortItems, + ); + }, + ); + }, + ), + DropdownButton( + value: null, + hint: Text(CVLocalizations.of(context).partListItemPerPage), + items: getDropDownMenuElementPerPage(), + onChanged: (value) {}, + ) + ], + ); + + return ListView( + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + children: [ + if (widget.showOptions) sortRow, + for (var group in state.elements) + GroupProfileWidget(group: group), + if (widget.showOptions) + Center( + child: FlatButton( + onPressed: null, + child: Text(CVLocalizations.of(context).groupListLoadMore), + ), + ), + ], + ); + } else if (state is GroupListFailure) { + return ErrorList( + error: CVLocalizations.of(context).notSupported, + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); + } + return Container(); + }, + ); + } +} diff --git a/lib/src/ui/widgets/elements/group_list_widget.dart b/lib/src/ui/widgets/elements/group_list_widget.dart index 5d184b2..172a29a 100644 --- a/lib/src/ui/widgets/elements/group_list_widget.dart +++ b/lib/src/ui/widgets/elements/group_list_widget.dart @@ -1,247 +1,48 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/widgets.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; + +abstract class GroupListWidget extends StatefulWidget { + final String parentPartId; + final String ownerId; + final GroupListBloc groupListBloc; -/// A widget to list all [GroupViewModel] from [PartViewModel] or from a search -class GroupListWidget extends StatelessWidget { GroupListWidget({ Key key, - this.fromPartViewModel, - this.fromSearch, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) - : assert(fromPartViewModel != null || fromSearch != null), + this.parentPartId, + this.ownerId, + this.groupListBloc, + }) : assert( + parentPartId != null && ownerId == null && groupListBloc == null), + assert( + parentPartId == null && ownerId != null && groupListBloc == null), + assert( + parentPartId == null && ownerId == null && groupListBloc != null), super(key: key); - - final PartViewModel fromPartViewModel; - final Object fromSearch; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; - - @override - Widget build(BuildContext context) { - if (fromPartViewModel != null) { - return _GroupListFromPartViewModel( - partViewModel: fromPartViewModel, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else if (fromSearch != null) { - return _GroupListFromSearch( - search: fromSearch, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else { - return ErrorList( - error: CVLocalizations - .of(context) - .notYetImplemented, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } - } -} - -/// A widget to list all [GroupViewModel] from [PartViewModel] -class _GroupListFromPartViewModel extends StatelessWidget { - _GroupListFromPartViewModel({ - @required this.partViewModel, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(partViewModel != null); - - final PartViewModel partViewModel; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; - - @override - Widget build(BuildContext context) { - ElementListBloc _groupListBloc = - BlocProvider.of>(context); - _groupListBloc.dispatch( - ElementListFetchFromParent(parentId: partViewModel.id); - - return StreamBuilder>( - stream: _groupListBloc.groupsStream, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return ErrorList( - error: snapshot.error, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else if (snapshot.hasData) { - return _GroupList( - groupViewModels: snapshot.data, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } - return LoadingList( - count: partViewModel.groupIds.length, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - }, - ); - } } -/// A widget to list all groups from search -class _GroupListFromSearch extends StatelessWidget { - _GroupListFromSearch({ - @required this.search, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(search != null); - - final Object search; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; +/// If [widget.groupListBloc] exists the lifecycle of it will be managed by its creator +abstract class GroupListWidgetState + extends State { + GroupListBloc groupListBloc; @override - Widget build(BuildContext context) { - return ErrorList( - error: CVLocalizations - .of(context) - .notYetImplemented, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); + void initState() { + super.initState(); + groupListBloc = widget.groupListBloc; + if (widget.groupListBloc == null) { + var provider = RepositoriesProvider.of(context); + groupListBloc = GroupListBloc(cvRepository: provider.cvRepository); + groupListBloc.dispatch(GroupListInitialized( + parentPartId: widget.parentPartId, + ownerId: widget.ownerId, + )); + } } -} - -/// A widget to list all [GroupViewModel] -class _GroupList extends StatelessWidget { - _GroupList({ - @required this.groupViewModels, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(groupViewModels != null); - - final List groupViewModels; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; @override - Widget build(BuildContext context) { - ElementListBloc _groupListBloc = BlocProvider.of< - ElementListBloc>(context); - - final List sortItems = [ - SortListItem(field: 'title', title: 'Title', value: SortState.NoSort) - ]; - - return ListView.builder( - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - itemCount: - showOptions ? groupViewModels.length + 2 : groupViewModels.length, - itemBuilder: (BuildContext context, int i) { - if (showOptions) { - if (i == 0) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: Icon(Icons.sort_by_alpha), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return SortDialog( - title: - Text(CVLocalizations - .of(context) - .partListSorting), - sortItems: sortItems, - ); - }, - ); - }, - ), - StreamBuilder( - stream: _groupListBloc.groupPerPage, - builder: - (BuildContext context, AsyncSnapshot snapshot) { - return DropdownButton( - value: snapshot.data, - hint: - Text(CVLocalizations - .of(context) - .partListItemPerPage), - items: getDropDownMenuElementPerPage(), - onChanged: (value) { - _groupListBloc.setItemsPerPage(value); - }, - ); - }, - ), - ], - ); - } - i--; - if (i == groupViewModels.length) { - return Center( - child: FlatButton( - onPressed: null, - child: Text(CVLocalizations - .of(context) - .groupListLoadMore), - ), - ); - } - } - return GroupProfileWidget(group: groupViewModels[i]); - }, - ); + void dispose() { + if (widget.groupListBloc == null) groupListBloc.dispose(); + super.dispose(); } } diff --git a/lib/src/ui/widgets/elements/group_profile_widget.dart b/lib/src/ui/widgets/elements/group_profile_widget.dart index cddf795..02eea64 100644 --- a/lib/src/ui/widgets/elements/group_profile_widget.dart +++ b/lib/src/ui/widgets/elements/group_profile_widget.dart @@ -5,7 +5,7 @@ import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_list_profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; @@ -26,11 +26,11 @@ class _GroupProfileWidgetState extends GroupWidgetState { bloc: groupBloc, builder: (BuildContext context, GroupState state) { if (state is GroupLoaded) { - GroupViewModel groupViewModel; - if (groupViewModel.type == kCVGroupTypeListHorizontal) { - return _GroupHorizontal(group: groupViewModel); - } else if (groupViewModel.type == kCVGroupTypeListVertical) { - return _GroupVertical(group: groupViewModel); + GroupViewModel group; + if (group.type == kCVGroupTypeListHorizontal) { + return _GroupHorizontal(group: group); + } else if (group.type == kCVGroupTypeListVertical) { + return _GroupVertical(group: group); } else { return ErrorContent( message: CVLocalizations.of(context).notSupported); @@ -75,9 +75,8 @@ class _GroupHorizontal extends StatelessWidget { ), Container( height: AppDimensions.horizontalEntryListHeight, - child: EntryListWidget( - fromGroupViewModel: group, - showOptions: false, + child: SimpleEntryListProfile( + entryIds: group.entryIds, scrollDirection: Axis.horizontal, shrinkWrap: true, ), @@ -120,9 +119,8 @@ class _GroupVertical extends StatelessWidget { ), Card( elevation: 2.0, - child: EntryListWidget( - fromGroupViewModel: group, - showOptions: false, + child: SimpleEntryListProfile( + entryIds: group.entryIds, scrollDirection: Axis.vertical, shrinkWrap: true, physics: ClampingScrollPhysics(), diff --git a/lib/src/ui/widgets/elements/group_widget.dart b/lib/src/ui/widgets/elements/group_widget.dart index 1036f32..03d6cc1 100644 --- a/lib/src/ui/widgets/elements/group_widget.dart +++ b/lib/src/ui/widgets/elements/group_widget.dart @@ -1,22 +1,23 @@ import 'package:flutter/widgets.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/element_widget.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; /// If [groupBloc] given we assume that it have been already initialized and -abstract class GroupWidget extends ElementWidget { +abstract class GroupWidget extends StatefulWidget { + final String groupId; + final GroupViewModel group; final GroupBloc groupBloc; - GroupWidget({Key key, String groupId, GroupViewModel group, this.groupBloc}) + GroupWidget({Key key, this.groupId, this.group, this.groupBloc}) : assert(groupId != null && group == null && groupBloc == null), assert(groupId == null && group != null && groupBloc == null), assert(groupId == null && group == null && groupBloc != null), - super(key: key, elementId: groupId, element: group); + super(key: key); } -/// If [widget.profileBloc] exists the lifecycle of it will be managed by its creator -abstract class GroupWidgetState - extends ElementWidgetState { +/// If [widget.groupBloc] exists the lifecycle of it will be managed by its creator +abstract class GroupWidgetState extends State { GroupBloc groupBloc; @override @@ -26,10 +27,11 @@ abstract class GroupWidgetState groupBloc = widget.groupBloc; if (groupBloc == null) { - groupBloc = GroupBloc(); + var provider = RepositoriesProvider.of(context); + groupBloc = GroupBloc(cvRepository: provider.cvRepository); groupBloc.dispatch(GroupInitialized( - withId: widget.elementId, - withGroup: widget.element, + groupId: widget.groupId, + group: widget.group, )); } } diff --git a/lib/src/ui/widgets/elements/part_list_profile_widget.dart b/lib/src/ui/widgets/elements/part_list_profile_widget.dart new file mode 100644 index 0000000..766beda --- /dev/null +++ b/lib/src/ui/widgets/elements/part_list_profile_widget.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_profile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/utils.dart'; + +/// [SimplePartListProfile] is a dummy widget that use [partIds] or [parts] or +/// [partBlocs] to create a list of [PartProfileWidget] +class SimplePartListProfile extends StatelessWidget { + final List partIds; + final List parts; + final List partBlocs; + + /// List behaviors + final Axis scrollDirection; + final bool shrinkWrap; + final ScrollPhysics physics; + + const SimplePartListProfile({ + Key key, + this.partIds, + this.parts, + this.partBlocs, + + /// List behaviors + this.scrollDirection = Axis.vertical, + this.shrinkWrap = false, + this.physics, + }) : assert(partIds != null && parts == null && partBlocs == null), + assert(partIds == null && parts != null && partBlocs == null), + assert(partIds == null && parts == null && partBlocs != null), + super(key: key); + + @override + Widget build(BuildContext context) { + int itemCount; + IndexedWidgetBuilder itemBuilder; + + if (partIds != null && partIds.isNotEmpty) { + itemCount = partIds.length; + itemBuilder = (BuildContext context, int index) => + PartProfileWidget(partId: partIds[index]); + } else if (parts != null && parts.isNotEmpty) { + itemCount = parts.length; + itemBuilder = (BuildContext context, int index) => + PartProfileWidget(part: parts[index]); + } else if (partBlocs != null && partBlocs.isNotEmpty) { + itemCount = partBlocs.length; + itemBuilder = (BuildContext context, int index) => + PartProfileWidget(partBloc: partBlocs[index]); + } + + return ListView.builder( + scrollDirection: scrollDirection, + shrinkWrap: shrinkWrap, + physics: physics, + itemCount: itemCount, + itemBuilder: itemBuilder, + ); + } +} + +/// [ComplexPartListProfile] is a clever widget that use [parentProfileId] or +/// [ownerId] or [partListBloc] to display a list of [PartProfileWidget] +class ComplexPartListProfile extends PartListWidget { + /// Search, filter and sort options + final bool showOptions; + + /// List behaviors + final Axis scrollDirection; + final bool shrinkWrap; + final ScrollPhysics physics; + + ComplexPartListProfile({ + Key key, + String parentProfileId, + String ownerId, + PartListBloc partListBloc, + + /// Options + this.showOptions = false, + + /// List behaviors + this.scrollDirection = Axis.vertical, + this.shrinkWrap = false, + this.physics, + }) : super( + key: key, + parentProfileId: parentProfileId, + ownerId: ownerId, + partListBloc: partListBloc, + ); + + @override + State createState() => _PartListProfileState(); +} + +class _PartListProfileState + extends PartListWidgetState { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: widget.partListBloc, + builder: (BuildContext context, PartListState state) { + if (state is PartListLoading) { + return LoadingList( + count: state.count, + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); + } else if (state is PartListLoaded) { + final List sortItems = [ + SortListItem(field: 'name', title: 'Name', value: SortState.NoSort) + ]; + + final sortRow = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon(Icons.sort_by_alpha), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return SortDialog( + title: + Text(CVLocalizations.of(context).partListSorting), + sortItems: sortItems, + ); + }, + ); + }, + ), + DropdownButton( + value: null, + hint: Text(CVLocalizations.of(context).profileListItemPerPage), + items: getDropDownMenuElementPerPage(), + onChanged: (value) {}, + ) + ], + ); + + return ListView( + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + children: [ + if (widget.showOptions) sortRow, + for (var part in state.elements) PartProfileWidget(part: part), + if (widget.showOptions) + Center( + child: FlatButton( + onPressed: null, + child: Text(CVLocalizations.of(context).partListLoadMore), + ), + ), + ], + ); + } else if (state is PartListFailure) { + return ErrorList( + error: CVLocalizations.of(context).notSupported, + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); + } + return Container(); + }, + ); + } +} diff --git a/lib/src/ui/widgets/elements/part_list_widget.dart b/lib/src/ui/widgets/elements/part_list_widget.dart index b11c710..d85a2c1 100644 --- a/lib/src/ui/widgets/elements/part_list_widget.dart +++ b/lib/src/ui/widgets/elements/part_list_widget.dart @@ -1,234 +1,47 @@ -import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; + +abstract class PartListWidget extends StatefulWidget { + final String parentProfileId; + final String ownerId; + final PartListBloc partListBloc; -/// A widget to list all parts from [PartViewModel] or from a search -class PartListWidget extends StatelessWidget { PartListWidget({ Key key, - this.fromProfileViewModel, - this.fromSearch, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(fromProfileViewModel != null || fromSearch != null), + this.parentProfileId, + this.ownerId, + this.partListBloc, + }) : assert( + parentProfileId != null && ownerId == null && partListBloc == null), + assert( + parentProfileId == null && ownerId != null && partListBloc == null), + assert( + parentProfileId == null && ownerId == null && partListBloc != null), super(key: key); - - final ProfileViewModel fromProfileViewModel; - final Object fromSearch; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; - - @override - Widget build(BuildContext context) { - if (fromProfileViewModel != null) { - return _PartListFromProfile( - profileViewModel: fromProfileViewModel, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else if (fromSearch != null) { - return _PartListFromSearch( - search: fromSearch, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } - return ErrorList( - error: CVLocalizations.of(context).notYetImplemented, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } -} - -class _PartListFromProfile extends StatelessWidget { - _PartListFromProfile({ - @required this.profileViewModel, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(profileViewModel != null); - - final ProfileViewModel profileViewModel; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; - - @override - Widget build(BuildContext context) { - ElementListBloc _partListBloc = - BlocProvider.of>(context); - _partListBloc.fetchProfileParts(profileViewModel.id); - - return StreamBuilder>( - stream: _partListBloc.partsStream, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return ErrorList( - error: snapshot.error, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else if (snapshot.hasData) { - return _PartList( - partViewModels: snapshot.data, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else { - return LoadingList( - count: profileViewModel.parts.length, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } - }, - ); - } } -class _PartListFromSearch extends StatelessWidget { - _PartListFromSearch({ - @required this.search, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(search != null); - - final Object search; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; +/// If [widget.partListBloc] exists the lifecycle of it will be managed by its creator +abstract class PartListWidgetState extends State { + PartListBloc partListBloc; @override - Widget build(BuildContext context) { - return ErrorList( - error: CVLocalizations.of(context).notYetImplemented, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); + void initState() { + super.initState(); + partListBloc = widget.partListBloc; + if (widget.partListBloc == null) { + var provider = RepositoriesProvider.of(context); + partListBloc = PartListBloc(cvRepository: provider.cvRepository); + partListBloc.dispatch(PartListInitialized( + parentProfileId: widget.parentProfileId, + ownerId: widget.ownerId, + )); + } } -} - -class _PartList extends StatelessWidget { - _PartList({ - @required this.partViewModels, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(partViewModels != null); - - final List partViewModels; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; @override - Widget build(BuildContext context) { - ElementListBloc _partListBloc = - BlocProvider.of>(context); - - final List sortItems = [ - SortListItem(field: 'order', title: 'Order', value: SortState.NoSort), - SortListItem(field: 'name', title: 'Name', value: SortState.NoSort) - ]; - - return ListView.builder( - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - itemCount: - showOptions ? partViewModels.length + 2 : partViewModels.length, - itemBuilder: (BuildContext context, int i) { - if (showOptions) { - if (i == 0) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: Icon(Icons.sort_by_alpha), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return SortDialog( - title: - Text(CVLocalizations.of(context).partListSorting), - sortItems: sortItems, - ); - }, - ); - }, - ), - StreamBuilder( - stream: _partListBloc.partPerPage, - builder: - (BuildContext context, AsyncSnapshot snapshot) { - return DropdownButton( - value: snapshot.data, - hint: - Text(CVLocalizations.of(context).partListItemPerPage), - items: getDropDownMenuElementPerPage(), - onChanged: (value) { - _partListBloc.setItemsPerPage(value); - }, - ); - }, - ), - ], - ); - } - i--; - if (i == partViewModels.length) { - return Center( - child: FlatButton( - onPressed: null, - child: Text(CVLocalizations.of(context).partListLoadMore), - ), - ); - } - } - return PartProfileWidget(part: partViewModels[i]); - }, - ); + void dispose() { + if (widget.partListBloc == null) partListBloc.dispose(); + super.dispose(); } } diff --git a/lib/src/ui/widgets/elements/part_profile_widget.dart b/lib/src/ui/widgets/elements/part_profile_widget.dart index 559d14a..4a32924 100644 --- a/lib/src/ui/widgets/elements/part_profile_widget.dart +++ b/lib/src/ui/widgets/elements/part_profile_widget.dart @@ -5,7 +5,7 @@ import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_list_profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; @@ -34,7 +34,7 @@ class _PartProfileWidgetState extends PartWidgetState { ); } if (state is PartLoaded) { - return _PartWidgetFromModel(partViewModel: state.element); + return _PartWidgetFromModel(part: state.element); } else if (state is PartFailure) { return ErrorContent( message: translateError(context, state.error), @@ -49,20 +49,20 @@ class _PartProfileWidgetState extends PartWidgetState { class _PartWidgetFromModel extends StatelessWidget { _PartWidgetFromModel({ - @required this.partViewModel, + @required this.part, }) : assert(PartViewModel != null); - final PartViewModel partViewModel; + final PartViewModel part; @override Widget build(BuildContext context) { - if (partViewModel.type == kCVPartTypeListHorizontal) { + if (part.type == kCVPartTypeListHorizontal) { return _PartWidgetFromModelHorizontal( - partViewModel: partViewModel, + partViewModel: part, ); - } else if (partViewModel.type == kCVPartTypeListVertical) { + } else if (part.type == kCVPartTypeListVertical) { return _PartWidgetFromModelVertical( - partViewModel: partViewModel, + part: part, ); } else { return ErrorContent(message: CVLocalizations.of(context).notSupported); @@ -98,8 +98,8 @@ class _PartWidgetFromModelHorizontal extends StatelessWidget { ), Container( height: AppDimensions.horizontalGroupListHeight, - child: GroupListWidget( - fromPartViewModel: partViewModel, + child: SimpleGroupListProfile( + groupIds: partViewModel.groupIds, scrollDirection: Axis.horizontal, shrinkWrap: true, ), @@ -111,10 +111,10 @@ class _PartWidgetFromModelHorizontal extends StatelessWidget { class _PartWidgetFromModelVertical extends StatelessWidget { _PartWidgetFromModelVertical({ - @required this.partViewModel, - }) : assert(PartViewModel != null); + @required this.part, + }) : assert(part != null); - final PartViewModel partViewModel; + final PartViewModel part; @override Widget build(BuildContext context) { @@ -124,19 +124,19 @@ class _PartWidgetFromModelVertical extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - partViewModel.name.toUpperCase(), + part.name.toUpperCase(), style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold), ), FlatButton( child: Text(CVLocalizations.of(context).partWidgetDetails), - onPressed: () => navigateToPart(context, partViewModel.id), + onPressed: () => navigateToPart(context, part.id), ), ], ), - GroupListWidget( - fromPartViewModel: partViewModel, + SimpleGroupListProfile( + groupIds: part.groupIds, scrollDirection: Axis.vertical, shrinkWrap: true, physics: ClampingScrollPhysics(), diff --git a/lib/src/ui/widgets/elements/part_widget.dart b/lib/src/ui/widgets/elements/part_widget.dart index a0fe892..a7f5bd5 100644 --- a/lib/src/ui/widgets/elements/part_widget.dart +++ b/lib/src/ui/widgets/elements/part_widget.dart @@ -1,22 +1,23 @@ import 'package:flutter/widgets.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/element_widget.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; /// If [partBloc] given we assume that it have been already initialized -abstract class PartWidget extends ElementWidget { +abstract class PartWidget extends StatefulWidget { + final String partId; + final PartViewModel part; final PartBloc partBloc; - PartWidget({Key key, String partId, PartViewModel part, this.partBloc}) + PartWidget({Key key, this.partId, this.part, this.partBloc}) : assert(partId != null && part == null && partBloc == null), assert(partId == null && part != null && partBloc == null), assert(partId == null && part == null && partBloc != null), - super(key: key, elementId: partId, element: part); + super(key: key); } -/// If [widget.profileBloc] exists the lifecycle of it will be managed by its creator -abstract class PartWidgetState - extends ElementWidgetState { +/// If [widget.partBloc] exists the lifecycle of it will be managed by its creator +abstract class PartWidgetState extends State { PartBloc partBloc; @override @@ -26,10 +27,11 @@ abstract class PartWidgetState partBloc = widget.partBloc; if (partBloc == null) { - partBloc = PartBloc(); + var provider = RepositoriesProvider.of(context); + partBloc = PartBloc(cvRepository: provider.cvRepository); partBloc.dispatch(PartInitialized( - withId: widget.elementId, - withPart: widget.element, + partId: widget.partId, + part: widget.part, )); } } diff --git a/lib/src/ui/widgets/elements/profile_list_tile_widget.dart b/lib/src/ui/widgets/elements/profile_list_tile_widget.dart new file mode 100644 index 0000000..48fb10c --- /dev/null +++ b/lib/src/ui/widgets/elements/profile_list_tile_widget.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_list_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/utils.dart'; + +/// [SimpleProfileListProfile] is a dummy widget that use [profileIds] or [profiles] or +/// [profileBlocs] to create a list of [ProfileTile] +class SimpleProfileListProfile extends StatelessWidget { + final List profileIds; + final List profiles; + final List profileBlocs; + + /// List behaviors + final Axis scrollDirection; + final bool shrinkWrap; + final ScrollPhysics physics; + + const SimpleProfileListProfile({ + Key key, + this.profileIds, + this.profiles, + this.profileBlocs, + + /// List behaviors + this.scrollDirection = Axis.vertical, + this.shrinkWrap = false, + this.physics, + }) : assert(profileIds != null && profiles == null && profileBlocs == null), + assert(profileIds == null && profiles != null && profileBlocs == null), + assert(profileIds == null && profiles == null && profileBlocs != null), + super(key: key); + + @override + Widget build(BuildContext context) { + int itemCount; + IndexedWidgetBuilder itemBuilder; + + if (profileIds != null && profileIds.isNotEmpty) { + itemCount = profileIds.length; + itemBuilder = (BuildContext context, int index) => + ProfileTile(profileId: profileIds[index]); + } else if (profiles != null && profiles.isNotEmpty) { + itemCount = profiles.length; + itemBuilder = (BuildContext context, int index) => + ProfileTile(profile: profiles[index]); + } else if (profileBlocs != null && profileBlocs.isNotEmpty) { + itemCount = profileBlocs.length; + itemBuilder = (BuildContext context, int index) => + ProfileTile(profileBloc: profileBlocs[index]); + } + + return ListView.builder( + scrollDirection: scrollDirection, + shrinkWrap: shrinkWrap, + physics: physics, + itemCount: itemCount, + itemBuilder: itemBuilder, + ); + } +} + +/// [ComplexProfileListProfile] is a clever widget that use [parentProfileId] or +/// [ownerId] or [profileListBloc] to display a list of [ProfileProfileWidget] +class ComplexProfileListProfile extends ProfileListWidget { + /// Search, filter and sort options + final bool showOptions; + + /// List behaviors + final Axis scrollDirection; + final bool shrinkWrap; + final ScrollPhysics physics; + + ComplexProfileListProfile({ + Key key, + String parentUserId, + String ownerId, + ProfileListBloc profileListBloc, + + /// Options + this.showOptions = false, + + /// List behaviors + this.scrollDirection = Axis.vertical, + this.shrinkWrap = false, + this.physics, + }) : super( + key: key, + parentUserId: parentUserId, + ownerId: ownerId, + profileListBloc: profileListBloc, + ); + + @override + State createState() => _ProfileListProfileState(); +} + +class _ProfileListProfileState + extends ProfileListWidgetState { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: widget.profileListBloc, + builder: (BuildContext context, ProfileListState state) { + if (state is ProfileListLoading) { + return LoadingList( + count: state.count, + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); + } else if (state is ProfileListLoaded) { + final List sortItems = [ + SortListItem(field: 'name', title: 'Name', value: SortState.NoSort) + ]; + + final sortRow = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon(Icons.sort_by_alpha), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return SortDialog( + title: Text( + CVLocalizations.of(context).profileListSorting), + sortItems: sortItems, + ); + }, + ); + }, + ), + DropdownButton( + value: null, + hint: Text(CVLocalizations.of(context).profileListItemPerPage), + items: getDropDownMenuElementPerPage(), + onChanged: (value) {}, + ) + ], + ); + + return ListView( + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + children: [ + if (widget.showOptions) sortRow, + for (var profile in state.elements) ProfileTile(profile: profile), + if (widget.showOptions) + Center( + child: FlatButton( + onPressed: null, + child: + Text(CVLocalizations.of(context).profileListLoadMore), + ), + ), + ], + ); + } else if (state is ProfileListFailure) { + return ErrorList( + error: CVLocalizations.of(context).notSupported, + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); + } + return Container(); + }, + ); + } +} diff --git a/lib/src/ui/widgets/elements/profile_list_widget.dart b/lib/src/ui/widgets/elements/profile_list_widget.dart index 4107bc1..730bf33 100644 --- a/lib/src/ui/widgets/elements/profile_list_widget.dart +++ b/lib/src/ui/widgets/elements/profile_list_widget.dart @@ -1,267 +1,49 @@ -import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; -/// A widget to list all profiles from [UserViewModel] or from a search -class ProfileListWidget extends StatelessWidget { - const ProfileListWidget({ +/// If [profileListBloc] given we assume that it have been already initialized +abstract class ProfileListWidget extends StatefulWidget { + final String parentUserId; + final String ownerId; + final ProfileListBloc profileListBloc; + + ProfileListWidget({ Key key, - this.fromUserViewModel, - this.fromSearch, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(fromUserViewModel != null || fromSearch != null), + this.parentUserId, + this.ownerId, + this.profileListBloc, + }) : assert( + parentUserId != null && ownerId == null && profileListBloc == null), + assert( + parentUserId == null && ownerId != null && profileListBloc == null), + assert( + parentUserId == null && ownerId == null && profileListBloc != null), super(key: key); - - final UserViewModel fromUserViewModel; - final Object fromSearch; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; - - @override - Widget build(BuildContext context) { - if (fromUserViewModel != null) { - return _ProfileListFromUserViewModel( - fromUserViewModel, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else if (fromSearch != null) { - return _ProfileListFromSearch( - search: fromSearch, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else { - return ErrorList( - error: CVLocalizations.of(context).notYetImplemented, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } - } -} - -class _ProfileListFromUserViewModel extends StatelessWidget { - _ProfileListFromUserViewModel( - this.userViewModel, { - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }); - - final UserViewModel userViewModel; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; - - @override - Widget build(BuildContext context) { - ElementListBloc profileListBloc = - BlocProvider.of>(context); - - profileListBloc.fetchAccountProfiles(); - - return StreamBuilder>( - stream: profileListBloc.profilesStream, - builder: (BuildContext context, - AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return ErrorList( - error: snapshot.error, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else if (snapshot.hasData) { - return _ProfileList( - profileViewModels: snapshot.data, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else { - return LoadingList( - count: userViewModel.profileIds.length, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } - }, - ); - } } -class _ProfileListFromSearch extends StatelessWidget { - _ProfileListFromSearch({ - this.search, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(search != null); - - final Object search; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; +/// If [widget.profileListBloc] exists the lifecycle of it will be managed by its creator +abstract class ProfileListWidgetState + extends State { + ProfileListBloc profileListBloc; @override - Widget build(BuildContext context) { - ProfileListBloc profileListBloc = BlocProvider.of(context); - profileListBloc.fetchProfiles(search); - - return StreamBuilder>( - stream: profileListBloc.profilesStream, - builder: (BuildContext context, - AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return ErrorList( - error: snapshot.error, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else if (snapshot.hasData) { - return _ProfileList( - profileViewModels: snapshot.data, - showOptions: showOptions, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } else { - return LoadingList( - count: 1, - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - ); - } - }, - ); + void initState() { + super.initState(); + profileListBloc = widget.profileListBloc; + if (widget.profileListBloc == null) { + var provider = RepositoriesProvider.of(context); + profileListBloc = ProfileListBloc(cvRepository: provider.cvRepository); + profileListBloc.dispatch(ProfileListInitialized( + parentUserId: widget.parentUserId, + ownerId: widget.ownerId, + )); + } } -} - -class _ProfileList extends StatelessWidget { - _ProfileList({ - @required this.profileViewModels, - this.showOptions = false, - this.scrollDirection = Axis.vertical, - this.shrinkWrap = false, - this.physics, - }) : assert(profileViewModels != null); - - final List profileViewModels; - - final bool showOptions; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; @override - Widget build(BuildContext context) { - ElementListBloc _profileListBloc = - BlocProvider.of>(context); - - final List sortItems = [ - SortListItem(field: 'title', title: 'Title', value: SortState.NoSort) - ]; - - return ListView.builder( - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, - itemCount: - showOptions ? profileViewModels.length + 2 : profileViewModels.length, - itemBuilder: (BuildContext context, int i) { - if (showOptions) { - if (i == 0) { - return Container( - height: AppDimensions.listHeaderDefaultHeightMax, - color: Colors.transparent, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: Icon(Icons.sort_by_alpha), - tooltip: CVLocalizations.of(context).profileListSorting, - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return SortDialog( - title: Text( - CVLocalizations.of(context).profileListSorting), - sortItems: sortItems, - ); - }, - ); - }, - ), - StreamBuilder( - stream: _profileListBloc.profilePerPage, - builder: - (BuildContext context, AsyncSnapshot snapshot) { - return DropdownButton( - value: snapshot.data, - hint: Text( - CVLocalizations.of(context).partListItemPerPage), - items: getDropDownMenuElementPerPage(), - onChanged: (value) { - _profileListBloc.setItemsPerPage(value); - }, - ); - }, - ), - ], - ), - ); - } - i--; - if (i == profileViewModels.length) { - return Center( - child: FlatButton( - onPressed: null, - child: Text(CVLocalizations.of(context).profileListLoadMore), - ), - ); - } - } - return ProfileTile(profileViewModel: profileViewModels[i]); - }, - ); + void dispose() { + if (widget.profileListBloc == null) profileListBloc.dispose(); + super.dispose(); } } diff --git a/lib/src/ui/widgets/elements/profile_tile_widget.dart b/lib/src/ui/widgets/elements/profile_tile_widget.dart index ebc3583..bea91f9 100644 --- a/lib/src/ui/widgets/elements/profile_tile_widget.dart +++ b/lib/src/ui/widgets/elements/profile_tile_widget.dart @@ -1,28 +1,53 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -class ProfileTile extends StatelessWidget { - final ProfileViewModel profileViewModel; - - const ProfileTile({ +class ProfileTile extends ProfileWidget { + ProfileTile({ Key key, - this.profileViewModel, - }) : assert(profileViewModel != null), - super(key: key); + String profileId, + ProfileViewModel profile, + ProfileBloc profileBloc, + }) : super( + key: key, + profileId: profileId, + profile: profile, + profileBloc: profileBloc, + ); + + @override + State createState() => _ProfileTileState(); +} +class _ProfileTileState extends ProfileWidgetState { @override Widget build(BuildContext context) { - return ListTile( - leading: InitialCircleAvatar( - backgroundImage: NetworkImage(profileViewModel.picture ?? ''), - ), - title: Text(profileViewModel.title ?? ''), - subtitle: Text(profileViewModel.subtitle ?? ''), - onTap: () => navigateToProfile(context, profileViewModel.id), - trailing: Icon(MdiIcons.accountDetails), + return BlocBuilder( + bloc: widget.profileBloc, + builder: (BuildContext context, ProfileState state) { + if (state is ProfileLoading) { + } else if (state is ProfileLoaded) { + var profile = state.element; + return ListTile( + leading: InitialCircleAvatar( + backgroundImage: NetworkImage(profile.picture ?? ''), + ), + title: Text(profile.title ?? ''), + subtitle: Text(profile.subtitle ?? ''), + onTap: () => navigateToProfile(context, profile.id), + trailing: Icon(MdiIcons.accountDetails), + ); + } else if (state is ProfileFailure) { + return ErrorTile(message: '${state.error.runtimeType}'); + } + return Container(); + }, ); } } diff --git a/lib/src/ui/widgets/elements/profile_widget.dart b/lib/src/ui/widgets/elements/profile_widget.dart index 6fa88a2..4bd9fe5 100644 --- a/lib/src/ui/widgets/elements/profile_widget.dart +++ b/lib/src/ui/widgets/elements/profile_widget.dart @@ -1,23 +1,23 @@ import 'package:flutter/widgets.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/element_widget.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; /// If [profileBloc] given we assume that it have been already initialized -abstract class ProfileWidget extends ElementWidget { +abstract class ProfileWidget extends StatefulWidget { + final String profileId; + final ProfileViewModel profile; final ProfileBloc profileBloc; - ProfileWidget( - {Key key, String profileId, ProfileViewModel profile, this.profileBloc}) + ProfileWidget({Key key, this.profileId, this.profile, this.profileBloc}) : assert(profileId != null && profile == null && profileBloc == null), assert(profileId == null && profile != null && profileBloc == null), assert(profileId == null && profile == null && profileBloc != null), - super(key: key, elementId: profileId, element: profile); + super(key: key); } /// If [widget.profileBloc] exists the lifecycle of it will be managed by its creator -abstract class ProfileWidgetState - extends ElementWidgetState { +abstract class ProfileWidgetState extends State { ProfileBloc profileBloc; @override @@ -27,10 +27,11 @@ abstract class ProfileWidgetState profileBloc = widget.profileBloc; if (profileBloc == null) { - profileBloc = ProfileBloc(); + var provider = RepositoriesProvider.of(context); + profileBloc = ProfileBloc(cvRepository: provider.cvRepository); profileBloc.dispatch(ProfileInitialized( - withId: widget.elementId, - withProfile: widget.element, + profileId: widget.profileId, + profile: widget.profile, )); } } diff --git a/lib/src/ui/widgets/error_widget.dart b/lib/src/ui/widgets/error_widget.dart index 5522423..54c9e28 100644 --- a/lib/src/ui/widgets/error_widget.dart +++ b/lib/src/ui/widgets/error_widget.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; class ErrorContent extends StatelessWidget { @@ -27,7 +29,29 @@ class ErrorContent extends StatelessWidget { } } +class ErrorTile extends StatelessWidget { + final String message; + + const ErrorTile({ + Key key, + @required this.message, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: Icon(MdiIcons.alertCircleOutline), + title: Text(CVLocalizations.of(context).errorOccurred), + subtitle: Text(message), + ); + } +} + class ErrorCard extends StatelessWidget { + final String message; + final double height; + final double width; + const ErrorCard({ Key key, @required this.message, @@ -36,10 +60,6 @@ class ErrorCard extends StatelessWidget { }) : assert(message != null), super(key: key); - final String message; - final double height; - final double width; - @override Widget build(BuildContext context) { return Card( diff --git a/lib/src/ui/widgets/loading_widget.dart b/lib/src/ui/widgets/loading_widget.dart index 03454b1..0faf38d 100644 --- a/lib/src/ui/widgets/loading_widget.dart +++ b/lib/src/ui/widgets/loading_widget.dart @@ -7,20 +7,20 @@ import 'package:flutter/widgets.dart'; /// Specify the number of loading lines to display class LoadingShadowContent extends StatefulWidget { + final int numberOfTitleLines; + final int numberOfContentLines; + final EdgeInsetsGeometry padding; + const LoadingShadowContent({ Key key, - this.numberOfTitleLines = 1, - this.numberOfContentLines = 3, + this.numberOfTitleLines = 0, + this.numberOfContentLines = 0, this.padding = const EdgeInsets.all(0.0), }) : assert(numberOfTitleLines != null), assert(numberOfContentLines != null), assert(padding != null), super(key: key); - final int numberOfTitleLines; - final int numberOfContentLines; - final EdgeInsetsGeometry padding; - _LoadingShadowContentState createState() => _LoadingShadowContentState(); } diff --git a/lib/src/ui/widgets/menu_button_widget.dart b/lib/src/ui/widgets/menu_button_widget.dart index 4a31456..02b5a39 100644 --- a/lib/src/ui/widgets/menu_button_widget.dart +++ b/lib/src/ui/widgets/menu_button_widget.dart @@ -74,8 +74,8 @@ class _MenuButtonConnectedState extends State<_MenuButtonConnected> { child: IconButton( onPressed: () => openMenuBottomSheet(context), icon: InitialCircleAvatar( - text: state.userModel.username, - backgroundImage: NetworkImage(state.userModel.picture), + text: state.user.username, + backgroundImage: NetworkImage(state.user.picture), ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 3371f72..919a195 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.35.4" + version: "0.36.3" args: dependency: transitive description: @@ -42,21 +42,21 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.1.4" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.3.2" + version: "0.4.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "0.5.0" + version: "0.6.1" build_resolvers: dependency: transitive description: @@ -70,28 +70,28 @@ packages: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.3.3" + version: "1.4.0" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "3.0.3" + version: "3.0.5" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "4.2.0" + version: "4.2.2" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "6.4.0" + version: "6.5.0" charcode: dependency: transitive description: @@ -134,13 +134,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.6" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.0" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.2.4" + version: "1.2.7" dio: dependency: transitive description: @@ -197,7 +204,7 @@ packages: name: front_end url: "https://pub.dartlang.org" source: hosted - version: "0.1.14" + version: "0.1.18" glob: dependency: transitive description: @@ -212,6 +219,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+2" http: dependency: transitive description: @@ -225,7 +239,7 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" http_parser: dependency: transitive description: @@ -246,7 +260,7 @@ packages: name: intl_translation url: "https://pub.dartlang.org" source: hosted - version: "0.17.3" + version: "0.17.4" io: dependency: transitive description: @@ -267,14 +281,14 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.4.0" kernel: dependency: transitive description: name: kernel url: "https://pub.dartlang.org" source: hosted - version: "0.3.14" + version: "0.3.18" logging: dependency: "direct main" description: @@ -440,7 +454,7 @@ packages: name: stream_transform url: "https://pub.dartlang.org" source: hosted - version: "0.0.17" + version: "0.0.19" string_scanner: dependency: transitive description: @@ -512,5 +526,5 @@ packages: source: hosted version: "2.1.15" sdks: - dart: ">=2.2.0 <3.0.0" + dart: ">=2.3.0-dev.0.1 <3.0.0" flutter: ">=0.1.4 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 46a85ef..4501c5d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: social_cv_client_flutter description: A new Flutter application. environment: - sdk: '>=2.1.0 <3.0.0' + sdk: '>=2.2.2 <3.0.0' dependencies: From e0070e01bed01c68a3890e47ebfaa56cea9795ae Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sat, 18 May 2019 15:04:21 +0200 Subject: [PATCH 03/17] [TASK] Edited error widgets - Added doc - Edited error use - Edited code format --- lib/src/app.dart | 5 +- lib/src/routes.dart | 4 +- lib/src/ui/pages/account_page.dart | 21 ++--- .../ui/pages/elements/entry_profile_page.dart | 4 +- .../ui/pages/elements/group_profile_page.dart | 4 +- .../pages/elements/profile_profile_page.dart | 5 +- lib/src/ui/pages/home_page.dart | 6 +- lib/src/ui/pages/main_page.dart | 4 +- lib/src/ui/widgets/account_tile_widget.dart | 12 +-- .../ui/widgets/elements/element_widget.dart | 2 - .../elements/entry_list_profile_widget.dart | 11 ++- .../elements/entry_profile_widget.dart | 34 ++++--- .../elements/group_list_profile_widget.dart | 10 +- .../elements/group_profile_widget.dart | 6 +- .../elements/part_list_profile_widget.dart | 10 +- .../widgets/elements/part_profile_widget.dart | 14 +-- .../elements/profile_list_tile_widget.dart | 10 +- .../widgets/elements/profile_tile_widget.dart | 5 +- lib/src/ui/widgets/error_widget.dart | 94 +++++++++++-------- lib/src/ui/widgets/login_form_widget.dart | 8 +- lib/src/ui/widgets/menu_button_widget.dart | 20 ++-- lib/src/utils/logging_service.dart | 22 ++--- pubspec.yaml | 34 +++---- 23 files changed, 189 insertions(+), 156 deletions(-) delete mode 100644 lib/src/ui/widgets/elements/element_widget.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index 0f84701..184c0d8 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; import 'package:social_cv_client_flutter/src/routes.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; @@ -50,7 +51,7 @@ class _ConfigWrapperAppState extends State { } else if (state is ConfigLoaded) { return _CVApp(state); } - return Container(); + return ErrorRow(error: NotImplementedYetError()); }, ); } @@ -122,7 +123,7 @@ class _CVAppState extends State<_CVApp> { if (state is AppInitialized) return _CVInitializedApp(state: state); - if (state is AppFailure) return ErrorCard(message: 'Error Setup App'); + if (state is AppFailure) return ErrorCard(error: state.error); if (state is AppLoading) return Center(child: CircularProgressIndicator()); diff --git a/lib/src/routes.dart b/lib/src/routes.dart index 9e0eae2..8d7c1d8 100644 --- a/lib/src/routes.dart +++ b/lib/src/routes.dart @@ -13,7 +13,7 @@ import 'package:social_cv_client_flutter/src/ui/pages/settings_page.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; class Routes { - final String _TAG = 'Routes'; + final String _tag = '$Routes'; final Router router = Router(); Routes({ @@ -32,7 +32,7 @@ class Routes { final PreferencesRepository preferencesRepository; void _defineRoutes() { - logger.info('$_TAG:_defineRoutes'); + logger.info('$_tag:$_defineRoutes'); router.define( AppPaths.kPathHome, diff --git a/lib/src/ui/pages/account_page.dart b/lib/src/ui/pages/account_page.dart index 0acd440..c77e146 100644 --- a/lib/src/ui/pages/account_page.dart +++ b/lib/src/ui/pages/account_page.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; class AccountPage extends StatefulWidget { const AccountPage({Key key}) : super(key: key); @@ -17,14 +17,14 @@ class AccountPage extends StatefulWidget { } class _AccountPageState extends State { - final String _TAG = '_AccountPageState'; + final String _tag = '$_AccountPageState'; - // TODO: Add AuthenticationBloc + /// TODO: Add AuthenticationBloc AuthenticationBloc get _authBloc => null; @override Widget build(BuildContext context) { - logger.info('$_TAG:build'); + logger.info('$_tag:$build'); return SafeArea( left: false, @@ -76,7 +76,7 @@ class _AccountPageDetailsConnected extends StatefulWidget { class _AccountPageDetailsConnectedState extends State<_AccountPageDetailsConnected> { - // TODO: Add AccountBloc + /// TODO: Add AccountBloc AccountBloc get _accountBloc => null; @override @@ -87,9 +87,7 @@ class _AccountPageDetailsConnectedState bloc: _accountBloc, builder: (BuildContext context, AccountState state) { if (state is AccountUninitialized) { - return Container(); - } - if (state is AccountLoaded) { + } else if (state is AccountLoaded) { return ListView( children: [ ExpansionTile( @@ -113,11 +111,10 @@ class _AccountPageDetailsConnectedState ), ], ); + } else if (state is AccountFailed) { + return ErrorCard(error: state.error); } - if (state is AccountFailed) { - return ErrorCard(message: translateError(context, state.error)); - } - return Container(); + return ErrorCard(error: NotImplementedYetError()); }, ); } diff --git a/lib/src/ui/pages/elements/entry_profile_page.dart b/lib/src/ui/pages/elements/entry_profile_page.dart index d1bbfc8..941281b 100644 --- a/lib/src/ui/pages/elements/entry_profile_page.dart +++ b/lib/src/ui/pages/elements/entry_profile_page.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; class EntryPage extends EntryWidget { @@ -58,7 +60,7 @@ class _EntryPageState extends EntryWidgetState { ), ); } - return Container(); + return ErrorCard(error: NotImplementedYetError()); }, ); } diff --git a/lib/src/ui/pages/elements/group_profile_page.dart b/lib/src/ui/pages/elements/group_profile_page.dart index e28a08d..487f6b3 100644 --- a/lib/src/ui/pages/elements/group_profile_page.dart +++ b/lib/src/ui/pages/elements/group_profile_page.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; class GroupPage extends GroupWidget { @@ -49,7 +51,7 @@ class _GroupPageState extends GroupWidgetState { ), ); } - return Container(); + return ErrorCard(error: NotImplementedYetError()); }, ); } diff --git a/lib/src/ui/pages/elements/profile_profile_page.dart b/lib/src/ui/pages/elements/profile_profile_page.dart index 42aa357..ec350d4 100644 --- a/lib/src/ui/pages/elements/profile_profile_page.dart +++ b/lib/src/ui/pages/elements/profile_profile_page.dart @@ -9,7 +9,6 @@ import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; /// TODO : Build owner interaction with ProfileViewModel.owner @@ -44,12 +43,12 @@ class _ProfileProfilePageState extends ProfileWidgetState { ProfileViewModel profile = state.element; slivers.addAll(profile.partIds .map((partId) => - SliverToBoxAdapter(child: PartProfileWidget(partId: partId))) + SliverToBoxAdapter(child: PartProfileWidget(partId: partId))) .toList()); } else if (state is ProfileFailure) { slivers.add( SliverToBoxAdapter( - child: ErrorCard(message: translateError(context, state.error)), + child: ErrorCard(error: state.error), ), ); } diff --git a/lib/src/ui/pages/home_page.dart b/lib/src/ui/pages/home_page.dart index 840779a..5314f3c 100644 --- a/lib/src/ui/pages/home_page.dart +++ b/lib/src/ui/pages/home_page.dart @@ -3,13 +3,15 @@ import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.da import 'package:social_cv_client_flutter/src/utils/logger.dart'; class HomePage extends StatelessWidget { - const HomePage({ + final String _tag = '$HomePage'; + + HomePage({ Key key, }) : super(key: key); @override Widget build(BuildContext context) { - logger.info('Building HomePage'); + logger.info('$_tag:$build'); return SafeArea( left: false, right: false, diff --git a/lib/src/ui/pages/main_page.dart b/lib/src/ui/pages/main_page.dart index 7b24e0a..8382984 100644 --- a/lib/src/ui/pages/main_page.dart +++ b/lib/src/ui/pages/main_page.dart @@ -16,7 +16,7 @@ class MainPage extends StatefulWidget { } class _MainPageState extends State { - final String _TAG = '_MainPageState'; + final String _tag = '$_MainPageState'; int _currentIndex = 0; final List _children = [ @@ -26,7 +26,7 @@ class _MainPageState extends State { @override Widget build(BuildContext context) { - logger.info('$_TAG:build'); + logger.info('$_tag:$build'); return Scaffold( appBar: AppBar( diff --git a/lib/src/ui/widgets/account_tile_widget.dart b/lib/src/ui/widgets/account_tile_widget.dart index c6ac7c6..2902e9d 100644 --- a/lib/src/ui/widgets/account_tile_widget.dart +++ b/lib/src/ui/widgets/account_tile_widget.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; @@ -26,7 +28,7 @@ class _AccountTitleState extends State { return _AccountTileConnected(); if (state is AuthenticationUnauthenticated) return _AccountTileNotConnected(); - return Container(); + return ErrorTile(error: NotImplementedYetError()); }, ); } @@ -55,9 +57,7 @@ class _AccountTileConnectedState extends State<_AccountTileConnected> { bloc: _accountBloc, builder: (BuildContext context, AccountState state) { if (state is AccountUninitialized) { - return Container(); - } - if (state is AccountLoaded) { + } else if (state is AccountLoaded) { var userModel = state.user; return ListTile( leading: InitialCircleAvatar( @@ -71,10 +71,10 @@ class _AccountTileConnectedState extends State<_AccountTileConnected> { onPressed: () => null, // TODO: Add logout ), ); - } - if (state is AccountFailed) { + } else if (state is AccountFailed) { return Container(child: Text('${state.error}')); } + return ErrorTile(error: NotImplementedYetError()); }, ); } diff --git a/lib/src/ui/widgets/elements/element_widget.dart b/lib/src/ui/widgets/elements/element_widget.dart deleted file mode 100644 index fe166d6..0000000 --- a/lib/src/ui/widgets/elements/element_widget.dart +++ /dev/null @@ -1,2 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_dart_common/models.dart'; diff --git a/lib/src/ui/widgets/elements/entry_list_profile_widget.dart b/lib/src/ui/widgets/elements/entry_list_profile_widget.dart index 0f5ef26..72a9a28 100644 --- a/lib/src/ui/widgets/elements/entry_list_profile_widget.dart +++ b/lib/src/ui/widgets/elements/entry_list_profile_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_list_widget.dart'; @@ -166,13 +167,19 @@ class _EntryListProfileState ); } else if (state is EntryListFailure) { return ErrorList( - error: CVLocalizations.of(context).notSupported, + error: state.error, scrollDirection: widget.scrollDirection, shrinkWrap: widget.shrinkWrap, physics: widget.physics, ); } - return Container(); + + return ErrorList( + error: NotImplementedYetError(), + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); }, ); } diff --git a/lib/src/ui/widgets/elements/entry_profile_widget.dart b/lib/src/ui/widgets/elements/entry_profile_widget.dart index 265aa99..eff8434 100644 --- a/lib/src/ui/widgets/elements/entry_profile_widget.dart +++ b/lib/src/ui/widgets/elements/entry_profile_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; @@ -21,35 +22,32 @@ class EntryProfileWidget extends EntryWidget { class _EntryProfileWidgetState extends EntryWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: this.entryBloc, builder: (BuildContext context, EntryState state) { if (state is EntryLoaded) { - EntryViewModel entryViewModel = state.element; - if (entryViewModel.type == kCVEntryTypeMap) { - return _EntryWidgetMap(entry: entryViewModel); - } else if (entryViewModel.type == kCVEntryTypeEvent) { - return _EntryWidgetEvent(entry: entryViewModel); - } else if (entryViewModel.type == kCVEntryTypeTag) { - return _EntryWidgetTag(entryViewModel); - } else { - return ErrorContent( - message: CVLocalizations.of(context).notSupported); + final entry = state.element; + if (entry.type == kCVEntryTypeMap) { + return _EntryWidgetMap(entry: entry); + } else if (entry.type == kCVEntryTypeEvent) { + return _EntryWidgetEvent(entry: entry); + } else if (entry.type == kCVEntryTypeTag) { + return _EntryWidgetTag(entry); } } - return Container(); + return ErrorRow(error: NotImplementedYetError()); }, ); } } class _EntryWidgetMap extends StatelessWidget { + final EntryViewModel entry; + _EntryWidgetMap({ @required this.entry, }) : assert(EntryViewModel != null); - final EntryViewModel entry; - @override Widget build(BuildContext context) { return InkWell( @@ -77,12 +75,12 @@ class _EntryWidgetMap extends StatelessWidget { } class _EntryWidgetEvent extends StatelessWidget { + final EntryViewModel entry; + _EntryWidgetEvent({ @required this.entry, }) : assert(EntryViewModel != null); - final EntryViewModel entry; - @override Widget build(BuildContext context) { return Card( @@ -144,10 +142,10 @@ class _EntryWidgetEvent extends StatelessWidget { } class _EntryWidgetTag extends StatelessWidget { - _EntryWidgetTag(this.entry); - final EntryViewModel entry; + _EntryWidgetTag(this.entry); + @override Widget build(BuildContext context) { List tags = entry.content; diff --git a/lib/src/ui/widgets/elements/group_list_profile_widget.dart b/lib/src/ui/widgets/elements/group_list_profile_widget.dart index 133670b..66096d7 100644 --- a/lib/src/ui/widgets/elements/group_list_profile_widget.dart +++ b/lib/src/ui/widgets/elements/group_list_profile_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_list_widget.dart'; @@ -166,13 +167,18 @@ class _GroupListProfileState ); } else if (state is GroupListFailure) { return ErrorList( - error: CVLocalizations.of(context).notSupported, + error: state.error, scrollDirection: widget.scrollDirection, shrinkWrap: widget.shrinkWrap, physics: widget.physics, ); } - return Container(); + return ErrorList( + error: NotImplementedYetError(), + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); }, ); } diff --git a/lib/src/ui/widgets/elements/group_profile_widget.dart b/lib/src/ui/widgets/elements/group_profile_widget.dart index 02eea64..ba92f85 100644 --- a/lib/src/ui/widgets/elements/group_profile_widget.dart +++ b/lib/src/ui/widgets/elements/group_profile_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; @@ -32,11 +33,10 @@ class _GroupProfileWidgetState extends GroupWidgetState { } else if (group.type == kCVGroupTypeListVertical) { return _GroupVertical(group: group); } else { - return ErrorContent( - message: CVLocalizations.of(context).notSupported); + return ErrorRow(error: NotImplementedYetError()); } } - return Container(); + return ErrorRow(error: NotImplementedYetError()); }, ); } diff --git a/lib/src/ui/widgets/elements/part_list_profile_widget.dart b/lib/src/ui/widgets/elements/part_list_profile_widget.dart index 766beda..caa73a6 100644 --- a/lib/src/ui/widgets/elements/part_list_profile_widget.dart +++ b/lib/src/ui/widgets/elements/part_list_profile_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_list_widget.dart'; @@ -167,13 +168,18 @@ class _PartListProfileState ); } else if (state is PartListFailure) { return ErrorList( - error: CVLocalizations.of(context).notSupported, + error: state.error, scrollDirection: widget.scrollDirection, shrinkWrap: widget.shrinkWrap, physics: widget.physics, ); } - return Container(); + return ErrorList( + error: NotImplementedYetError(), + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); }, ); } diff --git a/lib/src/ui/widgets/elements/part_profile_widget.dart b/lib/src/ui/widgets/elements/part_profile_widget.dart index 4a32924..dbda66d 100644 --- a/lib/src/ui/widgets/elements/part_profile_widget.dart +++ b/lib/src/ui/widgets/elements/part_profile_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; @@ -10,7 +11,6 @@ import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_widget.dar import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; class PartProfileWidget extends PartWidget { PartProfileWidget( @@ -32,16 +32,12 @@ class _PartProfileWidgetState extends PartWidgetState { numberOfTitleLines: 1, numberOfContentLines: 2, ); - } - if (state is PartLoaded) { + } else if (state is PartLoaded) { return _PartWidgetFromModel(part: state.element); } else if (state is PartFailure) { - return ErrorContent( - message: translateError(context, state.error), - ); + return ErrorRow(error: state.error); } - return ErrorContent( - message: CVLocalizations.of(context).notYetImplemented); + return ErrorRow(error: NotImplementedYetError()); }, ); } @@ -65,7 +61,7 @@ class _PartWidgetFromModel extends StatelessWidget { part: part, ); } else { - return ErrorContent(message: CVLocalizations.of(context).notSupported); + return ErrorRow(error: NotImplementedYetError()); } } } diff --git a/lib/src/ui/widgets/elements/profile_list_tile_widget.dart b/lib/src/ui/widgets/elements/profile_list_tile_widget.dart index 48fb10c..568401c 100644 --- a/lib/src/ui/widgets/elements/profile_list_tile_widget.dart +++ b/lib/src/ui/widgets/elements/profile_list_tile_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_list_widget.dart'; @@ -168,13 +169,18 @@ class _ProfileListProfileState ); } else if (state is ProfileListFailure) { return ErrorList( - error: CVLocalizations.of(context).notSupported, + error: state.error, scrollDirection: widget.scrollDirection, shrinkWrap: widget.shrinkWrap, physics: widget.physics, ); } - return Container(); + return ErrorList( + error: NotImplementedYetError(), + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + physics: widget.physics, + ); }, ); } diff --git a/lib/src/ui/widgets/elements/profile_tile_widget.dart b/lib/src/ui/widgets/elements/profile_tile_widget.dart index bea91f9..1314955 100644 --- a/lib/src/ui/widgets/elements/profile_tile_widget.dart +++ b/lib/src/ui/widgets/elements/profile_tile_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; @@ -44,9 +45,9 @@ class _ProfileTileState extends ProfileWidgetState { trailing: Icon(MdiIcons.accountDetails), ); } else if (state is ProfileFailure) { - return ErrorTile(message: '${state.error.runtimeType}'); + return ErrorTile(error: state.error); } - return Container(); + return ErrorTile(error: NotImplementedYetError()); }, ); } diff --git a/lib/src/ui/widgets/error_widget.dart b/lib/src/ui/widgets/error_widget.dart index 54c9e28..4759626 100644 --- a/lib/src/ui/widgets/error_widget.dart +++ b/lib/src/ui/widgets/error_widget.dart @@ -5,60 +5,65 @@ import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; -class ErrorContent extends StatelessWidget { - ErrorContent({ - @required this.message, - }) : assert(message != null); +abstract class ErrorWidget extends StatelessWidget { + final Error error; - final String message; + ErrorWidget({Key key, @required this.error}) + : assert(error != null, 'No $Error given'), + super(key: key); +} + +/// [ErrorText] is a [Text] widget to display [Error] +class ErrorText extends ErrorWidget { + ErrorText({Key key, @required Error error}) : super(key: key, error: error); + + @override + Widget build(BuildContext context) { + return Text(translateError(context, error)); + } +} + +/// [ErrorRow] is a [Row] widget to display [Error] +class ErrorRow extends ErrorWidget { + ErrorRow({Key key, @required Error error}) : super(key: key, error: error); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Icon( - Icons.error, - color: AppColors.errorColor, - ), - Expanded( - child: Text(message, textAlign: TextAlign.center), - ) + Icon(Icons.error, color: AppColors.errorColor), + Expanded(child: ErrorText(error: error)) ], ); } } -class ErrorTile extends StatelessWidget { - final String message; - - const ErrorTile({ - Key key, - @required this.message, - }) : super(key: key); +/// [ErrorTile] is a [ListTile] widget to display [Error] +class ErrorTile extends ErrorWidget { + ErrorTile({Key key, @required Error error}) : super(key: key, error: error); @override Widget build(BuildContext context) { return ListTile( leading: Icon(MdiIcons.alertCircleOutline), title: Text(CVLocalizations.of(context).errorOccurred), - subtitle: Text(message), + subtitle: ErrorText(error: error), ); } } -class ErrorCard extends StatelessWidget { - final String message; +/// [ErrorCard] is a [Card] widget to display [Error] +class ErrorCard extends ErrorWidget { final double height; final double width; - const ErrorCard({ + ErrorCard({ Key key, - @required this.message, + @required Error error, this.height, this.width, - }) : assert(message != null), - super(key: key); + }) : super(key: key, error: error); @override Widget build(BuildContext context) { @@ -67,25 +72,28 @@ class ErrorCard extends StatelessWidget { height: height, width: width, padding: EdgeInsets.all(10.0), - child: ErrorContent(message: message), + child: ErrorRow(error: error), ), ); } } -class ErrorList extends StatelessWidget { +/// [ErrorList] is a [ListView] widget to display [Error] +class ErrorList extends ErrorWidget { + /// List behaviors + final Axis scrollDirection; + final bool shrinkWrap; + final ScrollPhysics physics; + ErrorList({ - @required this.error, + Key key, + @required Error error, + + /// List behaviors this.scrollDirection = Axis.vertical, this.shrinkWrap = false, this.physics, - }) : assert(error != null); - - final Object error; - - final Axis scrollDirection; - final bool shrinkWrap; - final ScrollPhysics physics; + }) : super(key: key, error: error); @override Widget build(BuildContext context) { @@ -94,8 +102,20 @@ class ErrorList extends StatelessWidget { shrinkWrap: this.shrinkWrap, physics: this.physics, children: [ - ErrorCard(message: translateError(context, error)), + ErrorTile(error: error), ], ); } } + +/// [ErrorPage] is a [Scaffold] widget to display [Error] +class ErrorPage extends ErrorWidget { + ErrorPage({Key key, @required Error error}) : super(key: key, error: error); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center(child: ErrorCard(error: error)), + ); + } +} diff --git a/lib/src/ui/widgets/login_form_widget.dart b/lib/src/ui/widgets/login_form_widget.dart index 97fdc5f..0b22d01 100644 --- a/lib/src/ui/widgets/login_form_widget.dart +++ b/lib/src/ui/widgets/login_form_widget.dart @@ -7,7 +7,6 @@ import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; class LoginFormWidget extends StatefulWidget { const LoginFormWidget({ @@ -19,7 +18,7 @@ class LoginFormWidget extends StatefulWidget { } class _LoginFormWidgetState extends State { - static const _TAG = '_LoginFormWidgetState'; + final String _tag = '$_LoginFormWidgetState'; _LoginFormWidgetState(); @@ -38,7 +37,7 @@ class _LoginFormWidgetState extends State { @override Widget build(BuildContext context) { - logger.info('$_TAG:build'); + logger.info('$_tag:$build'); return BlocBuilder( bloc: _loginBloc, @@ -119,8 +118,7 @@ class _LoginFormWidgetState extends State { ), ), (state is LoginFailure) - ? ErrorContent( - message: translateError(context, state.error)) + ? ErrorRow(error: state.error) : Container(), ], ), diff --git a/lib/src/ui/widgets/menu_button_widget.dart b/lib/src/ui/widgets/menu_button_widget.dart index 02b5a39..2cd316f 100644 --- a/lib/src/ui/widgets/menu_button_widget.dart +++ b/lib/src/ui/widgets/menu_button_widget.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/errors.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; @@ -25,17 +27,15 @@ class _MenuButtonState extends State { if (state is AuthenticationAuthenticated) return _MenuButtonConnected(); if (state is AuthenticationUnauthenticated) return _MenuButtonNotConnected(); - return Container(); + return ErrorRow(error: NotImplementedYetError()); }, ); } } -//////////////////////////////////////////////////////////////////////////////// -// // -// _MenuButtonNotConnected // -// // -//////////////////////////////////////////////////////////////////////////////// +/// ---------------------------------------------------------- +/// ---------------------- Not connected --------------------- +/// ---------------------------------------------------------- class _MenuButtonNotConnected extends StatelessWidget { @override @@ -47,11 +47,9 @@ class _MenuButtonNotConnected extends StatelessWidget { } } -//////////////////////////////////////////////////////////////////////////////// -// // -// _MenuButtonConnected // -// // -//////////////////////////////////////////////////////////////////////////////// +/// ---------------------------------------------------------- +/// ---------------------- Connected ------------------------- +/// ---------------------------------------------------------- class _MenuButtonConnected extends StatefulWidget { _MenuButtonConnected({Key key}) : super(key: key); diff --git a/lib/src/utils/logging_service.dart b/lib/src/utils/logging_service.dart index 3ca85e6..8e7742d 100644 --- a/lib/src/utils/logging_service.dart +++ b/lib/src/utils/logging_service.dart @@ -198,9 +198,9 @@ class LoggingService { static void info(String message, {String title, int autoCloseSeconds = -1}) => _instance.doInfo(message, title: title); - // ----------------------------------------------------------------------- - // Error - // ----------------------------------------------------------------------- + /// ----------------------------------------------------------------------- + /// Error + /// ----------------------------------------------------------------------- /// /// Do error log. Something went wrong, the app will not continue the @@ -221,15 +221,13 @@ class LoggingService { static void error(String message, {int errorCode}) => _instance.doError(message, errorCode); - // ----------------------------------------------------------------------- - // Fatal - // ----------------------------------------------------------------------- + /// ----------------------------------------------------------------------- + /// Fatal + /// ----------------------------------------------------------------------- - /// /// Fatal error. Something went wrong, the app will not continue the /// way it is meant to be and a mail with error details must be /// send to us. - /// void doFatal(String message, int errorCode, {String stackTrace}) { doLog(message, type: LogType.fatal, errorCode: errorCode, stackTrace: stackTrace); @@ -237,15 +235,13 @@ class LoggingService { doWarning('Fatal error not handled'); } - /// /// Static fatal function - /// static void fatal(String message, {int errorCode, String stackTrace}) => _instance.doFatal(message, errorCode, stackTrace: stackTrace); - // ----------------------------------------------------------------------- - // Log list - // ----------------------------------------------------------------------- + /// ----------------------------------------------------------------------- + /// Log list + /// ----------------------------------------------------------------------- static get logList => _instance._logList; diff --git a/pubspec.yaml b/pubspec.yaml index 4501c5d..398fb41 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,10 +8,10 @@ dependencies: # Common Social CV Logic social_cv_client_dart_common: - path: ../Social-CV-Client-Dart-common -# git: -# url: https://github.com/axellebot/Social-CV-Client-Dart-common -# ref: develop + path: ../Social-CV-Client-Dart-common + # git: + # url: https://github.com/axellebot/Social-CV-Client-Dart-common + # ref: develop flutter: sdk: flutter @@ -61,25 +61,25 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - assets/images/account_card_details-white.png - - assets/images/account_card_details-blue.png - - assets/images/default-avatar.png - - assets/images/default-banner.jpg + - assets/images/account_card_details-white.png + - assets/images/account_card_details-blue.png + - assets/images/default-avatar.png + - assets/images/default-banner.jpg - - config.json # secret data + - config.json # secret data fonts: - - family: Google Sans - fonts: - - asset: assets/fonts/GoogleSans-Regular.ttf - - asset: assets/fonts/GoogleSans-Medium.ttf - weight: 500 - - asset: assets/fonts/GoogleSans-Bold.ttf - weight: 700 + - family: Google Sans + fonts: + - asset: assets/fonts/GoogleSans-Regular.ttf + - asset: assets/fonts/GoogleSans-Medium.ttf + weight: 500 + - asset: assets/fonts/GoogleSans-Bold.ttf + weight: 700 # # For details regarding fonts from package dependencies, # see https://flutter.io/custom-fonts/#from-packages authors: -- Axel LE BOT \ No newline at end of file + - Axel LE BOT \ No newline at end of file From 4055fd92a08a2bbd7a02546753cf12018e97c6e3 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sat, 18 May 2019 15:30:46 +0200 Subject: [PATCH 04/17] [TASK] Edited loading and error widgets - Added LoadingApp/ErrorApp and ErrorPage/LoadingPage use --- lib/src/app.dart | 37 ++++++++++++++------------ lib/src/ui/pages/auth_page.dart | 4 +-- lib/src/ui/pages/splash_page.dart | 2 +- lib/src/ui/widgets/error_widget.dart | 12 +++++++++ lib/src/ui/widgets/loading_widget.dart | 28 +++++++++++++++++-- 5 files changed, 61 insertions(+), 22 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 184c0d8..d8a3c7e 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -11,6 +11,7 @@ import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.da import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/splash_page.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'domain/blocs/configuration/configuration.dart'; @@ -49,25 +50,25 @@ class _ConfigWrapperAppState extends State { color: AppColors.primaryColor, ); } else if (state is ConfigLoaded) { - return _CVApp(state); + return _ConfiguredApp(state); } - return ErrorRow(error: NotImplementedYetError()); + return ErrorApp(error: NotImplementedYetError()); }, ); } } -class _CVApp extends StatefulWidget { +class _ConfiguredApp extends StatefulWidget { final ConfigLoaded state; - _CVApp(this.state); + _ConfiguredApp(this.state); @override - State createState() => _CVAppState(); + State createState() => _ConfiguredAppState(); } -class _CVAppState extends State<_CVApp> { - final String _tag = "_CVAppState"; +class _ConfiguredAppState extends State<_ConfiguredApp> { + final String _tag = '$_ConfiguredAppState'; AppBloc _appBloc; AccountBloc _accountBloc; @@ -114,19 +115,21 @@ class _CVAppState extends State<_CVApp> { @override Widget build(BuildContext context) { - logger.info('$_tag:build'); + logger.info('$_tag:$build'); return BlocBuilder( bloc: _appBloc, builder: (BuildContext context, AppState state) { - if (state is AppUninitialized) Text('App Uninitialized'); - - if (state is AppInitialized) return _CVInitializedApp(state: state); - - if (state is AppFailure) return ErrorCard(error: state.error); + if (state is AppUninitialized) { + } else if (state is AppLoading) { + return LoadingApp(); + } else if (state is AppInitialized) { + return _CVInitializedApp(state: state); + } else if (state is AppFailure) { + return ErrorApp(error: state.error); + } - if (state is AppLoading) - return Center(child: CircularProgressIndicator()); + return ErrorApp(error: NotImplementedYetError()); }, ); } @@ -141,12 +144,12 @@ class _CVInitializedApp extends StatelessWidget { @override Widget build(BuildContext context) { - logger.info('$_tag:build'); + logger.info('$_tag:$build'); RepositoriesProvider repositories = RepositoriesProvider.of(context); ///Routes - Routes routes = Routes( + final routes = Routes( cvRepository: repositories.cvRepository, preferencesRepository: repositories.preferencesRepository, configRepository: repositories.configRepository, diff --git a/lib/src/ui/pages/auth_page.dart b/lib/src/ui/pages/auth_page.dart index 6b1d94c..0cb9a01 100644 --- a/lib/src/ui/pages/auth_page.dart +++ b/lib/src/ui/pages/auth_page.dart @@ -16,7 +16,7 @@ class AuthPage extends StatefulWidget { class _AuthPageState extends State with SingleTickerProviderStateMixin { - static const String _TAG = '_AuthPageState'; + final String _tag = '$_AuthPageState'; final GlobalKey _scaffoldKey = new GlobalKey(); @@ -27,7 +27,7 @@ class _AuthPageState extends State @override Widget build(BuildContext context) { - logger.info('$_TAG:build'); + logger.info('$_tag:$build'); return Scaffold( key: _scaffoldKey, diff --git a/lib/src/ui/pages/splash_page.dart b/lib/src/ui/pages/splash_page.dart index 1f5747c..7628fe6 100644 --- a/lib/src/ui/pages/splash_page.dart +++ b/lib/src/ui/pages/splash_page.dart @@ -6,7 +6,7 @@ class SplashPage extends StatelessWidget { @override Widget build(BuildContext context) { - print('$_tag:build'); + print('$_tag:$build'); return Scaffold( backgroundColor: AppColors.primaryColor, diff --git a/lib/src/ui/widgets/error_widget.dart b/lib/src/ui/widgets/error_widget.dart index 4759626..e01df75 100644 --- a/lib/src/ui/widgets/error_widget.dart +++ b/lib/src/ui/widgets/error_widget.dart @@ -119,3 +119,15 @@ class ErrorPage extends ErrorWidget { ); } } + +class ErrorApp extends ErrorWidget { + ErrorApp({Key key, @required Error error}) : super(key: key, error: error); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: ErrorPage(error: error), + color: AppColors.primaryColor, + ); + } +} diff --git a/lib/src/ui/widgets/loading_widget.dart b/lib/src/ui/widgets/loading_widget.dart index 0faf38d..59a549b 100644 --- a/lib/src/ui/widgets/loading_widget.dart +++ b/lib/src/ui/widgets/loading_widget.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; /// LoadingCard displays lines with opacity moving up and down /// Specify the number of loading lines to display @@ -51,7 +52,7 @@ class _LoadingShadowContentState extends State @override void dispose() { - _loadingOpacity.dispose(); + _loadingOpacity?.dispose(); super.dispose(); } @@ -80,7 +81,7 @@ class _LoadingShadowContentState extends State height: 13.0, width: MediaQuery.of(context).size.width / _divideFactor, - ///constraints: BoxConstraints.expand(), + //constraints: BoxConstraints.expand(), decoration: BoxDecoration( color: Colors.grey.withOpacity(_opacity.value), borderRadius: BorderRadius.circular(6.5)), @@ -172,3 +173,26 @@ class LoadingList extends StatelessWidget { ); } } + +class LoadingPage extends StatelessWidget { + LoadingPage({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center(child: CircularProgressIndicator()), + ); + } +} + +class LoadingApp extends StatelessWidget { + LoadingApp({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: LoadingPage(), + color: AppColors.primaryColor, + ); + } +} From 8d9ce3fa2b379debd39f0c45be22d195620c3f5a Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sat, 18 May 2019 22:59:01 +0200 Subject: [PATCH 05/17] [FEAT] Changed logger - Upgraded `flutter_bloc` package to `^0.13.0` - Upgraded `rxdart` package to `^0.22.0` --- lib/main.dart | 21 +- lib/src/app.dart | 6 +- .../configuration/configuration_bloc.dart | 6 +- .../configuration/configuration_state.dart | 12 + lib/src/routes.dart | 22 +- lib/src/ui/pages/account_page.dart | 4 +- lib/src/ui/pages/auth_page.dart | 4 +- .../pages/elements/profile_profile_page.dart | 4 +- lib/src/ui/pages/home_page.dart | 4 +- lib/src/ui/pages/main_page.dart | 4 +- lib/src/ui/pages/settings_page.dart | 4 +- lib/src/ui/pages/splash_page.dart | 3 +- .../elements/entry_list_profile_widget.dart | 3 +- .../elements/entry_profile_widget.dart | 1 + .../elements/group_list_profile_widget.dart | 3 +- .../elements/group_profile_widget.dart | 1 + .../widgets/elements/part_profile_widget.dart | 1 + lib/src/ui/widgets/login_form_widget.dart | 8 +- lib/src/ui/widgets/sort_dialog_widget.dart | 4 +- lib/src/utils/logger.dart | 15 -- lib/src/utils/logging_service.dart | 225 +++++++----------- lib/src/utils/utils.dart | 5 +- lib/src/utils/validators.dart | 4 +- pubspec.lock | 8 +- pubspec.yaml | 12 +- 25 files changed, 163 insertions(+), 221 deletions(-) delete mode 100644 lib/src/utils/logger.dart diff --git a/lib/main.dart b/lib/main.dart index 298eb28..2ee6b77 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,7 +7,6 @@ import 'package:social_cv_client_flutter/src/app.dart'; import 'package:social_cv_client_flutter/src/data/managers/local_configuration_manager.dart'; import 'package:social_cv_client_flutter/src/data/managers/shared_preferences_manager.dart'; import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; // TODO automatically set this to false for release builds @@ -18,8 +17,6 @@ Future main() async { /// [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); void run() async { - initLogger(package: "CV App"); - ConfigRepository configRepository = LocalConfigManager(); PreferencesRepository preferencesRepository = SharedPreferencesManager(); @@ -56,25 +53,23 @@ Future main() async { } } -/// /// Global error handler. Show stack trace -/// void globalErrorHandler(details) { - String stackTrace; + StackTrace stackTrace; if (details is FlutterErrorDetails) { if (details.exception is Error) { - stackTrace = details.stack.toString(); + stackTrace = details.stack; } } else if (details is Error) { - stackTrace = details.stackTrace.toString(); + stackTrace = details.stackTrace; } else { + Logger.fatal( + details.toString(), + errorCode: ErrorCodes.UNHANDLED_EXCEPTION, + ); throw details; } - LoggingService.fatal( - details.toString(), - errorCode: ErrorCodes.UNHANDLED_EXCEPTION, - stackTrace: stackTrace, - ); + Logger.error('${details.runtimeType}', stackTrace: stackTrace); } diff --git a/lib/src/app.dart b/lib/src/app.dart index d8a3c7e..52f336f 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -12,7 +12,7 @@ import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/splash_page.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; import 'domain/blocs/configuration/configuration.dart'; @@ -115,7 +115,7 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { @override Widget build(BuildContext context) { - logger.info('$_tag:$build'); + Logger.log('$_tag:$build'); return BlocBuilder( bloc: _appBloc, @@ -144,7 +144,7 @@ class _CVInitializedApp extends StatelessWidget { @override Widget build(BuildContext context) { - logger.info('$_tag:$build'); + Logger.log('$_tag:$build'); RepositoriesProvider repositories = RepositoriesProvider.of(context); diff --git a/lib/src/domain/blocs/configuration/configuration_bloc.dart b/lib/src/domain/blocs/configuration/configuration_bloc.dart index b5d54ee..644ab28 100644 --- a/lib/src/domain/blocs/configuration/configuration_bloc.dart +++ b/lib/src/domain/blocs/configuration/configuration_bloc.dart @@ -4,6 +4,7 @@ import 'package:social_cv_client_dart_common/repositories.dart'; import 'package:social_cv_client_flutter/src/data/managers/local_configuration_manager.dart'; import 'package:social_cv_client_flutter/src/data/managers/shared_preferences_manager.dart'; import 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class ConfigurationBloc extends Bloc { final String _tag = '$ConfigurationBloc'; @@ -61,8 +62,9 @@ class ConfigurationBloc extends Bloc { preferencesRepository: _preferencesRepository, configRepository: _configRepository, ); - } catch (error) { - print(error.runtimeType); + } catch (error, stacktrace) { + Logger.error('${error.runtimeType}', stackTrace: stacktrace); + yield ConfigFailure(error: error); } } } diff --git a/lib/src/domain/blocs/configuration/configuration_state.dart b/lib/src/domain/blocs/configuration/configuration_state.dart index 8389d38..50736bc 100644 --- a/lib/src/domain/blocs/configuration/configuration_state.dart +++ b/lib/src/domain/blocs/configuration/configuration_state.dart @@ -4,6 +4,9 @@ import 'package:social_cv_client_dart_common/repositories.dart'; abstract class ConfigurationState extends Equatable { ConfigurationState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; } class ConfigLoading extends ConfigurationState {} @@ -23,3 +26,12 @@ class ConfigLoaded extends ConfigurationState { assert(configRepository != null, 'No $ConfigRepository given'), super([cvRepository, preferencesRepository, configRepository]); } + +class ConfigFailure extends ConfigurationState { + final Error error; + + ConfigFailure({@required this.error}) : super([error]); + + @override + String toString() => '$runtimeType{ error: ${error.runtimeType} }'; +} diff --git a/lib/src/routes.dart b/lib/src/routes.dart index 8d7c1d8..c624f86 100644 --- a/lib/src/routes.dart +++ b/lib/src/routes.dart @@ -10,7 +10,7 @@ import 'package:social_cv_client_flutter/src/ui/pages/elements/profile_profile_p import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/search_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/settings_page.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class Routes { final String _tag = '$Routes'; @@ -32,13 +32,13 @@ class Routes { final PreferencesRepository preferencesRepository; void _defineRoutes() { - logger.info('$_tag:$_defineRoutes'); + Logger.log('$_tag:$_defineRoutes'); router.define( AppPaths.kPathHome, handler: Handler( handlerFunc: (BuildContext context, Map params) { - logger.info('Navigate to ${AppPaths.kPathHome}'); + Logger.log('Navigate to ${AppPaths.kPathHome}'); return MainPage(); }, ), @@ -48,7 +48,7 @@ class Routes { AppPaths.kPathAccount, handler: Handler( handlerFunc: (BuildContext context, Map params) { - logger.info('Navigate to ${AppPaths.kPathAccount}'); + Logger.log('Navigate to ${AppPaths.kPathAccount}'); return MainPage(); }, ), @@ -58,7 +58,7 @@ class Routes { AppPaths.kPathLogin, handler: Handler( handlerFunc: (BuildContext context, Map params) { - logger.info('Navigate to ${AppPaths.kPathLogin}'); + Logger.log('Navigate to ${AppPaths.kPathLogin}'); return AuthPage(); }, ), @@ -68,7 +68,7 @@ class Routes { AppPaths.kPathSettings, handler: Handler( handlerFunc: (BuildContext context, Map params) { - logger.info('Navigate to ${AppPaths.kPathSettings}'); + Logger.log('Navigate to ${AppPaths.kPathSettings}'); return SettingsPage(); }, ), @@ -78,7 +78,7 @@ class Routes { AppPaths.kPathSearch, handler: Handler( handlerFunc: (BuildContext context, Map params) { - logger.info('Navigate to ${AppPaths.kPathSearch}'); + Logger.log('Navigate to ${AppPaths.kPathSearch}'); return SearchPage(); }, @@ -91,7 +91,7 @@ class Routes { handlerFunc: (BuildContext context, Map params) { var profileId = params[AppPaths.kParamProfileId][0]; - logger.info('Navigate to ${AppPaths.kPathProfiles}/$profileId'); + Logger.log('Navigate to ${AppPaths.kPathProfiles}/$profileId'); return ProfileProfilePage(profileId: profileId); }, @@ -104,7 +104,7 @@ class Routes { handlerFunc: (BuildContext context, Map params) { var partId = params[AppPaths.kParamPartId][0]; - logger.info('Navigate to ${AppPaths.kPathParts}/$partId'); + Logger.log('Navigate to ${AppPaths.kPathParts}/$partId'); return PartProfilePage(partId: partId); }, @@ -117,7 +117,7 @@ class Routes { handlerFunc: (BuildContext context, Map params) { var groupId = params[AppPaths.kParamGroupId][0]; - logger.info('Navigate to ${AppPaths.kPathGroups}/$groupId'); + Logger.log('Navigate to ${AppPaths.kPathGroups}/$groupId'); return GroupPage(groupId: groupId); }, @@ -130,7 +130,7 @@ class Routes { handlerFunc: (BuildContext context, Map params) { var entryId = params[AppPaths.kParamEntryId][0]; - logger.info('Navigate to ${AppPaths.kPathEntries}/$entryId'); + Logger.log('Navigate to ${AppPaths.kPathEntries}/$entryId'); return EntryPage(entryId: entryId); }, diff --git a/lib/src/ui/pages/account_page.dart b/lib/src/ui/pages/account_page.dart index c77e146..0aa98d6 100644 --- a/lib/src/ui/pages/account_page.dart +++ b/lib/src/ui/pages/account_page.dart @@ -6,7 +6,7 @@ import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; class AccountPage extends StatefulWidget { @@ -24,7 +24,7 @@ class _AccountPageState extends State { @override Widget build(BuildContext context) { - logger.info('$_tag:$build'); + Logger.log('$_tag:$build'); return SafeArea( left: false, diff --git a/lib/src/ui/pages/auth_page.dart b/lib/src/ui/pages/auth_page.dart index 0cb9a01..52eaf4d 100644 --- a/lib/src/ui/pages/auth_page.dart +++ b/lib/src/ui/pages/auth_page.dart @@ -5,7 +5,7 @@ import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/login_form_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/register_form_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class AuthPage extends StatefulWidget { AuthPage({Key key}) : super(key: key); @@ -27,7 +27,7 @@ class _AuthPageState extends State @override Widget build(BuildContext context) { - logger.info('$_tag:$build'); + Logger.log('$_tag:$build'); return Scaffold( key: _scaffoldKey, diff --git a/lib/src/ui/pages/elements/profile_profile_page.dart b/lib/src/ui/pages/elements/profile_profile_page.dart index ec350d4..b29d1fb 100644 --- a/lib/src/ui/pages/elements/profile_profile_page.dart +++ b/lib/src/ui/pages/elements/profile_profile_page.dart @@ -8,7 +8,7 @@ import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_widget. import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; /// TODO : Build owner interaction with ProfileViewModel.owner @@ -31,7 +31,7 @@ class ProfileProfilePage extends ProfileWidget { class _ProfileProfilePageState extends ProfileWidgetState { @override Widget build(BuildContext context) { - logger.info('Building ProfilePage'); + Logger.log('Building ProfilePage'); return BlocBuilder( bloc: profileBloc, diff --git a/lib/src/ui/pages/home_page.dart b/lib/src/ui/pages/home_page.dart index 5314f3c..240a786 100644 --- a/lib/src/ui/pages/home_page.dart +++ b/lib/src/ui/pages/home_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class HomePage extends StatelessWidget { final String _tag = '$HomePage'; @@ -11,7 +11,7 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { - logger.info('$_tag:$build'); + Logger.log('$_tag:$build'); return SafeArea( left: false, right: false, diff --git a/lib/src/ui/pages/main_page.dart b/lib/src/ui/pages/main_page.dart index 8382984..d5eea7b 100644 --- a/lib/src/ui/pages/main_page.dart +++ b/lib/src/ui/pages/main_page.dart @@ -5,7 +5,7 @@ import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.da import 'package:social_cv_client_flutter/src/ui/pages/account_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/home_page.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/menu_button_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; class MainPage extends StatefulWidget { @@ -26,7 +26,7 @@ class _MainPageState extends State { @override Widget build(BuildContext context) { - logger.info('$_tag:$build'); + Logger.log('$_tag:$build'); return Scaffold( appBar: AppBar( diff --git a/lib/src/ui/pages/settings_page.dart b/lib/src/ui/pages/settings_page.dart index c8099c8..f85b3ec 100644 --- a/lib/src/ui/pages/settings_page.dart +++ b/lib/src/ui/pages/settings_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class SettingsPage extends StatelessWidget { const SettingsPage({ @@ -10,7 +10,7 @@ class SettingsPage extends StatelessWidget { @override Widget build(BuildContext context) { - logger.info('Building SettingsPage'); + Logger.log('Building SettingsPage'); return Scaffold( appBar: AppBar( title: Text(CVLocalizations.of(context).settingsTitle), diff --git a/lib/src/ui/pages/splash_page.dart b/lib/src/ui/pages/splash_page.dart index 7628fe6..404dc43 100644 --- a/lib/src/ui/pages/splash_page.dart +++ b/lib/src/ui/pages/splash_page.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class SplashPage extends StatelessWidget { final String _tag = 'SplashPage'; @override Widget build(BuildContext context) { - print('$_tag:$build'); + Logger.log('$_tag:$build'); return Scaffold( backgroundColor: AppColors.primaryColor, diff --git a/lib/src/ui/widgets/elements/entry_list_profile_widget.dart b/lib/src/ui/widgets/elements/entry_list_profile_widget.dart index 72a9a28..b6fc373 100644 --- a/lib/src/ui/widgets/elements/entry_list_profile_widget.dart +++ b/lib/src/ui/widgets/elements/entry_list_profile_widget.dart @@ -13,7 +13,8 @@ import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart' import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; -/// [SimpleEntryListProfile] is a dummy widget that use [entryIds] or [entries] or [entryBlocs] to create a list of [EntryProfileWidget] +/// [SimpleEntryListProfile] is a dummy widget that use [entryIds] or [entries] +/// or [entryBlocs] to create a list of [EntryProfileWidget] class SimpleEntryListProfile extends StatelessWidget { final List entryIds; final List entries; diff --git a/lib/src/ui/widgets/elements/entry_profile_widget.dart b/lib/src/ui/widgets/elements/entry_profile_widget.dart index eff8434..0c5261a 100644 --- a/lib/src/ui/widgets/elements/entry_profile_widget.dart +++ b/lib/src/ui/widgets/elements/entry_profile_widget.dart @@ -10,6 +10,7 @@ import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_widget.da import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +/// [EntryProfileWidget] is an [EntryWidget] for profile display purpose class EntryProfileWidget extends EntryWidget { EntryProfileWidget( {Key key, String entryId, EntryViewModel entry, EntryBloc entryBloc}) diff --git a/lib/src/ui/widgets/elements/group_list_profile_widget.dart b/lib/src/ui/widgets/elements/group_list_profile_widget.dart index 66096d7..62a9e98 100644 --- a/lib/src/ui/widgets/elements/group_list_profile_widget.dart +++ b/lib/src/ui/widgets/elements/group_list_profile_widget.dart @@ -13,7 +13,8 @@ import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart' import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; -/// [SimpleGroupListProfile] is a dummy widget that use [groupIds] or [groups] or [groupBlocs] to create a list of [GroupProfileWidget] +/// [SimpleGroupListProfile] is a dummy widget that use [groupIds] or [groups] +/// or [groupBlocs] to create a list of [GroupProfileWidget] class SimpleGroupListProfile extends StatelessWidget { final List groupIds; final List groups; diff --git a/lib/src/ui/widgets/elements/group_profile_widget.dart b/lib/src/ui/widgets/elements/group_profile_widget.dart index ba92f85..7faf216 100644 --- a/lib/src/ui/widgets/elements/group_profile_widget.dart +++ b/lib/src/ui/widgets/elements/group_profile_widget.dart @@ -11,6 +11,7 @@ import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_widget.da import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +/// [GroupProfileWidget] is an [GroupWidget] for profile display purpose class GroupProfileWidget extends GroupWidget { GroupProfileWidget( {Key key, String groupId, GroupViewModel group, GroupBloc groupBloc}) diff --git a/lib/src/ui/widgets/elements/part_profile_widget.dart b/lib/src/ui/widgets/elements/part_profile_widget.dart index dbda66d..9507091 100644 --- a/lib/src/ui/widgets/elements/part_profile_widget.dart +++ b/lib/src/ui/widgets/elements/part_profile_widget.dart @@ -12,6 +12,7 @@ import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +/// [PartProfileWidget] is an [PartWidget] for profile display purpose class PartProfileWidget extends PartWidget { PartProfileWidget( {Key key, String partId, PartViewModel part, PartBloc partBloc}) diff --git a/lib/src/ui/widgets/login_form_widget.dart b/lib/src/ui/widgets/login_form_widget.dart index 0b22d01..6ee3aff 100644 --- a/lib/src/ui/widgets/login_form_widget.dart +++ b/lib/src/ui/widgets/login_form_widget.dart @@ -6,7 +6,7 @@ import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class LoginFormWidget extends StatefulWidget { const LoginFormWidget({ @@ -37,7 +37,7 @@ class _LoginFormWidgetState extends State { @override Widget build(BuildContext context) { - logger.info('$_tag:$build'); + Logger.log('$_tag:$build'); return BlocBuilder( bloc: _loginBloc, @@ -239,7 +239,7 @@ class _LoginFormWidgetState extends State { Padding( padding: EdgeInsets.only(top: 10.0, right: 40.0), child: GestureDetector( - onTap: () => print('Facebook button pressed'), + onTap: () => Logger.log('Facebook button pressed'), child: Container( padding: const EdgeInsets.all(15.0), decoration: new BoxDecoration( @@ -256,7 +256,7 @@ class _LoginFormWidgetState extends State { Padding( padding: EdgeInsets.only(top: 10.0), child: GestureDetector( - onTap: () => print('Google button pressed'), + onTap: () => Logger.log('Google button pressed'), child: Container( padding: const EdgeInsets.all(15.0), decoration: new BoxDecoration( diff --git a/lib/src/ui/widgets/sort_dialog_widget.dart b/lib/src/ui/widgets/sort_dialog_widget.dart index a8fe671..48139da 100644 --- a/lib/src/ui/widgets/sort_dialog_widget.dart +++ b/lib/src/ui/widgets/sort_dialog_widget.dart @@ -4,7 +4,7 @@ import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class SortDialog extends StatefulWidget { const SortDialog({ @@ -31,7 +31,7 @@ class _SortDialogState extends State { title: Text(sortItem.title), onChanged: (SortState value) { setState(() { - logger.info('${sortItem.field} $value'); + Logger.log('${sortItem.field} $value'); sortItem.value = value; }); }, diff --git a/lib/src/utils/logger.dart b/lib/src/utils/logger.dart deleted file mode 100644 index d4cf5b7..0000000 --- a/lib/src/utils/logger.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:logging/logging.dart'; -import 'package:meta/meta.dart'; - -Logger logger; - -void initLogger({@required String package, String tag}) { - assert(package != null); - - Logger.root.level = Level.ALL; - Logger.root.onRecord.listen((LogRecord rec) { - print('${rec.time}:${rec.loggerName}: ${rec.level.name} -> ${rec.message}'); - }); - - logger = Logger(tag?.toUpperCase() ?? package.toUpperCase()); -} diff --git a/lib/src/utils/logging_service.dart b/lib/src/utils/logging_service.dart index 8e7742d..7605f1f 100644 --- a/lib/src/utils/logging_service.dart +++ b/lib/src/utils/logging_service.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:meta/meta.dart'; //////////////////////////////////////////////////////////////////////////////// @@ -16,11 +14,6 @@ enum LogType { fatal, } -enum FatalErrorHandling { - dumpPopup, - dumpPopupEmail, -} - class ErrorCodes { static const int UNHANDLED_EXCEPTION = 1; static const int LOGIN_FAILED = 20; @@ -34,36 +27,36 @@ class ErrorCodes { // // //////////////////////////////////////////////////////////////////////////////// +/// A class that define a log entry class LogEntry { + final String title; final String message; - final LogType type; - final Duration time; - final String stackTrace; final int errorCode; - final bool startup; + final Duration time; + final StackTrace stackTrace; + final LogType type; LogEntry({ + this.title, + this.message, @required this.type, @required this.time, - this.message, this.errorCode, this.stackTrace, - this.startup = false, }); @override String toString() { - String msg; - msg = message; - final err = errorCode != null ? (errorCode == ErrorCodes.UNHANDLED_EXCEPTION - ? 'Unhandled Exception\n' - : 'Error code: $errorCode. ') - : ''; - final trace = stackTrace != null ? '\n$stackTrace' : ''; - final start = startup ? '[startup]' : ''; - return '[${time.inSeconds}.${time.inMilliseconds % 1000}]$start $err$msg$trace'; + ? "Unhandled Exception\n" + : "Error code: $errorCode. ") + : ""; + final trace = stackTrace != null ? "\n$stackTrace" : ''; + + final mess = (title != null) ? '$title:$message' : message; + + return '[${time.toString()}] $err $mess ${trace ?? ''}'; } } @@ -73,83 +66,72 @@ class LogEntry { // // //////////////////////////////////////////////////////////////////////////////// -class LoggingService { +/// A class that provide logging services +class Logger { static const _LOG_LENGTH = 256; static const _ACTIONS_LOG_LENGTH = 10; - static final _instance = LoggingService._newInstance(); + static final _instance = Logger(); final DateTime _startupTime = DateTime.now(); - List _startupLog = List(); - List _messagesLog = List(_LOG_LENGTH); - List _actionsLog = List(_ACTIONS_LOG_LENGTH); - File _logFile; + List _messagesLog = List(_LOG_LENGTH); int nextLogPos = 0; int nextActionPos = 0; - static const String INFO = 'info'; - static const String ERROR = 'error'; - - final fatalErrorsHandling = FatalErrorHandling.dumpPopupEmail; - - bool startupPhase = true; + static const String INFO = "info"; + static const String ERROR = "error"; Duration get runtime { return DateTime.now().difference(_startupTime); } - // ----------------------------------------------------------------------- - // Constructor - // ----------------------------------------------------------------------- - - LoggingService._newInstance() : super(); + /// ----------------------------------------------------------------------- + /// Constructor + /// ----------------------------------------------------------------------- - factory LoggingService() { + factory Logger() { return _instance; } - // ----------------------------------------------------------------------- - // Initialization - // ----------------------------------------------------------------------- + /// ----------------------------------------------------------------------- + /// Initialization + /// ----------------------------------------------------------------------- /// /// Init local log file /// Future init() async { -// _logFile = await appStorageService.addFile('log.txt', 'log', ''); + //_logFile = await localStorageManager.getDocumentsFile("log.txt"); } - // ----------------------------------------------------------------------- - // General log - // ----------------------------------------------------------------------- + /// ----------------------------------------------------------------------- + /// General log + /// ----------------------------------------------------------------------- - /// /// Print the message and add a new log entry to the log list - /// void doLog( String message, { + String title, LogType type = LogType.log, int errorCode, - String stackTrace, + StackTrace stackTrace, }) { - if (message == null || message == '' || message == ' ') { - message = ' ----'; + if (message == null || message == "" || message == " ") { + message = " ----"; } final entry = LogEntry( + message: message, + title: title, type: type, time: runtime, - message: message, errorCode: errorCode, stackTrace: stackTrace, - startup: startupPhase, ); - if (startupPhase) { - _startupLog.add(entry); - } else { - _messagesLog[nextLogPos] = entry; - nextLogPos = (nextLogPos + 1) % _messagesLog.length; - } + + _messagesLog[nextLogPos] = entry; + nextLogPos = (nextLogPos + 1) % _messagesLog.length; + print(entry); } @@ -158,85 +140,81 @@ class LoggingService { /// static void log(String message) => _instance.doLog(message); - // ----------------------------------------------------------------------- - // Warning - // ----------------------------------------------------------------------- + /// ----------------------------------------------------------------------- + /// Warning + /// ----------------------------------------------------------------------- /// /// Do warning log. Use this when something is (probably) wrong /// but the execution will likely be continued without any serious /// errors. /// - void doWarning(String message) { - doLog(message, type: LogType.warning); + void doWarning(String message, {StackTrace stackTrace}) { + doLog(message, type: LogType.warning, stackTrace: stackTrace); } /// /// Static warning function /// - static void warning(String message) => _instance.doWarning(message); + static void warning(String message, {StackTrace stackTrace}) => + _instance.doWarning(message, stackTrace: stackTrace); - // ----------------------------------------------------------------------- - // Info - // ----------------------------------------------------------------------- + /// ----------------------------------------------------------------------- + /// Info + /// ----------------------------------------------------------------------- - /// /// Do info log. Use this for general information like startup information. - /// - void doInfo(String message, {String title, int autoCloseSeconds = -1}) { - if (title == null) { - title = INFO; - } - doLog(message); -// return doDialog(message, title, null, null, Dialog.OK, null, null, -// dialogSize, null, autoCloseSeconds); + void doInfo(String message, + {String title, int autoCloseSeconds = -1, StackTrace stackTrace}) { + doLog(message, + title: title ?? INFO, type: LogType.info, stackTrace: stackTrace); } - /// /// Static info function - /// - static void info(String message, {String title, int autoCloseSeconds = -1}) => - _instance.doInfo(message, title: title); + static void info(String message, {String title, int autoCloseSeconds = -1}) { + _instance.doInfo( + message, + title: title, + autoCloseSeconds: autoCloseSeconds, + ); + } - /// ----------------------------------------------------------------------- - /// Error - /// ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + // Error + // ----------------------------------------------------------------------- - /// /// Do error log. Something went wrong, the app will not continue the /// way it is meant to be. - /// - void doError(String message, errorCode, {autoCloseSeconds = -1}) { - final title = '$ERROR ${errorCode != 0 ? errorCode : ''}'; - - doLog(message, type: LogType.error); + void doError( + String message, + errorCode, { + String title, + autoCloseSeconds = -1, + StackTrace stackTrace, + }) { + final title = "$ERROR ${errorCode != 0 ? errorCode : ''}"; -// return doDialog(message, title, null, null, Dialog.OK, null, null, -// dialogSize, null, autoCloseSeconds); + doLog(message, title: title, type: LogType.error, stackTrace: stackTrace); } - /// /// Static error function - /// - static void error(String message, {int errorCode}) => - _instance.doError(message, errorCode); + static void error(String message, {int errorCode, StackTrace stackTrace}) => + _instance.doError(message, errorCode, stackTrace: stackTrace); - /// ----------------------------------------------------------------------- - /// Fatal - /// ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + // Fatal + // ----------------------------------------------------------------------- /// Fatal error. Something went wrong, the app will not continue the /// way it is meant to be and a mail with error details must be /// send to us. - void doFatal(String message, int errorCode, {String stackTrace}) { + void doFatal(String message, int errorCode, {StackTrace stackTrace}) { doLog(message, type: LogType.fatal, errorCode: errorCode, stackTrace: stackTrace); - - doWarning('Fatal error not handled'); } /// Static fatal function - static void fatal(String message, {int errorCode, String stackTrace}) => + static void fatal(String message, {int errorCode, StackTrace stackTrace}) => _instance.doFatal(message, errorCode, stackTrace: stackTrace); /// ----------------------------------------------------------------------- @@ -246,7 +224,7 @@ class LoggingService { static get logList => _instance._logList; get _logList { - final List log = _startupLog + _messagesLog + _actionsLog; + final List log = _messagesLog; log.removeWhere((e) => e == null); log.sort((a, b) => a.time > b.time ? 1 : -1); return log; @@ -255,41 +233,6 @@ class LoggingService { static get logString => _instance._logString; get _logString { - return _logList.join('\n'); + return _logList.join("\n"); } - -// Future _sendLog() async { -// DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); -// AndroidDeviceInfo i = await deviceInfo.androidInfo; -// final version = { -// 'baseOS': i.version.baseOS, -// 'codename': i.version.codename, -// 'incremental': i.version.incremental, -// 'previewSdkInt': i.version.previewSdkInt, -// 'release': i.version.release, -// 'sdkInt': i.version.sdkInt, -// 'securityPatch': i.version.securityPatch, -// }; -// -// final info = { -// 'android id': i.androidId, -// 'isPhysicalDevice': i.isPhysicalDevice, -// 'type': i.type, -// 'version': version, -// 'device': i.device, -// 'brand': i.brand, -// 'manufacturer': i.manufacturer, -// 'model': i.model, -// 'product': i.product, -// 'display': i.display, -// 'fingerprint': i.fingerprint, -// 'hardware': i.hardware, -// 'host': i.host, -// 'id': i.id, -// 'tags': i.tags, -// 'board': i.board, -// 'bootloader': i.bootloader, -// }; -// -// } } diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 13c0d7b..6baeee5 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -5,7 +5,8 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/ui/commons/defaults.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; + +import 'logging_service.dart'; String getInitials(String nameString) { if (nameString.isEmpty) return ' '; @@ -42,7 +43,7 @@ List> getDropDownMenuElementPerPage() { String translateError(BuildContext context, dynamic err) { CVLocalizations loc = CVLocalizations.of(context); - logger.info('Translating error'); + Logger.log('Translating error'); if (err is Exception) { if (err is FormatException) diff --git a/lib/src/utils/validators.dart b/lib/src/utils/validators.dart index fe2c7fa..bd16dac 100644 --- a/lib/src/utils/validators.dart +++ b/lib/src/utils/validators.dart @@ -24,11 +24,11 @@ class Validators { final validatePassword = StreamTransformer.fromHandlers( handleData: (password, sink) { - print('PerformPasswordValidation : $password'); + print('PerformPasswordValidation : HIDDEN'); if (password.isEmpty) { sink.addError(ValidationErrors.ERROR_LOGIN_NO_PASSWORD); } else { - print('PerformPasswordValidation : $password is correct'); + print('PerformPasswordValidation : HIDDEN is correct'); sink.add(password); } }); diff --git a/pubspec.lock b/pubspec.lock index 919a195..8ee8a58 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -28,7 +28,7 @@ packages: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "0.12.0" + version: "0.13.0" boolean_selector: dependency: transitive description: @@ -187,7 +187,7 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "0.11.1" + version: "0.13.0" flutter_localizations: dependency: "direct main" description: flutter @@ -290,7 +290,7 @@ packages: source: hosted version: "0.3.18" logging: - dependency: "direct main" + dependency: transitive description: name: logging url: "https://pub.dartlang.org" @@ -393,7 +393,7 @@ packages: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.21.0" + version: "0.22.0" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 398fb41..84e97b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,17 +18,15 @@ dependencies: flutter_localizations: sdk: flutter - logging: ^0.11.3+2 - # Routes fluro: ^1.3.7 - # Bloc Pattern - flutter_bloc: ^0.11.1 - - # Rx + # Async/Rx async: ^2.0.8 - rxdart: ^0.21.0 + rxdart: ^0.22.0 + + # Bloc Pattern + flutter_bloc: ^0.13.0 # Storage shared_preferences: ^0.4.3 From 82c917be4fb1fd544c9714b44e754284e7c49860 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sun, 19 May 2019 00:04:43 +0200 Subject: [PATCH 06/17] [FEAT] Added dependency injection - Added `provider` package for DI - Deleted RepositoriesProvider --- lib/main.dart | 44 ++++---------- lib/src/app.dart | 57 ++++++++++++++----- lib/src/data/repositories/.gitkeep | 1 + .../repositories/repositories_provider.dart | 28 --------- .../configuration/configuration_bloc.dart | 1 + .../configuration/configuration_event.dart | 4 ++ lib/src/routes.dart | 17 +----- lib/src/ui/pages/account_page.dart | 3 - lib/src/ui/pages/search_page.dart | 3 - .../widgets/elements/entry_list_widget.dart | 7 ++- lib/src/ui/widgets/elements/entry_widget.dart | 7 ++- .../widgets/elements/group_list_widget.dart | 9 ++- lib/src/ui/widgets/elements/group_widget.dart | 7 ++- .../ui/widgets/elements/part_list_widget.dart | 9 ++- lib/src/ui/widgets/elements/part_widget.dart | 7 ++- .../widgets/elements/profile_list_widget.dart | 7 ++- .../ui/widgets/elements/profile_widget.dart | 7 ++- pubspec.lock | 7 +++ pubspec.yaml | 3 + 19 files changed, 107 insertions(+), 121 deletions(-) create mode 100644 lib/src/data/repositories/.gitkeep delete mode 100644 lib/src/data/repositories/repositories_provider.dart diff --git a/lib/main.dart b/lib/main.dart index 2ee6b77..68a5505 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,54 +1,30 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/managers.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:flutter/rendering.dart'; import 'package:social_cv_client_flutter/src/app.dart'; -import 'package:social_cv_client_flutter/src/data/managers/local_configuration_manager.dart'; -import 'package:social_cv_client_flutter/src/data/managers/shared_preferences_manager.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; -// TODO automatically set this to false for release builds -const DEBUG_MODE = true; +/// TODO: automatically set this to false for release builds +const bool DEBUG_MODE = true; +const bool DEBUG_PAINT_SIZE = false; Future main() async { + String _tag = '$main'; + /// SystemChrome.setPreferredOrientations( /// [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); void run() async { - ConfigRepository configRepository = LocalConfigManager(); - PreferencesRepository preferencesRepository = SharedPreferencesManager(); - - CVApiManager cvClient = DefaultCVApiManager( - apiBaseUrl: await configRepository.getApiServerUrl(), - apiInterceptor: ApiInterceptor( - accessToken: await preferencesRepository.getAccessToken(), - refreshToken: await preferencesRepository.getRefreshToken(), - ), - ); - - CVCacheManager cacheManager = DefaultCVCacheManager(); - - CVRepository cvRepository = DefaultCloudCVRepository( - cvApiManager: cvClient, - cvCacheManager: cacheManager, - ); - - runApp( - RepositoriesProvider( - cvRepository: cvRepository, - preferencesRepository: preferencesRepository, - configRepository: configRepository, - child: ConfigWrapperApp(), - ), - ); + runApp(ConfigWrapperApp()); } + FlutterError.onError = globalErrorHandler; + if (DEBUG_MODE) { + debugPaintSizeEnabled = DEBUG_PAINT_SIZE; run(); } else { - FlutterError.onError = globalErrorHandler; runZoned(run, onError: globalErrorHandler); } } diff --git a/lib/src/app.dart b/lib/src/app.dart index 52f336f..788e285 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -2,9 +2,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration.dart'; import 'package:social_cv_client_flutter/src/routes.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; @@ -14,8 +16,6 @@ import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; -import 'domain/blocs/configuration/configuration.dart'; - class ConfigWrapperApp extends StatefulWidget { const ConfigWrapperApp({Key key}) : super(key: key); @@ -30,6 +30,8 @@ class _ConfigWrapperAppState extends State { void initState() { super.initState(); _configBloc = ConfigurationBloc(); + + /// Inform ConfigBloc that the application have been launched _configBloc.dispatch(AppLaunched()); } @@ -101,6 +103,9 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { ); _registerBloc = RegisterBloc(authenticationBloc: _authBloc); + + /// Inform AuthBloc that the application have been configured + _authBloc.dispatch(AppStarted()); } @override @@ -124,7 +129,35 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { } else if (state is AppLoading) { return LoadingApp(); } else if (state is AppInitialized) { - return _CVInitializedApp(state: state); + /// Dependency Injection of repositories and blocs + /// Use updateShouldNotify to make dependencies available in + /// `initState` methods of children widgets + return MultiProvider( + providers: [ + Provider.value( + value: _state.cvRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: _state.configRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: _state.preferencesRepository, + updateShouldNotify: (previous, current) => false, + ), + ], + child: BlocProviderTree( + blocProviders: [ + BlocProvider(bloc: _appBloc), + BlocProvider(bloc: _accountBloc), + BlocProvider(bloc: _authBloc), + BlocProvider(bloc: _loginBloc), + BlocProvider(bloc: _registerBloc), + ], + child: _InitializedApp(state: state), + ), + ); } else if (state is AppFailure) { return ErrorApp(error: state.error); } @@ -135,25 +168,19 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { } } -class _CVInitializedApp extends StatelessWidget { - final String _tag = "_CVInitializedApp"; - - const _CVInitializedApp({this.state}); +class _InitializedApp extends StatelessWidget { + final String _tag = '$_InitializedApp'; final AppInitialized state; + _InitializedApp({this.state}); + @override Widget build(BuildContext context) { Logger.log('$_tag:$build'); - RepositoriesProvider repositories = RepositoriesProvider.of(context); - ///Routes - final routes = Routes( - cvRepository: repositories.cvRepository, - preferencesRepository: repositories.preferencesRepository, - configRepository: repositories.configRepository, - ); + final routes = Routes(context); return MaterialApp( onGenerateTitle: (BuildContext context) => diff --git a/lib/src/data/repositories/.gitkeep b/lib/src/data/repositories/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/src/data/repositories/.gitkeep @@ -0,0 +1 @@ + diff --git a/lib/src/data/repositories/repositories_provider.dart b/lib/src/data/repositories/repositories_provider.dart deleted file mode 100644 index 5182987..0000000 --- a/lib/src/data/repositories/repositories_provider.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; - -class RepositoriesProvider extends InheritedWidget { - const RepositoriesProvider({ - Key key, - Widget child, - @required this.cvRepository, - @required this.configRepository, - @required this.preferencesRepository, - }) : assert(cvRepository != null, 'No $CVRepository given'), - assert(configRepository != null, 'No $ConfigRepository given'), - assert( - preferencesRepository != null, 'No $PreferencesRepository given'), - super(key: key, child: child); - - final CVRepository cvRepository; - final ConfigRepository configRepository; - final PreferencesRepository preferencesRepository; - - @override - bool updateShouldNotify(InheritedWidget oldWidget) => true; - - static RepositoriesProvider of(BuildContext context) { - return context.inheritFromWidgetOfExactType(RepositoriesProvider) - as RepositoriesProvider; - } -} diff --git a/lib/src/domain/blocs/configuration/configuration_bloc.dart b/lib/src/domain/blocs/configuration/configuration_bloc.dart index 644ab28..1d834c0 100644 --- a/lib/src/domain/blocs/configuration/configuration_bloc.dart +++ b/lib/src/domain/blocs/configuration/configuration_bloc.dart @@ -41,6 +41,7 @@ class ConfigurationBloc extends Bloc { /// Interceptors _apiInterceptor = ApiInterceptor( accessToken: await _preferencesRepository.getAccessToken(), + refreshToken: await _preferencesRepository.getRefreshToken(), ); /// Managers diff --git a/lib/src/domain/blocs/configuration/configuration_event.dart b/lib/src/domain/blocs/configuration/configuration_event.dart index 053ba50..bad7b3b 100644 --- a/lib/src/domain/blocs/configuration/configuration_event.dart +++ b/lib/src/domain/blocs/configuration/configuration_event.dart @@ -1,7 +1,11 @@ import 'package:equatable/equatable.dart'; +/// [ConfigurationEvent] that must be dispatch to [ApplicationBloc] abstract class ConfigurationEvent extends Equatable { ConfigurationEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; } class AppLaunched extends ConfigurationEvent {} diff --git a/lib/src/routes.dart b/lib/src/routes.dart index c624f86..b09bc0b 100644 --- a/lib/src/routes.dart +++ b/lib/src/routes.dart @@ -1,6 +1,5 @@ import 'package:fluro/fluro.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; import 'package:social_cv_client_flutter/src/ui/commons/paths.dart'; import 'package:social_cv_client_flutter/src/ui/pages/auth_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/elements/entry_profile_page.dart'; @@ -16,21 +15,12 @@ class Routes { final String _tag = '$Routes'; final Router router = Router(); - Routes({ - @required this.cvRepository, - @required this.configRepository, - @required this.preferencesRepository, - }) : assert(cvRepository != null, 'No CV repository given'), - assert(configRepository != null, 'No config repository given'), - assert(preferencesRepository != null, - 'No preferences repositories given') { + final BuildContext context; + + Routes(this.context) : assert(context != null, 'No $BuildContext given') { _defineRoutes(); } - final CVRepository cvRepository; - final ConfigRepository configRepository; - final PreferencesRepository preferencesRepository; - void _defineRoutes() { Logger.log('$_tag:$_defineRoutes'); @@ -79,7 +69,6 @@ class Routes { handler: Handler( handlerFunc: (BuildContext context, Map params) { Logger.log('Navigate to ${AppPaths.kPathSearch}'); - return SearchPage(); }, ), diff --git a/lib/src/ui/pages/account_page.dart b/lib/src/ui/pages/account_page.dart index 0aa98d6..93008b7 100644 --- a/lib/src/ui/pages/account_page.dart +++ b/lib/src/ui/pages/account_page.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; @@ -81,8 +80,6 @@ class _AccountPageDetailsConnectedState @override Widget build(BuildContext context) { - RepositoriesProvider _repositories = RepositoriesProvider.of(context); - return BlocBuilder( bloc: _accountBloc, builder: (BuildContext context, AccountState state) { diff --git a/lib/src/ui/pages/search_page.dart b/lib/src/ui/pages/search_page.dart index b804d0f..87c190b 100644 --- a/lib/src/ui/pages/search_page.dart +++ b/lib/src/ui/pages/search_page.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; import 'package:social_cv_client_flutter/src/ui/commons/tags.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; @@ -11,8 +10,6 @@ class SearchPage extends StatelessWidget { @override Widget build(BuildContext context) { - RepositoriesProvider _repositories = RepositoriesProvider.of(context); - return Scaffold( body: CustomScrollView( slivers: [ diff --git a/lib/src/ui/widgets/elements/entry_list_widget.dart b/lib/src/ui/widgets/elements/entry_list_widget.dart index 6127dca..8d4bfcc 100644 --- a/lib/src/ui/widgets/elements/entry_list_widget.dart +++ b/lib/src/ui/widgets/elements/entry_list_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; /// [EntryListWidget] is a clever widget that use an [EntryListBloc] /// based on [parentGroupId] or [ownerId] or [entryListBloc] @@ -35,8 +36,8 @@ abstract class ComplexEntryListState entryListBloc = widget.entryListBloc; if (entryListBloc == null) { - var provider = RepositoriesProvider.of(context); - entryListBloc = EntryListBloc(cvRepository: provider.cvRepository); + final cvRepository = Provider.of(context); + entryListBloc = EntryListBloc(cvRepository: cvRepository); entryListBloc.dispatch(EntryListInitialized( parentGroupId: widget.parentGroupId, ownerId: widget.ownerId, diff --git a/lib/src/ui/widgets/elements/entry_widget.dart b/lib/src/ui/widgets/elements/entry_widget.dart index 649fe12..7fd1bc1 100644 --- a/lib/src/ui/widgets/elements/entry_widget.dart +++ b/lib/src/ui/widgets/elements/entry_widget.dart @@ -1,7 +1,8 @@ import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; /// If [entryBloc] given we assume that it have been already initialized abstract class EntryWidget extends StatefulWidget { @@ -27,8 +28,8 @@ abstract class EntryWidgetState extends State { entryBloc = widget.entryBloc; if (entryBloc == null) { - var provider = RepositoriesProvider.of(context); - entryBloc = EntryBloc(cvRepository: provider.cvRepository); + final cvRepository = Provider.of(context); + entryBloc = EntryBloc(cvRepository: cvRepository); entryBloc.dispatch(EntryInitialized( entryId: widget.entryId, entry: widget.entry, diff --git a/lib/src/ui/widgets/elements/group_list_widget.dart b/lib/src/ui/widgets/elements/group_list_widget.dart index 172a29a..c5eca8f 100644 --- a/lib/src/ui/widgets/elements/group_list_widget.dart +++ b/lib/src/ui/widgets/elements/group_list_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; abstract class GroupListWidget extends StatefulWidget { final String parentPartId; @@ -29,10 +30,12 @@ abstract class GroupListWidgetState @override void initState() { super.initState(); + groupListBloc = widget.groupListBloc; + if (widget.groupListBloc == null) { - var provider = RepositoriesProvider.of(context); - groupListBloc = GroupListBloc(cvRepository: provider.cvRepository); + final cvRepository = Provider.of(context); + groupListBloc = GroupListBloc(cvRepository: cvRepository); groupListBloc.dispatch(GroupListInitialized( parentPartId: widget.parentPartId, ownerId: widget.ownerId, diff --git a/lib/src/ui/widgets/elements/group_widget.dart b/lib/src/ui/widgets/elements/group_widget.dart index 03d6cc1..338e3e4 100644 --- a/lib/src/ui/widgets/elements/group_widget.dart +++ b/lib/src/ui/widgets/elements/group_widget.dart @@ -1,7 +1,8 @@ import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; /// If [groupBloc] given we assume that it have been already initialized and abstract class GroupWidget extends StatefulWidget { @@ -27,8 +28,8 @@ abstract class GroupWidgetState extends State { groupBloc = widget.groupBloc; if (groupBloc == null) { - var provider = RepositoriesProvider.of(context); - groupBloc = GroupBloc(cvRepository: provider.cvRepository); + final cvRepository = Provider.of(context); + groupBloc = GroupBloc(cvRepository: cvRepository); groupBloc.dispatch(GroupInitialized( groupId: widget.groupId, group: widget.group, diff --git a/lib/src/ui/widgets/elements/part_list_widget.dart b/lib/src/ui/widgets/elements/part_list_widget.dart index d85a2c1..e47ae39 100644 --- a/lib/src/ui/widgets/elements/part_list_widget.dart +++ b/lib/src/ui/widgets/elements/part_list_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; abstract class PartListWidget extends StatefulWidget { final String parentProfileId; @@ -28,10 +29,12 @@ abstract class PartListWidgetState extends State { @override void initState() { super.initState(); + partListBloc = widget.partListBloc; + if (widget.partListBloc == null) { - var provider = RepositoriesProvider.of(context); - partListBloc = PartListBloc(cvRepository: provider.cvRepository); + final cvRepository = Provider.of(context); + partListBloc = PartListBloc(cvRepository: cvRepository); partListBloc.dispatch(PartListInitialized( parentProfileId: widget.parentProfileId, ownerId: widget.ownerId, diff --git a/lib/src/ui/widgets/elements/part_widget.dart b/lib/src/ui/widgets/elements/part_widget.dart index a7f5bd5..663324e 100644 --- a/lib/src/ui/widgets/elements/part_widget.dart +++ b/lib/src/ui/widgets/elements/part_widget.dart @@ -1,7 +1,8 @@ import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; /// If [partBloc] given we assume that it have been already initialized abstract class PartWidget extends StatefulWidget { @@ -27,8 +28,8 @@ abstract class PartWidgetState extends State { partBloc = widget.partBloc; if (partBloc == null) { - var provider = RepositoriesProvider.of(context); - partBloc = PartBloc(cvRepository: provider.cvRepository); + final cvRepository = Provider.of(context); + partBloc = PartBloc(cvRepository: cvRepository); partBloc.dispatch(PartInitialized( partId: widget.partId, part: widget.part, diff --git a/lib/src/ui/widgets/elements/profile_list_widget.dart b/lib/src/ui/widgets/elements/profile_list_widget.dart index 730bf33..2e90e8c 100644 --- a/lib/src/ui/widgets/elements/profile_list_widget.dart +++ b/lib/src/ui/widgets/elements/profile_list_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; /// If [profileListBloc] given we assume that it have been already initialized abstract class ProfileListWidget extends StatefulWidget { @@ -32,8 +33,8 @@ abstract class ProfileListWidgetState super.initState(); profileListBloc = widget.profileListBloc; if (widget.profileListBloc == null) { - var provider = RepositoriesProvider.of(context); - profileListBloc = ProfileListBloc(cvRepository: provider.cvRepository); + final cvRepository = Provider.of(context); + profileListBloc = ProfileListBloc(cvRepository: cvRepository); profileListBloc.dispatch(ProfileListInitialized( parentUserId: widget.parentUserId, ownerId: widget.ownerId, diff --git a/lib/src/ui/widgets/elements/profile_widget.dart b/lib/src/ui/widgets/elements/profile_widget.dart index 4bd9fe5..73f8c02 100644 --- a/lib/src/ui/widgets/elements/profile_widget.dart +++ b/lib/src/ui/widgets/elements/profile_widget.dart @@ -1,7 +1,8 @@ import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/repositories_provider.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; /// If [profileBloc] given we assume that it have been already initialized abstract class ProfileWidget extends StatefulWidget { @@ -27,8 +28,8 @@ abstract class ProfileWidgetState extends State { profileBloc = widget.profileBloc; if (profileBloc == null) { - var provider = RepositoriesProvider.of(context); - profileBloc = ProfileBloc(cvRepository: provider.cvRepository); + final cvRepository = Provider.of(context); + profileBloc = ProfileBloc(cvRepository: cvRepository); profileBloc.dispatch(ProfileInitialized( profileId: widget.profileId, profile: widget.profile, diff --git a/pubspec.lock b/pubspec.lock index 8ee8a58..496a904 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -366,6 +366,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" pub_semver: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 84e97b6..634f327 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,6 +28,9 @@ dependencies: # Bloc Pattern flutter_bloc: ^0.13.0 + # Dependency Injection + provider: ^2.0.1 + # Storage shared_preferences: ^0.4.3 From b0cca6f76bcf57e63d363aa6490744a234501a2e Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sun, 19 May 2019 00:25:46 +0200 Subject: [PATCH 07/17] [TASK] Moved login and register bloc scope --- lib/src/app.dart | 9 ----- lib/src/ui/widgets/login_form_widget.dart | 33 ++++++++++++----- lib/src/ui/widgets/register_form_widget.dart | 37 +++++++++++++------- 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 788e285..c6c22f6 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -97,13 +97,6 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { accountBloc: _accountBloc, ); - _loginBloc = LoginBloc( - cvRepository: _state.cvRepository, - authBloc: _authBloc, - ); - - _registerBloc = RegisterBloc(authenticationBloc: _authBloc); - /// Inform AuthBloc that the application have been configured _authBloc.dispatch(AppStarted()); } @@ -113,8 +106,6 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { _appBloc?.dispose(); _accountBloc?.dispose(); _authBloc?.dispose(); - _loginBloc?.dispose(); - _registerBloc?.dispose(); super.dispose(); } diff --git a/lib/src/ui/widgets/login_form_widget.dart b/lib/src/ui/widgets/login_form_widget.dart index 6ee3aff..d97c7a5 100644 --- a/lib/src/ui/widgets/login_form_widget.dart +++ b/lib/src/ui/widgets/login_form_widget.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; @@ -32,8 +34,28 @@ class _LoginFormWidgetState extends State { String errorText; - // TODO: Add loginbloc - LoginBloc get _loginBloc => null; + LoginBloc _loginBloc; + + @override + void initState() { + super.initState(); + + final authBloc = BlocProvider.of(context); + final cvRepository = Provider.of(context); + + _loginBloc = LoginBloc( + authenticationBloc: authBloc, + cvRepository: cvRepository, + ); + } + + @override + void dispose() { + myFocusNodeEmailLogin.dispose(); + myFocusNodePasswordLogin.dispose(); + _loginBloc?.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { @@ -279,13 +301,6 @@ class _LoginFormWidgetState extends State { ); } - @override - void dispose() { - myFocusNodeEmailLogin.dispose(); - myFocusNodePasswordLogin.dispose(); - super.dispose(); - } - void _toggleLogin() { setState(() { _obscureTextLogin = !_obscureTextLogin; diff --git a/lib/src/ui/widgets/register_form_widget.dart b/lib/src/ui/widgets/register_form_widget.dart index 44dbf41..c603a53 100644 --- a/lib/src/ui/widgets/register_form_widget.dart +++ b/lib/src/ui/widgets/register_form_widget.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; @@ -27,13 +29,32 @@ class _RegisterFormWidgetState extends State { TextEditingController signupConfirmPasswordController = new TextEditingController(); - // TODO : add register bloc - RegisterBloc get _registerBloc => null; + RegisterBloc _registerBloc; @override - Widget build(BuildContext context) { - RegisterBloc _registerBloc = BlocProvider.of(context); + void initState() { + super.initState(); + + final authBloc = BlocProvider.of(context); + final cvRepository = Provider.of(context); + + _registerBloc = RegisterBloc( + cvRepository: cvRepository, + authenticationBloc: authBloc, + ); + } + + @override + void dispose() { + myFocusNodeFirstName?.dispose(); + myFocusNodeEmail?.dispose(); + myFocusNodePassword?.dispose(); + _registerBloc?.dispose(); + super.dispose(); + } + @override + Widget build(BuildContext context) { return BlocBuilder( bloc: _registerBloc, builder: (BuildContext context, RegisterState state) { @@ -272,14 +293,6 @@ class _RegisterFormWidgetState extends State { }); } - @override - void dispose() { - myFocusNodeFirstName.dispose(); - myFocusNodeEmail.dispose(); - myFocusNodePassword.dispose(); - super.dispose(); - } - void _toggleSignup() { setState(() { _obscureTextSignup = !_obscureTextSignup; From 26d1beb6f31f072e0c9e8931efac780477e26d52 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sun, 19 May 2019 01:22:03 +0200 Subject: [PATCH 08/17] [FEAT] Added Config fetch from bundled file --- .../data/managers/asset_config_manager.dart | 40 +++++++++++++++++++ .../managers/local_configuration_manager.dart | 36 ----------------- .../configuration/configuration_bloc.dart | 14 ++++--- 3 files changed, 48 insertions(+), 42 deletions(-) create mode 100644 lib/src/data/managers/asset_config_manager.dart delete mode 100644 lib/src/data/managers/local_configuration_manager.dart diff --git a/lib/src/data/managers/asset_config_manager.dart b/lib/src/data/managers/asset_config_manager.dart new file mode 100644 index 0000000..dd15f2f --- /dev/null +++ b/lib/src/data/managers/asset_config_manager.dart @@ -0,0 +1,40 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:social_cv_client_dart_common/models.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; + +/// From https://medium.com/@sokrato/storing-your-secret-keys-in-flutter-c0b9af1c0f69 +class AssetConfigManager implements ConfigRepository { + static const _configPath = 'config.json'; + + ConfigDataModel _config; + + Future _load() async { + await rootBundle.loadStructuredData( + _configPath, + (jsonStr) { + Map jsonMap = json.decode(jsonStr); + _config = ConfigDataModel.fromJson(jsonMap); + }, + ); + } + + @override + Future getApiServerUrl() async { + if (_config == null) await _load(); + return Future.value(_config.apiServerUrl); + } + + @override + Future getClientId() async { + if (_config == null) await _load(); + return Future.value(_config.clientId); + } + + @override + Future getClientSecret() async { + if (_config == null) await _load(); + return Future.value(_config.clientSecret); + } +} diff --git a/lib/src/data/managers/local_configuration_manager.dart b/lib/src/data/managers/local_configuration_manager.dart deleted file mode 100644 index 91f56ea..0000000 --- a/lib/src/data/managers/local_configuration_manager.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'dart:async' show Future; -import 'dart:convert' show json; - -import 'package:flutter/services.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; - -/// From https://medium.com/@sokrato/storing-your-secret-keys-in-flutter-c0b9af1c0f69 - -class LocalConfigManager implements ConfigRepository { - static const secretPath = 'config.json'; - - String _apiServerUrl; - String _clientId; - String _clientSecret; - - LocalConfigManager() { - rootBundle.loadStructuredData( - secretPath, - (jsonStr) { - Map jsonMap = json.decode(jsonStr); - _apiServerUrl = jsonMap["serverUrl"]; - _clientId = jsonMap["clientId"]; - _clientSecret = jsonMap["clientSecret"]; - }, - ); - } - - @override - Future getApiServerUrl() => Future.value(_apiServerUrl); - - @override - Future getClientId() => Future.value(_clientId); - - @override - Future getClientSecret() => Future.value(_clientSecret); -} diff --git a/lib/src/domain/blocs/configuration/configuration_bloc.dart b/lib/src/domain/blocs/configuration/configuration_bloc.dart index 1d834c0..6154f18 100644 --- a/lib/src/domain/blocs/configuration/configuration_bloc.dart +++ b/lib/src/domain/blocs/configuration/configuration_bloc.dart @@ -1,7 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:social_cv_client_dart_common/managers.dart'; import 'package:social_cv_client_dart_common/repositories.dart'; -import 'package:social_cv_client_flutter/src/data/managers/local_configuration_manager.dart'; +import 'package:social_cv_client_flutter/src/data/managers/asset_config_manager.dart'; import 'package:social_cv_client_flutter/src/data/managers/shared_preferences_manager.dart'; import 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; @@ -36,23 +36,25 @@ class ConfigurationBloc extends Bloc { Stream _mapAppLaunchedEventToState() async* { try { yield ConfigLoading(); + + /// Preferences managers and repositories _preferencesRepository = SharedPreferencesManager(); - /// Interceptors + /// Config managers and repositories + _configRepository = AssetConfigManager(); + + /// CV Managers and repositories _apiInterceptor = ApiInterceptor( accessToken: await _preferencesRepository.getAccessToken(), refreshToken: await _preferencesRepository.getRefreshToken(), ); - - /// Managers _cvApiManager = DefaultCVApiManager( apiInterceptor: _apiInterceptor, apiBaseUrl: await _configRepository.getApiServerUrl(), ); + _cvCacheManager = DefaultCVCacheManager(); - /// Repositories - _configRepository = LocalConfigManager(); _cvRepository = DefaultCloudCVRepository( cvApiManager: _cvApiManager, cvCacheManager: _cvCacheManager, From 2175124f7584c5c740c614c6487bd4ce6a158db0 Mon Sep 17 00:00:00 2001 From: Axel Le Bot Date: Sun, 19 May 2019 02:19:17 +0200 Subject: [PATCH 09/17] [FIX] Fixed Logger --- lib/main.dart | 2 +- lib/src/app.dart | 7 +++++-- lib/src/utils/logging_service.dart | 3 ++- lib/src/utils/utils.dart | 11 +---------- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 68a5505..a4e3ca3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -41,7 +41,7 @@ void globalErrorHandler(details) { stackTrace = details.stackTrace; } else { Logger.fatal( - details.toString(), + '${details.runtimeType}', errorCode: ErrorCodes.UNHANDLED_EXCEPTION, ); throw details; diff --git a/lib/src/app.dart b/lib/src/app.dart index c6c22f6..8ed2da5 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -52,7 +52,10 @@ class _ConfigWrapperAppState extends State { color: AppColors.primaryColor, ); } else if (state is ConfigLoaded) { - return _ConfiguredApp(state); + return BlocProvider( + bloc: _configBloc, + child: _ConfiguredApp(state: state), + ); } return ErrorApp(error: NotImplementedYetError()); }, @@ -63,7 +66,7 @@ class _ConfigWrapperAppState extends State { class _ConfiguredApp extends StatefulWidget { final ConfigLoaded state; - _ConfiguredApp(this.state); + _ConfiguredApp({Key key, @required this.state}) : super(key: key); @override State createState() => _ConfiguredAppState(); diff --git a/lib/src/utils/logging_service.dart b/lib/src/utils/logging_service.dart index 7605f1f..45e24d8 100644 --- a/lib/src/utils/logging_service.dart +++ b/lib/src/utils/logging_service.dart @@ -70,7 +70,7 @@ class LogEntry { class Logger { static const _LOG_LENGTH = 256; static const _ACTIONS_LOG_LENGTH = 10; - static final _instance = Logger(); + static final _instance = Logger._newInstance(); final DateTime _startupTime = DateTime.now(); @@ -89,6 +89,7 @@ class Logger { /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- + Logger._newInstance() : super(); factory Logger() { return _instance; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 6baeee5..5b86ff1 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/ui/commons/defaults.dart'; @@ -19,14 +18,6 @@ String getInitials(String nameString) { return initials; } -void printException(dynamic e, StackTrace stackTrace, [String message]) { - if (message != null) { - debugPrint('$message: $e'); - } else { - debugPrint(e.toString()); - } -} - List> getDropDownMenuElementPerPage() { List _values = [ kCVItemsPerPage1, @@ -150,5 +141,5 @@ String translateError(BuildContext context, dynamic err) { } ///Default - return err.toString(); + return '${err.runtimeType}'; } From 82c737037d0c0499b01cf21c54b374c52e7dc3a9 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sun, 19 May 2019 17:52:35 +0200 Subject: [PATCH 10/17] [FEAT] Changed repositories and managers - Added config repository - Added all preferences repositories (app/auth) - Added assets manager - Edited shared preferences managers - Added SplashApp and edited SplashScreen --- lib/src/app.dart | 35 ++-- .../app_shared_preferences_manager.dart | 75 +++++++++ .../auth_shared_preferences_manager.dart | 91 +++++++++++ ...anager.dart => config_assets_manager.dart} | 11 +- .../managers/shared_preferences_manager.dart | 154 ------------------ lib/src/data/models/config_model.dart | 29 ++++ lib/src/data/repositories/.gitkeep | 1 - .../local_app_preferences_repository.dart | 63 +++++++ .../local_auth_preferences_repository.dart | 80 +++++++++ .../repositories/local_config_repository.dart | 28 ++++ .../configuration/configuration_bloc.dart | 50 +++--- .../configuration/configuration_state.dart | 31 +++- lib/src/ui/widgets/error_widget.dart | 13 +- .../splash_widget.dart} | 14 +- .../ui/widgets/theme_switch_tile_widget.dart | 32 ++-- pubspec.lock | 18 +- pubspec.yaml | 4 + 17 files changed, 501 insertions(+), 228 deletions(-) create mode 100644 lib/src/data/managers/app_shared_preferences_manager.dart create mode 100644 lib/src/data/managers/auth_shared_preferences_manager.dart rename lib/src/data/managers/{asset_config_manager.dart => config_assets_manager.dart} (80%) delete mode 100644 lib/src/data/managers/shared_preferences_manager.dart create mode 100644 lib/src/data/models/config_model.dart delete mode 100644 lib/src/data/repositories/.gitkeep create mode 100644 lib/src/data/repositories/local_app_preferences_repository.dart create mode 100644 lib/src/data/repositories/local_auth_preferences_repository.dart create mode 100644 lib/src/data/repositories/local_config_repository.dart rename lib/src/ui/{pages/splash_page.dart => widgets/splash_widget.dart} (70%) diff --git a/lib/src/app.dart b/lib/src/app.dart index 8ed2da5..2ef4e4a 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -11,9 +11,9 @@ import 'package:social_cv_client_flutter/src/routes.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/splash_page.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/splash_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class ConfigWrapperApp extends StatefulWidget { @@ -47,10 +47,7 @@ class _ConfigWrapperAppState extends State { bloc: _configBloc, builder: (BuildContext context, ConfigurationState state) { if (state is ConfigLoading) { - return WidgetsApp( - home: SplashPage(), - color: AppColors.primaryColor, - ); + return SplashApp(); } else if (state is ConfigLoaded) { return BlocProvider( bloc: _configBloc, @@ -78,29 +75,32 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { AppBloc _appBloc; AccountBloc _accountBloc; AuthenticationBloc _authBloc; - LoginBloc _loginBloc; - RegisterBloc _registerBloc; ConfigLoaded get _state => widget.state; @override void initState() { super.initState(); - _appBloc = AppBloc(preferencesRepository: _state.preferencesRepository); + _appBloc = AppBloc( + appPreferencesRepository: _state.appPreferencesRepository, + ); _accountBloc = AccountBloc( cvRepository: _state.cvRepository, - preferencesRepository: _state.preferencesRepository, + appPreferencesRepository: _state.appPreferencesRepository, ); _authBloc = AuthenticationBloc( cvRepository: _state.cvRepository, - preferencesRepository: _state.preferencesRepository, + authPreferencesRepository: _state.authPreferencesRepository, configRepository: _state.configRepository, accountBloc: _accountBloc, ); - /// Inform AuthBloc that the application have been configured + /// Inform AppBloc that the application just started + _appBloc.dispatch(AppConfigured()); + + /// Inform AuthBloc that the application just started _authBloc.dispatch(AppStarted()); } @@ -119,8 +119,7 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { return BlocBuilder( bloc: _appBloc, builder: (BuildContext context, AppState state) { - if (state is AppUninitialized) { - } else if (state is AppLoading) { + if (state is AppLoading) { return LoadingApp(); } else if (state is AppInitialized) { /// Dependency Injection of repositories and blocs @@ -136,8 +135,12 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { value: _state.configRepository, updateShouldNotify: (previous, current) => false, ), - Provider.value( - value: _state.preferencesRepository, + Provider.value( + value: _state.authPreferencesRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: _state.appPreferencesRepository, updateShouldNotify: (previous, current) => false, ), ], @@ -146,8 +149,6 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { BlocProvider(bloc: _appBloc), BlocProvider(bloc: _accountBloc), BlocProvider(bloc: _authBloc), - BlocProvider(bloc: _loginBloc), - BlocProvider(bloc: _registerBloc), ], child: _InitializedApp(state: state), ), diff --git a/lib/src/data/managers/app_shared_preferences_manager.dart b/lib/src/data/managers/app_shared_preferences_manager.dart new file mode 100644 index 0000000..bdd1138 --- /dev/null +++ b/lib/src/data/managers/app_shared_preferences_manager.dart @@ -0,0 +1,75 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +/// One of the possible Implementation of PreferencesService Interface +class AppSharedPreferencesManager { + final String _keyUserId = 'USER_ID'; + final String _keyUserEmail = 'USER_EMAIL'; + final String _keyAppTheme = 'APP_THEME'; + + Future get _prefs => SharedPreferences.getInstance(); + + AppSharedPreferencesManager(); + + /// ---------------------------------------------------------- + /// ------------------------- User --------------------------- + /// ---------------------------------------------------------- + + Future setUserId(String userId) async { + final prefs = await _prefs; + return prefs.setString(_keyUserId, userId); + } + + Future getUserId() async { + final prefs = await _prefs; + return prefs.getString(_keyUserId); + } + + Future deleteUserId() async { + final prefs = await _prefs; + return prefs.remove(_keyUserId); + } + + Future setUserEmail(String userEmail) async { + final prefs = await _prefs; + return prefs.setString(_keyUserEmail, userEmail); + } + + Future getUserEmail() async { + final prefs = await _prefs; + return prefs.getString(_keyUserEmail); + } + + Future deleteUserEmail() async { + final prefs = await _prefs; + return prefs.remove(_keyUserEmail); + } + + /// ---------------------------------------------------------- + /// ------------------------- Theme -------------------------- + /// ---------------------------------------------------------- + + Future getAppTheme() async { + final SharedPreferences prefs = await _prefs; + return prefs.getString(_keyAppTheme); + } + + Future setAppTheme(String theme) async { + final SharedPreferences prefs = await _prefs; + return prefs.setString(_keyAppTheme, theme); + } + + Future deleteAppTheme() async { + final SharedPreferences prefs = await _prefs; + return prefs.remove(_keyAppTheme); + } + + /// ---------------------------------------------------------- + /// -------------------------- All --------------------------- + /// ---------------------------------------------------------- + + Future deleteAll() async { + await this.deleteUserId(); + await this.deleteUserEmail(); + await this.deleteAppTheme(); + } +} diff --git a/lib/src/data/managers/auth_shared_preferences_manager.dart b/lib/src/data/managers/auth_shared_preferences_manager.dart new file mode 100644 index 0000000..4c5b203 --- /dev/null +++ b/lib/src/data/managers/auth_shared_preferences_manager.dart @@ -0,0 +1,91 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +/// One of the possible Implementation of PreferencesService Interface +class AuthSharedPreferencesManager { + final String _keyOAuthAccessToken = 'OAUTH_ACCESS_TOKEN'; + final String _keyOAuthAccessTokenExpiration = 'OAUTH_ACCESS_TOKEN_EXPIRATION'; + final String _keyOAuthRefreshToken = 'OAUTH_REFRESH_TOKEN'; + final String _keyOAuthRefreshTokenExpiration = + 'OAUTH_REFRESH_TOKEN_EXPIRATION'; + final String _keyAuthConnected = 'AUTH_CONNECTED'; + + Future get _prefs => SharedPreferences.getInstance(); + + AuthSharedPreferencesManager(); + + /// ---------------------------------------------------------- + /// ------------------------- Tokens ------------------------- + /// ---------------------------------------------------------- + + Future getAccessToken() async { + final prefs = await _prefs; + return prefs.getString(_keyOAuthAccessToken) ?? ''; + } + + Future setAccessToken(String accessToken) async { + final prefs = await _prefs; + return prefs.setString(_keyOAuthAccessToken, accessToken); + } + + Future deleteAccessToken() async { + final prefs = await _prefs; + return prefs.remove(_keyOAuthAccessToken); + } + + Future getAccessTokenExpiration() async { + final prefs = await _prefs; + return prefs.getInt(_keyOAuthAccessTokenExpiration) ?? ''; + } + + Future setAccessTokenExpiration(int accessTokenExpiration) async { + final prefs = await _prefs; + return prefs.setInt(_keyOAuthAccessTokenExpiration, accessTokenExpiration); + } + + Future deleteAccessTokenExpiration() async { + final prefs = await _prefs; + return prefs.remove(_keyOAuthAccessTokenExpiration); + } + + Future getRefreshToken() async { + final prefs = await _prefs; + return prefs.getString(_keyOAuthRefreshToken) ?? ''; + } + + Future setRefreshToken(String refreshToken) async { + final prefs = await _prefs; + return prefs.setString(_keyOAuthRefreshToken, refreshToken); + } + + Future deleteRefreshToken() async { + final prefs = await _prefs; + return prefs.remove(_keyOAuthRefreshToken); + } + + Future getRefreshTokenExpiration() async { + final prefs = await _prefs; + return prefs.getString(_keyOAuthRefreshTokenExpiration) ?? ''; + } + + Future setRefreshTokenExpiration(int refreshTokenExpiration) async { + final prefs = await _prefs; + return prefs.setInt( + _keyOAuthRefreshTokenExpiration, refreshTokenExpiration); + } + + Future deleteRefreshTokenExpiration() async { + final prefs = await _prefs; + return prefs.remove(_keyOAuthRefreshTokenExpiration); + } + + /// ---------------------------------------------------------- + /// -------------------------- All --------------------------- + /// ---------------------------------------------------------- + + Future deleteAll() async { + await this.deleteAccessToken(); + await this.deleteAccessTokenExpiration(); + await this.deleteRefreshToken(); + await this.deleteRefreshTokenExpiration(); + } +} diff --git a/lib/src/data/managers/asset_config_manager.dart b/lib/src/data/managers/config_assets_manager.dart similarity index 80% rename from lib/src/data/managers/asset_config_manager.dart rename to lib/src/data/managers/config_assets_manager.dart index dd15f2f..6b30d9b 100644 --- a/lib/src/data/managers/asset_config_manager.dart +++ b/lib/src/data/managers/config_assets_manager.dart @@ -1,15 +1,15 @@ import 'dart:convert'; import 'package:flutter/services.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/src/data/models/config_model.dart'; /// From https://medium.com/@sokrato/storing-your-secret-keys-in-flutter-c0b9af1c0f69 -class AssetConfigManager implements ConfigRepository { +class ConfigAssetsManager { static const _configPath = 'config.json'; - ConfigDataModel _config; + ConfigAssetsManager(); + Future _load() async { await rootBundle.loadStructuredData( _configPath, @@ -20,19 +20,16 @@ class AssetConfigManager implements ConfigRepository { ); } - @override Future getApiServerUrl() async { if (_config == null) await _load(); return Future.value(_config.apiServerUrl); } - @override Future getClientId() async { if (_config == null) await _load(); return Future.value(_config.clientId); } - @override Future getClientSecret() async { if (_config == null) await _load(); return Future.value(_config.clientSecret); diff --git a/lib/src/data/managers/shared_preferences_manager.dart b/lib/src/data/managers/shared_preferences_manager.dart deleted file mode 100644 index bc38ab7..0000000 --- a/lib/src/data/managers/shared_preferences_manager.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; - -class SharedPreferencesManager implements PreferencesRepository { - static const String KEY_OAUTH_ACCESS_TOKEN = 'OAUTH_ACCESS_TOKEN'; - static const String KEY_OAUTH_ACCESS_TOKEN_EXPIRATION = - 'OAUTH_ACCESS_TOKEN_EXPIRATION'; - static const String KEY_OAUTH_REFRESH_TOKEN = 'OAUTH_REFRESH_TOKEN'; - static const String KEY_OAUTH_REFRESH_TOKEN_EXPIRATION = - 'OAUTH_REFRESH_TOKEN_EXPIRATION'; - static const String KEY_AUTH_CONNECTED = 'AUTH_CONNECTED'; - static const String KEY_USER_ID = 'USER_ID'; - static const String KEY_APP_THEME = 'APP_THEME'; - - static Future get _prefs => - SharedPreferences.getInstance(); - - /// ---------------------------------------------------------- - /// ------------------------- Tokens ------------------------- - /// ---------------------------------------------------------- - - Future getAccessToken() async { - final SharedPreferences prefs = await _prefs; - return prefs.getString(KEY_OAUTH_ACCESS_TOKEN) ?? ''; - } - - Future setAccessToken(String accessToken) async { - final SharedPreferences prefs = await _prefs; - return prefs.setString(KEY_OAUTH_ACCESS_TOKEN, accessToken); - } - - Future deleteAccessToken() async { - final SharedPreferences prefs = await _prefs; - return prefs.remove(KEY_OAUTH_ACCESS_TOKEN); - } - - Future getAccessTokenExpiration() async { - final SharedPreferences prefs = await _prefs; - return prefs.getInt(KEY_OAUTH_ACCESS_TOKEN_EXPIRATION) ?? ''; - } - - Future setAccessTokenExpiration(int accessTokenExpiration) async { - final SharedPreferences prefs = await _prefs; - return prefs.setInt( - KEY_OAUTH_ACCESS_TOKEN_EXPIRATION, accessTokenExpiration); - } - - Future deleteAccessTokenExpiration() async { - final SharedPreferences prefs = await _prefs; - return prefs.remove(KEY_OAUTH_ACCESS_TOKEN_EXPIRATION); - } - - Future getRefreshToken() async { - final SharedPreferences prefs = await _prefs; - return prefs.getString(KEY_OAUTH_REFRESH_TOKEN) ?? ''; - } - - Future setRefreshToken(String refreshToken) async { - final SharedPreferences prefs = await _prefs; - return prefs.setString(KEY_OAUTH_REFRESH_TOKEN, refreshToken); - } - - Future deleteRefreshToken() async { - final SharedPreferences prefs = await _prefs; - return prefs.remove(KEY_OAUTH_REFRESH_TOKEN); - } - - Future getRefreshTokenExpiration() async { - final SharedPreferences prefs = await _prefs; - return prefs.getString(KEY_OAUTH_REFRESH_TOKEN_EXPIRATION) ?? ''; - } - - Future setRefreshTokenExpiration(int refreshTokenExpiration) async { - final SharedPreferences prefs = await _prefs; - return prefs.setInt( - KEY_OAUTH_REFRESH_TOKEN_EXPIRATION, refreshTokenExpiration); - } - - Future deleteRefreshTokenExpiration() async { - final SharedPreferences prefs = await _prefs; - return prefs.remove(KEY_OAUTH_REFRESH_TOKEN_EXPIRATION); - } - - /// ---------------------------------------------------------- - /// ----------------------- Connected ------------------------ - /// ---------------------------------------------------------- - - Future isAuthConnected() async { - final SharedPreferences prefs = await _prefs; - return prefs.getBool(KEY_AUTH_CONNECTED); - } - - Future setAuthConnected(bool connected) async { - final SharedPreferences prefs = await _prefs; - return prefs.setBool(KEY_AUTH_CONNECTED, connected); - } - - Future deleteAuthConnected() async { - final SharedPreferences prefs = await _prefs; - return prefs.remove(KEY_AUTH_CONNECTED); - } - - /// ---------------------------------------------------------- - /// ------------------------- User --------------------------- - /// ---------------------------------------------------------- - - Future setUserId(String userId) async { - final SharedPreferences prefs = await _prefs; - return prefs.setString(KEY_USER_ID, userId); - } - - Future getUserId() async { - final SharedPreferences prefs = await _prefs; - return prefs.getString(KEY_USER_ID); - } - - Future deleteUserId() async { - final SharedPreferences prefs = await _prefs; - return prefs.remove(KEY_USER_ID); - } - - /// ---------------------------------------------------------- - /// ------------------------- Theme -------------------------- - /// ---------------------------------------------------------- - - Future getAppTheme() async { - final SharedPreferences prefs = await _prefs; - return prefs.getString(KEY_APP_THEME); - } - - Future setAppTheme(String theme) async { - final SharedPreferences prefs = await _prefs; - return prefs.setString(KEY_APP_THEME, theme); - } - - Future deleteAppTheme() async { - final SharedPreferences prefs = await _prefs; - return prefs.remove(KEY_APP_THEME); - } - - /// ---------------------------------------------------------- - /// -------------------------- All --------------------------- - /// ---------------------------------------------------------- - - Future deleteAll() async { - await this.deleteAccessToken(); - await this.deleteAccessTokenExpiration(); - await this.deleteRefreshToken(); - await this.deleteRefreshTokenExpiration(); - await this.deleteAuthConnected(); - await this.deleteUserId(); - await this.deleteAppTheme(); - } -} diff --git a/lib/src/data/models/config_model.dart b/lib/src/data/models/config_model.dart new file mode 100644 index 0000000..ede4b70 --- /dev/null +++ b/lib/src/data/models/config_model.dart @@ -0,0 +1,29 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'config_model.g.dart'; + +@JsonSerializable() +class ConfigDataModel { + @JsonKey(name: 'apiServerUrl') + String apiServerUrl; + @JsonKey(name: 'clientId') + String clientId; + @JsonKey(name: 'clientSecret') + String clientSecret; + + ConfigDataModel({ + @required this.apiServerUrl, + @required this.clientId, + @required this.clientSecret, + }) : super(); + + factory ConfigDataModel.fromJson(Map json) => + _$ConfigDataModelFromJson(json); + + Map toJson() => _$ConfigDataModelToJson(this); + + @override + String toString() => + '$runtimeType{ apiServerUrl: $apiServerUrl, clientId: $clientId, clientSecret: $clientSecret }'; +} diff --git a/lib/src/data/repositories/.gitkeep b/lib/src/data/repositories/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/lib/src/data/repositories/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/src/data/repositories/local_app_preferences_repository.dart b/lib/src/data/repositories/local_app_preferences_repository.dart new file mode 100644 index 0000000..c096cfa --- /dev/null +++ b/lib/src/data/repositories/local_app_preferences_repository.dart @@ -0,0 +1,63 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/src/data/managers/app_shared_preferences_manager.dart'; + +class LocalAppPreferencesRepository implements AppPreferencesRepository { + final AppSharedPreferencesManager appSharedPreferencesManager; + + LocalAppPreferencesRepository({@required this.appSharedPreferencesManager}) + : assert( + appSharedPreferencesManager != null, + 'No $AppSharedPreferencesManager given', + ); + + @override + Future deleteAll() async { + return await appSharedPreferencesManager.deleteAll(); + } + + @override + Future deleteAppTheme() async { + return await appSharedPreferencesManager.deleteAppTheme(); + } + + @override + Future deleteUserEmail() async { + return await appSharedPreferencesManager.deleteUserEmail(); + } + + @override + Future deleteUserId() async { + return await appSharedPreferencesManager.deleteUserId(); + } + + @override + Future getAppTheme() async { + return await appSharedPreferencesManager.getAppTheme(); + } + + @override + Future getUserEmail() async { + return await appSharedPreferencesManager.getUserEmail(); + } + + @override + Future getUserId() async { + return await appSharedPreferencesManager.getUserId(); + } + + @override + Future setAppTheme(String theme) async { + return await appSharedPreferencesManager.setAppTheme(theme); + } + + @override + Future setUserEmail(String userEmail) async { + return await appSharedPreferencesManager.setUserEmail(userEmail); + } + + @override + Future setUserId(String userId) async { + return await appSharedPreferencesManager.setUserId(userId); + } +} diff --git a/lib/src/data/repositories/local_auth_preferences_repository.dart b/lib/src/data/repositories/local_auth_preferences_repository.dart new file mode 100644 index 0000000..9c0ccb8 --- /dev/null +++ b/lib/src/data/repositories/local_auth_preferences_repository.dart @@ -0,0 +1,80 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/src/data/managers/auth_shared_preferences_manager.dart'; + +class LocalAuthPreferencesRepository implements AuthPreferencesRepository { + final AuthSharedPreferencesManager authSharedPreferencesManager; + + LocalAuthPreferencesRepository({@required this.authSharedPreferencesManager}) + : assert( + authSharedPreferencesManager != null, + 'No $AuthSharedPreferencesManager given', + ); + + @override + Future deleteAccessToken() async { + return await authSharedPreferencesManager.deleteAccessToken(); + } + + @override + Future deleteAccessTokenExpiration() async { + return await authSharedPreferencesManager.deleteAccessTokenExpiration(); + } + + @override + Future deleteRefreshToken() async { + return await authSharedPreferencesManager.deleteRefreshToken(); + } + + @override + Future deleteRefreshTokenExpiration() async { + return await authSharedPreferencesManager.deleteRefreshTokenExpiration(); + } + + @override + Future getAccessToken() async { + return await authSharedPreferencesManager.getAccessToken(); + } + + @override + Future getAccessTokenExpiration() async { + return await authSharedPreferencesManager.getAccessTokenExpiration(); + } + + @override + Future getRefreshToken() async { + return await authSharedPreferencesManager.getRefreshToken(); + } + + @override + Future getRefreshTokenExpiration() async { + return await authSharedPreferencesManager.getRefreshTokenExpiration(); + } + + @override + Future setAccessToken(String accessToken) async { + return await authSharedPreferencesManager.setAccessToken(accessToken); + } + + @override + Future setAccessTokenExpiration(int accessTokenExpiration) async { + return await authSharedPreferencesManager + .setAccessTokenExpiration(accessTokenExpiration); + } + + @override + Future setRefreshToken(String refreshToken) async { + return await authSharedPreferencesManager.setRefreshToken(refreshToken); + } + + @override + Future setRefreshTokenExpiration(int refreshTokenExpiration) async { + return await authSharedPreferencesManager + .setRefreshTokenExpiration(refreshTokenExpiration); + } + + @override + Future deleteAll() async { + return await authSharedPreferencesManager.deleteAll(); + } +} diff --git a/lib/src/data/repositories/local_config_repository.dart b/lib/src/data/repositories/local_config_repository.dart new file mode 100644 index 0000000..b0d5e7c --- /dev/null +++ b/lib/src/data/repositories/local_config_repository.dart @@ -0,0 +1,28 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/src/data/managers/config_assets_manager.dart'; + +class LocalConfigRepository implements ConfigRepository { + final ConfigAssetsManager configAssetsManager; + + LocalConfigRepository({@required this.configAssetsManager}) + : assert( + configAssetsManager != null, + 'No $LocalConfigRepository given', + ); + + @override + Future getApiServerUrl() async { + return await configAssetsManager.getApiServerUrl(); + } + + @override + Future getClientId() async { + return await configAssetsManager.getClientId(); + } + + @override + Future getClientSecret() async { + return await configAssetsManager.getClientSecret(); + } +} diff --git a/lib/src/domain/blocs/configuration/configuration_bloc.dart b/lib/src/domain/blocs/configuration/configuration_bloc.dart index 6154f18..7dc9bf8 100644 --- a/lib/src/domain/blocs/configuration/configuration_bloc.dart +++ b/lib/src/domain/blocs/configuration/configuration_bloc.dart @@ -1,8 +1,12 @@ import 'package:bloc/bloc.dart'; import 'package:social_cv_client_dart_common/managers.dart'; import 'package:social_cv_client_dart_common/repositories.dart'; -import 'package:social_cv_client_flutter/src/data/managers/asset_config_manager.dart'; -import 'package:social_cv_client_flutter/src/data/managers/shared_preferences_manager.dart'; +import 'package:social_cv_client_flutter/src/data/managers/app_shared_preferences_manager.dart'; +import 'package:social_cv_client_flutter/src/data/managers/auth_shared_preferences_manager.dart'; +import 'package:social_cv_client_flutter/src/data/managers/config_assets_manager.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/local_app_preferences_repository.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/local_auth_preferences_repository.dart'; +import 'package:social_cv_client_flutter/src/data/repositories/local_config_repository.dart'; import 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; @@ -11,16 +15,10 @@ class ConfigurationBloc extends Bloc { ConfigurationBloc() : super(); - /// Interceptors - ApiInterceptor _apiInterceptor; - - /// Managers - CVApiManager _cvApiManager; - CVCacheManager _cvCacheManager; - /// Repositories CVRepository _cvRepository; - PreferencesRepository _preferencesRepository; + AuthPreferencesRepository _authPreferencesRepository; + AppPreferencesRepository _appPreferencesRepository; ConfigRepository _configRepository; @override @@ -37,23 +35,34 @@ class ConfigurationBloc extends Bloc { try { yield ConfigLoading(); - /// Preferences managers and repositories - _preferencesRepository = SharedPreferencesManager(); + /// Managers + final _appSharedPreferencesManager = AppSharedPreferencesManager(); + final _authSharedPreferencesManager = AuthSharedPreferencesManager(); + final _localConfigManager = ConfigAssetsManager(); - /// Config managers and repositories - _configRepository = AssetConfigManager(); + /// Repositories + _appPreferencesRepository = LocalAppPreferencesRepository( + appSharedPreferencesManager: _appSharedPreferencesManager, + ); + _authPreferencesRepository = LocalAuthPreferencesRepository( + authSharedPreferencesManager: _authSharedPreferencesManager, + ); + _configRepository = LocalConfigRepository( + configAssetsManager: _localConfigManager, + ); /// CV Managers and repositories - _apiInterceptor = ApiInterceptor( - accessToken: await _preferencesRepository.getAccessToken(), - refreshToken: await _preferencesRepository.getRefreshToken(), + final _apiInterceptor = ApiInterceptor( + accessToken: await _authPreferencesRepository.getAccessToken(), + refreshToken: await _authPreferencesRepository.getRefreshToken(), ); - _cvApiManager = DefaultCVApiManager( + + final _cvApiManager = DefaultCVApiManager( apiInterceptor: _apiInterceptor, apiBaseUrl: await _configRepository.getApiServerUrl(), ); - _cvCacheManager = DefaultCVCacheManager(); + final _cvCacheManager = DefaultCVCacheManager(); _cvRepository = DefaultCloudCVRepository( cvApiManager: _cvApiManager, @@ -62,7 +71,8 @@ class ConfigurationBloc extends Bloc { yield ConfigLoaded( cvRepository: _cvRepository, - preferencesRepository: _preferencesRepository, + authPreferencesRepository: _authPreferencesRepository, + appPreferencesRepository: _appPreferencesRepository, configRepository: _configRepository, ); } catch (error, stacktrace) { diff --git a/lib/src/domain/blocs/configuration/configuration_state.dart b/lib/src/domain/blocs/configuration/configuration_state.dart index 50736bc..b07f616 100644 --- a/lib/src/domain/blocs/configuration/configuration_state.dart +++ b/lib/src/domain/blocs/configuration/configuration_state.dart @@ -13,18 +13,37 @@ class ConfigLoading extends ConfigurationState {} class ConfigLoaded extends ConfigurationState { final CVRepository cvRepository; - final PreferencesRepository preferencesRepository; + final AuthPreferencesRepository authPreferencesRepository; + final AppPreferencesRepository appPreferencesRepository; final ConfigRepository configRepository; ConfigLoaded({ @required this.cvRepository, - @required this.preferencesRepository, + @required this.authPreferencesRepository, + @required this.appPreferencesRepository, @required this.configRepository, - }) : assert(cvRepository != null, 'No $CVRepository given'), + }) : assert( + cvRepository != null, + 'No $CVRepository given', + ), assert( - preferencesRepository != null, 'No $PreferencesRepository given'), - assert(configRepository != null, 'No $ConfigRepository given'), - super([cvRepository, preferencesRepository, configRepository]); + authPreferencesRepository != null, + 'No $AuthPreferencesRepository given', + ), + assert( + appPreferencesRepository != null, + 'No $AppPreferencesRepository given', + ), + assert( + configRepository != null, + 'No $ConfigRepository given', + ), + super([ + cvRepository, + authPreferencesRepository, + appPreferencesRepository, + configRepository, + ]); } class ConfigFailure extends ConfigurationState { diff --git a/lib/src/ui/widgets/error_widget.dart b/lib/src/ui/widgets/error_widget.dart index e01df75..a18b4d8 100644 --- a/lib/src/ui/widgets/error_widget.dart +++ b/lib/src/ui/widgets/error_widget.dart @@ -15,11 +15,20 @@ abstract class ErrorWidget extends StatelessWidget { /// [ErrorText] is a [Text] widget to display [Error] class ErrorText extends ErrorWidget { - ErrorText({Key key, @required Error error}) : super(key: key, error: error); + final TextAlign textAlign; + + ErrorText({ + Key key, + @required Error error, + this.textAlign = TextAlign.center, + }) : super(key: key, error: error); @override Widget build(BuildContext context) { - return Text(translateError(context, error)); + return Text( + translateError(context, error), + textAlign: textAlign, + ); } } diff --git a/lib/src/ui/pages/splash_page.dart b/lib/src/ui/widgets/splash_widget.dart similarity index 70% rename from lib/src/ui/pages/splash_page.dart rename to lib/src/ui/widgets/splash_widget.dart index 404dc43..d62b0ff 100644 --- a/lib/src/ui/pages/splash_page.dart +++ b/lib/src/ui/widgets/splash_widget.dart @@ -3,7 +3,7 @@ import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class SplashPage extends StatelessWidget { - final String _tag = 'SplashPage'; + final String _tag = '$SplashPage'; @override Widget build(BuildContext context) { @@ -23,3 +23,15 @@ class SplashPage extends StatelessWidget { ); } } + +class SplashApp extends StatelessWidget { + SplashApp() : super(); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: SplashPage(), + color: AppColors.primaryColor, + ); + } +} diff --git a/lib/src/ui/widgets/theme_switch_tile_widget.dart b/lib/src/ui/widgets/theme_switch_tile_widget.dart index c1af522..66c895e 100644 --- a/lib/src/ui/widgets/theme_switch_tile_widget.dart +++ b/lib/src/ui/widgets/theme_switch_tile_widget.dart @@ -14,11 +14,6 @@ class ThemeSwitchTile extends StatelessWidget { return BlocBuilder( bloc: _appBloc, builder: (BuildContext context, AppState state) { - if (state is AppUninitialized) { - return ListTile( - title: Text(CVLocalizations.of(context).settingsThemeCTA), - ); - } if (state is AppLoading) { return ListTile( title: Text(CVLocalizations.of(context).settingsThemeCTA), @@ -27,19 +22,20 @@ class ThemeSwitchTile extends StatelessWidget { } if (state is AppInitialized) { return SwitchListTile( - secondary: Icon( - state.theme == ThemeType.DARK - ? MdiIcons.weatherSunny - : MdiIcons.whiteBalanceSunny, - ), - title: Text(CVLocalizations.of(context).settingsThemeCTA), - value: state.theme == ThemeType.DARK ? true : false, - onChanged: (bool enable) { - if (enable) - _appBloc.dispatch(AppThemeChanged(theme: ThemeType.DARK)); - else - _appBloc.dispatch(AppThemeChanged(theme: ThemeType.LIGHT)); - }); + secondary: Icon( + state.theme == ThemeType.DARK + ? MdiIcons.weatherSunny + : MdiIcons.whiteBalanceSunny, + ), + title: Text(CVLocalizations.of(context).settingsThemeCTA), + value: state.theme == ThemeType.DARK ? true : false, + onChanged: (bool enable) { + if (enable) + _appBloc.dispatch(AppThemeChanged(theme: ThemeType.DARK)); + else + _appBloc.dispatch(AppThemeChanged(theme: ThemeType.LIGHT)); + }, + ); } }, ); diff --git a/pubspec.lock b/pubspec.lock index 496a904..3cc929c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -276,12 +276,19 @@ packages: source: hosted version: "0.6.1+1" json_annotation: - dependency: transitive + dependency: "direct main" description: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.3.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" kernel: dependency: transitive description: @@ -434,6 +441,13 @@ packages: relative: true source: path version: "1.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.4+2" source_span: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 634f327..19b16f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,9 @@ dependencies: # Storage shared_preferences: ^0.4.3 + # Serialization + json_annotation: ^2.3.0 + # Translations intl: ^0.15.7 intl_translation: ^0.17.0 @@ -47,6 +50,7 @@ dependencies: dev_dependencies: build_runner: ^1.0.0 + json_serializable: ^2.3.0 flutter_test: sdk: flutter From 16875f82f06b6b72010e6db4b951724fd5852669 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sun, 19 May 2019 19:55:35 +0200 Subject: [PATCH 11/17] [FIX] Fixed few workflow issues - Edited router - Edited dimensions - Edited heroes tag - Added logging --- lib/src/app.dart | 78 ++++++++-------- lib/src/{routes.dart => router.dart} | 8 +- lib/src/ui/commons/defaults.dart | 2 +- lib/src/ui/commons/dimensions.dart | 18 ++-- lib/src/ui/commons/tags.dart | 4 +- lib/src/ui/pages/account_page.dart | 31 ++----- lib/src/ui/pages/auth_page.dart | 13 +-- lib/src/ui/pages/home_page.dart | 5 +- lib/src/ui/pages/main_page.dart | 14 ++- lib/src/ui/pages/search_page.dart | 11 ++- lib/src/ui/widgets/account_tile_widget.dart | 22 ++--- .../ui/widgets/arc_banner_image_widget.dart | 16 ++-- lib/src/ui/widgets/error_widget.dart | 39 +++++++- .../widgets/initial_circle_avatar_widget.dart | 19 ++-- lib/src/ui/widgets/loading_widget.dart | 3 +- lib/src/ui/widgets/login_form_widget.dart | 4 +- .../ui/widgets/menu_bottom_sheet_widget.dart | 7 +- lib/src/ui/widgets/menu_button_widget.dart | 88 +++++++++++-------- lib/src/ui/widgets/profile_image_widget.dart | 13 +-- lib/src/ui/widgets/register_form_widget.dart | 5 ++ lib/src/ui/widgets/sort_box_widget.dart | 17 ++-- lib/src/ui/widgets/sort_dialog_widget.dart | 4 + lib/src/ui/widgets/sort_list_tile_widget.dart | 2 +- lib/src/ui/widgets/splash_widget.dart | 2 +- .../ui/widgets/theme_switch_tile_widget.dart | 12 ++- 25 files changed, 253 insertions(+), 184 deletions(-) rename lib/src/{routes.dart => router.dart} (96%) diff --git a/lib/src/app.dart b/lib/src/app.dart index 2ef4e4a..250f7d7 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -7,7 +7,7 @@ import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/repositories.dart'; import 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration.dart'; -import 'package:social_cv_client_flutter/src/routes.dart'; +import 'package:social_cv_client_flutter/src/router.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; @@ -51,7 +51,27 @@ class _ConfigWrapperAppState extends State { } else if (state is ConfigLoaded) { return BlocProvider( bloc: _configBloc, - child: _ConfiguredApp(state: state), + child: MultiProvider( + providers: [ + Provider.value( + value: state.cvRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: state.configRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: state.authPreferencesRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: state.appPreferencesRepository, + updateShouldNotify: (previous, current) => false, + ), + ], + child: _AppWrapper(state: state), + ), ); } return ErrorApp(error: NotImplementedYetError()); @@ -60,17 +80,17 @@ class _ConfigWrapperAppState extends State { } } -class _ConfiguredApp extends StatefulWidget { +class _AppWrapper extends StatefulWidget { final ConfigLoaded state; - _ConfiguredApp({Key key, @required this.state}) : super(key: key); + _AppWrapper({Key key, @required this.state}) : super(key: key); @override - State createState() => _ConfiguredAppState(); + State createState() => _AppWrapperState(); } -class _ConfiguredAppState extends State<_ConfiguredApp> { - final String _tag = '$_ConfiguredAppState'; +class _AppWrapperState extends State<_AppWrapper> { + final String _tag = '$_AppWrapperState'; AppBloc _appBloc; AccountBloc _accountBloc; @@ -125,33 +145,13 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { /// Dependency Injection of repositories and blocs /// Use updateShouldNotify to make dependencies available in /// `initState` methods of children widgets - return MultiProvider( - providers: [ - Provider.value( - value: _state.cvRepository, - updateShouldNotify: (previous, current) => false, - ), - Provider.value( - value: _state.configRepository, - updateShouldNotify: (previous, current) => false, - ), - Provider.value( - value: _state.authPreferencesRepository, - updateShouldNotify: (previous, current) => false, - ), - Provider.value( - value: _state.appPreferencesRepository, - updateShouldNotify: (previous, current) => false, - ), + return BlocProviderTree( + blocProviders: [ + BlocProvider(bloc: _appBloc), + BlocProvider(bloc: _authBloc), + BlocProvider(bloc: _accountBloc), ], - child: BlocProviderTree( - blocProviders: [ - BlocProvider(bloc: _appBloc), - BlocProvider(bloc: _accountBloc), - BlocProvider(bloc: _authBloc), - ], - child: _InitializedApp(state: state), - ), + child: _App(state: state), ); } else if (state is AppFailure) { return ErrorApp(error: state.error); @@ -163,26 +163,28 @@ class _ConfiguredAppState extends State<_ConfiguredApp> { } } -class _InitializedApp extends StatelessWidget { - final String _tag = '$_InitializedApp'; +class _App extends StatelessWidget { + final String _tag = '$_App'; final AppInitialized state; - _InitializedApp({this.state}); + _App({Key key, @required this.state}) + : assert(state != null, 'No $AppInitialized given'), + super(key: key); @override Widget build(BuildContext context) { Logger.log('$_tag:$build'); ///Routes - final routes = Routes(context); + final appRouter = AppRouter(); return MaterialApp( onGenerateTitle: (BuildContext context) => CVLocalizations.of(context).appName, theme: _buildCVTheme(state.theme), home: MainPage(), - onGenerateRoute: routes.router.generator, + onGenerateRoute: appRouter.router.generator, ///Use Fluro routes localizationsDelegates: [ diff --git a/lib/src/routes.dart b/lib/src/router.dart similarity index 96% rename from lib/src/routes.dart rename to lib/src/router.dart index b09bc0b..c26405f 100644 --- a/lib/src/routes.dart +++ b/lib/src/router.dart @@ -11,13 +11,11 @@ import 'package:social_cv_client_flutter/src/ui/pages/search_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/settings_page.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; -class Routes { - final String _tag = '$Routes'; +class AppRouter { + final String _tag = '$AppRouter'; final Router router = Router(); - final BuildContext context; - - Routes(this.context) : assert(context != null, 'No $BuildContext given') { + AppRouter() { _defineRoutes(); } diff --git a/lib/src/ui/commons/defaults.dart b/lib/src/ui/commons/defaults.dart index 8682a56..8dc5c14 100644 --- a/lib/src/ui/commons/defaults.dart +++ b/lib/src/ui/commons/defaults.dart @@ -3,4 +3,4 @@ const String kCVItemsPerPage1 = '5'; const String kCVItemsPerPage2 = '10'; const String kCVItemsPerPage3 = '25'; const String kCVItemsPerPage4 = '50'; -const String kCVItemsPerPageDefault = kCVItemsPerPage1; +const String kCVItemsDefaultPerPage = kCVItemsPerPage1; diff --git a/lib/src/ui/commons/dimensions.dart b/lib/src/ui/commons/dimensions.dart index 2567a33..a2371d8 100644 --- a/lib/src/ui/commons/dimensions.dart +++ b/lib/src/ui/commons/dimensions.dart @@ -1,15 +1,23 @@ class AppDimensions { - ///Profile + /// App + + static const double menuButtonPadding = 3.0; + + /// Card + static const double cardDefaultElevation = 2.0; + static const double cardDefaultPadding = 20.0; + + /// Profile static const double profileAvatarMin = 5.0; static const double profileAvatarMax = 50.0; static const double profileAvatarElevation = 2.0; - ///Group + /// Group static const double groupPadding = 5.0; - ///Entry + /// Entry static const double entryPadding = 10.0; static const double entryTagSpacing = 4.0; @@ -20,12 +28,12 @@ class AppDimensions { static const double horizontalEntryListHeight = entryEventHeight; static const double horizontalGroupListHeight = 300.0; - ///Sort Dialog + /// Sort Dialog static const double sortDialogWidth = 200.0; static const double sortDialogHeight = 300.0; - ///List + /// List static const double listHeaderDefaultHeightMax = 40.0; static const double listHeaderDefaultHeightMin = 40.0; diff --git a/lib/src/ui/commons/tags.dart b/lib/src/ui/commons/tags.dart index 4beb430..69b219c 100644 --- a/lib/src/ui/commons/tags.dart +++ b/lib/src/ui/commons/tags.dart @@ -1 +1,3 @@ -const String kHeroSearchFAB = 'TAG_HERO_SEARCH_FAB'; +class AppHeroes { + static const String searchFab = 'TAG_HERO_SEARCH_FAB'; +} diff --git a/lib/src/ui/pages/account_page.dart b/lib/src/ui/pages/account_page.dart index 93008b7..6f52d94 100644 --- a/lib/src/ui/pages/account_page.dart +++ b/lib/src/ui/pages/account_page.dart @@ -8,18 +8,8 @@ import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -class AccountPage extends StatefulWidget { - const AccountPage({Key key}) : super(key: key); - - @override - State createState() => _AccountPageState(); -} - -class _AccountPageState extends State { - final String _tag = '$_AccountPageState'; - - /// TODO: Add AuthenticationBloc - AuthenticationBloc get _authBloc => null; +class AccountPage extends StatelessWidget { + final String _tag = '$AccountPage'; @override Widget build(BuildContext context) { @@ -29,7 +19,7 @@ class _AccountPageState extends State { left: false, right: false, child: BlocBuilder( - bloc: _authBloc, + bloc: BlocProvider.of(context), builder: (BuildContext context, AuthenticationState state) { if (state is AuthenticationUninitialized) return Container(); if (state is AuthenticationUnauthenticated) @@ -66,22 +56,11 @@ class _AccountPageDetailsNotConnected extends StatelessWidget { // _AccountPageDetailsConnected // // // //////////////////////////////////////////////////////////////////////////////// -class _AccountPageDetailsConnected extends StatefulWidget { - _AccountPageDetailsConnected({Key key}) : super(key: key); - - @override - State createState() => _AccountPageDetailsConnectedState(); -} - -class _AccountPageDetailsConnectedState - extends State<_AccountPageDetailsConnected> { - /// TODO: Add AccountBloc - AccountBloc get _accountBloc => null; - +class _AccountPageDetailsConnected extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - bloc: _accountBloc, + bloc: BlocProvider.of(context), builder: (BuildContext context, AccountState state) { if (state is AccountUninitialized) { } else if (state is AccountLoaded) { diff --git a/lib/src/ui/pages/auth_page.dart b/lib/src/ui/pages/auth_page.dart index 52eaf4d..cc539f1 100644 --- a/lib/src/ui/pages/auth_page.dart +++ b/lib/src/ui/pages/auth_page.dart @@ -32,8 +32,8 @@ class _AuthPageState extends State return Scaffold( key: _scaffoldKey, body: NotificationListener( - onNotification: (overscroll) { - overscroll.disallowGlow(); + onNotification: (overScroll) { + overScroll.disallowGlow(); }, child: SingleChildScrollView( child: Container( @@ -59,10 +59,11 @@ class _AuthPageState extends State Padding( padding: EdgeInsets.only(top: 75.0), child: Image( - width: 250.0, - height: 191.0, - fit: BoxFit.fill, - image: new AssetImage('assets/img/login_logo.png')), + width: 250.0, + height: 191.0, + fit: BoxFit.fill, + image: new AssetImage('assets/img/login_logo.png'), + ), ), Padding( padding: EdgeInsets.only(top: 20.0), diff --git a/lib/src/ui/pages/home_page.dart b/lib/src/ui/pages/home_page.dart index 240a786..53ee2de 100644 --- a/lib/src/ui/pages/home_page.dart +++ b/lib/src/ui/pages/home_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; @@ -12,6 +13,7 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { Logger.log('$_tag:$build'); + return SafeArea( left: false, right: false, @@ -20,8 +22,9 @@ class HomePage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Card( + elevation: AppDimensions.cardDefaultElevation, child: Container( - padding: EdgeInsets.all(25.0), + padding: EdgeInsets.all(AppDimensions.cardDefaultPadding), child: Text( CVLocalizations.of(context).homeWelcome, style: Theme.of(context).textTheme.body2, diff --git a/lib/src/ui/pages/main_page.dart b/lib/src/ui/pages/main_page.dart index d5eea7b..edc0fca 100644 --- a/lib/src/ui/pages/main_page.dart +++ b/lib/src/ui/pages/main_page.dart @@ -38,7 +38,7 @@ class _MainPageState extends State { ), body: _children.elementAt(_currentIndex), floatingActionButton: FloatingActionButton.extended( - heroTag: kHeroSearchFAB, + heroTag: AppHeroes.searchFab, icon: Icon(Icons.search), label: Text(CVLocalizations.of(context).search), foregroundColor: Colors.white, @@ -83,8 +83,14 @@ class _MainPageState extends State { } void _onTabTapped(int index) { - setState(() { - _currentIndex = index; - }); + if (index == 0) { + setState(() { + _currentIndex = 0; + }); + } else if (index == 2) { + setState(() { + _currentIndex = 1; + }); + } } } diff --git a/lib/src/ui/pages/search_page.dart b/lib/src/ui/pages/search_page.dart index 87c190b..3cd5786 100644 --- a/lib/src/ui/pages/search_page.dart +++ b/lib/src/ui/pages/search_page.dart @@ -1,15 +1,18 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/commons/tags.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; /// Add controller for the input class SearchPage extends StatelessWidget { - const SearchPage({ - Key key, - }) : super(key: key); + final String _tag = '$SearchPage'; + + SearchPage({Key key}) : super(key: key); @override Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + return Scaffold( body: CustomScrollView( slivers: [ @@ -18,7 +21,7 @@ class SearchPage extends StatelessWidget { ), SliverToBoxAdapter( child: Hero( - tag: kHeroSearchFAB, + tag: AppHeroes.searchFab, child: Card( child: Container( padding: EdgeInsets.all(10.0), diff --git a/lib/src/ui/widgets/account_tile_widget.dart b/lib/src/ui/widgets/account_tile_widget.dart index 2902e9d..ab284a6 100644 --- a/lib/src/ui/widgets/account_tile_widget.dart +++ b/lib/src/ui/widgets/account_tile_widget.dart @@ -6,6 +6,7 @@ import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; class AccountTile extends StatefulWidget { @@ -16,13 +17,10 @@ class AccountTile extends StatefulWidget { } class _AccountTitleState extends State { - // TODO: Add AuthenticationBloc - AuthenticationBloc get _authBloc => null; - @override Widget build(BuildContext context) { return BlocBuilder( - bloc: _authBloc, + bloc: BlocProvider.of(context), builder: (BuildContext context, AuthenticationState state) { if (state is AuthenticationAuthenticated) return _AccountTileConnected(); @@ -40,21 +38,17 @@ class _AccountTitleState extends State { // // //////////////////////////////////////////////////////////////////////////////// -class _AccountTileConnected extends StatefulWidget { - _AccountTileConnected({Key key}) : super(key: key); +class _AccountTileConnected extends StatelessWidget { + final String _tag = '$_AccountTileConnected'; - @override - State createState() => _AccountTileConnectedState(); -} - -class _AccountTileConnectedState extends State<_AccountTileConnected> { - // TODO: Add AccountBloc - AccountBloc get _accountBloc => null; + _AccountTileConnected({Key key}) : super(key: key); @override Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + return BlocBuilder( - bloc: _accountBloc, + bloc: BlocProvider.of(context), builder: (BuildContext context, AccountState state) { if (state is AccountUninitialized) { } else if (state is AccountLoaded) { diff --git a/lib/src/ui/widgets/arc_banner_image_widget.dart b/lib/src/ui/widgets/arc_banner_image_widget.dart index c1b488c..bd99831 100644 --- a/lib/src/ui/widgets/arc_banner_image_widget.dart +++ b/lib/src/ui/widgets/arc_banner_image_widget.dart @@ -1,21 +1,25 @@ import 'package:flutter/material.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class ArcBannerImage extends StatelessWidget { - const ArcBannerImage({ - Key key, - @required this.image, - }) : super(key: key); + final String _tag = '$ArcBannerImage'; - final ImageProvider image; + final ImageProvider imageProvider; + + ArcBannerImage({Key key, @required this.imageProvider}) + : assert(imageProvider != null, 'No $ImageProvider given'), + super(key: key); @override Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + var screenWidth = MediaQuery.of(context).size.width; return ClipPath( clipper: ArcClipper(), child: Image( - image: image, + image: imageProvider, width: screenWidth, height: 230.0, fit: BoxFit.cover, diff --git a/lib/src/ui/widgets/error_widget.dart b/lib/src/ui/widgets/error_widget.dart index a18b4d8..4fad1dc 100644 --- a/lib/src/ui/widgets/error_widget.dart +++ b/lib/src/ui/widgets/error_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; @@ -13,6 +14,35 @@ abstract class ErrorWidget extends StatelessWidget { super(key: key); } +/// [ErrorIcon] is a [Icon] widget to display [Error] +class ErrorIcon extends StatelessWidget { + final IconData icon; + final double size; + final Color color; + final String semanticLabel; + final TextDirection textDirection; + + ErrorIcon({ + Key key, + this.icon = MdiIcons.alertCircleOutline, + this.size, + this.color = AppColors.errorColor, + this.semanticLabel, + this.textDirection, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Icon( + icon, + size: size, + color: color, + semanticLabel: semanticLabel, + textDirection: textDirection, + ); + } +} + /// [ErrorText] is a [Text] widget to display [Error] class ErrorText extends ErrorWidget { final TextAlign textAlign; @@ -41,8 +71,8 @@ class ErrorRow extends ErrorWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Icon(Icons.error, color: AppColors.errorColor), - Expanded(child: ErrorText(error: error)) + ErrorIcon(), + Expanded(child: ErrorText(error: error)), ], ); } @@ -55,7 +85,7 @@ class ErrorTile extends ErrorWidget { @override Widget build(BuildContext context) { return ListTile( - leading: Icon(MdiIcons.alertCircleOutline), + leading: ErrorIcon(), title: Text(CVLocalizations.of(context).errorOccurred), subtitle: ErrorText(error: error), ); @@ -77,10 +107,11 @@ class ErrorCard extends ErrorWidget { @override Widget build(BuildContext context) { return Card( + elevation: AppDimensions.cardDefaultElevation, child: Container( height: height, width: width, - padding: EdgeInsets.all(10.0), + padding: EdgeInsets.all(AppDimensions.cardDefaultPadding), child: ErrorRow(error: error), ), ); diff --git a/lib/src/ui/widgets/initial_circle_avatar_widget.dart b/lib/src/ui/widgets/initial_circle_avatar_widget.dart index ff11403..480e753 100644 --- a/lib/src/ui/widgets/initial_circle_avatar_widget.dart +++ b/lib/src/ui/widgets/initial_circle_avatar_widget.dart @@ -1,7 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; class InitialCircleAvatar extends StatefulWidget { + final String text; + final double elevation; + final ImageProvider backgroundImage; + final double radius; + final double minRadius; + final double maxRadius; + InitialCircleAvatar({ Key key, this.text = '', @@ -13,18 +21,13 @@ class InitialCircleAvatar extends StatefulWidget { }) : assert(radius == null || (minRadius == null && maxRadius == null)), super(key: key); - final String text; - final double elevation; - final ImageProvider backgroundImage; - final double radius; - final double minRadius; - final double maxRadius; - @override _InitialCircleAvatarState createState() => new _InitialCircleAvatarState(); } class _InitialCircleAvatarState extends State { + final String _tag = '$_InitialCircleAvatarState'; + bool _checkLoading = true; @override @@ -43,6 +46,8 @@ class _InitialCircleAvatarState extends State { @override Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + return _checkLoading == true ? Material( shape: CircleBorder(), diff --git a/lib/src/ui/widgets/loading_widget.dart b/lib/src/ui/widgets/loading_widget.dart index 59a549b..7d97c59 100644 --- a/lib/src/ui/widgets/loading_widget.dart +++ b/lib/src/ui/widgets/loading_widget.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; /// LoadingCard displays lines with opacity moving up and down /// Specify the number of loading lines to display @@ -133,7 +134,7 @@ class LoadingCard extends StatelessWidget { child: Container( height: height, width: width, - padding: EdgeInsets.all(10.0), + padding: EdgeInsets.all(AppDimensions.cardDefaultPadding), child: LoadingShadowContent( numberOfTitleLines: numberOfTitleLines, numberOfContentLines: numberOfContentLines, diff --git a/lib/src/ui/widgets/login_form_widget.dart b/lib/src/ui/widgets/login_form_widget.dart index d97c7a5..3d3135a 100644 --- a/lib/src/ui/widgets/login_form_widget.dart +++ b/lib/src/ui/widgets/login_form_widget.dart @@ -11,9 +11,7 @@ import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class LoginFormWidget extends StatefulWidget { - const LoginFormWidget({ - Key key, - }) : super(key: key); + const LoginFormWidget({Key key}) : super(key: key); @override State createState() => _LoginFormWidgetState(); diff --git a/lib/src/ui/widgets/menu_bottom_sheet_widget.dart b/lib/src/ui/widgets/menu_bottom_sheet_widget.dart index 0344bf9..8375bc0 100644 --- a/lib/src/ui/widgets/menu_bottom_sheet_widget.dart +++ b/lib/src/ui/widgets/menu_bottom_sheet_widget.dart @@ -2,10 +2,13 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/account_tile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; class MenuBottomSheet extends StatelessWidget { - const MenuBottomSheet({ + final String _tag = '$MenuBottomSheet'; + + MenuBottomSheet({ Key key, this.backgroundColor, this.borderRadius = const BorderRadius.only( @@ -18,6 +21,8 @@ class MenuBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + return SafeArea( left: false, right: false, diff --git a/lib/src/ui/widgets/menu_button_widget.dart b/lib/src/ui/widgets/menu_button_widget.dart index 2cd316f..da49648 100644 --- a/lib/src/ui/widgets/menu_button_widget.dart +++ b/lib/src/ui/widgets/menu_button_widget.dart @@ -2,33 +2,39 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/errors.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; -class MenuButton extends StatefulWidget { - const MenuButton({ +class MenuButton extends StatelessWidget { + final String _tag = '$MenuButton'; + final EdgeInsetsGeometry padding; + + MenuButton({ Key key, + this.padding = const EdgeInsets.symmetric( + vertical: AppDimensions.menuButtonPadding, + ), }) : super(key: key); - @override - State createState() => _MenuButtonState(); -} - -class _MenuButtonState extends State { - // TODO: Add AuthBloc - AuthenticationBloc get _authBloc => null; - @override Widget build(BuildContext context) { - return BlocBuilder( - bloc: _authBloc, - builder: (BuildContext context, AuthenticationState state) { - if (state is AuthenticationAuthenticated) return _MenuButtonConnected(); - if (state is AuthenticationUnauthenticated) - return _MenuButtonNotConnected(); - return ErrorRow(error: NotImplementedYetError()); - }, + Logger.log('$_tag:$build'); + + return Padding( + padding: padding, + child: BlocBuilder( + bloc: BlocProvider.of(context), + builder: (BuildContext context, AuthenticationState state) { + if (state is AuthenticationAuthenticated) + return _MenuButtonConnected(); + if (state is AuthenticationUnauthenticated) + return _MenuButtonNotConnected(); + return ErrorRow(error: NotImplementedYetError()); + }, + ), ); } } @@ -38,8 +44,11 @@ class _MenuButtonState extends State { /// ---------------------------------------------------------- class _MenuButtonNotConnected extends StatelessWidget { + final String _tag = '$_MenuButtonNotConnected'; + @override Widget build(BuildContext context) { + Logger.log('$_tag:$build'); return IconButton( onPressed: () => openMenuBottomSheet(context), icon: Icon(Icons.menu), @@ -51,33 +60,40 @@ class _MenuButtonNotConnected extends StatelessWidget { /// ---------------------- Connected ------------------------- /// ---------------------------------------------------------- -class _MenuButtonConnected extends StatefulWidget { - _MenuButtonConnected({Key key}) : super(key: key); +class _MenuButtonConnected extends StatelessWidget { + final String _tag = '$_MenuButtonConnected'; - @override - State createState() => _MenuButtonConnectedState(); -} - -class _MenuButtonConnectedState extends State<_MenuButtonConnected> { - AccountBloc get _accountBloc => null; + _MenuButtonConnected({Key key}) : super(key: key); @override Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + return BlocBuilder( - bloc: _accountBloc, + bloc: BlocProvider.of(context), builder: (BuildContext context, AccountState state) { - if (state is AccountLoaded) { - return Padding( - padding: EdgeInsets.only(top: 3.0, bottom: 3.0), - child: IconButton( - onPressed: () => openMenuBottomSheet(context), - icon: InitialCircleAvatar( - text: state.user.username, - backgroundImage: NetworkImage(state.user.picture), - ), + if (state is AccountUninitialized) { + return _MenuButtonNotConnected(); + } else if (state is AccountLoading) { + return GestureDetector( + onTap: () => openMenuBottomSheet(context), + child: CircularProgressIndicator(), + ); + } else if (state is AccountLoaded) { + return IconButton( + onPressed: () => openMenuBottomSheet(context), + icon: InitialCircleAvatar( + text: state.user.username, + backgroundImage: NetworkImage(state.user.picture), ), ); + } else if (state is AccountFailed) { + return IconButton( + onPressed: () => openMenuBottomSheet(context), + icon: ErrorIcon(), + ); } + return ErrorText(error: NotImplementedYetError()); }, ); } diff --git a/lib/src/ui/widgets/profile_image_widget.dart b/lib/src/ui/widgets/profile_image_widget.dart index 533c8f7..c1f0a49 100644 --- a/lib/src/ui/widgets/profile_image_widget.dart +++ b/lib/src/ui/widgets/profile_image_widget.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class ProfileImage extends StatelessWidget { - const ProfileImage({ - Key key, - @required this.imageUrl, - }) : assert(imageUrl != null), + final String _tag = '$ProfileImage'; + + ProfileImage({Key key, @required this.imageUrl}) + : assert(imageUrl != null), super(key: key); static const RATIO = 1; @@ -14,7 +15,9 @@ class ProfileImage extends StatelessWidget { @override Widget build(BuildContext context) { - return new Material( + Logger.log('$_tag:$build'); + + return Material( borderRadius: new BorderRadius.circular(75.0), elevation: 2.0, child: InitialCircleAvatar(), diff --git a/lib/src/ui/widgets/register_form_widget.dart b/lib/src/ui/widgets/register_form_widget.dart index c603a53..5e4dd89 100644 --- a/lib/src/ui/widgets/register_form_widget.dart +++ b/lib/src/ui/widgets/register_form_widget.dart @@ -7,6 +7,7 @@ import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/repositories.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class RegisterFormWidget extends StatefulWidget { @override @@ -14,6 +15,8 @@ class RegisterFormWidget extends StatefulWidget { } class _RegisterFormWidgetState extends State { + final String _tag = '$_RegisterFormWidgetState'; + final FocusNode myFocusNodePassword = FocusNode(); final FocusNode myFocusNodeEmail = FocusNode(); final FocusNode myFocusNodeFirstName = FocusNode(); @@ -55,6 +58,8 @@ class _RegisterFormWidgetState extends State { @override Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + return BlocBuilder( bloc: _registerBloc, builder: (BuildContext context, RegisterState state) { diff --git a/lib/src/ui/widgets/sort_box_widget.dart b/lib/src/ui/widgets/sort_box_widget.dart index 5c6f081..5a5f665 100644 --- a/lib/src/ui/widgets/sort_box_widget.dart +++ b/lib/src/ui/widgets/sort_box_widget.dart @@ -7,8 +7,8 @@ enum SortState { NoSort, } -class Sortbox extends StatefulWidget { - const Sortbox({ +class SortBox extends StatelessWidget { + const SortBox({ Key key, this.value, this.onChanged, @@ -18,25 +18,20 @@ class Sortbox extends StatefulWidget { final ValueChanged onChanged; /// The width of a checkbox widget. - static const double width = 18.0; + final double width = 18.0; - @override - _SortboxState createState() => _SortboxState(); -} - -class _SortboxState extends State { @override Widget build(BuildContext context) { IconData iconSort; - if (widget.value == SortState.SortAsc) { + if (value == SortState.SortAsc) { iconSort = Icons.arrow_upward; - } else if (widget.value == SortState.SortDesc) { + } else if (value == SortState.SortDesc) { iconSort = Icons.arrow_downward; } else { iconSort = Icons.close; } return GestureDetector( - onTap: () => widget.onChanged(widget.value), + onTap: () => onChanged(value), child: Icon(iconSort), ); } diff --git a/lib/src/ui/widgets/sort_dialog_widget.dart b/lib/src/ui/widgets/sort_dialog_widget.dart index 48139da..d5d6dbe 100644 --- a/lib/src/ui/widgets/sort_dialog_widget.dart +++ b/lib/src/ui/widgets/sort_dialog_widget.dart @@ -22,8 +22,12 @@ class SortDialog extends StatefulWidget { } class _SortDialogState extends State { + final String _tag = '$_SortDialogState'; + @override Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + final _listTiles = widget.sortItems .map((sortItem) => SortListTile( key: Key(sortItem.field), diff --git a/lib/src/ui/widgets/sort_list_tile_widget.dart b/lib/src/ui/widgets/sort_list_tile_widget.dart index 08baf47..22d2856 100644 --- a/lib/src/ui/widgets/sort_list_tile_widget.dart +++ b/lib/src/ui/widgets/sort_list_tile_widget.dart @@ -37,7 +37,7 @@ class SortListTile extends StatelessWidget { key: key, leading: Icon(Icons.unfold_more), title: title, - trailing: Sortbox(value: value, onChanged: onChanged), + trailing: SortBox(value: value, onChanged: onChanged), onTap: onChanged != null ? () { if (value == SortState.SortAsc) onChanged(SortState.SortDesc); diff --git a/lib/src/ui/widgets/splash_widget.dart b/lib/src/ui/widgets/splash_widget.dart index d62b0ff..3925f71 100644 --- a/lib/src/ui/widgets/splash_widget.dart +++ b/lib/src/ui/widgets/splash_widget.dart @@ -16,7 +16,7 @@ class SplashPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), - Text("Social CV"), + Text('Social CV'), ], ), ), diff --git a/lib/src/ui/widgets/theme_switch_tile_widget.dart b/lib/src/ui/widgets/theme_switch_tile_widget.dart index 66c895e..e4daac6 100644 --- a/lib/src/ui/widgets/theme_switch_tile_widget.dart +++ b/lib/src/ui/widgets/theme_switch_tile_widget.dart @@ -3,12 +3,17 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class ThemeSwitchTile extends StatelessWidget { - const ThemeSwitchTile({Key key}) : super(key: key); + final String _tag = '$ThemeSwitchTile'; + + ThemeSwitchTile({Key key}) : super(key: key); @override Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + AppBloc _appBloc = BlocProvider.of(context); return BlocBuilder( @@ -30,10 +35,11 @@ class ThemeSwitchTile extends StatelessWidget { title: Text(CVLocalizations.of(context).settingsThemeCTA), value: state.theme == ThemeType.DARK ? true : false, onChanged: (bool enable) { - if (enable) + if (enable) { _appBloc.dispatch(AppThemeChanged(theme: ThemeType.DARK)); - else + } else { _appBloc.dispatch(AppThemeChanged(theme: ThemeType.LIGHT)); + } }, ); } From 281e792cac3ea0d778b399f72af73226a4251f45 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Sun, 19 May 2019 20:55:30 +0200 Subject: [PATCH 12/17] [FIX] Fixed App rebuild on AppLoading event - Added tricks to avoid display of LoadingApp on AppLoading event --- lib/src/app.dart | 10 +-- lib/src/ui/pages/settings_page.dart | 4 +- .../ui/widgets/menu_bottom_sheet_widget.dart | 4 +- .../theme_switch_list_tile_widget.dart | 61 +++++++++++++++++++ .../ui/widgets/theme_switch_tile_widget.dart | 49 --------------- 5 files changed, 71 insertions(+), 57 deletions(-) create mode 100644 lib/src/ui/widgets/theme_switch_list_tile_widget.dart delete mode 100644 lib/src/ui/widgets/theme_switch_tile_widget.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index 250f7d7..797984b 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -12,7 +12,6 @@ import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/splash_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; @@ -136,12 +135,15 @@ class _AppWrapperState extends State<_AppWrapper> { Widget build(BuildContext context) { Logger.log('$_tag:$build'); + AppState tmpState = AppInitialized.defaultValues(); + return BlocBuilder( bloc: _appBloc, builder: (BuildContext context, AppState state) { - if (state is AppLoading) { - return LoadingApp(); - } else if (state is AppInitialized) { + if (state is AppLoading || state is AppInitialized) { + if (state is AppLoading) state = tmpState; + tmpState = state; + /// Dependency Injection of repositories and blocs /// Use updateShouldNotify to make dependencies available in /// `initState` methods of children widgets diff --git a/lib/src/ui/pages/settings_page.dart b/lib/src/ui/pages/settings_page.dart index f85b3ec..c4f1af0 100644 --- a/lib/src/ui/pages/settings_page.dart +++ b/lib/src/ui/pages/settings_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_list_tile_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class SettingsPage extends StatelessWidget { @@ -20,7 +20,7 @@ class SettingsPage extends StatelessWidget { right: false, child: ListView( children: [ - ThemeSwitchTile(), + ThemeSwitchListTile(), AboutListTile(icon: Icon(Icons.info)), ], ), diff --git a/lib/src/ui/widgets/menu_bottom_sheet_widget.dart b/lib/src/ui/widgets/menu_bottom_sheet_widget.dart index 8375bc0..168685d 100644 --- a/lib/src/ui/widgets/menu_bottom_sheet_widget.dart +++ b/lib/src/ui/widgets/menu_bottom_sheet_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/account_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_list_tile_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; @@ -30,7 +30,7 @@ class MenuBottomSheet extends StatelessWidget { children: [ AccountTile(), Divider(), - ThemeSwitchTile(), + ThemeSwitchListTile(), ListTile( leading: Icon(Icons.settings), title: Text(CVLocalizations.of(context).settingsCTA), diff --git a/lib/src/ui/widgets/theme_switch_list_tile_widget.dart b/lib/src/ui/widgets/theme_switch_list_tile_widget.dart new file mode 100644 index 0000000..eef348a --- /dev/null +++ b/lib/src/ui/widgets/theme_switch_list_tile_widget.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; + +class ThemeSwitchListTile extends StatelessWidget { + final String _tag = '$ThemeSwitchListTile'; + + ThemeSwitchListTile({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + + AppBloc _appBloc = BlocProvider.of(context); + + return BlocBuilder( + bloc: _appBloc, + builder: (BuildContext context, AppState state) { + if (state is AppLoading || state is AppInitialized) { + bool value; + Widget leadingWidget; + Widget trailingWidget; + + final function = (bool enable) { + if (enable) { + _appBloc.dispatch(AppThemeChanged(theme: ThemeType.DARK)); + } else { + _appBloc.dispatch(AppThemeChanged(theme: ThemeType.LIGHT)); + } + }; + + if (state is AppLoading) { + value = true; + leadingWidget = CircularProgressIndicator(); + trailingWidget = CircularProgressIndicator(); + } else if (state is AppInitialized) { + value = (state.theme == ThemeType.DARK) ? true : false; + leadingWidget = Icon(state.theme == ThemeType.DARK + ? MdiIcons.weatherSunny + : MdiIcons.whiteBalanceSunny); + + trailingWidget = Switch( + value: value, + onChanged: function, + ); + } + + return ListTile( + onTap: () => function(!value), + leading: leadingWidget, + title: Text(CVLocalizations.of(context).settingsThemeCTA), + trailing: trailingWidget, + ); + } + }, + ); + } +} diff --git a/lib/src/ui/widgets/theme_switch_tile_widget.dart b/lib/src/ui/widgets/theme_switch_tile_widget.dart deleted file mode 100644 index e4daac6..0000000 --- a/lib/src/ui/widgets/theme_switch_tile_widget.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; - -class ThemeSwitchTile extends StatelessWidget { - final String _tag = '$ThemeSwitchTile'; - - ThemeSwitchTile({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - Logger.log('$_tag:$build'); - - AppBloc _appBloc = BlocProvider.of(context); - - return BlocBuilder( - bloc: _appBloc, - builder: (BuildContext context, AppState state) { - if (state is AppLoading) { - return ListTile( - title: Text(CVLocalizations.of(context).settingsThemeCTA), - trailing: CircularProgressIndicator(), - ); - } - if (state is AppInitialized) { - return SwitchListTile( - secondary: Icon( - state.theme == ThemeType.DARK - ? MdiIcons.weatherSunny - : MdiIcons.whiteBalanceSunny, - ), - title: Text(CVLocalizations.of(context).settingsThemeCTA), - value: state.theme == ThemeType.DARK ? true : false, - onChanged: (bool enable) { - if (enable) { - _appBloc.dispatch(AppThemeChanged(theme: ThemeType.DARK)); - } else { - _appBloc.dispatch(AppThemeChanged(theme: ThemeType.LIGHT)); - } - }, - ); - } - }, - ); - } -} From f8db7296ab0bbfeb60d936bda03d00fc227e5ea2 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Mon, 20 May 2019 00:08:11 +0200 Subject: [PATCH 13/17] [TASK] Edited auth widget and flows - Fixed authentication shared preferences - Edited error widgets - Fix `Provider.of` use by setting `listen` to false - Edited register and login form - Added assets and AppAssets class --- README.md | 2 +- assets/images/login_logo.png | Bin 0 -> 12564 bytes .../auth_shared_preferences_manager.dart | 9 +- lib/src/ui/commons/assets.dart | 3 + lib/src/ui/commons/colors.dart | 16 +- lib/src/ui/commons/dimensions.dart | 11 + lib/src/ui/localizations/cv_localization.dart | 124 +++--- .../ui/localizations/cv_localization_en.dart | 125 +++--- .../ui/localizations/cv_localization_fr.dart | 137 ++++--- lib/src/ui/pages/auth_page.dart | 384 +++++++++--------- lib/src/ui/widgets/account_tile_widget.dart | 7 +- .../widgets/elements/entry_list_widget.dart | 2 +- lib/src/ui/widgets/elements/entry_widget.dart | 3 +- .../widgets/elements/group_list_widget.dart | 3 +- lib/src/ui/widgets/elements/group_widget.dart | 3 +- .../ui/widgets/elements/part_list_widget.dart | 3 +- lib/src/ui/widgets/elements/part_widget.dart | 3 +- .../widgets/elements/profile_list_widget.dart | 3 +- .../ui/widgets/elements/profile_widget.dart | 3 +- lib/src/ui/widgets/error_widget.dart | 67 ++- lib/src/ui/widgets/loading_widget.dart | 22 +- lib/src/ui/widgets/login_form_widget.dart | 21 +- lib/src/ui/widgets/register_form_widget.dart | 29 +- lib/src/ui/widgets/repository_provider.dart | 21 + pubspec.lock | 8 +- pubspec.yaml | 10 +- 26 files changed, 601 insertions(+), 418 deletions(-) create mode 100644 assets/images/login_logo.png create mode 100644 lib/src/ui/commons/assets.dart create mode 100644 lib/src/ui/widgets/repository_provider.dart diff --git a/README.md b/README.md index b90422e..194ea11 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ For help getting started with Flutter, view our online ## Installation ### Config -Use secret template `./secrets.json.dist` to add secret file `./secrets.json` +Use secret template `./config.json.dist` to add secret file `./config.json` ### Generate models [documentation](https://flutter.io/json/) diff --git a/assets/images/login_logo.png b/assets/images/login_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b5ccd30d51e163880d33ac7adf48ee4775eebede GIT binary patch literal 12564 zcmd^Gdsq|KwvU1a+lnA6wgRC+i-;Qfup|%?v>>1r>H|>3pwbp4tq_AkNFY#8O8_Aa zinJO)1*E8mFGL^#s~`n6&>&zb1P#I%5F!K!;gOs@lLQ~_z31F}`xlOho(H=gm@@%v>u z*Rsa1al1IV!8h`_m-O>Scb>ax&85OikH)Y|NHY>1-+lCmV3GF8VE8W`a~67LEl#^< z#K>Yx=(W><27CCfXz)+GG5BK?0sfc(?ND*A-Oc4|e)mA3&L2tG*CA|^w3Yt2sqASZ zO0q!IDrwgG)C(27~2B|!VWb*JUT9&v`%HOs>(HV6ymS*g50DKnRjLe?wXu zetm2Kq7mearU8=>TLBzs{f(d^0ILx=AWHxoUT+$P!;3BgaL6`%*(EgNzX7e?4B&1N zDQG57bOiz=J%VTe3@t*y5^eYmoseqqdedH2@ic`0j5=zY=Sc zC%S5&w-vwvHXT6QC_-zip%d5#UvnC6uu-lj55XHL%DQ1SXc|C+4Z@G~i~tS@PX30p zF4pIZuW^z3(EN4(RmT$c9ouPlZ*n^)&BG?!zBR};oNfuWLecw?aWf%NA-9i1|MK>d z{;g>!T574m&VK8LON+Oqq1MmsHs0AE=US6yxHZio5_Q+o(COIX*Yq~Mrfv1fV6W*H z7w_svbsHN+Ecc3AGK-FOi2N_yLI>^npLpxPxsBSI1~RnNV0z|q#5& zwDohP77mdhtv%7&OH_QSCwjRThkm0C90akJx=TYR_)_l>G|yOj3DO{l`!JeS8mwn4qQ>e%8hdvkDYLctYb%y`|}KxTN&Pc!aX?j(9QoVwu!SbRAjva2t$ zl-%iLchtr#ono?QnP|MjO!l0{i`<>2q#axQAEAD5Ce4DeS(8?kR#obWrlbEzm4Ldt zH4Ser1IoenA6u-aK&SFAs4A6?EoccnO8_7;4#Pt9)C&e{rT>3CC_@x;clyB@pxLbq zMza{nX|2Za0~i8-`Gc1w{@eym0@mUc0Pb!tsq)eTXV^-J2$dWGc1Ae=;gsT~;$QIhb6Zk9J@^36uDRPmg~wXnwFDgoHpULy4w7gp*0Vjd zW+O_t-g=RHL%8luUqAz>=Lp&xqllA_5Z{~1 zkSWMV7646E1T5SKG{k>r18d@uYXhH{b;39iF9_3FrV|Ex0E9^=%+2WqVTh%7{Rd4^ z2dv6sl!Lds`}>I1@gUHmZ962N4m?b4GC)KY*i1GL+b57_>6HNIa#-YzI;zSDOuUwh z%gKq?vpoV^O|%-v>GcDrT%Aa3kANX~)fU%&bIhdsZ*Sz$q z_GI|Vt01UA(iB@0nbZPA?IkVB@F0c%Pppz7bR!9CaW#BC2R$2h2OKSMeCL2?0CQTU zpFcw;7Vz@t&!(17Jp;?(78h0;(Gd0pFbEl$zWOTIP>{#4acG{_7oh$9!>b@)VZZ?w z>wE#WK6p9eC$}-i;a6+{htFQiKXPey{(}O>=ObbLv+6eb?yPc(@O%V)7cK= zTc;5Kx4CZ01CXsV8o*lbOMq#;fd45|kPjo_!-JqtzYrWpJc<#vjdxehu}=J(%m)yLLjHgSCXih%CO`{HuRm zfTJ{n{H_@GJ=XFED!-$EtMJ_BcFes_oHCkTX`<(11_?2n7ubE_C~YCx>6fqPmqf>t zu%hyyf<>}QwH*(cipg#SN{P-cA|I?vJEvpxswWcbh@As8$#_yf*UpKjep|otu9R3D z9hBoOy3&a!rw-*D8LDx2g7KJZ5L~0G;YEr|pk%_C<>qvr#%C9XZ(~x8 zX;x;{U@L}E9kI0$U&OQyY~HHplQ5j9V4q7;_&s_oXxcM%cU7Xo*#@|ZusX~EG!BT_ zw|Z(A1G~aMp-BRcgNm+=p}iG!hq&;Ajf9eWs0zV$^PEr73AZr8oei6GlV#np%@ zeRs`}PMjO)SuKwb|80s#IsE$ub+=CY>6FOOGK-z&R|1KwqDhIx*c7ghiFU8RIXYI~={%E;~ zJ(N;3ZzUC%dAx48cZw{Vq{0ER9D$YLe!2NZQK)l`8WHUQRA>aqipOSG|;5Od#kjqjMCv z8fvkU&&3nCSJR3%>4na&X2^U)ZEzvDcIL_{@4nqRkzKh3Pr%iv_R5`nCb5|vE+B45 z+U{B*o!FjtL~$Nlmm`f;oVwE*?8eXEoYj5|fDq2g%vGt1!7uu{5^m=CRiC?m`8ZS5 z7!cG9+8rck-vg?S?Ur30IVkh$D?E~g77!8>Q!z;U0_y+qL(F`iR)2jEx*L?P*ENb#(_S~iJ=R z!Y`Q_l=tzx$CC*+(lJKn$>mVnxn{j_$7!)2QZxrQ@XdW1HZOCJRt+9}4ynd!RbhUa z57noSbDz}pSn#Z6()JoAGV-3VdvkTEe_^5)vmu4jiQ1cut@hrc-hM~!@jI=X?W(wP z0=eE6(&rveWYIZMZQCO!)Q4J4_g7h8PeR|Uz77_Azd)NcqIYUkZ?YX1LIu9`07`TV z<8x4;GC$h?zDSW2CGs#VelE{NIx!$Ncr+$=uJ?|$7@r?%mc{43F%__tH1(|_7gAI` zW8gk(3xAGq-3o6qA$y$s{S*r*@V2Vsc6*o-h#0w`kk&H%qSi1!wt#*GKUr(xok@Qi zs%M`uhdSLDb|R{eC|E@6_oeE?nTOZ(@O=T`Q+=&&RDpjsu2>`bVSIlBllP@V`m zRJ5fxVf02VN9sKNfPoj74 z;OMrGkS{tXiq7Gv?x-X4k>m=`OJk>@A!sk>jfubZIvoR^MP@)4;4qsi+O0JP~UpEF5_B0 zC@VP?Eg_f^O7VL--zzMjpfSZ7e#o%T9vnH(CEaQ!sHV`~%cu)6O|6D2`lbRwFsG@j z$;r3m7W1Tnm159qQ(_(3+-kZS1m6B6$Wf(_i6#M^! zalb8+^lYEvY|Gj76Ri8^?ho2ckNz&ceYCt`GJ6IV42DqF;ufMvvs4mQpme=HxR=qC z*T?e>Y|c76Ufy#;Mf~QzE{-T(Y*y#Q752g~!PSo0135)ycUpnM$Idztn`%^B@Adjp zIT4_bi1Q3GlSfJ?e)Q-}k@s`2cL-SZ3_rXbj#5YAj0k$#A+sh%wK6}dsr9}_wU&M# z^C=HZ=1?ZG-USejJc%B-9XaPcrb*jCZ{xZ-%=P)jUs5b<(`_XmCds~C#ULG8>H}|* zp!stf3%ISi*C7Xc`|#f1p7l0vlI;rbI=MWS%)O}${pQ2^-a|24l-(a1k0!puYOc&* zYAyrC7R9URrl}7KChpq|+*Xcww1rTJ1aenFp8+i^`)Ww#PYRZ|$8XRxU#eY-n!M6P z_YR4sK4gm_l%5y%$A@opT1~#te|EEi<$y<5ki86`J7y2A5LaNz#z0Go7z3(RN3vsO zqyx)~`R*@@rJ2X(v_(=K~4X2N__Eg15P%!lU+Tp{B!6khgqt`H1 zt3rL}B--C*Y;k9jGP(=ZO5CsKn)&dq33rGgXF(5E!}xT6Q3Q$deRt!121U`CTiGn2 z6#I?6WvZ)opVLUv#6E+JYaXD-*!YI>qt)LR-8|PkmhCT>#OSK?hdcNgS+^;yfbjwv zg_Y9!cFrhK7~1!Q9yJO6}IR(X)J%%dVaYDsEPVh+2h9!^(@aD@|l?9#(9LDp9$zGHi$&Xi}F$ zwrA?ceW@O{U45L9FWsnDm;wD2T35Q};)o)fl+ig~MizLf8{OzOEj(iqJ+QxtGQ>QtHtJY54V9sVO{otA3op_0B4>?%Df=Ks2lUu|odW3)>vIRl z*0v)#swu#>&iVZcW*##@BAMI8iFn9T%Tu_hIN#x4&HE{m-y{{8gA>Q`k30?M_403#<@?g(o?>4{D;wzHLr{Bu+BuebLQD%=ucvM}SgBJEh|FY=K zbVw$P8gV4|A3=3j&vHW9Q`^18kU3Pt_~5ptB#6E{tNtOzp%{B@rCZ?WXG$vFpMT{u zZ*hF7Mt&I9q{P@(M&~NjMVs-faO$#xsNIx_N$hO3#Frg2A>0x*harbNlt<+U<@RBW zUl!f@*~|6Ow(=Rv=gSFze$aWkU^(v(|bX0Zqj&Ow>h9Tg8^mb{~Lu{pFc=}A&h zIU5u0_trC-fzVvD?cAcrnGPL-hjZ+8fv#4C^J|{fMupBsN3Xk8DgW$b#>%jciu*Bx zW6kbtJov4_RcQ)JZMEtNo_l2T(DFqjFK**XI?kn5X!h7+S4GQg5CG&f6l-M@1CEjz z^Vc|^cUx909qI-c+(=s1z$A|ZC0?TwP8ZAOSZJSyxU`XCC!l?4Wz;IMB*7&fOqhY6b`IObFr-9mz#>y~3}+}FU7m5#05SWbF|T4CEp}d) zzfGS@_>hgY82*TiI$xGjqoT+`l7XKWKo5VOn(Md#%3LZpR7ng>xBZTpjzBGLIA!SD z8dX2K+_vj97*ru4!Qn+EN^H!-kg|-Tu&1NAxApgzi%M|rWo_m8tl$=QfmRYWW*4pE z5`FMuNuNs&=J`s;gEk`<4(70WH|=d*G_GG8OOv$X33eTm4@8J+#u`#K%tgj<{ z>XcTT>!Ys2LNN2v@u{~HalPxvn>d4u!PG~)-Q@B}zWku4*++v?_JsKb_Q3NX?UM&Q z(nh{K)5YVNdA{fJYz zzTKLfv>*IRA93R&bX=+XSo63tta9RRPK#tBTOOHxqk_KVbQ5V7vIGXu^ofa2GHhZ; z4%W4{V5#8q$y|f{I+h{I0j**5I$$fa@|(erGNNU96KjbRRU5JmVWDR|*-sr}2f>sk z01c14fB|u5Y$k=*vqB2Nm=Z}9*BqYumtBGbMar7-4Cd{^rS+_w_P{;Gq>3 z{M3qS*qN&dlK)JDH3L%Px@}yz2shB)+z^9(&J+H)6^y3B62WL0{^AiZLev3@|4SN) z@C)npv^yLQ;5}Uln1+PsucO_8Nce+Rz#hR1tnyz&KQ~#+@2{m_bScdLe^1AGdc5@h zUrd*I0z82>5(ybV5kSemru8A$uh-E#a^Y`~pNGg5rbpQa`yLp&S{t2yvhc@+Uxggo zMc6%~d#&G33*I%72>owftWB8l$}6w3`1k1@Fc1OC0j_!QXV0)zne4XoS*W-Rcd1|S zN8YjvMisTU91AAQo(4XhZ!p63gMUD}fWP296ukN9KC6G7ZpPt=zyli1ap+L?FRNYQ R|JwoO get _prefs => SharedPreferences.getInstance(); @@ -19,7 +18,7 @@ class AuthSharedPreferencesManager { Future getAccessToken() async { final prefs = await _prefs; - return prefs.getString(_keyOAuthAccessToken) ?? ''; + return prefs.getString(_keyOAuthAccessToken); } Future setAccessToken(String accessToken) async { @@ -34,7 +33,7 @@ class AuthSharedPreferencesManager { Future getAccessTokenExpiration() async { final prefs = await _prefs; - return prefs.getInt(_keyOAuthAccessTokenExpiration) ?? ''; + return prefs.getInt(_keyOAuthAccessTokenExpiration); } Future setAccessTokenExpiration(int accessTokenExpiration) async { @@ -49,7 +48,7 @@ class AuthSharedPreferencesManager { Future getRefreshToken() async { final prefs = await _prefs; - return prefs.getString(_keyOAuthRefreshToken) ?? ''; + return prefs.getString(_keyOAuthRefreshToken); } Future setRefreshToken(String refreshToken) async { @@ -64,7 +63,7 @@ class AuthSharedPreferencesManager { Future getRefreshTokenExpiration() async { final prefs = await _prefs; - return prefs.getString(_keyOAuthRefreshTokenExpiration) ?? ''; + return prefs.getString(_keyOAuthRefreshTokenExpiration); } Future setRefreshTokenExpiration(int refreshTokenExpiration) async { diff --git a/lib/src/ui/commons/assets.dart b/lib/src/ui/commons/assets.dart new file mode 100644 index 0000000..b025d74 --- /dev/null +++ b/lib/src/ui/commons/assets.dart @@ -0,0 +1,3 @@ +class AppAssets { + static const String loginLogoImage = 'assets/images/login_logo.png'; +} diff --git a/lib/src/ui/commons/colors.dart b/lib/src/ui/commons/colors.dart index 54f744b..4bf4a8d 100644 --- a/lib/src/ui/commons/colors.dart +++ b/lib/src/ui/commons/colors.dart @@ -3,17 +3,17 @@ import 'dart:ui'; import 'package:flutter/material.dart'; class AppColors { - ///Colors - static const Color blue = Colors.blue; - static const Color orange = Colors.deepOrange; - static const Color pink = Colors.pink; + /// Colors + static const Color colorBlue = Colors.blue; + static const Color colorOrange = Colors.deepOrange; + static const Color colorPink = Colors.pink; static const Color white = Colors.white; - static const Color black = Colors.black; + static const Color colorBlack = Colors.black; - /// Misc - static const Color errorColor = Colors.red; - static const Color warningColor = Colors.yellow; + /// Misc Colors static const Color successColor = Colors.green; + static const Color warningColor = Colors.yellow; + static const Color errorColor = Colors.red; ///Basics static const Color primaryColor = const Color(0xFF2196f3); diff --git a/lib/src/ui/commons/dimensions.dart b/lib/src/ui/commons/dimensions.dart index a2371d8..1bb0c43 100644 --- a/lib/src/ui/commons/dimensions.dart +++ b/lib/src/ui/commons/dimensions.dart @@ -1,9 +1,20 @@ class AppDimensions { + /// Default values + + static const double defaultCardElevation = 2.0; + static const double defaultCardPadding = 15.0; + static const double defaultFormInputPadding = 15.0; + /// App static const double menuButtonPadding = 3.0; + /// Auth Page + + static const double authPageMinHeight = 800.0; + /// Card + static const double cardDefaultElevation = 2.0; static const double cardDefaultPadding = 20.0; diff --git a/lib/src/ui/localizations/cv_localization.dart b/lib/src/ui/localizations/cv_localization.dart index f2fd2c8..603aed5 100644 --- a/lib/src/ui/localizations/cv_localization.dart +++ b/lib/src/ui/localizations/cv_localization.dart @@ -11,12 +11,15 @@ abstract class CVLocalizations { } /// App + /// String get appName; /// Auth Page + String get authTitle; /// Auth Stuff + String get authNoEmailTitle; String get authNotEmailExplain; @@ -87,9 +90,11 @@ abstract class CVLocalizations { String get accountMyProfile; /// Profile Page + String get profileTitle; /// Settings Page + String get settingsTitle; String get settingsThemeCTA; @@ -101,14 +106,17 @@ abstract class CVLocalizations { String get settingsThemeDark; /// Search Page + String get searchTitle; String get searchSearchBarHint; /// Profile Widget + String get profileWidgetDetails; /// Profile Widget List + String get profileListOptions; String get profileListSorting; @@ -130,9 +138,11 @@ abstract class CVLocalizations { String get partListLoadMore; /// Group Widget + String get groupWidgetDetails; /// Group Widget List + String get groupListOptions; String get groupListSorting; @@ -142,9 +152,11 @@ abstract class CVLocalizations { String get groupListLoadMore; /// Entry Widget + String get entryWidgetDetails; /// Entry Widget List + String get entryListOptions; String get entryListSorting; @@ -154,21 +166,77 @@ abstract class CVLocalizations { String get entryListLoadMore; /// Sort Dialog + String get sortDialogCancel; String get sortDialogConfirm; + /// Menu Widget + + String get menuPPCTA; + + String get menuToSCTA; + + /// Others + + String get middleDot; + + String get username; + + String get email; + + String get password; + + String get passwordRepeat; + + String get token; + + String get cancelCTA; + + String get settingsCTA; + + String get account; + + String get home; + + String get resume; + + String get profile; + + String get search; + + String get history; + + String get loadMore; + + String get errorOccurred; + + String get retryCTA; + + String get yesCTA; + + String get noCTA; + + String get moreCTA; + + String get notYetImplemented; + + String get notSupported; + /// Exception Error + String get exceptionFormatException; String get exceptionTimeoutException; - ///Api Error + /// Api Error + String get apiErrorWrongPasswordError; String get apiErrorUserNotFoundError; - ///Server Error : HTTP 400 + /// Server Error : HTTP 400 + String get httpClientErrorBadRequest; String get httpClientErrorPaymentRequired; @@ -200,6 +268,7 @@ abstract class CVLocalizations { String get httpClientErrorUpgradeRequired; ///Server Error : HTTP 500 + String get httpServerErrorInternalServerError; String get httpServerErrorNotImplemented; @@ -211,57 +280,6 @@ abstract class CVLocalizations { String get httpServerErrorGatewayTimeout; String get httpServerErrorHttpVersionNotSupported; - - ///Menu Widget - - String get menuPPCTA; - - String get menuToSCTA; - - ///Others - String get middleDot; - - String get username; - - String get email; - - String get password; - - String get passwordRepeat; - - String get token; - - String get cancelCTA; - - String get settingsCTA; - - String get account; - - String get home; - - String get resume; - - String get profile; - - String get search; - - String get history; - - String get loadMore; - - String get errorOccurred; - - String get retryCTA; - - String get yesCTA; - - String get noCTA; - - String get moreCTA; - - String get notYetImplemented; - - String get notSupported; } class CVLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/src/ui/localizations/cv_localization_en.dart b/lib/src/ui/localizations/cv_localization_en.dart index 79a1f44..13ec191 100644 --- a/lib/src/ui/localizations/cv_localization_en.dart +++ b/lib/src/ui/localizations/cv_localization_en.dart @@ -8,10 +8,12 @@ class CVLocalizationsEN implements CVLocalizations { const CVLocalizationsEN(); /// App + @override String get appName => 'Social CV'; /// Auth Page + @override String get authTitle => 'Connection'; @@ -98,6 +100,7 @@ class CVLocalizationsEN implements CVLocalizations { String get authOr => 'OR'; /// Home Page + @override String get homeTitle => 'Social CV'; @@ -119,10 +122,12 @@ class CVLocalizationsEN implements CVLocalizations { String get accountMyProfile => 'My profiles'; /// Profile Page + @override String get profileTitle => 'Profile'; /// Settings Page + @override String get settingsTitle => 'Settings'; @@ -139,6 +144,7 @@ class CVLocalizationsEN implements CVLocalizations { String get settingsThemeDark => 'Dark'; /// Search Page + @override String get searchTitle => 'Search'; @@ -146,9 +152,11 @@ class CVLocalizationsEN implements CVLocalizations { String get searchSearchBarHint => 'Search resume...'; /// Profile Widget + String get profileWidgetDetails => 'Profile details'; /// Profile Widget List + String get profileListOptions => 'Profile options'; String get profileListSorting => 'Sorting Profiles'; @@ -158,9 +166,11 @@ class CVLocalizationsEN implements CVLocalizations { String get profileListLoadMore => 'Load more profiles'; /// Part Widget + String get partWidgetDetails => 'Part détails'; /// Part Widget List + @override String get partListOptions => 'Part list options'; @@ -174,9 +184,11 @@ class CVLocalizationsEN implements CVLocalizations { String get partListLoadMore => 'Load more parts'; /// Group Widget + String get groupWidgetDetails => 'Group détails'; /// Group Widget List + @override String get groupListOptions => 'Group list options'; @@ -190,9 +202,11 @@ class CVLocalizationsEN implements CVLocalizations { String get groupListLoadMore => 'Load more groups'; /// Entry Widget + String get entryWidgetDetails => 'Entry détails'; /// Entry Widget List + @override String get entryListOptions => 'Entry list options'; @@ -213,158 +227,163 @@ class CVLocalizationsEN implements CVLocalizations { String get sortDialogConfirm => 'Confirm'; /// Menu Widget + @override String get menuPPCTA => 'Privacy Policy'; @override String get menuToSCTA => 'Terms of Service'; - /// Exception Error + /// Others + @override - String get exceptionFormatException => 'Exception : Wrong Format'; + String get middleDot => '·'; @override - String get exceptionTimeoutException => 'Exception : Request Timeout'; + String get username => 'Username'; - /// Api Error @override - String get apiErrorWrongPasswordError => 'Wrong password'; + String get email => 'Email'; @override - String get apiErrorUserNotFoundError => 'User not found'; + String get password => 'Password'; - /// Server Error : HTTP 400 @override - String get httpClientErrorBadRequest => 'Bad request'; + String get passwordRepeat => 'Repeat Password'; @override - String get httpClientErrorPaymentRequired => 'Payment required'; + String get token => 'Token'; @override - String get httpClientErrorForbidden => 'Forbidden'; + String get cancelCTA => 'Cancel'; @override - String get httpClientErrorNotFound => 'Not found'; + String get settingsCTA => 'Settings'; @override - String get httpClientErrorMethodNotAllowed => 'Not allowed'; + String get account => 'Account'; @override - String get httpClientErrorNotAcceptable => 'Not acceptable'; + String get home => 'Home'; @override - String get httpClientErrorRequestTimeout => 'Request timeout'; + String get resume => 'Resume'; @override - String get httpClientErrorConflict => 'Conflict'; + String get profile => 'Profile'; @override - String get httpClientErrorGone => 'Gone'; + String get search => 'Search'; @override - String get httpClientErrorLengthRequired => 'Length required'; + String get history => 'History'; @override - String get httpClientErrorPayloadTooLarge => 'Payload too large'; + String get loadMore => 'Load more'; @override - String get httpClientErrorURITooLong => 'URI too long'; + String get errorOccurred => 'An error occurred'; @override - String get httpClientErrorUnsupportedMediaType => 'Unsupported media type'; + String get retryCTA => 'Retry'; @override - String get httpClientErrorExpectationFailed => 'Expectation Failed'; + String get yesCTA => 'Yes'; @override - String get httpClientErrorUpgradeRequired => 'Upgrade required'; + String get noCTA => 'No'; - /// Server Error : HTTP 500 @override - String get httpServerErrorInternalServerError => 'Internal Server Error'; + String get moreCTA => 'More'; @override - String get httpServerErrorNotImplemented => 'Not implemented'; + String get notYetImplemented => 'Not yet implemented'; @override - String get httpServerErrorBadGateway => 'Bad Gateway'; + String get notSupported => 'Not supported'; + /// Exception Error @override - String get httpServerErrorServiceUnavailable => 'Service Unavailable'; + String get exceptionFormatException => 'Exception : Wrong Format'; @override - String get httpServerErrorGatewayTimeout => 'Gateway Timeout'; + String get exceptionTimeoutException => 'Exception : Request Timeout'; + + /// Api Error @override - String get httpServerErrorHttpVersionNotSupported => - 'HTTP Version Not Supported'; + String get apiErrorWrongPasswordError => 'Wrong password'; - /// Others @override - String get middleDot => '·'; + String get apiErrorUserNotFoundError => 'User not found'; + + /// Server Error : HTTP 400 @override - String get username => 'Username'; + String get httpClientErrorBadRequest => 'Bad request'; @override - String get email => 'Email'; + String get httpClientErrorPaymentRequired => 'Payment required'; @override - String get password => 'Password'; + String get httpClientErrorForbidden => 'Forbidden'; @override - String get passwordRepeat => 'Repeat Password'; + String get httpClientErrorNotFound => 'Not found'; @override - String get token => 'Token'; + String get httpClientErrorMethodNotAllowed => 'Not allowed'; @override - String get cancelCTA => 'Cancel'; + String get httpClientErrorNotAcceptable => 'Not acceptable'; @override - String get settingsCTA => 'Settings'; + String get httpClientErrorRequestTimeout => 'Request timeout'; @override - String get account => 'Account'; + String get httpClientErrorConflict => 'Conflict'; @override - String get home => 'Home'; + String get httpClientErrorGone => 'Gone'; @override - String get resume => 'Resume'; + String get httpClientErrorLengthRequired => 'Length required'; @override - String get profile => 'Profile'; + String get httpClientErrorPayloadTooLarge => 'Payload too large'; @override - String get search => 'Search'; + String get httpClientErrorURITooLong => 'URI too long'; @override - String get history => 'History'; + String get httpClientErrorUnsupportedMediaType => 'Unsupported media type'; @override - String get loadMore => 'Load more'; + String get httpClientErrorExpectationFailed => 'Expectation Failed'; @override - String get errorOccurred => 'An error occurred'; + String get httpClientErrorUpgradeRequired => 'Upgrade required'; + + /// Server Error : HTTP 500 @override - String get retryCTA => 'Retry'; + String get httpServerErrorInternalServerError => 'Internal Server Error'; @override - String get yesCTA => 'Yes'; + String get httpServerErrorNotImplemented => 'Not implemented'; @override - String get noCTA => 'No'; + String get httpServerErrorBadGateway => 'Bad Gateway'; @override - String get moreCTA => 'More'; + String get httpServerErrorServiceUnavailable => 'Service Unavailable'; @override - String get notYetImplemented => 'Not yet implemented'; + String get httpServerErrorGatewayTimeout => 'Gateway Timeout'; @override - String get notSupported => 'Not supported'; + String get httpServerErrorHttpVersionNotSupported => + 'HTTP Version Not Supported'; /// Creates an object that provides US English resource values for the /// application. diff --git a/lib/src/ui/localizations/cv_localization_fr.dart b/lib/src/ui/localizations/cv_localization_fr.dart index 97315b0..02bc064 100644 --- a/lib/src/ui/localizations/cv_localization_fr.dart +++ b/lib/src/ui/localizations/cv_localization_fr.dart @@ -7,10 +7,13 @@ import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.da class CVLocalizationsFR implements CVLocalizations { const CVLocalizationsFR(); + /// App + @override String get appName => 'Social CV'; /// Auth Page + @override String get authTitle => 'Connexion'; @@ -97,6 +100,7 @@ class CVLocalizationsFR implements CVLocalizations { String get authOr => 'OU'; /// Home Page + @override String get homeTitle => 'Social CV'; @@ -118,6 +122,7 @@ class CVLocalizationsFR implements CVLocalizations { String get accountMyProfile => 'Mes profils'; /// Profile Page + @override String get profileTitle => 'Profil'; @@ -142,6 +147,7 @@ class CVLocalizationsFR implements CVLocalizations { String get settingsThemeDark => 'Sombre'; /// Search Page + @override String get searchTitle => 'Recherche'; @@ -149,9 +155,11 @@ class CVLocalizationsFR implements CVLocalizations { String get searchSearchBarHint => 'Rechercher un profil ...'; /// Profile Widget + String get profileWidgetDetails => 'Détails du profile'; - ///Profile Widget List + /// Profile Widget List + String get profileListOptions => 'Options profiles'; String get profileListSorting => 'Trier Profiles'; @@ -161,9 +169,11 @@ class CVLocalizationsFR implements CVLocalizations { String get profileListLoadMore => 'Charger plus de profiles'; /// Part Widget + String get partWidgetDetails => 'Détails de la partie'; /// Part Widget List + @override String get partListOptions => 'Options'; @@ -177,9 +187,11 @@ class CVLocalizationsFR implements CVLocalizations { String get partListLoadMore => 'charger plus de parties'; /// Group Widget + String get groupWidgetDetails => 'Détails du groupe'; /// Group Widget List + @override String get groupListOptions => 'Options'; @@ -193,9 +205,11 @@ class CVLocalizationsFR implements CVLocalizations { String get groupListLoadMore => 'charger plus de groupes'; /// Entry Widget + String get entryWidgetDetails => 'Détails de l\'entrée'; /// Entry Widget List + @override String get entryListOptions => 'Options'; @@ -209,167 +223,172 @@ class CVLocalizationsFR implements CVLocalizations { String get entryListLoadMore => 'Charger plus de entrées'; /// Sort Dialog + @override String get sortDialogCancel => 'Annuler'; @override String get sortDialogConfirm => 'Valider'; - /// Exception Error - @override - String get exceptionFormatException => 'Exception : Mauvais Format'; + /// Menu Widget @override - String get exceptionTimeoutException => 'Exception : Requete Expiré'; + String get menuPPCTA => 'Politique de confidentialité'; - /// Api Error @override - String get apiErrorWrongPasswordError => 'Mauvais mot de passe'; + String get menuToSCTA => 'Termes de Service'; + /// Others @override - String get apiErrorUserNotFoundError => 'Utilisateur introuvable'; + String get middleDot => '·'; - /// Server Error : HTTP 400 @override - String get httpClientErrorBadRequest => 'Mauvaise requete'; + String get username => 'Nom d\'utilisateur'; @override - String get httpClientErrorPaymentRequired => 'Paiement requis'; + String get email => 'Email'; @override - String get httpClientErrorForbidden => 'Interdit'; + String get password => 'Mot de passe'; @override - String get httpClientErrorNotFound => 'Introuvable'; + String get passwordRepeat => 'Password répété'; @override - String get httpClientErrorMethodNotAllowed => 'Non autorisé'; + String get token => 'Jeton'; @override - String get httpClientErrorNotAcceptable => 'Non acceptable'; + String get cancelCTA => 'Annuler'; @override - String get httpClientErrorRequestTimeout => 'Requete expirée'; + String get account => 'Compte'; @override - String get httpClientErrorConflict => 'Conflit'; + String get home => 'Accueil'; @override - String get httpClientErrorGone => 'Disparu'; + String get resume => 'CV'; @override - String get httpClientErrorLengthRequired => 'Taille requise'; + String get profile => 'Profil'; @override - String get httpClientErrorPayloadTooLarge => 'Payload trop large'; + String get search => 'Rechercher'; @override - String get httpClientErrorURITooLong => 'Lien trop long'; + String get history => 'Historique'; @override - String get httpClientErrorUnsupportedMediaType => - 'Type de média non supporté'; + String get loadMore => 'Charger plus'; @override - String get httpClientErrorExpectationFailed => 'Échoué'; + String get errorOccurred => 'Une erreur s\'est produite'; @override - String get httpClientErrorUpgradeRequired => 'Mise à jour requise'; + String get retryCTA => 'Re-éssayer'; - /// Server Error : HTTP 500 @override - String get httpServerErrorInternalServerError => 'Erreur Serveur interne'; + String get yesCTA => 'Oui'; @override - String get httpServerErrorNotImplemented => 'Non implementé'; + String get noCTA => 'Non'; @override - String get httpServerErrorBadGateway => 'Mauvaise passerelle'; + String get authSignInSucceed => 'Connecté'; @override - String get httpServerErrorServiceUnavailable => 'Service non disponible '; + String get moreCTA => 'Plus'; @override - String get httpServerErrorGatewayTimeout => 'Temps ecoulé'; + String get notYetImplemented => 'Pas encore implémenté'; @override - String get httpServerErrorHttpVersionNotSupported => - 'Version HTTP non supporté'; + String get notSupported => 'Non supporté'; - /// Menu Widget + /// Exception Error @override - String get menuPPCTA => 'Politique de confidentialité'; + String get exceptionFormatException => 'Exception : Mauvais Format'; @override - String get menuToSCTA => 'Termes de Service'; + String get exceptionTimeoutException => 'Exception : Requete Expiré'; + + /// Api Error - /// Others @override - String get middleDot => '·'; + String get apiErrorWrongPasswordError => 'Mauvais mot de passe'; @override - String get username => 'Nom d\'utilisateur'; + String get apiErrorUserNotFoundError => 'Utilisateur introuvable'; + + /// Server Error : HTTP 400 @override - String get email => 'Email'; + String get httpClientErrorBadRequest => 'Mauvaise requete'; @override - String get password => 'Mot de passe'; + String get httpClientErrorPaymentRequired => 'Paiement requis'; @override - String get passwordRepeat => 'Password répété'; + String get httpClientErrorForbidden => 'Interdit'; @override - String get token => 'Jeton'; + String get httpClientErrorNotFound => 'Introuvable'; @override - String get cancelCTA => 'Annuler'; + String get httpClientErrorMethodNotAllowed => 'Non autorisé'; @override - String get account => 'Compte'; + String get httpClientErrorNotAcceptable => 'Non acceptable'; @override - String get home => 'Accueil'; + String get httpClientErrorRequestTimeout => 'Requete expirée'; @override - String get resume => 'CV'; + String get httpClientErrorConflict => 'Conflit'; @override - String get profile => 'Profil'; + String get httpClientErrorGone => 'Disparu'; @override - String get search => 'Rechercher'; + String get httpClientErrorLengthRequired => 'Taille requise'; @override - String get history => 'Historique'; + String get httpClientErrorPayloadTooLarge => 'Payload trop large'; @override - String get loadMore => 'Charger plus'; + String get httpClientErrorURITooLong => 'Lien trop long'; @override - String get errorOccurred => 'Une erreur s\'est produite'; + String get httpClientErrorUnsupportedMediaType => + 'Type de média non supporté'; @override - String get retryCTA => 'Re-éssayer'; + String get httpClientErrorExpectationFailed => 'Échoué'; @override - String get yesCTA => 'Oui'; + String get httpClientErrorUpgradeRequired => 'Mise à jour requise'; + + /// Server Error : HTTP 500 @override - String get noCTA => 'Non'; + String get httpServerErrorInternalServerError => 'Erreur Serveur interne'; @override - String get authSignInSucceed => 'Connecté'; + String get httpServerErrorNotImplemented => 'Non implementé'; @override - String get moreCTA => 'Plus'; + String get httpServerErrorBadGateway => 'Mauvaise passerelle'; @override - String get notYetImplemented => 'Pas encore implémenté'; + String get httpServerErrorServiceUnavailable => 'Service non disponible '; @override - String get notSupported => 'Non supporté'; + String get httpServerErrorGatewayTimeout => 'Temps ecoulé'; + + @override + String get httpServerErrorHttpVersionNotSupported => + 'Version HTTP non supporté'; /// Creates an object that provides US English resource values for the /// application. diff --git a/lib/src/ui/pages/auth_page.dart b/lib/src/ui/pages/auth_page.dart index cc539f1..9a2cf2e 100644 --- a/lib/src/ui/pages/auth_page.dart +++ b/lib/src/ui/pages/auth_page.dart @@ -1,103 +1,83 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_dart_common/blocs.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/assets.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/login_form_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/register_form_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; class AuthPage extends StatefulWidget { - AuthPage({Key key}) : super(key: key); - @override - State createState() => _AuthPageState(); + _AuthPageState createState() => _AuthPageState(); } -class _AuthPageState extends State - with SingleTickerProviderStateMixin { +class _AuthPageState extends State { final String _tag = '$_AuthPageState'; - final GlobalKey _scaffoldKey = new GlobalKey(); + // Variable + double screenWidth; + double screenHeight; + + static const _kDuration = const Duration(milliseconds: 300); + + static const _kCurve = Curves.ease; PageController _pageController; - Color left = Colors.black; - Color right = Colors.white; + // Business + @override + void initState() { + print('$_tag:$initState()'); + super.initState(); + _pageController = PageController(); + } + + @override + void dispose() { + print('$_tag:$dispose()'); + _pageController?.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + print('$_tag:$build'); + screenHeight = MediaQuery.of(context).size.height; + screenWidth = MediaQuery.of(context).size.width; + + screenHeight = (screenHeight > AppDimensions.authPageMinHeight) + ? screenHeight + : AppDimensions.authPageMinHeight; return Scaffold( - key: _scaffoldKey, - body: NotificationListener( - onNotification: (overScroll) { - overScroll.disallowGlow(); - }, + backgroundColor: AppColors.primaryColor, + body: BlocListenerTree( + blocListeners: [ + BlocListener( + bloc: BlocProvider.of(context), + listener: (BuildContext context, AuthenticationState state) { + if (state is AuthenticationAuthenticated) { + Scaffold.of(context).showSnackBar(SnackBar( + backgroundColor: AppColors.successColor, + content: Text(CVLocalizations.of(context).authSignInSucceed), + )); + Future.delayed(Duration(seconds: 1)) + .then((_) => Navigator.of(context).pop()); + } + }, + ), + ], child: SingleChildScrollView( child: Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height >= 775.0 - ? MediaQuery.of(context).size.height - : 775.0, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - AppColors.loginGradientStart, - AppColors.loginGradientEnd - ], - begin: const FractionalOffset(0.0, 0.0), - end: const FractionalOffset(1.0, 1.0), - stops: [0.0, 1.0], - tileMode: TileMode.clamp, - ), - ), - child: Column( - mainAxisSize: MainAxisSize.max, + height: screenHeight, + child: Stack( children: [ - Padding( - padding: EdgeInsets.only(top: 75.0), - child: Image( - width: 250.0, - height: 191.0, - fit: BoxFit.fill, - image: new AssetImage('assets/img/login_logo.png'), - ), - ), - Padding( - padding: EdgeInsets.only(top: 20.0), - child: _buildMenuBar(context), - ), - Expanded( - flex: 2, - child: PageView( - controller: _pageController, - onPageChanged: (i) { - if (i == 0) { - setState(() { - right = Colors.white; - left = Colors.black; - }); - } else if (i == 1) { - setState(() { - right = Colors.black; - left = Colors.white; - }); - } - }, - children: [ - new ConstrainedBox( - constraints: const BoxConstraints.expand(), - child: LoginFormWidget(), - ), - new ConstrainedBox( - constraints: const BoxConstraints.expand(), - child: RegisterFormWidget(), - ), - ], - ), - ), + _buildHeaderSection(context), + _buildAuthSection(context) ], ), ), @@ -106,129 +86,171 @@ class _AuthPageState extends State ); } - @override - void dispose() { - _pageController?.dispose(); - super.dispose(); + Widget _buildHeaderSection(BuildContext context) { + return Container( + height: screenHeight * 0.25, + width: screenWidth, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Image.asset( + AppAssets.loginLogoImage, + fit: BoxFit.scaleDown, + ), + Text( + CVLocalizations.of(context).authTitle, + style: Theme.of(context) + .textTheme + .title + .copyWith(color: AppColors.white), + ), + ], + ), + ); } - @override - void initState() { - super.initState(); - -// SystemChrome.setPreferredOrientations([ -// DeviceOrientation.portraitUp, -// DeviceOrientation.portraitDown, -// ]); - - _pageController = PageController(); + Widget _buildAuthSection(BuildContext context) { + return Stack( + children: [ + _buildAuthPageView(context), + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + height: screenHeight * 0.25, + ), + Container( + height: screenHeight * 0.05, + color: Colors.transparent, + child: DotsIndicator( + color: AppColors.white, + controller: _pageController, + itemCount: 2, + onPageSelected: (int page) { + _pageController.animateToPage( + page, + duration: _kDuration, + curve: _kCurve, + ); + }, + ), + ), + ], + ), + ], + ); } - Widget _buildMenuBar(BuildContext context) { - return Container( - width: 300.0, - height: 50.0, - decoration: BoxDecoration( - color: Color(0x552B2B2B), - borderRadius: BorderRadius.all(Radius.circular(25.0)), - ), - child: CustomPaint( - painter: TabIndicationPainter(pageController: _pageController), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + Widget _buildAuthPageView(BuildContext context) { + return PageView( + controller: _pageController, + children: [ + Column( children: [ - Expanded( - child: FlatButton( - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - onPressed: _onSignInButtonPress, - child: Text( - CVLocalizations.of(context).authSignIn, - style: TextStyle( - color: left, - fontSize: 16.0, - ), - ), + Container( + height: screenHeight * 0.25, + ), + Container( + height: screenHeight * 0.05, + ), + Container( + height: screenHeight * 0.70, + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [LoginFormWidget()], ), ), - //Container(height: 33.0, width: 1.0, color: Colors.white), - Expanded( - child: FlatButton( - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - onPressed: _onSignUpButtonPress, - child: Text( - CVLocalizations.of(context).authSignUp, - style: TextStyle( - color: right, - fontSize: 16.0, - ), - ), + ], + ), + Column( + children: [ + Container( + height: screenHeight * 0.25, + ), + Container( + height: screenHeight * 0.05, + ), + Container( + height: screenHeight * 0.70, + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [RegisterFormWidget()], ), ), ], ), - ), + ], ); } +} - void _onSignInButtonPress() { - _pageController.animateToPage(0, - duration: Duration(milliseconds: 500), curve: Curves.decelerate); - } +/// An indicator showing the currently selected page of a PageController +class DotsIndicator extends AnimatedWidget { + DotsIndicator({ + this.controller, + this.itemCount, + this.onPageSelected, + this.color: Colors.white, + }) : super(listenable: controller); - void _onSignUpButtonPress() { - _pageController?.animateToPage(1, - duration: Duration(milliseconds: 500), curve: Curves.decelerate); - } -} + /// The PageController that this DotsIndicator is representing. + final PageController controller; -class TabIndicationPainter extends CustomPainter { - Paint painter; - final double dxTarget; - final double dxEntry; - final double radius; - final double dy; - - final PageController pageController; - - TabIndicationPainter( - {this.dxTarget = 125.0, - this.dxEntry = 25.0, - this.radius = 21.0, - this.dy = 25.0, - this.pageController}) - : super(repaint: pageController) { - painter = new Paint() - ..color = Color(0xFFFFFFFF) - ..style = PaintingStyle.fill; - } + /// The number of items managed by the PageController + final int itemCount; - @override - void paint(Canvas canvas, Size size) { - final pos = pageController.position; - double fullExtent = - (pos.maxScrollExtent - pos.minScrollExtent + pos.viewportDimension); - - double pageOffset = pos.extentBefore / fullExtent; - - bool left2right = dxEntry < dxTarget; - Offset entry = new Offset(left2right ? dxEntry : dxTarget, dy); - Offset target = new Offset(left2right ? dxTarget : dxEntry, dy); - - Path path = new Path(); - path.addArc( - new Rect.fromCircle(center: entry, radius: radius), 0.5 * pi, 1 * pi); - path.addRect( - new Rect.fromLTRB(entry.dx, dy - radius, target.dx, dy + radius)); - path.addArc( - new Rect.fromCircle(center: target, radius: radius), 1.5 * pi, 1 * pi); - - canvas.translate(size.width * pageOffset, 0.0); - canvas.drawShadow(path, Color(0xFFfbab66), 3.0, true); - canvas.drawPath(path, painter); + /// Called when a dot is tapped + final ValueChanged onPageSelected; + + /// The color of the dots. + /// + /// Defaults to `Colors.white`. + final Color color; + + // The base size of the dots + static const double _kDotSize = 8.0; + + // The increase in the size of the selected dot + static const double _kMaxZoom = 2.0; + + // The distance between the center of each dot + static const double _kDotSpacing = 25.0; + + Widget _buildDot(int index) { + double selectedness = Curves.easeOut.transform( + max( + 0.0, + 1.0 - ((controller.page ?? controller.initialPage) - index).abs(), + ), + ); + double zoom = 1.0 + (_kMaxZoom - 1.0) * selectedness; + return new Container( + width: _kDotSpacing, + child: new Center( + child: new Material( + elevation: AppDimensions.defaultCardElevation, + color: color, + type: MaterialType.circle, + child: new Container( + width: _kDotSize * zoom, + height: _kDotSize * zoom, + child: new InkWell( + onTap: () => onPageSelected(index), + ), + ), + ), + ), + ); } - @override - bool shouldRepaint(TabIndicationPainter oldDelegate) => true; + Widget build(BuildContext context) { + return new Row( + mainAxisAlignment: MainAxisAlignment.center, + children: new List.generate(itemCount, _buildDot), + ); + } } diff --git a/lib/src/ui/widgets/account_tile_widget.dart b/lib/src/ui/widgets/account_tile_widget.dart index ab284a6..bedfd69 100644 --- a/lib/src/ui/widgets/account_tile_widget.dart +++ b/lib/src/ui/widgets/account_tile_widget.dart @@ -22,10 +22,11 @@ class _AccountTitleState extends State { return BlocBuilder( bloc: BlocProvider.of(context), builder: (BuildContext context, AuthenticationState state) { - if (state is AuthenticationAuthenticated) + if (state is AuthenticationAuthenticated) { return _AccountTileConnected(); - if (state is AuthenticationUnauthenticated) + } else if (state is AuthenticationUnauthenticated) { return _AccountTileNotConnected(); + } return ErrorTile(error: NotImplementedYetError()); }, ); @@ -66,7 +67,7 @@ class _AccountTileConnected extends StatelessWidget { ), ); } else if (state is AccountFailed) { - return Container(child: Text('${state.error}')); + return ErrorTile(error: state.error); } return ErrorTile(error: NotImplementedYetError()); }, diff --git a/lib/src/ui/widgets/elements/entry_list_widget.dart b/lib/src/ui/widgets/elements/entry_list_widget.dart index 8d4bfcc..6aa94e3 100644 --- a/lib/src/ui/widgets/elements/entry_list_widget.dart +++ b/lib/src/ui/widgets/elements/entry_list_widget.dart @@ -36,7 +36,7 @@ abstract class ComplexEntryListState entryListBloc = widget.entryListBloc; if (entryListBloc == null) { - final cvRepository = Provider.of(context); + final cvRepository = Provider.of(context, listen: false); entryListBloc = EntryListBloc(cvRepository: cvRepository); entryListBloc.dispatch(EntryListInitialized( parentGroupId: widget.parentGroupId, diff --git a/lib/src/ui/widgets/elements/entry_widget.dart b/lib/src/ui/widgets/elements/entry_widget.dart index 7fd1bc1..15d05b1 100644 --- a/lib/src/ui/widgets/elements/entry_widget.dart +++ b/lib/src/ui/widgets/elements/entry_widget.dart @@ -28,7 +28,8 @@ abstract class EntryWidgetState extends State { entryBloc = widget.entryBloc; if (entryBloc == null) { - final cvRepository = Provider.of(context); + final cvRepository = Provider.of(context, listen: false); + entryBloc = EntryBloc(cvRepository: cvRepository); entryBloc.dispatch(EntryInitialized( entryId: widget.entryId, diff --git a/lib/src/ui/widgets/elements/group_list_widget.dart b/lib/src/ui/widgets/elements/group_list_widget.dart index c5eca8f..651692e 100644 --- a/lib/src/ui/widgets/elements/group_list_widget.dart +++ b/lib/src/ui/widgets/elements/group_list_widget.dart @@ -34,7 +34,8 @@ abstract class GroupListWidgetState groupListBloc = widget.groupListBloc; if (widget.groupListBloc == null) { - final cvRepository = Provider.of(context); + final cvRepository = Provider.of(context, listen: false); + groupListBloc = GroupListBloc(cvRepository: cvRepository); groupListBloc.dispatch(GroupListInitialized( parentPartId: widget.parentPartId, diff --git a/lib/src/ui/widgets/elements/group_widget.dart b/lib/src/ui/widgets/elements/group_widget.dart index 338e3e4..e4b492a 100644 --- a/lib/src/ui/widgets/elements/group_widget.dart +++ b/lib/src/ui/widgets/elements/group_widget.dart @@ -28,7 +28,8 @@ abstract class GroupWidgetState extends State { groupBloc = widget.groupBloc; if (groupBloc == null) { - final cvRepository = Provider.of(context); + final cvRepository = Provider.of(context, listen: false); + groupBloc = GroupBloc(cvRepository: cvRepository); groupBloc.dispatch(GroupInitialized( groupId: widget.groupId, diff --git a/lib/src/ui/widgets/elements/part_list_widget.dart b/lib/src/ui/widgets/elements/part_list_widget.dart index e47ae39..3351811 100644 --- a/lib/src/ui/widgets/elements/part_list_widget.dart +++ b/lib/src/ui/widgets/elements/part_list_widget.dart @@ -33,7 +33,8 @@ abstract class PartListWidgetState extends State { partListBloc = widget.partListBloc; if (widget.partListBloc == null) { - final cvRepository = Provider.of(context); + final cvRepository = Provider.of(context, listen: false); + partListBloc = PartListBloc(cvRepository: cvRepository); partListBloc.dispatch(PartListInitialized( parentProfileId: widget.parentProfileId, diff --git a/lib/src/ui/widgets/elements/part_widget.dart b/lib/src/ui/widgets/elements/part_widget.dart index 663324e..24db6eb 100644 --- a/lib/src/ui/widgets/elements/part_widget.dart +++ b/lib/src/ui/widgets/elements/part_widget.dart @@ -28,7 +28,8 @@ abstract class PartWidgetState extends State { partBloc = widget.partBloc; if (partBloc == null) { - final cvRepository = Provider.of(context); + final cvRepository = Provider.of(context, listen: false); + partBloc = PartBloc(cvRepository: cvRepository); partBloc.dispatch(PartInitialized( partId: widget.partId, diff --git a/lib/src/ui/widgets/elements/profile_list_widget.dart b/lib/src/ui/widgets/elements/profile_list_widget.dart index 2e90e8c..a9c65bb 100644 --- a/lib/src/ui/widgets/elements/profile_list_widget.dart +++ b/lib/src/ui/widgets/elements/profile_list_widget.dart @@ -33,7 +33,8 @@ abstract class ProfileListWidgetState super.initState(); profileListBloc = widget.profileListBloc; if (widget.profileListBloc == null) { - final cvRepository = Provider.of(context); + final cvRepository = Provider.of(context, listen: false); + profileListBloc = ProfileListBloc(cvRepository: cvRepository); profileListBloc.dispatch(ProfileListInitialized( parentUserId: widget.parentUserId, diff --git a/lib/src/ui/widgets/elements/profile_widget.dart b/lib/src/ui/widgets/elements/profile_widget.dart index 73f8c02..5b05155 100644 --- a/lib/src/ui/widgets/elements/profile_widget.dart +++ b/lib/src/ui/widgets/elements/profile_widget.dart @@ -28,7 +28,8 @@ abstract class ProfileWidgetState extends State { profileBloc = widget.profileBloc; if (profileBloc == null) { - final cvRepository = Provider.of(context); + final cvRepository = Provider.of(context, listen: false); + profileBloc = ProfileBloc(cvRepository: cvRepository); profileBloc.dispatch(ProfileInitialized( profileId: widget.profileId, diff --git a/lib/src/ui/widgets/error_widget.dart b/lib/src/ui/widgets/error_widget.dart index 4fad1dc..a16804d 100644 --- a/lib/src/ui/widgets/error_widget.dart +++ b/lib/src/ui/widgets/error_widget.dart @@ -6,6 +6,9 @@ import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; +/// [ErrorWidget] is a based widget for [Error] +/// +/// Must be initialized with an [error] abstract class ErrorWidget extends StatelessWidget { final Error error; @@ -14,7 +17,9 @@ abstract class ErrorWidget extends StatelessWidget { super(key: key); } -/// [ErrorIcon] is a [Icon] widget to display [Error] +/// [ErrorIcon] is a [Icon] like widget to display [Error] +/// +/// See [Icon] widget for more documentation class ErrorIcon extends StatelessWidget { final IconData icon; final double size; @@ -43,26 +48,58 @@ class ErrorIcon extends StatelessWidget { } } -/// [ErrorText] is a [Text] widget to display [Error] +/// [ErrorText] is a [Text] like widget like to display [Error] +/// +/// See [Text] widget for more documentation class ErrorText extends ErrorWidget { + final TextStyle style; + final StrutStyle strutStyle; + final TextAlign textAlign; + final TextDirection textDirection; + final Locale locale; + final bool softWrap; + final TextOverflow overflow; + final double textScaleFactor; + final int maxLines; + final String semanticsLabel; ErrorText({ Key key, @required Error error, + this.style, + this.strutStyle, this.textAlign = TextAlign.center, + this.textDirection, + this.locale, + this.softWrap, + this.overflow, + this.textScaleFactor, + this.maxLines, + this.semanticsLabel, }) : super(key: key, error: error); @override Widget build(BuildContext context) { return Text( translateError(context, error), + style: style, + strutStyle: strutStyle, textAlign: textAlign, + textDirection: textDirection, + locale: locale, + softWrap: softWrap, + overflow: overflow, + textScaleFactor: textScaleFactor, + maxLines: maxLines, + semanticsLabel: semanticsLabel, ); } } -/// [ErrorRow] is a [Row] widget to display [Error] +/// [ErrorRow] is a [Row] like widget to display [Error] +/// +/// See [Row] widget for more documentation class ErrorRow extends ErrorWidget { ErrorRow({Key key, @required Error error}) : super(key: key, error: error); @@ -78,7 +115,9 @@ class ErrorRow extends ErrorWidget { } } -/// [ErrorTile] is a [ListTile] widget to display [Error] +/// [ErrorTile] is a [ListTile] like widget to display [Error] +/// +/// See [ListTile] widget for more documentation class ErrorTile extends ErrorWidget { ErrorTile({Key key, @required Error error}) : super(key: key, error: error); @@ -87,12 +126,17 @@ class ErrorTile extends ErrorWidget { return ListTile( leading: ErrorIcon(), title: Text(CVLocalizations.of(context).errorOccurred), - subtitle: ErrorText(error: error), + subtitle: ErrorText( + error: error, + textAlign: TextAlign.left, + ), ); } } -/// [ErrorCard] is a [Card] widget to display [Error] +/// [ErrorCard] is a [Card] like widget to display [Error] +/// +/// See [Card] widget for more documentation class ErrorCard extends ErrorWidget { final double height; final double width; @@ -118,7 +162,9 @@ class ErrorCard extends ErrorWidget { } } -/// [ErrorList] is a [ListView] widget to display [Error] +/// [ErrorList] is a [ListView] like widget to display [Error] +/// +/// See [ListView] widget for more documentation class ErrorList extends ErrorWidget { /// List behaviors final Axis scrollDirection; @@ -148,7 +194,9 @@ class ErrorList extends ErrorWidget { } } -/// [ErrorPage] is a [Scaffold] widget to display [Error] +/// [ErrorPage] is a [Scaffold] like widget to display [Error] +/// +/// See [Scaffold] widget for more documentation class ErrorPage extends ErrorWidget { ErrorPage({Key key, @required Error error}) : super(key: key, error: error); @@ -160,6 +208,9 @@ class ErrorPage extends ErrorWidget { } } +/// [ErrorPage] is a [MaterialApp] like widget to display [Error] +/// +/// See [MaterialApp] widget for more documentation class ErrorApp extends ErrorWidget { ErrorApp({Key key, @required Error error}) : super(key: key, error: error); diff --git a/lib/src/ui/widgets/loading_widget.dart b/lib/src/ui/widgets/loading_widget.dart index 7d97c59..88e873a 100644 --- a/lib/src/ui/widgets/loading_widget.dart +++ b/lib/src/ui/widgets/loading_widget.dart @@ -5,9 +5,8 @@ import 'package:flutter/widgets.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; -/// LoadingCard displays lines with opacity moving up and down +/// [LoadingShadowContent] displays lines with opacity moving up and down /// Specify the number of loading lines to display - class LoadingShadowContent extends StatefulWidget { final int numberOfTitleLines; final int numberOfContentLines; @@ -114,6 +113,9 @@ class _LoadingShadowContentState extends State } } +/// [LoadingCard] is a [Card] like widget to display loading state +/// +/// See [Card] widget for more documentation class LoadingCard extends StatelessWidget { const LoadingCard({ Key key, @@ -144,7 +146,9 @@ class LoadingCard extends StatelessWidget { } } -/// A widget to list loading entries +/// [LoadingList] is a [ListView] like widget to display loading state +/// +/// See [ListView] widget for more documentation class LoadingList extends StatelessWidget { LoadingList({ @required this.count, @@ -175,8 +179,11 @@ class LoadingList extends StatelessWidget { } } -class LoadingPage extends StatelessWidget { - LoadingPage({Key key}) : super(key: key); +/// [LoadingScaffold] is a [Scaffold] like widget to display loading state +/// +/// See [Scaffold] widget for more documentation +class LoadingScaffold extends StatelessWidget { + LoadingScaffold({Key key}) : super(key: key); @override Widget build(BuildContext context) { @@ -186,13 +193,16 @@ class LoadingPage extends StatelessWidget { } } +/// [LoadingApp] is a [MaterialApp] like widget to display loading state +/// +/// See [MaterialApp] widget for more documentation class LoadingApp extends StatelessWidget { LoadingApp({Key key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( - home: LoadingPage(), + home: LoadingScaffold(), color: AppColors.primaryColor, ); } diff --git a/lib/src/ui/widgets/login_form_widget.dart b/lib/src/ui/widgets/login_form_widget.dart index 3d3135a..e70c02d 100644 --- a/lib/src/ui/widgets/login_form_widget.dart +++ b/lib/src/ui/widgets/login_form_widget.dart @@ -39,7 +39,7 @@ class _LoginFormWidgetState extends State { super.initState(); final authBloc = BlocProvider.of(context); - final cvRepository = Provider.of(context); + final cvRepository = Provider.of(context, listen: false); _loginBloc = LoginBloc( authenticationBloc: authBloc, @@ -83,18 +83,20 @@ class _LoginFormWidgetState extends State { children: [ Padding( padding: EdgeInsets.only( - top: 20.0, - bottom: 20.0, - left: 25.0, - right: 25.0), + top: 20.0, + bottom: 20.0, + left: 25.0, + right: 25.0, + ), child: TextField( focusNode: myFocusNodeEmailLogin, controller: loginEmailController, keyboardType: TextInputType.emailAddress, style: TextStyle( - fontSize: 16.0, color: Colors.black), + fontSize: 16.0, + color: Colors.black, + ), decoration: InputDecoration( - border: InputBorder.none, icon: Icon( MdiIcons.email, color: Colors.black, @@ -116,9 +118,10 @@ class _LoginFormWidgetState extends State { controller: loginPasswordController, obscureText: _obscureTextLogin, style: TextStyle( - fontSize: 16.0, color: Colors.black), + fontSize: 16.0, + color: Colors.black, + ), decoration: InputDecoration( - border: InputBorder.none, icon: Icon( MdiIcons.lock, size: 22.0, diff --git a/lib/src/ui/widgets/register_form_widget.dart b/lib/src/ui/widgets/register_form_widget.dart index 5e4dd89..d288989 100644 --- a/lib/src/ui/widgets/register_form_widget.dart +++ b/lib/src/ui/widgets/register_form_widget.dart @@ -39,7 +39,7 @@ class _RegisterFormWidgetState extends State { super.initState(); final authBloc = BlocProvider.of(context); - final cvRepository = Provider.of(context); + final cvRepository = Provider.of(context, listen: false); _registerBloc = RegisterBloc( cvRepository: cvRepository, @@ -63,7 +63,7 @@ class _RegisterFormWidgetState extends State { return BlocBuilder( bloc: _registerBloc, builder: (BuildContext context, RegisterState state) { - Container( + return Container( padding: EdgeInsets.only(top: 23.0), child: Column( children: [ @@ -93,12 +93,7 @@ class _RegisterFormWidgetState extends State { controller: signupFisrtNameController, keyboardType: TextInputType.text, textCapitalization: TextCapitalization.words, - style: TextStyle( - fontSize: 16.0, - color: Colors.black, - ), decoration: InputDecoration( - border: InputBorder.none, icon: Icon( MdiIcons.account, color: Colors.black, @@ -115,10 +110,11 @@ class _RegisterFormWidgetState extends State { ), Padding( padding: EdgeInsets.only( - top: 20.0, - bottom: 20.0, - left: 25.0, - right: 25.0), + top: 20.0, + bottom: 20.0, + left: 25.0, + right: 25.0, + ), child: TextField( focusNode: myFocusNodeFirstName, controller: signupLastNameController, @@ -129,7 +125,6 @@ class _RegisterFormWidgetState extends State { color: Colors.black, ), decoration: InputDecoration( - border: InputBorder.none, icon: Icon( MdiIcons.account, color: Colors.black, @@ -155,9 +150,10 @@ class _RegisterFormWidgetState extends State { controller: signupEmailController, keyboardType: TextInputType.emailAddress, style: TextStyle( - fontSize: 16.0, color: Colors.black), + fontSize: 16.0, + color: Colors.black, + ), decoration: InputDecoration( - border: InputBorder.none, icon: Icon( MdiIcons.email, color: Colors.black, @@ -183,9 +179,10 @@ class _RegisterFormWidgetState extends State { controller: signupPasswordController, obscureText: _obscureTextSignup, style: TextStyle( - fontSize: 16.0, color: Colors.black), + fontSize: 16.0, + color: Colors.black, + ), decoration: InputDecoration( - border: InputBorder.none, icon: Icon( MdiIcons.lock, color: Colors.black, diff --git a/lib/src/ui/widgets/repository_provider.dart b/lib/src/ui/widgets/repository_provider.dart new file mode 100644 index 0000000..ba12718 --- /dev/null +++ b/lib/src/ui/widgets/repository_provider.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class RepositoryProvider extends InheritedWidget { + static Type typeOf() => T; + + final CVRepository repository; + + const RepositoryProvider({ + Key key, + @required Widget child, + @required this.repository, + }) : super(key: key, child: child); + + @override + bool updateShouldNotify(InheritedWidget _) => false; + + static T of(BuildContext context) { + final type = typeOf>(); + return context.inheritFromWidgetOfExactType(type) as T; + } +} diff --git a/pubspec.lock b/pubspec.lock index 3cc929c..9ce1a2c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -437,9 +437,11 @@ packages: social_cv_client_dart_common: dependency: "direct main" description: - path: "..\\Social-CV-Client-Dart-common" - relative: true - source: path + path: "." + ref: "feature/blocs" + resolved-ref: "4f643a6b63a7eda24cb55a12c52d280d07aee4cc" + url: "https://github.com/axellebot/Social-CV-Client-Dart-common" + source: git version: "1.0.0" source_gen: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index 19b16f9..3c6f5ae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,10 +8,10 @@ dependencies: # Common Social CV Logic social_cv_client_dart_common: - path: ../Social-CV-Client-Dart-common - # git: - # url: https://github.com/axellebot/Social-CV-Client-Dart-common - # ref: develop +# path: ../Social-CV-Client-Dart-common + git: + url: https://github.com/axellebot/Social-CV-Client-Dart-common + ref: feature/blocs flutter: sdk: flutter @@ -70,7 +70,7 @@ flutter: - assets/images/account_card_details-blue.png - assets/images/default-avatar.png - assets/images/default-banner.jpg - + - assets/images/login_logo.png - config.json # secret data From b5e1e42bce2172b8e5964b4b9cd627e3bf89a502 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Wed, 17 Jul 2019 17:13:23 +0200 Subject: [PATCH 14/17] [TASK] Edited logger --- .gitignore | 76 +++- lib/main.dart | 2 +- lib/src/app.dart | 2 +- .../configuration/configuration_bloc.dart | 2 +- lib/src/router.dart | 2 +- lib/src/ui/pages/account_page.dart | 2 +- lib/src/ui/pages/auth_page.dart | 8 +- .../pages/elements/profile_profile_page.dart | 2 +- lib/src/ui/pages/home_page.dart | 2 +- lib/src/ui/pages/main_page.dart | 2 +- lib/src/ui/pages/search_page.dart | 2 +- lib/src/ui/pages/settings_page.dart | 2 +- lib/src/ui/widgets/account_tile_widget.dart | 2 +- .../ui/widgets/arc_banner_image_widget.dart | 2 +- .../widgets/initial_circle_avatar_widget.dart | 2 +- lib/src/ui/widgets/login_form_widget.dart | 339 ++++----------- .../ui/widgets/menu_bottom_sheet_widget.dart | 2 +- lib/src/ui/widgets/menu_button_widget.dart | 2 +- lib/src/ui/widgets/profile_image_widget.dart | 2 +- lib/src/ui/widgets/register_form_widget.dart | 391 ++++++------------ lib/src/ui/widgets/sort_dialog_widget.dart | 2 +- lib/src/ui/widgets/splash_widget.dart | 2 +- .../theme_switch_list_tile_widget.dart | 2 +- .../{logging_service.dart => logger.dart} | 0 lib/src/utils/utils.dart | 2 +- pubspec.yaml | 2 +- 26 files changed, 296 insertions(+), 560 deletions(-) rename lib/src/utils/{logging_service.dart => logger.dart} (100%) diff --git a/.gitignore b/.gitignore index 0cfb168..6c08bfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,77 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages + + # Created by https://www.gitignore.io/api/dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio # Edit at https://www.gitignore.io/?templates=dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio @@ -328,4 +402,4 @@ packages ios/.symlinks/ # Plugins -config.json \ No newline at end of file +config.json diff --git a/lib/main.dart b/lib/main.dart index a4e3ca3..8c0b604 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:social_cv_client_flutter/src/app.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; /// TODO: automatically set this to false for release builds const bool DEBUG_MODE = true; diff --git a/lib/src/app.dart b/lib/src/app.dart index 797984b..7931ae7 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -13,7 +13,7 @@ import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.da import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/splash_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; class ConfigWrapperApp extends StatefulWidget { const ConfigWrapperApp({Key key}) : super(key: key); diff --git a/lib/src/domain/blocs/configuration/configuration_bloc.dart b/lib/src/domain/blocs/configuration/configuration_bloc.dart index 7dc9bf8..09392c0 100644 --- a/lib/src/domain/blocs/configuration/configuration_bloc.dart +++ b/lib/src/domain/blocs/configuration/configuration_bloc.dart @@ -8,7 +8,7 @@ import 'package:social_cv_client_flutter/src/data/repositories/local_app_prefere import 'package:social_cv_client_flutter/src/data/repositories/local_auth_preferences_repository.dart'; import 'package:social_cv_client_flutter/src/data/repositories/local_config_repository.dart'; import 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; class ConfigurationBloc extends Bloc { final String _tag = '$ConfigurationBloc'; diff --git a/lib/src/router.dart b/lib/src/router.dart index c26405f..57569a4 100644 --- a/lib/src/router.dart +++ b/lib/src/router.dart @@ -9,7 +9,7 @@ import 'package:social_cv_client_flutter/src/ui/pages/elements/profile_profile_p import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/search_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/settings_page.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; class AppRouter { final String _tag = '$AppRouter'; diff --git a/lib/src/ui/pages/account_page.dart b/lib/src/ui/pages/account_page.dart index 6f52d94..adbcc85 100644 --- a/lib/src/ui/pages/account_page.dart +++ b/lib/src/ui/pages/account_page.dart @@ -5,7 +5,7 @@ import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; class AccountPage extends StatelessWidget { diff --git a/lib/src/ui/pages/auth_page.dart b/lib/src/ui/pages/auth_page.dart index 9a2cf2e..9e870c5 100644 --- a/lib/src/ui/pages/auth_page.dart +++ b/lib/src/ui/pages/auth_page.dart @@ -160,7 +160,9 @@ class _AuthPageState extends State { padding: EdgeInsets.all(10.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, - children: [LoginFormWidget()], + children: [ + LoginForm(), + ], ), ), ], @@ -178,7 +180,9 @@ class _AuthPageState extends State { padding: EdgeInsets.all(10.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, - children: [RegisterFormWidget()], + children: [ + RegisterForm(), + ], ), ), ], diff --git a/lib/src/ui/pages/elements/profile_profile_page.dart b/lib/src/ui/pages/elements/profile_profile_page.dart index b29d1fb..3201b54 100644 --- a/lib/src/ui/pages/elements/profile_profile_page.dart +++ b/lib/src/ui/pages/elements/profile_profile_page.dart @@ -8,7 +8,7 @@ import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_widget. import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; /// TODO : Build owner interaction with ProfileViewModel.owner diff --git a/lib/src/ui/pages/home_page.dart b/lib/src/ui/pages/home_page.dart index 53ee2de..57ae535 100644 --- a/lib/src/ui/pages/home_page.dart +++ b/lib/src/ui/pages/home_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; class HomePage extends StatelessWidget { final String _tag = '$HomePage'; diff --git a/lib/src/ui/pages/main_page.dart b/lib/src/ui/pages/main_page.dart index edc0fca..d6adaf6 100644 --- a/lib/src/ui/pages/main_page.dart +++ b/lib/src/ui/pages/main_page.dart @@ -5,7 +5,7 @@ import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.da import 'package:social_cv_client_flutter/src/ui/pages/account_page.dart'; import 'package:social_cv_client_flutter/src/ui/pages/home_page.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/menu_button_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; class MainPage extends StatefulWidget { diff --git a/lib/src/ui/pages/search_page.dart b/lib/src/ui/pages/search_page.dart index 3cd5786..cb288a5 100644 --- a/lib/src/ui/pages/search_page.dart +++ b/lib/src/ui/pages/search_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/commons/tags.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; /// Add controller for the input class SearchPage extends StatelessWidget { diff --git a/lib/src/ui/pages/settings_page.dart b/lib/src/ui/pages/settings_page.dart index c4f1af0..1509cbf 100644 --- a/lib/src/ui/pages/settings_page.dart +++ b/lib/src/ui/pages/settings_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; class SettingsPage extends StatelessWidget { const SettingsPage({ diff --git a/lib/src/ui/widgets/account_tile_widget.dart b/lib/src/ui/widgets/account_tile_widget.dart index bedfd69..0fbd294 100644 --- a/lib/src/ui/widgets/account_tile_widget.dart +++ b/lib/src/ui/widgets/account_tile_widget.dart @@ -6,7 +6,7 @@ import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; class AccountTile extends StatefulWidget { diff --git a/lib/src/ui/widgets/arc_banner_image_widget.dart b/lib/src/ui/widgets/arc_banner_image_widget.dart index bd99831..67899f1 100644 --- a/lib/src/ui/widgets/arc_banner_image_widget.dart +++ b/lib/src/ui/widgets/arc_banner_image_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; class ArcBannerImage extends StatelessWidget { final String _tag = '$ArcBannerImage'; diff --git a/lib/src/ui/widgets/initial_circle_avatar_widget.dart b/lib/src/ui/widgets/initial_circle_avatar_widget.dart index 480e753..bbc17f4 100644 --- a/lib/src/ui/widgets/initial_circle_avatar_widget.dart +++ b/lib/src/ui/widgets/initial_circle_avatar_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; class InitialCircleAvatar extends StatefulWidget { diff --git a/lib/src/ui/widgets/login_form_widget.dart b/lib/src/ui/widgets/login_form_widget.dart index e70c02d..9683033 100644 --- a/lib/src/ui/widgets/login_form_widget.dart +++ b/lib/src/ui/widgets/login_form_widget.dart @@ -2,25 +2,17 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; - -class LoginFormWidget extends StatefulWidget { - const LoginFormWidget({Key key}) : super(key: key); +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +class LoginForm extends StatefulWidget { @override - State createState() => _LoginFormWidgetState(); + State createState() => _LoginFormState(); } -class _LoginFormWidgetState extends State { - final String _tag = '$_LoginFormWidgetState'; - - _LoginFormWidgetState(); +class _LoginFormState extends State { + final String _tag = '$_LoginFormState'; final FocusNode myFocusNodeEmailLogin = FocusNode(); final FocusNode myFocusNodePasswordLogin = FocusNode(); @@ -30,288 +22,99 @@ class _LoginFormWidgetState extends State { bool _obscureTextLogin = true; - String errorText; - - LoginBloc _loginBloc; - - @override - void initState() { - super.initState(); - - final authBloc = BlocProvider.of(context); - final cvRepository = Provider.of(context, listen: false); - - _loginBloc = LoginBloc( - authenticationBloc: authBloc, - cvRepository: cvRepository, - ); - } - @override void dispose() { + print('$_tag:$dispose()'); + loginEmailController.dispose(); + loginPasswordController.dispose(); myFocusNodeEmailLogin.dispose(); myFocusNodePasswordLogin.dispose(); - _loginBloc?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + print('$_tag:$build'); + LoginBloc _loginBloc = BlocProvider.of(context); + + void _loginPressed() { + _loginBloc.dispatch(LoginButtonPressed( + email: loginEmailController.text, + password: loginPasswordController.text, + )); + } - return BlocBuilder( + return BlocListener( bloc: _loginBloc, - builder: (BuildContext context, LoginState state) { - return Container( - padding: EdgeInsets.only(top: 23.0), - child: Column( - children: [ - Stack( - alignment: Alignment.topCenter, - overflow: Overflow.visible, - children: [ - Card( - elevation: 2.0, - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), - ), - child: Container( - width: 300.0, - height: 190.0, - child: Column( - children: [ - Padding( - padding: EdgeInsets.only( - top: 20.0, - bottom: 20.0, - left: 25.0, - right: 25.0, - ), - child: TextField( - focusNode: myFocusNodeEmailLogin, - controller: loginEmailController, - keyboardType: TextInputType.emailAddress, - style: TextStyle( - fontSize: 16.0, - color: Colors.black, - ), - decoration: InputDecoration( - icon: Icon( - MdiIcons.email, - color: Colors.black, - size: 22.0, - ), - hintText: CVLocalizations.of(context).email, - hintStyle: TextStyle(fontSize: 17.0), - ), - ), - ), - Padding( - padding: EdgeInsets.only( - top: 20.0, - bottom: 20.0, - left: 25.0, - right: 25.0), - child: TextField( - focusNode: myFocusNodePasswordLogin, - controller: loginPasswordController, - obscureText: _obscureTextLogin, - style: TextStyle( - fontSize: 16.0, - color: Colors.black, - ), - decoration: InputDecoration( - icon: Icon( - MdiIcons.lock, - size: 22.0, - color: Colors.black, - ), - hintText: CVLocalizations.of(context).password, - hintStyle: TextStyle(fontSize: 17.0), - suffixIcon: GestureDetector( - onTap: _toggleLogin, - child: Icon( - MdiIcons.eye, - size: 15.0, - color: Colors.black, - ), - ), - ), - ), - ), - (state is LoginFailure) - ? ErrorRow(error: state.error) - : Container(), - ], - ), - ), - ), - Container( - margin: EdgeInsets.only(top: 170.0), - decoration: new BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - boxShadow: [ - BoxShadow( - color: AppColors.loginGradientStart, - offset: Offset(1.0, 6.0), - blurRadius: 20.0, - ), - BoxShadow( - color: AppColors.loginGradientEnd, - offset: Offset(1.0, 6.0), - blurRadius: 20.0, - ), - ], - gradient: new LinearGradient( - colors: [ - AppColors.loginGradientEnd, - AppColors.loginGradientStart - ], - begin: const FractionalOffset(0.2, 0.2), - end: const FractionalOffset(1.0, 1.0), - stops: [0.0, 1.0], - tileMode: TileMode.clamp), - ), - child: MaterialButton( - highlightColor: Colors.transparent, - splashColor: AppColors.loginGradientEnd, - //shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 42.0), - child: Text( - CVLocalizations.of(context).authSignInCTA, - style: TextStyle( - color: Colors.white, - fontSize: 25.0, - ), - ), - ), - onPressed: state is! RegisterLoading - ? _onLoginButtonPressed - : null, - ), - ), - ], - ), - Padding( - padding: EdgeInsets.only(top: 10.0), - child: FlatButton( - onPressed: () {}, - child: Text( - 'Forgot Password?', - style: TextStyle( - decoration: TextDecoration.underline, - color: Colors.white, - fontSize: 16.0, - ), - )), - ), - Padding( - padding: EdgeInsets.only(top: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - decoration: BoxDecoration( - gradient: new LinearGradient( - colors: [ - Colors.white10, - Colors.white, - ], - begin: const FractionalOffset(0.0, 0.0), - end: const FractionalOffset(1.0, 1.0), - stops: [0.0, 1.0], - tileMode: TileMode.clamp), - ), - width: 100.0, - height: 1.0, - ), - Padding( - padding: EdgeInsets.only(left: 15.0, right: 15.0), - child: Text( - 'Or', - style: TextStyle( - color: Colors.white, - fontSize: 16.0, - ), - ), - ), - Container( - decoration: BoxDecoration( - gradient: new LinearGradient( - colors: [ - Colors.white, - Colors.white10, - ], - begin: const FractionalOffset(0.0, 0.0), - end: const FractionalOffset(1.0, 1.0), - stops: [0.0, 1.0], - tileMode: TileMode.clamp), - ), - width: 100.0, - height: 1.0, - ), - ], - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ + listener: (BuildContext context, LoginState state) { + if (state is LoginFailure) { + Scaffold.of(context).showSnackBar( + SnackBar( + backgroundColor: AppColors.errorColor, + content: Text('${state.error.runtimeType}'), + ), + ); + } + }, + child: BlocBuilder( + bloc: _loginBloc, + builder: (BuildContext context, LoginState state) { + return Card( + elevation: AppDimensions.defaultCardElevation, + child: Padding( + padding: EdgeInsets.all(AppDimensions.defaultCardPadding), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center(child: Text('Login')), Padding( - padding: EdgeInsets.only(top: 10.0, right: 40.0), - child: GestureDetector( - onTap: () => Logger.log('Facebook button pressed'), - child: Container( - padding: const EdgeInsets.all(15.0), - decoration: new BoxDecoration( - shape: BoxShape.circle, - color: Colors.white, - ), - child: new Icon( - MdiIcons.facebook, - color: Color(0xFF0084ff), - ), + padding: const EdgeInsets.all( + AppDimensions.defaultFormInputPadding), + child: TextFormField( + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + controller: loginEmailController, + decoration: InputDecoration( + hintText: 'someone@email.com', + labelText: 'Email', ), ), ), Padding( - padding: EdgeInsets.only(top: 10.0), - child: GestureDetector( - onTap: () => Logger.log('Google button pressed'), - child: Container( - padding: const EdgeInsets.all(15.0), - decoration: new BoxDecoration( - shape: BoxShape.circle, - color: Colors.white, - ), - child: new Icon( - MdiIcons.google, - color: Color(0xFF0084ff), + padding: const EdgeInsets.all( + AppDimensions.defaultFormInputPadding), + child: TextFormField( + controller: loginPasswordController, + obscureText: _obscureTextLogin, + decoration: InputDecoration( + suffixIcon: IconButton( + icon: Icon(_obscureTextLogin + ? MdiIcons.eyeOffOutline + : MdiIcons.eyeOutline), + onPressed: _toggleLoginPassword, ), + labelText: 'Password', ), ), ), + MaterialButton( + child: Text('LOGIN'), + onPressed: state is! LoginLoading ? _loginPressed : null, + ), ], ), - ], - ), - ); - }, + ), + ); + }, + ), ); } - void _toggleLogin() { + void _toggleLoginPassword() { setState(() { _obscureTextLogin = !_obscureTextLogin; }); } - - _onLoginButtonPressed() { - _loginBloc.dispatch(LoginButtonPressed( - email: loginEmailController.text, - password: loginPasswordController.text, - )); - } } diff --git a/lib/src/ui/widgets/menu_bottom_sheet_widget.dart b/lib/src/ui/widgets/menu_bottom_sheet_widget.dart index 168685d..5598c5a 100644 --- a/lib/src/ui/widgets/menu_bottom_sheet_widget.dart +++ b/lib/src/ui/widgets/menu_bottom_sheet_widget.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/account_tile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; class MenuBottomSheet extends StatelessWidget { diff --git a/lib/src/ui/widgets/menu_button_widget.dart b/lib/src/ui/widgets/menu_button_widget.dart index da49648..1f8fcb5 100644 --- a/lib/src/ui/widgets/menu_button_widget.dart +++ b/lib/src/ui/widgets/menu_button_widget.dart @@ -5,7 +5,7 @@ import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'package:social_cv_client_flutter/src/utils/navigation.dart'; class MenuButton extends StatelessWidget { diff --git a/lib/src/ui/widgets/profile_image_widget.dart b/lib/src/ui/widgets/profile_image_widget.dart index c1f0a49..07d7a1e 100644 --- a/lib/src/ui/widgets/profile_image_widget.dart +++ b/lib/src/ui/widgets/profile_image_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; class ProfileImage extends StatelessWidget { final String _tag = '$ProfileImage'; diff --git a/lib/src/ui/widgets/register_form_widget.dart b/lib/src/ui/widgets/register_form_widget.dart index d288989..c73a465 100644 --- a/lib/src/ui/widgets/register_form_widget.dart +++ b/lib/src/ui/widgets/register_form_widget.dart @@ -2,317 +2,172 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:provider/provider.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; -class RegisterFormWidget extends StatefulWidget { +class RegisterForm extends StatefulWidget { @override - State createState() => _RegisterFormWidgetState(); + State createState() => _RegisterFormState(); } -class _RegisterFormWidgetState extends State { - final String _tag = '$_RegisterFormWidgetState'; +class _RegisterFormState extends State { + final String _tag = '$_RegisterFormState'; final FocusNode myFocusNodePassword = FocusNode(); final FocusNode myFocusNodeEmail = FocusNode(); + final FocusNode myFocusNodeFirstName = FocusNode(); final FocusNode myFocusNodeLastName = FocusNode(); - bool _obscureTextSignup = true; - bool _obscureTextSignupConfirm = true; - - TextEditingController signupEmailController = new TextEditingController(); - TextEditingController signupFisrtNameController = new TextEditingController(); - TextEditingController signupLastNameController = new TextEditingController(); - TextEditingController signupPasswordController = new TextEditingController(); - TextEditingController signupConfirmPasswordController = - new TextEditingController(); - - RegisterBloc _registerBloc; - - @override - void initState() { - super.initState(); + bool _obscureTextSignUp = true; + bool _obscureTextSignUpConfirm = true; - final authBloc = BlocProvider.of(context); - final cvRepository = Provider.of(context, listen: false); + TextEditingController signUpEmailController = new TextEditingController(); - _registerBloc = RegisterBloc( - cvRepository: cvRepository, - authenticationBloc: authBloc, - ); - } + TextEditingController signUpFirstNameController = new TextEditingController(); + TextEditingController signUpLastNameController = new TextEditingController(); + TextEditingController signUpPasswordController = new TextEditingController(); + TextEditingController signUpConfirmPasswordController = + new TextEditingController(); @override void dispose() { - myFocusNodeFirstName?.dispose(); - myFocusNodeEmail?.dispose(); - myFocusNodePassword?.dispose(); - _registerBloc?.dispose(); + print('$_tag:$dispose()'); + + myFocusNodeFirstName.dispose(); + myFocusNodeLastName.dispose(); + myFocusNodeEmail.dispose(); + myFocusNodePassword.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + print('$_tag:$build'); + RegisterBloc _registerBloc = BlocProvider.of(context); + + void _registerPressed() { + _registerBloc.dispatch(RegisterButtonPressed( + email: signUpEmailController.text, + password: signUpPasswordController.text, + fName: signUpFirstNameController.text, + lName: signUpLastNameController.text, + )); + } - return BlocBuilder( + return BlocListener( + bloc: _registerBloc, + listener: (BuildContext context, RegisterState state) { + if (state is RegisterFailure) { + Scaffold.of(context).showSnackBar( + SnackBar( + backgroundColor: AppColors.errorColor, + content: Text('${state.error.runtimeType}'), + ), + ); + } + }, + child: BlocBuilder( bloc: _registerBloc, builder: (BuildContext context, RegisterState state) { - return Container( - padding: EdgeInsets.only(top: 23.0), - child: Column( - children: [ - Stack( - alignment: Alignment.topCenter, - overflow: Overflow.visible, - children: [ - Card( - elevation: 2.0, - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), + return Card( + elevation: AppDimensions.defaultCardElevation, + child: Padding( + padding: EdgeInsets.all(AppDimensions.defaultCardPadding), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center(child: Text('Register')), + Padding( + padding: const EdgeInsets.all( + AppDimensions.defaultFormInputPadding), + child: TextFormField( + decoration: InputDecoration( + labelText: 'Firstname', ), - child: Container( - width: 300.0, - height: 360.0, - child: Column( - children: [ - Padding( - padding: EdgeInsets.only( - top: 20.0, - bottom: 20.0, - left: 25.0, - right: 25.0), - child: TextField( - focusNode: myFocusNodeFirstName, - controller: signupFisrtNameController, - keyboardType: TextInputType.text, - textCapitalization: TextCapitalization.words, - decoration: InputDecoration( - icon: Icon( - MdiIcons.account, - color: Colors.black, - ), - hintText: 'Firstname', - hintStyle: TextStyle(fontSize: 16.0), - ), - ), - ), - Container( - width: 250.0, - height: 1.0, - color: Colors.grey[400], - ), - Padding( - padding: EdgeInsets.only( - top: 20.0, - bottom: 20.0, - left: 25.0, - right: 25.0, - ), - child: TextField( - focusNode: myFocusNodeFirstName, - controller: signupLastNameController, - keyboardType: TextInputType.text, - textCapitalization: TextCapitalization.words, - style: TextStyle( - fontSize: 16.0, - color: Colors.black, - ), - decoration: InputDecoration( - icon: Icon( - MdiIcons.account, - color: Colors.black, - ), - hintText: 'Lastname', - hintStyle: TextStyle(fontSize: 16.0), - ), - ), - ), - Container( - width: 250.0, - height: 1.0, - color: Colors.grey[400], - ), - Padding( - padding: EdgeInsets.only( - top: 20.0, - bottom: 20.0, - left: 25.0, - right: 25.0), - child: TextField( - focusNode: myFocusNodeEmail, - controller: signupEmailController, - keyboardType: TextInputType.emailAddress, - style: TextStyle( - fontSize: 16.0, - color: Colors.black, - ), - decoration: InputDecoration( - icon: Icon( - MdiIcons.email, - color: Colors.black, - ), - hintText: CVLocalizations.of(context).email, - hintStyle: TextStyle(fontSize: 16.0), - ), - ), - ), - Container( - width: 250.0, - height: 1.0, - color: Colors.grey[400], - ), - Padding( - padding: EdgeInsets.only( - top: 20.0, - bottom: 20.0, - left: 25.0, - right: 25.0), - child: TextField( - focusNode: myFocusNodePassword, - controller: signupPasswordController, - obscureText: _obscureTextSignup, - style: TextStyle( - fontSize: 16.0, - color: Colors.black, - ), - decoration: InputDecoration( - icon: Icon( - MdiIcons.lock, - color: Colors.black, - ), - hintText: - CVLocalizations.of(context).password, - hintStyle: TextStyle(fontSize: 16.0), - suffixIcon: GestureDetector( - onTap: _toggleSignup, - child: Icon( - MdiIcons.eye, - size: 15.0, - color: Colors.black, - ), - ), - ), - ), - ), - Container( - width: 250.0, - height: 1.0, - color: Colors.grey[400], - ), - Padding( - padding: EdgeInsets.only( - top: 20.0, - bottom: 20.0, - left: 25.0, - right: 25.0), - child: TextField( - controller: signupConfirmPasswordController, - obscureText: _obscureTextSignupConfirm, - style: TextStyle( - fontSize: 16.0, color: Colors.black), - decoration: InputDecoration( - border: InputBorder.none, - icon: Icon( - MdiIcons.lock, - color: Colors.black, - ), - hintText: CVLocalizations.of(context) - .passwordRepeat, - hintStyle: TextStyle(fontSize: 16.0), - suffixIcon: GestureDetector( - onTap: _toggleSignupConfirm, - child: Icon( - MdiIcons.eye, - size: 15.0, - color: Colors.black, - ), - ), - ), - ), - ), - ], - ), + ), + ), + Padding( + padding: const EdgeInsets.all( + AppDimensions.defaultFormInputPadding), + child: TextFormField( + decoration: InputDecoration( + labelText: 'Lastname', ), ), - Container( - margin: EdgeInsets.only(top: 340.0), - decoration: new BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - boxShadow: [ - BoxShadow( - color: AppColors.loginGradientStart, - offset: Offset(1.0, 6.0), - blurRadius: 20.0, - ), - BoxShadow( - color: AppColors.loginGradientEnd, - offset: Offset(1.0, 6.0), - blurRadius: 20.0, - ), - ], - gradient: new LinearGradient( - colors: [ - AppColors.loginGradientEnd, - AppColors.loginGradientStart - ], - begin: const FractionalOffset(0.2, 0.2), - end: const FractionalOffset(1.0, 1.0), - stops: [0.0, 1.0], - tileMode: TileMode.clamp), + ), + Padding( + padding: const EdgeInsets.all( + AppDimensions.defaultFormInputPadding), + child: TextFormField( + controller: signUpEmailController, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + hintText: 'someone@email.com', + labelText: 'Email', ), - child: MaterialButton( - highlightColor: Colors.transparent, - splashColor: AppColors.loginGradientEnd, - //shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 42.0), - child: Text( - CVLocalizations.of(context).authSignUpCTA, - style: TextStyle( - color: Colors.white, - fontSize: 25.0, - ), - ), + ), + ), + Padding( + padding: const EdgeInsets.all( + AppDimensions.defaultFormInputPadding), + child: TextFormField( + controller: signUpPasswordController, + obscureText: _obscureTextSignUp, + decoration: InputDecoration( + suffixIcon: IconButton( + icon: Icon(_obscureTextSignUp + ? MdiIcons.eyeOffOutline + : MdiIcons.eyeOutline), + onPressed: _toggleSignup, ), - onPressed: state is! RegisterLoading - ? _onRegisterButtonPressed - : null, + labelText: 'Password', ), ), - ], - ), - ], + ), + Padding( + padding: const EdgeInsets.all( + AppDimensions.defaultFormInputPadding), + child: TextFormField( + controller: signUpConfirmPasswordController, + obscureText: _obscureTextSignUpConfirm, + decoration: InputDecoration( + suffixIcon: IconButton( + icon: Icon(_obscureTextSignUpConfirm + ? MdiIcons.eyeOffOutline + : MdiIcons.eyeOutline), + onPressed: _toggleSignupConfirm, + ), + labelText: 'Password Confirm', + ), + ), + ), + MaterialButton( + child: Text('REGISTER'), + onPressed: + state is! RegisterLoading ? _registerPressed : null, + ), + ], + ), ), ); - }); + }, + ), + ); } void _toggleSignup() { setState(() { - _obscureTextSignup = !_obscureTextSignup; + _obscureTextSignUp = !_obscureTextSignUp; }); } void _toggleSignupConfirm() { setState(() { - _obscureTextSignupConfirm = !_obscureTextSignupConfirm; + _obscureTextSignUpConfirm = !_obscureTextSignUpConfirm; }); } - - _onRegisterButtonPressed() { - _registerBloc.dispatch(RegisterButtonPressed( - fName: signupFisrtNameController.text, - lName: signupLastNameController.text, - email: signupEmailController.text, - password: signupPasswordController.text, - )); - } } diff --git a/lib/src/ui/widgets/sort_dialog_widget.dart b/lib/src/ui/widgets/sort_dialog_widget.dart index d5d6dbe..e39778e 100644 --- a/lib/src/ui/widgets/sort_dialog_widget.dart +++ b/lib/src/ui/widgets/sort_dialog_widget.dart @@ -4,7 +4,7 @@ import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; class SortDialog extends StatefulWidget { const SortDialog({ diff --git a/lib/src/ui/widgets/splash_widget.dart b/lib/src/ui/widgets/splash_widget.dart index 3925f71..d5dd05a 100644 --- a/lib/src/ui/widgets/splash_widget.dart +++ b/lib/src/ui/widgets/splash_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; class SplashPage extends StatelessWidget { final String _tag = '$SplashPage'; diff --git a/lib/src/ui/widgets/theme_switch_list_tile_widget.dart b/lib/src/ui/widgets/theme_switch_list_tile_widget.dart index eef348a..9e0a89d 100644 --- a/lib/src/ui/widgets/theme_switch_list_tile_widget.dart +++ b/lib/src/ui/widgets/theme_switch_list_tile_widget.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logging_service.dart'; +import 'package:social_cv_client_flutter/src/utils/logger.dart'; class ThemeSwitchListTile extends StatelessWidget { final String _tag = '$ThemeSwitchListTile'; diff --git a/lib/src/utils/logging_service.dart b/lib/src/utils/logger.dart similarity index 100% rename from lib/src/utils/logging_service.dart rename to lib/src/utils/logger.dart diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 5b86ff1..c73561e 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -5,7 +5,7 @@ import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_flutter/src/ui/commons/defaults.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'logging_service.dart'; +import 'logger.dart'; String getInitials(String nameString) { if (nameString.isEmpty) return ' '; diff --git a/pubspec.yaml b/pubspec.yaml index 3c6f5ae..47a95e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,7 +8,7 @@ dependencies: # Common Social CV Logic social_cv_client_dart_common: -# path: ../Social-CV-Client-Dart-common + #path: ../Social-CV-Client-Dart-common git: url: https://github.com/axellebot/Social-CV-Client-Dart-common ref: feature/blocs From f62f56c84796a61f9cceb1b2fb999245c7883335 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Fri, 19 Jul 2019 12:18:40 +0200 Subject: [PATCH 15/17] [TASK] Merged dimensions and colors in styles - Edited error widgets to add dynamic error type (Error, Exception ...) --- lib/src/app.dart | 14 +-- lib/src/ui/commons/colors.dart | 40 ------ lib/src/ui/commons/dimensions.dart | 51 -------- lib/src/ui/commons/styles.dart | 115 ++++++++++++++++++ lib/src/ui/pages/auth_page.dart | 17 ++- .../pages/elements/profile_profile_page.dart | 14 +-- lib/src/ui/pages/home_page.dart | 6 +- .../elements/entry_profile_widget.dart | 16 +-- .../elements/group_profile_widget.dart | 14 ++- .../widgets/elements/part_profile_widget.dart | 4 +- lib/src/ui/widgets/error_widget.dart | 89 +++++++------- .../widgets/initial_circle_avatar_widget.dart | 34 +++--- lib/src/ui/widgets/loading_widget.dart | 7 +- lib/src/ui/widgets/login_form_widget.dart | 15 +-- lib/src/ui/widgets/menu_button_widget.dart | 4 +- lib/src/ui/widgets/register_form_widget.dart | 24 ++-- lib/src/ui/widgets/sort_dialog_widget.dart | 6 +- lib/src/ui/widgets/splash_widget.dart | 6 +- 18 files changed, 247 insertions(+), 229 deletions(-) delete mode 100644 lib/src/ui/commons/colors.dart delete mode 100644 lib/src/ui/commons/dimensions.dart create mode 100644 lib/src/ui/commons/styles.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index 7931ae7..d883edc 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -8,7 +8,7 @@ import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/repositories.dart'; import 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration.dart'; import 'package:social_cv_client_flutter/src/router.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; @@ -212,13 +212,13 @@ class _App extends StatelessWidget { } return base.copyWith( - primaryColor: AppColors.primaryColor, - primaryColorLight: AppColors.primaryColorLight, - primaryColorDark: AppColors.primaryColorDark, - accentColor: AppColors.accentColor, + primaryColor: AppStyles.primaryColor, + primaryColorLight: AppStyles.primaryColorLight, + primaryColorDark: AppStyles.primaryColorDark, + accentColor: AppStyles.accentColor, buttonColor: (theme != ThemeType.DARK) - ? AppColors.white - : AppColors.primaryColorDark, + ? AppStyles.colorWhite + : AppStyles.primaryColorDark, inputDecorationTheme: InputDecorationTheme( border: OutlineInputBorder(), ), diff --git a/lib/src/ui/commons/colors.dart b/lib/src/ui/commons/colors.dart deleted file mode 100644 index 4bf4a8d..0000000 --- a/lib/src/ui/commons/colors.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; - -class AppColors { - /// Colors - static const Color colorBlue = Colors.blue; - static const Color colorOrange = Colors.deepOrange; - static const Color colorPink = Colors.pink; - static const Color white = Colors.white; - static const Color colorBlack = Colors.black; - - /// Misc Colors - static const Color successColor = Colors.green; - static const Color warningColor = Colors.yellow; - static const Color errorColor = Colors.red; - - ///Basics - static const Color primaryColor = const Color(0xFF2196f3); - static const Color primaryColorLight = const Color(0xFF6ec6ff); - static const Color primaryColorDark = const Color(0xFF0069c0); - static const Color textOnPrimary = const Color(0xFFFFFFFF); - static const Color accentColor = const Color(0xFFFF5722); - static const Color accentColorLight = const Color(0xFFff8a50); - static const Color accentColorDark = const Color(0xFFc41c00); - static const Color textOnAccent = const Color(0xFFFFFFFF); - - static const Color backgroundColor = const Color(0xFFFFFFFF); - static const Color backgroundColorLight = backgroundColor; - static const Color backgroundColorDark = const Color(0xFF353A3A); - - ///Cards - static const Color cardBackgroundColor = const Color(0xFFFFFFFF); - static const Color cardBackgroundColorLight = backgroundColor; - static const Color cardBackgroundColorDark = const Color(0xFF353A3A); - - /// Auth Stuff - static const Color loginGradientEnd = primaryColorLight; - static const Color loginGradientStart = primaryColorDark; -} diff --git a/lib/src/ui/commons/dimensions.dart b/lib/src/ui/commons/dimensions.dart deleted file mode 100644 index 1bb0c43..0000000 --- a/lib/src/ui/commons/dimensions.dart +++ /dev/null @@ -1,51 +0,0 @@ -class AppDimensions { - /// Default values - - static const double defaultCardElevation = 2.0; - static const double defaultCardPadding = 15.0; - static const double defaultFormInputPadding = 15.0; - - /// App - - static const double menuButtonPadding = 3.0; - - /// Auth Page - - static const double authPageMinHeight = 800.0; - - /// Card - - static const double cardDefaultElevation = 2.0; - static const double cardDefaultPadding = 20.0; - - /// Profile - - static const double profileAvatarMin = 5.0; - static const double profileAvatarMax = 50.0; - static const double profileAvatarElevation = 2.0; - - /// Group - - static const double groupPadding = 5.0; - - /// Entry - - static const double entryPadding = 10.0; - static const double entryTagSpacing = 4.0; - static const double entryCardElevation = 2.0; - static const double entryEventHeight = 200.0; - static const double entryEventHWidth = 300.0; - - static const double horizontalEntryListHeight = entryEventHeight; - static const double horizontalGroupListHeight = 300.0; - - /// Sort Dialog - - static const double sortDialogWidth = 200.0; - static const double sortDialogHeight = 300.0; - - /// List - - static const double listHeaderDefaultHeightMax = 40.0; - static const double listHeaderDefaultHeightMin = 40.0; -} diff --git a/lib/src/ui/commons/styles.dart b/lib/src/ui/commons/styles.dart new file mode 100644 index 0000000..d59e1f9 --- /dev/null +++ b/lib/src/ui/commons/styles.dart @@ -0,0 +1,115 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class AppStyles { + /// -------------------------------------------------------------------------- + /// Commons Colors + /// -------------------------------------------------------------------------- + + static const Color colorBlue = Colors.blue; + static const Color colorOrange = Colors.deepOrange; + static const Color colorPink = Colors.pink; + static const Color colorWhite = Colors.white; + static const Color colorBlack = Colors.black; + + /// -------------------------------------------------------------------------- + /// Commons Colors + /// -------------------------------------------------------------------------- + + static const Color successColor = Colors.green; + static const Color warningColor = Colors.yellow; + static const Color errorColor = Colors.red; + + /// -------------------------------------------------------------------------- + /// Basic Colors + /// -------------------------------------------------------------------------- + + static const Color primaryColor = const Color(0xFF2196f3); + static const Color primaryColorLight = const Color(0xFF6ec6ff); + static const Color primaryColorDark = const Color(0xFF0069c0); + static const Color textOnPrimary = const Color(0xFFFFFFFF); + static const Color accentColor = const Color(0xFFFF5722); + static const Color accentColorLight = const Color(0xFFff8a50); + static const Color accentColorDark = const Color(0xFFc41c00); + static const Color textOnAccent = const Color(0xFFFFFFFF); + static const Color backgroundColor = const Color(0xFFFFFFFF); + static const Color backgroundColorLight = backgroundColor; + static const Color backgroundColorDark = const Color(0xFF3A3A3A); + + /// -------------------------------------------------------------------------- + /// Default dimensions + /// -------------------------------------------------------------------------- + + static const double defaultCardElevation = 2.0; + static const EdgeInsets defaultCardPadding = EdgeInsets.all(15.0); + static const EdgeInsets defaultFormInputPadding = EdgeInsets.all(15.0); + + /// -------------------------------------------------------------------------- + /// Card + /// -------------------------------------------------------------------------- + + static const Color cardBackgroundColor = const Color(0xFFFFFFFF); + static const Color cardBackgroundColorLight = backgroundColor; + static const Color cardBackgroundColorDark = const Color(0xFF353A3A); + + static const double cardDefaultElevation = 2.0; + static const EdgeInsets cardDefaultPadding = EdgeInsets.all(20.0); + + /// Sort Dialog + + static const double sortDialogWidth = 200.0; + static const double sortDialogHeight = 300.0; + + /// -------------------------------------------------------------------------- + /// List + /// -------------------------------------------------------------------------- + + static const double listHeaderDefaultHeightMax = 40.0; + static const double listHeaderDefaultHeightMin = 40.0; + + /// -------------------------------------------------------------------------- + /// App + /// -------------------------------------------------------------------------- + + static const double appMenuButtonVerticalPadding = 3.0; + + /// -------------------------------------------------------------------------- + /// Auth + /// -------------------------------------------------------------------------- + + static const double authPageMinHeight = 800.0; + static const Color authLoginGradientEnd = primaryColorLight; + static const Color authLoginGradientStart = primaryColorDark; + + /// -------------------------------------------------------------------------- + /// Element Profile + /// -------------------------------------------------------------------------- + + static const double profileAvatarMin = 5.0; + static const double profileAvatarMax = 50.0; + static const double profileAvatarElevation = 2.0; + + /// -------------------------------------------------------------------------- + /// Element Part + /// -------------------------------------------------------------------------- + + /// -------------------------------------------------------------------------- + /// Element Group + /// -------------------------------------------------------------------------- + + static const double groupHorizontalPadding = 5.0; + static const double groupHorizontalListHeight = 300.0; + + /// -------------------------------------------------------------------------- + /// Element Entry + /// -------------------------------------------------------------------------- + + static const EdgeInsets entryPadding = const EdgeInsets.all(10.0); + static const double entryTagSpacing = 4.0; + static const double entryCardElevation = 2.0; + static const double entryEventHeight = 200.0; + static const double entryEventHWidth = 300.0; + + static const double entryHorizontalListHeight = entryEventHeight; +} diff --git a/lib/src/ui/pages/auth_page.dart b/lib/src/ui/pages/auth_page.dart index 9e870c5..883d865 100644 --- a/lib/src/ui/pages/auth_page.dart +++ b/lib/src/ui/pages/auth_page.dart @@ -4,8 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_flutter/src/ui/commons/assets.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/login_form_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/register_form_widget.dart'; @@ -49,12 +48,12 @@ class _AuthPageState extends State { screenHeight = MediaQuery.of(context).size.height; screenWidth = MediaQuery.of(context).size.width; - screenHeight = (screenHeight > AppDimensions.authPageMinHeight) + screenHeight = (screenHeight > AppStyles.authPageMinHeight) ? screenHeight - : AppDimensions.authPageMinHeight; + : AppStyles.authPageMinHeight; return Scaffold( - backgroundColor: AppColors.primaryColor, + backgroundColor: AppStyles.primaryColor, body: BlocListenerTree( blocListeners: [ BlocListener( @@ -62,7 +61,7 @@ class _AuthPageState extends State { listener: (BuildContext context, AuthenticationState state) { if (state is AuthenticationAuthenticated) { Scaffold.of(context).showSnackBar(SnackBar( - backgroundColor: AppColors.successColor, + backgroundColor: AppStyles.successColor, content: Text(CVLocalizations.of(context).authSignInSucceed), )); Future.delayed(Duration(seconds: 1)) @@ -104,7 +103,7 @@ class _AuthPageState extends State { style: Theme.of(context) .textTheme .title - .copyWith(color: AppColors.white), + .copyWith(color: AppStyles.colorWhite), ), ], ), @@ -125,7 +124,7 @@ class _AuthPageState extends State { height: screenHeight * 0.05, color: Colors.transparent, child: DotsIndicator( - color: AppColors.white, + color: AppStyles.colorWhite, controller: _pageController, itemCount: 2, onPageSelected: (int page) { @@ -236,7 +235,7 @@ class DotsIndicator extends AnimatedWidget { width: _kDotSpacing, child: new Center( child: new Material( - elevation: AppDimensions.defaultCardElevation, + elevation: AppStyles.defaultCardElevation, color: color, type: MaterialType.circle, child: new Container( diff --git a/lib/src/ui/pages/elements/profile_profile_page.dart b/lib/src/ui/pages/elements/profile_profile_page.dart index 3201b54..80e4e4d 100644 --- a/lib/src/ui/pages/elements/profile_profile_page.dart +++ b/lib/src/ui/pages/elements/profile_profile_page.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; @@ -80,9 +80,9 @@ class _ProfilePageAppBar extends StatelessWidget { ); Widget avatarWidget = InitialCircleAvatar( - elevation: AppDimensions.profileAvatarElevation, - maxRadius: AppDimensions.profileAvatarMax, - minRadius: AppDimensions.profileAvatarMin, + elevation: AppStyles.profileAvatarElevation, + maxRadius: AppStyles.profileAvatarMax, + minRadius: AppStyles.profileAvatarMin, backgroundImage: AssetImage('images/default-avatar.png'), ); @@ -100,9 +100,9 @@ class _ProfilePageAppBar extends StatelessWidget { ); avatarWidget = InitialCircleAvatar( text: profile.title, - elevation: AppDimensions.profileAvatarElevation, - maxRadius: AppDimensions.profileAvatarMax, - minRadius: AppDimensions.profileAvatarMin, + elevation: AppStyles.profileAvatarElevation, + maxRadius: AppStyles.profileAvatarMax, + minRadius: AppStyles.profileAvatarMin, backgroundImage: NetworkImage(profile.picture.toString()), ); diff --git a/lib/src/ui/pages/home_page.dart b/lib/src/ui/pages/home_page.dart index 57ae535..3e11565 100644 --- a/lib/src/ui/pages/home_page.dart +++ b/lib/src/ui/pages/home_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; @@ -22,9 +22,9 @@ class HomePage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Card( - elevation: AppDimensions.cardDefaultElevation, + elevation: AppStyles.cardDefaultElevation, child: Container( - padding: EdgeInsets.all(AppDimensions.cardDefaultPadding), + padding: AppStyles.cardDefaultPadding, child: Text( CVLocalizations.of(context).homeWelcome, style: Theme.of(context).textTheme.body2, diff --git a/lib/src/ui/widgets/elements/entry_profile_widget.dart b/lib/src/ui/widgets/elements/entry_profile_widget.dart index 0c5261a..b3c254e 100644 --- a/lib/src/ui/widgets/elements/entry_profile_widget.dart +++ b/lib/src/ui/widgets/elements/entry_profile_widget.dart @@ -4,7 +4,7 @@ import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; @@ -54,7 +54,7 @@ class _EntryWidgetMap extends StatelessWidget { return InkWell( onTap: () => navigateToEntry(context, entry: entry), child: Container( - padding: EdgeInsets.all(AppDimensions.entryPadding), + padding: AppStyles.entryPadding, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -85,11 +85,11 @@ class _EntryWidgetEvent extends StatelessWidget { @override Widget build(BuildContext context) { return Card( - elevation: AppDimensions.entryCardElevation, + elevation: AppStyles.entryCardElevation, child: Container( - height: AppDimensions.entryEventHeight, - width: AppDimensions.entryEventHWidth, - padding: const EdgeInsets.all(AppDimensions.entryPadding), + height: AppStyles.entryEventHeight, + width: AppStyles.entryEventHWidth, + padding: AppStyles.entryPadding, child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -156,7 +156,7 @@ class _EntryWidgetTag extends StatelessWidget { }); return Container( - padding: EdgeInsets.all(AppDimensions.entryPadding), + padding: AppStyles.entryPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -178,7 +178,7 @@ class _EntryWidgetTag extends StatelessWidget { ), Wrap( alignment: WrapAlignment.start, - spacing: AppDimensions.entryTagSpacing, + spacing: AppStyles.entryTagSpacing, runSpacing: 0.0, children: _tagWidgets, ) diff --git a/lib/src/ui/widgets/elements/group_profile_widget.dart b/lib/src/ui/widgets/elements/group_profile_widget.dart index 7faf216..fa49727 100644 --- a/lib/src/ui/widgets/elements/group_profile_widget.dart +++ b/lib/src/ui/widgets/elements/group_profile_widget.dart @@ -4,7 +4,7 @@ import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_list_profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_widget.dart'; @@ -57,15 +57,17 @@ class _GroupHorizontal extends StatelessWidget { children: [ Container( padding: const EdgeInsets.symmetric( - horizontal: AppDimensions.groupPadding), + horizontal: AppStyles.groupHorizontalPadding, + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( group.name.toUpperCase(), style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold), + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + ), ), FlatButton( child: Text(CVLocalizations.of(context).groupWidgetDetails), @@ -75,7 +77,7 @@ class _GroupHorizontal extends StatelessWidget { ), ), Container( - height: AppDimensions.horizontalEntryListHeight, + height: AppStyles.entryHorizontalListHeight, child: SimpleEntryListProfile( entryIds: group.entryIds, scrollDirection: Axis.horizontal, @@ -101,7 +103,7 @@ class _GroupVertical extends StatelessWidget { children: [ Container( padding: const EdgeInsets.symmetric( - horizontal: AppDimensions.groupPadding), + horizontal: AppStyles.groupHorizontalPadding), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/src/ui/widgets/elements/part_profile_widget.dart b/lib/src/ui/widgets/elements/part_profile_widget.dart index 9507091..b0b0f77 100644 --- a/lib/src/ui/widgets/elements/part_profile_widget.dart +++ b/lib/src/ui/widgets/elements/part_profile_widget.dart @@ -4,7 +4,7 @@ import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/errors.dart'; import 'package:social_cv_client_dart_common/models.dart'; import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_list_profile_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_widget.dart'; @@ -94,7 +94,7 @@ class _PartWidgetFromModelHorizontal extends StatelessWidget { ], ), Container( - height: AppDimensions.horizontalGroupListHeight, + height: AppStyles.groupHorizontalListHeight, child: SimpleGroupListProfile( groupIds: partViewModel.groupIds, scrollDirection: Axis.horizontal, diff --git a/lib/src/ui/widgets/error_widget.dart b/lib/src/ui/widgets/error_widget.dart index a16804d..fb85318 100644 --- a/lib/src/ui/widgets/error_widget.dart +++ b/lib/src/ui/widgets/error_widget.dart @@ -1,23 +1,24 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; -/// [ErrorWidget] is a based widget for [Error] +/// [CustomErrorWidget] is a based widget for error /// /// Must be initialized with an [error] -abstract class ErrorWidget extends StatelessWidget { - final Error error; +abstract class CustomErrorWidget extends StatelessWidget { + final dynamic error; - ErrorWidget({Key key, @required this.error}) - : assert(error != null, 'No $Error given'), + const CustomErrorWidget({Key key, @required this.error}) + : assert(error != null, 'No error given'), super(key: key); } -/// [ErrorIcon] is a [Icon] like widget to display [Error] +/// [ErrorIcon] is a [Icon] like widget to display error /// /// See [Icon] widget for more documentation class ErrorIcon extends StatelessWidget { @@ -27,11 +28,11 @@ class ErrorIcon extends StatelessWidget { final String semanticLabel; final TextDirection textDirection; - ErrorIcon({ + const ErrorIcon({ Key key, this.icon = MdiIcons.alertCircleOutline, this.size, - this.color = AppColors.errorColor, + this.color = AppStyles.errorColor, this.semanticLabel, this.textDirection, }) : super(key: key); @@ -48,10 +49,10 @@ class ErrorIcon extends StatelessWidget { } } -/// [ErrorText] is a [Text] like widget like to display [Error] +/// [ErrorText] is a [Text] like widget like to display error /// /// See [Text] widget for more documentation -class ErrorText extends ErrorWidget { +class ErrorText extends CustomErrorWidget { final TextStyle style; final StrutStyle strutStyle; @@ -64,9 +65,9 @@ class ErrorText extends ErrorWidget { final int maxLines; final String semanticsLabel; - ErrorText({ + const ErrorText({ Key key, - @required Error error, + @required dynamic error, this.style, this.strutStyle, this.textAlign = TextAlign.center, @@ -83,8 +84,8 @@ class ErrorText extends ErrorWidget { Widget build(BuildContext context) { return Text( translateError(context, error), - style: style, - strutStyle: strutStyle, + style: style as TextStyle, + strutStyle: strutStyle as StrutStyle, textAlign: textAlign, textDirection: textDirection, locale: locale, @@ -100,31 +101,33 @@ class ErrorText extends ErrorWidget { /// [ErrorRow] is a [Row] like widget to display [Error] /// /// See [Row] widget for more documentation -class ErrorRow extends ErrorWidget { - ErrorRow({Key key, @required Error error}) : super(key: key, error: error); +class ErrorRow extends CustomErrorWidget { + const ErrorRow({Key key, @required dynamic error}) + : super(key: key, error: error); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - ErrorIcon(), + const ErrorIcon(), Expanded(child: ErrorText(error: error)), ], ); } } -/// [ErrorTile] is a [ListTile] like widget to display [Error] +/// [ErrorTile] is a [ListTile] like widget to display error /// /// See [ListTile] widget for more documentation -class ErrorTile extends ErrorWidget { - ErrorTile({Key key, @required Error error}) : super(key: key, error: error); +class ErrorTile extends CustomErrorWidget { + const ErrorTile({Key key, @required dynamic error}) + : super(key: key, error: error); @override Widget build(BuildContext context) { return ListTile( - leading: ErrorIcon(), + leading: const ErrorIcon(), title: Text(CVLocalizations.of(context).errorOccurred), subtitle: ErrorText( error: error, @@ -134,16 +137,16 @@ class ErrorTile extends ErrorWidget { } } -/// [ErrorCard] is a [Card] like widget to display [Error] +/// [ErrorCard] is a [Card] like widget to display error /// /// See [Card] widget for more documentation -class ErrorCard extends ErrorWidget { +class ErrorCard extends CustomErrorWidget { final double height; final double width; - ErrorCard({ + const ErrorCard({ Key key, - @required Error error, + @required dynamic error, this.height, this.width, }) : super(key: key, error: error); @@ -151,27 +154,27 @@ class ErrorCard extends ErrorWidget { @override Widget build(BuildContext context) { return Card( - elevation: AppDimensions.cardDefaultElevation, + elevation: AppStyles.cardDefaultElevation, child: Container( height: height, width: width, - padding: EdgeInsets.all(AppDimensions.cardDefaultPadding), + padding: AppStyles.cardDefaultPadding, child: ErrorRow(error: error), ), ); } } -/// [ErrorList] is a [ListView] like widget to display [Error] +/// [ErrorList] is a [ListView] like widget to display error /// /// See [ListView] widget for more documentation -class ErrorList extends ErrorWidget { +class ErrorList extends CustomErrorWidget { /// List behaviors final Axis scrollDirection; final bool shrinkWrap; final ScrollPhysics physics; - ErrorList({ + const ErrorList({ Key key, @required Error error, @@ -184,9 +187,9 @@ class ErrorList extends ErrorWidget { @override Widget build(BuildContext context) { return ListView( - scrollDirection: this.scrollDirection, - shrinkWrap: this.shrinkWrap, - physics: this.physics, + scrollDirection: scrollDirection, + shrinkWrap: shrinkWrap, + physics: physics, children: [ ErrorTile(error: error), ], @@ -194,11 +197,12 @@ class ErrorList extends ErrorWidget { } } -/// [ErrorPage] is a [Scaffold] like widget to display [Error] +/// [ErrorPage] is a [Scaffold] like widget to display error /// /// See [Scaffold] widget for more documentation -class ErrorPage extends ErrorWidget { - ErrorPage({Key key, @required Error error}) : super(key: key, error: error); +class ErrorPage extends CustomErrorWidget { + const ErrorPage({Key key, @required dynamic error}) + : super(key: key, error: error); @override Widget build(BuildContext context) { @@ -208,17 +212,18 @@ class ErrorPage extends ErrorWidget { } } -/// [ErrorPage] is a [MaterialApp] like widget to display [Error] +/// [ErrorApp] is a [MaterialApp] like widget to display error /// /// See [MaterialApp] widget for more documentation -class ErrorApp extends ErrorWidget { - ErrorApp({Key key, @required Error error}) : super(key: key, error: error); +class ErrorApp extends CustomErrorWidget { + const ErrorApp({Key key, @required dynamic error}) + : super(key: key, error: error); @override Widget build(BuildContext context) { return MaterialApp( home: ErrorPage(error: error), - color: AppColors.primaryColor, + color: AppStyles.primaryColor, ); } } diff --git a/lib/src/ui/widgets/initial_circle_avatar_widget.dart b/lib/src/ui/widgets/initial_circle_avatar_widget.dart index bbc17f4..fef6cb2 100644 --- a/lib/src/ui/widgets/initial_circle_avatar_widget.dart +++ b/lib/src/ui/widgets/initial_circle_avatar_widget.dart @@ -1,16 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; import 'package:social_cv_client_flutter/src/utils/utils.dart'; class InitialCircleAvatar extends StatefulWidget { - final String text; - final double elevation; - final ImageProvider backgroundImage; - final double radius; - final double minRadius; - final double maxRadius; - - InitialCircleAvatar({ + const InitialCircleAvatar({ Key key, this.text = '', this.elevation = 0.0, @@ -21,33 +13,36 @@ class InitialCircleAvatar extends StatefulWidget { }) : assert(radius == null || (minRadius == null && maxRadius == null)), super(key: key); + final String text; + final double elevation; + final ImageProvider backgroundImage; + final double radius; + final double minRadius; + final double maxRadius; + @override - _InitialCircleAvatarState createState() => new _InitialCircleAvatarState(); + _InitialCircleAvatarState createState() => _InitialCircleAvatarState(); } class _InitialCircleAvatarState extends State { - final String _tag = '$_InitialCircleAvatarState'; - bool _checkLoading = true; @override void initState() { super.initState(); widget.backgroundImage - .resolve(new ImageConfiguration()) - .addListener((_, __) { + ?.resolve(const ImageConfiguration()) + ?.addListener(ImageStreamListener((_, __) { if (mounted) { setState(() { _checkLoading = false; }); } - }); + })); } @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); - return _checkLoading == true ? Material( shape: CircleBorder(), @@ -56,7 +51,10 @@ class _InitialCircleAvatarState extends State { minRadius: widget.minRadius, maxRadius: widget.maxRadius, radius: widget.radius, - child: Text(getInitials(widget.text)), + child: Text( + getInitials(widget.text), + textAlign: TextAlign.center, + ), ), ) : Material( diff --git a/lib/src/ui/widgets/loading_widget.dart b/lib/src/ui/widgets/loading_widget.dart index 88e873a..0cd1f1c 100644 --- a/lib/src/ui/widgets/loading_widget.dart +++ b/lib/src/ui/widgets/loading_widget.dart @@ -2,8 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; /// [LoadingShadowContent] displays lines with opacity moving up and down /// Specify the number of loading lines to display @@ -136,7 +135,7 @@ class LoadingCard extends StatelessWidget { child: Container( height: height, width: width, - padding: EdgeInsets.all(AppDimensions.cardDefaultPadding), + padding: AppStyles.cardDefaultPadding, child: LoadingShadowContent( numberOfTitleLines: numberOfTitleLines, numberOfContentLines: numberOfContentLines, @@ -203,7 +202,7 @@ class LoadingApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: LoadingScaffold(), - color: AppColors.primaryColor, + color: AppStyles.primaryColor, ); } } diff --git a/lib/src/ui/widgets/login_form_widget.dart b/lib/src/ui/widgets/login_form_widget.dart index 9683033..7123c62 100644 --- a/lib/src/ui/widgets/login_form_widget.dart +++ b/lib/src/ui/widgets/login_form_widget.dart @@ -3,8 +3,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; class LoginForm extends StatefulWidget { @override @@ -50,7 +49,7 @@ class _LoginFormState extends State { if (state is LoginFailure) { Scaffold.of(context).showSnackBar( SnackBar( - backgroundColor: AppColors.errorColor, + backgroundColor: AppStyles.errorColor, content: Text('${state.error.runtimeType}'), ), ); @@ -60,9 +59,9 @@ class _LoginFormState extends State { bloc: _loginBloc, builder: (BuildContext context, LoginState state) { return Card( - elevation: AppDimensions.defaultCardElevation, + elevation: AppStyles.defaultCardElevation, child: Padding( - padding: EdgeInsets.all(AppDimensions.defaultCardPadding), + padding: AppStyles.defaultCardPadding, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, @@ -70,8 +69,7 @@ class _LoginFormState extends State { children: [ Center(child: Text('Login')), Padding( - padding: const EdgeInsets.all( - AppDimensions.defaultFormInputPadding), + padding: AppStyles.defaultFormInputPadding, child: TextFormField( keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, @@ -83,8 +81,7 @@ class _LoginFormState extends State { ), ), Padding( - padding: const EdgeInsets.all( - AppDimensions.defaultFormInputPadding), + padding: AppStyles.defaultFormInputPadding, child: TextFormField( controller: loginPasswordController, obscureText: _obscureTextLogin, diff --git a/lib/src/ui/widgets/menu_button_widget.dart b/lib/src/ui/widgets/menu_button_widget.dart index 1f8fcb5..48d1916 100644 --- a/lib/src/ui/widgets/menu_button_widget.dart +++ b/lib/src/ui/widgets/menu_button_widget.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; @@ -15,7 +15,7 @@ class MenuButton extends StatelessWidget { MenuButton({ Key key, this.padding = const EdgeInsets.symmetric( - vertical: AppDimensions.menuButtonPadding, + vertical: AppStyles.appMenuButtonVerticalPadding, ), }) : super(key: key); diff --git a/lib/src/ui/widgets/register_form_widget.dart b/lib/src/ui/widgets/register_form_widget.dart index c73a465..0688fc2 100644 --- a/lib/src/ui/widgets/register_form_widget.dart +++ b/lib/src/ui/widgets/register_form_widget.dart @@ -3,8 +3,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; class RegisterForm extends StatefulWidget { @override @@ -62,7 +61,7 @@ class _RegisterFormState extends State { if (state is RegisterFailure) { Scaffold.of(context).showSnackBar( SnackBar( - backgroundColor: AppColors.errorColor, + backgroundColor: AppStyles.errorColor, content: Text('${state.error.runtimeType}'), ), ); @@ -72,9 +71,9 @@ class _RegisterFormState extends State { bloc: _registerBloc, builder: (BuildContext context, RegisterState state) { return Card( - elevation: AppDimensions.defaultCardElevation, + elevation: AppStyles.defaultCardElevation, child: Padding( - padding: EdgeInsets.all(AppDimensions.defaultCardPadding), + padding: AppStyles.defaultCardPadding, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, @@ -82,8 +81,7 @@ class _RegisterFormState extends State { children: [ Center(child: Text('Register')), Padding( - padding: const EdgeInsets.all( - AppDimensions.defaultFormInputPadding), + padding: AppStyles.defaultFormInputPadding, child: TextFormField( decoration: InputDecoration( labelText: 'Firstname', @@ -91,8 +89,7 @@ class _RegisterFormState extends State { ), ), Padding( - padding: const EdgeInsets.all( - AppDimensions.defaultFormInputPadding), + padding: AppStyles.defaultFormInputPadding, child: TextFormField( decoration: InputDecoration( labelText: 'Lastname', @@ -100,8 +97,7 @@ class _RegisterFormState extends State { ), ), Padding( - padding: const EdgeInsets.all( - AppDimensions.defaultFormInputPadding), + padding: AppStyles.defaultFormInputPadding, child: TextFormField( controller: signUpEmailController, keyboardType: TextInputType.emailAddress, @@ -112,8 +108,7 @@ class _RegisterFormState extends State { ), ), Padding( - padding: const EdgeInsets.all( - AppDimensions.defaultFormInputPadding), + padding: AppStyles.defaultFormInputPadding, child: TextFormField( controller: signUpPasswordController, obscureText: _obscureTextSignUp, @@ -129,8 +124,7 @@ class _RegisterFormState extends State { ), ), Padding( - padding: const EdgeInsets.all( - AppDimensions.defaultFormInputPadding), + padding: AppStyles.defaultFormInputPadding, child: TextFormField( controller: signUpConfirmPasswordController, obscureText: _obscureTextSignUpConfirm, diff --git a/lib/src/ui/widgets/sort_dialog_widget.dart b/lib/src/ui/widgets/sort_dialog_widget.dart index e39778e..a718694 100644 --- a/lib/src/ui/widgets/sort_dialog_widget.dart +++ b/lib/src/ui/widgets/sort_dialog_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/dimensions.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; @@ -46,8 +46,8 @@ class _SortDialogState extends State { contentPadding: EdgeInsets.all(0.0), title: widget.title, content: Container( - width: AppDimensions.sortDialogWidth, - height: AppDimensions.sortDialogHeight, + width: AppStyles.sortDialogWidth, + height: AppStyles.sortDialogHeight, child: ReorderableListView( onReorder: _onReorder, children: _listTiles, diff --git a/lib/src/ui/widgets/splash_widget.dart b/lib/src/ui/widgets/splash_widget.dart index d5dd05a..03a96a6 100644 --- a/lib/src/ui/widgets/splash_widget.dart +++ b/lib/src/ui/widgets/splash_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/colors.dart'; +import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; import 'package:social_cv_client_flutter/src/utils/logger.dart'; class SplashPage extends StatelessWidget { @@ -10,7 +10,7 @@ class SplashPage extends StatelessWidget { Logger.log('$_tag:$build'); return Scaffold( - backgroundColor: AppColors.primaryColor, + backgroundColor: AppStyles.primaryColor, body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -31,7 +31,7 @@ class SplashApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: SplashPage(), - color: AppColors.primaryColor, + color: AppStyles.primaryColor, ); } } From c6e60f973adce3a84b96d0efbd7983ec22d373f1 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Wed, 24 Jul 2019 23:05:03 +0200 Subject: [PATCH 16/17] [TASK] Cleaned architecture - Data layer: - Updated managers to implement common lib classes (services and data stores) - Presentation layer: - Merged dimensions and colors in styles - Edited authentication page - Edited error widgets to add dynamic error type (Error, Exception ...) - Added error and exception translation - Upgrade bloc lib to 0.20.0 - Added linting options --- README.md | 11 + analysis_options.yaml | 93 ++++ lib/bloc.dart | 3 + lib/data.dart | 9 + lib/domain.dart | 2 + lib/main.dart | 6 +- lib/presentation.dart | 80 +++ lib/src/app.dart | 236 --------- .../blocs/configuration/configuration.dart | 3 + .../configuration/configuration_bloc.dart | 178 +++++++ .../configuration/configuration_event.dart | 2 +- .../configuration/configuration_state.dart | 70 +++ .../app_shared_preferences_manager.dart | 95 ++-- .../auth_shared_preferences_manager.dart | 84 +-- .../data/managers/config_assets_manager.dart | 21 +- lib/src/data/models/config_model.dart | 9 +- .../local_app_preferences_repository.dart | 63 --- .../local_auth_preferences_repository.dart | 80 --- .../repositories/local_config_repository.dart | 28 - .../blocs/configuration/configuration.dart | 4 - .../configuration/configuration_bloc.dart | 83 --- .../configuration/configuration_state.dart | 56 -- lib/src/presentation/app.dart | 265 ++++++++++ .../commons/api_values.dart | 0 .../{ui => presentation}/commons/assets.dart | 0 .../commons/defaults.dart | 0 .../{ui => presentation}/commons/paths.dart | 0 .../{ui => presentation}/commons/styles.dart | 26 +- .../{ui => presentation}/commons/tags.dart | 0 .../localizations/cv_localization.dart | 361 +++++++++++++ .../localizations/cv_localization_en.dart | 479 +++++++++++++++++ .../localizations/cv_localization_fr.dart | 482 ++++++++++++++++++ .../models/view_models.dart | 0 .../pages/account_page.dart | 52 +- lib/src/presentation/pages/auth_page.dart | 215 ++++++++ .../pages/elements/entry_profile_page.dart | 30 +- .../pages/elements/group_profile_page.dart | 22 +- .../pages/elements/part_profile_page.dart | 25 +- .../pages/elements/profile_profile_page.dart | 24 +- .../{ui => presentation}/pages/home_page.dart | 6 +- .../{ui => presentation}/pages/main_page.dart | 11 +- .../pages/search_page.dart | 12 +- .../pages/settings_page.dart | 6 +- lib/src/{ => presentation}/router.dart | 26 +- lib/src/{ => presentation}/utils/logger.dart | 36 +- .../{ => presentation}/utils/navigation.dart | 19 +- .../presentation/utils/translate_error.dart | 185 +++++++ lib/src/presentation/utils/utils.dart | 28 + .../{ => presentation}/utils/validators.dart | 0 .../widgets/account_tile_widget.dart | 37 +- .../widgets/arc_banner_image_widget.dart | 18 +- .../widgets/bubble_indication_painter.dart | 52 ++ .../elements/entry_list_profile_widget.dart | 19 +- .../widgets/elements/entry_list_widget.dart | 10 +- .../elements/entry_profile_widget.dart | 38 +- .../widgets/elements/entry_widget.dart | 11 +- .../elements/group_list_profile_widget.dart | 19 +- .../widgets/elements/group_list_widget.dart | 8 +- .../elements/group_profile_widget.dart | 23 +- .../widgets/elements/group_widget.dart | 11 +- .../elements/part_list_profile_widget.dart | 27 +- .../widgets/elements/part_list_widget.dart | 8 +- .../widgets/elements/part_profile_widget.dart | 38 +- .../widgets/elements/part_widget.dart | 13 +- .../elements/profile_list_tile_widget.dart | 19 +- .../widgets/elements/profile_list_widget.dart | 9 +- .../widgets/elements/profile_tile_widget.dart | 19 +- .../widgets/elements/profile_widget.dart | 14 +- .../widgets/error_widget.dart | 7 +- .../widgets/initial_circle_avatar_widget.dart | 2 +- .../widgets/loading_widget.dart | 19 +- .../widgets/login_form_widget.dart | 32 +- .../widgets/menu_bottom_sheet_widget.dart | 17 +- .../widgets/menu_button_widget.dart | 48 +- .../widgets/profile_image_widget.dart | 9 +- .../widgets/register_form_widget.dart | 53 +- .../widgets/repository_provider.dart | 0 .../widgets/sort_box_widget.dart | 0 .../widgets/sort_dialog_widget.dart | 12 +- .../widgets/sort_list_tile_widget.dart | 2 +- .../widgets/splash_widget.dart | 6 +- .../widgets/theme_switch_tile_widget.dart | 35 ++ lib/src/ui/localizations/cv_localization.dart | 312 ------------ .../ui/localizations/cv_localization_en.dart | 405 --------------- .../ui/localizations/cv_localization_fr.dart | 410 --------------- lib/src/ui/pages/auth_page.dart | 259 ---------- lib/src/ui/widgets/rounded_modal.dart | 298 ----------- .../theme_switch_list_tile_widget.dart | 61 --- lib/src/utils/utils.dart | 145 ------ pubspec.lock | 76 ++- pubspec.yaml | 12 +- 91 files changed, 3080 insertions(+), 3059 deletions(-) create mode 100644 analysis_options.yaml create mode 100644 lib/bloc.dart create mode 100644 lib/data.dart create mode 100644 lib/domain.dart create mode 100644 lib/presentation.dart delete mode 100644 lib/src/app.dart create mode 100644 lib/src/bloc/blocs/configuration/configuration.dart create mode 100644 lib/src/bloc/blocs/configuration/configuration_bloc.dart rename lib/src/{domain => bloc}/blocs/configuration/configuration_event.dart (79%) create mode 100644 lib/src/bloc/blocs/configuration/configuration_state.dart delete mode 100644 lib/src/data/repositories/local_app_preferences_repository.dart delete mode 100644 lib/src/data/repositories/local_auth_preferences_repository.dart delete mode 100644 lib/src/data/repositories/local_config_repository.dart delete mode 100644 lib/src/domain/blocs/configuration/configuration.dart delete mode 100644 lib/src/domain/blocs/configuration/configuration_bloc.dart delete mode 100644 lib/src/domain/blocs/configuration/configuration_state.dart create mode 100644 lib/src/presentation/app.dart rename lib/src/{ui => presentation}/commons/api_values.dart (100%) rename lib/src/{ui => presentation}/commons/assets.dart (100%) rename lib/src/{ui => presentation}/commons/defaults.dart (100%) rename lib/src/{ui => presentation}/commons/paths.dart (100%) rename lib/src/{ui => presentation}/commons/styles.dart (83%) rename lib/src/{ui => presentation}/commons/tags.dart (100%) create mode 100644 lib/src/presentation/localizations/cv_localization.dart create mode 100644 lib/src/presentation/localizations/cv_localization_en.dart create mode 100644 lib/src/presentation/localizations/cv_localization_fr.dart rename lib/src/{ui => presentation}/models/view_models.dart (100%) rename lib/src/{ui => presentation}/pages/account_page.dart (59%) create mode 100644 lib/src/presentation/pages/auth_page.dart rename lib/src/{ui => presentation}/pages/elements/entry_profile_page.dart (60%) rename lib/src/{ui => presentation}/pages/elements/group_profile_page.dart (65%) rename lib/src/{ui => presentation}/pages/elements/part_profile_page.dart (60%) rename lib/src/{ui => presentation}/pages/elements/profile_profile_page.dart (82%) rename lib/src/{ui => presentation}/pages/home_page.dart (76%) rename lib/src/{ui => presentation}/pages/main_page.dart (80%) rename lib/src/{ui => presentation}/pages/search_page.dart (78%) rename lib/src/{ui => presentation}/pages/settings_page.dart (66%) rename lib/src/{ => presentation}/router.dart (71%) rename lib/src/{ => presentation}/utils/logger.dart (90%) rename lib/src/{ => presentation}/utils/navigation.dart (68%) create mode 100644 lib/src/presentation/utils/translate_error.dart create mode 100644 lib/src/presentation/utils/utils.dart rename lib/src/{ => presentation}/utils/validators.dart (100%) rename lib/src/{ui => presentation}/widgets/account_tile_widget.dart (62%) rename lib/src/{ui => presentation}/widgets/arc_banner_image_widget.dart (69%) create mode 100644 lib/src/presentation/widgets/bubble_indication_painter.dart rename lib/src/{ui => presentation}/widgets/elements/entry_list_profile_widget.dart (84%) rename lib/src/{ui => presentation}/widgets/elements/entry_list_widget.dart (82%) rename lib/src/{ui => presentation}/widgets/elements/entry_profile_widget.dart (80%) rename lib/src/{ui => presentation}/widgets/elements/entry_widget.dart (76%) rename lib/src/{ui => presentation}/widgets/elements/group_list_profile_widget.dart (84%) rename lib/src/{ui => presentation}/widgets/elements/group_list_widget.dart (83%) rename lib/src/{ui => presentation}/widgets/elements/group_profile_widget.dart (79%) rename lib/src/{ui => presentation}/widgets/elements/group_widget.dart (77%) rename lib/src/{ui => presentation}/widgets/elements/part_list_profile_widget.dart (84%) rename lib/src/{ui => presentation}/widgets/elements/part_list_widget.dart (82%) rename lib/src/{ui => presentation}/widgets/elements/part_profile_widget.dart (71%) rename lib/src/{ui => presentation}/widgets/elements/part_widget.dart (71%) rename lib/src/{ui => presentation}/widgets/elements/profile_list_tile_widget.dart (84%) rename lib/src/{ui => presentation}/widgets/elements/profile_list_widget.dart (83%) rename lib/src/{ui => presentation}/widgets/elements/profile_tile_widget.dart (67%) rename lib/src/{ui => presentation}/widgets/elements/profile_widget.dart (71%) rename lib/src/{ui => presentation}/widgets/error_widget.dart (94%) rename lib/src/{ui => presentation}/widgets/initial_circle_avatar_widget.dart (96%) rename lib/src/{ui => presentation}/widgets/loading_widget.dart (91%) rename lib/src/{ui => presentation}/widgets/login_form_widget.dart (77%) rename lib/src/{ui => presentation}/widgets/menu_bottom_sheet_widget.dart (65%) rename lib/src/{ui => presentation}/widgets/menu_button_widget.dart (57%) rename lib/src/{ui => presentation}/widgets/profile_image_widget.dart (58%) rename lib/src/{ui => presentation}/widgets/register_form_widget.dart (73%) rename lib/src/{ui => presentation}/widgets/repository_provider.dart (100%) rename lib/src/{ui => presentation}/widgets/sort_box_widget.dart (100%) rename lib/src/{ui => presentation}/widgets/sort_dialog_widget.dart (80%) rename lib/src/{ui => presentation}/widgets/sort_list_tile_widget.dart (93%) rename lib/src/{ui => presentation}/widgets/splash_widget.dart (78%) create mode 100644 lib/src/presentation/widgets/theme_switch_tile_widget.dart delete mode 100644 lib/src/ui/localizations/cv_localization.dart delete mode 100644 lib/src/ui/localizations/cv_localization_en.dart delete mode 100644 lib/src/ui/localizations/cv_localization_fr.dart delete mode 100644 lib/src/ui/pages/auth_page.dart delete mode 100644 lib/src/ui/widgets/rounded_modal.dart delete mode 100644 lib/src/ui/widgets/theme_switch_list_tile_widget.dart delete mode 100644 lib/src/utils/utils.dart diff --git a/README.md b/README.md index 194ea11..4a2b83d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,17 @@ A new Flutter application about "resumes". For help getting started with Flutter, view our online [documentation](https://flutter.io/). +## Directory structure + +- Date: +Contains data layer shared classes that are not framework dependent (e.g. Flutter, AngularDart) or interfaces that must be used by client application +- Domain: +Contains domain layer company rules +- Bloc: +Contains shared business logic components +- Presentation: +Contains commons classes + ## Installation ### Config diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..ff48c07 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,93 @@ +analyzer: + strong-mode: + implicit-casts: false + +linter: + rules: + - always_declare_return_types + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_double_and_int_checks + - avoid_empty_else + - avoid_field_initializers_in_const_classes + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_types_as_parameter_names + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_types + - cancel_subscriptions + - close_sinks + - constant_identifier_names + - control_flow_in_finally + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + - hash_and_equals + - implementation_imports + - invariant_booleans + - iterable_contains_unrelated_type + - join_return_with_assignment + - library_names + - library_prefixes + - list_remove_unrelated_type + - literal_only_boolean_expressions + - no_duplicate_case_values + - non_constant_identifier_names + - null_closures + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + - prefer_final_fields + - prefer_final_locals + - prefer_foreach + - prefer_generic_function_type_aliases + - prefer_interpolation_to_compose_strings + - prefer_is_not_empty + - prefer_iterable_whereType + - prefer_single_quotes + - prefer_typing_uninitialized_variables + - recursive_getters + - slash_for_doc_comments + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_annotate_public_apis + - type_init_formals + - unawaited_futures + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_this + - unrelated_type_equality_checks + - use_rethrow_when_possible + - use_string_buffers + - use_to_and_as_if_applicable + - valid_regexps + - void_checks \ No newline at end of file diff --git a/lib/bloc.dart b/lib/bloc.dart new file mode 100644 index 0000000..5585912 --- /dev/null +++ b/lib/bloc.dart @@ -0,0 +1,3 @@ +export 'package:social_cv_client_dart_common/bloc.dart'; + +export 'package:social_cv_client_flutter/src/bloc/blocs/configuration/configuration.dart'; diff --git a/lib/data.dart b/lib/data.dart new file mode 100644 index 0000000..e33bcaf --- /dev/null +++ b/lib/data.dart @@ -0,0 +1,9 @@ +export 'package:social_cv_client_dart_common/data.dart'; + +/// ---------------------------------------------------------------------------- +/// Managers +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/data/managers/app_shared_preferences_manager.dart'; +export 'package:social_cv_client_flutter/src/data/managers/auth_shared_preferences_manager.dart'; +export 'package:social_cv_client_flutter/src/data/managers/config_assets_manager.dart'; diff --git a/lib/domain.dart b/lib/domain.dart new file mode 100644 index 0000000..4111daa --- /dev/null +++ b/lib/domain.dart @@ -0,0 +1,2 @@ +export 'package:social_cv_client_dart_common/domain.dart'; + diff --git a/lib/main.dart b/lib/main.dart index 8c0b604..bfec27b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,14 +2,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:social_cv_client_flutter/src/app.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/presentation/app.dart'; +import 'package:social_cv_client_flutter/src/presentation/utils/logger.dart'; /// TODO: automatically set this to false for release builds const bool DEBUG_MODE = true; const bool DEBUG_PAINT_SIZE = false; -Future main() async { +FutureOr main() async { String _tag = '$main'; /// SystemChrome.setPreferredOrientations( diff --git a/lib/presentation.dart b/lib/presentation.dart new file mode 100644 index 0000000..ad9cc85 --- /dev/null +++ b/lib/presentation.dart @@ -0,0 +1,80 @@ +/// ---------------------------------------------------------------------------- +/// Commons +/// ---------------------------------------------------------------------------- + +export 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +export 'package:social_cv_client_dart_common/presentation.dart'; +export 'package:social_cv_client_flutter/src/presentation/commons/api_values.dart'; +export 'package:social_cv_client_flutter/src/presentation/commons/assets.dart'; +export 'package:social_cv_client_flutter/src/presentation/commons/paths.dart'; +export 'package:social_cv_client_flutter/src/presentation/commons/styles.dart'; +export 'package:social_cv_client_flutter/src/presentation/commons/tags.dart'; + +/// ---------------------------------------------------------------------------- +/// Localizations +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/account_page.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/account_page.dart'; + +/// ---------------------------------------------------------------------------- +/// Pages +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/presentation/pages/auth_page.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/elements/entry_profile_page.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/elements/group_profile_page.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/elements/part_profile_page.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/elements/profile_profile_page.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/home_page.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/main_page.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/search_page.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/search_page.dart'; +export 'package:social_cv_client_flutter/src/presentation/pages/settings_page.dart'; + +/// ---------------------------------------------------------------------------- +/// Utils +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/presentation/router.dart'; +export 'package:social_cv_client_flutter/src/presentation/utils/logger.dart'; +export 'package:social_cv_client_flutter/src/presentation/utils/navigation.dart'; +export 'package:social_cv_client_flutter/src/presentation/utils/translate_error.dart'; +export 'package:social_cv_client_flutter/src/presentation/utils/utils.dart'; + +/// ---------------------------------------------------------------------------- +/// Widget +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/presentation/widgets/account_tile_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/arc_banner_image_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/bubble_indication_painter.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/entry_list_profile_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/entry_list_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/entry_profile_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/entry_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/group_list_profile_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/group_list_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/group_profile_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/group_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/part_list_profile_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/part_profile_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/part_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/profile_list_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/profile_tile_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/elements/profile_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/error_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/initial_circle_avatar_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/loading_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/login_form_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/menu_bottom_sheet_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/menu_button_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/profile_image_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/register_form_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/repository_provider.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/sort_box_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/sort_dialog_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/sort_list_tile_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/splash_widget.dart'; +export 'package:social_cv_client_flutter/src/presentation/widgets/theme_switch_tile_widget.dart'; diff --git a/lib/src/app.dart b/lib/src/app.dart deleted file mode 100644 index d883edc..0000000 --- a/lib/src/app.dart +++ /dev/null @@ -1,236 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:provider/provider.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; -import 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration.dart'; -import 'package:social_cv_client_flutter/src/router.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/splash_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; - -class ConfigWrapperApp extends StatefulWidget { - const ConfigWrapperApp({Key key}) : super(key: key); - - @override - State createState() => _ConfigWrapperAppState(); -} - -class _ConfigWrapperAppState extends State { - ConfigurationBloc _configBloc; - - @override - void initState() { - super.initState(); - _configBloc = ConfigurationBloc(); - - /// Inform ConfigBloc that the application have been launched - _configBloc.dispatch(AppLaunched()); - } - - @override - void dispose() { - _configBloc?.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return BlocBuilder( - bloc: _configBloc, - builder: (BuildContext context, ConfigurationState state) { - if (state is ConfigLoading) { - return SplashApp(); - } else if (state is ConfigLoaded) { - return BlocProvider( - bloc: _configBloc, - child: MultiProvider( - providers: [ - Provider.value( - value: state.cvRepository, - updateShouldNotify: (previous, current) => false, - ), - Provider.value( - value: state.configRepository, - updateShouldNotify: (previous, current) => false, - ), - Provider.value( - value: state.authPreferencesRepository, - updateShouldNotify: (previous, current) => false, - ), - Provider.value( - value: state.appPreferencesRepository, - updateShouldNotify: (previous, current) => false, - ), - ], - child: _AppWrapper(state: state), - ), - ); - } - return ErrorApp(error: NotImplementedYetError()); - }, - ); - } -} - -class _AppWrapper extends StatefulWidget { - final ConfigLoaded state; - - _AppWrapper({Key key, @required this.state}) : super(key: key); - - @override - State createState() => _AppWrapperState(); -} - -class _AppWrapperState extends State<_AppWrapper> { - final String _tag = '$_AppWrapperState'; - - AppBloc _appBloc; - AccountBloc _accountBloc; - AuthenticationBloc _authBloc; - - ConfigLoaded get _state => widget.state; - - @override - void initState() { - super.initState(); - _appBloc = AppBloc( - appPreferencesRepository: _state.appPreferencesRepository, - ); - - _accountBloc = AccountBloc( - cvRepository: _state.cvRepository, - appPreferencesRepository: _state.appPreferencesRepository, - ); - - _authBloc = AuthenticationBloc( - cvRepository: _state.cvRepository, - authPreferencesRepository: _state.authPreferencesRepository, - configRepository: _state.configRepository, - accountBloc: _accountBloc, - ); - - /// Inform AppBloc that the application just started - _appBloc.dispatch(AppConfigured()); - - /// Inform AuthBloc that the application just started - _authBloc.dispatch(AppStarted()); - } - - @override - void dispose() { - _appBloc?.dispose(); - _accountBloc?.dispose(); - _authBloc?.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - Logger.log('$_tag:$build'); - - AppState tmpState = AppInitialized.defaultValues(); - - return BlocBuilder( - bloc: _appBloc, - builder: (BuildContext context, AppState state) { - if (state is AppLoading || state is AppInitialized) { - if (state is AppLoading) state = tmpState; - tmpState = state; - - /// Dependency Injection of repositories and blocs - /// Use updateShouldNotify to make dependencies available in - /// `initState` methods of children widgets - return BlocProviderTree( - blocProviders: [ - BlocProvider(bloc: _appBloc), - BlocProvider(bloc: _authBloc), - BlocProvider(bloc: _accountBloc), - ], - child: _App(state: state), - ); - } else if (state is AppFailure) { - return ErrorApp(error: state.error); - } - - return ErrorApp(error: NotImplementedYetError()); - }, - ); - } -} - -class _App extends StatelessWidget { - final String _tag = '$_App'; - - final AppInitialized state; - - _App({Key key, @required this.state}) - : assert(state != null, 'No $AppInitialized given'), - super(key: key); - - @override - Widget build(BuildContext context) { - Logger.log('$_tag:$build'); - - ///Routes - final appRouter = AppRouter(); - - return MaterialApp( - onGenerateTitle: (BuildContext context) => - CVLocalizations.of(context).appName, - theme: _buildCVTheme(state.theme), - home: MainPage(), - onGenerateRoute: appRouter.router.generator, - - ///Use Fluro routes - localizationsDelegates: [ - const CVLocalizationsDelegate(), - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - supportedLocales: [ - const Locale('en'), - const Locale('fr'), - ], - debugShowCheckedModeBanner: false, -// showSemanticsDebugger: true, - ); - } - - ThemeData _buildCVTheme(String theme) { - ThemeData base; - if (theme != ThemeType.DARK) - base = ThemeData.light(); - else { - base = ThemeData.dark(); - } - - return base.copyWith( - primaryColor: AppStyles.primaryColor, - primaryColorLight: AppStyles.primaryColorLight, - primaryColorDark: AppStyles.primaryColorDark, - accentColor: AppStyles.accentColor, - buttonColor: (theme != ThemeType.DARK) - ? AppStyles.colorWhite - : AppStyles.primaryColorDark, - inputDecorationTheme: InputDecorationTheme( - border: OutlineInputBorder(), - ), - textTheme: _buildCVTextTheme(base.textTheme), - primaryTextTheme: _buildCVTextTheme(base.primaryTextTheme), - accentTextTheme: _buildCVTextTheme(base.accentTextTheme), - ); - } - - TextTheme _buildCVTextTheme(TextTheme base) { - return base.apply( - fontFamily: 'Google Sans', - ); - } -} diff --git a/lib/src/bloc/blocs/configuration/configuration.dart b/lib/src/bloc/blocs/configuration/configuration.dart new file mode 100644 index 0000000..5a48431 --- /dev/null +++ b/lib/src/bloc/blocs/configuration/configuration.dart @@ -0,0 +1,3 @@ +export './configuration_bloc.dart'; +export './configuration_event.dart'; +export './configuration_state.dart'; diff --git a/lib/src/bloc/blocs/configuration/configuration_bloc.dart b/lib/src/bloc/blocs/configuration/configuration_bloc.dart new file mode 100644 index 0000000..b8b8d2c --- /dev/null +++ b/lib/src/bloc/blocs/configuration/configuration_bloc.dart @@ -0,0 +1,178 @@ +import 'package:bloc/bloc.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +class ConfigurationBloc extends Bloc { + final String _tag = '$ConfigurationBloc'; + + ConfigurationBloc() : super(); + + /// Services + FoundationConfigService _foundationConfigService; + CVAuthService _cvAuthService; + + /// Repositories + AuthInfoRepository _authInfoRepository; + AppPrefsRepository _appPrefsRepository; + + /// Entities Repositories + IdentityRepository _identityRepository; + UserRepository _userRepository; + ProfileRepository _profileRepository; + PartRepository _partRepository; + GroupRepository _groupRepository; + EntryRepository _entryRepository; + + @override + ConfigurationState get initialState => ConfigLoading(); + + @override + Stream mapEventToState(ConfigurationEvent event) async* { + if (event is AppLaunched) { + yield* _mapAppLaunchedEventToState(); + } + } + + /// ----------------------------------------------------------------------- + /// All Event map to State + /// ----------------------------------------------------------------------- + + Stream _mapAppLaunchedEventToState() async* { + try { + yield ConfigLoading(); + + _foundationConfigService = ConfigAssetsManager(); + + final AuthInfoDataStore diskAuthInfoDataStore = + AuthSharedPreferencesManager(); + + final apiInterceptor = ApiInterceptor( + accessToken: await diskAuthInfoDataStore.getAccessToken(), + refreshToken: await diskAuthInfoDataStore.getRefreshToken(), + ); + + final CVApiManager cvApiManager = CVApiManager( + apiBaseUrl: await _foundationConfigService.getApiServerUrl(), + tokenInterceptor: apiInterceptor, + ); + + _cvAuthService = cvApiManager; + + // Data Stores + + final AppPrefsDataStore diskAppPrefsDataStore = AppPrefsManager(); + + final IdentityDataStore memoryIdentityDataStore = + MemoryIdentityDataStore(); + final IdentityDataStore cloudIdentityDataStore = cvApiManager; + + final ProfileDataStore memoryProfileDataStore = MemoryProfileDataStore(); + final ProfileDataStore cloudProfileDataStore = cvApiManager; + + final UserDataStore memoryUserDataStore = MemoryUserDataStore(); + final UserDataStore cloudUserDataStore = cvApiManager; + + final PartDataStore memoryPartDataStore = MemoryPartDataStore(); + final PartDataStore cloudPartDataStore = cvApiManager; + + final GroupDataStore memoryGroupDataStore = MemoryGroupDataStore(); + final GroupDataStore cloudGroupDataStore = cvApiManager; + + final EntryDataStore memoryEntryDataStore = MemoryEntryDataStore(); + final EntryDataStore cloudEntryDataStore = cvApiManager; + + // Data Store Factories + + final appPrefsDataStoreFactory = AppPrefsDataStoreFactory( + diskDataStore: diskAppPrefsDataStore, + ); + + final authInfoDataStoreFactory = AuthInfoDataStoreFactory( + diskDataStore: diskAuthInfoDataStore, + ); + + final identityDataStoreFactory = IdentityDataStoreFactory( + memoryDataStore: memoryIdentityDataStore, + cloudDataStore: cloudIdentityDataStore, + ); + + final userDataStoreFactory = UserDataStoreFactory( + memoryDataStore: memoryUserDataStore, + cloudDataStore: cloudUserDataStore, + ); + + final profileDataStoreFactory = ProfileDataStoreFactory( + memoryDataStore: memoryProfileDataStore, + cloudDataStore: cloudProfileDataStore, + ); + + final partDataStoreFactory = PartDataStoreFactory( + memoryDataStore: memoryPartDataStore, + cloudDataStore: cloudPartDataStore, + ); + + final groupDataStoreFactory = GroupDataStoreFactory( + memoryDataStore: memoryGroupDataStore, + cloudDataStore: cloudGroupDataStore, + ); + + final entryDataStoreFactory = EntryDataStoreFactory( + memoryDataStore: memoryEntryDataStore, + cloudDataStore: cloudEntryDataStore, + ); + + // Repositories + + _appPrefsRepository = ImplAppPrefsRepository( + factory: appPrefsDataStoreFactory, + ); + + _authInfoRepository = ImplAuthInfoRepository( + factory: authInfoDataStoreFactory, + ); + + _identityRepository = ImplIdentityRepository( + factory: identityDataStoreFactory, + ); + + _userRepository = ImplUserRepository( + factory: userDataStoreFactory, + ); + + _profileRepository = ImplProfileRepository( + factory: profileDataStoreFactory, + ); + + _partRepository = ImplPartRepository( + factory: partDataStoreFactory, + ); + + _groupRepository = ImplGroupRepository( + factory: groupDataStoreFactory, + ); + + _entryRepository = ImplEntryRepository( + factory: entryDataStoreFactory, + ); + + // Return config + + yield ConfigLoaded( + cvAuthService: _cvAuthService, + authInfoRepository: _authInfoRepository, + appPrefsRepository: _appPrefsRepository, + identityRepository: _identityRepository, + userRepository: _userRepository, + profileRepository: _profileRepository, + partRepository: _partRepository, + groupRepository: _groupRepository, + entryRepository: _entryRepository, + ); + } catch (error, stacktrace) { + Logger.error('${error.runtimeType}', stackTrace: stacktrace); + yield ConfigFailure(error: error); + } + } +} diff --git a/lib/src/domain/blocs/configuration/configuration_event.dart b/lib/src/bloc/blocs/configuration/configuration_event.dart similarity index 79% rename from lib/src/domain/blocs/configuration/configuration_event.dart rename to lib/src/bloc/blocs/configuration/configuration_event.dart index bad7b3b..ae9b059 100644 --- a/lib/src/domain/blocs/configuration/configuration_event.dart +++ b/lib/src/bloc/blocs/configuration/configuration_event.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; -/// [ConfigurationEvent] that must be dispatch to [ApplicationBloc] +/// [ConfigurationEvent] that must be dispatch to [AppBloc] abstract class ConfigurationEvent extends Equatable { ConfigurationEvent([List props = const []]) : super(props); diff --git a/lib/src/bloc/blocs/configuration/configuration_state.dart b/lib/src/bloc/blocs/configuration/configuration_state.dart new file mode 100644 index 0000000..cefdd55 --- /dev/null +++ b/lib/src/bloc/blocs/configuration/configuration_state.dart @@ -0,0 +1,70 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class ConfigurationState extends Equatable { + ConfigurationState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class ConfigLoading extends ConfigurationState {} + +class ConfigLoaded extends ConfigurationState { + final CVAuthService cvAuthService; + + final AuthInfoRepository authInfoRepository; + final AppPrefsRepository appPrefsRepository; + + final IdentityRepository identityRepository; + final UserRepository userRepository; + final ProfileRepository profileRepository; + final PartRepository partRepository; + final GroupRepository groupRepository; + final EntryRepository entryRepository; + + ConfigLoaded({ + @required this.cvAuthService, + @required this.authInfoRepository, + @required this.appPrefsRepository, + @required this.identityRepository, + @required this.userRepository, + @required this.profileRepository, + @required this.partRepository, + @required this.groupRepository, + @required this.entryRepository, + }) : assert(cvAuthService != null, 'No $CVAuthService given'), + assert(authInfoRepository != null, 'No $AuthInfoRepository given'), + assert(appPrefsRepository != null, 'No $AppPrefsRepository given'), + assert(identityRepository != null, 'No $IdentityRepository given'), + assert(userRepository != null, 'No $UserRepository given'), + assert(profileRepository != null, 'No $ProfileRepository given'), + assert(partRepository != null, 'No $PartRepository given'), + assert(groupRepository != null, 'No $GroupRepository given'), + assert(entryRepository != null, 'No $EntryRepository given'), + super([ + cvAuthService, + authInfoRepository, + appPrefsRepository, + identityRepository, + userRepository, + profileRepository, + partRepository, + groupRepository, + entryRepository, + ]); +} + +class ConfigFailure extends ConfigurationState { + final dynamic error; + + ConfigFailure({@required this.error}) + : assert(error != null), + super([error]); + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/data/managers/app_shared_preferences_manager.dart b/lib/src/data/managers/app_shared_preferences_manager.dart index bdd1138..f36ff7e 100644 --- a/lib/src/data/managers/app_shared_preferences_manager.dart +++ b/lib/src/data/managers/app_shared_preferences_manager.dart @@ -1,75 +1,50 @@ -import 'package:shared_preferences/shared_preferences.dart'; - -/// One of the possible Implementation of PreferencesService Interface -class AppSharedPreferencesManager { - final String _keyUserId = 'USER_ID'; - final String _keyUserEmail = 'USER_EMAIL'; - final String _keyAppTheme = 'APP_THEME'; - - Future get _prefs => SharedPreferences.getInstance(); - - AppSharedPreferencesManager(); - - /// ---------------------------------------------------------- - /// ------------------------- User --------------------------- - /// ---------------------------------------------------------- - - Future setUserId(String userId) async { - final prefs = await _prefs; - return prefs.setString(_keyUserId, userId); - } +import 'dart:async'; - Future getUserId() async { - final prefs = await _prefs; - return prefs.getString(_keyUserId); - } +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:social_cv_client_flutter/data.dart'; - Future deleteUserId() async { - final prefs = await _prefs; - return prefs.remove(_keyUserId); - } +/// Application preferences manager implementation +/// providing [AppPrefsDataStore] +class AppPrefsManager implements AppPrefsDataStore { + final String _keyAppDarkMode = 'APP_DARK_MODE'; - Future setUserEmail(String userEmail) async { - final prefs = await _prefs; - return prefs.setString(_keyUserEmail, userEmail); - } + FutureOr get _prefs => SharedPreferences.getInstance(); - Future getUserEmail() async { - final prefs = await _prefs; - return prefs.getString(_keyUserEmail); - } - - Future deleteUserEmail() async { - final prefs = await _prefs; - return prefs.remove(_keyUserEmail); - } + AppPrefsManager(); - /// ---------------------------------------------------------- - /// ------------------------- Theme -------------------------- - /// ---------------------------------------------------------- + /// -------------------------------------------------------------------------- + /// Dark Mode + /// -------------------------------------------------------------------------- - Future getAppTheme() async { - final SharedPreferences prefs = await _prefs; - return prefs.getString(_keyAppTheme); + @override + FutureOr getDarkMode() async { + final storage = await _prefs; + return storage.getBool(_keyAppDarkMode); } - Future setAppTheme(String theme) async { - final SharedPreferences prefs = await _prefs; - return prefs.setString(_keyAppTheme, theme); + @override + FutureOr toggleDarkMode(bool darkMode) async { + final storage = await _prefs; + return await storage.setBool( + _keyAppDarkMode, + darkMode, + ); } - Future deleteAppTheme() async { - final SharedPreferences prefs = await _prefs; - return prefs.remove(_keyAppTheme); + @override + FutureOr deleteDarkMode() async { + final storage = await _prefs; + await storage.remove(_keyAppDarkMode); + return null; } - /// ---------------------------------------------------------- - /// -------------------------- All --------------------------- - /// ---------------------------------------------------------- + /// -------------------------------------------------------------------------- + /// All + /// -------------------------------------------------------------------------- - Future deleteAll() async { - await this.deleteUserId(); - await this.deleteUserEmail(); - await this.deleteAppTheme(); + @override + Future deleteAll() async { + final storage = await _prefs; + await storage.remove(_keyAppDarkMode); } } diff --git a/lib/src/data/managers/auth_shared_preferences_manager.dart b/lib/src/data/managers/auth_shared_preferences_manager.dart index c716609..b0d134f 100644 --- a/lib/src/data/managers/auth_shared_preferences_manager.dart +++ b/lib/src/data/managers/auth_shared_preferences_manager.dart @@ -1,14 +1,17 @@ +import 'dart:async'; + import 'package:shared_preferences/shared_preferences.dart'; +import 'package:social_cv_client_flutter/data.dart'; /// One of the possible Implementation of PreferencesService Interface -class AuthSharedPreferencesManager { +class AuthSharedPreferencesManager implements AuthInfoDataStore { final String _keyOAuthAccessToken = 'OAUTH_ACCESS_TOKEN'; final String _keyOAuthAccessTokenExpiration = 'OAUTH_ACCESS_TOKEN_EXPIRATION'; final String _keyOAuthRefreshToken = 'OAUTH_REFRESH_TOKEN'; final String _keyOAuthRefreshTokenExpiration = 'OAUTH_REFRESH_TOKEN_EXPIRATION'; - Future get _prefs => SharedPreferences.getInstance(); + FutureOr get _prefs => SharedPreferences.getInstance(); AuthSharedPreferencesManager(); @@ -16,65 +19,88 @@ class AuthSharedPreferencesManager { /// ------------------------- Tokens ------------------------- /// ---------------------------------------------------------- - Future getAccessToken() async { + @override + FutureOr getAccessToken() async { final prefs = await _prefs; return prefs.getString(_keyOAuthAccessToken); } - Future setAccessToken(String accessToken) async { + @override + FutureOr setAccessToken(String token) async { final prefs = await _prefs; - return prefs.setString(_keyOAuthAccessToken, accessToken); + return (await prefs.setString(_keyOAuthAccessToken, token)) ? token : null; } - Future deleteAccessToken() async { + @override + FutureOr deleteAccessToken() async { final prefs = await _prefs; - return prefs.remove(_keyOAuthAccessToken); + await prefs.remove(_keyOAuthAccessToken); + return null; } - Future getAccessTokenExpiration() async { + @override + FutureOr getAccessTokenExpiration() async { final prefs = await _prefs; - return prefs.getInt(_keyOAuthAccessTokenExpiration); + return DateTime.parse(prefs.getString(_keyOAuthAccessTokenExpiration)); } - Future setAccessTokenExpiration(int accessTokenExpiration) async { + @override + FutureOr setAccessTokenExpiration(DateTime expiration) async { final prefs = await _prefs; - return prefs.setInt(_keyOAuthAccessTokenExpiration, accessTokenExpiration); + return (await prefs.setString( + _keyOAuthAccessTokenExpiration, expiration.toIso8601String())) + ? expiration + : null; } - Future deleteAccessTokenExpiration() async { + @override + FutureOr deleteAccessTokenExpiration() async { final prefs = await _prefs; - return prefs.remove(_keyOAuthAccessTokenExpiration); + await prefs.remove(_keyOAuthAccessTokenExpiration); + return null; } - Future getRefreshToken() async { + @override + FutureOr getRefreshToken() async { final prefs = await _prefs; return prefs.getString(_keyOAuthRefreshToken); } - Future setRefreshToken(String refreshToken) async { + @override + FutureOr setRefreshToken(String refreshToken) async { final prefs = await _prefs; - return prefs.setString(_keyOAuthRefreshToken, refreshToken); + return (await prefs.setString(_keyOAuthRefreshToken, refreshToken)) + ? refreshToken + : null; } - Future deleteRefreshToken() async { + @override + FutureOr deleteRefreshToken() async { final prefs = await _prefs; - return prefs.remove(_keyOAuthRefreshToken); + await prefs.remove(_keyOAuthRefreshToken); + return null; } - Future getRefreshTokenExpiration() async { + @override + FutureOr getRefreshTokenExpiration() async { final prefs = await _prefs; - return prefs.getString(_keyOAuthRefreshTokenExpiration); + return DateTime.parse(prefs.getString(_keyOAuthRefreshTokenExpiration)); } - Future setRefreshTokenExpiration(int refreshTokenExpiration) async { + @override + FutureOr setRefreshTokenExpiration(DateTime expiration) async { final prefs = await _prefs; - return prefs.setInt( - _keyOAuthRefreshTokenExpiration, refreshTokenExpiration); + return (await prefs.setString( + _keyOAuthRefreshTokenExpiration, expiration.toIso8601String())) + ? expiration + : null; } - Future deleteRefreshTokenExpiration() async { + @override + FutureOr deleteRefreshTokenExpiration() async { final prefs = await _prefs; - return prefs.remove(_keyOAuthRefreshTokenExpiration); + await prefs.remove(_keyOAuthRefreshTokenExpiration); + return null; } /// ---------------------------------------------------------- @@ -82,9 +108,9 @@ class AuthSharedPreferencesManager { /// ---------------------------------------------------------- Future deleteAll() async { - await this.deleteAccessToken(); - await this.deleteAccessTokenExpiration(); - await this.deleteRefreshToken(); - await this.deleteRefreshTokenExpiration(); + await deleteAccessToken(); + await deleteAccessTokenExpiration(); + await deleteRefreshToken(); + await deleteRefreshTokenExpiration(); } } diff --git a/lib/src/data/managers/config_assets_manager.dart b/lib/src/data/managers/config_assets_manager.dart index 6b30d9b..e7f384f 100644 --- a/lib/src/data/managers/config_assets_manager.dart +++ b/lib/src/data/managers/config_assets_manager.dart @@ -1,36 +1,41 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/services.dart'; +import 'package:social_cv_client_flutter/domain.dart'; import 'package:social_cv_client_flutter/src/data/models/config_model.dart'; /// From https://medium.com/@sokrato/storing-your-secret-keys-in-flutter-c0b9af1c0f69 -class ConfigAssetsManager { +class ConfigAssetsManager implements FoundationConfigService { static const _configPath = 'config.json'; ConfigDataModel _config; ConfigAssetsManager(); - Future _load() async { - await rootBundle.loadStructuredData( + FutureOr _load() async { + final jsonMap = await rootBundle.loadStructuredData>( _configPath, (jsonStr) { - Map jsonMap = json.decode(jsonStr); - _config = ConfigDataModel.fromJson(jsonMap); + return Future.value(json.decode(jsonStr) as Map); }, ); + _config = ConfigDataModel.fromJson(jsonMap); } - Future getApiServerUrl() async { + @override + FutureOr getApiServerUrl() async { if (_config == null) await _load(); return Future.value(_config.apiServerUrl); } - Future getClientId() async { + @override + FutureOr getClientId() async { if (_config == null) await _load(); return Future.value(_config.clientId); } - Future getClientSecret() async { + @override + FutureOr getClientSecret() async { if (_config == null) await _load(); return Future.value(_config.clientSecret); } diff --git a/lib/src/data/models/config_model.dart b/lib/src/data/models/config_model.dart index ede4b70..394931f 100644 --- a/lib/src/data/models/config_model.dart +++ b/lib/src/data/models/config_model.dart @@ -7,8 +7,10 @@ part 'config_model.g.dart'; class ConfigDataModel { @JsonKey(name: 'apiServerUrl') String apiServerUrl; + @JsonKey(name: 'clientId') String clientId; + @JsonKey(name: 'clientSecret') String clientSecret; @@ -24,6 +26,9 @@ class ConfigDataModel { Map toJson() => _$ConfigDataModelToJson(this); @override - String toString() => - '$runtimeType{ apiServerUrl: $apiServerUrl, clientId: $clientId, clientSecret: $clientSecret }'; + String toString() => '$runtimeType{ ' + 'apiServerUrl: $apiServerUrl, ' + 'clientId: $clientId, ' + 'clientSecret: $clientSecret' + ' }'; } diff --git a/lib/src/data/repositories/local_app_preferences_repository.dart b/lib/src/data/repositories/local_app_preferences_repository.dart deleted file mode 100644 index c096cfa..0000000 --- a/lib/src/data/repositories/local_app_preferences_repository.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; -import 'package:social_cv_client_flutter/src/data/managers/app_shared_preferences_manager.dart'; - -class LocalAppPreferencesRepository implements AppPreferencesRepository { - final AppSharedPreferencesManager appSharedPreferencesManager; - - LocalAppPreferencesRepository({@required this.appSharedPreferencesManager}) - : assert( - appSharedPreferencesManager != null, - 'No $AppSharedPreferencesManager given', - ); - - @override - Future deleteAll() async { - return await appSharedPreferencesManager.deleteAll(); - } - - @override - Future deleteAppTheme() async { - return await appSharedPreferencesManager.deleteAppTheme(); - } - - @override - Future deleteUserEmail() async { - return await appSharedPreferencesManager.deleteUserEmail(); - } - - @override - Future deleteUserId() async { - return await appSharedPreferencesManager.deleteUserId(); - } - - @override - Future getAppTheme() async { - return await appSharedPreferencesManager.getAppTheme(); - } - - @override - Future getUserEmail() async { - return await appSharedPreferencesManager.getUserEmail(); - } - - @override - Future getUserId() async { - return await appSharedPreferencesManager.getUserId(); - } - - @override - Future setAppTheme(String theme) async { - return await appSharedPreferencesManager.setAppTheme(theme); - } - - @override - Future setUserEmail(String userEmail) async { - return await appSharedPreferencesManager.setUserEmail(userEmail); - } - - @override - Future setUserId(String userId) async { - return await appSharedPreferencesManager.setUserId(userId); - } -} diff --git a/lib/src/data/repositories/local_auth_preferences_repository.dart b/lib/src/data/repositories/local_auth_preferences_repository.dart deleted file mode 100644 index 9c0ccb8..0000000 --- a/lib/src/data/repositories/local_auth_preferences_repository.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; -import 'package:social_cv_client_flutter/src/data/managers/auth_shared_preferences_manager.dart'; - -class LocalAuthPreferencesRepository implements AuthPreferencesRepository { - final AuthSharedPreferencesManager authSharedPreferencesManager; - - LocalAuthPreferencesRepository({@required this.authSharedPreferencesManager}) - : assert( - authSharedPreferencesManager != null, - 'No $AuthSharedPreferencesManager given', - ); - - @override - Future deleteAccessToken() async { - return await authSharedPreferencesManager.deleteAccessToken(); - } - - @override - Future deleteAccessTokenExpiration() async { - return await authSharedPreferencesManager.deleteAccessTokenExpiration(); - } - - @override - Future deleteRefreshToken() async { - return await authSharedPreferencesManager.deleteRefreshToken(); - } - - @override - Future deleteRefreshTokenExpiration() async { - return await authSharedPreferencesManager.deleteRefreshTokenExpiration(); - } - - @override - Future getAccessToken() async { - return await authSharedPreferencesManager.getAccessToken(); - } - - @override - Future getAccessTokenExpiration() async { - return await authSharedPreferencesManager.getAccessTokenExpiration(); - } - - @override - Future getRefreshToken() async { - return await authSharedPreferencesManager.getRefreshToken(); - } - - @override - Future getRefreshTokenExpiration() async { - return await authSharedPreferencesManager.getRefreshTokenExpiration(); - } - - @override - Future setAccessToken(String accessToken) async { - return await authSharedPreferencesManager.setAccessToken(accessToken); - } - - @override - Future setAccessTokenExpiration(int accessTokenExpiration) async { - return await authSharedPreferencesManager - .setAccessTokenExpiration(accessTokenExpiration); - } - - @override - Future setRefreshToken(String refreshToken) async { - return await authSharedPreferencesManager.setRefreshToken(refreshToken); - } - - @override - Future setRefreshTokenExpiration(int refreshTokenExpiration) async { - return await authSharedPreferencesManager - .setRefreshTokenExpiration(refreshTokenExpiration); - } - - @override - Future deleteAll() async { - return await authSharedPreferencesManager.deleteAll(); - } -} diff --git a/lib/src/data/repositories/local_config_repository.dart b/lib/src/data/repositories/local_config_repository.dart deleted file mode 100644 index b0d5e7c..0000000 --- a/lib/src/data/repositories/local_config_repository.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; -import 'package:social_cv_client_flutter/src/data/managers/config_assets_manager.dart'; - -class LocalConfigRepository implements ConfigRepository { - final ConfigAssetsManager configAssetsManager; - - LocalConfigRepository({@required this.configAssetsManager}) - : assert( - configAssetsManager != null, - 'No $LocalConfigRepository given', - ); - - @override - Future getApiServerUrl() async { - return await configAssetsManager.getApiServerUrl(); - } - - @override - Future getClientId() async { - return await configAssetsManager.getClientId(); - } - - @override - Future getClientSecret() async { - return await configAssetsManager.getClientSecret(); - } -} diff --git a/lib/src/domain/blocs/configuration/configuration.dart b/lib/src/domain/blocs/configuration/configuration.dart deleted file mode 100644 index 37baa13..0000000 --- a/lib/src/domain/blocs/configuration/configuration.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration_state.dart'; - -export './configuration_bloc.dart'; -export './configuration_event.dart'; diff --git a/lib/src/domain/blocs/configuration/configuration_bloc.dart b/lib/src/domain/blocs/configuration/configuration_bloc.dart deleted file mode 100644 index 09392c0..0000000 --- a/lib/src/domain/blocs/configuration/configuration_bloc.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:bloc/bloc.dart'; -import 'package:social_cv_client_dart_common/managers.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; -import 'package:social_cv_client_flutter/src/data/managers/app_shared_preferences_manager.dart'; -import 'package:social_cv_client_flutter/src/data/managers/auth_shared_preferences_manager.dart'; -import 'package:social_cv_client_flutter/src/data/managers/config_assets_manager.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/local_app_preferences_repository.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/local_auth_preferences_repository.dart'; -import 'package:social_cv_client_flutter/src/data/repositories/local_config_repository.dart'; -import 'package:social_cv_client_flutter/src/domain/blocs/configuration/configuration.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; - -class ConfigurationBloc extends Bloc { - final String _tag = '$ConfigurationBloc'; - - ConfigurationBloc() : super(); - - /// Repositories - CVRepository _cvRepository; - AuthPreferencesRepository _authPreferencesRepository; - AppPreferencesRepository _appPreferencesRepository; - ConfigRepository _configRepository; - - @override - ConfigurationState get initialState => ConfigLoading(); - - @override - Stream mapEventToState(ConfigurationEvent event) async* { - if (event is AppLaunched) { - yield* _mapAppLaunchedEventToState(); - } - } - - Stream _mapAppLaunchedEventToState() async* { - try { - yield ConfigLoading(); - - /// Managers - final _appSharedPreferencesManager = AppSharedPreferencesManager(); - final _authSharedPreferencesManager = AuthSharedPreferencesManager(); - final _localConfigManager = ConfigAssetsManager(); - - /// Repositories - _appPreferencesRepository = LocalAppPreferencesRepository( - appSharedPreferencesManager: _appSharedPreferencesManager, - ); - _authPreferencesRepository = LocalAuthPreferencesRepository( - authSharedPreferencesManager: _authSharedPreferencesManager, - ); - _configRepository = LocalConfigRepository( - configAssetsManager: _localConfigManager, - ); - - /// CV Managers and repositories - final _apiInterceptor = ApiInterceptor( - accessToken: await _authPreferencesRepository.getAccessToken(), - refreshToken: await _authPreferencesRepository.getRefreshToken(), - ); - - final _cvApiManager = DefaultCVApiManager( - apiInterceptor: _apiInterceptor, - apiBaseUrl: await _configRepository.getApiServerUrl(), - ); - - final _cvCacheManager = DefaultCVCacheManager(); - - _cvRepository = DefaultCloudCVRepository( - cvApiManager: _cvApiManager, - cvCacheManager: _cvCacheManager, - ); - - yield ConfigLoaded( - cvRepository: _cvRepository, - authPreferencesRepository: _authPreferencesRepository, - appPreferencesRepository: _appPreferencesRepository, - configRepository: _configRepository, - ); - } catch (error, stacktrace) { - Logger.error('${error.runtimeType}', stackTrace: stacktrace); - yield ConfigFailure(error: error); - } - } -} diff --git a/lib/src/domain/blocs/configuration/configuration_state.dart b/lib/src/domain/blocs/configuration/configuration_state.dart deleted file mode 100644 index b07f616..0000000 --- a/lib/src/domain/blocs/configuration/configuration_state.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; - -abstract class ConfigurationState extends Equatable { - ConfigurationState([List props = const []]) : super(props); - - @override - String toString() => '$runtimeType{}'; -} - -class ConfigLoading extends ConfigurationState {} - -class ConfigLoaded extends ConfigurationState { - final CVRepository cvRepository; - final AuthPreferencesRepository authPreferencesRepository; - final AppPreferencesRepository appPreferencesRepository; - final ConfigRepository configRepository; - - ConfigLoaded({ - @required this.cvRepository, - @required this.authPreferencesRepository, - @required this.appPreferencesRepository, - @required this.configRepository, - }) : assert( - cvRepository != null, - 'No $CVRepository given', - ), - assert( - authPreferencesRepository != null, - 'No $AuthPreferencesRepository given', - ), - assert( - appPreferencesRepository != null, - 'No $AppPreferencesRepository given', - ), - assert( - configRepository != null, - 'No $ConfigRepository given', - ), - super([ - cvRepository, - authPreferencesRepository, - appPreferencesRepository, - configRepository, - ]); -} - -class ConfigFailure extends ConfigurationState { - final Error error; - - ConfigFailure({@required this.error}) : super([error]); - - @override - String toString() => '$runtimeType{ error: ${error.runtimeType} }'; -} diff --git a/lib/src/presentation/app.dart b/lib/src/presentation/app.dart new file mode 100644 index 0000000..906b17e --- /dev/null +++ b/lib/src/presentation/app.dart @@ -0,0 +1,265 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +class ConfigWrapperApp extends StatefulWidget { + const ConfigWrapperApp({Key key}) : super(key: key); + + @override + State createState() => _ConfigWrapperAppState(); +} + +class _ConfigWrapperAppState extends State { + ConfigurationBloc _configBloc; + + @override + void initState() { + super.initState(); + _configBloc = ConfigurationBloc(); + + /// Inform ConfigBloc that the application have been launched + _configBloc.dispatch(AppLaunched()); + } + + @override + void dispose() { + _configBloc?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: _configBloc, + builder: (BuildContext context, ConfigurationState state) { + if (state is ConfigLoading) { + return SplashApp(); + } else if (state is ConfigLoaded) { + /// Dependency Injection of repositories + /// Use updateShouldNotify to make dependencies available in + /// `initState` methods of children widgets + return BlocProvider.value( + value: _configBloc, + child: MultiProvider( + providers: [ + Provider.value( + value: state.cvAuthService, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: state.authInfoRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: state.appPrefsRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: state.userRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: state.profileRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: state.partRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: state.groupRepository, + updateShouldNotify: (previous, current) => false, + ), + Provider.value( + value: state.entryRepository, + updateShouldNotify: (previous, current) => false, + ), + ], + child: _BlocWrapper(state: state), + ), + ); + } + return ErrorApp(error: NotImplementedYetError()); + }, + ); + } +} + +/// Wrap all global bloc to configure them +class _BlocWrapper extends StatefulWidget { + final ConfigLoaded state; + + const _BlocWrapper({Key key, @required this.state}) : super(key: key); + + @override + State createState() => _BlocWrapperState(); +} + +class _BlocWrapperState extends State<_BlocWrapper> { + final String _tag = '$_BlocWrapperState'; + + AppBloc _appBloc; + IdentityBloc _identityBloc; + LoginBloc _loginBloc; + RegisterBloc _registerBloc; + AuthenticationBloc _authBloc; + + ConfigLoaded get _state => widget.state; + + @override + void initState() { + super.initState(); + _appBloc = AppBloc( + appPreferencesRepository: _state.appPrefsRepository, + ); + + _loginBloc = LoginBloc(cvAuthService: _state.cvAuthService); + _registerBloc = RegisterBloc(cvAuthService: _state.cvAuthService); + + _authBloc = AuthenticationBloc( + authInfoRepository: _state.authInfoRepository, + cvAuthService: _state.cvAuthService, + loginBloc: _loginBloc, + registerBloc: _registerBloc, + ); + + _identityBloc = IdentityBloc( + identityRepo: _state.identityRepository, + authBloc: _authBloc, + ); + + // Inform AppBloc that the application just started + _appBloc.dispatch(AppConfigured()); + + // Inform AuthBloc that the application just started + _authBloc.dispatch(AppStarted()); + } + + @override + void dispose() { + _appBloc?.dispose(); + _loginBloc?.dispose(); + _registerBloc?.dispose(); + _identityBloc?.dispose(); + _authBloc?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Logger.log('$_tag:$build'); + + return MultiBlocProvider( + providers: [ + BlocProvider.value(value: _appBloc), + BlocProvider.value(value: _authBloc), + BlocProvider.value(value: _identityBloc), + BlocProvider.value(value: _loginBloc), + BlocProvider.value(value: _registerBloc), + ], + child: _App(), + ); + } +} + +class _App extends StatelessWidget { + final String _tag = '$_App'; + + _App({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + Logger.log('$_tag:build'); + + ///Routes + final appRouter = AppRouter(); + + AppInitialized tmpState; + + return BlocBuilder( + bloc: BlocProvider.of(context), + builder: (BuildContext context, AppState state) { + if (state is AppLoading || state is AppInitialized) { + if (state is AppInitialized) tmpState = state; + + return MaterialApp( + onGenerateTitle: (BuildContext context) => + CVLocalizations.of(context).appName, + theme: _buildCVTheme(tmpState.darkMode), + home: const MainPage(), + onGenerateRoute: appRouter.router.generator, + + // Use Fluro routes + localizationsDelegates: [ + const CVLocalizationsDelegate(), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en'), + Locale('fr'), + ], + debugShowCheckedModeBanner: false, + ); + } else if (state is AppFailure) { + return ErrorApp(error: state.error); + } + + return ErrorApp(error: NotImplementedYetError()); + }, + ); + } + + ThemeData _buildCVTheme(bool darkMode) { + ThemeData themeData; + if (!darkMode) { + themeData = ThemeData.light(); + } else { + themeData = ThemeData.dark(); + } + + themeData = themeData.copyWith( + primaryColor: AppStyles.primaryColor, + primaryColorLight: AppStyles.primaryColorLight, + primaryColorDark: AppStyles.primaryColorDark, + accentColor: AppStyles.accentColor, + inputDecorationTheme: InputDecorationTheme( + hasFloatingPlaceholder: true, + border: OutlineInputBorder(), + ), + ); + + Color buttonColor; + ButtonThemeData buttonTheme; + IconThemeData iconThemeData; + if (!darkMode) { + buttonColor = AppStyles.colorWhite; + buttonTheme = ButtonThemeData(buttonColor: themeData.primaryColorLight); + iconThemeData = IconThemeData(color: Colors.black); + } else { + buttonColor = AppStyles.primaryColorDark; + buttonTheme = ButtonThemeData(buttonColor: themeData.primaryColorDark); + iconThemeData = IconThemeData(color: Colors.white); + } + + return themeData.copyWith( + buttonColor: buttonColor, + buttonTheme: buttonTheme, + textTheme: _buildCVTextTheme(themeData.textTheme), + primaryTextTheme: _buildCVTextTheme(themeData.primaryTextTheme), + accentTextTheme: _buildCVTextTheme(themeData.accentTextTheme), + iconTheme: iconThemeData, + ); + } + + TextTheme _buildCVTextTheme(TextTheme base) { + return base.apply( + fontFamily: 'Arial', + ); + } +} diff --git a/lib/src/ui/commons/api_values.dart b/lib/src/presentation/commons/api_values.dart similarity index 100% rename from lib/src/ui/commons/api_values.dart rename to lib/src/presentation/commons/api_values.dart diff --git a/lib/src/ui/commons/assets.dart b/lib/src/presentation/commons/assets.dart similarity index 100% rename from lib/src/ui/commons/assets.dart rename to lib/src/presentation/commons/assets.dart diff --git a/lib/src/ui/commons/defaults.dart b/lib/src/presentation/commons/defaults.dart similarity index 100% rename from lib/src/ui/commons/defaults.dart rename to lib/src/presentation/commons/defaults.dart diff --git a/lib/src/ui/commons/paths.dart b/lib/src/presentation/commons/paths.dart similarity index 100% rename from lib/src/ui/commons/paths.dart rename to lib/src/presentation/commons/paths.dart diff --git a/lib/src/ui/commons/styles.dart b/lib/src/presentation/commons/styles.dart similarity index 83% rename from lib/src/ui/commons/styles.dart rename to lib/src/presentation/commons/styles.dart index d59e1f9..3d77536 100644 --- a/lib/src/ui/commons/styles.dart +++ b/lib/src/presentation/commons/styles.dart @@ -25,17 +25,17 @@ class AppStyles { /// Basic Colors /// -------------------------------------------------------------------------- - static const Color primaryColor = const Color(0xFF2196f3); - static const Color primaryColorLight = const Color(0xFF6ec6ff); - static const Color primaryColorDark = const Color(0xFF0069c0); - static const Color textOnPrimary = const Color(0xFFFFFFFF); - static const Color accentColor = const Color(0xFFFF5722); - static const Color accentColorLight = const Color(0xFFff8a50); - static const Color accentColorDark = const Color(0xFFc41c00); - static const Color textOnAccent = const Color(0xFFFFFFFF); - static const Color backgroundColor = const Color(0xFFFFFFFF); + static const Color primaryColor = Color(0xFF2196f3); + static const Color primaryColorLight = Color(0xFF6ec6ff); + static const Color primaryColorDark = Color(0xFF0069c0); + static const Color textOnPrimary = Color(0xFFFFFFFF); + static const Color accentColor = Color(0xFFFF5722); + static const Color accentColorLight = Color(0xFFff8a50); + static const Color accentColorDark = Color(0xFFc41c00); + static const Color textOnAccent = Color(0xFFFFFFFF); + static const Color backgroundColor = Color(0xFFFFFFFF); static const Color backgroundColorLight = backgroundColor; - static const Color backgroundColorDark = const Color(0xFF3A3A3A); + static const Color backgroundColorDark = Color(0xFF3A3A3A); /// -------------------------------------------------------------------------- /// Default dimensions @@ -49,9 +49,9 @@ class AppStyles { /// Card /// -------------------------------------------------------------------------- - static const Color cardBackgroundColor = const Color(0xFFFFFFFF); + static const Color cardBackgroundColor = Color(0xFFFFFFFF); static const Color cardBackgroundColorLight = backgroundColor; - static const Color cardBackgroundColorDark = const Color(0xFF353A3A); + static const Color cardBackgroundColorDark = Color(0xFF353A3A); static const double cardDefaultElevation = 2.0; static const EdgeInsets cardDefaultPadding = EdgeInsets.all(20.0); @@ -105,7 +105,7 @@ class AppStyles { /// Element Entry /// -------------------------------------------------------------------------- - static const EdgeInsets entryPadding = const EdgeInsets.all(10.0); + static const EdgeInsets entryPadding = EdgeInsets.all(10.0); static const double entryTagSpacing = 4.0; static const double entryCardElevation = 2.0; static const double entryEventHeight = 200.0; diff --git a/lib/src/ui/commons/tags.dart b/lib/src/presentation/commons/tags.dart similarity index 100% rename from lib/src/ui/commons/tags.dart rename to lib/src/presentation/commons/tags.dart diff --git a/lib/src/presentation/localizations/cv_localization.dart b/lib/src/presentation/localizations/cv_localization.dart new file mode 100644 index 0000000..90d7067 --- /dev/null +++ b/lib/src/presentation/localizations/cv_localization.dart @@ -0,0 +1,361 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization_en.dart'; +import 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization_fr.dart'; + +abstract class CVLocalizations { + static CVLocalizations of(BuildContext context) { + return Localizations.of(context, CVLocalizations); + } + + /// -------------------------------------------------------------------------- + /// Common + /// -------------------------------------------------------------------------- + + String get token; + + String get cancelCTA; + + String get settingsCTA; + + String get account; + + String get home; + + String get resume; + + String get profile; + + String get search; + + String get history; + + String get loadMore; + + String get retryCTA; + + String get yesCTA; + + String get noCTA; + + String get moreCTA; + + String get errorOccurred; + + /// -------------------------------------------------------------------------- + /// App + /// -------------------------------------------------------------------------- + + String get appName; + + /// -------------------------------------------------------------------------- + /// Menu Widget + /// -------------------------------------------------------------------------- + + String get menuPPCTA; + + String get menuToSCTA; + + /// -------------------------------------------------------------------------- + /// Home Page + /// -------------------------------------------------------------------------- + + String get homeTitle; + + String get homeCTA; + + String get homeWelcome; + + /// -------------------------------------------------------------------------- + /// Authentication Page - Login - SignUp + /// -------------------------------------------------------------------------- + + String get authTitle; + + String get authBubbleLoginCTA; + + String get authBubbleRegisterCTA; + + String get authNoEmailTitle; + + String get authNotEmailExplain; + + String get authNoEmailExplain; + + String get authNoPasswordTitle; + + String get authNotPasswordExplain; + + String get authNoPasswordExplain; + + String get authCreateYourAccount; + + String get authRegisterTitle; + + String get authRegisterCTA; + + String get authRegisterSucceed; + + String get authRegisterFailed; + + String get authLoginTitle; + + String get authLoginCTA; + + String get authLoginGoogleCTA; + + String get authLoginFacebookCTA; + + String get authLoginSucceed; + + String get authLoginFailed; + + String get authLogout; + + String get authLogoutCTA; + + String get authLogoutSucceed; + + String get authAccountAlreadyExistsFailure; + + String get authForgotPasswordCTA; + + String get authAlreadyHaveAccountCTA; + + String get authNoAccountCTA; + + String get authPrivacyExplain; + + String get authPrivacyReadCTA; + + String get authOr; + + /// -------------------------------------------------------------------------- + /// Account Page + /// -------------------------------------------------------------------------- + + String get accountTitle; + + String get accountCTA; + + String get accountMyProfile; + + /// -------------------------------------------------------------------------- + /// Setting Page + /// -------------------------------------------------------------------------- + + String get settingsTitle; + + String get settingsDarkModeCTA; + + String get settingsThemeLight; + + String get settingsThemeDark; + + /// Search Page + + String get searchTitle; + + String get searchSearchBarHint; + + /// -------------------------------------------------------------------------- + /// Profile Widget + /// -------------------------------------------------------------------------- + + String get profileTitle; + + String get profileWidgetDetails; + + String get profileListOptions; + + String get profileListSorting; + + String get profileListItemPerPage; + + String get profileListLoadMore; + + /// -------------------------------------------------------------------------- + /// Part Widget + /// -------------------------------------------------------------------------- + + String get partWidgetDetails; + + String get partListOptions; + + String get partListSorting; + + String get partListItemPerPage; + + String get partListLoadMore; + + /// -------------------------------------------------------------------------- + /// Group Widget + /// -------------------------------------------------------------------------- + + String get groupWidgetDetails; + + String get groupListOptions; + + String get groupListSorting; + + String get groupListItemPerPage; + + String get groupListLoadMore; + + /// -------------------------------------------------------------------------- + /// Entry Widget + /// -------------------------------------------------------------------------- + + String get entryWidgetDetails; + + String get entryListOptions; + + String get entryListSorting; + + String get entryListItemPerPage; + + String get entryListLoadMore; + + /// Sort Dialog + + String get sortDialogCancel; + + String get sortDialogConfirm; + + /// -------------------------------------------------------------------------- + /// Forms + /// -------------------------------------------------------------------------- + + String get formUsernameLabel; + + String get formEmailLabel; + + String get formEmailHint; + + String get formNoEmailExplain; + + String get formNotEmailExplain; + + String get formPasswordLabel; + + String get formNoPasswordExplain; + + String get formPassword2Label; + + String get formPasswordWrongPolicy; + + /// -------------------------------------------------------------------------- + /// Exceptions + /// -------------------------------------------------------------------------- + + String get exceptionFormatException; + + String get exceptionTimeoutException; + + /// -------------------------------------------------------------------------- + /// App Exceptions + /// -------------------------------------------------------------------------- + + String get appErrorAuthUnauthorized; + + String get appErrorAuthAccountDisabled; + + String get appErrorAuthForbidden; + + String get appErrorAuthNoToken; + + String get appErrorUserNotFound; + + String get appErrorServerSideProblem; + + /// -------------------------------------------------------------------------- + /// Errors + /// -------------------------------------------------------------------------- + + String get errorNotYetImplemented; + + String get errorNotSupported; + + /// -------------------------------------------------------------------------- + /// HTTP Client Error (4XX) + /// -------------------------------------------------------------------------- + + String get http400ClientErrorBadRequest; + + String get http401ClientErrorUnauthorized; + + String get http402ClientErrorPaymentRequired; + + String get http403ClientErrorForbidden; + + String get http404ClientErrorNotFound; + + String get http405ClientErrorMethodNotAllowed; + + String get http406ClientErrorNotAcceptable; + + String get http408ClientErrorRequestTimeout; + + String get http409ClientErrorConflict; + + String get http410ClientErrorGone; + + String get http411ClientErrorLengthRequired; + + String get http413ClientErrorPayloadTooLarge; + + String get http414ClientErrorURITooLong; + + String get http415ClientErrorUnsupportedMediaType; + + String get http417ClientErrorExpectationFailed; + + String get http426ClientErrorUpgradeRequired; + + /// -------------------------------------------------------------------------- + /// HTTP Server Error (5XX) + /// -------------------------------------------------------------------------- + + String get http500ServerErrorInternalServerError; + + String get http501ServerErrorNotImplemented; + + String get http502ServerErrorBadGateway; + + String get http503ServerErrorServiceUnavailable; + + String get http504ServerErrorGatewayTimeout; + + String get http505ServerErrorHttpVersionNotSupported; +} + +class CVLocalizationsDelegate extends LocalizationsDelegate { + const CVLocalizationsDelegate(); + + @override + bool isSupported(Locale locale) => ['en', 'fr'].contains(locale.languageCode); + + @override + Future load(Locale locale) async { + final String name = + (locale.countryCode == null || locale.countryCode.isEmpty) + ? locale.languageCode + : locale.toString(); + final String localeName = Intl.canonicalizedLocale(name); + Intl.defaultLocale = localeName; + + if (locale.languageCode == 'fr') { + return await CVLocalizationsFR.load(locale); + } else { + return await CVLocalizationsEN.load(locale); + } + } + + @override + bool shouldReload(CVLocalizationsDelegate old) => false; + + @override + String toString() => 'DefaultCVLocalizations.delegate(en_US)'; +} diff --git a/lib/src/presentation/localizations/cv_localization_en.dart b/lib/src/presentation/localizations/cv_localization_en.dart new file mode 100644 index 0000000..d28288d --- /dev/null +++ b/lib/src/presentation/localizations/cv_localization_en.dart @@ -0,0 +1,479 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization.dart'; + +class CVLocalizationsEN implements CVLocalizations { + const CVLocalizationsEN(); + + /// -------------------------------------------------------------------------- + /// Common + /// -------------------------------------------------------------------------- + + @override + String get token => 'Token'; + + @override + String get cancelCTA => 'Cancel'; + + @override + String get settingsCTA => 'Settings'; + + @override + String get account => 'Account'; + + @override + String get home => 'Home'; + + @override + String get resume => 'Resume'; + + @override + String get profile => 'Profile'; + + @override + String get search => 'Search'; + + @override + String get history => 'History'; + + @override + String get loadMore => 'Load more'; + + @override + String get retryCTA => 'Retry'; + + @override + String get yesCTA => 'Yes'; + + @override + String get noCTA => 'No'; + + @override + String get moreCTA => 'More'; + + @override + String get errorOccurred => 'An error occurred'; + + /// -------------------------------------------------------------------------- + /// App + /// -------------------------------------------------------------------------- + + @override + String get appName => 'Social CV'; + + /// -------------------------------------------------------------------------- + /// Menu Widget + /// -------------------------------------------------------------------------- + + @override + String get menuPPCTA => 'Privacy Policy'; + + @override + String get menuToSCTA => 'Terms of Service'; + + /// -------------------------------------------------------------------------- + /// Home Page + /// -------------------------------------------------------------------------- + + @override + String get homeTitle => 'Social CV'; + + @override + String get homeCTA => 'Home'; + + @override + String get homeWelcome => 'Welcome on our new resume social network !'; + + /// -------------------------------------------------------------------------- + /// Authentication Page - Login - SignUp + /// -------------------------------------------------------------------------- + + @override + String get authTitle => 'Connection'; + + @override + String get authBubbleLoginCTA => 'Login'; + + @override + String get authBubbleRegisterCTA => 'Register'; + + @override + String get authNoEmailTitle => 'Empty email'; + + @override + String get authNotEmailExplain => 'Please enter a real e-mail.'; + + @override + String get authNoEmailExplain => 'Please provide an email'; + + @override + String get authNoPasswordTitle => 'Empty password'; + + @override + String get authNoPasswordExplain => 'Please provide a password'; + + @override + String get authCreateYourAccount => 'Create your account'; + + @override + String get authPrivacyExplain => + 'Like privacy? We feel you. We don’t use or sell your data.'; + + @override + String get authPrivacyReadCTA => 'Touch to read our privacy policy.'; + + @override + String get authAlreadyHaveAccountCTA => 'Already have an account? Sign-in'; + + @override + String get authNotPasswordExplain => 'This is not a password'; + + @override + String get authRegisterTitle => 'Register'; + + @override + String get authRegisterCTA => 'Sign-up'; + + @override + String get authRegisterFailed => 'Registration failed!'; + + @override + String get authRegisterSucceed => 'Registration succeed!'; + + @override + String get authLoginTitle => 'Sign-in'; + + @override + String get authLoginCTA => 'Sign-in'; + + @override + String get authLoginGoogleCTA => 'Sign-in with Google'; + + @override + String get authLoginFacebookCTA => 'Sign-in with Facebook'; + + @override + String get authLoginSucceed => 'Login succeed!'; + + @override + String get authLoginFailed => 'Login failed!'; + + @override + String get authLogout => 'Logout'; + + @override + String get authLogoutCTA => 'Logout'; + + @override + String get authAccountAlreadyExistsFailure => + 'An account with this username or email already exists.'; + + @override + String get authLogoutSucceed => 'Logout succeed!'; + + @override + String get authForgotPasswordCTA => 'Forgot password?'; + + @override + String get authNoAccountCTA => 'Don\'t have an account yet? Sign-up'; + + @override + String get authOr => 'OR'; + + /// -------------------------------------------------------------------------- + /// Account Page + /// -------------------------------------------------------------------------- + + @override + String get accountTitle => 'Account'; + + @override + String get accountCTA => 'Account'; + + @override + String get accountMyProfile => 'My profiles'; + + /// -------------------------------------------------------------------------- + /// Setting Page + /// -------------------------------------------------------------------------- + + @override + String get settingsTitle => 'Settings'; + + @override + String get settingsDarkModeCTA => 'Dark Mode'; + + @override + String get settingsThemeLight => 'Light'; + + @override + String get settingsThemeDark => 'Dark'; + + /// Search Page + + @override + String get searchTitle => 'Search'; + + @override + String get searchSearchBarHint => 'Search resume...'; + + /// -------------------------------------------------------------------------- + /// Profile Widget + /// -------------------------------------------------------------------------- + + @override + String get profileTitle => 'Profile'; + + @override + String get profileWidgetDetails => 'Profile details'; + + @override + String get profileListOptions => 'Profile options'; + + @override + String get profileListSorting => 'Sorting Profiles'; + + @override + String get profileListItemPerPage => 'Profile per page'; + + @override + String get profileListLoadMore => 'Load more profiles'; + + /// -------------------------------------------------------------------------- + /// Part Widget + /// -------------------------------------------------------------------------- + + @override + String get partWidgetDetails => 'Part détails'; + + @override + String get partListOptions => 'Part list options'; + + @override + String get partListSorting => 'Sorting parts'; + + @override + String get partListItemPerPage => 'Parts per page'; + + @override + String get partListLoadMore => 'Load more parts'; + + /// -------------------------------------------------------------------------- + /// Group Widget + /// -------------------------------------------------------------------------- + + @override + String get groupWidgetDetails => 'Group détails'; + + @override + String get groupListOptions => 'Group list options'; + + @override + String get groupListSorting => 'Sorting groups'; + + @override + String get groupListItemPerPage => 'Groups per page'; + + @override + String get groupListLoadMore => 'Load more groups'; + + /// -------------------------------------------------------------------------- + /// Entry Widget + /// -------------------------------------------------------------------------- + + @override + String get entryWidgetDetails => 'Entry détails'; + + @override + String get entryListOptions => 'Entry list options'; + + @override + String get entryListSorting => 'Sorting entries'; + + @override + String get entryListItemPerPage => 'Entries per page'; + + @override + String get entryListLoadMore => 'Load more entries'; + + /// Sort Dialog + @override + String get sortDialogCancel => 'Cancel'; + + @override + String get sortDialogConfirm => 'Confirm'; + + /// -------------------------------------------------------------------------- + /// Forms + /// -------------------------------------------------------------------------- + + @override + String get formUsernameLabel => 'Username'; + + @override + String get formEmailLabel => 'Email'; + + @override + String get formEmailHint => 'someone@email.com'; + + @override + String get formNotEmailExplain => 'Please enter a real e-mail.'; + + @override + String get formNoEmailExplain => 'Please provide an email'; + + @override + String get formPasswordLabel => 'Password'; + + @override + String get formNoPasswordExplain => 'Please provide a password'; + + @override + String get formPassword2Label => 'Repeat password'; + + @override + String get formPasswordWrongPolicy => + 'The password do not fit to our password policy.'; + + /// -------------------------------------------------------------------------- + /// App Exceptions + /// -------------------------------------------------------------------------- + + @override + String get appErrorUserNotFound => 'User not found'; + + @override + String get appErrorAuthForbidden => 'Access forbidden'; + + @override + String get appErrorAuthNoToken => 'No token emitted'; + + @override + String get appErrorAuthUnauthorized => 'Not authorized'; + + @override + String get appErrorAuthAccountDisabled => 'Account disabled'; + + @override + String get appErrorServerSideProblem => 'A server side error occured'; + + /// -------------------------------------------------------------------------- + /// App Errors + /// -------------------------------------------------------------------------- + + @override + String get errorNotYetImplemented => 'Not yet implemented'; + + @override + String get errorNotSupported => 'Not supported'; + + /// -------------------------------------------------------------------------- + /// Others Exceptions + /// -------------------------------------------------------------------------- + + @override + String get exceptionFormatException => 'Exception : Wrong Format'; + + @override + String get exceptionTimeoutException => 'Exception : Request Timeout'; + + /// -------------------------------------------------------------------------- + /// HTTP Client Error (4XX) + /// -------------------------------------------------------------------------- + + @override + String get http400ClientErrorBadRequest => 'Bad request'; + + @override + String get http401ClientErrorUnauthorized => 'Unauthorized'; + + @override + String get http402ClientErrorPaymentRequired => 'Payment required'; + + @override + String get http403ClientErrorForbidden => 'Forbidden'; + + @override + String get http404ClientErrorNotFound => 'Not found'; + + @override + String get http405ClientErrorMethodNotAllowed => 'Not allowed'; + + @override + String get http406ClientErrorNotAcceptable => 'Not acceptable'; + + @override + String get http408ClientErrorRequestTimeout => 'Request timeout'; + + @override + String get http409ClientErrorConflict => 'Conflict'; + + @override + String get http410ClientErrorGone => 'Gone'; + + @override + String get http411ClientErrorLengthRequired => 'Length required'; + + @override + String get http413ClientErrorPayloadTooLarge => 'Payload too large'; + + @override + String get http414ClientErrorURITooLong => 'URI too long'; + + @override + String get http415ClientErrorUnsupportedMediaType => 'Unsupported media type'; + + @override + String get http417ClientErrorExpectationFailed => 'Expectation Failed'; + + @override + String get http426ClientErrorUpgradeRequired => 'Upgrade required'; + + /// -------------------------------------------------------------------------- + /// HTTP Server Error (5XX) + /// -------------------------------------------------------------------------- + + @override + String get http500ServerErrorInternalServerError => 'Internal Server Error'; + + @override + String get http501ServerErrorNotImplemented => 'Not implemented'; + + @override + String get http502ServerErrorBadGateway => 'Bad Gateway'; + + @override + String get http503ServerErrorServiceUnavailable => 'Service Unavailable'; + + @override + String get http504ServerErrorGatewayTimeout => 'Gateway Timeout'; + + @override + String get http505ServerErrorHttpVersionNotSupported => + 'HTTP Version Not Supported'; + + /// -------------------------------------------------------------------------- + /// Misc + /// -------------------------------------------------------------------------- + + /// Creates an object that provides US English resource values for the + /// application. + /// + /// The [locale] parameter is ignored. + /// + /// This method is typically used to create a [LocalizationsDelegate]. + /// The [MaterialApp] does so by default. + static FutureOr load(Locale locale) { + return SynchronousFuture(const CVLocalizationsEN()); + } + + /// A [LocalizationsDelegate] that uses [CVLocalizationsEN.load] + /// to create an instance of this class. + /// + /// [MaterialApp] automatically adds this value to [MaterialApp.localizationsDelegates]. + static const LocalizationsDelegate delegate = + CVLocalizationsDelegate(); +} diff --git a/lib/src/presentation/localizations/cv_localization_fr.dart b/lib/src/presentation/localizations/cv_localization_fr.dart new file mode 100644 index 0000000..0bceaf2 --- /dev/null +++ b/lib/src/presentation/localizations/cv_localization_fr.dart @@ -0,0 +1,482 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization.dart'; + +class CVLocalizationsFR implements CVLocalizations { + const CVLocalizationsFR(); + + /// -------------------------------------------------------------------------- + /// Common + /// -------------------------------------------------------------------------- + + @override + String get token => 'Jeton'; + + @override + String get cancelCTA => 'Annuler'; + + @override + String get account => 'Compte'; + + @override + String get home => 'Accueil'; + + @override + String get resume => 'CV'; + + @override + String get profile => 'Profil'; + + @override + String get search => 'Rechercher'; + + @override + String get history => 'Historique'; + + @override + String get loadMore => 'Charger plus'; + + @override + String get retryCTA => 'Re-éssayer'; + + @override + String get yesCTA => 'Oui'; + + @override + String get noCTA => 'Non'; + + @override + String get authLoginSucceed => 'Connecté'; + + @override + String get moreCTA => 'Plus'; + + @override + String get errorOccurred => 'Une erreur s\'est produite.'; + + /// -------------------------------------------------------------------------- + /// App + /// -------------------------------------------------------------------------- + + @override + String get appName => 'Social CV'; + + /// -------------------------------------------------------------------------- + /// Menu Widget + /// -------------------------------------------------------------------------- + + @override + String get menuPPCTA => 'Politique de confidentialité'; + + @override + String get menuToSCTA => 'Termes de Service'; + + /// -------------------------------------------------------------------------- + /// Home Page + /// -------------------------------------------------------------------------- + + @override + String get homeTitle => 'Social CV'; + + @override + String get homeCTA => 'Accueil'; + + @override + String get homeWelcome => 'Bienvenue sur notre nouveau réseau social de CV !'; + + /// -------------------------------------------------------------------------- + /// Authentication Page - Login - SignUp + /// -------------------------------------------------------------------------- + + @override + String get authTitle => 'Connexion'; + + @override + String get authBubbleLoginCTA => 'Login'; + + @override + String get authBubbleRegisterCTA => 'Register'; + + @override + String get authNoEmailTitle => 'E-mail vide'; + + @override + String get authNotEmailExplain => 'Merci de renseigner un e-mail existant.'; + + @override + String get authNoEmailExplain => 'Merci de renseigner un e-mail'; + + @override + String get authNoPasswordTitle => 'Mot de passe vide'; + + @override + String get authNoPasswordExplain => 'Merci de renseigner un mot de passe'; + + @override + String get authCreateYourAccount => 'Créez votre compte'; + + @override + String get authRegisterTitle => 'Inscription'; + + @override + String get authRegisterCTA => 'S\'inscrire'; + + @override + String get authRegisterFailed => 'Inscription échoué !'; + + @override + String get authRegisterSucceed => 'Inscription réussite !'; + + @override + String get authLoginTitle => 'Connectez vous avec votre compte'; + + @override + String get authLoginCTA => 'Se connecter'; + + @override + String get authLoginGoogleCTA => 'Se connecter avec Google'; + + @override + String get authLoginFacebookCTA => 'Se connecter avec Facebook'; + + @override + String get authLoginFailed => 'Connexion échoué !'; + + @override + String get authLogout => 'Se déconnecter'; + + @override + String get authLogoutCTA => 'Se déconnecter'; + + @override + String get authAccountAlreadyExistsFailure => + 'Un compte avec le même nom d\'utilisateur ou e-mail existe déjà.'; + + @override + String get authLogoutSucceed => 'Déconnexion réussite !'; + + @override + String get authPrivacyExplain => + 'Vous aimez votre vie privée ? Nous le savons. Nous n\'utilisons, ni ' + 'vendons vos données.'; + + @override + String get authPrivacyReadCTA => + 'Touchez ici pour lire notre politique de confidentialité.'; + + @override + String get authAlreadyHaveAccountCTA => + 'Vous avez déjà un compte ? Connectez-vous'; + + @override + String get authForgotPasswordCTA => 'Mot de passe oublié ?'; + + @override + String get authNoAccountCTA => 'Vous n\'avez pas de compte ? Inscrivez-vous'; + + @override + String get authNotPasswordExplain => 'Ceci n\'est pas un mot de passe'; + + @override + String get authOr => 'OU'; + + /// -------------------------------------------------------------------------- + /// Account Page + /// -------------------------------------------------------------------------- + + @override + String get accountTitle => 'Compte'; + + @override + String get accountCTA => 'Compte'; + + @override + String get accountMyProfile => 'Mes profils'; + + /// Settings Pages + + @override + String get settingsTitle => 'Paramètres'; + + @override + String get settingsCTA => 'Paramètres'; + + @override + String get settingsDarkModeCTA => 'Mode Sombre'; + + @override + String get settingsThemeLight => 'Claire'; + + @override + String get settingsThemeDark => 'Sombre'; + + /// Search Page + + @override + String get searchTitle => 'Recherche'; + + @override + String get searchSearchBarHint => 'Rechercher un profil ...'; + + /// -------------------------------------------------------------------------- + /// Profile Widget + /// -------------------------------------------------------------------------- + + @override + String get profileTitle => 'Profil'; + + @override + String get profileWidgetDetails => 'Détails du profile'; + + @override + String get profileListOptions => 'Options profiles'; + + @override + String get profileListSorting => 'Trier Profiles'; + + @override + String get profileListItemPerPage => 'Profils par page'; + + @override + String get profileListLoadMore => 'Charger plus de profiles'; + + /// -------------------------------------------------------------------------- + /// Part Widget + /// -------------------------------------------------------------------------- + + @override + String get partWidgetDetails => 'Détails de la partie'; + + @override + String get partListOptions => 'Options'; + + @override + String get partListSorting => 'Trier les parties'; + + @override + String get partListItemPerPage => 'Parties par page'; + + @override + String get partListLoadMore => 'charger plus de parties'; + + /// -------------------------------------------------------------------------- + /// Group Widget + /// -------------------------------------------------------------------------- + + @override + String get groupWidgetDetails => 'Détails du groupe'; + + @override + String get groupListOptions => 'Options'; + + @override + String get groupListSorting => 'Trier les groupes'; + + @override + String get groupListItemPerPage => 'Groupes par page'; + + @override + String get groupListLoadMore => 'charger plus de groupes'; + + /// -------------------------------------------------------------------------- + /// Entry Widget + /// -------------------------------------------------------------------------- + + @override + String get entryWidgetDetails => 'Détails de l\'entrée'; + + @override + String get entryListOptions => 'Options'; + + @override + String get entryListSorting => 'Trier les entrées'; + + @override + String get entryListItemPerPage => 'Entrées par page'; + + @override + String get entryListLoadMore => 'Charger plus de entrées'; + + /// Sort Dialog + + @override + String get sortDialogCancel => 'Annuler'; + + @override + String get sortDialogConfirm => 'Valider'; + + /// -------------------------------------------------------------------------- + /// Forms + /// -------------------------------------------------------------------------- + + @override + String get formUsernameLabel => 'Nom d\'utilisateur'; + + @override + String get formEmailLabel => 'Email'; + + @override + String get formEmailHint => 'someone@email.com'; + + @override + String get formNotEmailExplain => 'Please enter a real e-mail.'; + + @override + String get formNoEmailExplain => 'Please provide an email'; + + @override + String get formPasswordLabel => 'Password'; + + @override + String get formNoPasswordExplain => 'Please provide a password'; + + @override + String get formPassword2Label => 'Repeat password'; + + @override + String get formPasswordWrongPolicy => + 'The password do not fit to our password policy.'; + + /// -------------------------------------------------------------------------- + /// App Exceptions + /// -------------------------------------------------------------------------- + + @override + String get appErrorUserNotFound => 'User not found'; + + @override + String get appErrorAuthForbidden => 'Access forbidden'; + + @override + String get appErrorAuthNoToken => 'No token emitted'; + + @override + String get appErrorAuthUnauthorized => 'Not authorized'; + + @override + String get appErrorAuthAccountDisabled => 'Account disabled'; + + @override + String get appErrorServerSideProblem => 'A server side error occured'; + + /// -------------------------------------------------------------------------- + /// App Errors + /// -------------------------------------------------------------------------- + + @override + String get errorNotYetImplemented => 'Pas encore implémenté'; + + @override + String get errorNotSupported => 'Non supporté'; + + /// -------------------------------------------------------------------------- + /// Others Exceptions + /// -------------------------------------------------------------------------- + + @override + String get exceptionFormatException => 'Exception : Mauvais Format'; + + @override + String get exceptionTimeoutException => 'Exception : Requete Expiré'; + + /// -------------------------------------------------------------------------- + /// HTTP Client Error (4XX) + /// -------------------------------------------------------------------------- + + @override + String get http400ClientErrorBadRequest => 'Mauvaise requete'; + + @override + String get http401ClientErrorUnauthorized => 'Non authorisé'; + + @override + String get http402ClientErrorPaymentRequired => 'Paiement requis'; + + @override + String get http403ClientErrorForbidden => 'Interdit'; + + @override + String get http404ClientErrorNotFound => 'Introuvable'; + + @override + String get http405ClientErrorMethodNotAllowed => 'Non autorisé'; + + @override + String get http406ClientErrorNotAcceptable => 'Non acceptable'; + + @override + String get http408ClientErrorRequestTimeout => 'Requete expirée'; + + @override + String get http409ClientErrorConflict => 'Conflit'; + + @override + String get http410ClientErrorGone => 'Disparu'; + + @override + String get http411ClientErrorLengthRequired => 'Taille requise'; + + @override + String get http413ClientErrorPayloadTooLarge => 'Payload trop large'; + + @override + String get http414ClientErrorURITooLong => 'Lien trop long'; + + @override + String get http415ClientErrorUnsupportedMediaType => + 'Type de média non supporté'; + + @override + String get http417ClientErrorExpectationFailed => 'Échoué'; + + @override + String get http426ClientErrorUpgradeRequired => 'Mise à jour requise'; + + /// -------------------------------------------------------------------------- + /// HTTP Server Error (5XX) + /// -------------------------------------------------------------------------- + + @override + String get http500ServerErrorInternalServerError => 'Erreur Serveur interne'; + + @override + String get http501ServerErrorNotImplemented => 'Non implementé'; + + @override + String get http502ServerErrorBadGateway => 'Mauvaise passerelle'; + + @override + String get http503ServerErrorServiceUnavailable => 'Service non disponible '; + + @override + String get http504ServerErrorGatewayTimeout => 'Temps ecoulé'; + + @override + String get http505ServerErrorHttpVersionNotSupported => + 'Version HTTP non supporté'; + + /// -------------------------------------------------------------------------- + /// Misc + /// -------------------------------------------------------------------------- + + /// Creates an object that provides US English resource values for the + /// application. + /// + /// The [locale] parameter is ignored. + /// + /// This method is typically used to create a [LocalizationsDelegate]. + /// The [MaterialApp] does so by default. + static FutureOr load(Locale locale) { + return SynchronousFuture(const CVLocalizationsFR()); + } + + /// A [LocalizationsDelegate] that uses [CVLocalizationsFR.load] + /// to create an instance of this class. + /// + /// [MaterialApp] automatically adds this value to [MaterialApp.localizationsDelegates]. + static const LocalizationsDelegate delegate = + CVLocalizationsDelegate(); +} diff --git a/lib/src/ui/models/view_models.dart b/lib/src/presentation/models/view_models.dart similarity index 100% rename from lib/src/ui/models/view_models.dart rename to lib/src/presentation/models/view_models.dart diff --git a/lib/src/ui/pages/account_page.dart b/lib/src/presentation/pages/account_page.dart similarity index 59% rename from lib/src/ui/pages/account_page.dart rename to lib/src/presentation/pages/account_page.dart index adbcc85..f66629e 100644 --- a/lib/src/ui/pages/account_page.dart +++ b/lib/src/presentation/pages/account_page.dart @@ -1,32 +1,33 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/presentation/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/presentation/utils/navigation.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/error_widget.dart'; class AccountPage extends StatelessWidget { final String _tag = '$AccountPage'; @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); return SafeArea( left: false, right: false, - child: BlocBuilder( + child: BlocBuilder( bloc: BlocProvider.of(context), builder: (BuildContext context, AuthenticationState state) { - if (state is AuthenticationUninitialized) return Container(); - if (state is AuthenticationUnauthenticated) + if (state is AuthenticationUninitialized) + return Container(); + else if (state is AuthenticationUnauthenticated) return _AccountPageDetailsNotConnected(); - if (state is AuthenticationAuthenticated) + else if (state is AuthenticationAuthenticated) return _AccountPageDetailsConnected(); - if (state is AuthenticationLoading) + else if (state is AuthenticationLoading) return Center(child: CircularProgressIndicator()); }, ), @@ -44,26 +45,25 @@ class _AccountPageDetailsNotConnected extends StatelessWidget { Widget build(BuildContext context) { return Center( child: RaisedButton( - child: Text(CVLocalizations.of(context).authSignInCTA), + child: Text(CVLocalizations.of(context).authLoginCTA), onPressed: () => navigateToLogin(context), ), ); } } -//////////////////////////////////////////////////////////////////////////////// -// // -// _AccountPageDetailsConnected // -// // -//////////////////////////////////////////////////////////////////////////////// +/// ---------------------------------------------------------------------------- +/// _AccountPageDetailsConnected +/// ---------------------------------------------------------------------------- + class _AccountPageDetailsConnected extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( - bloc: BlocProvider.of(context), - builder: (BuildContext context, AccountState state) { - if (state is AccountUninitialized) { - } else if (state is AccountLoaded) { + return BlocBuilder( + bloc: BlocProvider.of(context), + builder: (BuildContext context, IdentityState state) { + if (state is IdentityUninitialized) { + } else if (state is IdentityLoaded) { return ListView( children: [ ExpansionTile( @@ -71,13 +71,13 @@ class _AccountPageDetailsConnected extends StatelessWidget { title: Text(CVLocalizations.of(context).accountMyProfile), children: [ // BlocProvider( -// bloc: ElementListBloc( +// bloc: ElementListBloc( // cvRepository: _repositories.cvRepository, // preferencesRepository: // _repositories.preferencesRepository, // ), // child: ProfileListWidget( -// fromUserViewModel: snapshot.data, +// fromUserEntity: snapshot.data, // showOptions: false, // shrinkWrap: true, // physics: ClampingScrollPhysics(), @@ -87,7 +87,7 @@ class _AccountPageDetailsConnected extends StatelessWidget { ), ], ); - } else if (state is AccountFailed) { + } else if (state is IdentityFailed) { return ErrorCard(error: state.error); } return ErrorCard(error: NotImplementedYetError()); diff --git a/lib/src/presentation/pages/auth_page.dart b/lib/src/presentation/pages/auth_page.dart new file mode 100644 index 0000000..7fc7c24 --- /dev/null +++ b/lib/src/presentation/pages/auth_page.dart @@ -0,0 +1,215 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +class AuthPage extends StatefulWidget { + @override + _AuthPageState createState() => _AuthPageState(); +} + +class _AuthPageState extends State { + final String _tag = '$_AuthPageState'; + + // Variable + double screenWidth; + double screenHeight; + + PageController _pageController; + + Color left = Colors.black; + Color right = Colors.white; + + // Business + @override + void initState() { + print('$_tag:initState()'); + super.initState(); + _pageController = PageController(); + } + + @override + void dispose() { + print('$_tag:dispose()'); + _pageController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + print('$_tag:build'); + screenHeight = MediaQuery.of(context).size.height; + screenWidth = MediaQuery.of(context).size.width; + + screenHeight = (screenHeight > AppStyles.authPageMinHeight) + ? screenHeight + : AppStyles.authPageMinHeight; + + return Scaffold( + backgroundColor: AppStyles.primaryColor, + body: MultiBlocListener( + listeners: [ + BlocListener( + bloc: BlocProvider.of(context), + listener: (BuildContext context, AuthenticationState state) { + if (state is AuthenticationAuthenticated) { + Scaffold.of(context).showSnackBar(SnackBar( + backgroundColor: AppStyles.successColor, + content: Text(CVLocalizations.of(context).authLoginSucceed), + )); + Future.delayed(const Duration(seconds: 1)) + .then((_) => Navigator.of(context).pop()); + } + }, + ), + ], + child: SingleChildScrollView( + child: Container( + height: screenHeight, + child: Stack( + children: [ + _buildHeaderSection(context), + _buildAuthSection(context) + ], + ), + ), + ), + ), + ); + } + + Widget _buildHeaderSection(BuildContext context) { + return Container( + height: screenHeight * 0.25, + width: screenWidth, + ); + } + + Widget _buildAuthSection(BuildContext context) { + return Stack( + children: [ + _buildAuthPageView(context), + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + height: screenHeight * 0.25, + ), + Container( + width: 300.0, + height: 50.0, + decoration: BoxDecoration( + color: const Color(0x552B2B2B), + borderRadius: const BorderRadius.all(Radius.circular(25.0)), + ), + child: CustomPaint( + painter: TabIndicationPainter(pageController: _pageController), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: FlatButton( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onPressed: _onSignInButtonPress, + child: Text( + CVLocalizations.of(context).authBubbleLoginCTA, + style: TextStyle( + color: left, + fontSize: 16.0, + ), + ), + ), + ), + //Container(height: 33.0, width: 1.0, color: Colors.white), + Expanded( + child: FlatButton( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onPressed: _onSignUpButtonPress, + child: Text( + CVLocalizations.of(context).authBubbleRegisterCTA, + style: TextStyle( + color: right, + fontSize: 16.0, + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ], + ); + } + + Widget _buildAuthPageView(BuildContext context) { + return PageView( + controller: _pageController, + onPageChanged: (i) { + if (i == 0) { + setState(() { + right = Colors.white; + left = Colors.black; + }); + } else if (i == 1) { + setState(() { + right = Colors.black; + left = Colors.white; + }); + } + }, + children: [ + Column( + children: [ + Container( + height: screenHeight * 0.25, + ), + Container( + height: screenHeight * 0.05, + ), + Container( + height: screenHeight * 0.70, + padding: const EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [LoginForm()], + ), + ), + ], + ), + Column( + children: [ + Container( + height: screenHeight * 0.25, + ), + Container( + height: screenHeight * 0.05, + ), + Container( + height: screenHeight * 0.70, + padding: const EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [RegisterForm()], + ), + ), + ], + ), + ], + ); + } + + void _onSignInButtonPress() { + _pageController.animateToPage(0, + duration: const Duration(milliseconds: 500), curve: Curves.decelerate); + } + + void _onSignUpButtonPress() { + _pageController?.animateToPage(1, + duration: const Duration(milliseconds: 500), curve: Curves.decelerate); + } +} diff --git a/lib/src/ui/pages/elements/entry_profile_page.dart b/lib/src/presentation/pages/elements/entry_profile_page.dart similarity index 60% rename from lib/src/ui/pages/elements/entry_profile_page.dart rename to lib/src/presentation/pages/elements/entry_profile_page.dart index 941281b..523ed6f 100644 --- a/lib/src/ui/pages/elements/entry_profile_page.dart +++ b/lib/src/presentation/pages/elements/entry_profile_page.dart @@ -1,15 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class EntryPage extends EntryWidget { - EntryPage( - {Key key, String entryId, EntryViewModel entry, EntryBloc entryBloc}) + EntryPage({Key key, String entryId, EntryEntity entry, EntryBloc entryBloc}) : super(key: key, entryId: entryId, entry: entry, entryBloc: entryBloc); @override @@ -19,42 +15,42 @@ class EntryPage extends EntryWidget { class _EntryPageState extends EntryWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: entryBloc, builder: (BuildContext context, EntryState state) { if (state is EntryLoading) { return Scaffold( appBar: AppBar( - title: LoadingShadowContent( + title: const LoadingShadowContent( numberOfTitleLines: 1, numberOfContentLines: 0, ), ), body: SingleChildScrollView( - child: LoadingShadowContent( + child: const LoadingShadowContent( numberOfContentLines: 2, - padding: EdgeInsets.all(10.0), + padding: const EdgeInsets.all(10.0), ), ), ); } else if (state is EntryLoaded) { - EntryViewModel model = state.element; + final EntryEntity model = state.element; return Scaffold( appBar: AppBar(title: Text(model.name)), body: ListView( children: [ ListTile( - title: Text('name'), + title: const Text('name'), subtitle: Text(model.name), ), ListTile( - title: Text('type'), + title: const Text('type'), subtitle: Text(model.type), ), ListTile( - title: Text('content'), - subtitle: Text(model.content), + title: const Text('content'), + subtitle: Text(model.content.toString()), ), ], ), diff --git a/lib/src/ui/pages/elements/group_profile_page.dart b/lib/src/presentation/pages/elements/group_profile_page.dart similarity index 65% rename from lib/src/ui/pages/elements/group_profile_page.dart rename to lib/src/presentation/pages/elements/group_profile_page.dart index 487f6b3..2af41e3 100644 --- a/lib/src/ui/pages/elements/group_profile_page.dart +++ b/lib/src/presentation/pages/elements/group_profile_page.dart @@ -1,16 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/elements/entry_profile_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/elements/group_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/loading_widget.dart'; class GroupPage extends GroupWidget { - GroupPage( - {Key key, String groupId, GroupViewModel group, GroupBloc groupBloc}) + GroupPage({Key key, String groupId, GroupEntity group, GroupBloc groupBloc}) : super(key: key, groupId: groupId, group: group, groupBloc: groupBloc); @override @@ -20,20 +18,20 @@ class GroupPage extends GroupWidget { class _GroupPageState extends GroupWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: groupBloc, builder: (BuildContext context, GroupState state) { if (state is GroupLoading) { return Scaffold( appBar: AppBar( - title: LoadingShadowContent( + title: const LoadingShadowContent( numberOfTitleLines: 1, numberOfContentLines: 0, ), ), body: SingleChildScrollView( child: SingleChildScrollView( - child: LoadingShadowContent( + child: const LoadingShadowContent( numberOfContentLines: 2, padding: EdgeInsets.all(10.0), ), diff --git a/lib/src/ui/pages/elements/part_profile_page.dart b/lib/src/presentation/pages/elements/part_profile_page.dart similarity index 60% rename from lib/src/ui/pages/elements/part_profile_page.dart rename to lib/src/presentation/pages/elements/part_profile_page.dart index f071ba3..a1b1c82 100644 --- a/lib/src/ui/pages/elements/part_profile_page.dart +++ b/lib/src/presentation/pages/elements/part_profile_page.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/elements/part_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/loading_widget.dart'; class PartProfilePage extends PartWidget { - PartProfilePage( - {Key key, String partId, PartViewModel part, PartBloc partBloc}) + const PartProfilePage( + {Key key, String partId, PartEntity part, PartBloc partBloc}) : super(key: key, partId: partId, part: part, partBloc: partBloc); @override @@ -17,19 +17,19 @@ class PartProfilePage extends PartWidget { class _PartProfilePageState extends PartWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: partBloc, builder: (BuildContext context, PartState state) { if (state is PartLoading) { return Scaffold( appBar: AppBar( - title: LoadingShadowContent( + title: const LoadingShadowContent( numberOfTitleLines: 1, ), ), body: SingleChildScrollView( child: SingleChildScrollView( - child: LoadingShadowContent( + child: const LoadingShadowContent( numberOfContentLines: 2, padding: EdgeInsets.all(10.0), ), @@ -37,15 +37,18 @@ class _PartProfilePageState extends PartWidgetState { ), ); } else if (state is PartLoaded) { - PartViewModel model = state.element; + final PartEntity model = state.element; return Scaffold( appBar: AppBar(title: Text(model.name)), body: ListView.builder( - itemBuilder: (BuildContext context, int index) {}, + itemBuilder: (BuildContext context, int index) { + return Container(); + }, ), ); } + return Container(); }, ); } diff --git a/lib/src/ui/pages/elements/profile_profile_page.dart b/lib/src/presentation/pages/elements/profile_profile_page.dart similarity index 82% rename from lib/src/ui/pages/elements/profile_profile_page.dart rename to lib/src/presentation/pages/elements/profile_profile_page.dart index 80e4e4d..01cc2ba 100644 --- a/lib/src/ui/pages/elements/profile_profile_page.dart +++ b/lib/src/presentation/pages/elements/profile_profile_page.dart @@ -1,22 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; - -/// TODO : Build owner interaction with ProfileViewModel.owner +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// TODO : Build owner interaction with ProfileEntity.owner class ProfileProfilePage extends ProfileWidget { ProfileProfilePage( {Key key, String profileId, - ProfileViewModel profile, + ProfileEntity profile, ProfileBloc profileBloc}) : super( key: key, @@ -33,14 +27,14 @@ class _ProfileProfilePageState extends ProfileWidgetState { Widget build(BuildContext context) { Logger.log('Building ProfilePage'); - return BlocBuilder( + return BlocBuilder( bloc: profileBloc, builder: (BuildContext context, ProfileState state) { List slivers = []; if (state is ProfileLoaded) { slivers.add(_ProfilePageAppBar(profile: state.element)); - ProfileViewModel profile = state.element; + ProfileEntity profile = state.element; slivers.addAll(profile.partIds .map((partId) => SliverToBoxAdapter(child: PartProfileWidget(partId: partId))) @@ -63,7 +57,7 @@ class _ProfileProfilePageState extends ProfileWidgetState { } class _ProfilePageAppBar extends StatelessWidget { - final ProfileViewModel profile; + final ProfileEntity profile; _ProfilePageAppBar({@required this.profile}); diff --git a/lib/src/ui/pages/home_page.dart b/lib/src/presentation/pages/home_page.dart similarity index 76% rename from lib/src/ui/pages/home_page.dart rename to lib/src/presentation/pages/home_page.dart index 3e11565..6036475 100644 --- a/lib/src/ui/pages/home_page.dart +++ b/lib/src/presentation/pages/home_page.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class HomePage extends StatelessWidget { final String _tag = '$HomePage'; @@ -12,7 +10,7 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); return SafeArea( left: false, diff --git a/lib/src/ui/pages/main_page.dart b/lib/src/presentation/pages/main_page.dart similarity index 80% rename from lib/src/ui/pages/main_page.dart rename to lib/src/presentation/pages/main_page.dart index d6adaf6..d428d2c 100644 --- a/lib/src/ui/pages/main_page.dart +++ b/lib/src/presentation/pages/main_page.dart @@ -1,12 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/tags.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/account_page.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/home_page.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/menu_button_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class MainPage extends StatefulWidget { const MainPage({Key key}) : super(key: key); @@ -26,7 +19,7 @@ class _MainPageState extends State { @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); return Scaffold( appBar: AppBar( diff --git a/lib/src/ui/pages/search_page.dart b/lib/src/presentation/pages/search_page.dart similarity index 78% rename from lib/src/ui/pages/search_page.dart rename to lib/src/presentation/pages/search_page.dart index cb288a5..3d5a218 100644 --- a/lib/src/ui/pages/search_page.dart +++ b/lib/src/presentation/pages/search_page.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/tags.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; /// Add controller for the input class SearchPage extends StatelessWidget { @@ -11,7 +9,7 @@ class SearchPage extends StatelessWidget { @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); return Scaffold( body: CustomScrollView( @@ -24,7 +22,7 @@ class SearchPage extends StatelessWidget { tag: AppHeroes.searchFab, child: Card( child: Container( - padding: EdgeInsets.all(10.0), + padding: const EdgeInsets.all(10.0), child: TextField( onSubmitted: null, autofocus: true, @@ -41,8 +39,8 @@ class SearchPage extends StatelessWidget { /// TODO: Add search result // SliverToBoxAdapter( -// child: BlocProvider>( -// bloc: ElementBloc( +// child: BlocProvider>( +// bloc: ElementBloc( // cvRepository: _repositories.cvRepository, // ), // child: ProfileListWidget( diff --git a/lib/src/ui/pages/settings_page.dart b/lib/src/presentation/pages/settings_page.dart similarity index 66% rename from lib/src/ui/pages/settings_page.dart rename to lib/src/presentation/pages/settings_page.dart index 1509cbf..7550a15 100644 --- a/lib/src/ui/pages/settings_page.dart +++ b/lib/src/presentation/pages/settings_page.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class SettingsPage extends StatelessWidget { const SettingsPage({ @@ -20,7 +18,7 @@ class SettingsPage extends StatelessWidget { right: false, child: ListView( children: [ - ThemeSwitchListTile(), + const ThemeSwitchTile(), AboutListTile(icon: Icon(Icons.info)), ], ), diff --git a/lib/src/router.dart b/lib/src/presentation/router.dart similarity index 71% rename from lib/src/router.dart rename to lib/src/presentation/router.dart index 57569a4..9d6c5e9 100644 --- a/lib/src/router.dart +++ b/lib/src/presentation/router.dart @@ -1,15 +1,6 @@ import 'package:fluro/fluro.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/paths.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/auth_page.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/elements/entry_profile_page.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/elements/group_profile_page.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/elements/part_profile_page.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/elements/profile_profile_page.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/main_page.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/search_page.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/settings_page.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class AppRouter { final String _tag = '$AppRouter'; @@ -20,14 +11,14 @@ class AppRouter { } void _defineRoutes() { - Logger.log('$_tag:$_defineRoutes'); + Logger.log('$_tag:_defineRoutes'); router.define( AppPaths.kPathHome, handler: Handler( handlerFunc: (BuildContext context, Map params) { Logger.log('Navigate to ${AppPaths.kPathHome}'); - return MainPage(); + return const MainPage(); }, ), ); @@ -37,7 +28,7 @@ class AppRouter { handler: Handler( handlerFunc: (BuildContext context, Map params) { Logger.log('Navigate to ${AppPaths.kPathAccount}'); - return MainPage(); + return const MainPage(); }, ), ); @@ -76,7 +67,8 @@ class AppRouter { '${AppPaths.kPathProfiles}/:${AppPaths.kParamProfileId}', handler: Handler( handlerFunc: (BuildContext context, Map params) { - var profileId = params[AppPaths.kParamProfileId][0]; + final String profileId = + params[AppPaths.kParamProfileId][0] as String; Logger.log('Navigate to ${AppPaths.kPathProfiles}/$profileId'); @@ -89,7 +81,7 @@ class AppRouter { '${AppPaths.kPathParts}/:${AppPaths.kParamPartId}', handler: Handler( handlerFunc: (BuildContext context, Map params) { - var partId = params[AppPaths.kParamPartId][0]; + final String partId = params[AppPaths.kParamPartId][0] as String; Logger.log('Navigate to ${AppPaths.kPathParts}/$partId'); @@ -102,7 +94,7 @@ class AppRouter { '${AppPaths.kPathGroups}/:${AppPaths.kParamGroupId}', handler: Handler( handlerFunc: (BuildContext context, Map params) { - var groupId = params[AppPaths.kParamGroupId][0]; + final String groupId = params[AppPaths.kParamGroupId][0] as String; Logger.log('Navigate to ${AppPaths.kPathGroups}/$groupId'); @@ -115,7 +107,7 @@ class AppRouter { '${AppPaths.kPathEntries}/:${AppPaths.kParamEntryId}', handler: Handler( handlerFunc: (BuildContext context, Map params) { - var entryId = params[AppPaths.kParamEntryId][0]; + final String entryId = params[AppPaths.kParamEntryId][0] as String; Logger.log('Navigate to ${AppPaths.kPathEntries}/$entryId'); diff --git a/lib/src/utils/logger.dart b/lib/src/presentation/utils/logger.dart similarity index 90% rename from lib/src/utils/logger.dart rename to lib/src/presentation/utils/logger.dart index 45e24d8..29a7ec7 100644 --- a/lib/src/utils/logger.dart +++ b/lib/src/presentation/utils/logger.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:meta/meta.dart'; //////////////////////////////////////////////////////////////////////////////// @@ -60,11 +62,9 @@ class LogEntry { } } -//////////////////////////////////////////////////////////////////////////////// -// // -// Logging service // -// // -//////////////////////////////////////////////////////////////////////////////// +/// ---------------------------------------------------------------------------- +/// Logger +/// ---------------------------------------------------------------------------- /// A class that provide logging services class Logger { @@ -74,7 +74,7 @@ class Logger { final DateTime _startupTime = DateTime.now(); - List _messagesLog = List(_LOG_LENGTH); + final List _messagesLog = List(_LOG_LENGTH); int nextLogPos = 0; int nextActionPos = 0; @@ -86,29 +86,29 @@ class Logger { return DateTime.now().difference(_startupTime); } - /// ----------------------------------------------------------------------- + /// -------------------------------------------------------------------------- /// Constructor - /// ----------------------------------------------------------------------- + /// -------------------------------------------------------------------------- Logger._newInstance() : super(); factory Logger() { return _instance; } - /// ----------------------------------------------------------------------- + /// -------------------------------------------------------------------------- /// Initialization - /// ----------------------------------------------------------------------- + /// -------------------------------------------------------------------------- /// /// Init local log file /// - Future init() async { + FutureOr init() async { //_logFile = await localStorageManager.getDocumentsFile("log.txt"); } - /// ----------------------------------------------------------------------- + /// -------------------------------------------------------------------------- /// General log - /// ----------------------------------------------------------------------- + /// -------------------------------------------------------------------------- /// Print the message and add a new log entry to the log list void doLog( @@ -224,11 +224,11 @@ class Logger { static get logList => _instance._logList; - get _logList { - final List log = _messagesLog; - log.removeWhere((e) => e == null); - log.sort((a, b) => a.time > b.time ? 1 : -1); - return log; + List get _logList { + final List logs = _messagesLog; + logs.removeWhere((e) => e == null); + logs.sort((a, b) => a.time > b.time ? 1 : -1); + return logs; } static get logString => _instance._logString; diff --git a/lib/src/utils/navigation.dart b/lib/src/presentation/utils/navigation.dart similarity index 68% rename from lib/src/utils/navigation.dart rename to lib/src/presentation/utils/navigation.dart index 53f3cbf..baf326e 100644 --- a/lib/src/utils/navigation.dart +++ b/lib/src/presentation/utils/navigation.dart @@ -1,10 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/paths.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/elements/entry_profile_page.dart'; -import 'package:social_cv_client_flutter/src/ui/pages/elements/group_profile_page.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/menu_bottom_sheet_widget.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; void navigateToLogin(BuildContext context) { Navigator.of(context).pushNamed(AppPaths.kPathLogin); @@ -20,15 +17,15 @@ void navigateToSearch(BuildContext context) { void navigateToProfile(BuildContext context, String profileId) { Navigator.of(context) - .pushNamed(AppPaths.kPathProfiles + '/${profileId ?? ''}'); + .pushNamed('${AppPaths.kPathProfiles}/${profileId ?? ''}'); } void navigateToPart(BuildContext context, String partId) { - Navigator.of(context).pushNamed(AppPaths.kPathParts + '/${partId ?? ''}'); + Navigator.of(context).pushNamed('${AppPaths.kPathParts}/${partId ?? ''}'); } void navigateToGroup(BuildContext context, - {String groupId, GroupViewModel group}) { + {String groupId, GroupEntity group}) { assert(groupId != null || group != null); if (group != null) { Navigator.of(context) @@ -39,13 +36,13 @@ void navigateToGroup(BuildContext context, // ); } else if (groupId != null) { Navigator.of(context).pushNamed( - AppPaths.kPathGroups + '/${groupId ?? ''}', + '${AppPaths.kPathGroups}/${groupId ?? ''}', ); } } void navigateToEntry(BuildContext context, - {String entryId, EntryViewModel entry}) { + {String entryId, EntryEntity entry}) { assert(entryId != null || entry != null); if (entry != null) { Navigator.of(context) @@ -56,7 +53,7 @@ void navigateToEntry(BuildContext context, // ); } else if (entryId != null) { Navigator.of(context) - .pushNamed(AppPaths.kPathEntries + '/${entryId ?? ''}'); + .pushNamed('${AppPaths.kPathEntries}/${entryId ?? ''}'); } } diff --git a/lib/src/presentation/utils/translate_error.dart b/lib/src/presentation/utils/translate_error.dart new file mode 100644 index 0000000..fae7b1e --- /dev/null +++ b/lib/src/presentation/utils/translate_error.dart @@ -0,0 +1,185 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/widgets.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Translate [Error] and [Exception] +String translateError(BuildContext context, dynamic err) { + Logger.log('Translating error'); + + final CVLocalizations loc = CVLocalizations.of(context); + + if (loc == null) return err.toString(); + + String txt; + + if (err is Exception) { + // Exceptions + if (err is AppException) { + txt = _translateAppException(context, err); + } else if (err is HttpException) { + txt = _translateHttpException(context, err); + } else if (err is FormatException) { + txt = loc.exceptionFormatException; + } else if (err is TimeoutException) { + txt = loc.exceptionTimeoutException; + } + } else if (err is Error) { + // Errors + if (err is NotImplementedYetError) { + txt = loc.errorNotYetImplemented; + } else if (err is NotSupportedError) { + txt = loc.errorNotSupported; + } + } else if (err is String) { + txt = err; + } else { + txt = '${err.runtimeType}'; + } + + return txt ??= + '${loc.errorOccurred}: ${err.runtimeType}${err?.message != null ? ' "${err?.message}"' : ''}'; +} + +/// Translate [HttpException] +String _translateHttpException(BuildContext context, HttpException err) { + final CVLocalizations loc = CVLocalizations.of(context); + String txt; + switch (err.statusCode) { + case HttpStatus.badRequest: + // HTTP 400 + txt = loc.http400ClientErrorBadRequest; + break; + case HttpStatus.unauthorized: + // HTTP 401 + txt = loc.http401ClientErrorUnauthorized; + break; + case HttpStatus.paymentRequired: + // HTTP 402 + txt = loc.http402ClientErrorPaymentRequired; + break; + case HttpStatus.forbidden: + // HTTP 403 + txt = loc.http403ClientErrorForbidden; + break; + case HttpStatus.notFound: + // HTTP 404 + txt = loc.http404ClientErrorNotFound; + break; + case HttpStatus.methodNotAllowed: + // HTTP 405 + txt = loc.http405ClientErrorMethodNotAllowed; + break; + case HttpStatus.notAcceptable: + // HTTP 406 + txt = loc.http406ClientErrorNotAcceptable; + break; + case HttpStatus.requestTimeout: + // HTTP 408 + txt = loc.http408ClientErrorRequestTimeout; + break; + case HttpStatus.conflict: + // HTTP 409 + txt = loc.http409ClientErrorConflict; + break; + case HttpStatus.gone: + // HTTP 410 + txt = loc.http410ClientErrorGone; + break; + case HttpStatus.lengthRequired: + // HTTP 411 + txt = loc.http411ClientErrorLengthRequired; + break; + case HttpStatus.requestEntityTooLarge: + // HTTP 413 + txt = loc.http413ClientErrorPayloadTooLarge; + break; + case HttpStatus.requestUriTooLong: + // HTTP 414 + txt = loc.http414ClientErrorURITooLong; + break; + case HttpStatus.unsupportedMediaType: + // HTTP 415 + txt = loc.http415ClientErrorUnsupportedMediaType; + break; + case HttpStatus.expectationFailed: + // HTTP 417 + txt = loc.http417ClientErrorExpectationFailed; + break; + case HttpStatus.upgradeRequired: + // HTTP 426 + txt = loc.http426ClientErrorUpgradeRequired; + break; + case HttpStatus.internalServerError: + // HTTP 500 + txt = loc.http500ServerErrorInternalServerError; + break; + case HttpStatus.notImplemented: + // HTTP 501 + txt = loc.http501ServerErrorNotImplemented; + break; + case HttpStatus.badGateway: + // HTTP 502 + txt = loc.http502ServerErrorBadGateway; + break; + case HttpStatus.serviceUnavailable: + // HTTP 503 + txt = loc.http503ServerErrorServiceUnavailable; + break; + case HttpStatus.gatewayTimeout: + // HTTP 504 + txt = loc.http504ServerErrorGatewayTimeout; + break; + case HttpStatus.httpVersionNotSupported: + // HTTP 505 + txt = loc.http505ServerErrorHttpVersionNotSupported; + break; + } + return txt; +} + +/// Translate [AppException] +String _translateAppException(BuildContext context, AppException err) { + final CVLocalizations loc = CVLocalizations.of(context); + String txt; + + switch (err.type) { + case AppExceptionType.somethingWentWrong: + txt = loc.errorOccurred; + break; + case AppExceptionType.authNoToken: + txt = loc.appErrorAuthNoToken; + break; + case AppExceptionType.authLoginFailed: + txt = loc.authLoginFailed; + break; + case AppExceptionType.authRegistrationFailed: + txt = loc.authRegisterFailed; + break; + case AppExceptionType.authAccountAlreadyExists: + txt = loc.authAccountAlreadyExistsFailure; + break; + case AppExceptionType.authAccountDisabled: + txt = loc.appErrorAuthAccountDisabled; + break; + case AppExceptionType.authUnauthorized: + txt = loc.appErrorAuthUnauthorized; + break; + case AppExceptionType.authForbidden: + txt = loc.appErrorAuthForbidden; + break; + case AppExceptionType.userNotFound: + txt = loc.appErrorUserNotFound; + break; + case AppExceptionType.formPasswordWrongPolicy: + txt = loc.formPasswordWrongPolicy; + break; + case AppExceptionType.serverSideProblem: + txt = loc.appErrorServerSideProblem; + break; + } + + return txt; +} diff --git a/lib/src/presentation/utils/utils.dart b/lib/src/presentation/utils/utils.dart new file mode 100644 index 0000000..fc5ea6e --- /dev/null +++ b/lib/src/presentation/utils/utils.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:social_cv_client_flutter/src/presentation/commons/defaults.dart'; + +String getInitials(String nameString) { + if (nameString.isEmpty) return ' '; + + final List nameArray = + nameString.replaceAll(RegExp(r'\s+\b|\b\s'), ' ').split(' '); + final String initials = + ((nameArray[0])[0] != null ? (nameArray[0])[0] : ' ') + + (nameArray.length == 1 ? ' ' : (nameArray[nameArray.length - 1])[0]); + + return initials; +} + +List> getDropDownMenuElementPerPage() { + final List _values = [ + kCVItemsPerPage1, + kCVItemsPerPage2, + kCVItemsPerPage3, + kCVItemsPerPage4 + ]; + final List> items = List(); + for (String value in _values) { + items.add(DropdownMenuItem(value: value, child: Text(value))); + } + return items; +} diff --git a/lib/src/utils/validators.dart b/lib/src/presentation/utils/validators.dart similarity index 100% rename from lib/src/utils/validators.dart rename to lib/src/presentation/utils/validators.dart diff --git a/lib/src/ui/widgets/account_tile_widget.dart b/lib/src/presentation/widgets/account_tile_widget.dart similarity index 62% rename from lib/src/ui/widgets/account_tile_widget.dart rename to lib/src/presentation/widgets/account_tile_widget.dart index 0fbd294..30b76b1 100644 --- a/lib/src/ui/widgets/account_tile_widget.dart +++ b/lib/src/presentation/widgets/account_tile_widget.dart @@ -1,13 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class AccountTile extends StatefulWidget { const AccountTile({Key key}) : super(key: key); @@ -19,7 +14,7 @@ class AccountTile extends StatefulWidget { class _AccountTitleState extends State { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: BlocProvider.of(context), builder: (BuildContext context, AuthenticationState state) { if (state is AuthenticationAuthenticated) { @@ -33,11 +28,9 @@ class _AccountTitleState extends State { } } -//////////////////////////////////////////////////////////////////////////////// -// // -// _AccountTileConnected // -// // -//////////////////////////////////////////////////////////////////////////////// +/// ---------------------------------------------------------------------------- +/// _AccountTileConnected +/// ---------------------------------------------------------------------------- class _AccountTileConnected extends StatelessWidget { final String _tag = '$_AccountTileConnected'; @@ -46,13 +39,13 @@ class _AccountTileConnected extends StatelessWidget { @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); - return BlocBuilder( - bloc: BlocProvider.of(context), - builder: (BuildContext context, AccountState state) { - if (state is AccountUninitialized) { - } else if (state is AccountLoaded) { + return BlocBuilder( + bloc: BlocProvider.of(context), + builder: (BuildContext context, IdentityState state) { + if (state is IdentityUninitialized) { + } else if (state is IdentityLoaded) { var userModel = state.user; return ListTile( leading: InitialCircleAvatar( @@ -66,7 +59,7 @@ class _AccountTileConnected extends StatelessWidget { onPressed: () => null, // TODO: Add logout ), ); - } else if (state is AccountFailed) { + } else if (state is IdentityFailed) { return ErrorTile(error: state.error); } return ErrorTile(error: NotImplementedYetError()); @@ -85,7 +78,7 @@ class _AccountTileNotConnected extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( child: ListTile( - title: Center(child: Text(CVLocalizations.of(context).authSignInCTA)), + title: Center(child: Text(CVLocalizations.of(context).authLoginCTA)), trailing: Icon(MdiIcons.login), ), onTap: () => navigateToLogin(context), diff --git a/lib/src/ui/widgets/arc_banner_image_widget.dart b/lib/src/presentation/widgets/arc_banner_image_widget.dart similarity index 69% rename from lib/src/ui/widgets/arc_banner_image_widget.dart rename to lib/src/presentation/widgets/arc_banner_image_widget.dart index 67899f1..bdd8a44 100644 --- a/lib/src/ui/widgets/arc_banner_image_widget.dart +++ b/lib/src/presentation/widgets/arc_banner_image_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class ArcBannerImage extends StatelessWidget { final String _tag = '$ArcBannerImage'; @@ -12,9 +12,9 @@ class ArcBannerImage extends StatelessWidget { @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); - var screenWidth = MediaQuery.of(context).size.width; + final screenWidth = MediaQuery.of(context).size.width; return ClipPath( clipper: ArcClipper(), @@ -31,17 +31,17 @@ class ArcBannerImage extends StatelessWidget { class ArcClipper extends CustomClipper { @override Path getClip(Size size) { - var path = new Path(); + final path = Path(); path.lineTo(0.0, size.height - 30); - var firstControlPoint = new Offset(size.width / 4, size.height); - var firstPoint = new Offset(size.width / 2, size.height); + final firstControlPoint = Offset(size.width / 4, size.height); + final firstPoint = Offset(size.width / 2, size.height); path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy, firstPoint.dx, firstPoint.dy); - var secondControlPoint = - new Offset(size.width - (size.width / 4), size.height); - var secondPoint = new Offset(size.width, size.height - 30); + final secondControlPoint = + Offset(size.width - (size.width / 4), size.height); + final secondPoint = Offset(size.width, size.height - 30); path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy, secondPoint.dx, secondPoint.dy); diff --git a/lib/src/presentation/widgets/bubble_indication_painter.dart b/lib/src/presentation/widgets/bubble_indication_painter.dart new file mode 100644 index 0000000..85af493 --- /dev/null +++ b/lib/src/presentation/widgets/bubble_indication_painter.dart @@ -0,0 +1,52 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class TabIndicationPainter extends CustomPainter { + Paint painter; + final double dxTarget; + final double dxEntry; + final double radius; + final double dy; + + final PageController pageController; + + TabIndicationPainter({ + this.dxTarget = 125.0, + this.dxEntry = 25.0, + this.radius = 21.0, + this.dy = 25.0, + this.pageController, + }) : super(repaint: pageController) { + painter = Paint() + ..color = const Color(0xFFFFFFFF) + ..style = PaintingStyle.fill; + } + + @override + void paint(Canvas canvas, Size size) { + final pos = pageController.position; + final double fullExtent = + pos.maxScrollExtent - pos.minScrollExtent + pos.viewportDimension; + + final double pageOffset = pos.extentBefore / fullExtent; + + final bool left2right = dxEntry < dxTarget; + final Offset entry = Offset(left2right ? dxEntry : dxTarget, dy); + final Offset target = Offset(left2right ? dxTarget : dxEntry, dy); + + final Path path = Path(); + path.addArc( + Rect.fromCircle(center: entry, radius: radius), 0.5 * pi, 1 * pi); + path.addRect(Rect.fromLTRB(entry.dx, dy - radius, target.dx, dy + radius)); + path.addArc( + Rect.fromCircle(center: target, radius: radius), 1.5 * pi, 1 * pi); + + canvas.translate(size.width * pageOffset, 0.0); + canvas.drawShadow(path, const Color(0xFFfbab66), 3.0, true); + canvas.drawPath(path, painter); + } + + @override + bool shouldRepaint(TabIndicationPainter oldDelegate) => true; +} diff --git a/lib/src/ui/widgets/elements/entry_list_profile_widget.dart b/lib/src/presentation/widgets/elements/entry_list_profile_widget.dart similarity index 84% rename from lib/src/ui/widgets/elements/entry_list_profile_widget.dart rename to lib/src/presentation/widgets/elements/entry_list_profile_widget.dart index b6fc373..7b5b417 100644 --- a/lib/src/ui/widgets/elements/entry_list_profile_widget.dart +++ b/lib/src/presentation/widgets/elements/entry_list_profile_widget.dart @@ -1,23 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_list_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; /// [SimpleEntryListProfile] is a dummy widget that use [entryIds] or [entries] /// or [entryBlocs] to create a list of [EntryProfileWidget] class SimpleEntryListProfile extends StatelessWidget { final List entryIds; - final List entries; + final List entries; final List entryBlocs; /// List behaviors @@ -107,7 +98,7 @@ class _EntryListProfileState extends ComplexEntryListState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: widget.entryListBloc, builder: (BuildContext context, EntryListState state) { if (state is EntryListLoading) { diff --git a/lib/src/ui/widgets/elements/entry_list_widget.dart b/lib/src/presentation/widgets/elements/entry_list_widget.dart similarity index 82% rename from lib/src/ui/widgets/elements/entry_list_widget.dart rename to lib/src/presentation/widgets/elements/entry_list_widget.dart index 6aa94e3..b5af7af 100644 --- a/lib/src/ui/widgets/elements/entry_list_widget.dart +++ b/lib/src/presentation/widgets/elements/entry_list_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; /// [EntryListWidget] is a clever widget that use an [EntryListBloc] /// based on [parentGroupId] or [ownerId] or [entryListBloc] @@ -10,7 +10,7 @@ abstract class EntryListWidget extends StatefulWidget { final String ownerId; final EntryListBloc entryListBloc; - EntryListWidget({ + const EntryListWidget({ Key key, this.parentGroupId, this.ownerId, @@ -36,8 +36,8 @@ abstract class ComplexEntryListState entryListBloc = widget.entryListBloc; if (entryListBloc == null) { - final cvRepository = Provider.of(context, listen: false); - entryListBloc = EntryListBloc(cvRepository: cvRepository); + final repo = Provider.of(context, listen: false); + entryListBloc = EntryListBloc(repository: repo); entryListBloc.dispatch(EntryListInitialized( parentGroupId: widget.parentGroupId, ownerId: widget.ownerId, diff --git a/lib/src/ui/widgets/elements/entry_profile_widget.dart b/lib/src/presentation/widgets/elements/entry_profile_widget.dart similarity index 80% rename from lib/src/ui/widgets/elements/entry_profile_widget.dart rename to lib/src/presentation/widgets/elements/entry_profile_widget.dart index b3c254e..c996221 100644 --- a/lib/src/ui/widgets/elements/entry_profile_widget.dart +++ b/lib/src/presentation/widgets/elements/entry_profile_widget.dart @@ -1,19 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; /// [EntryProfileWidget] is an [EntryWidget] for profile display purpose class EntryProfileWidget extends EntryWidget { EntryProfileWidget( - {Key key, String entryId, EntryViewModel entry, EntryBloc entryBloc}) + {Key key, String entryId, EntryEntity entry, EntryBloc entryBloc}) : super(key: key, entryId: entryId, entry: entry, entryBloc: entryBloc); @override @@ -23,8 +17,8 @@ class EntryProfileWidget extends EntryWidget { class _EntryProfileWidgetState extends EntryWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( - bloc: this.entryBloc, + return BlocBuilder( + bloc: entryBloc, builder: (BuildContext context, EntryState state) { if (state is EntryLoaded) { final entry = state.element; @@ -43,11 +37,11 @@ class _EntryProfileWidgetState extends EntryWidgetState { } class _EntryWidgetMap extends StatelessWidget { - final EntryViewModel entry; + final EntryEntity entry; _EntryWidgetMap({ @required this.entry, - }) : assert(EntryViewModel != null); + }) : assert(EntryEntity != null); @override Widget build(BuildContext context) { @@ -76,11 +70,11 @@ class _EntryWidgetMap extends StatelessWidget { } class _EntryWidgetEvent extends StatelessWidget { - final EntryViewModel entry; + final EntryEntity entry; _EntryWidgetEvent({ @required this.entry, - }) : assert(EntryViewModel != null); + }) : assert(EntryEntity != null); @override Widget build(BuildContext context) { @@ -122,7 +116,7 @@ class _EntryWidgetEvent extends StatelessWidget { ), Expanded( child: Text( - entry.content, + entry.content as String, textAlign: TextAlign.justify, ), ), @@ -143,17 +137,15 @@ class _EntryWidgetEvent extends StatelessWidget { } class _EntryWidgetTag extends StatelessWidget { - final EntryViewModel entry; + final EntryEntity entry; _EntryWidgetTag(this.entry); @override Widget build(BuildContext context) { - List tags = entry.content; - List _tagWidgets = []; - tags.forEach((dynamic tag) { - _tagWidgets.add(Chip(label: Text(tag as String))); - }); + final List tags = entry.content as List; + final List _tagWidgets = + tags.map((tag) => Chip(label: Text(tag))).toList(); return Container( padding: AppStyles.entryPadding, diff --git a/lib/src/ui/widgets/elements/entry_widget.dart b/lib/src/presentation/widgets/elements/entry_widget.dart similarity index 76% rename from lib/src/ui/widgets/elements/entry_widget.dart rename to lib/src/presentation/widgets/elements/entry_widget.dart index 15d05b1..13d2011 100644 --- a/lib/src/ui/widgets/elements/entry_widget.dart +++ b/lib/src/presentation/widgets/elements/entry_widget.dart @@ -1,13 +1,12 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; /// If [entryBloc] given we assume that it have been already initialized abstract class EntryWidget extends StatefulWidget { final String entryId; - final EntryViewModel entry; + final EntryEntity entry; final EntryBloc entryBloc; EntryWidget({Key key, this.entryId, this.entry, this.entryBloc}) @@ -28,9 +27,9 @@ abstract class EntryWidgetState extends State { entryBloc = widget.entryBloc; if (entryBloc == null) { - final cvRepository = Provider.of(context, listen: false); + final repo = Provider.of(context, listen: false); - entryBloc = EntryBloc(cvRepository: cvRepository); + entryBloc = EntryBloc(repository: repo); entryBloc.dispatch(EntryInitialized( entryId: widget.entryId, entry: widget.entry, diff --git a/lib/src/ui/widgets/elements/group_list_profile_widget.dart b/lib/src/presentation/widgets/elements/group_list_profile_widget.dart similarity index 84% rename from lib/src/ui/widgets/elements/group_list_profile_widget.dart rename to lib/src/presentation/widgets/elements/group_list_profile_widget.dart index 62a9e98..c2c6c34 100644 --- a/lib/src/ui/widgets/elements/group_list_profile_widget.dart +++ b/lib/src/presentation/widgets/elements/group_list_profile_widget.dart @@ -1,23 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_list_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; /// [SimpleGroupListProfile] is a dummy widget that use [groupIds] or [groups] /// or [groupBlocs] to create a list of [GroupProfileWidget] class SimpleGroupListProfile extends StatelessWidget { final List groupIds; - final List groups; + final List groups; final List groupBlocs; /// List behaviors @@ -107,7 +98,7 @@ class _GroupListProfileState extends GroupListWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: widget.groupListBloc, builder: (BuildContext context, GroupListState state) { if (state is GroupListLoading) { diff --git a/lib/src/ui/widgets/elements/group_list_widget.dart b/lib/src/presentation/widgets/elements/group_list_widget.dart similarity index 83% rename from lib/src/ui/widgets/elements/group_list_widget.dart rename to lib/src/presentation/widgets/elements/group_list_widget.dart index 651692e..d66621f 100644 --- a/lib/src/ui/widgets/elements/group_list_widget.dart +++ b/lib/src/presentation/widgets/elements/group_list_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; abstract class GroupListWidget extends StatefulWidget { final String parentPartId; @@ -34,9 +34,9 @@ abstract class GroupListWidgetState groupListBloc = widget.groupListBloc; if (widget.groupListBloc == null) { - final cvRepository = Provider.of(context, listen: false); + final groupRepo = Provider.of(context, listen: false); - groupListBloc = GroupListBloc(cvRepository: cvRepository); + groupListBloc = GroupListBloc(repository: groupRepo); groupListBloc.dispatch(GroupListInitialized( parentPartId: widget.parentPartId, ownerId: widget.ownerId, diff --git a/lib/src/ui/widgets/elements/group_profile_widget.dart b/lib/src/presentation/widgets/elements/group_profile_widget.dart similarity index 79% rename from lib/src/ui/widgets/elements/group_profile_widget.dart rename to lib/src/presentation/widgets/elements/group_profile_widget.dart index fa49727..314b39b 100644 --- a/lib/src/ui/widgets/elements/group_profile_widget.dart +++ b/lib/src/presentation/widgets/elements/group_profile_widget.dart @@ -1,20 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/entry_list_profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; /// [GroupProfileWidget] is an [GroupWidget] for profile display purpose class GroupProfileWidget extends GroupWidget { GroupProfileWidget( - {Key key, String groupId, GroupViewModel group, GroupBloc groupBloc}) + {Key key, String groupId, GroupEntity group, GroupBloc groupBloc}) : super(key: key, groupId: groupId, group: group, groupBloc: groupBloc); @override @@ -24,11 +17,11 @@ class GroupProfileWidget extends GroupWidget { class _GroupProfileWidgetState extends GroupWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: groupBloc, builder: (BuildContext context, GroupState state) { if (state is GroupLoaded) { - GroupViewModel group; + GroupEntity group; if (group.type == kCVGroupTypeListHorizontal) { return _GroupHorizontal(group: group); } else if (group.type == kCVGroupTypeListVertical) { @@ -48,7 +41,7 @@ class _GroupHorizontal extends StatelessWidget { @required this.group, }) : assert(group != null); - final GroupViewModel group; + final GroupEntity group; @override Widget build(BuildContext context) { @@ -94,7 +87,7 @@ class _GroupVertical extends StatelessWidget { @required this.group, }) : assert(group != null); - final GroupViewModel group; + final GroupEntity group; @override Widget build(BuildContext context) { diff --git a/lib/src/ui/widgets/elements/group_widget.dart b/lib/src/presentation/widgets/elements/group_widget.dart similarity index 77% rename from lib/src/ui/widgets/elements/group_widget.dart rename to lib/src/presentation/widgets/elements/group_widget.dart index e4b492a..7816671 100644 --- a/lib/src/ui/widgets/elements/group_widget.dart +++ b/lib/src/presentation/widgets/elements/group_widget.dart @@ -1,13 +1,12 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; /// If [groupBloc] given we assume that it have been already initialized and abstract class GroupWidget extends StatefulWidget { final String groupId; - final GroupViewModel group; + final GroupEntity group; final GroupBloc groupBloc; GroupWidget({Key key, this.groupId, this.group, this.groupBloc}) @@ -28,9 +27,9 @@ abstract class GroupWidgetState extends State { groupBloc = widget.groupBloc; if (groupBloc == null) { - final cvRepository = Provider.of(context, listen: false); + final groupRepo = Provider.of(context, listen: false); - groupBloc = GroupBloc(cvRepository: cvRepository); + groupBloc = GroupBloc(repository: groupRepo); groupBloc.dispatch(GroupInitialized( groupId: widget.groupId, group: widget.group, diff --git a/lib/src/ui/widgets/elements/part_list_profile_widget.dart b/lib/src/presentation/widgets/elements/part_list_profile_widget.dart similarity index 84% rename from lib/src/ui/widgets/elements/part_list_profile_widget.dart rename to lib/src/presentation/widgets/elements/part_list_profile_widget.dart index caa73a6..9b52b0c 100644 --- a/lib/src/ui/widgets/elements/part_list_profile_widget.dart +++ b/lib/src/presentation/widgets/elements/part_list_profile_widget.dart @@ -1,23 +1,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_list_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/elements/part_list_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/elements/part_profile_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/loading_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/sort_dialog_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/sort_list_tile_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/utils/utils.dart'; /// [SimplePartListProfile] is a dummy widget that use [partIds] or [parts] or /// [partBlocs] to create a list of [PartProfileWidget] class SimplePartListProfile extends StatelessWidget { final List partIds; - final List parts; + final List parts; final List partBlocs; /// List behaviors @@ -108,7 +107,7 @@ class _PartListProfileState extends PartListWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: widget.partListBloc, builder: (BuildContext context, PartListState state) { if (state is PartListLoading) { diff --git a/lib/src/ui/widgets/elements/part_list_widget.dart b/lib/src/presentation/widgets/elements/part_list_widget.dart similarity index 82% rename from lib/src/ui/widgets/elements/part_list_widget.dart rename to lib/src/presentation/widgets/elements/part_list_widget.dart index 3351811..6a69fe9 100644 --- a/lib/src/ui/widgets/elements/part_list_widget.dart +++ b/lib/src/presentation/widgets/elements/part_list_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; abstract class PartListWidget extends StatefulWidget { final String parentProfileId; @@ -33,9 +33,9 @@ abstract class PartListWidgetState extends State { partListBloc = widget.partListBloc; if (widget.partListBloc == null) { - final cvRepository = Provider.of(context, listen: false); + final partRepo = Provider.of(context, listen: false); - partListBloc = PartListBloc(cvRepository: cvRepository); + partListBloc = PartListBloc(repository: partRepo); partListBloc.dispatch(PartListInitialized( parentProfileId: widget.parentProfileId, ownerId: widget.ownerId, diff --git a/lib/src/ui/widgets/elements/part_profile_widget.dart b/lib/src/presentation/widgets/elements/part_profile_widget.dart similarity index 71% rename from lib/src/ui/widgets/elements/part_profile_widget.dart rename to lib/src/presentation/widgets/elements/part_profile_widget.dart index b0b0f77..74d5517 100644 --- a/lib/src/ui/widgets/elements/part_profile_widget.dart +++ b/lib/src/presentation/widgets/elements/part_profile_widget.dart @@ -1,21 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/api_values.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/group_list_profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/part_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; /// [PartProfileWidget] is an [PartWidget] for profile display purpose class PartProfileWidget extends PartWidget { PartProfileWidget( - {Key key, String partId, PartViewModel part, PartBloc partBloc}) + {Key key, String partId, PartEntity part, PartBloc partBloc}) : super(key: key, partId: partId, part: part, partBloc: partBloc); @override @@ -25,7 +17,7 @@ class PartProfileWidget extends PartWidget { class _PartProfileWidgetState extends PartWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: partBloc, builder: (BuildContext context, PartState state) { if (state is PartLoading) { @@ -47,15 +39,15 @@ class _PartProfileWidgetState extends PartWidgetState { class _PartWidgetFromModel extends StatelessWidget { _PartWidgetFromModel({ @required this.part, - }) : assert(PartViewModel != null); + }) : assert(PartEntity != null); - final PartViewModel part; + final PartEntity part; @override Widget build(BuildContext context) { if (part.type == kCVPartTypeListHorizontal) { return _PartWidgetFromModelHorizontal( - partViewModel: part, + partEntity: part, ); } else if (part.type == kCVPartTypeListVertical) { return _PartWidgetFromModelVertical( @@ -69,10 +61,10 @@ class _PartWidgetFromModel extends StatelessWidget { class _PartWidgetFromModelHorizontal extends StatelessWidget { _PartWidgetFromModelHorizontal({ - @required this.partViewModel, - }) : assert(PartViewModel != null); + @required this.partEntity, + }) : assert(PartEntity != null); - final PartViewModel partViewModel; + final PartEntity partEntity; @override Widget build(BuildContext context) { @@ -82,21 +74,21 @@ class _PartWidgetFromModelHorizontal extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - partViewModel.name.toUpperCase(), + partEntity.name.toUpperCase(), style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold), ), FlatButton( child: Text(CVLocalizations.of(context).partWidgetDetails), - onPressed: () => navigateToPart(context, partViewModel.id), + onPressed: () => navigateToPart(context, partEntity.id), ), ], ), Container( height: AppStyles.groupHorizontalListHeight, child: SimpleGroupListProfile( - groupIds: partViewModel.groupIds, + groupIds: partEntity.groupIds, scrollDirection: Axis.horizontal, shrinkWrap: true, ), @@ -111,7 +103,7 @@ class _PartWidgetFromModelVertical extends StatelessWidget { @required this.part, }) : assert(part != null); - final PartViewModel part; + final PartEntity part; @override Widget build(BuildContext context) { diff --git a/lib/src/ui/widgets/elements/part_widget.dart b/lib/src/presentation/widgets/elements/part_widget.dart similarity index 71% rename from lib/src/ui/widgets/elements/part_widget.dart rename to lib/src/presentation/widgets/elements/part_widget.dart index 24db6eb..3c4e8f4 100644 --- a/lib/src/ui/widgets/elements/part_widget.dart +++ b/lib/src/presentation/widgets/elements/part_widget.dart @@ -1,16 +1,15 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; /// If [partBloc] given we assume that it have been already initialized abstract class PartWidget extends StatefulWidget { final String partId; - final PartViewModel part; + final PartEntity part; final PartBloc partBloc; - PartWidget({Key key, this.partId, this.part, this.partBloc}) + const PartWidget({Key key, this.partId, this.part, this.partBloc}) : assert(partId != null && part == null && partBloc == null), assert(partId == null && part != null && partBloc == null), assert(partId == null && part == null && partBloc != null), @@ -28,9 +27,9 @@ abstract class PartWidgetState extends State { partBloc = widget.partBloc; if (partBloc == null) { - final cvRepository = Provider.of(context, listen: false); + final partRepo = Provider.of(context, listen: false); - partBloc = PartBloc(cvRepository: cvRepository); + partBloc = PartBloc(repository: partRepo); partBloc.dispatch(PartInitialized( partId: widget.partId, part: widget.part, diff --git a/lib/src/ui/widgets/elements/profile_list_tile_widget.dart b/lib/src/presentation/widgets/elements/profile_list_tile_widget.dart similarity index 84% rename from lib/src/ui/widgets/elements/profile_list_tile_widget.dart rename to lib/src/presentation/widgets/elements/profile_list_tile_widget.dart index 568401c..b06aa54 100644 --- a/lib/src/ui/widgets/elements/profile_list_tile_widget.dart +++ b/lib/src/presentation/widgets/elements/profile_list_tile_widget.dart @@ -1,23 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_list_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/loading_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_dialog_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; /// [SimpleProfileListProfile] is a dummy widget that use [profileIds] or [profiles] or /// [profileBlocs] to create a list of [ProfileTile] class SimpleProfileListProfile extends StatelessWidget { final List profileIds; - final List profiles; + final List profiles; final List profileBlocs; /// List behaviors @@ -108,7 +99,7 @@ class _ProfileListProfileState extends ProfileListWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: widget.profileListBloc, builder: (BuildContext context, ProfileListState state) { if (state is ProfileListLoading) { diff --git a/lib/src/ui/widgets/elements/profile_list_widget.dart b/lib/src/presentation/widgets/elements/profile_list_widget.dart similarity index 83% rename from lib/src/ui/widgets/elements/profile_list_widget.dart rename to lib/src/presentation/widgets/elements/profile_list_widget.dart index a9c65bb..4bc8a63 100644 --- a/lib/src/ui/widgets/elements/profile_list_widget.dart +++ b/lib/src/presentation/widgets/elements/profile_list_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; /// If [profileListBloc] given we assume that it have been already initialized abstract class ProfileListWidget extends StatefulWidget { @@ -33,9 +33,10 @@ abstract class ProfileListWidgetState super.initState(); profileListBloc = widget.profileListBloc; if (widget.profileListBloc == null) { - final cvRepository = Provider.of(context, listen: false); + final profileRepo = + Provider.of(context, listen: false); - profileListBloc = ProfileListBloc(cvRepository: cvRepository); + profileListBloc = ProfileListBloc(repository: profileRepo); profileListBloc.dispatch(ProfileListInitialized( parentUserId: widget.parentUserId, ownerId: widget.ownerId, diff --git a/lib/src/ui/widgets/elements/profile_tile_widget.dart b/lib/src/presentation/widgets/elements/profile_tile_widget.dart similarity index 67% rename from lib/src/ui/widgets/elements/profile_tile_widget.dart rename to lib/src/presentation/widgets/elements/profile_tile_widget.dart index 1314955..0c19345 100644 --- a/lib/src/ui/widgets/elements/profile_tile_widget.dart +++ b/lib/src/presentation/widgets/elements/profile_tile_widget.dart @@ -1,19 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/elements/profile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/src/presentation/utils/navigation.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/elements/profile_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/error_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/initial_circle_avatar_widget.dart'; class ProfileTile extends ProfileWidget { ProfileTile({ Key key, String profileId, - ProfileViewModel profile, + ProfileEntity profile, ProfileBloc profileBloc, }) : super( key: key, @@ -29,7 +28,7 @@ class ProfileTile extends ProfileWidget { class _ProfileTileState extends ProfileWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( bloc: widget.profileBloc, builder: (BuildContext context, ProfileState state) { if (state is ProfileLoading) { @@ -37,7 +36,7 @@ class _ProfileTileState extends ProfileWidgetState { var profile = state.element; return ListTile( leading: InitialCircleAvatar( - backgroundImage: NetworkImage(profile.picture ?? ''), + backgroundImage: NetworkImage('${profile.picture ?? ''}'), ), title: Text(profile.title ?? ''), subtitle: Text(profile.subtitle ?? ''), diff --git a/lib/src/ui/widgets/elements/profile_widget.dart b/lib/src/presentation/widgets/elements/profile_widget.dart similarity index 71% rename from lib/src/ui/widgets/elements/profile_widget.dart rename to lib/src/presentation/widgets/elements/profile_widget.dart index 5b05155..608ac2a 100644 --- a/lib/src/ui/widgets/elements/profile_widget.dart +++ b/lib/src/presentation/widgets/elements/profile_widget.dart @@ -1,16 +1,15 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/models.dart'; -import 'package:social_cv_client_dart_common/repositories.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; /// If [profileBloc] given we assume that it have been already initialized abstract class ProfileWidget extends StatefulWidget { final String profileId; - final ProfileViewModel profile; + final ProfileEntity profile; final ProfileBloc profileBloc; - ProfileWidget({Key key, this.profileId, this.profile, this.profileBloc}) + const ProfileWidget({Key key, this.profileId, this.profile, this.profileBloc}) : assert(profileId != null && profile == null && profileBloc == null), assert(profileId == null && profile != null && profileBloc == null), assert(profileId == null && profile == null && profileBloc != null), @@ -28,9 +27,10 @@ abstract class ProfileWidgetState extends State { profileBloc = widget.profileBloc; if (profileBloc == null) { - final cvRepository = Provider.of(context, listen: false); + final profileRepo = + Provider.of(context, listen: false); - profileBloc = ProfileBloc(cvRepository: cvRepository); + profileBloc = ProfileBloc(repository: profileRepo); profileBloc.dispatch(ProfileInitialized( profileId: widget.profileId, profile: widget.profile, diff --git a/lib/src/ui/widgets/error_widget.dart b/lib/src/presentation/widgets/error_widget.dart similarity index 94% rename from lib/src/ui/widgets/error_widget.dart rename to lib/src/presentation/widgets/error_widget.dart index fb85318..d25366c 100644 --- a/lib/src/ui/widgets/error_widget.dart +++ b/lib/src/presentation/widgets/error_widget.dart @@ -2,10 +2,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; /// [CustomErrorWidget] is a based widget for error /// @@ -176,7 +173,7 @@ class ErrorList extends CustomErrorWidget { const ErrorList({ Key key, - @required Error error, + @required dynamic error, /// List behaviors this.scrollDirection = Axis.vertical, diff --git a/lib/src/ui/widgets/initial_circle_avatar_widget.dart b/lib/src/presentation/widgets/initial_circle_avatar_widget.dart similarity index 96% rename from lib/src/ui/widgets/initial_circle_avatar_widget.dart rename to lib/src/presentation/widgets/initial_circle_avatar_widget.dart index fef6cb2..ae8b623 100644 --- a/lib/src/ui/widgets/initial_circle_avatar_widget.dart +++ b/lib/src/presentation/widgets/initial_circle_avatar_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/utils/utils.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class InitialCircleAvatar extends StatefulWidget { const InitialCircleAvatar({ diff --git a/lib/src/ui/widgets/loading_widget.dart b/lib/src/presentation/widgets/loading_widget.dart similarity index 91% rename from lib/src/ui/widgets/loading_widget.dart rename to lib/src/presentation/widgets/loading_widget.dart index 0cd1f1c..7ab4157 100644 --- a/lib/src/ui/widgets/loading_widget.dart +++ b/lib/src/presentation/widgets/loading_widget.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; /// [LoadingShadowContent] displays lines with opacity moving up and down /// Specify the number of loading lines to display @@ -21,13 +21,14 @@ class LoadingShadowContent extends StatefulWidget { assert(padding != null), super(key: key); + @override _LoadingShadowContentState createState() => _LoadingShadowContentState(); } class _LoadingShadowContentState extends State with SingleTickerProviderStateMixin { AnimationController _loadingOpacity; - Animation _opacity; + Animation _opacity; Random _rand; double _divideFactor; @@ -36,7 +37,7 @@ class _LoadingShadowContentState extends State @override void initState() { _rand = Random(); - _divideFactor = (_rand.nextInt(5) + 1.4); + _divideFactor = _rand.nextInt(5) + 1.4; _loadingOpacity = AnimationController( vsync: this, duration: Duration(milliseconds: 1500), @@ -69,7 +70,7 @@ class _LoadingShadowContentState extends State @override Widget build(BuildContext context) { - List _widgets = []; + final List _widgets = []; for (int i = 0; i < widget.numberOfTitleLines; i++) { _widgets.add( Align( @@ -168,7 +169,7 @@ class LoadingList extends StatelessWidget { itemCount: count, itemBuilder: (BuildContext context, int index) { return Card( - child: LoadingShadowContent( + child: const LoadingShadowContent( numberOfTitleLines: 1, numberOfContentLines: 2, ), @@ -182,12 +183,12 @@ class LoadingList extends StatelessWidget { /// /// See [Scaffold] widget for more documentation class LoadingScaffold extends StatelessWidget { - LoadingScaffold({Key key}) : super(key: key); + const LoadingScaffold({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( - body: Center(child: CircularProgressIndicator()), + body: Center(child: const CircularProgressIndicator()), ); } } @@ -196,12 +197,12 @@ class LoadingScaffold extends StatelessWidget { /// /// See [MaterialApp] widget for more documentation class LoadingApp extends StatelessWidget { - LoadingApp({Key key}) : super(key: key); + const LoadingApp({Key key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( - home: LoadingScaffold(), + home: const LoadingScaffold(), color: AppStyles.primaryColor, ); } diff --git a/lib/src/ui/widgets/login_form_widget.dart b/lib/src/presentation/widgets/login_form_widget.dart similarity index 77% rename from lib/src/ui/widgets/login_form_widget.dart rename to lib/src/presentation/widgets/login_form_widget.dart index 7123c62..9b725c0 100644 --- a/lib/src/ui/widgets/login_form_widget.dart +++ b/lib/src/presentation/widgets/login_form_widget.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class LoginForm extends StatefulWidget { @override @@ -16,14 +15,14 @@ class _LoginFormState extends State { final FocusNode myFocusNodeEmailLogin = FocusNode(); final FocusNode myFocusNodePasswordLogin = FocusNode(); - TextEditingController loginEmailController = new TextEditingController(); - TextEditingController loginPasswordController = new TextEditingController(); + TextEditingController loginEmailController = TextEditingController(); + TextEditingController loginPasswordController = TextEditingController(); bool _obscureTextLogin = true; @override void dispose() { - print('$_tag:$dispose()'); + print('$_tag:dispose()'); loginEmailController.dispose(); loginPasswordController.dispose(); myFocusNodeEmailLogin.dispose(); @@ -33,7 +32,7 @@ class _LoginFormState extends State { @override Widget build(BuildContext context) { - print('$_tag:$build'); + print('$_tag:build'); LoginBloc _loginBloc = BlocProvider.of(context); void _loginPressed() { @@ -43,19 +42,19 @@ class _LoginFormState extends State { )); } - return BlocListener( + return BlocListener( bloc: _loginBloc, listener: (BuildContext context, LoginState state) { if (state is LoginFailure) { Scaffold.of(context).showSnackBar( SnackBar( backgroundColor: AppStyles.errorColor, - content: Text('${state.error.runtimeType}'), + content: ErrorRow(error: state.error), ), ); } }, - child: BlocBuilder( + child: BlocBuilder( bloc: _loginBloc, builder: (BuildContext context, LoginState state) { return Card( @@ -67,7 +66,9 @@ class _LoginFormState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Center(child: Text('Login')), + Center( + child: Text(CVLocalizations.of(context).authLoginTitle), + ), Padding( padding: AppStyles.defaultFormInputPadding, child: TextFormField( @@ -75,8 +76,8 @@ class _LoginFormState extends State { textInputAction: TextInputAction.next, controller: loginEmailController, decoration: InputDecoration( - hintText: 'someone@email.com', - labelText: 'Email', + hintText: CVLocalizations.of(context).formEmailHint, + labelText: CVLocalizations.of(context).formEmailLabel, ), ), ), @@ -92,12 +93,13 @@ class _LoginFormState extends State { : MdiIcons.eyeOutline), onPressed: _toggleLoginPassword, ), - labelText: 'Password', + labelText: + CVLocalizations.of(context).formPasswordLabel, ), ), ), MaterialButton( - child: Text('LOGIN'), + child: Text(CVLocalizations.of(context).authLoginCTA), onPressed: state is! LoginLoading ? _loginPressed : null, ), ], diff --git a/lib/src/ui/widgets/menu_bottom_sheet_widget.dart b/lib/src/presentation/widgets/menu_bottom_sheet_widget.dart similarity index 65% rename from lib/src/ui/widgets/menu_bottom_sheet_widget.dart rename to lib/src/presentation/widgets/menu_bottom_sheet_widget.dart index 5598c5a..3491291 100644 --- a/lib/src/ui/widgets/menu_bottom_sheet_widget.dart +++ b/lib/src/presentation/widgets/menu_bottom_sheet_widget.dart @@ -1,9 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/account_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/theme_switch_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class MenuBottomSheet extends StatelessWidget { final String _tag = '$MenuBottomSheet'; @@ -12,8 +8,7 @@ class MenuBottomSheet extends StatelessWidget { Key key, this.backgroundColor, this.borderRadius = const BorderRadius.only( - topLeft: const Radius.circular(10.0), - topRight: const Radius.circular(10.0)), + topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)), }) : super(key: key); final Color backgroundColor; @@ -21,16 +16,16 @@ class MenuBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); return SafeArea( left: false, right: false, child: Wrap( children: [ - AccountTile(), + const AccountTile(), Divider(), - ThemeSwitchListTile(), + const ThemeSwitchTile(), ListTile( leading: Icon(Icons.settings), title: Text(CVLocalizations.of(context).settingsCTA), @@ -44,7 +39,7 @@ class MenuBottomSheet extends StatelessWidget { child: Text(CVLocalizations.of(context).menuPPCTA), onPressed: null, ), - Text(CVLocalizations.of(context).middleDot), + const Text('·'), MaterialButton( child: Text(CVLocalizations.of(context).menuToSCTA), onPressed: null, diff --git a/lib/src/ui/widgets/menu_button_widget.dart b/lib/src/presentation/widgets/menu_button_widget.dart similarity index 57% rename from lib/src/ui/widgets/menu_button_widget.dart rename to lib/src/presentation/widgets/menu_button_widget.dart index 48d1916..0a6d396 100644 --- a/lib/src/ui/widgets/menu_button_widget.dart +++ b/lib/src/presentation/widgets/menu_button_widget.dart @@ -1,12 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/error_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; -import 'package:social_cv_client_flutter/src/utils/navigation.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class MenuButton extends StatelessWidget { final String _tag = '$MenuButton'; @@ -21,11 +17,11 @@ class MenuButton extends StatelessWidget { @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); return Padding( padding: padding, - child: BlocBuilder( + child: BlocBuilder( bloc: BlocProvider.of(context), builder: (BuildContext context, AuthenticationState state) { if (state is AuthenticationAuthenticated) @@ -39,16 +35,16 @@ class MenuButton extends StatelessWidget { } } -/// ---------------------------------------------------------- -/// ---------------------- Not connected --------------------- -/// ---------------------------------------------------------- +/// ---------------------------------------------------------------------------- +/// Not connected +/// ---------------------------------------------------------------------------- class _MenuButtonNotConnected extends StatelessWidget { final String _tag = '$_MenuButtonNotConnected'; @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); return IconButton( onPressed: () => openMenuBottomSheet(context), icon: Icon(Icons.menu), @@ -56,9 +52,9 @@ class _MenuButtonNotConnected extends StatelessWidget { } } -/// ---------------------------------------------------------- -/// ---------------------- Connected ------------------------- -/// ---------------------------------------------------------- +/// ---------------------------------------------------------------------------- +/// Connected +/// ---------------------------------------------------------------------------- class _MenuButtonConnected extends StatelessWidget { final String _tag = '$_MenuButtonConnected'; @@ -67,19 +63,19 @@ class _MenuButtonConnected extends StatelessWidget { @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); - return BlocBuilder( - bloc: BlocProvider.of(context), - builder: (BuildContext context, AccountState state) { - if (state is AccountUninitialized) { + return BlocBuilder( + bloc: BlocProvider.of(context), + builder: (BuildContext context, IdentityState state) { + if (state is IdentityUninitialized) { return _MenuButtonNotConnected(); - } else if (state is AccountLoading) { + } else if (state is IdentityLoading) { return GestureDetector( onTap: () => openMenuBottomSheet(context), - child: CircularProgressIndicator(), + child: const CircularProgressIndicator(), ); - } else if (state is AccountLoaded) { + } else if (state is IdentityLoaded) { return IconButton( onPressed: () => openMenuBottomSheet(context), icon: InitialCircleAvatar( @@ -87,10 +83,10 @@ class _MenuButtonConnected extends StatelessWidget { backgroundImage: NetworkImage(state.user.picture), ), ); - } else if (state is AccountFailed) { + } else if (state is IdentityFailed) { return IconButton( onPressed: () => openMenuBottomSheet(context), - icon: ErrorIcon(), + icon: const ErrorIcon(), ); } return ErrorText(error: NotImplementedYetError()); diff --git a/lib/src/ui/widgets/profile_image_widget.dart b/lib/src/presentation/widgets/profile_image_widget.dart similarity index 58% rename from lib/src/ui/widgets/profile_image_widget.dart rename to lib/src/presentation/widgets/profile_image_widget.dart index 07d7a1e..cfd7661 100644 --- a/lib/src/ui/widgets/profile_image_widget.dart +++ b/lib/src/presentation/widgets/profile_image_widget.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/initial_circle_avatar_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; class ProfileImage extends StatelessWidget { final String _tag = '$ProfileImage'; @@ -15,12 +14,12 @@ class ProfileImage extends StatelessWidget { @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); return Material( - borderRadius: new BorderRadius.circular(75.0), + borderRadius: BorderRadius.circular(75.0), elevation: 2.0, - child: InitialCircleAvatar(), + child: const InitialCircleAvatar(), ); } } diff --git a/lib/src/ui/widgets/register_form_widget.dart b/lib/src/presentation/widgets/register_form_widget.dart similarity index 73% rename from lib/src/ui/widgets/register_form_widget.dart rename to lib/src/presentation/widgets/register_form_widget.dart index 0688fc2..6bf0c89 100644 --- a/lib/src/ui/widgets/register_form_widget.dart +++ b/lib/src/presentation/widgets/register_form_widget.dart @@ -2,8 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; +import 'package:social_cv_client_flutter/src/presentation/commons/styles.dart'; class RegisterForm extends StatefulWidget { @override @@ -22,17 +23,17 @@ class _RegisterFormState extends State { bool _obscureTextSignUp = true; bool _obscureTextSignUpConfirm = true; - TextEditingController signUpEmailController = new TextEditingController(); + TextEditingController signUpEmailController = TextEditingController(); - TextEditingController signUpFirstNameController = new TextEditingController(); - TextEditingController signUpLastNameController = new TextEditingController(); - TextEditingController signUpPasswordController = new TextEditingController(); + TextEditingController signUpFirstNameController = TextEditingController(); + TextEditingController signUpLastNameController = TextEditingController(); + TextEditingController signUpPasswordController = TextEditingController(); TextEditingController signUpConfirmPasswordController = - new TextEditingController(); + TextEditingController(); @override void dispose() { - print('$_tag:$dispose()'); + print('$_tag:dispose()'); myFocusNodeFirstName.dispose(); myFocusNodeLastName.dispose(); @@ -43,11 +44,11 @@ class _RegisterFormState extends State { @override Widget build(BuildContext context) { - print('$_tag:$build'); - RegisterBloc _registerBloc = BlocProvider.of(context); + print('$_tag:build'); + final RegisterBloc _registerBloc = BlocProvider.of(context); void _registerPressed() { - _registerBloc.dispatch(RegisterButtonPressed( + _registerBloc.dispatch(RegistrationEvent( email: signUpEmailController.text, password: signUpPasswordController.text, fName: signUpFirstNameController.text, @@ -55,19 +56,19 @@ class _RegisterFormState extends State { )); } - return BlocListener( + return BlocListener( bloc: _registerBloc, listener: (BuildContext context, RegisterState state) { if (state is RegisterFailure) { Scaffold.of(context).showSnackBar( SnackBar( backgroundColor: AppStyles.errorColor, - content: Text('${state.error.runtimeType}'), + content: ErrorRow(error: state.error), ), ); } }, - child: BlocBuilder( + child: BlocBuilder( bloc: _registerBloc, builder: (BuildContext context, RegisterState state) { return Card( @@ -79,7 +80,9 @@ class _RegisterFormState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Center(child: Text('Register')), + Center( + child: Text(CVLocalizations.of(context).authRegisterTitle), + ), Padding( padding: AppStyles.defaultFormInputPadding, child: TextFormField( @@ -102,8 +105,8 @@ class _RegisterFormState extends State { controller: signUpEmailController, keyboardType: TextInputType.emailAddress, decoration: InputDecoration( - hintText: 'someone@email.com', - labelText: 'Email', + hintText: CVLocalizations.of(context).formEmailHint, + labelText: CVLocalizations.of(context).formEmailLabel, ), ), ), @@ -117,9 +120,10 @@ class _RegisterFormState extends State { icon: Icon(_obscureTextSignUp ? MdiIcons.eyeOffOutline : MdiIcons.eyeOutline), - onPressed: _toggleSignup, + onPressed: _togglePasswordVisibility, ), - labelText: 'Password', + labelText: + CVLocalizations.of(context).formPasswordLabel, ), ), ), @@ -133,14 +137,15 @@ class _RegisterFormState extends State { icon: Icon(_obscureTextSignUpConfirm ? MdiIcons.eyeOffOutline : MdiIcons.eyeOutline), - onPressed: _toggleSignupConfirm, + onPressed: _togglePasswordConfirmationVisibility, ), - labelText: 'Password Confirm', + labelText: + CVLocalizations.of(context).formPassword2Label, ), ), ), MaterialButton( - child: Text('REGISTER'), + child: Text(CVLocalizations.of(context).authRegisterCTA), onPressed: state is! RegisterLoading ? _registerPressed : null, ), @@ -153,13 +158,13 @@ class _RegisterFormState extends State { ); } - void _toggleSignup() { + void _togglePasswordVisibility() { setState(() { _obscureTextSignUp = !_obscureTextSignUp; }); } - void _toggleSignupConfirm() { + void _togglePasswordConfirmationVisibility() { setState(() { _obscureTextSignUpConfirm = !_obscureTextSignUpConfirm; }); diff --git a/lib/src/ui/widgets/repository_provider.dart b/lib/src/presentation/widgets/repository_provider.dart similarity index 100% rename from lib/src/ui/widgets/repository_provider.dart rename to lib/src/presentation/widgets/repository_provider.dart diff --git a/lib/src/ui/widgets/sort_box_widget.dart b/lib/src/presentation/widgets/sort_box_widget.dart similarity index 100% rename from lib/src/ui/widgets/sort_box_widget.dart rename to lib/src/presentation/widgets/sort_box_widget.dart diff --git a/lib/src/ui/widgets/sort_dialog_widget.dart b/lib/src/presentation/widgets/sort_dialog_widget.dart similarity index 80% rename from lib/src/ui/widgets/sort_dialog_widget.dart rename to lib/src/presentation/widgets/sort_dialog_widget.dart index a718694..fd7ac62 100644 --- a/lib/src/ui/widgets/sort_dialog_widget.dart +++ b/lib/src/presentation/widgets/sort_dialog_widget.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_list_tile_widget.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/presentation/commons/styles.dart'; +import 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization.dart'; +import 'package:social_cv_client_flutter/src/presentation/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/sort_list_tile_widget.dart'; class SortDialog extends StatefulWidget { const SortDialog({ @@ -26,7 +26,7 @@ class _SortDialogState extends State { @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); final _listTiles = widget.sortItems .map((sortItem) => SortListTile( diff --git a/lib/src/ui/widgets/sort_list_tile_widget.dart b/lib/src/presentation/widgets/sort_list_tile_widget.dart similarity index 93% rename from lib/src/ui/widgets/sort_list_tile_widget.dart rename to lib/src/presentation/widgets/sort_list_tile_widget.dart index 22d2856..add7ea0 100644 --- a/lib/src/ui/widgets/sort_list_tile_widget.dart +++ b/lib/src/presentation/widgets/sort_list_tile_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/sort_box_widget.dart'; +import 'package:social_cv_client_flutter/src/presentation/widgets/sort_box_widget.dart'; class SortListItem { SortListItem({ diff --git a/lib/src/ui/widgets/splash_widget.dart b/lib/src/presentation/widgets/splash_widget.dart similarity index 78% rename from lib/src/ui/widgets/splash_widget.dart rename to lib/src/presentation/widgets/splash_widget.dart index 03a96a6..6a11bcc 100644 --- a/lib/src/ui/widgets/splash_widget.dart +++ b/lib/src/presentation/widgets/splash_widget.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; +import 'package:social_cv_client_flutter/src/presentation/commons/styles.dart'; +import 'package:social_cv_client_flutter/src/presentation/utils/logger.dart'; class SplashPage extends StatelessWidget { final String _tag = '$SplashPage'; @override Widget build(BuildContext context) { - Logger.log('$_tag:$build'); + Logger.log('$_tag:build'); return Scaffold( backgroundColor: AppStyles.primaryColor, diff --git a/lib/src/presentation/widgets/theme_switch_tile_widget.dart b/lib/src/presentation/widgets/theme_switch_tile_widget.dart new file mode 100644 index 0000000..214b5e5 --- /dev/null +++ b/lib/src/presentation/widgets/theme_switch_tile_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization.dart'; + +class ThemeSwitchTile extends StatelessWidget { + const ThemeSwitchTile({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final AppBloc _appBloc = BlocProvider.of(context); + + return BlocBuilder( + bloc: _appBloc, + builder: (BuildContext context, AppState state) { + if (state is AppInitialized) { + final bool darkMode = state.darkMode; + return SwitchListTile( + secondary: Icon( + darkMode ? MdiIcons.weatherSunny : MdiIcons.whiteBalanceSunny, + ), + title: Text(CVLocalizations.of(context).settingsDarkModeCTA), + value: darkMode, + onChanged: (bool newValue) => + _appBloc.dispatch(AppThemeChanged(darkMode: newValue)), + ); + } + return Container(child: Text('${state.runtimeType} state unhandled')); + }, + ); + } +} diff --git a/lib/src/ui/localizations/cv_localization.dart b/lib/src/ui/localizations/cv_localization.dart deleted file mode 100644 index 603aed5..0000000 --- a/lib/src/ui/localizations/cv_localization.dart +++ /dev/null @@ -1,312 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization_en.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization_fr.dart'; - -abstract class CVLocalizations { - static CVLocalizations of(BuildContext context) { - return Localizations.of(context, CVLocalizations); - } - - /// App - /// - String get appName; - - /// Auth Page - - String get authTitle; - - /// Auth Stuff - - String get authNoEmailTitle; - - String get authNotEmailExplain; - - String get authNoEmailExplain; - - String get authNoPasswordTitle; - - String get authNotPasswordExplain; - - String get authNoPasswordExplain; - - String get authCreateYourAccount; - - String get authSignUp; - - String get authSignUpCTA; - - String get authSignUpSucceed; - - String get authSignUpFailed; - - String get authSignIn; - - String get authSignInCTA; - - String get authSignInGoogleCTA; - - String get authSignInFacebookCTA; - - String get authSignInSucceed; - - String get authSignInFailed; - - String get authLogout; - - String get authLogoutCTA; - - String get authLogoutSucceed; - - String get authLogoutFailed; - - String get authPrivacyExplain; - - String get authPrivacyReadCTA; - - String get authAlreadyHaveAccountCTA; - - String get authForgotPasswordCTA; - - String get authNoAccountCTA; - - String get authOr; - - /// Home Page - String get homeTitle; - - String get homeCTA; - - String get homeWelcome; - - /// Account Page - - String get accountTitle; - - String get accountCTA; - - String get accountMyProfile; - - /// Profile Page - - String get profileTitle; - - /// Settings Page - - String get settingsTitle; - - String get settingsThemeCTA; - - String get settingsThemeDefault; - - String get settingsThemeLight; - - String get settingsThemeDark; - - /// Search Page - - String get searchTitle; - - String get searchSearchBarHint; - - /// Profile Widget - - String get profileWidgetDetails; - - /// Profile Widget List - - String get profileListOptions; - - String get profileListSorting; - - String get profileListItemPerPage; - - String get profileListLoadMore; - - /// Part Widget - String get partWidgetDetails; - - /// Part Widget List - String get partListOptions; - - String get partListSorting; - - String get partListItemPerPage; - - String get partListLoadMore; - - /// Group Widget - - String get groupWidgetDetails; - - /// Group Widget List - - String get groupListOptions; - - String get groupListSorting; - - String get groupListItemPerPage; - - String get groupListLoadMore; - - /// Entry Widget - - String get entryWidgetDetails; - - /// Entry Widget List - - String get entryListOptions; - - String get entryListSorting; - - String get entryListItemPerPage; - - String get entryListLoadMore; - - /// Sort Dialog - - String get sortDialogCancel; - - String get sortDialogConfirm; - - /// Menu Widget - - String get menuPPCTA; - - String get menuToSCTA; - - /// Others - - String get middleDot; - - String get username; - - String get email; - - String get password; - - String get passwordRepeat; - - String get token; - - String get cancelCTA; - - String get settingsCTA; - - String get account; - - String get home; - - String get resume; - - String get profile; - - String get search; - - String get history; - - String get loadMore; - - String get errorOccurred; - - String get retryCTA; - - String get yesCTA; - - String get noCTA; - - String get moreCTA; - - String get notYetImplemented; - - String get notSupported; - - /// Exception Error - - String get exceptionFormatException; - - String get exceptionTimeoutException; - - /// Api Error - - String get apiErrorWrongPasswordError; - - String get apiErrorUserNotFoundError; - - /// Server Error : HTTP 400 - - String get httpClientErrorBadRequest; - - String get httpClientErrorPaymentRequired; - - String get httpClientErrorForbidden; - - String get httpClientErrorNotFound; - - String get httpClientErrorMethodNotAllowed; - - String get httpClientErrorNotAcceptable; - - String get httpClientErrorRequestTimeout; - - String get httpClientErrorConflict; - - String get httpClientErrorGone; - - String get httpClientErrorLengthRequired; - - String get httpClientErrorPayloadTooLarge; - - String get httpClientErrorURITooLong; - - String get httpClientErrorUnsupportedMediaType; - - String get httpClientErrorExpectationFailed; - - String get httpClientErrorUpgradeRequired; - - ///Server Error : HTTP 500 - - String get httpServerErrorInternalServerError; - - String get httpServerErrorNotImplemented; - - String get httpServerErrorBadGateway; - - String get httpServerErrorServiceUnavailable; - - String get httpServerErrorGatewayTimeout; - - String get httpServerErrorHttpVersionNotSupported; -} - -class CVLocalizationsDelegate extends LocalizationsDelegate { - const CVLocalizationsDelegate(); - - @override - bool isSupported(Locale locale) => ['en', 'fr'].contains(locale.languageCode); - - @override - Future load(Locale locale) { - final String name = - (locale.countryCode == null || locale.countryCode.isEmpty) - ? locale.languageCode - : locale.toString(); - final String localeName = Intl.canonicalizedLocale(name); - Intl.defaultLocale = localeName; - - if (locale.languageCode == 'fr') { - return CVLocalizationsFR.load(locale); - } else { - return CVLocalizationsEN.load(locale); - } - } - - @override - bool shouldReload(CVLocalizationsDelegate old) => false; - - @override - String toString() => 'DefaultCVLocalizations.delegate(en_US)'; -} diff --git a/lib/src/ui/localizations/cv_localization_en.dart b/lib/src/ui/localizations/cv_localization_en.dart deleted file mode 100644 index 13ec191..0000000 --- a/lib/src/ui/localizations/cv_localization_en.dart +++ /dev/null @@ -1,405 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; - -class CVLocalizationsEN implements CVLocalizations { - const CVLocalizationsEN(); - - /// App - - @override - String get appName => 'Social CV'; - - /// Auth Page - - @override - String get authTitle => 'Connection'; - - @override - String get authNoEmailTitle => 'Empty email'; - - @override - String get authNotEmailExplain => 'Please enter a real e-mail.'; - - @override - String get authNoEmailExplain => 'Please provide an email'; - - @override - String get authNoPasswordTitle => 'Empty password'; - - @override - String get authNoPasswordExplain => 'Please provide a password'; - - @override - String get authCreateYourAccount => 'Create your account'; - - @override - String get authPrivacyExplain => - 'Like privacy? We feel you. We don’t use or sell your data.'; - - @override - String get authPrivacyReadCTA => 'Touch to read our privacy policy.'; - - @override - String get authAlreadyHaveAccountCTA => 'Already have an account? Sign-in'; - - @override - String get authNotPasswordExplain => 'This is not a password'; - - @override - String get authSignUp => 'Register'; - - @override - String get authSignUpCTA => 'Sign-up'; - - @override - String get authSignUpFailed => 'Registration failed!'; - - @override - String get authSignUpSucceed => 'Registration succeed!'; - - @override - String get authSignIn => 'Sign-in'; - - @override - String get authSignInCTA => 'Sign-in'; - - @override - String get authSignInGoogleCTA => 'Sign-in with Google'; - - @override - String get authSignInFacebookCTA => 'Sign-in with Facebook'; - - @override - String get authSignInSucceed => 'Login succeed!'; - - @override - String get authSignInFailed => 'Login failed!'; - - @override - String get authLogout => 'Logout'; - - @override - String get authLogoutCTA => 'Logout'; - - @override - String get authLogoutFailed => 'Logout failed!'; - - @override - String get authLogoutSucceed => 'Logout succeed!'; - - @override - String get authForgotPasswordCTA => 'Forgot password?'; - - @override - String get authNoAccountCTA => 'Don\'t have an account yet? Sign-up'; - - @override - String get authOr => 'OR'; - - /// Home Page - - @override - String get homeTitle => 'Social CV'; - - @override - String get homeCTA => 'Home'; - - @override - String get homeWelcome => 'Welcome on our new resume social network !'; - - /// Account Page - - @override - String get accountTitle => 'Account'; - - @override - String get accountCTA => 'Account'; - - @override - String get accountMyProfile => 'My profiles'; - - /// Profile Page - - @override - String get profileTitle => 'Profile'; - - /// Settings Page - - @override - String get settingsTitle => 'Settings'; - - @override - String get settingsThemeCTA => 'Dark Mode'; - - @override - String get settingsThemeDefault => 'Default'; - - @override - String get settingsThemeLight => 'Light'; - - @override - String get settingsThemeDark => 'Dark'; - - /// Search Page - - @override - String get searchTitle => 'Search'; - - @override - String get searchSearchBarHint => 'Search resume...'; - - /// Profile Widget - - String get profileWidgetDetails => 'Profile details'; - - /// Profile Widget List - - String get profileListOptions => 'Profile options'; - - String get profileListSorting => 'Sorting Profiles'; - - String get profileListItemPerPage => 'Profile per page'; - - String get profileListLoadMore => 'Load more profiles'; - - /// Part Widget - - String get partWidgetDetails => 'Part détails'; - - /// Part Widget List - - @override - String get partListOptions => 'Part list options'; - - @override - String get partListSorting => 'Sorting parts'; - - @override - String get partListItemPerPage => 'Parts per page'; - - @override - String get partListLoadMore => 'Load more parts'; - - /// Group Widget - - String get groupWidgetDetails => 'Group détails'; - - /// Group Widget List - - @override - String get groupListOptions => 'Group list options'; - - @override - String get groupListSorting => 'Sorting groups'; - - @override - String get groupListItemPerPage => 'Groups per page'; - - @override - String get groupListLoadMore => 'Load more groups'; - - /// Entry Widget - - String get entryWidgetDetails => 'Entry détails'; - - /// Entry Widget List - - @override - String get entryListOptions => 'Entry list options'; - - @override - String get entryListSorting => 'Sorting entries'; - - @override - String get entryListItemPerPage => 'Entries per page'; - - @override - String get entryListLoadMore => 'Load more entries'; - - /// Sort Dialog - @override - String get sortDialogCancel => 'Cancel'; - - @override - String get sortDialogConfirm => 'Confirm'; - - /// Menu Widget - - @override - String get menuPPCTA => 'Privacy Policy'; - - @override - String get menuToSCTA => 'Terms of Service'; - - /// Others - - @override - String get middleDot => '·'; - - @override - String get username => 'Username'; - - @override - String get email => 'Email'; - - @override - String get password => 'Password'; - - @override - String get passwordRepeat => 'Repeat Password'; - - @override - String get token => 'Token'; - - @override - String get cancelCTA => 'Cancel'; - - @override - String get settingsCTA => 'Settings'; - - @override - String get account => 'Account'; - - @override - String get home => 'Home'; - - @override - String get resume => 'Resume'; - - @override - String get profile => 'Profile'; - - @override - String get search => 'Search'; - - @override - String get history => 'History'; - - @override - String get loadMore => 'Load more'; - - @override - String get errorOccurred => 'An error occurred'; - - @override - String get retryCTA => 'Retry'; - - @override - String get yesCTA => 'Yes'; - - @override - String get noCTA => 'No'; - - @override - String get moreCTA => 'More'; - - @override - String get notYetImplemented => 'Not yet implemented'; - - @override - String get notSupported => 'Not supported'; - - /// Exception Error - @override - String get exceptionFormatException => 'Exception : Wrong Format'; - - @override - String get exceptionTimeoutException => 'Exception : Request Timeout'; - - /// Api Error - - @override - String get apiErrorWrongPasswordError => 'Wrong password'; - - @override - String get apiErrorUserNotFoundError => 'User not found'; - - /// Server Error : HTTP 400 - - @override - String get httpClientErrorBadRequest => 'Bad request'; - - @override - String get httpClientErrorPaymentRequired => 'Payment required'; - - @override - String get httpClientErrorForbidden => 'Forbidden'; - - @override - String get httpClientErrorNotFound => 'Not found'; - - @override - String get httpClientErrorMethodNotAllowed => 'Not allowed'; - - @override - String get httpClientErrorNotAcceptable => 'Not acceptable'; - - @override - String get httpClientErrorRequestTimeout => 'Request timeout'; - - @override - String get httpClientErrorConflict => 'Conflict'; - - @override - String get httpClientErrorGone => 'Gone'; - - @override - String get httpClientErrorLengthRequired => 'Length required'; - - @override - String get httpClientErrorPayloadTooLarge => 'Payload too large'; - - @override - String get httpClientErrorURITooLong => 'URI too long'; - - @override - String get httpClientErrorUnsupportedMediaType => 'Unsupported media type'; - - @override - String get httpClientErrorExpectationFailed => 'Expectation Failed'; - - @override - String get httpClientErrorUpgradeRequired => 'Upgrade required'; - - /// Server Error : HTTP 500 - - @override - String get httpServerErrorInternalServerError => 'Internal Server Error'; - - @override - String get httpServerErrorNotImplemented => 'Not implemented'; - - @override - String get httpServerErrorBadGateway => 'Bad Gateway'; - - @override - String get httpServerErrorServiceUnavailable => 'Service Unavailable'; - - @override - String get httpServerErrorGatewayTimeout => 'Gateway Timeout'; - - @override - String get httpServerErrorHttpVersionNotSupported => - 'HTTP Version Not Supported'; - - /// Creates an object that provides US English resource values for the - /// application. - /// - /// The [locale] parameter is ignored. - /// - /// This method is typically used to create a [LocalizationsDelegate]. - /// The [MaterialApp] does so by default. - static Future load(Locale locale) { - return SynchronousFuture(const CVLocalizationsEN()); - } - - /// A [LocalizationsDelegate] that uses [CVLocalizationsEN.load] - /// to create an instance of this class. - /// - /// [MaterialApp] automatically adds this value to [MaterialApp.localizationsDelegates]. - static const LocalizationsDelegate delegate = - CVLocalizationsDelegate(); -} diff --git a/lib/src/ui/localizations/cv_localization_fr.dart b/lib/src/ui/localizations/cv_localization_fr.dart deleted file mode 100644 index 02bc064..0000000 --- a/lib/src/ui/localizations/cv_localization_fr.dart +++ /dev/null @@ -1,410 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; - -class CVLocalizationsFR implements CVLocalizations { - const CVLocalizationsFR(); - - /// App - - @override - String get appName => 'Social CV'; - - /// Auth Page - - @override - String get authTitle => 'Connexion'; - - @override - String get authNoEmailTitle => 'E-mail vide'; - - @override - String get authNotEmailExplain => 'Merci de renseigner un e-mail existant.'; - - @override - String get authNoEmailExplain => 'Merci de renseigner un e-mail'; - - @override - String get authNoPasswordTitle => 'Mot de passe vide'; - - @override - String get authNoPasswordExplain => 'Merci de renseigner un mot de passe'; - - @override - String get authCreateYourAccount => 'Créez votre compte'; - - @override - String get authSignUp => 'Inscription'; - - @override - String get authSignUpCTA => 'S\'inscrire'; - - @override - String get authSignUpFailed => 'Inscription échoué !'; - - @override - String get authSignUpSucceed => 'Inscription réussite !'; - - @override - String get authSignIn => 'Connectez vous avec votre compte'; - - @override - String get authSignInCTA => 'Se connecter'; - - @override - String get authSignInGoogleCTA => 'Se connecter avec Google'; - - @override - String get authSignInFacebookCTA => 'Se connecter avec Facebook'; - - @override - String get authSignInFailed => 'Connexion échoué !'; - - @override - String get authLogout => 'Se déconnecter'; - - @override - String get authLogoutCTA => 'Se déconnecter'; - - @override - String get authLogoutFailed => 'Déconnexion échoué !'; - - @override - String get authLogoutSucceed => 'Déconnexion réussite !'; - - @override - String get authPrivacyExplain => - 'Vous aimez votre vie privée ? Nous le savons. Nous n\'utilisons, ni ' - 'vendons vos données.'; - - @override - String get authPrivacyReadCTA => - 'Touchez ici pour lire notre politique de confidentialité.'; - - @override - String get authAlreadyHaveAccountCTA => - 'Vous avez déjà un compte ? Connectez-vous'; - - @override - String get authForgotPasswordCTA => 'Mot de passe oublié ?'; - - @override - String get authNoAccountCTA => 'Vous n\'avez pas de compte ? Inscrivez-vous'; - - @override - String get authNotPasswordExplain => 'Ceci n\'est pas un mot de passe'; - - @override - String get authOr => 'OU'; - - /// Home Page - - @override - String get homeTitle => 'Social CV'; - - @override - String get homeCTA => 'Accueil'; - - @override - String get homeWelcome => 'Bienvenue sur notre nouveau réseau social de CV !'; - - /// Account Page - - @override - String get accountTitle => 'Compte'; - - @override - String get accountCTA => 'Compte'; - - @override - String get accountMyProfile => 'Mes profils'; - - /// Profile Page - - @override - String get profileTitle => 'Profil'; - - /// Settings Pages - - @override - String get settingsTitle => 'Paramètres'; - - @override - String get settingsCTA => 'Paramètres'; - - @override - String get settingsThemeCTA => 'Mode Sombre'; - - @override - String get settingsThemeDefault => 'Défaut'; - - @override - String get settingsThemeLight => 'Claire'; - - @override - String get settingsThemeDark => 'Sombre'; - - /// Search Page - - @override - String get searchTitle => 'Recherche'; - - @override - String get searchSearchBarHint => 'Rechercher un profil ...'; - - /// Profile Widget - - String get profileWidgetDetails => 'Détails du profile'; - - /// Profile Widget List - - String get profileListOptions => 'Options profiles'; - - String get profileListSorting => 'Trier Profiles'; - - String get profileListItemPerPage => 'Profils par page'; - - String get profileListLoadMore => 'Charger plus de profiles'; - - /// Part Widget - - String get partWidgetDetails => 'Détails de la partie'; - - /// Part Widget List - - @override - String get partListOptions => 'Options'; - - @override - String get partListSorting => 'Trier les parties'; - - @override - String get partListItemPerPage => 'Parties par page'; - - @override - String get partListLoadMore => 'charger plus de parties'; - - /// Group Widget - - String get groupWidgetDetails => 'Détails du groupe'; - - /// Group Widget List - - @override - String get groupListOptions => 'Options'; - - @override - String get groupListSorting => 'Trier les groupes'; - - @override - String get groupListItemPerPage => 'Groupes par page'; - - @override - String get groupListLoadMore => 'charger plus de groupes'; - - /// Entry Widget - - String get entryWidgetDetails => 'Détails de l\'entrée'; - - /// Entry Widget List - - @override - String get entryListOptions => 'Options'; - - @override - String get entryListSorting => 'Trier les entrées'; - - @override - String get entryListItemPerPage => 'Entrées par page'; - - @override - String get entryListLoadMore => 'Charger plus de entrées'; - - /// Sort Dialog - - @override - String get sortDialogCancel => 'Annuler'; - - @override - String get sortDialogConfirm => 'Valider'; - - /// Menu Widget - - @override - String get menuPPCTA => 'Politique de confidentialité'; - - @override - String get menuToSCTA => 'Termes de Service'; - - /// Others - @override - String get middleDot => '·'; - - @override - String get username => 'Nom d\'utilisateur'; - - @override - String get email => 'Email'; - - @override - String get password => 'Mot de passe'; - - @override - String get passwordRepeat => 'Password répété'; - - @override - String get token => 'Jeton'; - - @override - String get cancelCTA => 'Annuler'; - - @override - String get account => 'Compte'; - - @override - String get home => 'Accueil'; - - @override - String get resume => 'CV'; - - @override - String get profile => 'Profil'; - - @override - String get search => 'Rechercher'; - - @override - String get history => 'Historique'; - - @override - String get loadMore => 'Charger plus'; - - @override - String get errorOccurred => 'Une erreur s\'est produite'; - - @override - String get retryCTA => 'Re-éssayer'; - - @override - String get yesCTA => 'Oui'; - - @override - String get noCTA => 'Non'; - - @override - String get authSignInSucceed => 'Connecté'; - - @override - String get moreCTA => 'Plus'; - - @override - String get notYetImplemented => 'Pas encore implémenté'; - - @override - String get notSupported => 'Non supporté'; - - /// Exception Error - - @override - String get exceptionFormatException => 'Exception : Mauvais Format'; - - @override - String get exceptionTimeoutException => 'Exception : Requete Expiré'; - - /// Api Error - - @override - String get apiErrorWrongPasswordError => 'Mauvais mot de passe'; - - @override - String get apiErrorUserNotFoundError => 'Utilisateur introuvable'; - - /// Server Error : HTTP 400 - - @override - String get httpClientErrorBadRequest => 'Mauvaise requete'; - - @override - String get httpClientErrorPaymentRequired => 'Paiement requis'; - - @override - String get httpClientErrorForbidden => 'Interdit'; - - @override - String get httpClientErrorNotFound => 'Introuvable'; - - @override - String get httpClientErrorMethodNotAllowed => 'Non autorisé'; - - @override - String get httpClientErrorNotAcceptable => 'Non acceptable'; - - @override - String get httpClientErrorRequestTimeout => 'Requete expirée'; - - @override - String get httpClientErrorConflict => 'Conflit'; - - @override - String get httpClientErrorGone => 'Disparu'; - - @override - String get httpClientErrorLengthRequired => 'Taille requise'; - - @override - String get httpClientErrorPayloadTooLarge => 'Payload trop large'; - - @override - String get httpClientErrorURITooLong => 'Lien trop long'; - - @override - String get httpClientErrorUnsupportedMediaType => - 'Type de média non supporté'; - - @override - String get httpClientErrorExpectationFailed => 'Échoué'; - - @override - String get httpClientErrorUpgradeRequired => 'Mise à jour requise'; - - /// Server Error : HTTP 500 - - @override - String get httpServerErrorInternalServerError => 'Erreur Serveur interne'; - - @override - String get httpServerErrorNotImplemented => 'Non implementé'; - - @override - String get httpServerErrorBadGateway => 'Mauvaise passerelle'; - - @override - String get httpServerErrorServiceUnavailable => 'Service non disponible '; - - @override - String get httpServerErrorGatewayTimeout => 'Temps ecoulé'; - - @override - String get httpServerErrorHttpVersionNotSupported => - 'Version HTTP non supporté'; - - /// Creates an object that provides US English resource values for the - /// application. - /// - /// The [locale] parameter is ignored. - /// - /// This method is typically used to create a [LocalizationsDelegate]. - /// The [MaterialApp] does so by default. - static Future load(Locale locale) { - return SynchronousFuture(const CVLocalizationsFR()); - } - - /// A [LocalizationsDelegate] that uses [CVLocalizationsFR.load] - /// to create an instance of this class. - /// - /// [MaterialApp] automatically adds this value to [MaterialApp.localizationsDelegates]. - static const LocalizationsDelegate delegate = - CVLocalizationsDelegate(); -} diff --git a/lib/src/ui/pages/auth_page.dart b/lib/src/ui/pages/auth_page.dart deleted file mode 100644 index 883d865..0000000 --- a/lib/src/ui/pages/auth_page.dart +++ /dev/null @@ -1,259 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/assets.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/styles.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/login_form_widget.dart'; -import 'package:social_cv_client_flutter/src/ui/widgets/register_form_widget.dart'; - -class AuthPage extends StatefulWidget { - @override - _AuthPageState createState() => _AuthPageState(); -} - -class _AuthPageState extends State { - final String _tag = '$_AuthPageState'; - - // Variable - double screenWidth; - double screenHeight; - - static const _kDuration = const Duration(milliseconds: 300); - - static const _kCurve = Curves.ease; - - PageController _pageController; - - // Business - @override - void initState() { - print('$_tag:$initState()'); - super.initState(); - _pageController = PageController(); - } - - @override - void dispose() { - print('$_tag:$dispose()'); - _pageController?.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - print('$_tag:$build'); - screenHeight = MediaQuery.of(context).size.height; - screenWidth = MediaQuery.of(context).size.width; - - screenHeight = (screenHeight > AppStyles.authPageMinHeight) - ? screenHeight - : AppStyles.authPageMinHeight; - - return Scaffold( - backgroundColor: AppStyles.primaryColor, - body: BlocListenerTree( - blocListeners: [ - BlocListener( - bloc: BlocProvider.of(context), - listener: (BuildContext context, AuthenticationState state) { - if (state is AuthenticationAuthenticated) { - Scaffold.of(context).showSnackBar(SnackBar( - backgroundColor: AppStyles.successColor, - content: Text(CVLocalizations.of(context).authSignInSucceed), - )); - Future.delayed(Duration(seconds: 1)) - .then((_) => Navigator.of(context).pop()); - } - }, - ), - ], - child: SingleChildScrollView( - child: Container( - height: screenHeight, - child: Stack( - children: [ - _buildHeaderSection(context), - _buildAuthSection(context) - ], - ), - ), - ), - ), - ); - } - - Widget _buildHeaderSection(BuildContext context) { - return Container( - height: screenHeight * 0.25, - width: screenWidth, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - Image.asset( - AppAssets.loginLogoImage, - fit: BoxFit.scaleDown, - ), - Text( - CVLocalizations.of(context).authTitle, - style: Theme.of(context) - .textTheme - .title - .copyWith(color: AppStyles.colorWhite), - ), - ], - ), - ); - } - - Widget _buildAuthSection(BuildContext context) { - return Stack( - children: [ - _buildAuthPageView(context), - Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - height: screenHeight * 0.25, - ), - Container( - height: screenHeight * 0.05, - color: Colors.transparent, - child: DotsIndicator( - color: AppStyles.colorWhite, - controller: _pageController, - itemCount: 2, - onPageSelected: (int page) { - _pageController.animateToPage( - page, - duration: _kDuration, - curve: _kCurve, - ); - }, - ), - ), - ], - ), - ], - ); - } - - Widget _buildAuthPageView(BuildContext context) { - return PageView( - controller: _pageController, - children: [ - Column( - children: [ - Container( - height: screenHeight * 0.25, - ), - Container( - height: screenHeight * 0.05, - ), - Container( - height: screenHeight * 0.70, - padding: EdgeInsets.all(10.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - LoginForm(), - ], - ), - ), - ], - ), - Column( - children: [ - Container( - height: screenHeight * 0.25, - ), - Container( - height: screenHeight * 0.05, - ), - Container( - height: screenHeight * 0.70, - padding: EdgeInsets.all(10.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - RegisterForm(), - ], - ), - ), - ], - ), - ], - ); - } -} - -/// An indicator showing the currently selected page of a PageController -class DotsIndicator extends AnimatedWidget { - DotsIndicator({ - this.controller, - this.itemCount, - this.onPageSelected, - this.color: Colors.white, - }) : super(listenable: controller); - - /// The PageController that this DotsIndicator is representing. - final PageController controller; - - /// The number of items managed by the PageController - final int itemCount; - - /// Called when a dot is tapped - final ValueChanged onPageSelected; - - /// The color of the dots. - /// - /// Defaults to `Colors.white`. - final Color color; - - // The base size of the dots - static const double _kDotSize = 8.0; - - // The increase in the size of the selected dot - static const double _kMaxZoom = 2.0; - - // The distance between the center of each dot - static const double _kDotSpacing = 25.0; - - Widget _buildDot(int index) { - double selectedness = Curves.easeOut.transform( - max( - 0.0, - 1.0 - ((controller.page ?? controller.initialPage) - index).abs(), - ), - ); - double zoom = 1.0 + (_kMaxZoom - 1.0) * selectedness; - return new Container( - width: _kDotSpacing, - child: new Center( - child: new Material( - elevation: AppStyles.defaultCardElevation, - color: color, - type: MaterialType.circle, - child: new Container( - width: _kDotSize * zoom, - height: _kDotSize * zoom, - child: new InkWell( - onTap: () => onPageSelected(index), - ), - ), - ), - ), - ); - } - - Widget build(BuildContext context) { - return new Row( - mainAxisAlignment: MainAxisAlignment.center, - children: new List.generate(itemCount, _buildDot), - ); - } -} diff --git a/lib/src/ui/widgets/rounded_modal.dart b/lib/src/ui/widgets/rounded_modal.dart deleted file mode 100644 index 9a0fe31..0000000 --- a/lib/src/ui/widgets/rounded_modal.dart +++ /dev/null @@ -1,298 +0,0 @@ -library rounded_modal; - -import 'dart:async'; - -import 'package:flutter/material.dart'; - -/// Adapted from https://gist.github.com/slightfoot/5af4c5dfa52194a3f8577bf83af2e391 - -/// Below is the usage for this function, you'll only have to import this file -/// [radius] takes a double and will be the radius to the rounded corners of this modal -/// [color] will color the modal itself, the default being `Colors.white` -/// [builder] takes the content of the modal, if you're using [Column] -/// or a similar widget, remember to set `mainAxisSize: MainAxisSize.min` -/// so it will only take the needed space. -/// -/// This newer version also fixes the issue of keyboard overlap based on -/// [this gist](https://gist.github.com/slightfoot/5af4c5dfa52194a3f8577bf83af2e391). -/// -/// ```dart -/// showRoundedModalBottomSheet( -/// context: context, -/// radius: 10.0, /// This is the default -/// color: Colors.white, /// Also default -/// builder: (context) => ???, -/// ); -/// ``` -Future showRoundedModalBottomSheet({ - @required BuildContext context, - @required WidgetBuilder builder, - Color color, - double radius: 10.0, - bool autoResize: true, - bool dismissOnTap: true, -}) { - assert(context != null); - assert(builder != null); - assert(radius != null && radius > 0.0); - assert(color != Colors.transparent); - if (color == null) color = Theme.of(context).canvasColor; - - return Navigator.push( - context, - _RoundedCornerModalBottomSheetRoute( - builder: builder, - color: color, - radius: radius, - autoResize: autoResize, - dismissOnTap: dismissOnTap, - barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, - ), - ); -} - -const Duration _kRoundedBottomSheetDuration = const Duration(milliseconds: 300); -const double _kMinFlingVelocity = 600.0; -const double _kCloseProgressThreshold = 0.5; - -/// A material design modal bottom sheet. -/// -/// * _Modal_. A modal bottom sheet is an alternative to a menu or a dialog and -/// prevents the user from interacting with the rest of the app. Modal bottom -/// sheets can be created and displayed with the [showRoundedModalBottomSheet] -/// function. -/// -/// The [RoundedBottomSheet] widget itself is rarely used directly. Instead, prefer to -/// create a modal bottom sheet with [showRoundedModalBottomSheet]. -/// -/// See also: -/// -/// * [showRoundedModalBottomSheet] -/// * -class RoundedBottomSheet extends StatefulWidget { - /// Creates a bottom sheet. - /// - /// Typically, bottom sheets are created implicitly by - /// [ScaffoldState.showBottomSheet], for persistent bottom sheets, or by - /// [showModalBottomSheet], for modal bottom sheets. - const RoundedBottomSheet( - {Key key, - this.animationController, - @required this.onClosing, - @required this.builder}) - : assert(onClosing != null), - assert(builder != null), - super(key: key); - - /// The animation that controls the bottom sheet's position. - /// - /// The BottomSheet widget will manipulate the position of this animation, it - /// is not just a passive observer. - final AnimationController animationController; - - /// Called when the bottom sheet begins to close. - /// - /// A bottom sheet might be be prevented from closing (e.g., by user - /// interaction) even after this callback is called. For this reason, this - /// callback might be call multiple times for a given bottom sheet. - final VoidCallback onClosing; - - /// A builder for the contents of the sheet. - /// - /// The bottom sheet will wrap the widget produced by this builder in a - /// [Material] widget. - final WidgetBuilder builder; - - @override - _RoundedBottomSheetState createState() => _RoundedBottomSheetState(); - - /// Creates an animation controller suitable for controlling a [RoundedBottomSheet]. - static AnimationController createAnimationController(TickerProvider vsync) { - return AnimationController( - duration: _kRoundedBottomSheetDuration, - debugLabel: 'RoundedBottomSheet', - vsync: vsync, - ); - } -} - -class _RoundedBottomSheetState extends State { - final GlobalKey _childKey = GlobalKey(debugLabel: 'RoundedBottomSheet child'); - - double get _childHeight { - final RenderBox renderBox = _childKey.currentContext.findRenderObject(); - return renderBox.size.height; - } - - bool get _dismissUnderway => - widget.animationController.status == AnimationStatus.reverse; - - void _handleDragUpdate(DragUpdateDetails details) { - if (_dismissUnderway) return; - widget.animationController.value -= - details.primaryDelta / (_childHeight ?? details.primaryDelta); - } - - void _handleDragEnd(DragEndDetails details) { - if (_dismissUnderway) return; - if (details.velocity.pixelsPerSecond.dy > _kMinFlingVelocity) { - final double flingVelocity = - -details.velocity.pixelsPerSecond.dy / _childHeight; - if (widget.animationController.value > 0.0) - widget.animationController.fling(velocity: flingVelocity); - if (flingVelocity < 0.0) widget.onClosing(); - } else if (widget.animationController.value < _kCloseProgressThreshold) { - if (widget.animationController.value > 0.0) - widget.animationController.fling(velocity: -1.0); - widget.onClosing(); - } else { - widget.animationController.forward(); - } - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onVerticalDragUpdate: _handleDragUpdate, - onVerticalDragEnd: _handleDragEnd, - child: Material( - key: _childKey, - child: widget.builder(context), - ), - ); - } -} - -class _RoundedModalBottomSheetLayout extends SingleChildLayoutDelegate { - _RoundedModalBottomSheetLayout(this.bottomInset, this.progress); - - final double bottomInset; - final double progress; - - @override - BoxConstraints getConstraintsForChild(BoxConstraints constraints) { - return BoxConstraints( - minWidth: constraints.maxWidth, - maxWidth: constraints.maxWidth, - minHeight: 0.0, - maxHeight: constraints.maxHeight * 9.0 / 16.0); - } - - @override - Offset getPositionForChild(Size size, Size childSize) { - return Offset(0.0, size.height - bottomInset - childSize.height * progress); - } - - @override - bool shouldRelayout(_RoundedModalBottomSheetLayout oldDelegate) { - return progress != oldDelegate.progress || - bottomInset != oldDelegate.bottomInset; - } -} - -class _RoundedCornerModalBottomSheetRoute extends PopupRoute { - _RoundedCornerModalBottomSheetRoute({ - this.builder, - this.barrierLabel, - this.color, - this.radius, - this.autoResize: false, - this.dismissOnTap: true, - RouteSettings settings, - }) : super(settings: settings); - - final WidgetBuilder builder; - final double radius; - final Color color; - final bool autoResize; - final bool dismissOnTap; - - @override - Duration get transitionDuration => _kRoundedBottomSheetDuration; - - @override - Color get barrierColor => Colors.black54; - - @override - bool get barrierDismissible => true; - - @override - bool get opaque => false; - - @override - bool get maintainState => false; - - @override - String barrierLabel; - - AnimationController animationController; - - @override - AnimationController createAnimationController() { - assert(animationController == null); - animationController = - BottomSheet.createAnimationController(navigator.overlay); - return animationController; - } - - @override - Widget buildPage(BuildContext context, Animation animation, - Animation secondaryAnimation) { - return MediaQuery.removePadding( - context: context, - removeTop: true, - child: Theme( - data: Theme.of(context).copyWith(canvasColor: Colors.transparent), - child: _RoundedModalBottomSheet(route: this), - ), - ); - } -} - -class _RoundedModalBottomSheet extends StatefulWidget { - const _RoundedModalBottomSheet({Key key, this.route}) : super(key: key); - - final _RoundedCornerModalBottomSheetRoute route; - - @override - _RoundedModalBottomSheetState createState() => - _RoundedModalBottomSheetState(); -} - -class _RoundedModalBottomSheetState - extends State<_RoundedModalBottomSheet> { - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: widget.route.dismissOnTap ? () => Navigator.pop(context) : null, - child: AnimatedBuilder( - animation: widget.route.animation, - builder: (context, child) { - return CustomSingleChildLayout( - delegate: _RoundedModalBottomSheetLayout( - widget.route.autoResize - ? MediaQuery.of(context).viewInsets.bottom - : 0.0, - widget.route.animation.value), - child: RoundedBottomSheet( - animationController: widget.route.animationController, - onClosing: () => Navigator.pop(context), - builder: (context) => Container( - decoration: BoxDecoration( - color: widget.route.color, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(widget.route.radius), - topRight: Radius.circular(widget.route.radius), - ), - ), - child: SafeArea( - child: Builder(builder: widget.route.builder), - ), - ), - ), - ); - }, - ), - ); - } -} diff --git a/lib/src/ui/widgets/theme_switch_list_tile_widget.dart b/lib/src/ui/widgets/theme_switch_list_tile_widget.dart deleted file mode 100644 index 9e0a89d..0000000 --- a/lib/src/ui/widgets/theme_switch_list_tile_widget.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:social_cv_client_dart_common/blocs.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; -import 'package:social_cv_client_flutter/src/utils/logger.dart'; - -class ThemeSwitchListTile extends StatelessWidget { - final String _tag = '$ThemeSwitchListTile'; - - ThemeSwitchListTile({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - Logger.log('$_tag:$build'); - - AppBloc _appBloc = BlocProvider.of(context); - - return BlocBuilder( - bloc: _appBloc, - builder: (BuildContext context, AppState state) { - if (state is AppLoading || state is AppInitialized) { - bool value; - Widget leadingWidget; - Widget trailingWidget; - - final function = (bool enable) { - if (enable) { - _appBloc.dispatch(AppThemeChanged(theme: ThemeType.DARK)); - } else { - _appBloc.dispatch(AppThemeChanged(theme: ThemeType.LIGHT)); - } - }; - - if (state is AppLoading) { - value = true; - leadingWidget = CircularProgressIndicator(); - trailingWidget = CircularProgressIndicator(); - } else if (state is AppInitialized) { - value = (state.theme == ThemeType.DARK) ? true : false; - leadingWidget = Icon(state.theme == ThemeType.DARK - ? MdiIcons.weatherSunny - : MdiIcons.whiteBalanceSunny); - - trailingWidget = Switch( - value: value, - onChanged: function, - ); - } - - return ListTile( - onTap: () => function(!value), - leading: leadingWidget, - title: Text(CVLocalizations.of(context).settingsThemeCTA), - trailing: trailingWidget, - ); - } - }, - ); - } -} diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart deleted file mode 100644 index c73561e..0000000 --- a/lib/src/utils/utils.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:social_cv_client_dart_common/errors.dart'; -import 'package:social_cv_client_flutter/src/ui/commons/defaults.dart'; -import 'package:social_cv_client_flutter/src/ui/localizations/cv_localization.dart'; - -import 'logger.dart'; - -String getInitials(String nameString) { - if (nameString.isEmpty) return ' '; - - List nameArray = - nameString.replaceAll(new RegExp(r'\s+\b|\b\s'), ' ').split(' '); - String initials = ((nameArray[0])[0] != null ? (nameArray[0])[0] : ' ') + - (nameArray.length == 1 ? ' ' : (nameArray[nameArray.length - 1])[0]); - - return initials; -} - -List> getDropDownMenuElementPerPage() { - List _values = [ - kCVItemsPerPage1, - kCVItemsPerPage2, - kCVItemsPerPage3, - kCVItemsPerPage4 - ]; - List> items = new List(); - for (String value in _values) { - items.add(new DropdownMenuItem(value: value, child: new Text(value))); - } - return items; -} - -String translateError(BuildContext context, dynamic err) { - CVLocalizations loc = CVLocalizations.of(context); - Logger.log('Translating error'); - - if (err is Exception) { - if (err is FormatException) - return loc.exceptionFormatException; - else if (err is TimeoutException) return loc.exceptionTimeoutException; - } else if (err is BaseError) { - ///Api Errors - if (err is ApiErrorWrongPasswordError) - return loc.apiErrorWrongPasswordError; - - ///HTTP 500 - else if (err is ApiErrorUserNotFoundError) - return loc.apiErrorUserNotFoundError; - - ///HTTP 501 - - ///HTTP Client Errors - if (err is HttpClientErrorBadRequestError) - return loc.httpClientErrorBadRequest; - - ///HTTP 400 - else if (err is HttpClientErrorPaymentRequiredError) - return loc.httpClientErrorPaymentRequired; - - ///HTTP 402 - else if (err is HttpClientErrorForbiddenError) - return loc.httpClientErrorForbidden; - - ///HTTP 403 - else if (err is HttpClientErrorNotFoundError) - return loc.httpClientErrorNotFound; - - ///HTTP 404 - else if (err is HttpClientErrorMethodNotAllowedError) - return loc.httpClientErrorMethodNotAllowed; - - ///HTTP 405 - else if (err is HttpClientErrorNotAcceptableError) - return loc.httpClientErrorNotAcceptable; - - ///HTTP 406 - else if (err is HttpClientErrorRequestTimeoutError) - return loc.httpClientErrorRequestTimeout; - - ///HTTP 408 - else if (err is HttpClientErrorConflictError) - return loc.httpClientErrorConflict; - - ///HTTP 409 - else if (err is HttpClientErrorGoneError) - return loc.httpClientErrorGone; - - ///HTTP 410 - else if (err is HttpClientErrorLengthRequiredError) - return loc.httpClientErrorLengthRequired; - - ///HTTP 411 - else if (err is HttpClientErrorPayloadTooLargeError) - return loc.httpClientErrorPayloadTooLarge; - - ///HTTP 413 - else if (err is HttpClientErrorUriTooLongError) - return loc.httpClientErrorURITooLong; - - ///HTTP 414 - else if (err is HttpClientErrorUnsupportedMediaTypeError) - return loc.httpClientErrorUnsupportedMediaType; - - ///HTTP 415 - else if (err is HttpClientErrorExpectationFailedError) - return loc.httpClientErrorExpectationFailed; - - ///HTTP 417 - else if (err is HttpClientErrorUpgradeRequiredError) - return loc.httpClientErrorUpgradeRequired; - - ///HTTP 426 - - ///HTTP Server Errors - if (err is HttpServerErrorInternalServerError) - return loc.httpServerErrorInternalServerError; - - ///HTTP 500 - else if (err is HttpServerErrorNotImplementedError) - return loc.httpServerErrorNotImplemented; - - ///HTTP 501 - else if (err is HttpServerErrorBadGatewayError) - return loc.httpServerErrorBadGateway; - - ///HTTP 502 - else if (err is HttpServerErrorServiceUnavailableError) - return loc.httpServerErrorServiceUnavailable; - - ///HTTP 503 - else if (err is HttpServerErrorGatewayTimeoutError) - return loc.httpServerErrorGatewayTimeout; - - ///HTTP 504 - else if (err is HttpServerErrorHttpVersionNotSupportedError) - return loc.httpServerErrorHttpVersionNotSupported; - - ///HTTP 505 - } - - ///Default - return '${err.runtimeType}'; -} diff --git a/pubspec.lock b/pubspec.lock index 9ce1a2c..26f47f5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,5 +1,5 @@ # Generated by pub -# See https://www.dartlang.org/tools/pub/glossary#lockfile +# See https://dart.dev/tools/pub/glossary#lockfile packages: analyzer: dependency: transitive @@ -7,28 +7,28 @@ packages: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.36.3" + version: "0.36.4" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.1" + version: "1.5.2" async: dependency: "direct main" description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" bloc: dependency: transitive description: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "0.13.0" + version: "0.14.4" boolean_selector: dependency: transitive description: @@ -42,7 +42,7 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.1.4" + version: "1.1.5" build_config: dependency: transitive description: @@ -56,28 +56,28 @@ packages: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "0.6.1" + version: "2.0.0" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.6" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.6.2" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "3.0.5" + version: "3.0.6" built_collection: dependency: transitive description: @@ -91,7 +91,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "6.5.0" + version: "6.7.0" charcode: dependency: transitive description: @@ -126,7 +126,7 @@ packages: name: cookie_jar url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" crypto: dependency: transitive description: @@ -140,21 +140,21 @@ packages: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.0" + version: "0.16.1" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.2.7" + version: "1.2.9" dio: dependency: transitive description: name: dio url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.13" equatable: dependency: transitive description: @@ -175,7 +175,7 @@ packages: name: fluro url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.5.1" flutter: dependency: "direct main" description: flutter @@ -187,7 +187,7 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "0.13.0" + version: "0.20.0" flutter_localizations: dependency: "direct main" description: flutter @@ -204,7 +204,7 @@ packages: name: front_end url: "https://pub.dartlang.org" source: hosted - version: "0.1.18" + version: "0.1.19" glob: dependency: transitive description: @@ -239,7 +239,7 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.1.0" http_parser: dependency: transitive description: @@ -260,7 +260,7 @@ packages: name: intl_translation url: "https://pub.dartlang.org" source: hosted - version: "0.17.4" + version: "0.17.5" io: dependency: transitive description: @@ -295,7 +295,7 @@ packages: name: kernel url: "https://pub.dartlang.org" source: hosted - version: "0.3.18" + version: "0.3.19" logging: dependency: transitive description: @@ -330,7 +330,7 @@ packages: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.6+2" + version: "0.9.6+3" package_config: dependency: transitive description: @@ -358,14 +358,14 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.5.0" + version: "1.7.0" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.4.0" pool: dependency: transitive description: @@ -379,7 +379,7 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.0.0+1" pub_semver: dependency: transitive description: @@ -400,14 +400,14 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" rxdart: dependency: "direct main" description: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.22.0" + version: "0.22.1" shared_preferences: dependency: "direct main" description: @@ -437,11 +437,9 @@ packages: social_cv_client_dart_common: dependency: "direct main" description: - path: "." - ref: "feature/blocs" - resolved-ref: "4f643a6b63a7eda24cb55a12c52d280d07aee4cc" - url: "https://github.com/axellebot/Social-CV-Client-Dart-common" - source: git + path: "..\\Social-CV-Client-Dart-common" + relative: true + source: path version: "1.0.0" source_gen: dependency: transitive @@ -449,7 +447,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.4+2" + version: "0.9.4+3" source_span: dependency: transitive description: @@ -498,7 +496,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.4" + version: "0.2.5" timing: dependency: transitive description: @@ -533,21 +531,21 @@ packages: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+10" + version: "0.9.7+12" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.0.12" + version: "1.0.14" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.1.15" + version: "2.1.16" sdks: - dart: ">=2.3.0-dev.0.1 <3.0.0" - flutter: ">=0.1.4 <2.0.0" + dart: ">=2.4.0 <3.0.0" + flutter: ">=1.2.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 47a95e2..100f1cc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,10 +8,10 @@ dependencies: # Common Social CV Logic social_cv_client_dart_common: - #path: ../Social-CV-Client-Dart-common - git: - url: https://github.com/axellebot/Social-CV-Client-Dart-common - ref: feature/blocs + path: ../Social-CV-Client-Dart-common + #git: + # url: https://github.com/axellebot/Social-CV-Client-Dart-common + # ref: feature/blocs flutter: sdk: flutter @@ -26,10 +26,10 @@ dependencies: rxdart: ^0.22.0 # Bloc Pattern - flutter_bloc: ^0.13.0 + flutter_bloc: ^0.20.0 # Dependency Injection - provider: ^2.0.1 + provider: ^3.0.0 # Storage shared_preferences: ^0.4.3 From 84ced26b083af54ff9e26cd3e88d72f45a074799 Mon Sep 17 00:00:00 2001 From: Axel LE BOT Date: Mon, 16 Nov 2020 19:46:56 +0100 Subject: [PATCH 17/17] [TASK] Updated project - Integrated client common lib back - Upgraded flutter version - Enabled flutter for web - Migrate android app from Java to Kotlin --- .gitignore | 373 ++++++------ android/app/src/debug/AndroidManifest.xml | 7 + android/app/src/main/AndroidManifest.xml | 29 +- .../java/me/lebot/axel/cv/MainActivity.java | 14 - .../social_cv_client_flutter/MainActivity.kt | 6 + .../app/src/main/res/values-night/styles.xml | 18 + android/app/src/main/res/values/styles.xml | 10 + android/app/src/profile/AndroidManifest.xml | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + ios/Runner/AppDelegate.swift | 13 + ios/Runner/Runner-Bridging-Header.h | 1 + lib/bloc.dart | 25 +- lib/data.dart | 75 ++- lib/domain.dart | 45 +- lib/generated_plugin_registrant.dart | 16 + lib/main.dart | 2 + lib/presentation.dart | 30 +- .../bloc/blocs/application/application.dart | 3 + .../blocs/application/application_bloc.dart | 69 +++ .../blocs/application/application_event.dart | 23 + .../blocs/application/application_state.dart | 37 ++ .../blocs/authentication/authentication.dart | 3 + .../authentication/authentication_bloc.dart | 129 ++++ .../authentication/authentication_event.dart | 37 ++ .../authentication/authentication_state.dart | 30 + lib/src/bloc/blocs/element/element.dart | 3 + lib/src/bloc/blocs/element/element_bloc.dart | 17 + lib/src/bloc/blocs/element/element_event.dart | 17 + lib/src/bloc/blocs/element/element_state.dart | 29 + .../bloc/blocs/element_list/element_list.dart | 3 + .../blocs/element_list/element_list_bloc.dart | 25 + .../element_list/element_list_event.dart | 29 + .../element_list/element_list_state.dart | 33 + lib/src/bloc/blocs/entry/entry.dart | 3 + lib/src/bloc/blocs/entry/entry_bloc.dart | 81 +++ lib/src/bloc/blocs/entry/entry_event.dart | 35 ++ lib/src/bloc/blocs/entry/entry_state.dart | 42 ++ lib/src/bloc/blocs/entry_list/entry_list.dart | 3 + .../blocs/entry_list/entry_list_bloc.dart | 120 ++++ .../blocs/entry_list/entry_list_event.dart | 57 ++ .../blocs/entry_list/entry_list_state.dart | 52 ++ lib/src/bloc/blocs/group/group.dart | 3 + lib/src/bloc/blocs/group/group_bloc.dart | 83 +++ lib/src/bloc/blocs/group/group_event.dart | 33 + lib/src/bloc/blocs/group/group_state.dart | 42 ++ lib/src/bloc/blocs/group_list/group_list.dart | 3 + .../blocs/group_list/group_list_bloc.dart | 120 ++++ .../blocs/group_list/group_list_event.dart | 57 ++ .../blocs/group_list/group_list_state.dart | 52 ++ lib/src/bloc/blocs/identity/identity.dart | 3 + .../bloc/blocs/identity/identity_bloc.dart | 66 ++ .../bloc/blocs/identity/identity_event.dart | 11 + .../bloc/blocs/identity/identity_state.dart | 40 ++ lib/src/bloc/blocs/login/login.dart | 3 + lib/src/bloc/blocs/login/login_bloc.dart | 60 ++ lib/src/bloc/blocs/login/login_event.dart | 26 + lib/src/bloc/blocs/login/login_state.dart | 45 ++ lib/src/bloc/blocs/part/part.dart | 3 + lib/src/bloc/blocs/part/part_bloc.dart | 80 +++ lib/src/bloc/blocs/part/part_event.dart | 34 ++ lib/src/bloc/blocs/part/part_state.dart | 40 ++ lib/src/bloc/blocs/part_list/part_list.dart | 3 + .../bloc/blocs/part_list/part_list_bloc.dart | 110 ++++ .../bloc/blocs/part_list/part_list_event.dart | 57 ++ .../bloc/blocs/part_list/part_list_state.dart | 51 ++ lib/src/bloc/blocs/profile/profile.dart | 3 + lib/src/bloc/blocs/profile/profile_bloc.dart | 81 +++ lib/src/bloc/blocs/profile/profile_event.dart | 37 ++ lib/src/bloc/blocs/profile/profile_state.dart | 42 ++ .../bloc/blocs/profile_list/profile_list.dart | 3 + .../blocs/profile_list/profile_list_bloc.dart | 120 ++++ .../profile_list/profile_list_event.dart | 57 ++ .../profile_list/profile_list_state.dart | 53 ++ lib/src/bloc/blocs/register/register.dart | 3 + .../bloc/blocs/register/register_bloc.dart | 56 ++ .../bloc/blocs/register/register_event.dart | 29 + .../bloc/blocs/register/register_state.dart | 45 ++ lib/src/bloc/blocs/user/user.dart | 3 + lib/src/bloc/blocs/user/user_bloc.dart | 80 +++ lib/src/bloc/blocs/user/user_event.dart | 27 + lib/src/bloc/blocs/user/user_state.dart | 42 ++ lib/src/data/cache_model.dart | 16 + lib/src/data/exceptions/api_exceptions.dart | 59 ++ lib/src/data/managers/api_interceptor.dart | 117 ++++ lib/src/data/managers/cv_api_manager.dart | 572 ++++++++++++++++++ lib/src/data/models/base_model.dart | 28 + lib/src/data/models/element_model.dart | 17 + lib/src/data/models/entry_model.dart | 61 ++ lib/src/data/models/envelop_models.dart | 176 ++++++ lib/src/data/models/group_model.dart | 46 ++ lib/src/data/models/part_model.dart | 46 ++ lib/src/data/models/profile_model.dart | 61 ++ lib/src/data/models/user_model.dart | 67 ++ .../repositories/app_prefs_repository.dart | 32 + .../repositories/auth_info_repository.dart | 80 +++ .../data/repositories/entry_repository.dart | 101 ++++ .../data/repositories/group_repository.dart | 101 ++++ .../repositories/identity_repository.dart | 47 ++ .../data/repositories/part_repository.dart | 96 +++ .../data/repositories/profile_repository.dart | 75 +++ .../data/repositories/user_repository.dart | 57 ++ .../app_prefs_data_store.dart | 17 + .../app_prefs_data_store_factory.dart | 18 + .../auth_info_data_store.dart | 64 ++ .../auth_info_data_store_factory.dart | 18 + .../entry_data_store/entry_data_store.dart | 32 + .../entry_data_store_factory.dart | 23 + .../memory_entry_data_store.dart | 67 ++ .../group_date_store/group_data_store.dart | 32 + .../group_data_store_factory.dart | 21 + .../memory_group_data_store.dart | 66 ++ .../identity_data_store.dart | 9 + .../identity_data_store_factory.dart | 22 + .../memory_identity_data_store.dart | 36 ++ .../memory_part_data_store.dart | 67 ++ .../part_date_store/part_data_store.dart | 32 + .../part_data_store_factory.dart | 21 + .../memory_profile_data_store.dart | 57 ++ .../profile_data_store.dart | 24 + .../profile_data_store_factory.dart | 21 + .../memory_user_data_store.dart | 47 ++ .../user_data_store/user_data_store.dart | 16 + .../user_data_store_factory.dart | 21 + lib/src/data/utils/utils.dart | 9 + lib/src/domain/entities/auth_entity.dart | 6 + lib/src/domain/entities/base_entity.dart | 14 + lib/src/domain/entities/element_model.dart | 11 + lib/src/domain/entities/entry_entity.dart | 26 + lib/src/domain/entities/group_entity.dart | 20 + lib/src/domain/entities/part_entity.dart | 17 + lib/src/domain/entities/profile_entity.dart | 23 + lib/src/domain/entities/user_entity.dart | 21 + lib/src/domain/errors/commons_errors.dart | 19 + lib/src/domain/exceptions/app_exceptions.dart | 46 ++ .../domain/exceptions/http_exceptions.dart | 13 + .../app_preferences_repository.dart | 21 + .../repositories/auth_info_repository.dart | 58 ++ .../repositories/entity_repository.dart | 30 + .../domain/repositories/entry_repository.dart | 34 ++ .../domain/repositories/group_repository.dart | 34 ++ .../repositories/identity_repository.dart | 22 + .../domain/repositories/part_repository.dart | 34 ++ .../repositories/profile_repository.dart | 16 + .../domain/repositories/user_repository.dart | 5 + lib/src/domain/services/cv_auth_service.dart | 35 ++ .../services/foundation_config_service.dart | 12 + .../presentation/mappers/model_mapper.dart | 117 ++++ lib/src/presentation/models/api_models.dart | 21 + lib/src/presentation/models/cursor_model.dart | 24 + .../presentation/models/element_model.dart | 23 + lib/src/presentation/models/entry_model.dart | 74 +++ lib/src/presentation/models/group_model.dart | 58 ++ lib/src/presentation/models/part_model.dart | 58 ++ .../presentation/models/profile_model.dart | 73 +++ lib/src/presentation/models/user_model.dart | 42 ++ lib/src/presentation/router.dart | 2 +- pubspec.lock | 296 ++++++--- pubspec.yaml | 25 +- web/favicon.png | Bin 0 -> 917 bytes web/icons/Icon-192.png | Bin 0 -> 5292 bytes web/icons/Icon-512.png | Bin 0 -> 8252 bytes web/index.html | 45 ++ web/manifest.json | 23 + 163 files changed, 6964 insertions(+), 294 deletions(-) create mode 100644 android/app/src/debug/AndroidManifest.xml delete mode 100644 android/app/src/main/java/me/lebot/axel/cv/MainActivity.java create mode 100644 android/app/src/main/kotlin/me/lebot/axel/social_cv_client_flutter/MainActivity.kt create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 android/app/src/profile/AndroidManifest.xml create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner/AppDelegate.swift create mode 100644 ios/Runner/Runner-Bridging-Header.h create mode 100644 lib/generated_plugin_registrant.dart create mode 100644 lib/src/bloc/blocs/application/application.dart create mode 100644 lib/src/bloc/blocs/application/application_bloc.dart create mode 100644 lib/src/bloc/blocs/application/application_event.dart create mode 100644 lib/src/bloc/blocs/application/application_state.dart create mode 100644 lib/src/bloc/blocs/authentication/authentication.dart create mode 100644 lib/src/bloc/blocs/authentication/authentication_bloc.dart create mode 100644 lib/src/bloc/blocs/authentication/authentication_event.dart create mode 100644 lib/src/bloc/blocs/authentication/authentication_state.dart create mode 100644 lib/src/bloc/blocs/element/element.dart create mode 100644 lib/src/bloc/blocs/element/element_bloc.dart create mode 100644 lib/src/bloc/blocs/element/element_event.dart create mode 100644 lib/src/bloc/blocs/element/element_state.dart create mode 100644 lib/src/bloc/blocs/element_list/element_list.dart create mode 100644 lib/src/bloc/blocs/element_list/element_list_bloc.dart create mode 100644 lib/src/bloc/blocs/element_list/element_list_event.dart create mode 100644 lib/src/bloc/blocs/element_list/element_list_state.dart create mode 100644 lib/src/bloc/blocs/entry/entry.dart create mode 100644 lib/src/bloc/blocs/entry/entry_bloc.dart create mode 100644 lib/src/bloc/blocs/entry/entry_event.dart create mode 100644 lib/src/bloc/blocs/entry/entry_state.dart create mode 100644 lib/src/bloc/blocs/entry_list/entry_list.dart create mode 100644 lib/src/bloc/blocs/entry_list/entry_list_bloc.dart create mode 100644 lib/src/bloc/blocs/entry_list/entry_list_event.dart create mode 100644 lib/src/bloc/blocs/entry_list/entry_list_state.dart create mode 100644 lib/src/bloc/blocs/group/group.dart create mode 100644 lib/src/bloc/blocs/group/group_bloc.dart create mode 100644 lib/src/bloc/blocs/group/group_event.dart create mode 100644 lib/src/bloc/blocs/group/group_state.dart create mode 100644 lib/src/bloc/blocs/group_list/group_list.dart create mode 100644 lib/src/bloc/blocs/group_list/group_list_bloc.dart create mode 100644 lib/src/bloc/blocs/group_list/group_list_event.dart create mode 100644 lib/src/bloc/blocs/group_list/group_list_state.dart create mode 100644 lib/src/bloc/blocs/identity/identity.dart create mode 100644 lib/src/bloc/blocs/identity/identity_bloc.dart create mode 100644 lib/src/bloc/blocs/identity/identity_event.dart create mode 100644 lib/src/bloc/blocs/identity/identity_state.dart create mode 100644 lib/src/bloc/blocs/login/login.dart create mode 100644 lib/src/bloc/blocs/login/login_bloc.dart create mode 100644 lib/src/bloc/blocs/login/login_event.dart create mode 100644 lib/src/bloc/blocs/login/login_state.dart create mode 100644 lib/src/bloc/blocs/part/part.dart create mode 100644 lib/src/bloc/blocs/part/part_bloc.dart create mode 100644 lib/src/bloc/blocs/part/part_event.dart create mode 100644 lib/src/bloc/blocs/part/part_state.dart create mode 100644 lib/src/bloc/blocs/part_list/part_list.dart create mode 100644 lib/src/bloc/blocs/part_list/part_list_bloc.dart create mode 100644 lib/src/bloc/blocs/part_list/part_list_event.dart create mode 100644 lib/src/bloc/blocs/part_list/part_list_state.dart create mode 100644 lib/src/bloc/blocs/profile/profile.dart create mode 100644 lib/src/bloc/blocs/profile/profile_bloc.dart create mode 100644 lib/src/bloc/blocs/profile/profile_event.dart create mode 100644 lib/src/bloc/blocs/profile/profile_state.dart create mode 100644 lib/src/bloc/blocs/profile_list/profile_list.dart create mode 100644 lib/src/bloc/blocs/profile_list/profile_list_bloc.dart create mode 100644 lib/src/bloc/blocs/profile_list/profile_list_event.dart create mode 100644 lib/src/bloc/blocs/profile_list/profile_list_state.dart create mode 100644 lib/src/bloc/blocs/register/register.dart create mode 100644 lib/src/bloc/blocs/register/register_bloc.dart create mode 100644 lib/src/bloc/blocs/register/register_event.dart create mode 100644 lib/src/bloc/blocs/register/register_state.dart create mode 100644 lib/src/bloc/blocs/user/user.dart create mode 100644 lib/src/bloc/blocs/user/user_bloc.dart create mode 100644 lib/src/bloc/blocs/user/user_event.dart create mode 100644 lib/src/bloc/blocs/user/user_state.dart create mode 100644 lib/src/data/cache_model.dart create mode 100644 lib/src/data/exceptions/api_exceptions.dart create mode 100644 lib/src/data/managers/api_interceptor.dart create mode 100644 lib/src/data/managers/cv_api_manager.dart create mode 100644 lib/src/data/models/base_model.dart create mode 100644 lib/src/data/models/element_model.dart create mode 100644 lib/src/data/models/entry_model.dart create mode 100644 lib/src/data/models/envelop_models.dart create mode 100644 lib/src/data/models/group_model.dart create mode 100644 lib/src/data/models/part_model.dart create mode 100644 lib/src/data/models/profile_model.dart create mode 100644 lib/src/data/models/user_model.dart create mode 100644 lib/src/data/repositories/app_prefs_repository.dart create mode 100644 lib/src/data/repositories/auth_info_repository.dart create mode 100644 lib/src/data/repositories/entry_repository.dart create mode 100644 lib/src/data/repositories/group_repository.dart create mode 100644 lib/src/data/repositories/identity_repository.dart create mode 100644 lib/src/data/repositories/part_repository.dart create mode 100644 lib/src/data/repositories/profile_repository.dart create mode 100644 lib/src/data/repositories/user_repository.dart create mode 100644 lib/src/data/stores/app_prefs_data_store/app_prefs_data_store.dart create mode 100644 lib/src/data/stores/app_prefs_data_store/app_prefs_data_store_factory.dart create mode 100644 lib/src/data/stores/auth_info_data_store/auth_info_data_store.dart create mode 100644 lib/src/data/stores/auth_info_data_store/auth_info_data_store_factory.dart create mode 100644 lib/src/data/stores/entry_data_store/entry_data_store.dart create mode 100644 lib/src/data/stores/entry_data_store/entry_data_store_factory.dart create mode 100644 lib/src/data/stores/entry_data_store/memory_entry_data_store.dart create mode 100644 lib/src/data/stores/group_date_store/group_data_store.dart create mode 100644 lib/src/data/stores/group_date_store/group_data_store_factory.dart create mode 100644 lib/src/data/stores/group_date_store/memory_group_data_store.dart create mode 100644 lib/src/data/stores/identity_data_store/identity_data_store.dart create mode 100644 lib/src/data/stores/identity_data_store/identity_data_store_factory.dart create mode 100644 lib/src/data/stores/identity_data_store/memory_identity_data_store.dart create mode 100644 lib/src/data/stores/part_date_store/memory_part_data_store.dart create mode 100644 lib/src/data/stores/part_date_store/part_data_store.dart create mode 100644 lib/src/data/stores/part_date_store/part_data_store_factory.dart create mode 100644 lib/src/data/stores/profile_date_store/memory_profile_data_store.dart create mode 100644 lib/src/data/stores/profile_date_store/profile_data_store.dart create mode 100644 lib/src/data/stores/profile_date_store/profile_data_store_factory.dart create mode 100644 lib/src/data/stores/user_data_store/memory_user_data_store.dart create mode 100644 lib/src/data/stores/user_data_store/user_data_store.dart create mode 100644 lib/src/data/stores/user_data_store/user_data_store_factory.dart create mode 100644 lib/src/data/utils/utils.dart create mode 100644 lib/src/domain/entities/auth_entity.dart create mode 100644 lib/src/domain/entities/base_entity.dart create mode 100644 lib/src/domain/entities/element_model.dart create mode 100644 lib/src/domain/entities/entry_entity.dart create mode 100644 lib/src/domain/entities/group_entity.dart create mode 100644 lib/src/domain/entities/part_entity.dart create mode 100644 lib/src/domain/entities/profile_entity.dart create mode 100644 lib/src/domain/entities/user_entity.dart create mode 100644 lib/src/domain/errors/commons_errors.dart create mode 100644 lib/src/domain/exceptions/app_exceptions.dart create mode 100644 lib/src/domain/exceptions/http_exceptions.dart create mode 100644 lib/src/domain/repositories/app_preferences_repository.dart create mode 100644 lib/src/domain/repositories/auth_info_repository.dart create mode 100644 lib/src/domain/repositories/entity_repository.dart create mode 100644 lib/src/domain/repositories/entry_repository.dart create mode 100644 lib/src/domain/repositories/group_repository.dart create mode 100644 lib/src/domain/repositories/identity_repository.dart create mode 100644 lib/src/domain/repositories/part_repository.dart create mode 100644 lib/src/domain/repositories/profile_repository.dart create mode 100644 lib/src/domain/repositories/user_repository.dart create mode 100644 lib/src/domain/services/cv_auth_service.dart create mode 100644 lib/src/domain/services/foundation_config_service.dart create mode 100644 lib/src/presentation/mappers/model_mapper.dart create mode 100644 lib/src/presentation/models/api_models.dart create mode 100644 lib/src/presentation/models/cursor_model.dart create mode 100644 lib/src/presentation/models/element_model.dart create mode 100644 lib/src/presentation/models/entry_model.dart create mode 100644 lib/src/presentation/models/group_model.dart create mode 100644 lib/src/presentation/models/part_model.dart create mode 100644 lib/src/presentation/models/profile_model.dart create mode 100644 lib/src/presentation/models/user_model.dart create mode 100644 web/favicon.png create mode 100644 web/icons/Icon-192.png create mode 100644 web/icons/Icon-512.png create mode 100644 web/index.html create mode 100644 web/manifest.json diff --git a/.gitignore b/.gitignore index 6c08bfd..fe28037 100644 --- a/.gitignore +++ b/.gitignore @@ -15,70 +15,13 @@ *.iws .idea/ -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.packages -.pub-cache/ -.pub/ -/build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages - - - -# Created by https://www.gitignore.io/api/dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio -# Edit at https://www.gitignore.io/?templates=dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio +# Created by https://www.toptal.com/developers/gitignore/api/dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio +# Edit at https://www.toptal.com/developers/gitignore?templates=dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio ### Android ### # Built application files *.apk +*.aar *.ap_ *.aab @@ -92,6 +35,8 @@ bin/ gen/ out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ # Gradle files .gradle/ @@ -120,7 +65,11 @@ captures/ .idea/assetWizardSettings.xml .idea/dictionaries .idea/libraries +# Android Studio 3 in .gitignore file. .idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. @@ -129,9 +78,10 @@ captures/ # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild +.cxx/ # Google Services (e.g. APIs or Firebase) -google-services.json +# google-services.json # Freeline freeline.py @@ -145,117 +95,22 @@ fastlane/screenshots fastlane/test_output fastlane/readme.md -### Android Patch ### -gen-external-apklibs - -### AndroidStudio ### -# Covers files to be ignored for android development using Android Studio. - -# Built application files - -# Files for the ART/Dalvik VM - -# Java class files - -# Generated files - -# Gradle files -.gradle - -# Signing files -.signing/ - -# Local configuration file (sdk path, etc) - -# Proguard folder generated by Eclipse - -# Log Files - -# Android Studio -/*/build/ -/*/local.properties -/*/out -/*/*/build -/*/*/production -*.ipr -*~ -*.swp - -# Android Patch - -# External native build folder generated in Android Studio 2.2 and later - -# NDK -obj/ - -# IntelliJ IDEA -*.iws -/out/ - -# User-specific configurations -.idea/caches/ -.idea/libraries/ -.idea/shelf/ -.idea/.name -.idea/compiler.xml -.idea/copyright/profiles_settings.xml -.idea/encodings.xml -.idea/misc.xml -.idea/modules.xml -.idea/scopes/scope_settings.xml -.idea/vcs.xml -.idea/jsLibraryMappings.xml -.idea/datasources.xml -.idea/dataSources.ids -.idea/sqlDataSources.xml -.idea/dynamic.xml -.idea/uiDesigner.xml - -# OS-specific files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Legacy Eclipse project files -.classpath -.project -.cproject -.settings/ +# Version control +vcs.xml -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.war -*.ear +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ -# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) -hs_err_pid* - -## Plugin-specific files: - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Mongo Explorer plugin -.idea/mongoSettings.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -### AndroidStudio Patch ### +### Android Patch ### +gen-external-apklibs +output.json -!/gradle/wrapper/gradle-wrapper.jar +# Replacement of .externalNativeBuild directories introduced +# with Android Studio 3.5. ### CocoaPods ### ## CocoaPods GitIgnore Template @@ -288,9 +143,63 @@ doc/api/ *.js.map ### Flutter ### +# Flutter/Dart/Pub related +**/doc/api/ .flutter-plugins +.flutter-plugins-dependencies +.fvm/ +.pub-cache/ +.pub/ +lib/generated_plugin_registrant.dart + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/key.properties +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ### Linux ### +*~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* @@ -306,18 +215,23 @@ doc/api/ ### macOS ### # General +.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon + # Thumbnails +._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd +.Spotlight-V100 .TemporaryItems +.Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent @@ -330,6 +244,9 @@ Temporary Items ### Windows ### # Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db ehthumbs_vista.db # Dump file @@ -375,15 +292,126 @@ DerivedData/ *.perspectivev3 !default.perspectivev3 +## Gcc Patch +/*.gcno + ### Xcode Patch ### *.xcodeproj/* !*.xcodeproj/project.pbxproj !*.xcodeproj/xcshareddata/ !*.xcworkspace/contents.xcworkspacedata -/*.gcno **/xcshareddata/WorkspaceSettings.xcsettings -# End of https://www.gitignore.io/api/dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files + +# Files for the ART/Dalvik VM + +# Java class files + +# Generated files + +# Gradle files +.gradle + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) + +# Proguard folder generated by Eclipse + +# Log Files + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +*.ipr +*.swp + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Android Patch + +# External native build folder generated in Android Studio 2.2 and later + +# NDK +obj/ + +# IntelliJ IDEA +*.iws +/out/ + +# User-specific configurations +.idea/caches/ +.idea/libraries/ +.idea/shelf/ +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/scopes/scope_settings.xml +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml +.idea/jarRepositories.xml + +# OS-specific files +.DS_Store? + +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.toptal.com/developers/gitignore/api/dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio ### Custom ### @@ -403,3 +431,6 @@ packages ios/.symlinks/ # Plugins config.json + +# Flutter Plugin +.flutter-plugins-dependencies \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..aba602b --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c0758fb..cf73c6a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - - + + + + android:name="io.flutter.embedding.android.SplashScreenDrawable" + android:resource="@drawable/launch_background" + /> + + diff --git a/android/app/src/main/java/me/lebot/axel/cv/MainActivity.java b/android/app/src/main/java/me/lebot/axel/cv/MainActivity.java deleted file mode 100644 index 01f2582..0000000 --- a/android/app/src/main/java/me/lebot/axel/cv/MainActivity.java +++ /dev/null @@ -1,14 +0,0 @@ -package me.lebot.axel.cv; - -import android.os.Bundle; - -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; - -public class MainActivity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); - } -} diff --git a/android/app/src/main/kotlin/me/lebot/axel/social_cv_client_flutter/MainActivity.kt b/android/app/src/main/kotlin/me/lebot/axel/social_cv_client_flutter/MainActivity.kt new file mode 100644 index 0000000..080b341 --- /dev/null +++ b/android/app/src/main/kotlin/me/lebot/axel/social_cv_client_flutter/MainActivity.kt @@ -0,0 +1,6 @@ +package me.lebot.axel.social_cv_client_flutter + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..449a9f9 --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 00fa441..322503e 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,8 +1,18 @@ + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..aba602b --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/lib/bloc.dart b/lib/bloc.dart index 5585912..a67a23f 100644 --- a/lib/bloc.dart +++ b/lib/bloc.dart @@ -1,3 +1,24 @@ -export 'package:social_cv_client_dart_common/bloc.dart'; - +/// Blocs + Events + States +export 'package:social_cv_client_flutter/src/bloc/blocs/application/application.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/authentication/authentication.dart'; export 'package:social_cv_client_flutter/src/bloc/blocs/configuration/configuration.dart'; + +/// Element Blocs + Event + States +export 'package:social_cv_client_flutter/src/bloc/blocs/element/element.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/element_list/element_list.dart'; + +/// Element List Blocs + Events + States +export 'package:social_cv_client_flutter/src/bloc/blocs/entry/entry.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/entry_list/entry_list.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/group/group.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/group_list/group_list.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/identity/identity.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/login/login.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/part/part.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/part_list/part_list.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/profile/profile.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/profile_list/profile_list.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/register/register.dart'; +export 'package:social_cv_client_flutter/src/bloc/blocs/user/user.dart'; + +//export 'package:social_cv_client_flutter/src/bloc/blocs/user_list/user_list.dart'; diff --git a/lib/data.dart b/lib/data.dart index e33bcaf..ff3be7d 100644 --- a/lib/data.dart +++ b/lib/data.dart @@ -1,4 +1,77 @@ -export 'package:social_cv_client_dart_common/data.dart'; +export 'package:social_cv_client_flutter/src/data/cache_model.dart'; + +/// ---------------------------------------------------------------------------- +/// Exceptions +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/data/exceptions/api_exceptions.dart'; + +/// ---------------------------------------------------------------------------- +/// Managers +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/data/managers/api_interceptor.dart'; +export 'package:social_cv_client_flutter/src/data/managers/cv_api_manager.dart'; + +/// ---------------------------------------------------------------------------- +/// Models +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/data/models/element_model.dart'; +export 'package:social_cv_client_flutter/src/data/models/entry_model.dart'; +export 'package:social_cv_client_flutter/src/data/models/envelop_models.dart'; +export 'package:social_cv_client_flutter/src/data/models/group_model.dart'; +export 'package:social_cv_client_flutter/src/data/models/part_model.dart'; +export 'package:social_cv_client_flutter/src/data/models/profile_model.dart'; +export 'package:social_cv_client_flutter/src/data/models/user_model.dart'; + +/// ---------------------------------------------------------------------------- +/// Repositories +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/data/repositories/app_prefs_repository.dart'; +export 'package:social_cv_client_flutter/src/data/repositories/auth_info_repository.dart'; +export 'package:social_cv_client_flutter/src/data/repositories/entry_repository.dart'; +export 'package:social_cv_client_flutter/src/data/repositories/group_repository.dart'; +export 'package:social_cv_client_flutter/src/data/repositories/identity_repository.dart'; +export 'package:social_cv_client_flutter/src/data/repositories/part_repository.dart'; +export 'package:social_cv_client_flutter/src/data/repositories/profile_repository.dart'; +export 'package:social_cv_client_flutter/src/data/repositories/user_repository.dart'; +export 'package:social_cv_client_flutter/src/data/stores/app_prefs_data_store/app_prefs_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/app_prefs_data_store/app_prefs_data_store_factory.dart'; +export 'package:social_cv_client_flutter/src/data/stores/auth_info_data_store/auth_info_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/auth_info_data_store/auth_info_data_store_factory.dart'; +export 'package:social_cv_client_flutter/src/data/stores/entry_data_store/entry_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/entry_data_store/entry_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/entry_data_store/entry_data_store_factory.dart'; +export 'package:social_cv_client_flutter/src/data/stores/entry_data_store/memory_entry_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/group_date_store/group_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/group_date_store/group_data_store_factory.dart'; +export 'package:social_cv_client_flutter/src/data/stores/group_date_store/memory_group_data_store.dart'; + +/// ---------------------------------------------------------------------------- +/// Data Stores +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/data/stores/identity_data_store/identity_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/identity_data_store/identity_data_store_factory.dart'; +export 'package:social_cv_client_flutter/src/data/stores/identity_data_store/memory_identity_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/part_date_store/memory_part_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/part_date_store/part_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/part_date_store/part_data_store_factory.dart'; +export 'package:social_cv_client_flutter/src/data/stores/profile_date_store/memory_profile_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/profile_date_store/profile_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/profile_date_store/profile_data_store_factory.dart'; +export 'package:social_cv_client_flutter/src/data/stores/user_data_store/memory_user_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/user_data_store/user_data_store.dart'; +export 'package:social_cv_client_flutter/src/data/stores/user_data_store/user_data_store_factory.dart'; + +/// ---------------------------------------------------------------------------- +/// Data Stores +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/data/utils/utils.dart'; + /// ---------------------------------------------------------------------------- /// Managers diff --git a/lib/domain.dart b/lib/domain.dart index 4111daa..5372519 100644 --- a/lib/domain.dart +++ b/lib/domain.dart @@ -1,2 +1,45 @@ -export 'package:social_cv_client_dart_common/domain.dart'; +/// ---------------------------------------------------------------------------- +/// Entities +/// ---------------------------------------------------------------------------- +export 'package:social_cv_client_flutter/src/domain/entities/auth_entity.dart'; +export 'package:social_cv_client_flutter/src/domain/entities/base_entity.dart'; +export 'package:social_cv_client_flutter/src/domain/entities/element_model.dart'; +export 'package:social_cv_client_flutter/src/domain/entities/entry_entity.dart'; +export 'package:social_cv_client_flutter/src/domain/entities/group_entity.dart'; +export 'package:social_cv_client_flutter/src/domain/entities/part_entity.dart'; +export 'package:social_cv_client_flutter/src/domain/entities/profile_entity.dart'; +export 'package:social_cv_client_flutter/src/domain/entities/user_entity.dart'; + +/// ---------------------------------------------------------------------------- +/// Errors +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/domain/errors/commons_errors.dart'; + +/// ---------------------------------------------------------------------------- +/// Exceptions +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/domain/exceptions/app_exceptions.dart'; +export 'package:social_cv_client_flutter/src/domain/exceptions/http_exceptions.dart'; + +/// ---------------------------------------------------------------------------- +/// Repositories +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/domain/repositories/app_preferences_repository.dart'; +export 'package:social_cv_client_flutter/src/domain/repositories/auth_info_repository.dart'; +export 'package:social_cv_client_flutter/src/domain/repositories/entry_repository.dart'; +export 'package:social_cv_client_flutter/src/domain/repositories/group_repository.dart'; +export 'package:social_cv_client_flutter/src/domain/repositories/identity_repository.dart'; +export 'package:social_cv_client_flutter/src/domain/repositories/part_repository.dart'; +export 'package:social_cv_client_flutter/src/domain/repositories/profile_repository.dart'; +export 'package:social_cv_client_flutter/src/domain/repositories/user_repository.dart'; + +/// ---------------------------------------------------------------------------- +/// Services +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/domain/services/cv_auth_service.dart'; +export 'package:social_cv_client_flutter/src/domain/services/foundation_config_service.dart'; diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart new file mode 100644 index 0000000..9f6808e --- /dev/null +++ b/lib/generated_plugin_registrant.dart @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// ignore: unused_import +import 'dart:ui'; + +import 'package:shared_preferences_web/shared_preferences_web.dart'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +// ignore: public_member_api_docs +void registerPlugins(PluginRegistry registry) { + SharedPreferencesPlugin.registerWith(registry.registrarFor(SharedPreferencesPlugin)); + registry.registerMessageHandler(); +} diff --git a/lib/main.dart b/lib/main.dart index bfec27b..512b096 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,9 @@ import 'package:social_cv_client_flutter/src/presentation/app.dart'; import 'package:social_cv_client_flutter/src/presentation/utils/logger.dart'; /// TODO: automatically set this to false for release builds +// ignore: constant_identifier_names const bool DEBUG_MODE = true; +// ignore: constant_identifier_names const bool DEBUG_PAINT_SIZE = false; FutureOr main() async { diff --git a/lib/presentation.dart b/lib/presentation.dart index ad9cc85..15abd7a 100644 --- a/lib/presentation.dart +++ b/lib/presentation.dart @@ -1,9 +1,32 @@ /// ---------------------------------------------------------------------------- -/// Commons +/// Libs /// ---------------------------------------------------------------------------- export 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -export 'package:social_cv_client_dart_common/presentation.dart'; + +/// ---------------------------------------------------------------------------- +/// Mappers +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/presentation/mappers/model_mapper.dart'; + +/// ---------------------------------------------------------------------------- +/// View Models +/// ---------------------------------------------------------------------------- + +export 'package:social_cv_client_flutter/src/presentation/models/api_models.dart'; +export 'package:social_cv_client_flutter/src/presentation/models/cursor_model.dart'; +export 'package:social_cv_client_flutter/src/presentation/models/element_model.dart'; +export 'package:social_cv_client_flutter/src/presentation/models/entry_model.dart'; +export 'package:social_cv_client_flutter/src/presentation/models/group_model.dart'; +export 'package:social_cv_client_flutter/src/presentation/models/part_model.dart'; +export 'package:social_cv_client_flutter/src/presentation/models/profile_model.dart'; +export 'package:social_cv_client_flutter/src/presentation/models/user_model.dart'; + +/// ---------------------------------------------------------------------------- +/// Commons +/// ---------------------------------------------------------------------------- + export 'package:social_cv_client_flutter/src/presentation/commons/api_values.dart'; export 'package:social_cv_client_flutter/src/presentation/commons/assets.dart'; export 'package:social_cv_client_flutter/src/presentation/commons/paths.dart'; @@ -15,13 +38,12 @@ export 'package:social_cv_client_flutter/src/presentation/commons/tags.dart'; /// ---------------------------------------------------------------------------- export 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization.dart'; -export 'package:social_cv_client_flutter/src/presentation/pages/account_page.dart'; -export 'package:social_cv_client_flutter/src/presentation/pages/account_page.dart'; /// ---------------------------------------------------------------------------- /// Pages /// ---------------------------------------------------------------------------- +export 'package:social_cv_client_flutter/src/presentation/pages/account_page.dart'; export 'package:social_cv_client_flutter/src/presentation/pages/auth_page.dart'; export 'package:social_cv_client_flutter/src/presentation/pages/elements/entry_profile_page.dart'; export 'package:social_cv_client_flutter/src/presentation/pages/elements/group_profile_page.dart'; diff --git a/lib/src/bloc/blocs/application/application.dart b/lib/src/bloc/blocs/application/application.dart new file mode 100644 index 0000000..57124f2 --- /dev/null +++ b/lib/src/bloc/blocs/application/application.dart @@ -0,0 +1,3 @@ +export './application_bloc.dart'; +export './application_event.dart'; +export './application_state.dart'; diff --git a/lib/src/bloc/blocs/application/application_bloc.dart b/lib/src/bloc/blocs/application/application_bloc.dart new file mode 100644 index 0000000..e9174fb --- /dev/null +++ b/lib/src/bloc/blocs/application/application_bloc.dart @@ -0,0 +1,69 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Business Logic Component for Application behaviors +/// Can manage theme +class AppBloc extends Bloc { + final String _tag = '$AppBloc'; + + final AppPrefsRepository appPreferencesRepository; + + AppBloc({ + @required this.appPreferencesRepository, + }) : assert( + appPreferencesRepository != null, + 'No $AppPrefsRepository given', + ), + super(); + + @override + AppState get initialState => AppUninitialized(); + + @override + Stream mapEventToState(AppEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is AppConfigured) { + yield* _mapAppConfiguredToState(event); + } else if (event is AppThemeChanged) { + yield* _mapAppThemeChangedToState(event); + } + } + + /// ----------------------------------------------------------------------- + /// All Event map to State + /// ----------------------------------------------------------------------- + + /// Map [AppThemeChanged] to [AppState] + /// + /// ```dart + /// yield* _mapAppThemeChangedToState(event); + /// ``` + Stream _mapAppThemeChangedToState(AppThemeChanged event) async* { + try { + yield AppLoading(); + await appPreferencesRepository.toggleDarkMode(event.darkMode); + yield AppInitialized(darkMode: event.darkMode); + } catch (error) { + yield AppFailure(error: error); + } + } + + /// Map [AppConfigured] to [AppState] + /// + /// ```dart + /// yield* _mapAppConfiguredToState(event); + /// ``` + Stream _mapAppConfiguredToState(AppConfigured event) async* { + try { + yield AppLoading(); + final darkMode = await appPreferencesRepository.getDarkMode() ?? false; + yield AppInitialized(darkMode: darkMode); + } catch (error) { + yield AppFailure(error: error); + } + } +} diff --git a/lib/src/bloc/blocs/application/application_event.dart b/lib/src/bloc/blocs/application/application_event.dart new file mode 100644 index 0000000..e20c3ff --- /dev/null +++ b/lib/src/bloc/blocs/application/application_event.dart @@ -0,0 +1,23 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +/// [AppEvent] that must be dispatch to [AppBloc] +abstract class AppEvent extends Equatable { + AppEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class AppConfigured extends AppEvent {} + +class AppThemeChanged extends AppEvent { + final bool darkMode; + + AppThemeChanged({@required this.darkMode}) : super([darkMode]); + + @override + String toString() => '$runtimeType{ ' + 'darkMode: $darkMode' + ' }'; +} diff --git a/lib/src/bloc/blocs/application/application_state.dart b/lib/src/bloc/blocs/application/application_state.dart new file mode 100644 index 0000000..8fa1ca6 --- /dev/null +++ b/lib/src/bloc/blocs/application/application_state.dart @@ -0,0 +1,37 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +abstract class AppState extends Equatable { + AppState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class AppUninitialized extends AppState {} + +class AppInitialized extends AppState { + final bool darkMode; + + AppInitialized({this.darkMode = false}) : super([darkMode]); + + @override + String toString() => '$runtimeType{ ' + 'darkMode: $darkMode' + ' }'; +} + +class AppFailure extends AppState { + final dynamic error; + + AppFailure({@required this.error}) + : assert(error != null, 'No error given'), + super([error]); + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} + +class AppLoading extends AppState {} diff --git a/lib/src/bloc/blocs/authentication/authentication.dart b/lib/src/bloc/blocs/authentication/authentication.dart new file mode 100644 index 0000000..0383408 --- /dev/null +++ b/lib/src/bloc/blocs/authentication/authentication.dart @@ -0,0 +1,3 @@ +export './authentication_bloc.dart'; +export './authentication_event.dart'; +export './authentication_state.dart'; diff --git a/lib/src/bloc/blocs/authentication/authentication_bloc.dart b/lib/src/bloc/blocs/authentication/authentication_bloc.dart new file mode 100644 index 0000000..e38a1bb --- /dev/null +++ b/lib/src/bloc/blocs/authentication/authentication_bloc.dart @@ -0,0 +1,129 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Business Logic Component for Authentication +class AuthenticationBloc + extends Bloc { + final String _tag = '$AuthenticationBloc'; + + final CVAuthService cvAuthService; + final AuthInfoRepository authInfoRepository; + final LoginBloc loginBloc; + final RegisterBloc registerBloc; + + StreamSubscription registerBlocSubscription; + StreamSubscription loginBlocSubscription; + + AuthenticationBloc({ + @required this.cvAuthService, + @required this.authInfoRepository, + @required this.loginBloc, + @required this.registerBloc, + }) : assert(cvAuthService != null, 'No $CVAuthService given'), + assert(authInfoRepository != null, 'No $AppPrefsRepository given'), + assert(loginBloc != null, 'No $LoginBloc given'), + assert(registerBloc != null, 'No $RegisterBloc given'), + super() { + loginBlocSubscription = loginBloc.state.listen((state) { + if (state is LoginSucceed) { + dispatch(LoggedIn( + accessToken: state.accessToken, + accessTokenExpiration: state.accessTokenExpiration, + refreshToken: state.refreshToken, + )); + } + }); + + registerBlocSubscription = registerBloc.state.listen((state) { + if (state is RegisterSucceed) { + dispatch(LoggedIn( + accessToken: state.accessToken, + accessTokenExpiration: state.accessTokenExpiration, + refreshToken: state.refreshToken, + )); + } + }); + } + + @override + void dispose() { + loginBlocSubscription.cancel(); + registerBlocSubscription.cancel(); + super.dispose(); + } + + @override + AuthenticationState get initialState => AuthenticationUninitialized(); + + @override + Stream mapEventToState( + AuthenticationEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is AppStarted) { + yield* _mapAppStartedToState(event); + } else if (event is LoggedIn) { + yield* _mapLoggedInToState(event); + } else if (event is LoggedOut) { + yield* _mapLoggedOutToState(event); + } + } + + /// ----------------------------------------------------------------------- + /// All Event map to State + /// ----------------------------------------------------------------------- + + /// Map [AppStarted] to [AuthenticationState] + /// + /// ```dart + /// yield* _mapAppStartedToState(event); + /// ``` + Stream _mapAppStartedToState(AppStarted event) async* { + try { + final token = await authInfoRepository.getAccessToken(); + + /// TODO: Check access token expiration and fetch new access token with refresh token + /// TODO: Check refresh token expiration, if it's expired set state to Unauthenticated + + if (token != null && token?.length > 0) { + yield AuthenticationAuthenticated(); + } else { + yield AuthenticationUnauthenticated(); + } + } catch (error) { + yield AuthenticationFailed(error: error); + } + } + + /// Map [LoggedIn] to [AuthenticationState] + /// + /// ```dart + /// yield* _mapLoggedInToState(event); + /// ``` + Stream _mapLoggedInToState(LoggedIn event) async* { + yield AuthenticationLoading(); + await authInfoRepository.setAccessToken(event.accessToken); + await authInfoRepository + .setAccessTokenExpiration(event.accessTokenExpiration); + await authInfoRepository.setRefreshToken(event.refreshToken); + yield AuthenticationAuthenticated(); + } + + /// Map [LoggedIn] to [AuthenticationState] + /// + /// ```dart + /// yield* _mapLoggedInToState(event); + /// ``` + Stream _mapLoggedOutToState(LoggedOut event) async* { + yield AuthenticationLoading(); + await cvAuthService.logout(); + await authInfoRepository.deleteAccessToken(); + await authInfoRepository.deleteAccessTokenExpiration(); + await authInfoRepository.deleteRefreshToken(); + await authInfoRepository.deleteRefreshTokenExpiration(); + yield AuthenticationUnauthenticated(); + } +} diff --git a/lib/src/bloc/blocs/authentication/authentication_event.dart b/lib/src/bloc/blocs/authentication/authentication_event.dart new file mode 100644 index 0000000..f908226 --- /dev/null +++ b/lib/src/bloc/blocs/authentication/authentication_event.dart @@ -0,0 +1,37 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +/// [AuthenticationEvent] that must be dispatch to [AuthenticationBloc] +abstract class AuthenticationEvent extends Equatable { + AuthenticationEvent([List props = const []]) : super(props); +} + +/// Use [AppStarted] to begin auth process on startup +class AppStarted extends AuthenticationEvent {} + +/// Use [LoggedIn] to inform that user just logged in +class LoggedIn extends AuthenticationEvent { + final String accessToken; + final DateTime accessTokenExpiration; + final String refreshToken; + + LoggedIn({ + @required this.accessToken, + @required this.accessTokenExpiration, + @required this.refreshToken, + }) : super([ + accessToken, + accessTokenExpiration, + refreshToken, + ]); + + @override + String toString() => '$runtimeType{ ' + 'accessToken: $accessToken, ' + 'accessTokenExpiration: $accessTokenExpiration, ' + 'refreshToken: $refreshToken, ' + ' }'; +} + +/// Use [LoggedOut] to request logout +class LoggedOut extends AuthenticationEvent {} diff --git a/lib/src/bloc/blocs/authentication/authentication_state.dart b/lib/src/bloc/blocs/authentication/authentication_state.dart new file mode 100644 index 0000000..d2b9a2e --- /dev/null +++ b/lib/src/bloc/blocs/authentication/authentication_state.dart @@ -0,0 +1,30 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +abstract class AuthenticationState extends Equatable { + AuthenticationState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class AuthenticationUninitialized extends AuthenticationState {} + +class AuthenticationAuthenticated extends AuthenticationState {} + +class AuthenticationUnauthenticated extends AuthenticationState {} + +class AuthenticationLoading extends AuthenticationState {} + +class AuthenticationFailed extends AuthenticationState { + final dynamic error; + + AuthenticationFailed({@required this.error}) + : assert(error != null, 'No error given'), + super([error]); + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/element/element.dart b/lib/src/bloc/blocs/element/element.dart new file mode 100644 index 0000000..a927d20 --- /dev/null +++ b/lib/src/bloc/blocs/element/element.dart @@ -0,0 +1,3 @@ +export './element_bloc.dart'; +export './element_event.dart'; +export './element_state.dart'; diff --git a/lib/src/bloc/blocs/element/element_bloc.dart b/lib/src/bloc/blocs/element/element_bloc.dart new file mode 100644 index 0000000..10bad00 --- /dev/null +++ b/lib/src/bloc/blocs/element/element_bloc.dart @@ -0,0 +1,17 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Business Logic Component for profile elements +abstract class ElementBloc + extends Bloc { + final String _tag = '$ElementBloc<$T,$R,$E,$S>'; + + final R repository; + + T element; + + ElementBloc({@required this.repository}) + : assert(repository != null, 'No $R given'), + super(); +} diff --git a/lib/src/bloc/blocs/element/element_event.dart b/lib/src/bloc/blocs/element/element_event.dart new file mode 100644 index 0000000..ba5ee14 --- /dev/null +++ b/lib/src/bloc/blocs/element/element_event.dart @@ -0,0 +1,17 @@ +import 'package:social_cv_client_flutter/domain.dart'; + +mixin ElementInitialized { + String elementId; + T element; + + @override + String toString() => '$runtimeType{ ' + 'id: $elementId, ' + 'element: $element' + ' }'; +} + +mixin ElementRefresh { + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/bloc/blocs/element/element_state.dart b/lib/src/bloc/blocs/element/element_state.dart new file mode 100644 index 0000000..1824ab9 --- /dev/null +++ b/lib/src/bloc/blocs/element/element_state.dart @@ -0,0 +1,29 @@ +import 'package:social_cv_client_flutter/domain.dart'; + +mixin ElementUninitialized { + @override + String toString() => '$runtimeType{}'; +} + +mixin ElementLoading { + @override + String toString() => '$runtimeType{}'; +} + +mixin ElementLoaded { + T element; + + @override + String toString() => '$runtimeType{ ' + 'element: $element' + ' }'; +} + +mixin ElementFailure { + dynamic error; + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/element_list/element_list.dart b/lib/src/bloc/blocs/element_list/element_list.dart new file mode 100644 index 0000000..f84b7db --- /dev/null +++ b/lib/src/bloc/blocs/element_list/element_list.dart @@ -0,0 +1,3 @@ +export './element_list_bloc.dart'; +export './element_list_event.dart'; +export './element_list_state.dart'; diff --git a/lib/src/bloc/blocs/element_list/element_list_bloc.dart b/lib/src/bloc/blocs/element_list/element_list_bloc.dart new file mode 100644 index 0000000..21c9f1c --- /dev/null +++ b/lib/src/bloc/blocs/element_list/element_list_bloc.dart @@ -0,0 +1,25 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Business Logic Component for profile element list +abstract class ElementListBloc + extends Bloc { + final String _tag = '$ElementListBloc<$T>'; + + final R repository; + + String parentId; + List elements; + String ownerId; + + Cursor cursor; + + /// TODO: Add filter + /// TODO: Add sort + + ElementListBloc({@required this.repository}) + : assert(repository != null, 'No $R given'), + super(); +} diff --git a/lib/src/bloc/blocs/element_list/element_list_event.dart b/lib/src/bloc/blocs/element_list/element_list_event.dart new file mode 100644 index 0000000..c5ead90 --- /dev/null +++ b/lib/src/bloc/blocs/element_list/element_list_event.dart @@ -0,0 +1,29 @@ +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +mixin ElementListInitialized { + String parentId; + String ownerId; + Cursor cursor; + + @override + String toString() => '$runtimeType{ ' + 'parentId: $parentId, ' + 'ownerId: $ownerId, ' + 'cursor: $cursor' + ' }'; +} + +mixin ElementListRefresh { + @override + String toString() => '$runtimeType{}'; +} + +mixin ElementListLoadMore { + Cursor cursor; + + @override + String toString() => '$runtimeType{ ' + 'cursor: $cursor' + ' }'; +} diff --git a/lib/src/bloc/blocs/element_list/element_list_state.dart b/lib/src/bloc/blocs/element_list/element_list_state.dart new file mode 100644 index 0000000..2e54afa --- /dev/null +++ b/lib/src/bloc/blocs/element_list/element_list_state.dart @@ -0,0 +1,33 @@ +import 'package:social_cv_client_flutter/domain.dart'; + +mixin ElementListUninitialized { + @override + String toString() => '$runtimeType{}'; +} + +mixin ElementListLoading { + int count; + + @override + String toString() => '$runtimeType{ ' + 'count: $count' + ' }'; +} + +mixin ElementListLoaded { + List elements; + + @override + String toString() => '$runtimeType{ ' + 'elements: $elements' + ' }'; +} + +mixin ElementListFailure { + dynamic error; + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/entry/entry.dart b/lib/src/bloc/blocs/entry/entry.dart new file mode 100644 index 0000000..929412b --- /dev/null +++ b/lib/src/bloc/blocs/entry/entry.dart @@ -0,0 +1,3 @@ +export 'entry_bloc.dart'; +export 'entry_event.dart'; +export 'entry_state.dart'; diff --git a/lib/src/bloc/blocs/entry/entry_bloc.dart b/lib/src/bloc/blocs/entry/entry_bloc.dart new file mode 100644 index 0000000..7e29088 --- /dev/null +++ b/lib/src/bloc/blocs/entry/entry_bloc.dart @@ -0,0 +1,81 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Business Logic Component for Entry +class EntryBloc + extends ElementBloc { + final String _tag = '$EntryBloc'; + + EntryBloc({@required EntryRepository repository}) + : super(repository: repository); + + /// [_fallBackId] is used if [element] is never assigned and + /// an [EntryRefresh] is dispatched + String _fallBackId; + + @override + EntryState get initialState => EntryUninitialized(); + + @override + Stream mapEventToState(EntryEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is EntryInitialized) { + yield* _mapInitializedEventToState(event); + } else if (event is EntryRefresh) { + yield* _mapRefreshEventToState(event); + } + } + + /// -------------------------------------------------------------------------- + /// All Event map to State + /// -------------------------------------------------------------------------- + + /// Map [EntryInitialized] to [EntryState] + /// + /// ```dart + /// yield* _mapInitializedEventToState(event); + /// ``` + Stream _mapInitializedEventToState( + EntryInitialized event) async* { + print('$_tag:_mapInitializedEventToState($event)'); + try { + yield EntryLoading(); + + if (event.elementId != null) { + _fallBackId = event.elementId; + element = await repository.getById(event.elementId); + } else if (event.element != null) { + _fallBackId = event.element.id; + element = event.element; + } + + yield EntryLoaded(entry: element); + } catch (error) { + yield EntryFailure(error: error); + } + } + + /// Map [EntryRefresh] to [EntryState] + /// + /// ```dart + /// yield* _mapRefreshEventToState(event); + /// ``` + Stream _mapRefreshEventToState(EntryRefresh event) async* { + print('$_tag:_mapRefreshEventToState($event)'); + try { + yield EntryLoading(); + + element = await repository.getById( + element?.id ?? _fallBackId, + force: true, + ); + + _fallBackId = element.id; + + yield EntryLoaded(entry: element); + } catch (error) { + yield EntryFailure(error: error); + } + } +} diff --git a/lib/src/bloc/blocs/entry/entry_event.dart b/lib/src/bloc/blocs/entry/entry_event.dart new file mode 100644 index 0000000..a5608e1 --- /dev/null +++ b/lib/src/bloc/blocs/entry/entry_event.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// [EntryEvent] that must be dispatch to [EntryBloc] +abstract class EntryEvent extends Equatable { + EntryEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class EntryInitialized extends EntryEvent with ElementInitialized { + EntryInitialized({String entryId, EntryEntity entry}) + : assert( + entryId != null && entry == null, + '$EntryInitialized must be created with an $EntryEntity or its ID', + ), + assert( + entryId == null && entry != null, + '$EntryInitialized must be created with an $EntryEntity or its ID', + ), + super([entryId, entry]) { + elementId = entryId; + element = entry; + } + + @override + String toString() => '$runtimeType{ ' + 'entryId: $elementId, ' + 'element: $element' + ' }'; +} + +class EntryRefresh extends EntryEvent with ElementRefresh {} diff --git a/lib/src/bloc/blocs/entry/entry_state.dart b/lib/src/bloc/blocs/entry/entry_state.dart new file mode 100644 index 0000000..c19a5c1 --- /dev/null +++ b/lib/src/bloc/blocs/entry/entry_state.dart @@ -0,0 +1,42 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class EntryState extends Equatable { + EntryState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class EntryUninitialized extends EntryState + with ElementUninitialized {} + +class EntryLoading extends EntryState with ElementLoading {} + +class EntryLoaded extends EntryState with ElementLoaded { + EntryLoaded({EntryEntity entry}) : super([entry]) { + element = entry; + } + + @override + String toString() { + return '$runtimeType{ ' + 'entry: $element' + ' }'; + } +} + +class EntryFailure extends EntryState with ElementFailure { + EntryFailure({@required dynamic error}) + : assert(error != null, 'No error given'), + super([error]) { + this.error = error; + } + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/entry_list/entry_list.dart b/lib/src/bloc/blocs/entry_list/entry_list.dart new file mode 100644 index 0000000..f229303 --- /dev/null +++ b/lib/src/bloc/blocs/entry_list/entry_list.dart @@ -0,0 +1,3 @@ +export './entry_list_bloc.dart'; +export './entry_list_event.dart'; +export './entry_list_state.dart'; diff --git a/lib/src/bloc/blocs/entry_list/entry_list_bloc.dart b/lib/src/bloc/blocs/entry_list/entry_list_bloc.dart new file mode 100644 index 0000000..33c8f70 --- /dev/null +++ b/lib/src/bloc/blocs/entry_list/entry_list_bloc.dart @@ -0,0 +1,120 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Business Logic Component for Entry list +class EntryListBloc extends ElementListBloc { + final String _tag = '$EntryListBloc'; + + EntryListBloc({@required EntryRepository repository}) + : super(repository: repository); + + @override + EntryListState get initialState => EntryListUninitialized(); + + @override + Stream mapEventToState(EntryListEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is EntryListInitialized) { + yield* _mapEntryListInitializedEventToState(event); + } else if (event is EntryListRefresh) { + yield* _mapEntryListRefreshEventToState(event); + } else if (event is EntryListLoadMore) { + yield* _mapEntryListLoadMoreEventToState(event); + } + } + + /// -------------------------------------------------------------------------- + /// All Event map to State + /// -------------------------------------------------------------------------- + + /// Map [EntryListInitialized] to [EntryListState] + /// + /// ```dart + /// yield* _mapEntryListInitializedEventToState(event); + /// ``` + Stream _mapEntryListInitializedEventToState( + EntryListInitialized event) async* { + print('$_tag:_mapEntryListInitializedEventToState($event)'); + try { + /// TODO: Add refresh indicator stream + + parentId = event.parentId; + ownerId = event.ownerId; + cursor = event.cursor; + + elements = await _getEntries(cursor: event.cursor); + + yield EntryListLoaded(entries: elements); + } catch (error) { + yield EntryListFailure(error: error); + } + } + + /// Map [EntryListRefresh] to [EntryListState] + /// + /// ```dart + /// yield* _mapEntryListRefreshEventToState(event); + /// ``` + Stream _mapEntryListRefreshEventToState( + EntryListRefresh event) async* { + print('$_tag:_mapEntryListRefreshEventToState($event)'); + try { + /// TODO: Add refresh indicator stream + elements = await _getEntries(cursor: cursor); + yield EntryListLoaded(entries: elements); + } catch (error) { + yield EntryListFailure(error: error); + } + } + + /// Map [EntryListLoadMore] to [EntryListState] + /// + /// ```dart + /// yield* _mapEntryListRefreshEventToState(event); + /// ``` + Stream _mapEntryListLoadMoreEventToState( + EntryListLoadMore event) async* { + print('$_tag:_mapEntryListLoadMoreEventToState($event)'); + try { + /// TODO: Add load more indicator stream + + final List entries = await _getEntries( + cursor: event.cursor.copyWith(offset: elements.length), + ); + + /// Append to elements + elements.addAll(entries); + + /// Save cursor limit if use list refreshed + cursor = cursor.copyWith(limit: elements.length); + + yield EntryListLoaded(entries: elements); + } catch (error) { + yield EntryListFailure(error: error); + } + } + + FutureOr> _getEntries({@required Cursor cursor}) async { + print('$_tag:_getEntries({cursor: $cursor})'); + if (parentId != null) { + return await repository.getEntriesFromGroup( + parentId, + cursor: cursor, + ); + } else if (ownerId != null) { + return await repository.getEntriesFromUser( + ownerId, + cursor: cursor, + ); + } else { + return await repository.getList( + cursor: cursor, + ); + } + } +} diff --git a/lib/src/bloc/blocs/entry_list/entry_list_event.dart b/lib/src/bloc/blocs/entry_list/entry_list_event.dart new file mode 100644 index 0000000..22e5e7e --- /dev/null +++ b/lib/src/bloc/blocs/entry_list/entry_list_event.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// [EntryListEvent] that must be dispatch to [EntryListBloc] +abstract class EntryListEvent extends Equatable { + EntryListEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class EntryListInitialized extends EntryListEvent + with ElementListInitialized { + EntryListInitialized({ + String parentGroupId, + String ownerId, + Cursor cursor, + }) : assert( + parentGroupId != null && ownerId == null, + '$EntryListInitialized must be created with a parentId or an ownerId', + ), + assert( + parentGroupId == null && ownerId != null, + '$EntryListInitialized must be created with a parentId or an ownerId', + ), + super([parentGroupId, ownerId]) { + this.parentId = parentGroupId; + this.ownerId = ownerId; + this.cursor = cursor; + } + + @override + String toString() => '$runtimeType{ ' + 'parentId: $parentId, ' + 'ownerId: $ownerId, ' + 'cursor: $cursor' + ' }'; +} + +class EntryListRefresh extends EntryListEvent + with ElementListRefresh {} + +class EntryListLoadMore extends EntryListEvent + with ElementListLoadMore { + EntryListLoadMore({Cursor cursor}) + : assert(cursor != null), + super([cursor]) { + this.cursor = cursor; + } + + @override + String toString() => '$runtimeType{ ' + 'cursor: $cursor' + ' }'; +} diff --git a/lib/src/bloc/blocs/entry_list/entry_list_state.dart b/lib/src/bloc/blocs/entry_list/entry_list_state.dart new file mode 100644 index 0000000..ffef042 --- /dev/null +++ b/lib/src/bloc/blocs/entry_list/entry_list_state.dart @@ -0,0 +1,52 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class EntryListState extends Equatable { + EntryListState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class EntryListUninitialized extends EntryListState + with ElementListUninitialized {} + +class EntryListLoading extends EntryListState + with ElementListLoading { + EntryListLoading({int count = 0}) : super([count]) { + this.count = count; + } + + @override + String toString() => '$runtimeType{ ' + 'count: $count' + ' }'; +} + +class EntryListLoaded extends EntryListState + with ElementListLoaded { + EntryListLoaded({@required List entries}) : super([entries]) { + elements = entries; + } + + @override + String toString() => '$runtimeType{ ' + 'entries: $elements' + ' }'; +} + +class EntryListFailure extends EntryListState + with ElementListFailure { + EntryListFailure({@required dynamic error}) + : assert(error != null, 'No error given'), + super([error]) { + this.error = error; + } + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/group/group.dart b/lib/src/bloc/blocs/group/group.dart new file mode 100644 index 0000000..19c3ef1 --- /dev/null +++ b/lib/src/bloc/blocs/group/group.dart @@ -0,0 +1,3 @@ +export 'group_bloc.dart'; +export 'group_event.dart'; +export 'group_state.dart'; diff --git a/lib/src/bloc/blocs/group/group_bloc.dart b/lib/src/bloc/blocs/group/group_bloc.dart new file mode 100644 index 0000000..c49e91a --- /dev/null +++ b/lib/src/bloc/blocs/group/group_bloc.dart @@ -0,0 +1,83 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Business Logic Component for Group +class GroupBloc + extends ElementBloc { + final String _tag = '$GroupBloc'; + + GroupBloc({@required GroupRepository repository}) + : super(repository: repository); + + /// [_fallBackId] is used if [element] is never assigned and + /// an [GroupRefresh] is dispatched + String _fallBackId; + + @override + GroupState get initialState => GroupUninitialized(); + + @override + Stream mapEventToState(GroupEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is GroupInitialized) { + yield* _mapInitializedEventToState(event); + } else if (event is GroupRefresh) { + yield* _mapRefreshEventToState(event); + } + } + + /// -------------------------------------------------------------------------- + /// All Event map to State + /// -------------------------------------------------------------------------- + + Stream _mapInitializedEventToState( + GroupInitialized event) async* { + print('$_tag:_mapInitializedEventToState($event)'); + try { + yield GroupLoading(); + + if (event.elementId != null) { + _fallBackId = event.elementId; + element = await repository.getById(event.elementId); + } else if (event.element != null) { + _fallBackId = event.element.id; + element = event.element; + } + + yield GroupLoaded(group: element); + } catch (error) { + yield GroupFailure(error: error); + } + } + + /// Map [GroupRefresh] to [GroupState] + /// + /// ```dart + /// yield* _mapRefreshEventToState(event); + /// ``` + Stream _mapRefreshEventToState(GroupRefresh event) async* { + print('$_tag:_mapRefreshEventToState($event)'); + try { + yield GroupLoading(); + + element = await repository.getById( + element?.id ?? _fallBackId, + force: true, + ); + + _fallBackId = element.id; + + yield GroupLoaded(group: element); + } catch (error) { + yield GroupFailure(error: error); + } + } + + @override + String toString() { + return '$runtimeType{ ' + 'repository: $repository' + ' }'; + } +} diff --git a/lib/src/bloc/blocs/group/group_event.dart b/lib/src/bloc/blocs/group/group_event.dart new file mode 100644 index 0000000..21fe121 --- /dev/null +++ b/lib/src/bloc/blocs/group/group_event.dart @@ -0,0 +1,33 @@ +import 'package:equatable/equatable.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// [GroupEvent] that must be dispatch to [GroupBloc] + +abstract class GroupEvent extends Equatable { + GroupEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class GroupInitialized extends GroupEvent with ElementInitialized { + GroupInitialized({String groupId, GroupEntity group}) + : assert( + groupId != null && group == null, + '$GroupInitialized must be created with a $GroupEntity or its ID', + ), + assert( + groupId == null && group != null, + '$GroupInitialized must be created with a $GroupEntity or its ID', + ), + super([groupId, group]) { + this.elementId = groupId; + this.element = group; + } + + @override + String toString() => '$runtimeType{ id: $elementId, element: $element }'; +} + +class GroupRefresh extends GroupEvent with ElementRefresh {} diff --git a/lib/src/bloc/blocs/group/group_state.dart b/lib/src/bloc/blocs/group/group_state.dart new file mode 100644 index 0000000..ba68ce5 --- /dev/null +++ b/lib/src/bloc/blocs/group/group_state.dart @@ -0,0 +1,42 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class GroupState extends Equatable { + GroupState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class GroupUninitialized extends GroupState + with ElementUninitialized {} + +class GroupLoading extends GroupState with ElementLoading {} + +class GroupLoaded extends GroupState with ElementLoaded { + GroupLoaded({GroupEntity group}) : super([group]) { + element = group; + } + + @override + String toString() { + return '$runtimeType{ ' + 'group: $element' + ' }'; + } +} + +class GroupFailure extends GroupState with ElementFailure { + GroupFailure({@required dynamic error}) + : assert(error != null, 'No error given'), + super([error]) { + this.error = error; + } + + @override + String toString() => '$runtimeType { ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/group_list/group_list.dart b/lib/src/bloc/blocs/group_list/group_list.dart new file mode 100644 index 0000000..33f2978 --- /dev/null +++ b/lib/src/bloc/blocs/group_list/group_list.dart @@ -0,0 +1,3 @@ +export './group_list_bloc.dart'; +export './group_list_event.dart'; +export './group_list_state.dart'; diff --git a/lib/src/bloc/blocs/group_list/group_list_bloc.dart b/lib/src/bloc/blocs/group_list/group_list_bloc.dart new file mode 100644 index 0000000..fdf1574 --- /dev/null +++ b/lib/src/bloc/blocs/group_list/group_list_bloc.dart @@ -0,0 +1,120 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Business Logic Component for Group list +class GroupListBloc extends ElementListBloc { + final String _tag = '$GroupListBloc'; + + GroupListBloc({@required GroupRepository repository}) + : super(repository: repository); + + @override + GroupListState get initialState => GroupListUninitialized(); + + @override + Stream mapEventToState(GroupListEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is GroupListInitialized) { + yield* _mapGroupListInitializedEventToState(event); + } else if (event is GroupListRefresh) { + yield* _mapGroupListRefreshEventToState(event); + } else if (event is GroupListLoadMore) { + yield* _mapGroupListLoadMoreEventToState(event); + } + } + + /// -------------------------------------------------------------------------- + /// All Event map to State + /// -------------------------------------------------------------------------- + + /// Map [GroupListInitialized] to [GroupListState] + /// + /// ```dart + /// yield* _mapGroupListInitializedEventToState(event); + /// ``` + Stream _mapGroupListInitializedEventToState( + GroupListInitialized event) async* { + print('$_tag:_mapGroupListInitializedEventToState($event)'); + try { + /// TODO: Add refresh indicator stream + + parentId = event.parentId; + ownerId = event.ownerId; + cursor = event.cursor; + + elements = await _getGroups(cursor: cursor); + + yield GroupListLoaded(groups: elements); + } catch (error) { + yield GroupListFailure(error: error); + } + } + + /// Map [GroupListRefresh] to [GroupListState] + /// + /// ```dart + /// yield* _mapGroupListRefreshEventToState(event); + /// ``` + Stream _mapGroupListRefreshEventToState( + GroupListRefresh event) async* { + print('$_tag:_mapGroupListRefreshEventToState($event)'); + try { + /// TODO: Add refresh indicator stream + elements = await _getGroups(cursor: cursor); + yield GroupListLoaded(groups: elements); + } catch (error) { + yield GroupListFailure(error: error); + } + } + + /// Map [GroupListLoadMore] to [GroupListState] + /// + /// ```dart + /// yield* _mapGroupListLoadMoreEventToState(event); + /// ``` + Stream _mapGroupListLoadMoreEventToState( + GroupListLoadMore event) async* { + print('$_tag:_mapGroupListLoadMoreEventToState($event)'); + try { + /// TODO: Add load more indicator stream + + final List groups = await _getGroups( + cursor: event.cursor.copyWith(offset: elements.length), + ); + + /// Append to elements + elements.addAll(groups); + + /// Save cursor limit if use list refreshed + cursor = cursor.copyWith(limit: elements.length); + + yield GroupListLoaded(groups: elements); + } catch (error) { + yield GroupListFailure(error: error); + } + } + + FutureOr> _getGroups({@required Cursor cursor}) async { + print('$_tag:_getGroups({cursor: $cursor})'); + if (parentId != null) { + return await repository.getGroupsFromPart( + parentId, + cursor: cursor, + ); + } else if (ownerId != null) { + return await repository.getGroupsFromUser( + ownerId, + cursor: cursor, + ); + } else { + return await repository.getList( + cursor: cursor, + ); + } + } +} diff --git a/lib/src/bloc/blocs/group_list/group_list_event.dart b/lib/src/bloc/blocs/group_list/group_list_event.dart new file mode 100644 index 0000000..bce5e24 --- /dev/null +++ b/lib/src/bloc/blocs/group_list/group_list_event.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// [GroupListEvent] that must be dispatch to [GroupListBloc] +abstract class GroupListEvent extends Equatable { + GroupListEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class GroupListInitialized extends GroupListEvent + with ElementListInitialized { + GroupListInitialized({ + String parentPartId, + String ownerId, + Cursor cursor, + }) : assert( + parentPartId != null && ownerId == null, + '$GroupListInitialized must be created with a parentId or an ownerId', + ), + assert( + parentPartId == null && ownerId != null, + '$GroupListInitialized must be created with a parentId or an ownerId', + ), + super([parentPartId, ownerId]) { + this.parentId = parentPartId; + this.ownerId = ownerId; + this.cursor = cursor; + } + + @override + String toString() => '$runtimeType{ ' + 'parentPartId: $parentId, ' + 'ownerId: $ownerId, ' + 'cursor: $cursor' + ' }'; +} + +class GroupListRefresh extends GroupListEvent + with ElementListRefresh {} + +class GroupListLoadMore extends GroupListEvent + with ElementListLoadMore { + GroupListLoadMore({Cursor cursor}) + : assert(cursor != null), + super([cursor]) { + this.cursor = cursor; + } + + @override + String toString() => '$runtimeType{ ' + 'cursor: $cursor' + ' }'; +} diff --git a/lib/src/bloc/blocs/group_list/group_list_state.dart b/lib/src/bloc/blocs/group_list/group_list_state.dart new file mode 100644 index 0000000..a0b2875 --- /dev/null +++ b/lib/src/bloc/blocs/group_list/group_list_state.dart @@ -0,0 +1,52 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class GroupListState extends Equatable { + GroupListState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class GroupListUninitialized extends GroupListState + with ElementListUninitialized {} + +class GroupListLoading extends GroupListState + with ElementListLoading { + GroupListLoading({int count = 0}) : super([count]) { + this.count = count; + } + + @override + String toString() => '$runtimeType{ ' + 'count: $count' + ' }'; +} + +class GroupListLoaded extends GroupListState + with ElementListLoaded { + GroupListLoaded({@required List groups}) : super([groups]) { + elements = groups; + } + + @override + String toString() => '$runtimeType{ ' + 'groups: $elements' + ' }'; +} + +class GroupListFailure extends GroupListState + with ElementListFailure { + GroupListFailure({@required dynamic error}) + : assert(error != null, 'No error given'), + super([error]) { + this.error = error; + } + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/identity/identity.dart b/lib/src/bloc/blocs/identity/identity.dart new file mode 100644 index 0000000..a4fe72d --- /dev/null +++ b/lib/src/bloc/blocs/identity/identity.dart @@ -0,0 +1,3 @@ +export './identity_bloc.dart'; +export './identity_event.dart'; +export './identity_state.dart'; diff --git a/lib/src/bloc/blocs/identity/identity_bloc.dart b/lib/src/bloc/blocs/identity/identity_bloc.dart new file mode 100644 index 0000000..8245775 --- /dev/null +++ b/lib/src/bloc/blocs/identity/identity_bloc.dart @@ -0,0 +1,66 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Business Logic Component for Account +class IdentityBloc extends Bloc { + final String _tag = '$IdentityBloc'; + + final IdentityRepository identityRepo; + final AuthenticationBloc authBloc; + StreamSubscription authBlocSubscription; + + IdentityBloc({ + @required this.identityRepo, + @required this.authBloc, + }) : assert(identityRepo != null, 'No $IdentityRepository given'), + super() { + authBlocSubscription = authBloc.state.listen((state) { + if (state is AuthenticationAuthenticated) { + dispatch(IdentityRefresh()); + } + }); + } + + @override + void dispose() { + authBlocSubscription.cancel(); + super.dispose(); + } + + @override + IdentityState get initialState => IdentityUninitialized(); + + @override + Stream mapEventToState(IdentityEvent event) async* { + print('$_tag:mapEventToState($event)'); + + if (event is IdentityRefresh) { + yield* _mapAccountRefreshToState(event); + } + } + + /// ----------------------------------------------------------------------- + /// All Event map to State + /// ----------------------------------------------------------------------- + + /// Map [IdentityRefresh] to [IdentityState] + /// + /// ```dart + /// yield* _mapAccountRefreshToState(event); + /// ``` + Stream _mapAccountRefreshToState( + IdentityRefresh event) async* { + try { + yield IdentityLoading(); + final userModel = await identityRepo.getIdentity(); + yield IdentityLoaded(user: userModel); + } catch (error) { + print('$_tag:_mapAccountRefreshToState -> ${error.runtimeType}'); + yield IdentityFailed(error: error); + } + } +} diff --git a/lib/src/bloc/blocs/identity/identity_event.dart b/lib/src/bloc/blocs/identity/identity_event.dart new file mode 100644 index 0000000..c30d31f --- /dev/null +++ b/lib/src/bloc/blocs/identity/identity_event.dart @@ -0,0 +1,11 @@ +import 'package:equatable/equatable.dart'; + +/// [IdentityEvent] that must be dispatch to [AccountBloc] +abstract class IdentityEvent extends Equatable { + IdentityEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class IdentityRefresh extends IdentityEvent {} diff --git a/lib/src/bloc/blocs/identity/identity_state.dart b/lib/src/bloc/blocs/identity/identity_state.dart new file mode 100644 index 0000000..55ee837 --- /dev/null +++ b/lib/src/bloc/blocs/identity/identity_state.dart @@ -0,0 +1,40 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class IdentityState extends Equatable { + IdentityState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class IdentityUninitialized extends IdentityState {} + +class IdentityLoading extends IdentityState {} + +class IdentityLoaded extends IdentityState { + final UserEntity user; + + IdentityLoaded({ + @required this.user, + }) : super([user]); + + @override + String toString() => '$runtimeType{ ' + 'userModel: $user' + ' }'; +} + +class IdentityFailed extends IdentityState { + final dynamic error; + + IdentityFailed({@required this.error}) + : assert(error != null, 'No error given'), + super([error]); + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/login/login.dart b/lib/src/bloc/blocs/login/login.dart new file mode 100644 index 0000000..d3a5e51 --- /dev/null +++ b/lib/src/bloc/blocs/login/login.dart @@ -0,0 +1,3 @@ +export './login_bloc.dart'; +export './login_event.dart'; +export './login_state.dart'; diff --git a/lib/src/bloc/blocs/login/login_bloc.dart b/lib/src/bloc/blocs/login/login_bloc.dart new file mode 100644 index 0000000..d71f230 --- /dev/null +++ b/lib/src/bloc/blocs/login/login_bloc.dart @@ -0,0 +1,60 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Business Logic Component for Login +class LoginBloc extends Bloc { + final String _tag = '$LoginBloc'; + + final CVAuthService cvAuthService; + + LoginBloc({ + @required this.cvAuthService, + }) : assert(cvAuthService != null, 'No $CVAuthService given'), + super(); + + @override + LoginState get initialState => LoginInitial(); + + @override + Stream mapEventToState(LoginEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is LoginButtonPressed) { + yield* _mapLoginButtonPressedToState(event); + } + } + + /// -------------------------------------------------------------------------- + /// All Event map to State + /// -------------------------------------------------------------------------- + + /// Map [LoginButtonPressed] to [LoginState] + /// + /// ```dart + /// yield* _mapLoginButtonPressedToState(event); + /// ``` + Stream _mapLoginButtonPressedToState( + LoginButtonPressed event) async* { + try { + if (event is LoginButtonPressed) { + yield LoginLoading(); + + final auth = await cvAuthService.authenticate( + email: event.email, + password: event.password, + ); + + yield LoginSucceed( + accessToken: auth.accessToken, + accessTokenExpiration: auth.accessTokenExpiration, + refreshToken: auth.refreshToken, + ); + } + } catch (error) { + yield LoginFailure(error: error); + } + } +} diff --git a/lib/src/bloc/blocs/login/login_event.dart b/lib/src/bloc/blocs/login/login_event.dart new file mode 100644 index 0000000..636db38 --- /dev/null +++ b/lib/src/bloc/blocs/login/login_event.dart @@ -0,0 +1,26 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +/// [LoginEvent] that must be dispatch to [LoginBloc] +abstract class LoginEvent extends Equatable { + LoginEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class LoginButtonPressed extends LoginEvent { + final String email; + final String password; + + LoginButtonPressed({ + @required this.email, + @required this.password, + }) : super([email, password]); + + @override + String toString() => '$runtimeType{ ' + 'username: $email, ' + 'password: HIDDEN' + ' }'; +} diff --git a/lib/src/bloc/blocs/login/login_state.dart b/lib/src/bloc/blocs/login/login_state.dart new file mode 100644 index 0000000..52377a2 --- /dev/null +++ b/lib/src/bloc/blocs/login/login_state.dart @@ -0,0 +1,45 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +abstract class LoginState extends Equatable { + LoginState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class LoginInitial extends LoginState {} + +class LoginLoading extends LoginState {} + +class LoginFailure extends LoginState { + final dynamic error; + + LoginFailure({@required this.error}) + : assert(error != null, 'No error given'), + super([error]); + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} + +class LoginSucceed extends LoginState { + final String accessToken; + final DateTime accessTokenExpiration; + final String refreshToken; + + LoginSucceed({ + @required this.accessToken, + @required this.accessTokenExpiration, + @required this.refreshToken, + }) : super([accessToken, accessTokenExpiration, refreshToken]); + + @override + String toString() => '$runtimeType{ ' + 'accessToken: $accessToken, ' + 'accessToken: $accessTokenExpiration, ' + 'refreshToken: $refreshToken ' + ' }'; +} diff --git a/lib/src/bloc/blocs/part/part.dart b/lib/src/bloc/blocs/part/part.dart new file mode 100644 index 0000000..ebfbda0 --- /dev/null +++ b/lib/src/bloc/blocs/part/part.dart @@ -0,0 +1,3 @@ +export 'part_bloc.dart'; +export 'part_event.dart'; +export 'part_state.dart'; diff --git a/lib/src/bloc/blocs/part/part_bloc.dart b/lib/src/bloc/blocs/part/part_bloc.dart new file mode 100644 index 0000000..0a3e08c --- /dev/null +++ b/lib/src/bloc/blocs/part/part_bloc.dart @@ -0,0 +1,80 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Business Logic Component for Part +class PartBloc + extends ElementBloc { + final String _tag = '$PartBloc'; + + PartBloc({@required PartRepository repository}) + : super(repository: repository); + + /// [_fallBackId] is used if [element] is never assigned and + /// an [PartRefresh] is dispatched + String _fallBackId; + + @override + PartState get initialState => PartUninitialized(); + + @override + Stream mapEventToState(PartEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is PartInitialized) { + yield* _mapInitializedEventToState(event); + } else if (event is PartRefresh) { + yield* _mapRefreshEventToState(event); + } + } + + /// -------------------------------------------------------------------------- + /// All Event map to State + /// -------------------------------------------------------------------------- + + /// Map [PartInitialized] to [PartState] + /// + /// ```dart + /// yield* _mapInitializedEventToState(event); + /// ``` + Stream _mapInitializedEventToState(PartInitialized event) async* { + print('$_tag:_mapInitializedEventToState($event)'); + try { + yield PartLoading(); + + if (event.elementId != null) { + _fallBackId = event.elementId; + element = await repository.getById(event.elementId); + } else if (event.element != null) { + _fallBackId = event.element.id; + element = event.element; + } + + yield PartLoaded(part: element); + } catch (error) { + yield PartFailure(error: error); + } + } + + /// Map [PartRefresh] to [PartState] + /// + /// ```dart + /// yield* _mapRefreshEventToState(event); + /// ``` + Stream _mapRefreshEventToState(PartRefresh event) async* { + print('$_tag:_mapRefreshEventToState($event)'); + try { + yield PartLoading(); + + element = await repository.getById( + element?.id ?? _fallBackId, + force: true, + ); + + _fallBackId = element.id; + + yield PartLoaded(part: element); + } catch (error) { + yield PartFailure(error: error); + } + } +} diff --git a/lib/src/bloc/blocs/part/part_event.dart b/lib/src/bloc/blocs/part/part_event.dart new file mode 100644 index 0000000..1173417 --- /dev/null +++ b/lib/src/bloc/blocs/part/part_event.dart @@ -0,0 +1,34 @@ +import 'package:equatable/equatable.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class PartEvent extends Equatable { + PartEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class PartInitialized extends PartEvent with ElementInitialized { + PartInitialized({String partId, PartEntity part}) + : assert( + partId != null && part == null, + '$PartInitialized must be created with a $PartEntity or its ID', + ), + assert( + partId == null && part != null, + '$PartInitialized must be created with a $PartEntity or its ID', + ), + super([partId, part]) { + elementId = partId; + element = part; + } + + @override + String toString() => '$runtimeType{ ' + 'id: $elementId, ' + 'part: $element' + ' }'; +} + +class PartRefresh extends PartEvent with ElementRefresh {} diff --git a/lib/src/bloc/blocs/part/part_state.dart b/lib/src/bloc/blocs/part/part_state.dart new file mode 100644 index 0000000..d14f36c --- /dev/null +++ b/lib/src/bloc/blocs/part/part_state.dart @@ -0,0 +1,40 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class PartState extends Equatable { + PartState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class PartUninitialized extends PartState + with ElementUninitialized {} + +class PartLoading extends PartState with ElementLoading {} + +class PartLoaded extends PartState with ElementLoaded { + PartLoaded({PartEntity part}) : super([part]) { + element = part; + } + + @override + String toString() { + return '$runtimeType{ part: $element }'; + } +} + +class PartFailure extends PartState with ElementFailure { + PartFailure({@required dynamic error}) + : assert(error != null, 'No error given'), + super([error]) { + this.error = error; + } + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/part_list/part_list.dart b/lib/src/bloc/blocs/part_list/part_list.dart new file mode 100644 index 0000000..85aa6b1 --- /dev/null +++ b/lib/src/bloc/blocs/part_list/part_list.dart @@ -0,0 +1,3 @@ +export './part_list_bloc.dart'; +export './part_list_event.dart'; +export './part_list_state.dart'; diff --git a/lib/src/bloc/blocs/part_list/part_list_bloc.dart b/lib/src/bloc/blocs/part_list/part_list_bloc.dart new file mode 100644 index 0000000..7d2a5a8 --- /dev/null +++ b/lib/src/bloc/blocs/part_list/part_list_bloc.dart @@ -0,0 +1,110 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Business Logic Component for Part list +class PartListBloc extends ElementListBloc { + final String _tag = '$PartListBloc'; + + PartListBloc({@required PartRepository repository}) + : super(repository: repository); + + @override + PartListState get initialState => PartListUninitialized(); + + @override + Stream mapEventToState(PartListEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is PartListInitialized) { + yield* _mapPartListInitializedEventToState(event); + } else if (event is PartListRefresh) { + yield* _mapPartListRefreshEventToState(event); + } else if (event is PartListLoadMore) { + yield* _mapPartListLoadMoreEventToState(event); + } + } + + /// -------------------------------------------------------------------------- + /// All Event map to State + /// -------------------------------------------------------------------------- + + Stream _mapPartListInitializedEventToState( + PartListInitialized event) async* { + print('$_tag:_mapPartListInitializedEventToState($event)'); + try { + /// TODO: Add refresh indicator stream + + parentId = event.parentId; + ownerId = event.ownerId; + cursor = event.cursor; + + elements = await _getParts(cursor: cursor); + + yield PartListLoaded(parts: elements); + } catch (error) { + yield PartListFailure(error: error); + } + } + + Stream _mapPartListRefreshEventToState( + PartListRefresh event) async* { + print('$_tag:_mapPartListRefreshEventToState($event)'); + try { + /// TODO: Add refresh indicator stream + elements = await _getParts(cursor: cursor); + yield PartListLoaded(parts: elements); + } catch (error) { + yield PartListFailure(error: error); + } + } + + /// Map [PartListLoadMore] to [PartListState] + /// + /// ```dart + /// yield* _mapPartListLoadMoreEventToState(event); + /// ``` + Stream _mapPartListLoadMoreEventToState( + PartListLoadMore event) async* { + print('$_tag:_mapPartListLoadMoreEventToState($event)'); + try { + /// TODO: Add load more indicator stream + + List parts = await _getParts( + cursor: event.cursor.copyWith(offset: elements.length), + ); + + /// Append to elements + elements.addAll(parts); + + /// Save cursor limit if use list refreshed + cursor = cursor.copyWith(limit: elements.length); + + yield PartListLoaded(parts: elements); + } catch (error) { + yield PartListFailure(error: error); + } + } + + FutureOr> _getParts({@required Cursor cursor}) async { + print('$_tag:_getParts({cursor: $cursor})'); + if (parentId != null) { + return await repository.getPartsFromProfile( + parentId, + cursor: cursor, + ); + } else if (ownerId != null) { + return await repository.getPartsFromUser( + ownerId, + cursor: cursor, + ); + } else { + return await repository.getList( + cursor: cursor, + ); + } + } +} diff --git a/lib/src/bloc/blocs/part_list/part_list_event.dart b/lib/src/bloc/blocs/part_list/part_list_event.dart new file mode 100644 index 0000000..8f69057 --- /dev/null +++ b/lib/src/bloc/blocs/part_list/part_list_event.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// [PartListEvent] that must be dispatch to [PartListBloc] +abstract class PartListEvent extends Equatable { + PartListEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class PartListInitialized extends PartListEvent + with ElementListInitialized { + PartListInitialized({ + String parentProfileId, + String ownerId, + Cursor cursor, + }) : assert( + parentProfileId != null && ownerId == null, + '$PartListInitialized must be created with a parentId or an ownerId', + ), + assert( + parentProfileId == null && ownerId != null, + '$PartListInitialized must be created with a parentId or an ownerId', + ), + super([parentProfileId, ownerId]) { + this.parentId = parentProfileId; + this.ownerId = ownerId; + this.cursor = cursor; + } + + @override + String toString() => '$runtimeType{ ' + 'parentProfileId: $parentId, ' + 'ownerId: $ownerId, ' + 'cursor: $cursor' + ' }'; +} + +class PartListRefresh extends PartListEvent + with ElementListRefresh {} + +class PartListLoadMore extends PartListEvent + with ElementListLoadMore { + PartListLoadMore({Cursor cursor}) + : assert(cursor != null), + super([cursor]) { + this.cursor = cursor; + } + + @override + String toString() => '$runtimeType{ ' + 'cursor: $cursor' + ' }'; +} diff --git a/lib/src/bloc/blocs/part_list/part_list_state.dart b/lib/src/bloc/blocs/part_list/part_list_state.dart new file mode 100644 index 0000000..3ffd7a1 --- /dev/null +++ b/lib/src/bloc/blocs/part_list/part_list_state.dart @@ -0,0 +1,51 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class PartListState extends Equatable { + PartListState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class PartListUninitialized extends PartListState + with ElementListUninitialized {} + +class PartListLoading extends PartListState + with ElementListLoading { + PartListLoading({int count = 0}) : super([count]) { + this.count = count; + } + + @override + String toString() => '$runtimeType{ ' + 'count: $count' + ' }'; +} + +class PartListLoaded extends PartListState with ElementListLoaded { + PartListLoaded({@required List parts}) : super([parts]) { + elements = parts; + } + + @override + String toString() => '$runtimeType{ ' + 'parts: $elements' + ' }'; +} + +class PartListFailure extends PartListState + with ElementListFailure { + PartListFailure({@required dynamic error}) + : assert(error != null, 'No error given'), + super([error]) { + this.error = error; + } + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/profile/profile.dart b/lib/src/bloc/blocs/profile/profile.dart new file mode 100644 index 0000000..a642f12 --- /dev/null +++ b/lib/src/bloc/blocs/profile/profile.dart @@ -0,0 +1,3 @@ +export 'profile_bloc.dart'; +export 'profile_event.dart'; +export 'profile_state.dart'; diff --git a/lib/src/bloc/blocs/profile/profile_bloc.dart b/lib/src/bloc/blocs/profile/profile_bloc.dart new file mode 100644 index 0000000..2306284 --- /dev/null +++ b/lib/src/bloc/blocs/profile/profile_bloc.dart @@ -0,0 +1,81 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Business Logic Component for Profile +class ProfileBloc extends ElementBloc { + final String _tag = '$ProfileBloc'; + + ProfileBloc({@required ProfileRepository repository}) + : super(repository: repository); + + /// [_fallBackId] is used if [element] is never assigned and + /// an [ProfileRefresh] is dispatched + String _fallBackId; + + @override + ProfileState get initialState => ProfileUninitialized(); + + @override + Stream mapEventToState(ProfileEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is ProfileInitialized) { + yield* _mapInitializedEventToState(event); + } else if (event is ProfileRefresh) { + yield* _mapRefreshEventToState(event); + } + } + + /// -------------------------------------------------------------------------- + /// All Event map to State + /// -------------------------------------------------------------------------- + + /// Map [ProfileInitialized] to [ProfileState] + /// + /// ```dart + /// yield* _mapInitializedEventToState(event); + /// ``` + Stream _mapInitializedEventToState( + ProfileInitialized event) async* { + print('$_tag:_mapInitializedEventToState($event)'); + try { + yield ProfileLoading(); + + if (event.elementId != null) { + _fallBackId = event.elementId; + element = await await repository.getById(event.elementId); + } else if (event.element != null) { + _fallBackId = event.element.id; + element = event.element; + } + + yield ProfileLoaded(profile: element); + } catch (error) { + yield ProfileFailure(error: error); + } + } + + /// Map [ProfileRefresh] to [ProfileState] + /// + /// ```dart + /// yield* _mapRefreshEventToState(event); + /// ``` + Stream _mapRefreshEventToState(ProfileRefresh event) async* { + print('$_tag:_mapRefreshEventToState($event)'); + try { + yield ProfileLoading(); + + element = await repository.getById( + element?.id ?? _fallBackId, + force: true, + ); + + _fallBackId = element.id; + + yield ProfileLoaded(profile: element); + } catch (error) { + yield ProfileFailure(error: error); + } + } +} diff --git a/lib/src/bloc/blocs/profile/profile_event.dart b/lib/src/bloc/blocs/profile/profile_event.dart new file mode 100644 index 0000000..0e58fb0 --- /dev/null +++ b/lib/src/bloc/blocs/profile/profile_event.dart @@ -0,0 +1,37 @@ +import 'package:equatable/equatable.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// [ProfileEvent] that must be dispatch to [ProfileBloc] + +abstract class ProfileEvent extends Equatable { + ProfileEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class ProfileInitialized extends ProfileEvent + with ElementInitialized { + ProfileInitialized({String profileId, ProfileEntity profile}) + : assert( + profileId != null && profile == null, + '$ProfileInitialized must be created with an $ProfileEntity or its ID', + ), + assert( + profileId == null && profile != null, + '$ProfileInitialized must be created with an $ProfileEntity or its ID', + ), + super([profileId, profile]) { + this.elementId = profileId; + this.element = profile; + } + + @override + String toString() => '$runtimeType{ ' + 'id: $elementId, ' + 'element: $element' + ' }'; +} + +class ProfileRefresh extends ProfileEvent with ElementRefresh {} diff --git a/lib/src/bloc/blocs/profile/profile_state.dart b/lib/src/bloc/blocs/profile/profile_state.dart new file mode 100644 index 0000000..8c6cecc --- /dev/null +++ b/lib/src/bloc/blocs/profile/profile_state.dart @@ -0,0 +1,42 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class ProfileState extends Equatable { + ProfileState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class ProfileUninitialized extends ProfileState + with ElementUninitialized {} + +class ProfileLoading extends ProfileState with ElementLoading {} + +class ProfileLoaded extends ProfileState with ElementLoaded { + ProfileLoaded({ProfileEntity profile}) : super([profile]) { + element = profile; + } + + @override + String toString() { + return '$runtimeType{ ' + 'element: $element' + ' }'; + } +} + +class ProfileFailure extends ProfileState with ElementFailure { + ProfileFailure({@required dynamic error}) + : assert(error != null, 'No error given'), + super([error]) { + this.error = error; + } + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/profile_list/profile_list.dart b/lib/src/bloc/blocs/profile_list/profile_list.dart new file mode 100644 index 0000000..0d7c3b8 --- /dev/null +++ b/lib/src/bloc/blocs/profile_list/profile_list.dart @@ -0,0 +1,3 @@ +export './profile_list_bloc.dart'; +export './profile_list_event.dart'; +export './profile_list_state.dart'; diff --git a/lib/src/bloc/blocs/profile_list/profile_list_bloc.dart b/lib/src/bloc/blocs/profile_list/profile_list_bloc.dart new file mode 100644 index 0000000..a9f0aff --- /dev/null +++ b/lib/src/bloc/blocs/profile_list/profile_list_bloc.dart @@ -0,0 +1,120 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Business Logic Component for Profile list +class ProfileListBloc extends ElementListBloc { + final String _tag = '$ProfileListBloc'; + + ProfileListBloc({@required ProfileRepository repository}) + : super(repository: repository); + + @override + ProfileListState get initialState => ProfileListUninitialized(); + + @override + Stream mapEventToState(ProfileListEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is ProfileListInitialized) { + yield* _mapProfileListInitializedEventToState(event); + } else if (event is ProfileListRefresh) { + yield* _mapProfileListRefreshEventToState(event); + } else if (event is ProfileListLoadMore) { + yield* _mapProfileListLoadMoreEventToState(event); + } + } + + /// -------------------------------------------------------------------------- + /// All Event map to State + /// -------------------------------------------------------------------------- + + /// Map [ProfileListInitialized] to [ProfileListState] + /// + /// ```dart + /// yield* _mapProfileListInitializedEventToState(event); + /// ``` + Stream _mapProfileListInitializedEventToState( + ProfileListInitialized event) async* { + print('$_tag:_mapProfileListInitializedEventToState($event)'); + try { + /// TODO: Add refresh indicator stream + + parentId = event.parentId; + ownerId = event.ownerId; + cursor = event.cursor; + + elements = await _getProfiles(cursor: cursor); + + yield ProfileListLoaded(profiles: elements); + } catch (error) { + yield ProfileListFailure(error: error); + } + } + + /// Map [ProfileListRefresh] to [ProfileListState] + /// + /// ```dart + /// yield* _mapProfileListRefreshEventToState(event); + /// ``` + Stream _mapProfileListRefreshEventToState( + ProfileListRefresh event) async* { + print('$_tag:_mapProfileListRefreshEventToState($event)'); + try { + /// TODO: Add refresh indicator stream + elements = await _getProfiles(cursor: cursor); + yield ProfileListLoaded(profiles: elements); + } catch (error) { + yield ProfileListFailure(error: error); + } + } + + /// Map [ProfileListLoadMore] to [ProfileListState] + /// + /// ```dart + /// yield* _mapProfileListLoadMoreEventToState(event); + /// ``` + Stream _mapProfileListLoadMoreEventToState( + ProfileListLoadMore event) async* { + print('$_tag:_mapProfileListLoadMoreEventToState($event)'); + try { + /// TODO: Add load more indicator stream + + final List profiles = await _getProfiles( + cursor: event.cursor.copyWith(offset: elements.length), + ); + + /// Append to elements + elements.addAll(profiles); + + /// Save cursor limit if use list refreshed + cursor = cursor.copyWith(limit: elements.length); + + yield ProfileListLoaded(profiles: elements); + } catch (error) { + yield ProfileListFailure(error: error); + } + } + + FutureOr> _getProfiles({@required Cursor cursor}) async { + print('$_tag:_getProfiles({cursor: $cursor})'); + if (parentId != null) { + return await repository.getProfilesFromUser( + parentId, + cursor: cursor, + ); + } else if (ownerId != null) { + return await repository.getProfilesFromUser( + ownerId, + cursor: cursor, + ); + } else { + return await repository.getList( + cursor: cursor, + ); + } + } +} diff --git a/lib/src/bloc/blocs/profile_list/profile_list_event.dart b/lib/src/bloc/blocs/profile_list/profile_list_event.dart new file mode 100644 index 0000000..0ca2194 --- /dev/null +++ b/lib/src/bloc/blocs/profile_list/profile_list_event.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// [ProfileListEvent] that must be dispatch to [ProfileListBloc] +abstract class ProfileListEvent extends Equatable { + ProfileListEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class ProfileListInitialized extends ProfileListEvent + with ElementListInitialized { + ProfileListInitialized({ + String parentUserId, + String ownerId, + Cursor cursor, + }) : assert( + parentUserId != null && ownerId == null, + '$ProfileListInitialized must be created with a parentId or an ownerId', + ), + assert( + parentUserId == null && ownerId != null, + '$ProfileListInitialized must be created with a parentId or an ownerId', + ), + super([parentUserId, ownerId]) { + this.parentId = parentUserId; + this.ownerId = ownerId; + this.cursor = cursor; + } + + @override + String toString() => '$runtimeType{ ' + 'parentUserId: $parentId, ' + 'ownerId: $ownerId, ' + 'cursor: $cursor' + ' }'; +} + +class ProfileListRefresh extends ProfileListEvent + with ElementListRefresh {} + +class ProfileListLoadMore extends ProfileListEvent + with ElementListLoadMore { + ProfileListLoadMore({Cursor cursor}) + : assert(cursor != null), + super([cursor]) { + this.cursor = cursor; + } + + @override + String toString() => '$runtimeType{ ' + 'cursor: $cursor' + ' }'; +} diff --git a/lib/src/bloc/blocs/profile_list/profile_list_state.dart b/lib/src/bloc/blocs/profile_list/profile_list_state.dart new file mode 100644 index 0000000..6e60d9a --- /dev/null +++ b/lib/src/bloc/blocs/profile_list/profile_list_state.dart @@ -0,0 +1,53 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class ProfileListState extends Equatable { + ProfileListState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class ProfileListUninitialized extends ProfileListState + with ElementListUninitialized {} + +class ProfileListLoading extends ProfileListState + with ElementListLoading { + ProfileListLoading({int count = 0}) : super([count]) { + this.count = count; + } + + @override + String toString() => '$runtimeType{ ' + 'count: $count' + ' }'; +} + +class ProfileListLoaded extends ProfileListState + with ElementListLoaded { + ProfileListLoaded({@required List profiles}) + : super([profiles]) { + elements = profiles; + } + + @override + String toString() => '$runtimeType{ ' + 'profiles: $elements' + ' }'; +} + +class ProfileListFailure extends ProfileListState + with ElementListFailure { + ProfileListFailure({@required dynamic error}) + : assert(error != null, 'No error given'), + super([error]) { + this.error = error; + } + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/bloc/blocs/register/register.dart b/lib/src/bloc/blocs/register/register.dart new file mode 100644 index 0000000..991f214 --- /dev/null +++ b/lib/src/bloc/blocs/register/register.dart @@ -0,0 +1,3 @@ +export './register_bloc.dart'; +export './register_event.dart'; +export './register_state.dart'; diff --git a/lib/src/bloc/blocs/register/register_bloc.dart b/lib/src/bloc/blocs/register/register_bloc.dart new file mode 100644 index 0000000..9d4d56b --- /dev/null +++ b/lib/src/bloc/blocs/register/register_bloc.dart @@ -0,0 +1,56 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +class RegisterBloc extends Bloc { + final String _tag = '$RegisterBloc'; + + final CVAuthService cvAuthService; + + RegisterBloc({@required this.cvAuthService}) + : assert(cvAuthService != null, 'No $CVAuthService given'), + super(); + + @override + RegisterState get initialState => RegisterInitial(); + + @override + Stream mapEventToState(RegisterEvent event) async* { + print('$_tag:mapEventToState($event)'); + + if (event is RegistrationEvent) { + yield* _mapRegistrationEventToState(event); + } + } + + /// ----------------------------------------------------------------------- + /// All Event map to State + /// ----------------------------------------------------------------------- + + /// Map [RegistrationEvent] to [RegisterState] + /// + /// ```dart + /// yield* _mapRegistrationEventToState(event); + /// ``` + Stream _mapRegistrationEventToState( + RegistrationEvent event) async* { + try { + if (event is RegistrationEvent) { + yield RegisterLoading(); + cvAuthService.register( + fName: event.fName, + lName: event.lName, + email: event.email, + password: event.password, + ); + await Future.delayed(Duration(seconds: 2)); + yield RegisterInitial(); + } + } catch (error) { + yield RegisterFailure(error: error); + } + } +} diff --git a/lib/src/bloc/blocs/register/register_event.dart b/lib/src/bloc/blocs/register/register_event.dart new file mode 100644 index 0000000..8b20138 --- /dev/null +++ b/lib/src/bloc/blocs/register/register_event.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +/// [RegisterEvent] that must be dispatch to [RegisterBloc] +abstract class RegisterEvent extends Equatable { + RegisterEvent([List props = const []]) : super(props); +} + +class RegistrationEvent extends RegisterEvent { + final String fName; + final String lName; + final String email; + final String password; + + RegistrationEvent({ + @required this.fName, + @required this.lName, + @required this.email, + @required this.password, + }) : super([fName, lName, email, password]); + + @override + String toString() => '$runtimeType{ ' + 'fName: $fName, ' + 'lName: $lName, ' + 'email: $email, ' + 'password: HIDDEN' + ' }'; +} diff --git a/lib/src/bloc/blocs/register/register_state.dart b/lib/src/bloc/blocs/register/register_state.dart new file mode 100644 index 0000000..2ea9bc9 --- /dev/null +++ b/lib/src/bloc/blocs/register/register_state.dart @@ -0,0 +1,45 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +abstract class RegisterState extends Equatable { + RegisterState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class RegisterInitial extends RegisterState {} + +class RegisterLoading extends RegisterState {} + +class RegisterFailure extends RegisterState { + final dynamic error; + + RegisterFailure({@required this.error}) + : assert(error != null, 'No error given'), + super([error]); + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} + +class RegisterSucceed extends RegisterState { + final String accessToken; + final DateTime accessTokenExpiration; + final String refreshToken; + + RegisterSucceed({ + @required this.accessToken, + @required this.accessTokenExpiration, + @required this.refreshToken, + }) : super([accessToken, accessTokenExpiration, refreshToken]); + + @override + String toString() => '$runtimeType{ ' + 'accessToken: $accessToken, ' + 'accessToken: $accessTokenExpiration, ' + 'refreshToken: $refreshToken ' + ' }'; +} diff --git a/lib/src/bloc/blocs/user/user.dart b/lib/src/bloc/blocs/user/user.dart new file mode 100644 index 0000000..c0ab383 --- /dev/null +++ b/lib/src/bloc/blocs/user/user.dart @@ -0,0 +1,3 @@ +export 'user_bloc.dart'; +export 'user_event.dart'; +export 'user_state.dart'; diff --git a/lib/src/bloc/blocs/user/user_bloc.dart b/lib/src/bloc/blocs/user/user_bloc.dart new file mode 100644 index 0000000..0e01076 --- /dev/null +++ b/lib/src/bloc/blocs/user/user_bloc.dart @@ -0,0 +1,80 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Business Logic Component for User +class UserBloc + extends ElementBloc { + final String _tag = '$UserBloc'; + + UserBloc({@required UserRepository repository}) + : super(repository: repository); + + /// [_fallBackId] is used if [element] is never assigned and + /// an [UserRefresh] is dispatched + String _fallBackId; + + @override + UserState get initialState => UserUninitialized(); + + @override + Stream mapEventToState(UserEvent event) async* { + print('$_tag:mapEventToState($event)'); + if (event is UserInitialized) { + yield* _mapInitializedEventToState(event); + } else if (event is UserRefresh) { + yield* _mapRefreshEventToState(event); + } + } + + /// -------------------------------------------------------------------------- + /// All Event map to State + /// -------------------------------------------------------------------------- + + /// Map [UserInitialized] to [UserState] + /// + /// ```dart + /// yield* _mapInitializedEventToState(event); + /// ``` + Stream _mapInitializedEventToState(UserInitialized event) async* { + print('$_tag:_mapInitializedEventToState($event)'); + try { + yield UserLoading(); + + if (event.elementId != null) { + _fallBackId = event.elementId; + element = await await repository.getById(event.elementId); + } else if (event.element != null) { + _fallBackId = event.element.id; + element = event.element; + } + + yield UserLoaded(user: element); + } catch (error) { + yield UserFailure(error: error); + } + } + + /// Map [UserRefresh] to [UserState] + /// + /// ```dart + /// yield* _mapRefreshEventToState(event); + /// ``` + Stream _mapRefreshEventToState(UserRefresh event) async* { + print('$_tag:_mapRefreshEventToState($event)'); + try { + yield UserLoading(); + + element = await repository.getById( + element?.id ?? _fallBackId, + force: true, + ); + + _fallBackId = element.id; + + yield UserLoaded(user: element); + } catch (error) { + yield UserFailure(error: error); + } + } +} diff --git a/lib/src/bloc/blocs/user/user_event.dart b/lib/src/bloc/blocs/user/user_event.dart new file mode 100644 index 0000000..c5b04f2 --- /dev/null +++ b/lib/src/bloc/blocs/user/user_event.dart @@ -0,0 +1,27 @@ +import 'package:equatable/equatable.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// [UserEvent] that must be dispatch to [UserBloc] + +abstract class UserEvent extends Equatable { + UserEvent([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class UserInitialized extends UserEvent with ElementInitialized { + UserInitialized({String userId, UserEntity user}) + : assert(userId != null && user == null), + assert(userId == null && user != null), + super([userId, user]) { + this.elementId = userId; + this.element = user; + } + + @override + String toString() => '$runtimeType{ userId: $elementId, user: $element }'; +} + +class UserRefresh extends UserEvent with ElementRefresh {} diff --git a/lib/src/bloc/blocs/user/user_state.dart b/lib/src/bloc/blocs/user/user_state.dart new file mode 100644 index 0000000..fdfd6f8 --- /dev/null +++ b/lib/src/bloc/blocs/user/user_state.dart @@ -0,0 +1,42 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/bloc.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class UserState extends Equatable { + UserState([List props = const []]) : super(props); + + @override + String toString() => '$runtimeType{}'; +} + +class UserUninitialized extends UserState + with ElementUninitialized {} + +class UserLoading extends UserState with ElementLoading {} + +class UserLoaded extends UserState with ElementLoaded { + UserLoaded({UserEntity user}) : super([user]) { + element = user; + } + + @override + String toString() { + return '$runtimeType{ ' + 'element: $element' + ' }'; + } +} + +class UserFailure extends UserState with ElementFailure { + UserFailure({@required dynamic error}) + : assert(error != null, 'No error given'), + super([error]) { + this.error = error; + } + + @override + String toString() => '$runtimeType{ ' + 'error: $error' + ' }'; +} diff --git a/lib/src/data/cache_model.dart b/lib/src/data/cache_model.dart new file mode 100644 index 0000000..9762a8d --- /dev/null +++ b/lib/src/data/cache_model.dart @@ -0,0 +1,16 @@ +import 'package:meta/meta.dart'; + +class CacheModel { + CacheModel({ + @required this.model, + @required this.expiration, + }) : assert(model != null), + assert(model != null); + + T model; + DateTime expiration; + + bool isExpired() { + return DateTime.now().compareTo(expiration) >= 0 ? true : false; + } +} diff --git a/lib/src/data/exceptions/api_exceptions.dart b/lib/src/data/exceptions/api_exceptions.dart new file mode 100644 index 0000000..621a815 --- /dev/null +++ b/lib/src/data/exceptions/api_exceptions.dart @@ -0,0 +1,59 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +class ApiException extends AppException { + ApiException._internal({ + @required AppExceptionType type, + String message, + StackTrace stackTrace, + }) : assert(type != null, ' No $AppExceptionType given'), + super(type: type, message: message, stackTrace: stackTrace); + + factory ApiException.fromDioRequest({ + @required String errorCode, + String message, + StackTrace stackTrace, + }) { + assert(errorCode != null); + + final AppExceptionType errorType = _apiErrorCodes[errorCode]; + + return ApiException._internal( + type: errorType ?? AppExceptionType.somethingWentWrong, + message: message, + stackTrace: stackTrace, + ); + } +} + +/// Error map between api errors and domain exception types +Map _apiErrorCodes = { + /// -------------------------------------------------------------------------- + /// Server + /// -------------------------------------------------------------------------- + + 'SERVER_ERROR': AppExceptionType.serverSideProblem, + + /// -------------------------------------------------------------------------- + /// Authentication + /// -------------------------------------------------------------------------- + + 'AUTH_LOGIN_FAILED': AppExceptionType.authLoginFailed, + 'AUTH_REGISTRATION_FAILED': AppExceptionType.authRegistrationFailed, + 'AUTH_ACCOUNT_ALREADY_EXISTS': AppExceptionType.authAccountAlreadyExists, + 'AUTH_ACCOUNT_DISABLED': AppExceptionType.authAccountDisabled, + 'AUTH_NOT_AUTHORIZED': AppExceptionType.authUnauthorized, + 'AUTH_FORBIDDEN': AppExceptionType.authForbidden, + + /// -------------------------------------------------------------------------- + /// User + /// -------------------------------------------------------------------------- + + 'USER_NOT_FOUND': AppExceptionType.userNotFound, + + /// -------------------------------------------------------------------------- + /// Form + /// -------------------------------------------------------------------------- + + 'FORM_PASSWORD_WRONG_POLICY': AppExceptionType.formPasswordWrongPolicy, +}; diff --git a/lib/src/data/managers/api_interceptor.dart b/lib/src/data/managers/api_interceptor.dart new file mode 100644 index 0000000..2bfe157 --- /dev/null +++ b/lib/src/data/managers/api_interceptor.dart @@ -0,0 +1,117 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart' as domain; + +class ApiInterceptor extends Interceptor { + String _clientId; + String _clientSecret; + String _accessToken; + String _refreshToken; + + InterceptorsWrapper _interceptorWrapper; + + ApiInterceptor({ + String clientId, + String clientSecret, + String accessToken, + String refreshToken, + }) { + _clientId = clientId; + _clientSecret = clientSecret; + _accessToken = accessToken; + _refreshToken = refreshToken; + + _interceptorWrapper = + InterceptorsWrapper(onRequest: _onRequest, onResponse: _onResponse); + } + + InterceptorsWrapper get interceptorsWrapper => _interceptorWrapper; + + RequestOptions _onRequest(RequestOptions options) { + // Adding client credentials + if (_clientId != null && _clientSecret != null) { + options.headers.addAll({ + 'client_id': '$_clientId', + 'client_secret': '$_clientSecret', + 'grant_type': 'password', + }); + } + + // Adding access token + if (_accessToken != null) { + options.headers + .addAll({HttpHeaders.authorizationHeader: 'Bearer $_accessToken'}); + } + + // Adding refresh token + if (_refreshToken != null) { + options.headers.addAll({'refresh_token': '$_refreshToken'}); + } + + return options; + } + + Response _onResponse(Response response) { + /// Save access token + final data = response.data; + if (data is Map) { + if (data.containsKey('access_token')) { + _accessToken = data['access_token'] as String; + } + + /// Save refresh token + if (data.containsKey('refresh_token')) { + _refreshToken = response.data['refresh_token'] as String; + } + } + return response; + } + + FutureOr deleteAuthData() async { + _accessToken = null; + } + + @override + String toString() => '$runtimeType{}'; +} + +/// Parse response to check if there is any error +dynamic checkApiResponse(Response response, {StackTrace stackTrace}) { + // TODO: use Envelop type for Response body + final Map body = response.data as Map; + final String apiErrorCode = body['error'] as String; + final String apiMessage = body['message'] as String; + + if (apiErrorCode != null) { + throw ApiException.fromDioRequest( + errorCode: apiErrorCode, + message: apiMessage, + stackTrace: stackTrace ?? StackTrace.current, + ); + } + return response; +} + +dynamic apiErrorCatcher(dynamic err) { + if (err is DioError && err.response != null) { + final Response response = err.response; + final StackTrace stackTrace = (err?.error as dynamic).stackTrace as StackTrace; + + checkApiResponse(response, stackTrace: stackTrace); + + final statusCode = err?.response?.statusCode; + final statusMessage = err?.response?.statusMessage; + + if (statusCode != null) { + throw domain.HttpException( + statusCode: statusCode, + statusMessage: statusMessage, + stackTrace: stackTrace ?? StackTrace.current, + ); + } + } + return err; +} diff --git a/lib/src/data/managers/cv_api_manager.dart b/lib/src/data/managers/cv_api_manager.dart new file mode 100644 index 0000000..8902431 --- /dev/null +++ b/lib/src/data/managers/cv_api_manager.dart @@ -0,0 +1,572 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Default Implementation of [CVApiManager] +class CVApiManager + implements + CVAuthService, + IdentityDataStore, + UserDataStore, + ProfileDataStore, + PartDataStore, + GroupDataStore, + EntryDataStore { + final String _tag = '$CVApiManager'; + + final String apiBaseUrl; + + Dio _dio; + + static const String _pathOauth = '/oauth'; + static const String _pathOauthToken = '$_pathOauth/token'; + + static const String _pathMe = '/me'; + static const String _pathUsers = '/users'; + static const String _pathProfiles = '/profiles'; + static const String _pathParts = '/parts'; + static const String _pathGroups = '/groups'; + static const String _pathEntries = '/entries'; + + final ApiInterceptor tokenInterceptor; + + CVApiManager({ + @required this.apiBaseUrl, + @required this.tokenInterceptor, + }) : assert(apiBaseUrl != null, 'Missing api base url'), + assert(tokenInterceptor != null, 'No $ApiInterceptor given') { + _dio = Dio(BaseOptions( + baseUrl: apiBaseUrl, + connectTimeout: 3000, + contentType: '${ContentType.json}', + responseType: ResponseType.json, + )); + + // Add Interceptor + _dio.interceptors.add(tokenInterceptor.interceptorsWrapper); + + _dio.interceptors.add(InterceptorsWrapper( + onResponse: checkApiResponse, + onError: apiErrorCatcher, + )); + } + + /// -------------------------------------------------------------------------- + /// CVAuthService + /// -------------------------------------------------------------------------- + + @override + FutureOr authenticate({ + @required String email, + @required String password, + }) async { + print('$_tag:authenticate'); + + try { + final RequestAuthDataModel oauthModel = RequestAuthDataModel( + username: email, + password: password, + ); + + final Response> response = + await _dio.post>(_pathOauthToken, + data: oauthModel.toJson()); + + final model = ResponseAuthDataModel.fromJson(response.data); + return model; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr register({ + String fName, + String lName, + String email, + String password, + }) { + // TODO: implement register + throw NotImplementedYetError(); + } + + /// Logout + @override + FutureOr logout() async { + await tokenInterceptor.deleteAuthData(); + } + + /// -------------------------------------------------------------------------- + /// IdentityDataStore + /// -------------------------------------------------------------------------- + + @override + FutureOr getIdentity() async { + print('$_tag:getIdentity()'); + + try { + final Response> response = + await _dio.get>(_pathMe); + + final DataEnvelop envelop = + DataEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr setIdentity(UserDataModel userModel) { + // TODO: implement setIdentity + throw NotImplementedYetError(); + } + + FutureOr> getProfilesFromIdentity({ + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getProfilesFromIdentity'); + + try { + const String _path = '$_pathMe$_pathProfiles'; + + final Response> response = + await _dio.get>( + _path, + queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + }, + ); + + final envelop = + DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + /// -------------------------------------------------------------------------- + /// UserDataStore + /// -------------------------------------------------------------------------- + + @override + FutureOr getUser(String userId) async { + print('$_tag:getUser($userId)'); + + try { + final String _path = '$_pathUsers/$userId'; + + final Response> response = + await _dio.get>(_path); + final envelop = DataEnvelop.fromJson(response.data); + + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr setUser(UserDataModel userModel) { + print('$_tag:setUser($userModel)'); + // TODO: implement setUser + throw NotImplementedYetError(); + } + + @override + FutureOr> getUsers({ + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getUsers'); + + try { + const String _path = _pathUsers; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + }); + + final envelop = DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + /// -------------------------------------------------------------------------- + /// ProfileDataStore + /// -------------------------------------------------------------------------- + + @override + FutureOr getProfile(String profileId) async { + print('$_tag:fetchProfile($profileId)'); + + try { + final String _path = '$_pathProfiles/$profileId'; + + final Response> response = + await _dio.get>(_path); + final envelop = DataEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr setProfile(ProfileDataModel profileModel) { + print('$_tag:setProfile($profileModel)'); + // TODO: implement setProfile + throw NotImplementedYetError(); + } + + @override + FutureOr> getProfiles({ + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getProfiles'); + + try { + const String _path = _pathProfiles; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + }); + + final envelop = + DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr> getProfilesFromUser( + String userId, { + Cursor cursor = const Cursor(), + }) async { + print('$_tag:fetchProfilesFromUser'); + + try { + final String _path = '$_pathUsers/$userId$_pathProfiles'; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + }); + + final envelop = + DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + /// -------------------------------------------------------------------------- + /// PartDataStore + /// -------------------------------------------------------------------------- + + @override + FutureOr getPart(String partId) async { + print('$_tag:fetchPart($partId)'); + + try { + final String _path = '$_pathParts/$partId'; + + final Response> response = + await _dio.get>(_path); + final envelop = DataEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr setPart(PartDataModel partModel) { + // TODO: implement setPart + throw NotImplementedYetError(); + } + + @override + FutureOr> getParts({ + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getParts'); + + try { + const String _path = _pathParts; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + }); + + final envelop = DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr> getPartsFromProfile( + String profileId, { + Cursor cursor = const Cursor(), + }) async { + print('$_tag:fetchPartsFromProfile'); + + try { + final String _path = '$_pathProfiles/$profileId$_pathParts'; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + 'sort': '+order' + }); + + final envelop = DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr> getPartsFromUser( + String userId, { + Cursor cursor = const Cursor(), + }) async { + print('$_tag:fetchPartsFromUser($userId)'); + + try { + final String _path = '$_pathUsers/$userId$_pathParts'; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + }); + + final envelop = DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + /// -------------------------------------------------------------------------- + /// GroupDataStore + /// -------------------------------------------------------------------------- + + @override + FutureOr getGroup(String groupId) async { + print('$_tag:getGroup($groupId)'); + + try { + final String _path = '$_pathGroups/$groupId'; + + final Response> response = + await _dio.get>(_path); + final envelop = DataEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr setGroup(GroupDataModel groupModel) { + print('$_tag:setGroup($groupModel)'); +// TODO: implement setGroup + throw NotImplementedYetError(); + } + + @override + FutureOr> getGroups({ + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getGroups'); + + try { + const String _path = _pathGroups; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + }); + + final envelop = DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr> getGroupsFromPart( + String partId, { + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getGroupsFromPart($partId)'); + + try { + final String _path = '$_pathParts/$partId$_pathGroups'; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + 'sort': '+order', + }); + + final envelop = DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr> getGroupsFromUser( + String userId, { + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getGroupsFromUser($userId)'); + + try { + final String _path = '$_pathUsers/$userId$_pathGroups'; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + }); + + final envelop = DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + /// -------------------------------------------------------------------------- + /// EntryDataStore + /// -------------------------------------------------------------------------- + + @override + FutureOr getEntry(String entryId) async { + print('$_tag:getEntry($entryId)'); + + try { + final String _path = '$_pathEntries/$entryId'; + + final Response> response = + await _dio.get>(_path); + final envelop = DataEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr> getEntries({ + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getEntries'); + + try { + const String _path = _pathEntries; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + }); + + final envelop = DataArrayEnvelop.fromJson(response.data); + + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr setEntry(EntryDataModel entryModel) { + print('$_tag:setEntry($entryModel)'); + // TODO: implement setEntry + throw NotImplementedYetError(); + } + + @override + FutureOr> getEntriesFromGroup( + String groupId, { + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getEntriesFromGroup($groupId)'); + + try { + final String _path = '$_pathGroups/$groupId$_pathEntries'; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + 'sort': '+order', + }); + final envelop = DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + @override + FutureOr> getEntriesFromUser( + String userId, { + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getEntriesFromUser($userId)'); + + try { + final String _path = '$_pathUsers/$userId$_pathEntries'; + + final Response> response = + await _dio.get>(_path, queryParameters: { + 'offset': cursor.offset.toString(), + 'limit': cursor.limit.toString(), + }); + + final envelop = DataArrayEnvelop.fromJson(response.data); + return envelop.data; + } on DioError catch (e) { + throw e.error ?? e; + } + } + + /// -------------------------------------------------------------------------- + /// Misc + /// -------------------------------------------------------------------------- + + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/data/models/base_model.dart b/lib/src/data/models/base_model.dart new file mode 100644 index 0000000..19d8ec6 --- /dev/null +++ b/lib/src/data/models/base_model.dart @@ -0,0 +1,28 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class BaseDataModel implements BaseEntity { + @JsonKey(name: '_id') + @override + String id; + + @JsonKey(name: 'createdAt') + @override + DateTime createdAt; + + @JsonKey(name: 'updatedAt') + @override + DateTime updatedAt; + + @JsonKey(name: '__v') + @override + int version; + + BaseDataModel({ + @required this.id, + this.createdAt, + this.updatedAt, + this.version, + }) : super(); +} diff --git a/lib/src/data/models/element_model.dart b/lib/src/data/models/element_model.dart new file mode 100644 index 0000000..0d53677 --- /dev/null +++ b/lib/src/data/models/element_model.dart @@ -0,0 +1,17 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/src/data/models/base_model.dart'; + +abstract class ElementDataModel extends BaseDataModel implements ElementEntity { + ElementDataModel({ + @required String id, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); +} diff --git a/lib/src/data/models/entry_model.dart b/lib/src/data/models/entry_model.dart new file mode 100644 index 0000000..d947b0a --- /dev/null +++ b/lib/src/data/models/entry_model.dart @@ -0,0 +1,61 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +part 'entry_model.g.dart'; + +@JsonSerializable() +class EntryDataModel extends ElementDataModel implements EntryEntity { + @JsonKey(name: 'name') + @override + String name; + + @JsonKey(name: 'type') + @override + String type; + + @JsonKey(name: 'content') + @override + dynamic content; + + @JsonKey(name: 'startDate') + @override + String startDate; + + @JsonKey(name: 'endDate') + @override + String endDate; + + @JsonKey(name: 'location') + @override + String location; + + @JsonKey(name: 'owner') + @override + String ownerId; + + EntryDataModel({ + @required String id, + this.name, + this.type, + this.content, + this.startDate, + this.endDate, + this.location, + this.ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); + + factory EntryDataModel.fromJson(Map json) => + _$EntryDataModelFromJson(json); + + Map toJson() => _$EntryDataModelToJson(this); +} diff --git a/lib/src/data/models/envelop_models.dart b/lib/src/data/models/envelop_models.dart new file mode 100644 index 0000000..903d82f --- /dev/null +++ b/lib/src/data/models/envelop_models.dart @@ -0,0 +1,176 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +part 'envelop_models.g.dart'; + +/// ---------------------------------------------------------------------------- +/// Envelop +/// ---------------------------------------------------------------------------- + +abstract class _Envelop extends Object { + @JsonKey(name: 'error') + final bool error; + + @JsonKey(name: 'message') + final String message; + + _Envelop({ + this.error, + this.message, + }); +} + +/// Envelop containing [data] of type [T] +@JsonSerializable() +class DataEnvelop extends _Envelop { + @_GenericConverter() + T data; + + DataEnvelop({ + bool error, + String message, + this.data, + }) : super(error: error, message: message); + + factory DataEnvelop.fromJson(Map json) => + _$DataEnvelopFromJson(json); + + Map toJson() => _$DataEnvelopToJson(this); +} + +/// Envelop with containing [data] list of type [T] +@JsonSerializable() +class DataArrayEnvelop extends _Envelop { + @_GenericConverter() + List data; + + int total; + + DataArrayEnvelop({ + bool error, + String message, + this.data, + this.total, + }) : super(error: error, message: message); + + factory DataArrayEnvelop.fromJson(Map json) => + _$DataArrayEnvelopFromJson(json); + + Map toJson() => _$DataArrayEnvelopToJson(this); +} + +/// Provide json serialization and deserialization methods +/// for generic/template field type +/// +/// Working for generic type only +/// +/// Based on +/// https://github.com/dart-lang/json_serializable/blob/ee2c5c788279af01860624303abe16811850b82c/example/lib/json_converter_example.dart +/// +/// Example: +/// ```dart +/// @_GenericConverter +/// List generics +/// +/// @_GenericConverter +/// T generic +/// ``` +/// +class _GenericConverter implements JsonConverter { + const _GenericConverter(); + + @override + T fromJson(Object json) { + print('fromJson'); + final T t = (T as dynamic)?.fromJson(json) as T + // This will only work if `json` is a native JSON type: + // num, String, bool, null, etc + // *and* is assignable to `T`. + ?? + json as T; + + if (t == null) { + throw Exception('Type $T no supported'); + } + return t; + } + + @override + Object toJson(T object) { + print('toJson'); + // This will only work if `object` is a native JSON type: + // num, String, bool, null, etc + // Or if it has a `toJson()` function`. + return (object as dynamic)?.toJson() ?? object; + } +} + +@JsonSerializable() +class RequestAuthDataModel extends Object { + @JsonKey(name: 'username') + final String username; + @JsonKey(name: 'password') + final String password; + + RequestAuthDataModel({ + @required this.username, + @required this.password, + }) : assert(username != null && password != null), + super(); + + factory RequestAuthDataModel.fromJson(Map json) => + _$RequestAuthDataModelFromJson(json); + + Map toJson() => _$RequestAuthDataModelToJson(this); + + @override + String toString() => '$runtimeType{ ' + 'username: $username, ' + 'password: HIDDEN' + ' }'; +} + +@JsonSerializable() +class ResponseAuthDataModel implements AuthEntity { + @JsonKey(name: 'access_token') + @override + String accessToken; + + @JsonKey(name: 'refresh_token') + @override + String refreshToken; + + @JsonKey(name: 'expires_in') + int accessTokenExpiresIn; + + @JsonKey(name: 'token_type') + @override + String tokenType; + + @override + DateTime accessTokenExpiration; + + ResponseAuthDataModel({ + this.accessToken, + this.refreshToken, + this.accessTokenExpiresIn, + this.tokenType, + }) : super() { + accessTokenExpiration = + DateTime.now().add(Duration(milliseconds: accessTokenExpiresIn)); + } + + factory ResponseAuthDataModel.fromJson(Map json) => + _$ResponseAuthDataModelFromJson(json); + + Map toJson() => _$ResponseAuthDataModelToJson(this); + + @override + String toString() => '$runtimeType{ ' + 'accessToken: $accessToken, ' + 'refreshToken: $refreshToken, ' + 'accessTokenExpiresAt: $accessTokenExpiresIn, ' + 'tokenType: $tokenType' + ' }'; +} diff --git a/lib/src/data/models/group_model.dart b/lib/src/data/models/group_model.dart new file mode 100644 index 0000000..f0e19bc --- /dev/null +++ b/lib/src/data/models/group_model.dart @@ -0,0 +1,46 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +part 'group_model.g.dart'; + +@JsonSerializable() +class GroupDataModel extends ElementDataModel implements GroupEntity { + @JsonKey(name: 'name') + @override + String name; + + @JsonKey(name: 'entries') + @override + List entryIds; + + @JsonKey(name: 'type') + @override + String type; + + @JsonKey(name: 'owner') + @override + String ownerId; + + GroupDataModel({ + @required String id, + this.name, + this.type, + this.entryIds, + this.ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); + + factory GroupDataModel.fromJson(Map json) => + _$GroupDataModelFromJson(json); + + Map toJson() => _$GroupDataModelToJson(this); +} diff --git a/lib/src/data/models/part_model.dart b/lib/src/data/models/part_model.dart new file mode 100644 index 0000000..4d27deb --- /dev/null +++ b/lib/src/data/models/part_model.dart @@ -0,0 +1,46 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +part 'part_model.g.dart'; + +@JsonSerializable() +class PartDataModel extends ElementDataModel implements PartEntity { + @JsonKey(name: 'name') + @override + String name; + + @JsonKey(name: 'groups') + @override + List groupIds; + + @JsonKey(name: 'type') + @override + String type; + + @JsonKey(name: 'owner') + @override + String ownerId; + + PartDataModel({ + @required String id, + this.name, + this.groupIds, + this.type, + this.ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); + + factory PartDataModel.fromJson(Map json) => + _$PartDataModelFromJson(json); + + Map toJson() => _$PartDataModelToJson(this); +} diff --git a/lib/src/data/models/profile_model.dart b/lib/src/data/models/profile_model.dart new file mode 100644 index 0000000..80a4dde --- /dev/null +++ b/lib/src/data/models/profile_model.dart @@ -0,0 +1,61 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +part 'profile_model.g.dart'; + +@JsonSerializable() +class ProfileDataModel extends ElementDataModel implements ProfileEntity { + @JsonKey(name: 'title') + @override + String title; + + @JsonKey(name: 'subtitle') + @override + String subtitle; + + @JsonKey(name: 'picture') + @override + Uri picture; + + @JsonKey(name: 'cover') + @override + Uri cover; + + @JsonKey(name: 'parts') + @override + List partIds; + + @JsonKey(name: 'type') + @override + String type; + + @JsonKey(name: 'owner') + @override + String ownerId; + + ProfileDataModel({ + @required String id, + this.title, + this.subtitle, + this.picture, + this.cover, + this.partIds, + this.type, + this.ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); + + factory ProfileDataModel.fromJson(Map json) => + _$ProfileDataModelFromJson(json); + + Map toJson() => _$ProfileDataModelToJson(this); +} diff --git a/lib/src/data/models/user_model.dart b/lib/src/data/models/user_model.dart new file mode 100644 index 0000000..c4b2821 --- /dev/null +++ b/lib/src/data/models/user_model.dart @@ -0,0 +1,67 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +part 'user_model.g.dart'; + +@JsonSerializable() +class UserDataModel extends ElementDataModel implements UserEntity { + @JsonKey(name: 'disabled') + @override + bool disabled; + + @JsonKey(name: 'email') + @override + String email; + + @JsonKey(name: 'username') + @override + String username; + + @JsonKey(name: 'picture') + @override + String picture; + + @JsonKey(name: 'profiles') + @override + List profileIds; + + @JsonKey(name: 'permission') + @override + dynamic permission; + + UserDataModel({ + @required String id, + this.disabled, + this.email, + this.username, + this.picture, + this.profileIds, + this.permission, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); + + factory UserDataModel.fromJson(Map json) => + _$UserDataModelFromJson(json); + + Map toJson() => _$UserDataModelToJson(this); + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'disabled: $disabled, ' + 'email: $email, ' + 'username: $username, ' + 'picture: $picture, ' + 'profileIds: $profileIds, ' + 'permission: $permission' + ' }'; +} diff --git a/lib/src/data/repositories/app_prefs_repository.dart b/lib/src/data/repositories/app_prefs_repository.dart new file mode 100644 index 0000000..c4cfb72 --- /dev/null +++ b/lib/src/data/repositories/app_prefs_repository.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +class ImplAppPrefsRepository extends AppPrefsRepository { + final AppPrefsDataStoreFactory factory; + + ImplAppPrefsRepository({@required this.factory}) + : assert(factory != null, 'No $AppPrefsDataStoreFactory given'); + + @override + FutureOr toggleDarkMode(bool darkMode) { + return factory.create.toggleDarkMode(darkMode); + } + + @override + FutureOr getDarkMode() { + return factory.create.getDarkMode(); + } + + @override + FutureOr deleteDarkMode() { + return factory.create.getDarkMode(); + } + + @override + FutureOr deleteAll() { + return factory.create.deleteAll(); + } +} diff --git a/lib/src/data/repositories/auth_info_repository.dart b/lib/src/data/repositories/auth_info_repository.dart new file mode 100644 index 0000000..0f687a2 --- /dev/null +++ b/lib/src/data/repositories/auth_info_repository.dart @@ -0,0 +1,80 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +class ImplAuthInfoRepository implements AuthInfoRepository { + final AuthInfoDataStoreFactory factory; + + ImplAuthInfoRepository({@required this.factory}) + : assert(factory != null, 'No $AuthInfoDataStoreFactory given'); + + /// -------------------------------------------------------------------------- + /// Access Token + /// -------------------------------------------------------------------------- + + @override + FutureOr getAccessToken() { + return factory.diskDataStore.getAccessToken(); + } + + @override + FutureOr setAccessToken(String token) { + return factory.diskDataStore.setAccessToken(token); + } + + @override + FutureOr deleteAccessToken() { + return factory.diskDataStore.deleteAccessToken(); + } + + @override + FutureOr getAccessTokenExpiration() { + return factory.diskDataStore.getAccessTokenExpiration(); + } + + @override + FutureOr setAccessTokenExpiration(DateTime expiration) { + return factory.diskDataStore.setAccessTokenExpiration(expiration); + } + + @override + FutureOr deleteAccessTokenExpiration() { + return factory.diskDataStore.deleteAccessTokenExpiration(); + } + + /// -------------------------------------------------------------------------- + /// Refresh Token + /// -------------------------------------------------------------------------- + + @override + FutureOr getRefreshToken() { + return factory.diskDataStore.getRefreshToken(); + } + + @override + FutureOr setRefreshToken(String token) { + return factory.diskDataStore.setRefreshToken(token); + } + + @override + FutureOr deleteRefreshToken() { + return factory.diskDataStore.deleteRefreshToken(); + } + + @override + FutureOr getRefreshTokenExpiration() { + return factory.diskDataStore.getRefreshTokenExpiration(); + } + + @override + FutureOr setRefreshTokenExpiration(DateTime expiration) { + return factory.diskDataStore.setRefreshTokenExpiration(expiration); + } + + @override + FutureOr deleteRefreshTokenExpiration() { + return factory.diskDataStore.deleteRefreshTokenExpiration(); + } +} diff --git a/lib/src/data/repositories/entry_repository.dart b/lib/src/data/repositories/entry_repository.dart new file mode 100644 index 0000000..bd07941 --- /dev/null +++ b/lib/src/data/repositories/entry_repository.dart @@ -0,0 +1,101 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +class ImplEntryRepository extends EntryRepository { + final String _tag = '$ImplEntryRepository'; + + final EntryDataStoreFactory factory; + + ImplEntryRepository({@required this.factory}) : assert(factory != null); + + @override + FutureOr getById( + String id, { + bool force = false, + }) async { + print('$_tag:getById($id)'); + assert(id != null); + + EntryDataModel dataModel; + + if (!force) dataModel = await factory.memoryDataStore.getEntry(id); + + if (dataModel == null) { + dataModel = await factory.cloudDataStore.getEntry(id); + factory.memoryDataStore.setEntry(dataModel); + } + + return dataModel; + } + + @override + FutureOr> getList({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getList'); + + final dataModels = await factory.cloudDataStore.getEntries( + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setEntry); + + return dataModels; + } + + @override + FutureOr> getEntriesFromGroup( + String groupId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getEntriesFromGroup($groupId)'); + assert(groupId != null); + + final dataModels = await factory.cloudDataStore.getEntriesFromGroup( + groupId, + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setEntry); + + return dataModels; + } + + @override + FutureOr> getEntriesFromUser( + String userId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getEntriesFromUser($userId)'); + assert(userId != null); + + final dataModels = await factory.cloudDataStore.getEntriesFromUser( + userId, + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setEntry); + + return dataModels; + } + + @override + String toString() => '$runtimeType{ ' + 'factory: $factory' + ' }'; +} diff --git a/lib/src/data/repositories/group_repository.dart b/lib/src/data/repositories/group_repository.dart new file mode 100644 index 0000000..78e6336 --- /dev/null +++ b/lib/src/data/repositories/group_repository.dart @@ -0,0 +1,101 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +class ImplGroupRepository extends GroupRepository { + final String _tag = '$ImplGroupRepository'; + + final GroupDataStoreFactory factory; + + ImplGroupRepository({@required this.factory}) : assert(factory != null); + + @override + FutureOr getById( + String id, { + bool force = false, + }) async { + print('$_tag:getById($id)'); + assert(id != null); + + GroupDataModel dataModel; + + if (!force) dataModel = await factory.memoryDataStore.getGroup(id); + + if (dataModel == null) { + dataModel = await factory.cloudDataStore.getGroup(id); + await factory.memoryDataStore.setGroup(dataModel); + } + + return dataModel; + } + + @override + FutureOr> getList({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getList'); + + final dataModels = await factory.cloudDataStore.getGroups( + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setGroup); + + return dataModels; + } + + @override + FutureOr> getGroupsFromPart( + String partId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }) async { + assert(partId != null); + print('$_tag:getGroupsFromPart($partId)'); + + final dataModels = await factory.cloudDataStore.getGroupsFromPart( + partId, + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setGroup); + + return dataModels; + } + + @override + FutureOr> getGroupsFromUser( + String userId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }) async { + assert(userId != null); + print('$_tag:getGroupsFromUser($userId)'); + + final dataModels = await factory.cloudDataStore.getGroupsFromUser( + userId, + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setGroup); + + return dataModels; + } + + @override + String toString() => '$runtimeType{ ' + 'factory: $factory' + ' }'; +} diff --git a/lib/src/data/repositories/identity_repository.dart b/lib/src/data/repositories/identity_repository.dart new file mode 100644 index 0000000..44461f9 --- /dev/null +++ b/lib/src/data/repositories/identity_repository.dart @@ -0,0 +1,47 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +class ImplIdentityRepository extends IdentityRepository { + final String _tag = '$ImplIdentityRepository'; + + final IdentityDataStoreFactory factory; + + ImplIdentityRepository({@required this.factory}) : assert(factory != null); + + @override + FutureOr getIdentity() async { + print('$_tag:getAccount'); + + final dataModel = await factory.memoryDataStore.getIdentity(); + + if (dataModel == null) { + final dataModel = await factory.cloudDataStore.getIdentity(); + factory.memoryDataStore.setIdentity(dataModel); + } + + return dataModel; + } + +// @override +// FutureOr> getProfilesFromIdentity({ +// /// TODO: Add filters +// /// TODO: Add sort +// Cursor cursor = const Cursor(), +// }) async { +// print('$_tag:getProfilesFromIdentity'); +// +// final dataModels = await factory.cloudDataStore.getProfilesFromIdentity( +// cursor: cursor, +// ); +// +// return dataModels; +// } + + @override + String toString() => '$runtimeType{ ' + 'factory: $factory' + ' }'; +} diff --git a/lib/src/data/repositories/part_repository.dart b/lib/src/data/repositories/part_repository.dart new file mode 100644 index 0000000..2b88946 --- /dev/null +++ b/lib/src/data/repositories/part_repository.dart @@ -0,0 +1,96 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +class ImplPartRepository extends PartRepository { + final String _tag = '$ImplPartRepository'; + + final PartDataStoreFactory factory; + + ImplPartRepository({@required this.factory}) : assert(factory != null); + + @override + FutureOr getById( + String id, { + bool force = false, + }) async { + print('$_tag:getById($id)'); + + PartDataModel dataModel; + + if (!force) dataModel = await factory.memoryDataStore.getPart(id); + + if (dataModel == null) { + dataModel = await factory.cloudDataStore.getPart(id); + factory.memoryDataStore.setPart(dataModel); + } + + return dataModel; + } + + @override + FutureOr> getList({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getList'); + + final dataModels = await factory.cloudDataStore.getParts( + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setPart); + + return dataModels; + } + + @override + FutureOr> getPartsFromProfile( + String profileId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }) async { + assert(profileId != null); + print('$_tag:getPartsFromProfile'); + + final dataModels = await factory.cloudDataStore.getPartsFromProfile( + profileId, + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setPart); + + return dataModels; + } + + @override + FutureOr> getPartsFromUser( + String userId, { + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getPartsFromUser'); + + final dataModels = await factory.cloudDataStore.getPartsFromUser( + userId, + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setPart); + + return dataModels; + } + + @override + String toString() => '$runtimeType{ ' + 'factory: $factory' + ' }'; +} diff --git a/lib/src/data/repositories/profile_repository.dart b/lib/src/data/repositories/profile_repository.dart new file mode 100644 index 0000000..b08ce2b --- /dev/null +++ b/lib/src/data/repositories/profile_repository.dart @@ -0,0 +1,75 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +class ImplProfileRepository extends ProfileRepository { + final String _tag = '$ImplProfileRepository'; + + final ProfileDataStoreFactory factory; + + ImplProfileRepository({@required this.factory}) : assert(factory != null); + + @override + FutureOr getById( + String id, { + bool force = false, + }) async { + print('$_tag:getById($id)'); + assert(id != null); + + ProfileDataModel dataModel; + + if (!force) dataModel = await factory.memoryDataStore.getProfile(id); + + if (dataModel == null) { + dataModel = await factory.cloudDataStore.getProfile(id); + factory.memoryDataStore.setProfile(dataModel); + } + + return dataModel; + } + + @override + FutureOr> getList({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getList'); + + final dataModels = await factory.cloudDataStore.getProfiles( + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setProfile); + + return dataModels; + } + + @override + FutureOr> getProfilesFromUser( + String userId, { + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getProfilesFromUser($userId)'); + + final dataModels = await factory.cloudDataStore.getProfilesFromUser( + userId, + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setProfile); + + return dataModels; + } + + @override + String toString() => '$runtimeType{ ' + 'factory: $factory' + ' }'; +} diff --git a/lib/src/data/repositories/user_repository.dart b/lib/src/data/repositories/user_repository.dart new file mode 100644 index 0000000..6152b45 --- /dev/null +++ b/lib/src/data/repositories/user_repository.dart @@ -0,0 +1,57 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +class ImplUserRepository extends UserRepository { + final String _tag = '$ImplUserRepository'; + + final UserDataStoreFactory factory; + + ImplUserRepository({@required this.factory}) : assert(factory != null); + + @override + FutureOr getById( + String id, { + bool force = false, + }) async { + assert(id != null); + print('$_tag:getUser($id)'); + + UserDataModel dataModel; + + if (!force) dataModel = await factory.memoryDataStore.getUser(id); + + if (dataModel == null) { + dataModel = await factory.cloudDataStore.getUser(id); + factory.memoryDataStore.setUser(dataModel); + } + + return dataModel; + } + + @override + FutureOr> getList({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }) async { + print('$_tag:getUsers'); + + final dataModels = await factory.cloudDataStore.getUsers( + cursor: cursor, + ); + + // Store in RAM + dataModels.map(factory.memoryDataStore.setUser); + + return dataModels; + } + + @override + String toString() => '$runtimeType{ ' + 'factory: $factory' + ' }'; +} diff --git a/lib/src/data/stores/app_prefs_data_store/app_prefs_data_store.dart b/lib/src/data/stores/app_prefs_data_store/app_prefs_data_store.dart new file mode 100644 index 0000000..8263aa8 --- /dev/null +++ b/lib/src/data/stores/app_prefs_data_store/app_prefs_data_store.dart @@ -0,0 +1,17 @@ +import 'dart:async'; + +abstract class AppPrefsDataStore { + /// Get App dark mode + /// + /// Must return the application dark mode [bool] or [null] if not found + FutureOr getDarkMode(); + + /// Set Application dark mode([bool]) + FutureOr toggleDarkMode(bool darkMode); + + /// Delete Application dark mode + FutureOr deleteDarkMode(); + + /// Delete all application preferences + FutureOr deleteAll(); +} diff --git a/lib/src/data/stores/app_prefs_data_store/app_prefs_data_store_factory.dart b/lib/src/data/stores/app_prefs_data_store/app_prefs_data_store_factory.dart new file mode 100644 index 0000000..468f5ad --- /dev/null +++ b/lib/src/data/stores/app_prefs_data_store/app_prefs_data_store_factory.dart @@ -0,0 +1,18 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; + +class AppPrefsDataStoreFactory { + final String _tag = '$AppPrefsDataStoreFactory'; + + final AppPrefsDataStore diskDataStore; + + AppPrefsDataStore get create => diskDataStore; + + AppPrefsDataStoreFactory({@required this.diskDataStore}) + : assert(diskDataStore != null, 'No disk $AppPrefsDataStore given'); + + @override + String toString() => '$runtimeType{ ' + 'diskDataStore: $diskDataStore' + ' }'; +} diff --git a/lib/src/data/stores/auth_info_data_store/auth_info_data_store.dart b/lib/src/data/stores/auth_info_data_store/auth_info_data_store.dart new file mode 100644 index 0000000..b9f4203 --- /dev/null +++ b/lib/src/data/stores/auth_info_data_store/auth_info_data_store.dart @@ -0,0 +1,64 @@ +import 'dart:async'; + +abstract class AuthInfoDataStore { + /// -------------------------------------------------------------------------- + /// Access Token + /// -------------------------------------------------------------------------- + + /// Set access token ([String]) + FutureOr setAccessToken(String accessToken); + + /// Get access token + /// + /// Must return a access token ([String]) or [null] otherwise + FutureOr getAccessToken(); + + /// Delete access token + FutureOr deleteAccessToken(); + + /// Set access token expiration datetime as([DateTime]) + FutureOr setAccessTokenExpiration(DateTime expiration); + + /// Get access token expiration time + /// + /// Must return the access token expiration datetime ([DateTime]) + /// or [null] otherwise + FutureOr getAccessTokenExpiration(); + + /// Delete access token expiration datetime + FutureOr deleteAccessTokenExpiration(); + + /// -------------------------------------------------------------------------- + /// Refresh Token + /// -------------------------------------------------------------------------- + + /// Set refresh token ([String]) + FutureOr setRefreshToken(String token); + + /// Get refresh token + /// + /// Must return a refresh token ([String]) or [null] otherwise + FutureOr getRefreshToken(); + + /// Delete refresh token + FutureOr deleteRefreshToken(); + + /// Set refresh token expiration datetime as timestamp ([int]) + FutureOr setRefreshTokenExpiration(DateTime expiration); + + /// Get refresh token expiration datetime + /// + /// Must return the access token expiration datetime ([DateTime]) + /// or [null] otherwise + FutureOr getRefreshTokenExpiration(); + + /// Delete refresh token expiration date + FutureOr deleteRefreshTokenExpiration(); + + /// -------------------------------------------------------------------------- + /// Misc + /// -------------------------------------------------------------------------- + + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/data/stores/auth_info_data_store/auth_info_data_store_factory.dart b/lib/src/data/stores/auth_info_data_store/auth_info_data_store_factory.dart new file mode 100644 index 0000000..9811b2d --- /dev/null +++ b/lib/src/data/stores/auth_info_data_store/auth_info_data_store_factory.dart @@ -0,0 +1,18 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; + +class AuthInfoDataStoreFactory { + final String _tag = '$AuthInfoDataStoreFactory'; + + final AuthInfoDataStore diskDataStore; + + AuthInfoDataStore get create => diskDataStore; + + AuthInfoDataStoreFactory({@required this.diskDataStore}) + : assert(diskDataStore != null, 'No disk $AuthInfoDataStore given'); + + @override + String toString() => '$runtimeType{ ' + 'diskDataStore: $diskDataStore' + ' }'; +} diff --git a/lib/src/data/stores/entry_data_store/entry_data_store.dart b/lib/src/data/stores/entry_data_store/entry_data_store.dart new file mode 100644 index 0000000..1e18fab --- /dev/null +++ b/lib/src/data/stores/entry_data_store/entry_data_store.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +abstract class EntryDataStore { + FutureOr getEntry(String entryId); + + FutureOr setEntry(EntryDataModel entryModel); + + FutureOr> getEntries({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + + FutureOr> getEntriesFromGroup( + String groupId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + + FutureOr> getEntriesFromUser( + String userId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); +} diff --git a/lib/src/data/stores/entry_data_store/entry_data_store_factory.dart b/lib/src/data/stores/entry_data_store/entry_data_store_factory.dart new file mode 100644 index 0000000..eaef772 --- /dev/null +++ b/lib/src/data/stores/entry_data_store/entry_data_store_factory.dart @@ -0,0 +1,23 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; + +class EntryDataStoreFactory { + final String _tag = '$EntryDataStoreFactory'; + + final EntryDataStore cloudDataStore; + final EntryDataStore memoryDataStore; + + EntryDataStore get create => cloudDataStore; + + EntryDataStoreFactory({ + @required this.cloudDataStore, + @required this.memoryDataStore, + }) : assert(cloudDataStore != null, 'No cloud $EntryDataStore given'), + assert(memoryDataStore != null, 'No memory $EntryDataStore given'); + + @override + String toString() => '$runtimeType{ ' + 'memoryDataStore: $memoryDataStore, ' + 'cloudDataStore: $cloudDataStore' + ' }'; +} diff --git a/lib/src/data/stores/entry_data_store/memory_entry_data_store.dart b/lib/src/data/stores/entry_data_store/memory_entry_data_store.dart new file mode 100644 index 0000000..490eddc --- /dev/null +++ b/lib/src/data/stores/entry_data_store/memory_entry_data_store.dart @@ -0,0 +1,67 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Memory implementation of [EntryDataStore] +class MemoryEntryDataStore implements EntryDataStore { + final String _tag = '$MemoryEntryDataStore'; + + MemoryEntryDataStore(); + + final _entries = >{}; + + @override + FutureOr getEntry(String entryId) async { + print('$_tag:getEntry($entryId)'); + + final CacheModel cacheModel = _entries[entryId]; + return (cacheModel != null && !cacheModel.isExpired()) + ? cacheModel.model + : null; + } + + @override + FutureOr setEntry(EntryDataModel entryModel) async { + print('$_tag:setEntry($entryModel)'); + + final DateTime expiration = + generateExpirationDateTime(Duration(minutes: 1)); + final cacheModel = + CacheModel(model: entryModel, expiration: expiration); + _entries[entryModel.id] = cacheModel; + + return cacheModel.model; + } + + @override + FutureOr> getEntries({ + Cursor cursor = const Cursor(), + }) { + return _entries.values.map((value) => value.model).toList(); + } + + @override + FutureOr> getEntriesFromGroup( + String groupId, { + Cursor cursor = const Cursor(), + }) { + // TODO: implement getEntriesFromGroup + throw NotImplementedYetError(); + } + + @override + FutureOr> getEntriesFromUser( + String userId, { + Cursor cursor = const Cursor(), + }) { + return _entries.values + .map((e) => e.model) + .where((model) => model.ownerId == userId) + .toList(); + } + + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/data/stores/group_date_store/group_data_store.dart b/lib/src/data/stores/group_date_store/group_data_store.dart new file mode 100644 index 0000000..d16537e --- /dev/null +++ b/lib/src/data/stores/group_date_store/group_data_store.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +abstract class GroupDataStore { + FutureOr getGroup(String groupId); + + FutureOr setGroup(GroupDataModel groupModel); + + FutureOr> getGroups({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + + FutureOr> getGroupsFromPart( + String partId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + + FutureOr> getGroupsFromUser( + String userId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); +} diff --git a/lib/src/data/stores/group_date_store/group_data_store_factory.dart b/lib/src/data/stores/group_date_store/group_data_store_factory.dart new file mode 100644 index 0000000..c2dc15a --- /dev/null +++ b/lib/src/data/stores/group_date_store/group_data_store_factory.dart @@ -0,0 +1,21 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; + +class GroupDataStoreFactory { + final GroupDataStore cloudDataStore; + final GroupDataStore memoryDataStore; + + GroupDataStoreFactory({ + @required this.cloudDataStore, + @required this.memoryDataStore, + }) : assert(cloudDataStore != null, 'No cloud $GroupDataStore given'), + assert(memoryDataStore != null, 'No memory $GroupDataStore given'); + + GroupDataStore get create => cloudDataStore; + + @override + String toString() => '$runtimeType{ ' + 'memoryDataStore: $memoryDataStore, ' + 'cloudDataStore: $cloudDataStore' + ' }'; +} diff --git a/lib/src/data/stores/group_date_store/memory_group_data_store.dart b/lib/src/data/stores/group_date_store/memory_group_data_store.dart new file mode 100644 index 0000000..ec9af3e --- /dev/null +++ b/lib/src/data/stores/group_date_store/memory_group_data_store.dart @@ -0,0 +1,66 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Memory implementation of [GroupDataStore] +class MemoryGroupDataStore implements GroupDataStore { + final String _tag = '$MemoryGroupDataStore'; + + MemoryGroupDataStore(); + + final _groups = >{}; + + @override + FutureOr getGroup(String groupId) async { + print('$_tag:getGroup($groupId)'); + + final CacheModel cacheModel = _groups[groupId]; + return (cacheModel != null && !cacheModel.isExpired()) + ? cacheModel.model + : null; + } + + @override + FutureOr setGroup(GroupDataModel groupModel) async { + print('$_tag:setGroup($groupModel)'); + + final DateTime expiration = + generateExpirationDateTime(Duration(minutes: 1)); + final cacheModel = + CacheModel(model: groupModel, expiration: expiration); + _groups[groupModel.id] = cacheModel; + + return cacheModel.model; + } + + @override + FutureOr> getGroups({ + Cursor cursor = const Cursor(), + }) { + return _groups.values.map((value) => value.model).toList(); + } + + @override + FutureOr> getGroupsFromPart( + String partId, { + Cursor cursor = const Cursor(), + }) { + // TODO: implement getGroupsFromPart + return null; + } + + @override + FutureOr> getGroupsFromUser( + String userId, { + Cursor cursor = const Cursor(), + }) { + return _groups.values + .map((e) => e.model) + .where((model) => model.ownerId == userId) + .toList(); + } + + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/data/stores/identity_data_store/identity_data_store.dart b/lib/src/data/stores/identity_data_store/identity_data_store.dart new file mode 100644 index 0000000..a0953f4 --- /dev/null +++ b/lib/src/data/stores/identity_data_store/identity_data_store.dart @@ -0,0 +1,9 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; + +abstract class IdentityDataStore { + FutureOr getIdentity(); + + FutureOr setIdentity(UserDataModel userModel); +} diff --git a/lib/src/data/stores/identity_data_store/identity_data_store_factory.dart b/lib/src/data/stores/identity_data_store/identity_data_store_factory.dart new file mode 100644 index 0000000..adb9430 --- /dev/null +++ b/lib/src/data/stores/identity_data_store/identity_data_store_factory.dart @@ -0,0 +1,22 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; + +class IdentityDataStoreFactory { + final String _tag = '$IdentityDataStoreFactory'; + + final IdentityDataStore memoryDataStore; + final IdentityDataStore cloudDataStore; + + IdentityDataStoreFactory( + {@required this.memoryDataStore, @required this.cloudDataStore}) + : assert(memoryDataStore != null, ' No memory $IdentityDataStore given'), + assert(cloudDataStore != null, ' No cloud $IdentityDataStore given'); + + IdentityDataStore get create => memoryDataStore; + + @override + String toString() => '$runtimeType{ ' + 'memoryDataStore: $memoryDataStore, ' + 'cloudDataStore: $cloudDataStore' + ' }'; +} diff --git a/lib/src/data/stores/identity_data_store/memory_identity_data_store.dart b/lib/src/data/stores/identity_data_store/memory_identity_data_store.dart new file mode 100644 index 0000000..450fd88 --- /dev/null +++ b/lib/src/data/stores/identity_data_store/memory_identity_data_store.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; + +/// Memory implementation of [IdentityDataStore] +class MemoryIdentityDataStore implements IdentityDataStore { + final String _tag = '$MemoryIdentityDataStore'; + + MemoryIdentityDataStore(); + + CacheModel _accountCache; + + @override + FutureOr getIdentity() async { + print('$_tag:getAccount()'); + + return (_accountCache != null && !_accountCache.isExpired()) + ? _accountCache.model + : null; + } + + @override + FutureOr setIdentity(UserDataModel userModel) async { + print('$_tag:setAccount($userModel)'); + + final DateTime expiration = + generateExpirationDateTime(Duration(minutes: 1)); + _accountCache = + CacheModel(model: userModel, expiration: expiration); + + return _accountCache.model; + } + + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/data/stores/part_date_store/memory_part_data_store.dart b/lib/src/data/stores/part_date_store/memory_part_data_store.dart new file mode 100644 index 0000000..4c42451 --- /dev/null +++ b/lib/src/data/stores/part_date_store/memory_part_data_store.dart @@ -0,0 +1,67 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Memory implementation of [PartDataStore] +class MemoryPartDataStore implements PartDataStore { + final String _tag = '$MemoryPartDataStore'; + + MemoryPartDataStore(); + + final _parts = >{}; + + @override + FutureOr getPart(String partId) async { + print('$_tag:getPart($partId)'); + + final CacheModel cacheModel = _parts[partId]; + return (cacheModel != null && !cacheModel.isExpired()) + ? cacheModel.model + : null; + } + + @override + FutureOr setPart(PartDataModel partModel) async { + print('$_tag:setPart($partModel)'); + + final DateTime expiration = + generateExpirationDateTime(Duration(minutes: 1)); + final cacheModel = + CacheModel(model: partModel, expiration: expiration); + _parts[partModel.id] = cacheModel; + + return cacheModel.model; + } + + @override + FutureOr> getParts({ + Cursor cursor = const Cursor(), + }) { + return _parts.values.map((value) => value.model).toList(); + } + + @override + FutureOr> getPartsFromProfile( + String profileId, { + Cursor cursor = const Cursor(), + }) { + // TODO: implement getPartsFromProfile + throw NotImplementedYetError(); + } + + @override + FutureOr> getPartsFromUser( + String userId, { + Cursor cursor = const Cursor(), + }) { + return _parts.values + .map((e) => e.model) + .where((model) => model.ownerId == userId) + .toList(); + } + + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/data/stores/part_date_store/part_data_store.dart b/lib/src/data/stores/part_date_store/part_data_store.dart new file mode 100644 index 0000000..bb372c6 --- /dev/null +++ b/lib/src/data/stores/part_date_store/part_data_store.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +abstract class PartDataStore { + FutureOr getPart(String partId); + + FutureOr setPart(PartDataModel partModel); + + FutureOr> getParts({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + + FutureOr> getPartsFromProfile( + String profileId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + + FutureOr> getPartsFromUser( + String userId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); +} diff --git a/lib/src/data/stores/part_date_store/part_data_store_factory.dart b/lib/src/data/stores/part_date_store/part_data_store_factory.dart new file mode 100644 index 0000000..5e844c9 --- /dev/null +++ b/lib/src/data/stores/part_date_store/part_data_store_factory.dart @@ -0,0 +1,21 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; + +class PartDataStoreFactory { + final PartDataStore cloudDataStore; + final PartDataStore memoryDataStore; + + PartDataStoreFactory({ + @required this.cloudDataStore, + @required this.memoryDataStore, + }) : assert(cloudDataStore != null, 'No cloud $PartDataStore given'), + assert(memoryDataStore != null, 'No memory $PartDataStore given'); + + PartDataStore get create => cloudDataStore; + + @override + String toString() => '$runtimeType{ ' + 'memoryDataStore: $memoryDataStore, ' + 'cloudDataStore: $cloudDataStore' + ' }'; +} diff --git a/lib/src/data/stores/profile_date_store/memory_profile_data_store.dart b/lib/src/data/stores/profile_date_store/memory_profile_data_store.dart new file mode 100644 index 0000000..7919786 --- /dev/null +++ b/lib/src/data/stores/profile_date_store/memory_profile_data_store.dart @@ -0,0 +1,57 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Memory implementation of [ProfileDataStore] +class MemoryProfileDataStore implements ProfileDataStore { + final String _tag = '$MemoryProfileDataStore'; + + MemoryProfileDataStore(); + + final _profiles = >{}; + + @override + FutureOr getProfile(String profileId) async { + print('$_tag:getProfile($profileId)'); + + final CacheModel cacheModel = _profiles[profileId]; + return (cacheModel != null && !cacheModel.isExpired()) + ? cacheModel.model + : null; + } + + @override + FutureOr setProfile(ProfileDataModel profileModel) async { + print('$_tag:setProfile($profileModel)'); + + final DateTime expiration = + generateExpirationDateTime(Duration(minutes: 1)); + final cacheModel = CacheModel( + model: profileModel, expiration: expiration); + _profiles[profileModel.id] = cacheModel; + + return cacheModel.model; + } + + @override + FutureOr> getProfiles({ + Cursor cursor = const Cursor(), + }) { + return _profiles.values.map((value) => value.model).toList(); + } + + @override + FutureOr> getProfilesFromUser( + String userId, { + Cursor cursor = const Cursor(), + }) { + return _profiles.values + .map((value) => value.model) + .where((model) => model.ownerId == userId) + .toList(); + } + + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/data/stores/profile_date_store/profile_data_store.dart b/lib/src/data/stores/profile_date_store/profile_data_store.dart new file mode 100644 index 0000000..87476ef --- /dev/null +++ b/lib/src/data/stores/profile_date_store/profile_data_store.dart @@ -0,0 +1,24 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +abstract class ProfileDataStore { + FutureOr getProfile(String profileId); + + FutureOr setProfile(ProfileDataModel profileModel); + + FutureOr> getProfiles({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + + FutureOr> getProfilesFromUser( + String userId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); +} diff --git a/lib/src/data/stores/profile_date_store/profile_data_store_factory.dart b/lib/src/data/stores/profile_date_store/profile_data_store_factory.dart new file mode 100644 index 0000000..bba21db --- /dev/null +++ b/lib/src/data/stores/profile_date_store/profile_data_store_factory.dart @@ -0,0 +1,21 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; + +class ProfileDataStoreFactory { + final ProfileDataStore cloudDataStore; + final ProfileDataStore memoryDataStore; + + ProfileDataStoreFactory({ + @required this.cloudDataStore, + @required this.memoryDataStore, + }) : assert(cloudDataStore != null, 'No cloud $ProfileDataStore given'), + assert(memoryDataStore != null, 'No memory $ProfileDataStore given'); + + ProfileDataStore get create => cloudDataStore; + + @override + String toString() => '$runtimeType{ ' + 'memoryDataStore: $memoryDataStore, ' + 'cloudDataStore: $cloudDataStore' + ' }'; +} diff --git a/lib/src/data/stores/user_data_store/memory_user_data_store.dart b/lib/src/data/stores/user_data_store/memory_user_data_store.dart new file mode 100644 index 0000000..510701b --- /dev/null +++ b/lib/src/data/stores/user_data_store/memory_user_data_store.dart @@ -0,0 +1,47 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +/// Memory implementation of [UserDataStore] +class MemoryUserDataStore implements UserDataStore { + final String _tag = '$MemoryUserDataStore'; + + MemoryUserDataStore(); + + final _users = >{}; + + @override + FutureOr getUser(String userId) async { + print('$_tag:getUser($userId)'); + + final CacheModel cacheModel = _users[userId]; + return (cacheModel != null && !cacheModel.isExpired()) + ? cacheModel.model + : null; + } + + @override + FutureOr setUser(UserDataModel userModel) async { + print('$_tag:setUser($userModel)'); + + final DateTime expiration = + generateExpirationDateTime(Duration(minutes: 1)); + final cacheModel = + CacheModel(model: userModel, expiration: expiration); + _users[userModel.id] = cacheModel; + + return cacheModel.model; + } + + @override + FutureOr> getUsers({ + Cursor cursor = const Cursor(), + }) { + print('$_tag:getUsers'); + return _users.values.map((value) => value.model).toList(); + } + + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/data/stores/user_data_store/user_data_store.dart b/lib/src/data/stores/user_data_store/user_data_store.dart new file mode 100644 index 0000000..407dbaa --- /dev/null +++ b/lib/src/data/stores/user_data_store/user_data_store.dart @@ -0,0 +1,16 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; + +abstract class UserDataStore { + FutureOr getUser(String userId); + + FutureOr setUser(UserDataModel userModel); + + FutureOr> getUsers({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); +} diff --git a/lib/src/data/stores/user_data_store/user_data_store_factory.dart b/lib/src/data/stores/user_data_store/user_data_store_factory.dart new file mode 100644 index 0000000..289e12c --- /dev/null +++ b/lib/src/data/stores/user_data_store/user_data_store_factory.dart @@ -0,0 +1,21 @@ +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/data.dart'; + +class UserDataStoreFactory { + final UserDataStore cloudDataStore; + final UserDataStore memoryDataStore; + + UserDataStoreFactory({ + @required this.cloudDataStore, + @required this.memoryDataStore, + }) : assert(cloudDataStore != null, 'No cloud $UserDataStore given'), + assert(memoryDataStore != null, 'No memory $UserDataStore given'); + + UserDataStore get create => cloudDataStore; + + @override + String toString() => '$runtimeType{ ' + 'memoryDataStore: $memoryDataStore, ' + 'cloudDataStore: $cloudDataStore' + ' }'; +} diff --git a/lib/src/data/utils/utils.dart b/lib/src/data/utils/utils.dart new file mode 100644 index 0000000..946e104 --- /dev/null +++ b/lib/src/data/utils/utils.dart @@ -0,0 +1,9 @@ +import 'package:social_cv_client_flutter/presentation.dart'; + +DateTime generateExpirationDateTime(Duration duration) { + return DateTime.now().add(duration); +} + +Cursor checkCursor(Cursor cursor) { + return cursor ??= Cursor(); +} diff --git a/lib/src/domain/entities/auth_entity.dart b/lib/src/domain/entities/auth_entity.dart new file mode 100644 index 0000000..a2c0c57 --- /dev/null +++ b/lib/src/domain/entities/auth_entity.dart @@ -0,0 +1,6 @@ +abstract class AuthEntity { + String accessToken; + String refreshToken; + DateTime accessTokenExpiration; + String tokenType; +} diff --git a/lib/src/domain/entities/base_entity.dart b/lib/src/domain/entities/base_entity.dart new file mode 100644 index 0000000..54eda23 --- /dev/null +++ b/lib/src/domain/entities/base_entity.dart @@ -0,0 +1,14 @@ +abstract class BaseEntity { + String id; + DateTime createdAt; + DateTime updatedAt; + int version; + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'createdAt: $createdAt, ' + 'updatedAt: $updatedAt, ' + 'version: $version' + ' }'; +} diff --git a/lib/src/domain/entities/element_model.dart b/lib/src/domain/entities/element_model.dart new file mode 100644 index 0000000..49ad97b --- /dev/null +++ b/lib/src/domain/entities/element_model.dart @@ -0,0 +1,11 @@ +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class ElementEntity extends BaseEntity { + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'createdAt: $createdAt, ' + 'updatedAt: $updatedAt, ' + 'version: $version' + ' }'; +} diff --git a/lib/src/domain/entities/entry_entity.dart b/lib/src/domain/entities/entry_entity.dart new file mode 100644 index 0000000..774d750 --- /dev/null +++ b/lib/src/domain/entities/entry_entity.dart @@ -0,0 +1,26 @@ +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class EntryEntity extends ElementEntity { + String name; + String type; + dynamic content; + String startDate; + String endDate; + String location; + String ownerId; + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'name: $name, ' + 'type: $type, ' + 'content: $content, ' + 'startDate: $startDate, ' + 'endDate: $endDate, ' + 'location: $location, ' + 'owner: $ownerId, ' + 'createdAt: $createdAt, ' + 'updatedAt: $updatedAt, ' + 'version: $version' + ' }'; +} diff --git a/lib/src/domain/entities/group_entity.dart b/lib/src/domain/entities/group_entity.dart new file mode 100644 index 0000000..9d05d26 --- /dev/null +++ b/lib/src/domain/entities/group_entity.dart @@ -0,0 +1,20 @@ +import 'package:social_cv_client_flutter/data.dart'; + +class GroupEntity extends ElementDataModel { + String name; + List entryIds; + String type; + String ownerId; + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'name: $name, ' + 'type: $type, ' + 'entryIds: $entryIds, ' + 'owner: $ownerId, ' + 'createdAt: $createdAt, ' + 'updatedAt: $updatedAt, ' + 'version: $version' + ' }'; +} diff --git a/lib/src/domain/entities/part_entity.dart b/lib/src/domain/entities/part_entity.dart new file mode 100644 index 0000000..337f59a --- /dev/null +++ b/lib/src/domain/entities/part_entity.dart @@ -0,0 +1,17 @@ +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class PartEntity extends ElementEntity { + String name; + List groupIds; + String type; + String ownerId; + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'name: $name, ' + 'groupIds: $groupIds, ' + 'type: $type, ' + 'owner: $ownerId' + ' }'; +} diff --git a/lib/src/domain/entities/profile_entity.dart b/lib/src/domain/entities/profile_entity.dart new file mode 100644 index 0000000..53f0bef --- /dev/null +++ b/lib/src/domain/entities/profile_entity.dart @@ -0,0 +1,23 @@ +import 'package:social_cv_client_flutter/domain.dart'; + +class ProfileEntity extends ElementEntity { + String title; + String subtitle; + Uri picture; + Uri cover; + List partIds; + String type; + String ownerId; + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'title: $title, ' + 'subtitle: $subtitle, ' + 'picture: $picture, ' + 'cover: $cover, ' + 'partIds: $partIds, ' + 'type: $type, ' + 'owner: $ownerId' + ' }'; +} diff --git a/lib/src/domain/entities/user_entity.dart b/lib/src/domain/entities/user_entity.dart new file mode 100644 index 0000000..808abe3 --- /dev/null +++ b/lib/src/domain/entities/user_entity.dart @@ -0,0 +1,21 @@ +import 'package:social_cv_client_flutter/domain.dart'; + +abstract class UserEntity extends ElementEntity { + String email; + String username; + bool disabled; + String picture; + List profileIds; + dynamic permission; + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'disabled: $disabled, ' + 'email: $email, ' + 'username: $username, ' + 'picture: $picture, ' + 'profileIds: $profileIds, ' + 'permission: $permission' + ' }'; +} diff --git a/lib/src/domain/errors/commons_errors.dart b/lib/src/domain/errors/commons_errors.dart new file mode 100644 index 0000000..0347d5f --- /dev/null +++ b/lib/src/domain/errors/commons_errors.dart @@ -0,0 +1,19 @@ +class NotImplementedYetError extends Error { + final String message; + + NotImplementedYetError([this.message]); + + @override + String toString() => message != null + ? 'NotImplementedYetError: $message' + : 'NotImplementedYetError'; +} + +class NotSupportedError extends Error { + final String message; + + NotSupportedError(this.message); + + @override + String toString() => 'Unsupported operation: $message'; +} diff --git a/lib/src/domain/exceptions/app_exceptions.dart b/lib/src/domain/exceptions/app_exceptions.dart new file mode 100644 index 0000000..18f8115 --- /dev/null +++ b/lib/src/domain/exceptions/app_exceptions.dart @@ -0,0 +1,46 @@ +import 'package:meta/meta.dart'; + +abstract class AppException implements Exception { + final String message; + final StackTrace stackTrace; + final AppExceptionType type; + + const AppException({ + @required this.type, + this.message, + this.stackTrace, + }) : assert(type != null, 'No $AppExceptionType given'); +} + +enum AppExceptionType { + /// -------------------------------------------------------------------------- + /// Common + /// -------------------------------------------------------------------------- + somethingWentWrong, + + /// -------------------------------------------------------------------------- + /// Server + /// -------------------------------------------------------------------------- + serverSideProblem, + + /// -------------------------------------------------------------------------- + /// Authentication + /// -------------------------------------------------------------------------- + authNoToken, + authLoginFailed, + authRegistrationFailed, + authAccountAlreadyExists, + authAccountDisabled, + authUnauthorized, + authForbidden, + + /// -------------------------------------------------------------------------- + /// User + /// -------------------------------------------------------------------------- + userNotFound, + + /// -------------------------------------------------------------------------- + /// Form + /// -------------------------------------------------------------------------- + formPasswordWrongPolicy, +} diff --git a/lib/src/domain/exceptions/http_exceptions.dart b/lib/src/domain/exceptions/http_exceptions.dart new file mode 100644 index 0000000..afbf403 --- /dev/null +++ b/lib/src/domain/exceptions/http_exceptions.dart @@ -0,0 +1,13 @@ +import 'package:meta/meta.dart'; + +class HttpException implements Exception { + final int statusCode; + final String statusMessage; + final StackTrace stackTrace; + + const HttpException({ + @required this.statusCode, + this.statusMessage, + this.stackTrace, + }) : assert(statusCode != null, 'No status code given'); +} diff --git a/lib/src/domain/repositories/app_preferences_repository.dart b/lib/src/domain/repositories/app_preferences_repository.dart new file mode 100644 index 0000000..d657578 --- /dev/null +++ b/lib/src/domain/repositories/app_preferences_repository.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +/// Repository interface for the application preferences +abstract class AppPrefsRepository { + /// Get App dark mode + /// + /// Must return the application dark mode [bool] or [null] if not found + FutureOr getDarkMode(); + + /// Set Application dark mode([bool]) + FutureOr toggleDarkMode(bool darkMode); + + /// Delete Application dark mode + FutureOr deleteDarkMode(); + + /// Delete all application preferences + FutureOr deleteAll(); + + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/domain/repositories/auth_info_repository.dart b/lib/src/domain/repositories/auth_info_repository.dart new file mode 100644 index 0000000..f448eb0 --- /dev/null +++ b/lib/src/domain/repositories/auth_info_repository.dart @@ -0,0 +1,58 @@ +import 'dart:async'; + +/// Repository interface for authentication info purpose +abstract class AuthInfoRepository { + /// -------------------------------------------------------------------------- + /// Access Token + /// -------------------------------------------------------------------------- + + /// Set access token ([String]) + FutureOr setAccessToken(String token); + + /// Get access token + /// + /// Must return a access token ([String]) or [null] otherwise + FutureOr getAccessToken(); + + /// Delete access token + FutureOr deleteAccessToken(); + + /// Set access token expiration datetime as timestamp ([DateTime]) + FutureOr setAccessTokenExpiration(DateTime expiration); + + /// Get access token expiration time + /// + /// Must return the access token expiration datetime as timestamp ([int]) + /// or [null] otherwise + FutureOr getAccessTokenExpiration(); + + /// Delete access token expiration datetime + FutureOr deleteAccessTokenExpiration(); + + /// -------------------------------------------------------------------------- + /// Refresh Token + /// -------------------------------------------------------------------------- + + /// Set refresh token ([String]) + FutureOr setRefreshToken(String token); + + /// Get refresh token + /// + /// Must return a refresh token ([String]) or [null] otherwise + FutureOr getRefreshToken(); + + /// Delete refresh token + FutureOr deleteRefreshToken(); + + /// Set refresh token expiration datetime as timestamp ([int]) + FutureOr setRefreshTokenExpiration(DateTime expiration); + + /// Get refresh token expiration datetime + /// + /// Must return the access token expiration datetime ([DateTime]) + /// or [null] otherwise + FutureOr getRefreshTokenExpiration(); + + /// Delete refresh token expiration date + FutureOr deleteRefreshTokenExpiration(); +} diff --git a/lib/src/domain/repositories/entity_repository.dart b/lib/src/domain/repositories/entity_repository.dart new file mode 100644 index 0000000..b2f1c27 --- /dev/null +++ b/lib/src/domain/repositories/entity_repository.dart @@ -0,0 +1,30 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/presentation.dart'; +import 'package:social_cv_client_flutter/src/domain/entities/base_entity.dart'; + +abstract class EntityRepository { + /// Fetch the [T] identified by [id] + /// + /// [force] can be used to avoid cache use ([$false] by default) + /// + /// Must return an [T] + FutureOr getById( + String id, { + bool force = false, + }); + + /// Fetch [T] list + /// + /// [Cursor] can be used to choose the offset and the limit + /// + /// Must return a [List] of [T] + FutureOr> getList({ + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + +// TODO: Add delete +// TODO: Add create +} diff --git a/lib/src/domain/repositories/entry_repository.dart b/lib/src/domain/repositories/entry_repository.dart new file mode 100644 index 0000000..7fa23b5 --- /dev/null +++ b/lib/src/domain/repositories/entry_repository.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; +import 'package:social_cv_client_flutter/src/domain/repositories/entity_repository.dart'; + +/// Repository interface for entry element purpose +abstract class EntryRepository extends EntityRepository { + /// Fetch groups from the parent group identified by [groupId] + /// + /// [Cursor] can be used to choose the offset and the limit + /// + /// Must return a [List] of [EntryEntity] + FutureOr> getEntriesFromGroup( + String groupId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + + /// Fetch entries from the user identified by [userId] + /// + /// [Cursor] can be used to choose the offset and the limit + /// + /// Must return a [List] of [EntryEntity] + FutureOr> getEntriesFromUser( + String userId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); +} diff --git a/lib/src/domain/repositories/group_repository.dart b/lib/src/domain/repositories/group_repository.dart new file mode 100644 index 0000000..827e3a1 --- /dev/null +++ b/lib/src/domain/repositories/group_repository.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; +import 'package:social_cv_client_flutter/src/domain/repositories/entity_repository.dart'; + +/// Repository interface for group element purpose +abstract class GroupRepository extends EntityRepository { + /// Fetch groups from the parent part identified by [partId] + /// + /// [Cursor] can be used to choose the offset and the limit + /// + /// Must return a [List] of [GroupEntity] + FutureOr> getGroupsFromPart( + String partId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + + /// Fetch groups from the user identified by [userId] + /// + /// [Cursor] can be used to choose the offset and the limit + /// + /// Must return a [List] of [GroupEntity] + FutureOr> getGroupsFromUser( + String userId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); +} diff --git a/lib/src/domain/repositories/identity_repository.dart b/lib/src/domain/repositories/identity_repository.dart new file mode 100644 index 0000000..d75f467 --- /dev/null +++ b/lib/src/domain/repositories/identity_repository.dart @@ -0,0 +1,22 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/domain.dart'; + +/// Repository interface for connected user information purpose +abstract class IdentityRepository { + /// Fetch information of the authenticated user + /// + /// Must return an [UserEntity] + FutureOr getIdentity(); + +// /// Fetch profiles from the authenticated user account +// /// +// /// [Cursor] can be used to choose the offset and the limit +// /// +// /// Must return a [List] of [ProfileEntity] +// FutureOr> getProfilesFromIdentity({ +// /// TODO: Add filters +// /// TODO: Add sort +// Cursor cursor = const Cursor(), +// }); +} diff --git a/lib/src/domain/repositories/part_repository.dart b/lib/src/domain/repositories/part_repository.dart new file mode 100644 index 0000000..5738364 --- /dev/null +++ b/lib/src/domain/repositories/part_repository.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; +import 'package:social_cv_client_flutter/src/domain/repositories/entity_repository.dart'; + +/// Repository interface for part element purpose +abstract class PartRepository extends EntityRepository { + /// Fetch parts from the parent profile identified by [profileId] + /// + /// [Cursor] can be used to choose the offset and the limit + /// + /// Must return a [List] of [PartEntity] + FutureOr> getPartsFromProfile( + String profileId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); + + /// Fetch parts from the user identified by [userId] + /// + /// [Cursor] can be used to choose the offset and the limit + /// + /// Must return a [List] of [PartEntity] + FutureOr> getPartsFromUser( + String userId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); +} diff --git a/lib/src/domain/repositories/profile_repository.dart b/lib/src/domain/repositories/profile_repository.dart new file mode 100644 index 0000000..9a332fa --- /dev/null +++ b/lib/src/domain/repositories/profile_repository.dart @@ -0,0 +1,16 @@ +import 'dart:async'; + +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; +import 'package:social_cv_client_flutter/src/domain/repositories/entity_repository.dart'; + +/// Repository interface for profile element purpose +abstract class ProfileRepository extends EntityRepository { + FutureOr> getProfilesFromUser( + String userId, { + + /// TODO: Add filters + /// TODO: Add sort + Cursor cursor = const Cursor(), + }); +} diff --git a/lib/src/domain/repositories/user_repository.dart b/lib/src/domain/repositories/user_repository.dart new file mode 100644 index 0000000..ef83b9a --- /dev/null +++ b/lib/src/domain/repositories/user_repository.dart @@ -0,0 +1,5 @@ +import 'package:social_cv_client_flutter/domain.dart'; +import 'package:social_cv_client_flutter/src/domain/repositories/entity_repository.dart'; + +/// Repository interface for user element purpose +abstract class UserRepository extends EntityRepository {} diff --git a/lib/src/domain/services/cv_auth_service.dart b/lib/src/domain/services/cv_auth_service.dart new file mode 100644 index 0000000..14566bf --- /dev/null +++ b/lib/src/domain/services/cv_auth_service.dart @@ -0,0 +1,35 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:social_cv_client_flutter/domain.dart'; + +/// Service for authentication purpose +abstract class CVAuthService { + /// -------------------------------------------------------------------------- + /// Auth + /// -------------------------------------------------------------------------- + + /// Authenticate + /// + /// Must use [email] and [password] to authenticate an user + /// + /// Must return an [AuthDataModel] + FutureOr authenticate({ + @required String email, + @required String password, + }); + + /// Register + /// + /// Must use [fName], [lName], [email] and [password] to register an user + /// + /// Must return an [AuthDataModel] + FutureOr register({ + @required String fName, + @required String lName, + @required String email, + @required String password, + }); + + FutureOr logout(); +} diff --git a/lib/src/domain/services/foundation_config_service.dart b/lib/src/domain/services/foundation_config_service.dart new file mode 100644 index 0000000..08ecf4e --- /dev/null +++ b/lib/src/domain/services/foundation_config_service.dart @@ -0,0 +1,12 @@ +import 'dart:async'; + +abstract class FoundationConfigService { + /// Get Api server url ([String]) + FutureOr getApiServerUrl(); + + /// Get client id ([String]) + FutureOr getClientId(); + + /// Get client secret ([String]) + FutureOr getClientSecret(); +} diff --git a/lib/src/presentation/mappers/model_mapper.dart b/lib/src/presentation/mappers/model_mapper.dart new file mode 100644 index 0000000..a534d36 --- /dev/null +++ b/lib/src/presentation/mappers/model_mapper.dart @@ -0,0 +1,117 @@ +import 'package:social_cv_client_flutter/data.dart'; +import 'package:social_cv_client_flutter/presentation.dart'; +import 'package:social_cv_client_flutter/src/data/models/envelop_models.dart'; + +/// [ModelMapper] for MVVM pattern +class ModelMapper { + final String _tag = '$ModelMapper'; + + static ModelMapper _instance; + + static _initState() { + _instance = ModelMapper(); + } + + static ModelMapper get instance { + if (_instance == null) { + _initState(); + } + return _instance; + } + + /// [ResponseAuthDataModel] to [AccessTokenViewModelModel] + AccessTokenViewModelModel toAccessTokenViewModelModel( + ResponseAuthDataModel dataModel) { + return AccessTokenViewModelModel( + accessToken: dataModel.accessToken, + refreshToken: dataModel.refreshToken, + accessTokenExpiresAt: dataModel.accessTokenExpiresIn, + tokenType: dataModel.tokenType, + ); + } + + /// [UserDataModel] to [UserViewModel] + UserViewModel toUserViewModel(UserDataModel dataModel) { + return UserViewModel( + id: dataModel.id, + disabled: dataModel.disabled, + username: dataModel.username, + email: dataModel.email, + picture: dataModel.picture, + profileIds: dataModel.profileIds, + permission: dataModel.permission, + createdAt: dataModel.createdAt, + updatedAt: dataModel.updatedAt, + version: dataModel.version, + ); + } + + /// [ProfileDataModel] to [ProfileViewModel] + ProfileViewModel toProfileViewModel(ProfileDataModel dataModel) { + /// TODO: Map Profile type with enum + return ProfileViewModel( + id: dataModel.id, + title: dataModel.title, + subtitle: dataModel.subtitle, + picture: dataModel.picture, + cover: dataModel.cover, + partIds: dataModel.partIds, + type: dataModel.type, + ownerId: dataModel.ownerId, + createdAt: dataModel.createdAt, + updatedAt: dataModel.updatedAt, + version: dataModel.version, + ); + } + + /// [PartDataModel] to [PartViewModel] + PartViewModel toPartViewModel(PartDataModel dataModel) { + /// TODO: Map Part type with enum + return PartViewModel( + id: dataModel.id, + name: dataModel.name, + groupIds: dataModel.groupIds, + type: dataModel.type, + ownerId: dataModel.ownerId, + createdAt: dataModel.createdAt, + updatedAt: dataModel.updatedAt, + version: dataModel.version, + ); + } + + /// [GroupDataModel] to [GroupViewModel] + GroupViewModel toGroupViewModel(GroupDataModel dataModel) { + /// TODO: Map Group type with enum + return GroupViewModel( + id: dataModel.id, + name: dataModel.name, + entryIds: dataModel.entryIds, + type: dataModel.type, + ownerId: dataModel.ownerId, + createdAt: dataModel.createdAt, + updatedAt: dataModel.updatedAt, + version: dataModel.version, + ); + } + + /// [EntryDataModel] to [EntryViewModel] + EntryViewModel toEntryViewModel(EntryDataModel dataModel) { + /// TODO: Map Entry type with enum + return EntryViewModel( + id: dataModel.id, + name: dataModel.name, + content: dataModel.content, + startDate: dataModel.startDate, + endDate: dataModel.endDate, + location: dataModel.location, + type: dataModel.type, + ownerId: dataModel.ownerId, + createdAt: dataModel.createdAt, + updatedAt: dataModel.updatedAt, + version: dataModel.version, + ); + } + + @override + String toString() => '$runtimeType{}'; +} diff --git a/lib/src/presentation/models/api_models.dart b/lib/src/presentation/models/api_models.dart new file mode 100644 index 0000000..5a2376b --- /dev/null +++ b/lib/src/presentation/models/api_models.dart @@ -0,0 +1,21 @@ +class AccessTokenViewModelModel { + final String accessToken; + final String refreshToken; + final int accessTokenExpiresAt; + final String tokenType; + + AccessTokenViewModelModel({ + this.accessToken, + this.refreshToken, + this.accessTokenExpiresAt, + this.tokenType, + }) : super(); + + @override + String toString() => '$runtimeType{ ' + 'accessToken: $accessToken, ' + 'refreshToken: $refreshToken, ' + 'accessTokenExpiresAt: $accessTokenExpiresAt, ' + 'tokenType: $tokenType' + ' }'; +} diff --git a/lib/src/presentation/models/cursor_model.dart b/lib/src/presentation/models/cursor_model.dart new file mode 100644 index 0000000..9ecf660 --- /dev/null +++ b/lib/src/presentation/models/cursor_model.dart @@ -0,0 +1,24 @@ +class Cursor { + /// [offset] indicate the offset of the search + final int offset; + + /// [limit] indicate the elements limits of a result + final int limit; + + const Cursor({this.offset = 0, this.limit = 5}) + : assert(offset != null, 'No offset($int) given'), + assert(limit != null, 'No limit($int) given'); + + @override + String toString() => '$runtimeType{ ' + 'offset: $offset, ' + 'limit: $limit' + ' }'; + + Cursor copyWith({int offset, int limit}) { + return Cursor( + offset: offset ?? this.offset, + limit: limit ?? this.limit, + ); + } +} diff --git a/lib/src/presentation/models/element_model.dart b/lib/src/presentation/models/element_model.dart new file mode 100644 index 0000000..64d9523 --- /dev/null +++ b/lib/src/presentation/models/element_model.dart @@ -0,0 +1,23 @@ +import 'package:meta/meta.dart'; + +abstract class ElementViewModel { + String id; + DateTime createdAt; + DateTime updatedAt; + int version; + + ElementViewModel({ + @required this.id, + this.createdAt, + this.updatedAt, + this.version, + }) : super(); + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'createdAt: $createdAt, ' + 'updatedAt: $updatedAt, ' + 'version: $version' + ' }'; +} diff --git a/lib/src/presentation/models/entry_model.dart b/lib/src/presentation/models/entry_model.dart new file mode 100644 index 0000000..81c2458 --- /dev/null +++ b/lib/src/presentation/models/entry_model.dart @@ -0,0 +1,74 @@ +import 'package:social_cv_client_flutter/presentation.dart'; + +class EntryViewModel extends ElementViewModel { + String name; + String type; + dynamic content; + String startDate; + String endDate; + String location; + String ownerId; + + EntryViewModel({ + String id, + this.name, + this.type, + this.content, + this.startDate, + this.endDate, + this.location, + this.ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); + + EntryViewModel copyWith({ + String id, + String name, + String type, + String content, + String startDate, + String endDate, + String location, + String groupId, + String ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) { + return EntryViewModel( + id: id ?? this.id, + name: name ?? this.name, + type: type ?? this.type, + content: content ?? this.content, + startDate: startDate ?? this.startDate, + endDate: endDate ?? this.endDate, + location: location ?? this.location, + ownerId: ownerId ?? this.ownerId, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + version: version ?? this.version, + ); + } + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'name: $name, ' + 'type: $type, ' + 'content: $content, ' + 'startDate: $startDate, ' + 'endDate: $endDate, ' + 'location: $location, ' + 'owner: $ownerId, ' + 'createdAt: $createdAt, ' + 'updatedAt: $updatedAt, ' + 'version: $version' + ' }'; +} diff --git a/lib/src/presentation/models/group_model.dart b/lib/src/presentation/models/group_model.dart new file mode 100644 index 0000000..2081daa --- /dev/null +++ b/lib/src/presentation/models/group_model.dart @@ -0,0 +1,58 @@ +import 'package:social_cv_client_flutter/presentation.dart'; + +class GroupViewModel extends ElementViewModel { + String name; + List entryIds; + String type; + String ownerId; + + GroupViewModel({ + String id, + this.name, + this.entryIds, + this.type, + this.ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); + + GroupViewModel copyWith({ + String id, + String name, + List entryIds, + String type, + String ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) { + return GroupViewModel( + id: id ?? this.id, + name: name ?? this.name, + entryIds: entryIds ?? this.entryIds, + type: type ?? this.type, + ownerId: ownerId ?? this.ownerId, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + version: version ?? this.version, + ); + } + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'name: $name, ' + 'type: $type, ' + 'entryIds: $entryIds, ' + 'owner: $ownerId, ' + 'createdAt: $createdAt, ' + 'updatedAt: $updatedAt, ' + 'version: $version' + ' }'; +} diff --git a/lib/src/presentation/models/part_model.dart b/lib/src/presentation/models/part_model.dart new file mode 100644 index 0000000..afe14e6 --- /dev/null +++ b/lib/src/presentation/models/part_model.dart @@ -0,0 +1,58 @@ +import 'package:social_cv_client_flutter/presentation.dart'; + +class PartViewModel extends ElementViewModel { + String name; + List groupIds; + String type; + String ownerId; + + PartViewModel({ + String id, + this.name, + this.groupIds, + this.type, + this.ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); + + PartViewModel copyWith({ + String id, + String name, + List groupIds, + String type, + String ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) { + return PartViewModel( + id: id ?? this.id, + name: name ?? this.name, + groupIds: groupIds ?? this.groupIds, + type: type ?? this.type, + ownerId: ownerId ?? this.ownerId, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + version: version ?? this.version, + ); + } + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'name: $name, ' + 'groupIds: $groupIds, ' + 'type: $type, ' + 'owner: $ownerId, ' + 'createdAt: $createdAt, ' + 'updatedAt: $updatedAt, ' + 'version: $version' + ' }'; +} diff --git a/lib/src/presentation/models/profile_model.dart b/lib/src/presentation/models/profile_model.dart new file mode 100644 index 0000000..72fde1a --- /dev/null +++ b/lib/src/presentation/models/profile_model.dart @@ -0,0 +1,73 @@ +import 'package:social_cv_client_flutter/presentation.dart'; + +class ProfileViewModel extends ElementViewModel { + String title; + String subtitle; + Uri picture; + Uri cover; + String type; + List partIds; + String ownerId; + + ProfileViewModel({ + String id, + this.title, + this.subtitle, + this.picture, + this.cover, + this.partIds, + this.type, + this.ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); + + ProfileViewModel copyWith({ + String id, + String title, + String subtitle, + Uri picture, + Uri cover, + List partIds, + String type, + String ownerId, + DateTime createdAt, + DateTime updatedAt, + int version, + }) { + return ProfileViewModel( + id: id ?? this.id, + title: title ?? this.title, + subtitle: subtitle ?? this.subtitle, + picture: picture ?? this.picture, + cover: cover ?? this.cover, + type: type ?? this.type, + partIds: partIds ?? this.partIds, + ownerId: ownerId ?? this.ownerId, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + version: version ?? this.version, + ); + } + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'title: $title, ' + 'subtitle: $subtitle, ' + 'picture: $picture, ' + 'cover: $cover, ' + 'type: $type, ' + 'partIds: $partIds, ' + 'owner: $ownerId, ' + 'createdAt: $createdAt, ' + 'updatedAt: $updatedAt, ' + 'verrsion: $version' + ' }'; +} diff --git a/lib/src/presentation/models/user_model.dart b/lib/src/presentation/models/user_model.dart new file mode 100644 index 0000000..8142760 --- /dev/null +++ b/lib/src/presentation/models/user_model.dart @@ -0,0 +1,42 @@ +import 'package:social_cv_client_flutter/presentation.dart'; + +class UserViewModel extends ElementViewModel { + bool disabled; + String email; + String username; + String picture; + List profileIds; + dynamic permission; + + UserViewModel({ + String id, + this.disabled, + this.email, + this.username, + this.picture, + this.profileIds, + this.permission, + DateTime createdAt, + DateTime updatedAt, + int version, + }) : super( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + version: version, + ); + + @override + String toString() => '$runtimeType{ ' + 'id: $id, ' + 'disabled: $disabled, ' + 'email: $email, ' + 'username: $username, ' + 'picture: $picture, ' + 'profileIds: $profileIds, ' + 'permission: $permission, ' + 'createdAt: $createdAt, ' + 'updatedAt: $updatedAt, ' + 'version: $version' + ' }'; +} diff --git a/lib/src/presentation/router.dart b/lib/src/presentation/router.dart index 9d6c5e9..47e9d99 100644 --- a/lib/src/presentation/router.dart +++ b/lib/src/presentation/router.dart @@ -4,7 +4,7 @@ import 'package:social_cv_client_flutter/presentation.dart'; class AppRouter { final String _tag = '$AppRouter'; - final Router router = Router(); + final FluroRouter router = FluroRouter(); AppRouter() { _defineRoutes(); diff --git a/pubspec.lock b/pubspec.lock index 26f47f5..32653f1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -14,16 +14,16 @@ packages: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.2" + version: "1.6.0" async: dependency: "direct main" description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.5.0-nullsafety.2" bloc: - dependency: transitive + dependency: "direct main" description: name: bloc url: "https://pub.dartlang.org" @@ -35,84 +35,105 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.1.0-nullsafety.2" build: dependency: transitive description: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.1.5" + version: "1.2.2" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.0" + version: "0.4.2" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.4" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.0.6" + version: "1.2.1" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "1.9.0" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "3.0.6" + version: "5.1.0" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "4.2.2" + version: "4.3.2" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "6.7.0" + version: "7.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0-nullsafety.4" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.2.0-nullsafety.2" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0-nullsafety.2" code_builder: dependency: transitive description: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.2.0" + version: "3.5.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.15.0-nullsafety.4" convert: dependency: transitive description: @@ -120,27 +141,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" - cookie_jar: - dependency: transitive - description: - name: cookie_jar - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.1.5" csslib: dependency: transitive description: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.1" + version: "0.16.2" dart_style: dependency: transitive description: @@ -149,33 +163,54 @@ packages: source: hosted version: "1.2.9" dio: - dependency: transitive + dependency: "direct main" description: name: dio url: "https://pub.dartlang.org" source: hosted - version: "2.1.13" + version: "3.0.10" equatable: - dependency: transitive + dependency: "direct main" description: name: equatable url: "https://pub.dartlang.org" source: hosted version: "0.1.10" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0-nullsafety.2" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.1" fixnum: dependency: transitive description: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "0.10.9" + version: "0.10.11" fluro: dependency: "direct main" description: name: fluro url: "https://pub.dartlang.org" source: hosted - version: "1.5.1" + version: "1.7.7" flutter: dependency: "direct main" description: flutter @@ -187,7 +222,7 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "0.20.0" + version: "0.20.1" flutter_localizations: dependency: "direct main" description: flutter @@ -198,6 +233,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" front_end: dependency: transitive description: @@ -211,7 +251,7 @@ packages: name: glob url: "https://pub.dartlang.org" source: hosted - version: "1.1.7" + version: "1.2.0" graphs: dependency: transitive description: @@ -225,56 +265,56 @@ packages: name: html url: "https://pub.dartlang.org" source: hosted - version: "0.14.0+2" + version: "0.14.0+4" http: - dependency: transitive + dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.0+2" + version: "0.12.2" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.3" + version: "3.1.4" intl: dependency: "direct main" description: name: intl url: "https://pub.dartlang.org" source: hosted - version: "0.15.8" + version: "0.16.1" intl_translation: dependency: "direct main" description: name: intl_translation url: "https://pub.dartlang.org" source: hosted - version: "0.17.5" + version: "0.17.10" io: dependency: transitive description: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.3" + version: "0.3.4" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.1+1" + version: "0.6.3-nullsafety.2" json_annotation: dependency: "direct main" description: @@ -302,42 +342,56 @@ packages: name: logging url: "https://pub.dartlang.org" source: hosted - version: "0.11.3+2" + version: "0.11.4" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.5" + version: "0.12.10-nullsafety.2" material_design_icons_flutter: dependency: "direct main" description: name: material_design_icons_flutter url: "https://pub.dartlang.org" source: hosted - version: "2.7.94" + version: "4.0.5755" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0-nullsafety.5" mime: dependency: transitive description: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.6+3" + version: "0.9.7" + node_interop: + dependency: transitive + description: + name: node_interop + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.9.3" package_resolver: dependency: transitive description: @@ -351,21 +405,56 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "1.8.0-nullsafety.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+3" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.9.2" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "3.1.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" pool: dependency: transitive description: @@ -373,55 +462,97 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.0" + process: + dependency: transitive + description: + name: process + 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: "3.0.0+1" + version: "3.2.0" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.2" + version: "1.4.4" pubspec_parse: dependency: transitive description: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.1.5" rxdart: dependency: "direct main" description: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.22.1" + version: "0.22.6" shared_preferences: dependency: "direct main" description: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" + version: "0.5.12+4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.2+4" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+11" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2+7" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+3" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.5" + version: "0.7.9" shelf_web_socket: dependency: transitive description: @@ -434,76 +565,69 @@ packages: description: flutter source: sdk version: "0.0.99" - social_cv_client_dart_common: - dependency: "direct main" - description: - path: "..\\Social-CV-Client-Dart-common" - relative: true - source: path - version: "1.0.0" source_gen: dependency: transitive description: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.4+3" + version: "0.9.4+4" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.8.0-nullsafety.3" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0-nullsafety.5" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety.2" stream_transform: dependency: transitive description: name: stream_transform url: "https://pub.dartlang.org" source: hosted - version: "0.0.19" + version: "1.2.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.1.0-nullsafety.2" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0-nullsafety.2" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.5" + version: "0.2.19-nullsafety.4" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+1" + version: "0.1.1+2" transparent_image: dependency: "direct main" description: @@ -517,35 +641,49 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0-nullsafety.4" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0-nullsafety.4" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+12" + version: "0.9.7+15" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.0.14" + version: "1.1.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.4" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.1.16" + version: "2.2.1" sdks: - dart: ">=2.4.0 <3.0.0" - flutter: ">=1.2.0 <2.0.0" + dart: ">=2.11.0-0.0 <2.12.0" + flutter: ">=1.17.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 100f1cc..f361e07 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,23 +1,21 @@ name: social_cv_client_flutter -description: A new Flutter application. +description: A new CV application. environment: - sdk: '>=2.2.2 <3.0.0' + sdk: ">=2.7.0 <3.0.0" dependencies: - - # Common Social CV Logic - social_cv_client_dart_common: - path: ../Social-CV-Client-Dart-common - #git: - # url: https://github.com/axellebot/Social-CV-Client-Dart-common - # ref: feature/blocs - flutter: sdk: flutter flutter_localizations: sdk: flutter + equatable: ^0.1.6 + + # Web Client + http: ^0.12.0 + dio: ^3.0.10 + # Routes fluro: ^1.3.7 @@ -27,25 +25,26 @@ dependencies: # Bloc Pattern flutter_bloc: ^0.20.0 + bloc: ^0.14.4 # Dependency Injection provider: ^3.0.0 # Storage - shared_preferences: ^0.4.3 + shared_preferences: ^0.5.12 # Serialization json_annotation: ^2.3.0 # Translations - intl: ^0.15.7 + intl: ^0.16.1 intl_translation: ^0.17.0 # UI Widgets transparent_image: ^0.1.0 # Icons - material_design_icons_flutter: ^2.7.94 + material_design_icons_flutter: ^4.0.5755 #cupertino_icons: ^0.1.0 dev_dependencies: diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 zcmZ`-2T+sGz6~)*FVZ`aW+(v>MIm&M-g^@e2u-B-DoB?qO+b1Tq<5uCCv>ESfRum& zp%X;f!~1{tzL__3=gjVJ=j=J>+nMj%ncXj1Q(b|Ckbw{Y0FWpt%4y%$uD=Z*c-x~o zE;IoE;xa#7Ll5nj-e4CuXB&G*IM~D21rCP$*xLXAK8rIMCSHuSu%bL&S3)8YI~vyp@KBu9Ph7R_pvKQ@xv>NQ`dZp(u{Z8K3yOB zn7-AR+d2JkW)KiGx0hosml;+eCXp6+w%@STjFY*CJ?udJ64&{BCbuebcuH;}(($@@ znNlgBA@ZXB)mcl9nbX#F!f_5Z=W>0kh|UVWnf!At4V*LQP%*gPdCXd6P@J4Td;!Ur z<2ZLmwr(NG`u#gDEMP19UcSzRTL@HsK+PnIXbVBT@oHm53DZr?~V(0{rsalAfwgo zEh=GviaqkF;}F_5-yA!1u3!gxaR&Mj)hLuj5Q-N-@Lra{%<4ONja8pycD90&>yMB` zchhd>0CsH`^|&TstH-8+R`CfoWqmTTF_0?zDOY`E`b)cVi!$4xA@oO;SyOjJyP^_j zx^@Gdf+w|FW@DMdOi8=4+LJl$#@R&&=UM`)G!y%6ZzQLoSL%*KE8IO0~&5XYR9 z&N)?goEiWA(YoRfT{06&D6Yuu@Qt&XVbuW@COb;>SP9~aRc+z`m`80pB2o%`#{xD@ zI3RAlukL5L>px6b?QW1Ac_0>ew%NM!XB2(H+1Y3AJC?C?O`GGs`331Nd4ZvG~bMo{lh~GeL zSL|tT*fF-HXxXYtfu5z+T5Mx9OdP7J4g%@oeC2FaWO1D{=NvL|DNZ}GO?O3`+H*SI z=grGv=7dL{+oY0eJFGO!Qe(e2F?CHW(i!!XkGo2tUvsQ)I9ev`H&=;`N%Z{L zO?vV%rDv$y(@1Yj@xfr7Kzr<~0{^T8wM80xf7IGQF_S-2c0)0D6b0~yD7BsCy+(zL z#N~%&e4iAwi4F$&dI7x6cE|B{f@lY5epaDh=2-(4N05VO~A zQT3hanGy_&p+7Fb^I#ewGsjyCEUmSCaP6JDB*=_()FgQ(-pZ28-{qx~2foO4%pM9e z*_63RT8XjgiaWY|*xydf;8MKLd{HnfZ2kM%iq}fstImB-K6A79B~YoPVa@tYN@T_$ zea+9)<%?=Fl!kd(Y!G(-o}ko28hg2!MR-o5BEa_72uj7Mrc&{lRh3u2%Y=Xk9^-qa zBPWaD=2qcuJ&@Tf6ue&)4_V*45=zWk@Z}Q?f5)*z)-+E|-yC4fs5CE6L_PH3=zI8p z*Z3!it{1e5_^(sF*v=0{`U9C741&lub89gdhKp|Y8CeC{_{wYK-LSbp{h)b~9^j!s z7e?Y{Z3pZv0J)(VL=g>l;<}xk=T*O5YR|hg0eg4u98f2IrA-MY+StQIuK-(*J6TRR z|IM(%uI~?`wsfyO6Tgmsy1b3a)j6M&-jgUjVg+mP*oTKdHg?5E`!r`7AE_#?Fc)&a z08KCq>Gc=ne{PCbRvs6gVW|tKdcE1#7C4e`M|j$C5EYZ~Y=jUtc zj`+?p4ba3uy7><7wIokM79jPza``{Lx0)zGWg;FW1^NKY+GpEi=rHJ+fVRGfXO zPHV52k?jxei_!YYAw1HIz}y8ZMwdZqU%ESwMn7~t zdI5%B;U7RF=jzRz^NuY9nM)&<%M>x>0(e$GpU9th%rHiZsIT>_qp%V~ILlyt^V`=d z!1+DX@ah?RnB$X!0xpTA0}lN@9V-ePx>wQ?-xrJr^qDlw?#O(RsXeAvM%}rg0NT#t z!CsT;-vB=B87ShG`GwO;OEbeL;a}LIu=&@9cb~Rsx(ZPNQ!NT7H{@j0e(DiLea>QD zPmpe90gEKHEZ8oQ@6%E7k-Ptn#z)b9NbD@_GTxEhbS+}Bb74WUaRy{w;E|MgDAvHw zL)ycgM7mB?XVh^OzbC?LKFMotw3r@i&VdUV%^Efdib)3@soX%vWCbnOyt@Y4swW925@bt45y0HY3YI~BnnzZYrinFy;L?2D3BAL`UQ zEj))+f>H7~g8*VuWQ83EtGcx`hun$QvuurSMg3l4IP8Fe`#C|N6mbYJ=n;+}EQm;< z!!N=5j1aAr_uEnnzrEV%_E|JpTb#1p1*}5!Ce!R@d$EtMR~%9# zd;h8=QGT)KMW2IKu_fA_>p_und#-;Q)p%%l0XZOXQicfX8M~7?8}@U^ihu;mizj)t zgV7wk%n-UOb z#!P5q?Ex+*Kx@*p`o$q8FWL*E^$&1*!gpv?Za$YO~{BHeGY*5%4HXUKa_A~~^d z=E*gf6&+LFF^`j4$T~dR)%{I)T?>@Ma?D!gi9I^HqvjPc3-v~=qpX1Mne@*rzT&Xw zQ9DXsSV@PqpEJO-g4A&L{F&;K6W60D!_vs?Vx!?w27XbEuJJP&);)^+VF1nHqHBWu z^>kI$M9yfOY8~|hZ9WB!q-9u&mKhEcRjlf2nm_@s;0D#c|@ED7NZE% zzR;>P5B{o4fzlfsn3CkBK&`OSb-YNrqx@N#4CK!>bQ(V(D#9|l!e9(%sz~PYk@8zt zPN9oK78&-IL_F zhsk1$6p;GqFbtB^ZHHP+cjMvA0(LqlskbdYE_rda>gvQLTiqOQ1~*7lg%z*&p`Ry& zRcG^DbbPj_jOKHTr8uk^15Boj6>hA2S-QY(W-6!FIq8h$<>MI>PYYRenQDBamO#Fv zAH5&ImqKBDn0v5kb|8i0wFhUBJTpT!rB-`zK)^SNnRmLraZcPYK7b{I@+}wXVdW-{Ps17qdRA3JatEd?rPV z4@}(DAMf5EqXCr4-B+~H1P#;t@O}B)tIJ(W6$LrK&0plTmnPpb1TKn3?f?Kk``?D+ zQ!MFqOX7JbsXfQrz`-M@hq7xlfNz;_B{^wbpG8des56x(Q)H)5eLeDwCrVR}hzr~= zM{yXR6IM?kXxauLza#@#u?Y|o;904HCqF<8yT~~c-xyRc0-vxofnxG^(x%>bj5r}N zyFT+xnn-?B`ohA>{+ZZQem=*Xpqz{=j8i2TAC#x-m;;mo{{sLB_z(UoAqD=A#*juZ zCv=J~i*O8;F}A^Wf#+zx;~3B{57xtoxC&j^ie^?**T`WT2OPRtC`xj~+3Kprn=rVM zVJ|h5ux%S{dO}!mq93}P+h36mZ5aZg1-?vhL$ke1d52qIiXSE(llCr5i=QUS?LIjc zV$4q=-)aaR4wsrQv}^shL5u%6;`uiSEs<1nG^?$kl$^6DL z43CjY`M*p}ew}}3rXc7Xck@k41jx}c;NgEIhKZ*jsBRZUP-x2cm;F1<5$jefl|ppO zmZd%%?gMJ^g9=RZ^#8Mf5aWNVhjAS^|DQO+q$)oeob_&ZLFL(zur$)); zU19yRm)z<4&4-M}7!9+^Wl}Uk?`S$#V2%pQ*SIH5KI-mn%i;Z7-)m$mN9CnI$G7?# zo`zVrUwoSL&_dJ92YhX5TKqaRkfPgC4=Q&=K+;_aDs&OU0&{WFH}kKX6uNQC6%oUH z2DZa1s3%Vtk|bglbxep-w)PbFG!J17`<$g8lVhqD2w;Z0zGsh-r zxZ13G$G<48leNqR!DCVt9)@}(zMI5w6Wo=N zpP1*3DI;~h2WDWgcKn*f!+ORD)f$DZFwgKBafEZmeXQMAsq9sxP9A)7zOYnkHT9JU zRA`umgmP9d6=PHmFIgx=0$(sjb>+0CHG)K@cPG{IxaJ&Ueo8)0RWgV9+gO7+Bl1(F z7!BslJ2MP*PWJ;x)QXbR$6jEr5q3 z(3}F@YO_P1NyTdEXRLU6fp?9V2-S=E+YaeLL{Y)W%6`k7$(EW8EZSA*(+;e5@jgD^I zaJQ2|oCM1n!A&-8`;#RDcZyk*+RPkn_r8?Ak@agHiSp*qFNX)&i21HE?yuZ;-C<3C zwJGd1lx5UzViP7sZJ&|LqH*mryb}y|%AOw+v)yc`qM)03qyyrqhX?ub`Cjwx2PrR! z)_z>5*!*$x1=Qa-0uE7jy0z`>|Ni#X+uV|%_81F7)b+nf%iz=`fF4g5UfHS_?PHbr zB;0$bK@=di?f`dS(j{l3-tSCfp~zUuva+=EWxJcRfp(<$@vd(GigM&~vaYZ0c#BTs z3ijkxMl=vw5AS&DcXQ%eeKt!uKvh2l3W?&3=dBHU=Gz?O!40S&&~ei2vg**c$o;i89~6DVns zG>9a*`k5)NI9|?W!@9>rzJ;9EJ=YlJTx1r1BA?H`LWijk(rTax9(OAu;q4_wTj-yj z1%W4GW&K4T=uEGb+E!>W0SD_C0RR91 literal 0 HcmV?d00001 diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 zcmd5=2T+s!lYZ%-(h(2@5fr2dC?F^$C=i-}R6$UX8af(!je;W5yC_|HmujSgN*6?W z3knF*TL1$|?oD*=zPbBVex*RUIKsL<(&Rj9%^UD2IK3W?2j>D?eWQgvS-HLymHo9%~|N2Q{~j za?*X-{b9JRowv_*Mh|;*-kPFn>PI;r<#kFaxFqbn?aq|PduQg=2Q;~Qc}#z)_T%x9 zE|0!a70`58wjREmAH38H1)#gof)U3g9FZ^ zF7&-0^Hy{4XHWLoC*hOG(dg~2g6&?-wqcpf{ z&3=o8vw7lMi22jCG9RQbv8H}`+}9^zSk`nlR8?Z&G2dlDy$4#+WOlg;VHqzuE=fM@ z?OI6HEJH4&tA?FVG}9>jAnq_^tlw8NbjNhfqk2rQr?h(F&WiKy03Sn=-;ZJRh~JrD zbt)zLbnabttEZ>zUiu`N*u4sfQaLE8-WDn@tHp50uD(^r-}UsUUu)`!Rl1PozAc!a z?uj|2QDQ%oV-jxUJmJycySBINSKdX{kDYRS=+`HgR2GO19fg&lZKyBFbbXhQV~v~L za^U944F1_GtuFXtvDdDNDvp<`fqy);>Vw=ncy!NB85Tw{&sT5&Ox%-p%8fTS;OzlRBwErvO+ROe?{%q-Zge=%Up|D4L#>4K@Ke=x%?*^_^P*KD zgXueMiS63!sEw@fNLB-i^F|@Oib+S4bcy{eu&e}Xvb^(mA!=U=Xr3||IpV~3K zQWzEsUeX_qBe6fky#M zzOJm5b+l;~>=sdp%i}}0h zO?B?i*W;Ndn02Y0GUUPxERG`3Bjtj!NroLoYtyVdLtl?SE*CYpf4|_${ku2s`*_)k zN=a}V8_2R5QANlxsq!1BkT6$4>9=-Ix4As@FSS;1q^#TXPrBsw>hJ}$jZ{kUHoP+H zvoYiR39gX}2OHIBYCa~6ERRPJ#V}RIIZakUmuIoLF*{sO8rAUEB9|+A#C|@kw5>u0 zBd=F!4I)Be8ycH*)X1-VPiZ+Ts8_GB;YW&ZFFUo|Sw|x~ZajLsp+_3gv((Q#N>?Jz zFBf`~p_#^${zhPIIJY~yo!7$-xi2LK%3&RkFg}Ax)3+dFCjGgKv^1;lUzQlPo^E{K zmCnrwJ)NuSaJEmueEPO@(_6h3f5mFffhkU9r8A8(JC5eOkux{gPmx_$Uv&|hyj)gN zd>JP8l2U&81@1Hc>#*su2xd{)T`Yw< zN$dSLUN}dfx)Fu`NcY}TuZ)SdviT{JHaiYgP4~@`x{&h*Hd>c3K_To9BnQi@;tuoL z%PYQo&{|IsM)_>BrF1oB~+`2_uZQ48z9!)mtUR zdfKE+b*w8cPu;F6RYJiYyV;PRBbThqHBEu_(U{(gGtjM}Zi$pL8Whx}<JwE3RM0F8x7%!!s)UJVq|TVd#hf1zVLya$;mYp(^oZQ2>=ZXU1c$}f zm|7kfk>=4KoQoQ!2&SOW5|JP1)%#55C$M(u4%SP~tHa&M+=;YsW=v(Old9L3(j)`u z2?#fK&1vtS?G6aOt@E`gZ9*qCmyvc>Ma@Q8^I4y~f3gs7*d=ATlP>1S zyF=k&6p2;7dn^8?+!wZO5r~B+;@KXFEn^&C=6ma1J7Au6y29iMIxd7#iW%=iUzq&C=$aPLa^Q zncia$@TIy6UT@69=nbty5epP>*fVW@5qbUcb2~Gg75dNd{COFLdiz3}kODn^U*=@E z0*$7u7Rl2u)=%fk4m8EK1ctR!6%Ve`e!O20L$0LkM#f+)n9h^dn{n`T*^~d+l*Qlx z$;JC0P9+en2Wlxjwq#z^a6pdnD6fJM!GV7_%8%c)kc5LZs_G^qvw)&J#6WSp< zmsd~1-(GrgjC56Pdf6#!dt^y8Rg}!#UXf)W%~PeU+kU`FeSZHk)%sFv++#Dujk-~m zFHvVJC}UBn2jN& zs!@nZ?e(iyZPNo`p1i#~wsv9l@#Z|ag3JR>0#u1iW9M1RK1iF6-RbJ4KYg?B`dET9 zyR~DjZ>%_vWYm*Z9_+^~hJ_|SNTzBKx=U0l9 z9x(J96b{`R)UVQ$I`wTJ@$_}`)_DyUNOso6=WOmQKI1e`oyYy1C&%AQU<0-`(ow)1 zT}gYdwWdm4wW6|K)LcfMe&psE0XGhMy&xS`@vLi|1#Za{D6l@#D!?nW87wcscUZgELT{Cz**^;Zb~7 z(~WFRO`~!WvyZAW-8v!6n&j*PLm9NlN}BuUN}@E^TX*4Or#dMMF?V9KBeLSiLO4?B zcE3WNIa-H{ThrlCoN=XjOGk1dT=xwwrmt<1a)mrRzg{35`@C!T?&_;Q4Ce=5=>z^*zE_c(0*vWo2_#TD<2)pLXV$FlwP}Ik74IdDQU@yhkCr5h zn5aa>B7PWy5NQ!vf7@p_qtC*{dZ8zLS;JetPkHi>IvPjtJ#ThGQD|Lq#@vE2xdl%`x4A8xOln}BiQ92Po zW;0%A?I5CQ_O`@Ad=`2BLPPbBuPUp@Hb%a_OOI}y{Rwa<#h z5^6M}s7VzE)2&I*33pA>e71d78QpF>sNK;?lj^Kl#wU7G++`N_oL4QPd-iPqBhhs| z(uVM}$ItF-onXuuXO}o$t)emBO3Hjfyil@*+GF;9j?`&67GBM;TGkLHi>@)rkS4Nj zAEk;u)`jc4C$qN6WV2dVd#q}2X6nKt&X*}I@jP%Srs%%DS92lpDY^K*Sx4`l;aql$ zt*-V{U&$DM>pdO?%jt$t=vg5|p+Rw?SPaLW zB6nvZ69$ne4Z(s$3=Rf&RX8L9PWMV*S0@R zuIk&ba#s6sxVZ51^4Kon46X^9`?DC9mEhWB3f+o4#2EXFqy0(UTc>GU| zGCJmI|Dn-dX#7|_6(fT)>&YQ0H&&JX3cTvAq(a@ydM4>5Njnuere{J8p;3?1az60* z$1E7Yyxt^ytULeokgDnRVKQw9vzHg1>X@@jM$n$HBlveIrKP5-GJq%iWH#odVwV6cF^kKX(@#%%uQVb>#T6L^mC@)%SMd4DF? zVky!~ge27>cpUP1Vi}Z32lbLV+CQy+T5Wdmva6Fg^lKb!zrg|HPU=5Qu}k;4GVH+x z%;&pN1LOce0w@9i1Mo-Y|7|z}fbch@BPp2{&R-5{GLoeu8@limQmFF zaJRR|^;kW_nw~0V^ zfTnR!Ni*;-%oSHG1yItARs~uxra|O?YJxBzLjpeE-=~TO3Dn`JL5Gz;F~O1u3|FE- zvK2Vve`ylc`a}G`gpHg58Cqc9fMoy1L}7x7T>%~b&irrNMo?np3`q;d3d;zTK>nrK zOjPS{@&74-fA7j)8uT9~*g23uGnxwIVj9HorzUX#s0pcp2?GH6i}~+kv9fWChtPa_ z@T3m+$0pbjdQw7jcnHn;Pi85hk_u2-1^}c)LNvjdam8K-XJ+KgKQ%!?2n_!#{$H|| zLO=%;hRo6EDmnOBKCL9Cg~ETU##@u^W_5joZ%Et%X_n##%JDOcsO=0VL|Lkk!VdRJ z^|~2pB@PUspT?NOeO?=0Vb+fAGc!j%Ufn-cB`s2A~W{Zj{`wqWq_-w0wr@6VrM zbzni@8c>WS!7c&|ZR$cQ;`niRw{4kG#e z70e!uX8VmP23SuJ*)#(&R=;SxGAvq|&>geL&!5Z7@0Z(No*W561n#u$Uc`f9pD70# z=sKOSK|bF~#khTTn)B28h^a1{;>EaRnHj~>i=Fnr3+Fa4 z`^+O5_itS#7kPd20rq66_wH`%?HNzWk@XFK0n;Z@Cx{kx==2L22zWH$Yg?7 zvDj|u{{+NR3JvUH({;b*$b(U5U z7(lF!1bz2%06+|-v(D?2KgwNw7( zJB#Tz+ZRi&U$i?f34m7>uTzO#+E5cbaiQ&L}UxyOQq~afbNB4EI{E04ZWg53w0A{O%qo=lF8d zf~ktGvIgf-a~zQoWf>loF7pOodrd0a2|BzwwPDV}ShauTK8*fmF6NRbO>Iw9zZU}u zw8Ya}?seBnEGQDmH#XpUUkj}N49tP<2jYwTFp!P+&Fd(%Z#yo80|5@zN(D{_pNow*&4%ql zW~&yp@scb-+Qj-EmErY+Tu=dUmf@*BoXY2&oKT8U?8?s1d}4a`Aq>7SV800m$FE~? zjmz(LY+Xx9sDX$;vU`xgw*jLw7dWOnWWCO8o|;}f>cu0Q&`0I{YudMn;P;L3R-uz# zfns_mZED_IakFBPP2r_S8XM$X)@O-xVKi4`7373Jkd5{2$M#%cRhWer3M(vr{S6>h zj{givZJ3(`yFL@``(afn&~iNx@B1|-qfYiZu?-_&Z8+R~v`d6R-}EX9IVXWO-!hL5 z*k6T#^2zAXdardU3Ao~I)4DGdAv2bx{4nOK`20rJo>rmk3S2ZDu}))8Z1m}CKigf0 z3L`3Y`{huj`xj9@`$xTZzZc3je?n^yG<8sw$`Y%}9mUsjUR%T!?k^(q)6FH6Af^b6 zlPg~IEwg0y;`t9y;#D+uz!oE4VP&Je!<#q*F?m5L5?J3i@!0J6q#eu z!RRU`-)HeqGi_UJZ(n~|PSNsv+Wgl{P-TvaUQ9j?ZCtvb^37U$sFpBrkT{7Jpd?HpIvj2!}RIq zH{9~+gErN2+}J`>Jvng2hwM`=PLNkc7pkjblKW|+Fk9rc)G1R>Ww>RC=r-|!m-u7( zc(a$9NG}w#PjWNMS~)o=i~WA&4L(YIW25@AL9+H9!?3Y}sv#MOdY{bb9j>p`{?O(P zIvb`n?_(gP2w3P#&91JX*md+bBEr%xUHMVqfB;(f?OPtMnAZ#rm5q5mh;a2f_si2_ z3oXWB?{NF(JtkAn6F(O{z@b76OIqMC$&oJ_&S|YbFJ*)3qVX_uNf5b8(!vGX19hsG z(OP>RmZp29KH9Ge2kKjKigUmOe^K_!UXP`von)PR8Qz$%=EmOB9xS(ZxE_tnyzo}7 z=6~$~9k0M~v}`w={AeqF?_)9q{m8K#6M{a&(;u;O41j)I$^T?lx5(zlebpY@NT&#N zR+1bB)-1-xj}R8uwqwf=iP1GbxBjneCC%UrSdSxK1vM^i9;bUkS#iRZw2H>rS<2<$ zNT3|sDH>{tXb=zq7XZi*K?#Zsa1h1{h5!Tq_YbKFm_*=A5-<~j63he;4`77!|LBlo zR^~tR3yxcU=gDFbshyF6>o0bdp$qmHS7D}m3;^QZq9kBBU|9$N-~oU?G5;jyFR7>z hN`IR97YZXIo@y!QgFWddJ3|0`sjFx!m))><{BI=FK%f8s literal 0 HcmV?d00001 diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..c172e90 --- /dev/null +++ b/web/index.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + social_cv_client_flutter + + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..aa9bddb --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "social_cv_client_flutter", + "short_name": "social_cv_client_flutter", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +}