diff --git a/.gitignore b/.gitignore index 2448344..ab15e32 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Custom *.pdf +*.env +env.g.dart # Miscellaneous *.class diff --git a/lib/env/env.dart b/lib/env/env.dart new file mode 100644 index 0000000..e593f22 --- /dev/null +++ b/lib/env/env.dart @@ -0,0 +1,12 @@ +import 'package:envied/envied.dart'; + +part 'env.g.dart'; + +@Envied() +abstract class Env { + @EnviedField( + varName: 'UNSPLASH_API_KEY', + defaultValue: '', + ) + static const unsplashApiKey = _Env.unsplashApiKey; +} diff --git a/lib/features/core/abstracts/base_view_model.dart b/lib/features/core/abstracts/base_view_model.dart index 9539637..7c57017 100644 --- a/lib/features/core/abstracts/base_view_model.dart +++ b/lib/features/core/abstracts/base_view_model.dart @@ -17,7 +17,7 @@ abstract class BaseViewModel extends ChangeNotifier { final ValueNotifier _state = ValueNotifier(ViewModelState.isInitialising); ValueListenable get state => _state; - final LoggingService _loggingService = LoggingService.locate; + final LoggingService log = LoggingService.locate; String? _errorMessage; String get errorMessage => _errorMessage ?? strings.somethingWentWrong; @@ -27,7 +27,7 @@ abstract class BaseViewModel extends ChangeNotifier { _mounted = mounted; _isInitialised.value = true; _state.value = ViewModelState.isInitialised; - _loggingService.successfulInit(location: runtimeType.toString()); + log.successfulInit(location: runtimeType.toString()); } void setBusy(bool isBusy) { @@ -55,7 +55,7 @@ abstract class BaseViewModel extends ChangeNotifier { @override void dispose() { super.dispose(); - _loggingService.successfulDispose(location: runtimeType.toString()); + log.successfulDispose(location: runtimeType.toString()); } late final bool Function() _mounted; diff --git a/lib/features/home/abstracts/images_api.dart b/lib/features/home/abstracts/images_api.dart index 5138887..17836ce 100644 --- a/lib/features/home/abstracts/images_api.dart +++ b/lib/features/home/abstracts/images_api.dart @@ -7,11 +7,15 @@ import '../data/dtos/image_model_dto.dart'; /// Since I used a site that was more obscure than the ones in the examples, this (otherwise pointless /// and convoluting) interface is for adding a bit of flexibility to change strategy to some other site. abstract class ImagesApi { + ImagesApi({required String token}) : _token = token; + + /// Access token provided to be used with API calls + final String _token; + /// Returns images fetched through an API as [ImageModelDTO]s. - FutureOr> fetchImageUri({required String token}); + FutureOr> fetchImageUri(); FutureOr> searchImages({ required String searchStr, - required String token, }); } diff --git a/lib/features/home/api/unsplash_images_api.dart b/lib/features/home/api/unsplash_images_api.dart index 67c29ea..fa9f696 100644 --- a/lib/features/home/api/unsplash_images_api.dart +++ b/lib/features/home/api/unsplash_images_api.dart @@ -9,12 +9,14 @@ import '/locator.dart'; import '../abstracts/images_api.dart'; import '../data/dtos/image_model_dto.dart'; -class UnsplashImagesApi implements ImagesApi { +class UnsplashImagesApi extends ImagesApi { final LoggingService _loggingService = LoggingService.locate; final random = Random(); + UnsplashImagesApi({required super.token}); + @override - FutureOr> fetchImageUri({required String token}) async { + FutureOr> fetchImageUri() async { // Dummy fetching delay emulation await Future.delayed(const Duration( milliseconds: ConstValues.defaultEmulatedLatencyMillis * ConstValues.numberOfImages)); @@ -54,7 +56,6 @@ class UnsplashImagesApi implements ImagesApi { @override FutureOr> searchImages({ required String searchStr, - required String token, }) async { final numberOfResults = random.nextIntInRange(min: 0, max: ConstValues.numberOfImages); diff --git a/lib/features/home/services/images_service.dart b/lib/features/home/services/images_service.dart index dd87e38..ce6f532 100644 --- a/lib/features/home/services/images_service.dart +++ b/lib/features/home/services/images_service.dart @@ -49,7 +49,7 @@ class ImagesService { Future _init() async { _loggingService.info('Fetching and creating image models...'); - final fetchedImageModelDtos = await _imagesApi.fetchImageUri(token: ''); + final fetchedImageModelDtos = await _imagesApi.fetchImageUri(); final favouritesStatuses = _localStorageService.storedFavouritesStates; // Prefill from stored values @@ -128,7 +128,6 @@ class ImagesService { case SearchOption.web: return (await _imagesApi.searchImages( searchStr: imageNamePart, - token: '', )) .map( (final imageModelDto) => ImageModel.fromDto( diff --git a/lib/features/home/views/gallery/gallery_view_model.dart b/lib/features/home/views/gallery/gallery_view_model.dart index dac601b..20304f6 100644 --- a/lib/features/home/views/gallery/gallery_view_model.dart +++ b/lib/features/home/views/gallery/gallery_view_model.dart @@ -6,7 +6,6 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import '/features/core/abstracts/base_view_model.dart'; import '/features/core/data/extensions/value_notifier_extensions.dart'; -import '/features/core/services/logging_service.dart'; import '/features/core/services/navigation_service.dart'; import '/locator.dart'; import '../../data/enums/search_option.dart'; @@ -20,17 +19,14 @@ class GalleryViewModel extends BaseViewModel { required ImagesService imagesService, required NavigationService navigationService, required ImageCacheManagerService imageCacheManagerService, - required LoggingService loggingService, }) : _imagesService = imagesService, _navigationService = navigationService, - _imageCacheManagerService = imageCacheManagerService, - _loggingService = loggingService; + _imageCacheManagerService = imageCacheManagerService; final ImagesService _imagesService; final NavigationService _navigationService; //todo(mehul): Use to implement pull-to-refresh or an extra widget final ImageCacheManagerService _imageCacheManagerService; - final LoggingService _loggingService; final ValueNotifier _isDisplayingPressingPrompt = ValueNotifier(true); ValueListenable get isDisplayingPressingPrompt => _isDisplayingPressingPrompt; @@ -59,7 +55,7 @@ class GalleryViewModel extends BaseViewModel { // If empty-string (from backspacing) -> reset state. if (searchTerm.isEmpty) { _imageSearchResultsNotifier.value = []; - _loggingService.info('Clearing results on search string removal'); + log.info('Clearing results on search string removal'); return; } @@ -82,7 +78,7 @@ class GalleryViewModel extends BaseViewModel { // If transitioning from 'Searching', clear previous results immediately if (_isSearchingNotifier.value) { _imageSearchResultsNotifier.value = []; - _loggingService.info('Clearing of results on view mode change'); + log.info('Clearing of results on view mode change'); } _isSearchingNotifier.flipValue(); @@ -92,10 +88,10 @@ class GalleryViewModel extends BaseViewModel { void onSearchOptionChanged(SearchOption? option) { _searchOptionNotifier.value = option!; - _loggingService.info('Switched over to $option search'); + log.info('Switched over to $option search'); _imageSearchResultsNotifier.value = []; - _loggingService.info('Cleared resultsw from view'); + log.info('Cleared resultsw from view'); //todo(mehul): Either redo search or force user to type in new (trigger) by clearing field } diff --git a/lib/features/home/views/image_carousel/image_carousel_view_model.dart b/lib/features/home/views/image_carousel/image_carousel_view_model.dart index 2a9eb70..85f4f1b 100644 --- a/lib/features/home/views/image_carousel/image_carousel_view_model.dart +++ b/lib/features/home/views/image_carousel/image_carousel_view_model.dart @@ -1,22 +1,18 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:mc_gallery/features/home/views/image_carousel/image_carousel_view.dart'; import '/features/core/abstracts/base_view_model.dart'; -import '/features/core/services/logging_service.dart'; import '/features/home/services/images_service.dart'; -import '/features/home/views/image_carousel/image_carousel_view.dart'; import '/locator.dart'; import '../../data/models/image_model.dart'; class ImageCarouselViewModel extends BaseViewModel { ImageCarouselViewModel({ required ImagesService imagesService, - required LoggingService loggingService, - }) : _imagesService = imagesService, - _loggingService = loggingService; + }) : _imagesService = imagesService; final ImagesService _imagesService; - final LoggingService _loggingService; late final ValueNotifier _currentImageModelNotifier; ValueListenable get currentImageModelListenable => _currentImageModelNotifier; @@ -24,8 +20,8 @@ class ImageCarouselViewModel extends BaseViewModel { @override Future initialise(bool Function() mounted, [arguments]) async { _currentImageModelNotifier = ValueNotifier(_imagesService.imageModels - .elementAt((arguments! as ImageCarouselViewArguments).imageIndexKey)); - _loggingService.info('Initialized with image: ${_currentImageModelNotifier.value.imageIndex}'); + .elementAt((arguments as ImageCarouselViewArguments).imageIndexKey)); + log.info('Initialized with image: ${_currentImageModelNotifier.value.imageIndex}'); super.initialise(mounted, arguments); } @@ -37,7 +33,7 @@ class ImageCarouselViewModel extends BaseViewModel { void swipedTo({required int newIndex}) { _currentImageModelNotifier.value = _imagesService.imageModelAt(index: newIndex); - _loggingService.info('Swiped to image: ${_currentImageModelNotifier.value.imageIndex}'); + log.info('Swiped to image: ${_currentImageModelNotifier.value.imageIndex}'); } String get currentImageUrl => currentImageModelListenable.value.uri.toString(); diff --git a/lib/locator.dart b/lib/locator.dart index 64d2c70..6547d25 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -3,6 +3,7 @@ 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'; +import 'package:mc_gallery/env/env.dart'; import 'features/core/abstracts/router/app_router.dart'; import 'features/core/services/app_lifecycle_service.dart'; @@ -37,7 +38,7 @@ class Locator { static void _registerAPIs() { instance().registerFactory( - () => UnsplashImagesApi(), + () => UnsplashImagesApi(token: Env.unsplashApiKey), ); } @@ -47,13 +48,11 @@ class Locator { imagesService: ImagesService.locate, navigationService: NavigationService.locate, imageCacheManagerService: ImageCacheManagerService.locate, - loggingService: LoggingService.locate, ), ); instance().registerFactory( () => ImageCarouselViewModel( imagesService: ImagesService.locate, - loggingService: LoggingService.locate, ), ); } diff --git a/pubspec.lock b/pubspec.lock index 41b1854..b9708a4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -232,6 +232,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.6" + envied: + dependency: "direct main" + description: + name: envied + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + envied_generator: + dependency: "direct dev" + description: + name: envied_generator + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 123390b..9e9ae6b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,9 @@ dependencies: hive: ^2.2.3 hive_flutter: ^1.1.0 + # Environment + envied: ^0.3.0 + # Util backend intl_utils: ^2.8.1 connectivity_plus: ^3.0.2 @@ -58,6 +61,7 @@ dev_dependencies: build_runner: ^2.3.3 json_serializable: ^6.5.4 hive_generator: ^2.0.0 + envied_generator: ^0.3.0 # Annotations json_annotation: ^4.7.0