ui backbone
This commit is contained in:
		
							parent
							
								
									3e374d24f6
								
							
						
					
					
						commit
						b7045fc242
					
				
					 24 changed files with 918 additions and 73 deletions
				
			
		|  | @ -1,29 +1,218 @@ | |||
| # This file configures the analyzer, which statically analyzes Dart code to | ||||
| # check for errors, warnings, and lints. | ||||
| # | ||||
| # The issues identified by the analyzer are surfaced in the UI of Dart-enabled | ||||
| # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be | ||||
| # invoked from the command line by running `flutter analyze`. | ||||
| analyzer: | ||||
|   strong-mode: | ||||
|     implicit-dynamic: true | ||||
|     implicit-casts: true | ||||
| 
 | ||||
| # The following line activates a set of recommended lints for Flutter apps, | ||||
| # packages, and plugins designed to encourage good coding practices. | ||||
| include: package:flutter_lints/flutter.yaml | ||||
|   errors: | ||||
|     avoid_dynamic_calls: error | ||||
|     missing_required_param: error | ||||
|     missing_return: error | ||||
|     # allow having TODOs in the code | ||||
|     todo: info | ||||
|     missing_enum_constant_in_switch: error | ||||
|     implicit_dynamic_type: info | ||||
|     implicit_dynamic_map_literal: info | ||||
|     implicit_dynamic_list_literal: info | ||||
|     implicit_dynamic_method: warning | ||||
|     implicit_dynamic_function: warning | ||||
|     invalid_use_of_protected_member: error | ||||
|     unused_local_variable: warning | ||||
|     deprecated_member_use: warning | ||||
|     unused_element: warning | ||||
|     unused_field: warning | ||||
|     dead_code: error | ||||
|     must_call_super: error | ||||
| 
 | ||||
|     # linter errors | ||||
|     unnecessary_statements: error | ||||
|     recursive_getters: error | ||||
|     unnecessary_new: error | ||||
|     unnecessary_getters_setters: error | ||||
|     use_function_type_syntax_for_parameters: error | ||||
|     no_duplicate_case_values: error | ||||
|     no_adjacent_strings_in_list: error | ||||
|     non_constant_identifier_names: error | ||||
|     constant_identifier_names: error | ||||
|     avoid_returning_this: error | ||||
|     prefer_const_constructors_in_immutables: error | ||||
|     prefer_const_literals_to_create_immutables: error | ||||
|     avoid_equals_and_hash_code_on_mutable_classes: error | ||||
|     camel_case_types: error | ||||
|     camel_case_extensions: error | ||||
|     library_names: error | ||||
|     file_names: error | ||||
|     library_prefixes: error | ||||
| 
 | ||||
|     # linter warnings | ||||
|     avoid_print: warning | ||||
|     unnecessary_lambdas: warning | ||||
|     use_key_in_widget_constructors: warning | ||||
|     prefer_final_fields: warning | ||||
|     prefer_final_locals: warning | ||||
|     prefer_final_in_for_each: warning | ||||
|     prefer_const_constructors: warning | ||||
|     unnecessary_const: warning | ||||
|     unnecessary_brace_in_string_interps: warning | ||||
| 
 | ||||
|     # Ignore analyzer hints for updating pubspecs when using Future or | ||||
|     # Stream and not importing dart:async | ||||
|     # Please see https://github.com/flutter/flutter/pull/24528 for details. | ||||
|     sdk_version_async_exported_from_core: ignore | ||||
|   exclude: | ||||
|     - "bin/cache/**" | ||||
|     - "**/*.chopper.dart" | ||||
|     - "**/generated/**" | ||||
|     - "**/*.g.dart" | ||||
| 
 | ||||
| linter: | ||||
|   # The lint rules applied to this project can be customized in the | ||||
|   # section below to disable rules from the `package:flutter_lints/flutter.yaml` | ||||
|   # included above or to enable additional rules. A list of all available lints | ||||
|   # and their documentation is published at | ||||
|   # https://dart-lang.github.io/linter/lints/index.html. | ||||
|   # | ||||
|   # Instead of disabling a lint rule for the entire project in the | ||||
|   # section below, it can also be suppressed for a single line of code | ||||
|   # or a specific dart file by using the `// ignore: name_of_lint` and | ||||
|   # `// ignore_for_file: name_of_lint` syntax on the line or in the file | ||||
|   # producing the lint. | ||||
|   rules: | ||||
|     # avoid_print: false  # Uncomment to disable the `avoid_print` rule | ||||
|     # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule | ||||
| 
 | ||||
| # Additional information about this file can be found at | ||||
| # https://dart.dev/guides/language/analysis-options | ||||
|     # these rules are documented on and in the same order as | ||||
|     # the Dart Lint rules page to make maintenance easier | ||||
|     # https://github.com/dart-lang/linter/blob/master/example/all.yaml | ||||
|     - always_declare_return_types | ||||
|     # always_put_control_body_on_new_line | ||||
|     # always_put_required_named_parameters_first | ||||
|     - always_require_non_null_named_parameters | ||||
|     - avoid_dynamic_calls | ||||
|     # - always_specify_types | ||||
|     - annotate_overrides | ||||
|     - avoid_print | ||||
|     # - avoid_annotating_with_dynamic # conflicts with always_specify_types | ||||
|     - avoid_returning_this | ||||
|     - avoid_init_to_null | ||||
|     - avoid_bool_literals_in_conditional_expressions | ||||
|     # - avoid_catches_without_on_clauses # we do this commonly | ||||
|     # - avoid_catching_errors # we do this commonly | ||||
|     #    - avoid_classes_with_only_static_members | ||||
|     # - avoid_double_and_int_checks # only useful when targeting JS runtime | ||||
|     - avoid_empty_else | ||||
|     - avoid_field_initializers_in_const_classes | ||||
|     - avoid_function_literals_in_foreach_calls | ||||
|     # - avoid_js_rounded_ints # only useful when targeting JS runtime | ||||
|     - avoid_null_checks_in_equality_operators | ||||
|     - avoid_positional_boolean_parameters | ||||
|     # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) | ||||
|     - avoid_relative_lib_imports | ||||
|     - avoid_renaming_method_parameters | ||||
|     - avoid_return_types_on_setters | ||||
|     # - avoid_returning_null # there are plenty of valid reasons to return null | ||||
|     # - avoid_returning_null_for_future # not yet tested | ||||
|     - avoid_returning_null_for_void | ||||
|     # - avoid_returning_this # there are plenty of valid reasons to return this | ||||
|     # - avoid_setters_without_getters # not yet tested | ||||
|     # - avoid_shadowing_type_parameters # not yet tested | ||||
|     # - avoid_single_cascade_in_expression_statements # not yet tested | ||||
|     - avoid_slow_async_io | ||||
|     - avoid_types_as_parameter_names | ||||
|     # - avoid_types_on_closure_parameters # conflicts with always_specify_types | ||||
|     - avoid_unused_constructor_parameters | ||||
|     - avoid_void_async | ||||
|     - await_only_futures | ||||
|     - camel_case_types | ||||
|     - cancel_subscriptions | ||||
|     # - cascade_invocations # not yet tested | ||||
|     # - close_sinks # not reliable enough | ||||
|     # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 | ||||
|     # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 | ||||
|     - control_flow_in_finally | ||||
|     # - curly_braces_in_flow_control_structures # not yet tested | ||||
|     # - diagnostic_describe_all_properties # not yet tested | ||||
|     - directives_ordering | ||||
|     - empty_catches | ||||
|     - empty_constructor_bodies | ||||
|     - empty_statements | ||||
|     # - file_names # not yet tested | ||||
|     - hash_and_equals | ||||
|     - implementation_imports | ||||
|     # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 | ||||
|     - iterable_contains_unrelated_type | ||||
|     # - join_return_with_assignment # not yet tested | ||||
|     - library_names | ||||
|     - library_prefixes | ||||
|     # - lines_longer_than_80_chars # not yet tested | ||||
|     - list_remove_unrelated_type | ||||
|     # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 | ||||
|     - no_adjacent_strings_in_list | ||||
|     - no_duplicate_case_values | ||||
|     - non_constant_identifier_names | ||||
|     # - null_closures  # not yet tested | ||||
|     # - omit_local_variable_types # opposite of always_specify_types | ||||
|     # - one_member_abstracts # too many false positives | ||||
|     # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 | ||||
|     - overridden_fields | ||||
|     - package_api_docs | ||||
|     - package_names | ||||
|     - package_prefixed_library_names | ||||
|     # - parameter_assignments # we do this commonly | ||||
|     - prefer_adjacent_string_concatenation | ||||
|     - prefer_asserts_in_initializer_lists | ||||
|     # - prefer_asserts_with_message # not yet tested | ||||
|     - 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 # not yet tested | ||||
|     - prefer_contains | ||||
|     # - prefer_double_quotes # opposite of prefer_single_quotes | ||||
|     - prefer_equal_for_default_values | ||||
|     # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods | ||||
|     - prefer_final_fields | ||||
|     # - prefer_final_in_for_each # not yet tested | ||||
|     - prefer_final_locals | ||||
|     # - prefer_for_elements_to_map_fromIterable # not yet tested | ||||
|     - prefer_foreach | ||||
|     # - prefer_function_declarations_over_variables # not yet tested | ||||
|     - prefer_generic_function_type_aliases | ||||
|     # - prefer_if_elements_to_conditional_expressions # not yet tested | ||||
|     - prefer_if_null_operators | ||||
|     - prefer_initializing_formals | ||||
|     - prefer_inlined_adds | ||||
|     # - prefer_int_literals # not yet tested | ||||
|     # - prefer_interpolation_to_compose_strings # not yet tested | ||||
|     - prefer_is_empty | ||||
|     - prefer_is_not_empty | ||||
|     - prefer_iterable_whereType | ||||
|     # - prefer_mixin # https://github.com/dart-lang/language/issues/32 | ||||
|     # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 | ||||
|     - prefer_single_quotes | ||||
|     - prefer_spread_collections | ||||
|     - prefer_typing_uninitialized_variables | ||||
|     - prefer_void_to_null | ||||
|     # - provide_deprecation_message # not yet tested | ||||
|     # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml | ||||
|     - recursive_getters | ||||
|     - slash_for_doc_comments | ||||
|     # - sort_child_properties_last # not yet tested | ||||
|     # - sort_constructors_first | ||||
|     - sort_pub_dependencies | ||||
|     - sort_unnamed_constructors_first | ||||
|     - test_types_in_equals | ||||
|     - throw_in_finally | ||||
|     # - type_annotate_public_apis # subset of always_specify_types | ||||
|     - type_init_formals | ||||
|     - unawaited_futures | ||||
|     # - unnecessary_await_in_return # not yet tested | ||||
|     - unnecessary_brace_in_string_interps | ||||
|     - unnecessary_const | ||||
|     - unnecessary_getters_setters | ||||
|     # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 | ||||
|     - unnecessary_new | ||||
|     - unnecessary_null_aware_assignments | ||||
|     - unnecessary_null_in_if_null_operators | ||||
|     - unnecessary_overrides | ||||
|     - unnecessary_parenthesis | ||||
|     - unnecessary_statements | ||||
|     - unnecessary_this | ||||
|     - unrelated_type_equality_checks | ||||
|     # - unsafe_html # not yet tested | ||||
|     - use_full_hex_values_for_flutter_colors | ||||
|     # - use_function_type_syntax_for_parameters # not yet tested | ||||
|     - use_rethrow_when_possible | ||||
|     - use_key_in_widget_constructors | ||||
|     # - use_setters_to_change_properties # not yet tested | ||||
|     # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 | ||||
|     # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review | ||||
|     - valid_regexps | ||||
|     # - void_checks # not yet tested | ||||
|  |  | |||
|  | @ -12,6 +12,8 @@ abstract class AppSetup { | |||
|   static Future<void> initialise() async { | ||||
|     WidgetsFlutterBinding.ensureInitialized(); | ||||
| 
 | ||||
|     await _setupStrings(); | ||||
| 
 | ||||
|     await SystemChrome.setPreferredOrientations([ | ||||
|       DeviceOrientation.portraitUp, | ||||
|       DeviceOrientation.portraitDown, | ||||
|  | @ -38,7 +40,7 @@ abstract class AppSetup { | |||
|     return supportedLocales.first; | ||||
|   } | ||||
| 
 | ||||
|   static Future<void> setupStrings() async { | ||||
|   static Future<void> _setupStrings() async { | ||||
|     await Strings.load(resolveLocale(WidgetsBinding.instance.window.locales, supportedLocales)); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,11 +1,11 @@ | |||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/widgets.dart'; | ||||
| import 'package:mc_gallery/features/core/services/logging_service.dart'; | ||||
| import 'package:mc_gallery/l10n/generated/l10n.dart'; | ||||
| 
 | ||||
| import '/l10n/generated/l10n.dart'; | ||||
| import '../data/enums/view_model_state.dart'; | ||||
| import '../services/logging_service.dart'; | ||||
| 
 | ||||
| abstract class BaseViewModel<E extends Object?> extends ChangeNotifier { | ||||
| abstract class BaseViewModel<T extends Object?> extends ChangeNotifier { | ||||
|   final ValueNotifier<bool> _isInitialised = ValueNotifier(false); | ||||
|   ValueListenable<bool> get isInitialised => _isInitialised; | ||||
| 
 | ||||
|  | @ -23,12 +23,11 @@ abstract class BaseViewModel<E extends Object?> extends ChangeNotifier { | |||
|   String get errorMessage => _errorMessage ?? strings.somethingWentWrong; | ||||
| 
 | ||||
|   @mustCallSuper | ||||
|   void initialise(DisposableBuildContext disposableBuildContext, bool Function() mounted, | ||||
|       [E? arguments]) { | ||||
|   void initialise(bool Function() mounted, [T? arguments]) { | ||||
|     _mounted = mounted; | ||||
|     _isInitialised.value = true; | ||||
|     _state.value = ViewModelState.isInitialised; | ||||
|     _loggingService.successfulInit(location: runtimeType.runtimeType.toString()); | ||||
|     _loggingService.successfulInit(location: runtimeType.toString()); | ||||
|   } | ||||
| 
 | ||||
|   void setBusy(bool isBusy) { | ||||
|  | @ -56,7 +55,7 @@ abstract class BaseViewModel<E extends Object?> extends ChangeNotifier { | |||
|   @override | ||||
|   void dispose() { | ||||
|     super.dispose(); | ||||
|     _loggingService.successfulDispose(location: runtimeType.runtimeType.toString()); | ||||
|     _loggingService.successfulDispose(location: runtimeType.toString()); | ||||
|   } | ||||
| 
 | ||||
|   late final bool Function() _mounted; | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import 'package:flutter/material.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:mc_gallery/features/home/views/gallery/gallery_view.dart'; | ||||
| import 'package:mc_gallery/features/home/views/image_carousel/image_carousel_view.dart'; | ||||
| 
 | ||||
| import '../../views/error_page_view.dart'; | ||||
| import 'routes.dart'; | ||||
|  | @ -14,12 +15,21 @@ class McgRouter { | |||
|       key: state.pageKey, | ||||
|       child: ErrorPageView(error: state.error), | ||||
|     ), | ||||
|     // TODO Add Redirect | ||||
|     //todo(mehul): Add Redirect | ||||
|     routes: [ | ||||
|       GoRoute( | ||||
|         path: Routes.home.routePath, | ||||
|         name: Routes.home.routeName, | ||||
|         builder: (context, _) => const GalleryView(), | ||||
|         routes: [ | ||||
|           GoRoute( | ||||
|             path: Routes.imageCarousel.routePath, | ||||
|             name: Routes.imageCarousel.routeName, | ||||
|             builder: (context, state) => ImageCarouselView( | ||||
|               imageCarouselViewArguments: state.extra as ImageCarouselViewArguments, | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ], | ||||
|   ); | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ enum Routes { | |||
|     routeName: 'Home', | ||||
|   )), | ||||
|   imageCarousel(RoutesInfo( | ||||
|     routePath: '/image_carousel', | ||||
|     routePath: 'image_carousel', | ||||
|     routeName: 'Image Carousel', | ||||
|   )); | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import 'package:flutter/material.dart'; | |||
| 
 | ||||
| abstract class ConstColours { | ||||
|   /// Smoke Gray => a neutral grey with neutral warmth/cold | ||||
|   static const imageBackgroundColour = Color.fromRGBO(127, 127, 125, 1.0); | ||||
|   static const galleryBackgroundColour = Color.fromRGBO(127, 127, 125, 1.0); | ||||
| 
 | ||||
|   static const white = Colors.white; | ||||
|   static const black = Colors.black; | ||||
|  |  | |||
|  | @ -4,6 +4,6 @@ abstract class ConstValues { | |||
|   static const List<String> backendUrlPathSegments = ['user', 'c_v_r']; | ||||
| 
 | ||||
|   static const int numberOfImages = 20; | ||||
|   static const int minImageSize = 100; | ||||
|   static const int maxImageSize = 250; | ||||
|   static const int minImageSize = 50; | ||||
|   static const int maxImageSize = 100; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										103
									
								
								lib/features/core/services/app_lifecycle_service.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								lib/features/core/services/app_lifecycle_service.dart
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,103 @@ | |||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:mc_gallery/locator.dart'; | ||||
| 
 | ||||
| import 'logging_service.dart'; | ||||
| 
 | ||||
| typedef AddLifeCycleListener = void Function({ | ||||
|   required void Function(AppLifecycleState appLifecycleState) listener, | ||||
|   required String tag, | ||||
|   bool tryCallListenerOnAdd, | ||||
| }); | ||||
| typedef RemoveLifeCycleListener = Future<void> Function({required String tag}); | ||||
| 
 | ||||
| /// Used to observe the current app lifecycle state. | ||||
| class AppLifecycleService with WidgetsBindingObserver { | ||||
|   AppLifecycleService({ | ||||
|     required LoggingService loggingService, | ||||
|   }) : _loggingService = loggingService { | ||||
|     WidgetsBinding.instance.addObserver(this); | ||||
|     _appLifeCycleState = WidgetsBinding.instance.lifecycleState; | ||||
|   } | ||||
| 
 | ||||
|   final LoggingService _loggingService; | ||||
| 
 | ||||
|   late final StreamController<AppLifecycleState> _streamController = StreamController.broadcast(); | ||||
|   final Map<String, StreamSubscription> _appLifecycleSubscriptions = {}; | ||||
| 
 | ||||
|   AppLifecycleState? _appLifeCycleState; | ||||
|   AppLifecycleState? get appLifeCycleState => _appLifeCycleState; | ||||
| 
 | ||||
|   Future<void> dispose() async { | ||||
|     _loggingService.info('Disposing app lifecycle service..'); | ||||
|     WidgetsBinding.instance.removeObserver(this); | ||||
|     for (final subscription in _appLifecycleSubscriptions.values) { | ||||
|       await subscription.cancel(); | ||||
|     } | ||||
|     _appLifecycleSubscriptions.clear(); | ||||
|     _loggingService.good('App lifecycle service disposed!'); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void didChangeAppLifecycleState(AppLifecycleState state) { | ||||
|     try { | ||||
|       _appLifeCycleState = state; | ||||
|       _streamController.add(state); | ||||
|     } catch (error, stackTrace) { | ||||
|       _loggingService.error( | ||||
|         'Something went wrong logging ${state.name} inside the didChangeAppLifeCycleState method', | ||||
|         error, | ||||
|         stackTrace, | ||||
|       ); | ||||
|     } | ||||
|     super.didChangeAppLifecycleState(state); | ||||
|   } | ||||
| 
 | ||||
|   void addListener({ | ||||
|     required void Function(AppLifecycleState appLifecycleState) listener, | ||||
|     required String tag, | ||||
|     bool tryCallListenerOnAdd = true, | ||||
|   }) { | ||||
|     try { | ||||
|       if (_appLifecycleSubscriptions.containsKey(tag)) { | ||||
|         _loggingService.warning('Tag already active, returning!'); | ||||
|       } else { | ||||
|         final message = 'Adding $tag appLifecycleState listener'; | ||||
|         _loggingService.info('$message..'); | ||||
|         if (_appLifeCycleState != null && tryCallListenerOnAdd) listener(_appLifeCycleState!); | ||||
|         _appLifecycleSubscriptions[tag] = _streamController.stream.listen(listener); | ||||
|         _loggingService.good('$message success!'); | ||||
|       } | ||||
|     } catch (error, stackTrace) { | ||||
|       _loggingService.error( | ||||
|         'Something went wrong adding $tag appLifecycleState listener!', | ||||
|         error, | ||||
|         stackTrace, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<void> removeListener({required String tag}) async { | ||||
|     try { | ||||
|       final message = 'Removing $tag appLifecycleState listener'; | ||||
|       _loggingService.info('$message..'); | ||||
|       final subscription = _appLifecycleSubscriptions[tag]; | ||||
|       if (subscription != null) { | ||||
|         await subscription.cancel(); | ||||
|         _appLifecycleSubscriptions.remove(tag); | ||||
|         _loggingService.good('$message success!'); | ||||
|       } else { | ||||
|         _loggingService.warning('Subscription was not found!'); | ||||
|       } | ||||
|     } catch (error, stackTrace) { | ||||
|       _loggingService.error( | ||||
|         'Something went wrong removing $tag appLifecycleState listener!', | ||||
|         error, | ||||
|         stackTrace, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   static AppLifecycleService get locate => Locator.locate(); | ||||
| } | ||||
|  | @ -1,5 +1,7 @@ | |||
| import 'package:flutter/widgets.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:mc_gallery/features/home/views/image_carousel/image_carousel_view.dart'; | ||||
| import 'package:mc_gallery/locator.dart'; | ||||
| 
 | ||||
| import '../abstracts/router/app_router.dart'; | ||||
| import '../abstracts/router/routes.dart'; | ||||
|  | @ -11,11 +13,19 @@ class NavigationService { | |||
| 
 | ||||
|   final McgRouter _mcgRouter; | ||||
| 
 | ||||
|   void pushImageCarouselView(BuildContext context) => | ||||
|       context.pushNamed(Routes.imageCarousel.routeName); | ||||
|   void pushImageCarouselView( | ||||
|     BuildContext context, { | ||||
|     required ImageCarouselViewArguments imageCarouselViewArguments, | ||||
|   }) => | ||||
|       context.pushNamed( | ||||
|         Routes.imageCarousel.routeName, | ||||
|         extra: imageCarouselViewArguments, | ||||
|       ); | ||||
| 
 | ||||
|   void backToGallery(BuildContext context) => context.pop(); | ||||
| 
 | ||||
|   void previous() {} | ||||
|   void next() {} | ||||
| 
 | ||||
|   static NavigationService get locate => Locator.locate(); | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/widgets.dart'; | ||||
| 
 | ||||
|  | @ -9,7 +8,7 @@ import '../services/connection_service.dart'; | |||
| class McgScaffold extends StatelessWidget { | ||||
|   const McgScaffold({ | ||||
|     this.appBar, | ||||
|     this.bodyBuilderCompleter, | ||||
|     this.bodyBuilderWaiter, | ||||
|     this.body, | ||||
|     this.waitingWidget, | ||||
|     this.forceInternetCheck = false, | ||||
|  | @ -19,10 +18,10 @@ class McgScaffold extends StatelessWidget { | |||
|   final AppBar? appBar; | ||||
| 
 | ||||
|   /// Awaits an external signal (complete) before building the body. | ||||
|   final Completer? bodyBuilderCompleter; | ||||
|   final ValueListenable<bool>? bodyBuilderWaiter; | ||||
|   final Widget? body; | ||||
| 
 | ||||
|   /// Custom widget to be used while awaiting [bodyBuilderCompleter]. | ||||
|   /// Custom widget to be used while awaiting [bodyBuilderWaiter]. | ||||
|   /// | ||||
|   /// Defaults to using [PlatformCircularProgressIndicator]. | ||||
|   final Widget? waitingWidget; | ||||
|  | @ -32,19 +31,12 @@ class McgScaffold extends StatelessWidget { | |||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final Widget loginOptionsBody = bodyBuilderCompleter != null | ||||
|         ? FutureBuilder( | ||||
|             future: bodyBuilderCompleter!.future, | ||||
|             builder: (context, snapshot) { | ||||
|               switch (snapshot.connectionState) { | ||||
|                 case ConnectionState.none: | ||||
|                 case ConnectionState.waiting: | ||||
|                 case ConnectionState.active: | ||||
|                   return Center(child: waitingWidget ?? const CircularProgressIndicator()); | ||||
|                 case ConnectionState.done: | ||||
|                   return body ?? const SizedBox.shrink(); | ||||
|               } | ||||
|             }, | ||||
|     final Widget loginOptionsBody = bodyBuilderWaiter != null | ||||
|         ? ValueListenableBuilder<bool>( | ||||
|             valueListenable: bodyBuilderWaiter!, | ||||
|             builder: (context, final isReady, child) => !isReady | ||||
|                 ? Center(child: waitingWidget ?? const CircularProgressIndicator()) | ||||
|                 : body ?? const SizedBox.shrink(), | ||||
|           ) | ||||
|         : body ?? const SizedBox.shrink(); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										47
									
								
								lib/features/core/widgets/view_model_builder.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								lib/features/core/widgets/view_model_builder.dart
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| import 'package:flutter/widgets.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| 
 | ||||
| import '../abstracts/base_view_model.dart'; | ||||
| 
 | ||||
| class ViewModelBuilder<T extends BaseViewModel> extends StatefulWidget { | ||||
|   const ViewModelBuilder({ | ||||
|     required Widget Function(BuildContext context, T model) builder, | ||||
|     required T Function() viewModelBuilder, | ||||
|     dynamic Function()? argumentBuilder, | ||||
|     super.key, | ||||
|   })  : _builder = builder, | ||||
|         _viewModelBuilder = viewModelBuilder, | ||||
|         _argumentBuilder = argumentBuilder; | ||||
| 
 | ||||
|   final Widget Function(BuildContext context, T model) _builder; | ||||
|   final T Function() _viewModelBuilder; | ||||
|   final dynamic Function()? _argumentBuilder; | ||||
| 
 | ||||
|   @override | ||||
|   _ViewModelBuilderState<T> createState() => _ViewModelBuilderState<T>(); | ||||
| } | ||||
| 
 | ||||
| class _ViewModelBuilderState<T extends BaseViewModel> extends State<ViewModelBuilder<T>> { | ||||
|   late final T _viewModel; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     _viewModel = widget._viewModelBuilder(); | ||||
|     _viewModel.initialise(() => mounted, widget._argumentBuilder?.call()); | ||||
|     super.initState(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     _viewModel.dispose(); | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext _) => ChangeNotifierProvider.value( | ||||
|         value: _viewModel, | ||||
|         child: Consumer<T>( | ||||
|           builder: (context, model, _) => widget._builder(context, model), | ||||
|         ), | ||||
|       ); | ||||
| } | ||||
|  | @ -18,8 +18,8 @@ class UnsplashImagesApi implements ImagesApi { | |||
| 
 | ||||
|       final imageUri = _imageUrlGenerator(imageSide: imageSide); | ||||
| 
 | ||||
|       return ImageModel<int>( | ||||
|         comparableIndex: imageIndex, | ||||
|       return ImageModel( | ||||
|         imageIndex: imageIndex, | ||||
|         uri: imageUri, | ||||
|         imageName: Strings.current.image, | ||||
|       ); | ||||
|  | @ -29,6 +29,6 @@ class UnsplashImagesApi implements ImagesApi { | |||
|   Uri _imageUrlGenerator({required int imageSide}) => Uri( | ||||
|         scheme: ConstValues.httpsScheme, | ||||
|         host: ConstValues.backendHost, | ||||
|         pathSegments: ConstValues.backendUrlPathSegments..add('${imageSide}x$imageSide'), | ||||
|         pathSegments: [...ConstValues.backendUrlPathSegments, '${imageSide}x$imageSide'], | ||||
|       ); | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| class ImageModel<T extends Comparable> { | ||||
| class ImageModel { | ||||
|   const ImageModel({ | ||||
|     required this.uri, | ||||
|     required this.comparableIndex, | ||||
|     required this.imageIndex, | ||||
|     required this.imageName, | ||||
|   }); | ||||
| 
 | ||||
|  | @ -11,7 +11,7 @@ class ImageModel<T extends Comparable> { | |||
|   final Uri uri; | ||||
| 
 | ||||
|   /// A unique identifier that can be used for indexing the image. | ||||
|   final T comparableIndex; | ||||
|   final int imageIndex; | ||||
| 
 | ||||
|   /// Given name of the image. | ||||
|   final String imageName; | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ class ImagesService { | |||
|   final ImagesApi _imagesApi; | ||||
| 
 | ||||
|   late final Iterable<ImageModel> _imageModels; | ||||
|   Iterable<ImageModel> get imageModels => _imageModels; | ||||
| 
 | ||||
|   Future<void> _init() async { | ||||
|     _imageModels = await _imagesApi.fetchImageUri(token: ''); | ||||
|  |  | |||
|  | @ -1,10 +1,71 @@ | |||
| import 'package:flutter/widgets.dart'; | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| 
 | ||||
| import '/features/core/data/constants/const_colors.dart'; | ||||
| import '/features/core/data/constants/const_durations.dart'; | ||||
| import '/features/core/widgets/mcg_scaffold.dart'; | ||||
| import '/features/core/widgets/view_model_builder.dart'; | ||||
| import 'gallery_view_model.dart'; | ||||
| 
 | ||||
| class GalleryView extends StatelessWidget { | ||||
|   const GalleryView({super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return const Placeholder(); | ||||
|     return ViewModelBuilder<GalleryViewModel>( | ||||
|       viewModelBuilder: () => GalleryViewModel.locate, | ||||
|       builder: (context, final model) => McgScaffold( | ||||
|         bodyBuilderWaiter: model.isInitialised, | ||||
|         appBar: AppBar( | ||||
|           title: Text(model.strings.gallery), | ||||
|         ), | ||||
|         body: Center( | ||||
|           child: ValueListenableBuilder<bool>( | ||||
|             valueListenable: model.isDisplayingPressingPrompt, | ||||
|             builder: (context, final isDisplayingPressingPrompt, _) => AnimatedSwitcher( | ||||
|               duration: ConstDurations.defaultAnimationDuration, | ||||
|               child: isDisplayingPressingPrompt | ||||
|                   ? ElevatedButton( | ||||
|                       onPressed: model.onPromptPressed, | ||||
|                       child: Text(model.strings.startLoadingPrompt), | ||||
|                     ) | ||||
|                   : DecoratedBox( | ||||
|                       decoration: const BoxDecoration(color: ConstColours.galleryBackgroundColour), | ||||
|                       child: SingleChildScrollView( | ||||
|                         // Using Wrap instead of GridView, to make use of different image sizes | ||||
|                         child: Wrap( | ||||
|                           runSpacing: 24, | ||||
|                           spacing: 8, | ||||
|                           alignment: WrapAlignment.center, | ||||
|                           runAlignment: WrapAlignment.center, | ||||
|                           crossAxisAlignment: WrapCrossAlignment.center, | ||||
|                           children: [ | ||||
|                             for (final imageModel in model.imageModels) | ||||
|                               GestureDetector( | ||||
|                                 onTap: () => model.pushImageCarouselView( | ||||
|                                   context, | ||||
|                                   imageModel: imageModel, | ||||
|                                 ), | ||||
|                                 child: Hero( | ||||
|                                   tag: imageModel.imageIndex.toString(), | ||||
|                                   child: CachedNetworkImage( | ||||
|                                     imageUrl: imageModel.uri.toString(), | ||||
|                                     cacheKey: imageModel.imageIndex.toString(), | ||||
|                                     progressIndicatorBuilder: (_, __, final progress) => | ||||
|                                         CircularProgressIndicator( | ||||
|                                       value: model.downloadProgressValue(progress: progress), | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                               ), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,75 @@ | |||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter_cache_manager/flutter_cache_manager.dart'; | ||||
| import 'package:mc_gallery/features/core/services/app_lifecycle_service.dart'; | ||||
| 
 | ||||
| import '/features/core/abstracts/base_view_model.dart'; | ||||
| import '/features/core/services/logging_service.dart'; | ||||
| import '/features/core/services/navigation_service.dart'; | ||||
| import '/features/home/data/models/image_model.dart'; | ||||
| import '/features/home/services/images_service.dart'; | ||||
| import '/features/home/views/image_carousel/image_carousel_view.dart'; | ||||
| import '/locator.dart'; | ||||
| 
 | ||||
| class GalleryViewModel extends BaseViewModel { | ||||
|   GalleryViewModel({ | ||||
|     required ImagesService imagesService, | ||||
|     required NavigationService navigationService, | ||||
|     required AppLifecycleService appLifecycleService, | ||||
|     required LoggingService loggingService, | ||||
|   })  : _imagesService = imagesService, | ||||
|         _navigationService = navigationService, | ||||
|         _appLifecycleService = appLifecycleService, | ||||
|         _loggingService = loggingService; | ||||
| 
 | ||||
|   final ImagesService _imagesService; | ||||
|   final NavigationService _navigationService; | ||||
|   final AppLifecycleService _appLifecycleService; | ||||
|   final LoggingService _loggingService; | ||||
| 
 | ||||
|   final ValueNotifier<bool> _isDisplayingPressingPrompt = ValueNotifier(true); | ||||
|   ValueListenable<bool> get isDisplayingPressingPrompt => _isDisplayingPressingPrompt; | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> initialise(bool Function() mounted, [arguments]) async { | ||||
|     _appLifecycleService.addListener( | ||||
|       tag: runtimeType.toString(), | ||||
|       listener: (final appLifecycleState) async { | ||||
|         switch (appLifecycleState) { | ||||
|           case AppLifecycleState.resumed: | ||||
|             break; | ||||
|           case AppLifecycleState.inactive: | ||||
|           case AppLifecycleState.paused: | ||||
|           case AppLifecycleState.detached: | ||||
|             await DefaultCacheManager().emptyCache(); | ||||
|         } | ||||
|       }, | ||||
|     ); | ||||
| 
 | ||||
|     super.initialise(mounted, arguments); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> dispose() async { | ||||
|     await _appLifecycleService.removeListener(tag: runtimeType.toString()); | ||||
| 
 | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   void onPromptPressed() => _isDisplayingPressingPrompt.value = false; | ||||
| 
 | ||||
|   Iterable<ImageModel> get imageModels => _imagesService.imageModels; | ||||
| 
 | ||||
|   void pushImageCarouselView(BuildContext context, {required ImageModel imageModel}) => | ||||
|       _navigationService.pushImageCarouselView( | ||||
|         context, | ||||
|         imageCarouselViewArguments: ImageCarouselViewArguments( | ||||
|           imageIndexKey: imageModel.imageIndex, | ||||
|         ), | ||||
|       ); | ||||
| 
 | ||||
|   static GalleryViewModel get locate => Locator.locate(); | ||||
| 
 | ||||
|   double? downloadProgressValue({required DownloadProgress progress}) => | ||||
|       progress.totalSize != null ? progress.downloaded / progress.totalSize! : null; | ||||
| } | ||||
|  | @ -0,0 +1,43 @@ | |||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| 
 | ||||
| import '/features/core/widgets/mcg_scaffold.dart'; | ||||
| import '/features/core/widgets/view_model_builder.dart'; | ||||
| import '/features/home/views/image_carousel/image_carousel_view_model.dart'; | ||||
| 
 | ||||
| class ImageCarouselViewArguments { | ||||
|   const ImageCarouselViewArguments({required this.imageIndexKey}); | ||||
|   final int imageIndexKey; | ||||
| } | ||||
| 
 | ||||
| class ImageCarouselView extends StatelessWidget { | ||||
|   const ImageCarouselView({ | ||||
|     required this.imageCarouselViewArguments, | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   final ImageCarouselViewArguments imageCarouselViewArguments; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return ViewModelBuilder<ImageCarouselViewModel>( | ||||
|       viewModelBuilder: () => ImageCarouselViewModel.locate, | ||||
|       argumentBuilder: () => imageCarouselViewArguments, | ||||
|       builder: (context, final model) => McgScaffold( | ||||
|         appBar: AppBar( | ||||
|           title: Text(model.strings.imageCarousel), | ||||
|         ), | ||||
|         body: Hero( | ||||
|           tag: model.currentImageKey, | ||||
|           child: CachedNetworkImage( | ||||
|             imageUrl: model.currentImageUrl, | ||||
|             cacheKey: model.currentImageKey, | ||||
|             progressIndicatorBuilder: (_, __, final progress) => CircularProgressIndicator( | ||||
|               value: model.downloadProgressValue(progress: progress), | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,68 @@ | |||
| import 'dart:ui'; | ||||
| 
 | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter_cache_manager/flutter_cache_manager.dart'; | ||||
| import 'package:mc_gallery/features/core/services/app_lifecycle_service.dart'; | ||||
| 
 | ||||
| import '/features/core/abstracts/base_view_model.dart'; | ||||
| import '/features/core/services/logging_service.dart'; | ||||
| import '/features/core/services/navigation_service.dart'; | ||||
| import '/features/home/data/models/image_model.dart'; | ||||
| import '/features/home/services/images_service.dart'; | ||||
| import '/features/home/views/image_carousel/image_carousel_view.dart'; | ||||
| import '/locator.dart'; | ||||
| 
 | ||||
| class ImageCarouselViewModel extends BaseViewModel { | ||||
|   ImageCarouselViewModel({ | ||||
|     required ImagesService imagesService, | ||||
|     required NavigationService navigationService, | ||||
|     required AppLifecycleService appLifecycleService, | ||||
|     required LoggingService loggingService, | ||||
|   })  : _imagesService = imagesService, | ||||
|         _navigationService = navigationService, | ||||
|         _appLifecycleService = appLifecycleService, | ||||
|         _loggingService = loggingService; | ||||
| 
 | ||||
|   final ImagesService _imagesService; | ||||
|   final NavigationService _navigationService; | ||||
|   final AppLifecycleService _appLifecycleService; | ||||
|   final LoggingService _loggingService; | ||||
| 
 | ||||
|   late final ValueNotifier<ImageModel> _currentImageModel; | ||||
|   ValueListenable<ImageModel> get currentImageModel => _currentImageModel; | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> initialise(bool Function() mounted, [arguments]) async { | ||||
|     _appLifecycleService.addListener( | ||||
|         tag: runtimeType.toString(), | ||||
|         listener: (final appLifecycleState) async { | ||||
|           switch (appLifecycleState) { | ||||
|             case AppLifecycleState.resumed: | ||||
|               break; | ||||
|             case AppLifecycleState.inactive: | ||||
|             case AppLifecycleState.paused: | ||||
|             case AppLifecycleState.detached: | ||||
|               await DefaultCacheManager().emptyCache(); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|     _currentImageModel = ValueNotifier(_imagesService.imageModels | ||||
|         .elementAt((arguments! as ImageCarouselViewArguments).imageIndexKey)); | ||||
| 
 | ||||
|     super.initialise(mounted, arguments); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> dispose() async { | ||||
|     await _appLifecycleService.removeListener(tag: runtimeType.toString()); | ||||
| 
 | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   String get currentImageUrl => currentImageModel.value.uri.toString(); | ||||
|   String get currentImageKey => currentImageModel.value.imageIndex.toString(); | ||||
|   double? downloadProgressValue({required DownloadProgress progress}) => | ||||
|       progress.totalSize != null ? progress.downloaded / progress.totalSize! : null; | ||||
| 
 | ||||
|   static ImageCarouselViewModel get locate => Locator.locate(); | ||||
| } | ||||
|  | @ -22,8 +22,12 @@ class MessageLookup extends MessageLookupByLibrary { | |||
| 
 | ||||
|   final messages = _notInlinedMessages(_notInlinedMessages); | ||||
|   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{ | ||||
|         "gallery": MessageLookupByLibrary.simpleMessage("Gallery"), | ||||
|         "image": MessageLookupByLibrary.simpleMessage("Image"), | ||||
|         "imageCarousel": MessageLookupByLibrary.simpleMessage("Image carousel"), | ||||
|         "somethingWentWrong": | ||||
|             MessageLookupByLibrary.simpleMessage("Something went wrong") | ||||
|             MessageLookupByLibrary.simpleMessage("Something went wrong"), | ||||
|         "startLoadingPrompt": | ||||
|             MessageLookupByLibrary.simpleMessage("Press me to start loading") | ||||
|       }; | ||||
| } | ||||
|  |  | |||
|  | @ -69,6 +69,36 @@ class Strings { | |||
|       args: [], | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /// `Gallery` | ||||
|   String get gallery { | ||||
|     return Intl.message( | ||||
|       'Gallery', | ||||
|       name: 'gallery', | ||||
|       desc: '', | ||||
|       args: [], | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /// `Press me to start loading` | ||||
|   String get startLoadingPrompt { | ||||
|     return Intl.message( | ||||
|       'Press me to start loading', | ||||
|       name: 'startLoadingPrompt', | ||||
|       desc: '', | ||||
|       args: [], | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /// `Image carousel` | ||||
|   String get imageCarousel { | ||||
|     return Intl.message( | ||||
|       'Image carousel', | ||||
|       name: 'imageCarousel', | ||||
|       desc: '', | ||||
|       args: [], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class AppLocalizationDelegate extends LocalizationsDelegate<Strings> { | ||||
|  |  | |||
|  | @ -2,5 +2,10 @@ | |||
|   "@@locale": "en", | ||||
|   "somethingWentWrong": "Something went wrong", | ||||
| 
 | ||||
|   "image": "Image" | ||||
|   "image": "Image", | ||||
| 
 | ||||
|   "gallery": "Gallery", | ||||
|   "startLoadingPrompt": "Press me to start loading", | ||||
| 
 | ||||
|   "imageCarousel": "Image carousel" | ||||
| } | ||||
|  | @ -1,3 +1,5 @@ | |||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:connectivity_plus/connectivity_plus.dart'; | ||||
| import 'package:get_it/get_it.dart'; | ||||
| import 'package:internet_connection_checker/internet_connection_checker.dart'; | ||||
|  | @ -6,7 +8,10 @@ import 'package:mc_gallery/features/core/services/logging_service.dart'; | |||
| import 'package:mc_gallery/features/core/services/navigation_service.dart'; | ||||
| import 'package:mc_gallery/features/home/api/unsplash_images_api.dart'; | ||||
| import 'package:mc_gallery/features/home/services/images_service.dart'; | ||||
| import 'package:mc_gallery/features/home/views/gallery/gallery_view_model.dart'; | ||||
| import 'package:mc_gallery/features/home/views/image_carousel/image_carousel_view_model.dart'; | ||||
| 
 | ||||
| import 'features/core/services/app_lifecycle_service.dart'; | ||||
| import 'features/core/services/connection_service.dart'; | ||||
| import 'features/core/services/overlay_service.dart'; | ||||
| 
 | ||||
|  | @ -23,8 +28,8 @@ class Locator { | |||
|     _registerViewModels(); | ||||
| 
 | ||||
|     await _registerServices(locator); | ||||
| 
 | ||||
|     await _registerRepos(locator); | ||||
| 
 | ||||
|     _registerSingletons(); | ||||
|   } | ||||
| 
 | ||||
|  | @ -34,9 +39,26 @@ class Locator { | |||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   static void _registerViewModels() {} | ||||
|   static void _registerViewModels() { | ||||
|     instance().registerFactory( | ||||
|       () => GalleryViewModel( | ||||
|         imagesService: ImagesService.locate, | ||||
|         navigationService: NavigationService.locate, | ||||
|         appLifecycleService: AppLifecycleService.locate, | ||||
|         loggingService: LoggingService.locate, | ||||
|       ), | ||||
|     ); | ||||
|     instance().registerFactory( | ||||
|       () => ImageCarouselViewModel( | ||||
|         imagesService: ImagesService.locate, | ||||
|         navigationService: NavigationService.locate, | ||||
|         appLifecycleService: AppLifecycleService.locate, | ||||
|         loggingService: LoggingService.locate, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   static _registerServices(GetIt it) { | ||||
|   static FutureOr<void> _registerServices(GetIt it) { | ||||
|     it.registerLazySingleton( | ||||
|       () => NavigationService( | ||||
|         mcgRouter: McgRouter.locate, | ||||
|  | @ -57,6 +79,12 @@ class Locator { | |||
|       ), | ||||
|       dispose: (param) => param.dispose(), | ||||
|     ); | ||||
|     instance().registerSingleton<AppLifecycleService>( | ||||
|       AppLifecycleService( | ||||
|         loggingService: LoggingService.locate, | ||||
|       ), | ||||
|       dispose: (param) async => await param.dispose(), | ||||
|     ); | ||||
|     it.registerSingleton( | ||||
|       ImagesService( | ||||
|         imagesApi: UnsplashImagesApi(), | ||||
|  | @ -65,7 +93,7 @@ class Locator { | |||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   static _registerRepos(GetIt locator) {} | ||||
|   static FutureOr<void> _registerRepos(GetIt locator) {} | ||||
| 
 | ||||
|   static void _registerSingletons() {} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										175
									
								
								pubspec.lock
									
										
									
									
									
								
							
							
						
						
									
										175
									
								
								pubspec.lock
									
										
									
									
									
								
							|  | @ -50,6 +50,27 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.0" | ||||
|   cached_network_image: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: cached_network_image | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "3.2.3" | ||||
|   cached_network_image_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: cached_network_image_platform_interface | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.0" | ||||
|   cached_network_image_web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: cached_network_image_web | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.2" | ||||
|   characters: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | @ -153,6 +174,20 @@ packages: | |||
|     description: flutter | ||||
|     source: sdk | ||||
|     version: "0.0.0" | ||||
|   flutter_blurhash: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_blurhash | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.7.0" | ||||
|   flutter_cache_manager: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: flutter_cache_manager | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "3.3.0" | ||||
|   flutter_lints: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
|  | @ -275,6 +310,13 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.8.0" | ||||
|   nested: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: nested | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.0" | ||||
|   nm: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | @ -282,6 +324,13 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.5.0" | ||||
|   octo_image: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: octo_image | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.2" | ||||
|   package_config: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | @ -310,6 +359,62 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.1" | ||||
|   path_provider: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: path_provider | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.11" | ||||
|   path_provider_android: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: path_provider_android | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.22" | ||||
|   path_provider_ios: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: path_provider_ios | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.11" | ||||
|   path_provider_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: path_provider_linux | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.7" | ||||
|   path_provider_macos: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: path_provider_macos | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.6" | ||||
|   path_provider_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: path_provider_platform_interface | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.5" | ||||
|   path_provider_windows: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: path_provider_windows | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.3" | ||||
|   pedantic: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: pedantic | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.11.1" | ||||
|   petitparser: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | @ -317,6 +422,13 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "5.1.0" | ||||
|   platform: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: platform | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "3.1.0" | ||||
|   plugin_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | @ -331,6 +443,20 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "3.6.2" | ||||
|   process: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: process | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "4.2.4" | ||||
|   provider: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: provider | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "6.0.5" | ||||
|   pub_semver: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | @ -338,6 +464,13 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.3" | ||||
|   rxdart: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: rxdart | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.27.7" | ||||
|   sky_engine: | ||||
|     dependency: transitive | ||||
|     description: flutter | ||||
|  | @ -350,6 +483,20 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.9.0" | ||||
|   sqflite: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: sqflite | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.2.2" | ||||
|   sqflite_common: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: sqflite_common | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.4.0+2" | ||||
|   stack_trace: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | @ -371,6 +518,13 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.1.1" | ||||
|   synchronized: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: synchronized | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "3.0.0+3" | ||||
|   talker: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|  | @ -413,6 +567,13 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.3.1" | ||||
|   uuid: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: uuid | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "3.0.7" | ||||
|   vector_math: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | @ -427,6 +588,20 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.2" | ||||
|   win32: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: win32 | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "3.1.3" | ||||
|   xdg_directories: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: xdg_directories | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.2.0+2" | ||||
|   xml: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  |  | |||
|  | @ -16,11 +16,14 @@ dependencies: | |||
|   # Routing | ||||
|   go_router: ^6.0.0 | ||||
| 
 | ||||
|   # Service locator | ||||
|   # State management | ||||
|   get_it: ^7.2.0 | ||||
|   provider: ^6.0.5 | ||||
| 
 | ||||
|   # Networking | ||||
|   dio: ^4.0.6 | ||||
|   cached_network_image: ^3.2.3 | ||||
|   flutter_cache_manager: ^3.3.0 | ||||
| 
 | ||||
|   # Util backend | ||||
|   intl_utils: ^2.8.1 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue