security update
This commit is contained in:
parent
6946b2e802
commit
ccc5eccda8
11 changed files with 58 additions and 31 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,5 +1,7 @@
|
||||||
# Custom
|
# Custom
|
||||||
*.pdf
|
*.pdf
|
||||||
|
*.env
|
||||||
|
env.g.dart
|
||||||
|
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
*.class
|
*.class
|
||||||
|
|
12
lib/env/env.dart
vendored
Normal file
12
lib/env/env.dart
vendored
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ abstract class BaseViewModel<T extends Object?> extends ChangeNotifier {
|
||||||
final ValueNotifier<ViewModelState> _state = ValueNotifier(ViewModelState.isInitialising);
|
final ValueNotifier<ViewModelState> _state = ValueNotifier(ViewModelState.isInitialising);
|
||||||
ValueListenable<ViewModelState> get state => _state;
|
ValueListenable<ViewModelState> get state => _state;
|
||||||
|
|
||||||
final LoggingService _loggingService = LoggingService.locate;
|
final LoggingService log = LoggingService.locate;
|
||||||
|
|
||||||
String? _errorMessage;
|
String? _errorMessage;
|
||||||
String get errorMessage => _errorMessage ?? strings.somethingWentWrong;
|
String get errorMessage => _errorMessage ?? strings.somethingWentWrong;
|
||||||
|
@ -27,7 +27,7 @@ abstract class BaseViewModel<T extends Object?> extends ChangeNotifier {
|
||||||
_mounted = mounted;
|
_mounted = mounted;
|
||||||
_isInitialised.value = true;
|
_isInitialised.value = true;
|
||||||
_state.value = ViewModelState.isInitialised;
|
_state.value = ViewModelState.isInitialised;
|
||||||
_loggingService.successfulInit(location: runtimeType.toString());
|
log.successfulInit(location: runtimeType.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBusy(bool isBusy) {
|
void setBusy(bool isBusy) {
|
||||||
|
@ -55,7 +55,7 @@ abstract class BaseViewModel<T extends Object?> extends ChangeNotifier {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
_loggingService.successfulDispose(location: runtimeType.toString());
|
log.successfulDispose(location: runtimeType.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
late final bool Function() _mounted;
|
late final bool Function() _mounted;
|
||||||
|
|
|
@ -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
|
/// 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.
|
/// and convoluting) interface is for adding a bit of flexibility to change strategy to some other site.
|
||||||
abstract class ImagesApi {
|
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.
|
/// Returns images fetched through an API as [ImageModelDTO]s.
|
||||||
FutureOr<Iterable<ImageModelDTO>> fetchImageUri({required String token});
|
FutureOr<Iterable<ImageModelDTO>> fetchImageUri();
|
||||||
|
|
||||||
FutureOr<Iterable<ImageModelDTO>> searchImages({
|
FutureOr<Iterable<ImageModelDTO>> searchImages({
|
||||||
required String searchStr,
|
required String searchStr,
|
||||||
required String token,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,14 @@ import '/locator.dart';
|
||||||
import '../abstracts/images_api.dart';
|
import '../abstracts/images_api.dart';
|
||||||
import '../data/dtos/image_model_dto.dart';
|
import '../data/dtos/image_model_dto.dart';
|
||||||
|
|
||||||
class UnsplashImagesApi implements ImagesApi {
|
class UnsplashImagesApi extends ImagesApi {
|
||||||
final LoggingService _loggingService = LoggingService.locate;
|
final LoggingService _loggingService = LoggingService.locate;
|
||||||
final random = Random();
|
final random = Random();
|
||||||
|
|
||||||
|
UnsplashImagesApi({required super.token});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<Iterable<ImageModelDTO>> fetchImageUri({required String token}) async {
|
FutureOr<Iterable<ImageModelDTO>> fetchImageUri() async {
|
||||||
// Dummy fetching delay emulation
|
// Dummy fetching delay emulation
|
||||||
await Future.delayed(const Duration(
|
await Future.delayed(const Duration(
|
||||||
milliseconds: ConstValues.defaultEmulatedLatencyMillis * ConstValues.numberOfImages));
|
milliseconds: ConstValues.defaultEmulatedLatencyMillis * ConstValues.numberOfImages));
|
||||||
|
@ -54,7 +56,6 @@ class UnsplashImagesApi implements ImagesApi {
|
||||||
@override
|
@override
|
||||||
FutureOr<Iterable<ImageModelDTO>> searchImages({
|
FutureOr<Iterable<ImageModelDTO>> searchImages({
|
||||||
required String searchStr,
|
required String searchStr,
|
||||||
required String token,
|
|
||||||
}) async {
|
}) async {
|
||||||
final numberOfResults = random.nextIntInRange(min: 0, max: ConstValues.numberOfImages);
|
final numberOfResults = random.nextIntInRange(min: 0, max: ConstValues.numberOfImages);
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ class ImagesService {
|
||||||
Future<void> _init() async {
|
Future<void> _init() async {
|
||||||
_loggingService.info('Fetching and creating image models...');
|
_loggingService.info('Fetching and creating image models...');
|
||||||
|
|
||||||
final fetchedImageModelDtos = await _imagesApi.fetchImageUri(token: '');
|
final fetchedImageModelDtos = await _imagesApi.fetchImageUri();
|
||||||
final favouritesStatuses = _localStorageService.storedFavouritesStates;
|
final favouritesStatuses = _localStorageService.storedFavouritesStates;
|
||||||
|
|
||||||
// Prefill from stored values
|
// Prefill from stored values
|
||||||
|
@ -128,7 +128,6 @@ class ImagesService {
|
||||||
case SearchOption.web:
|
case SearchOption.web:
|
||||||
return (await _imagesApi.searchImages(
|
return (await _imagesApi.searchImages(
|
||||||
searchStr: imageNamePart,
|
searchStr: imageNamePart,
|
||||||
token: '',
|
|
||||||
))
|
))
|
||||||
.map(
|
.map(
|
||||||
(final imageModelDto) => ImageModel.fromDto(
|
(final imageModelDto) => ImageModel.fromDto(
|
||||||
|
|
|
@ -6,7 +6,6 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
|
||||||
import '/features/core/abstracts/base_view_model.dart';
|
import '/features/core/abstracts/base_view_model.dart';
|
||||||
import '/features/core/data/extensions/value_notifier_extensions.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 '/features/core/services/navigation_service.dart';
|
||||||
import '/locator.dart';
|
import '/locator.dart';
|
||||||
import '../../data/enums/search_option.dart';
|
import '../../data/enums/search_option.dart';
|
||||||
|
@ -20,17 +19,14 @@ class GalleryViewModel extends BaseViewModel {
|
||||||
required ImagesService imagesService,
|
required ImagesService imagesService,
|
||||||
required NavigationService navigationService,
|
required NavigationService navigationService,
|
||||||
required ImageCacheManagerService imageCacheManagerService,
|
required ImageCacheManagerService imageCacheManagerService,
|
||||||
required LoggingService loggingService,
|
|
||||||
}) : _imagesService = imagesService,
|
}) : _imagesService = imagesService,
|
||||||
_navigationService = navigationService,
|
_navigationService = navigationService,
|
||||||
_imageCacheManagerService = imageCacheManagerService,
|
_imageCacheManagerService = imageCacheManagerService;
|
||||||
_loggingService = loggingService;
|
|
||||||
|
|
||||||
final ImagesService _imagesService;
|
final ImagesService _imagesService;
|
||||||
final NavigationService _navigationService;
|
final NavigationService _navigationService;
|
||||||
//todo(mehul): Use to implement pull-to-refresh or an extra widget
|
//todo(mehul): Use to implement pull-to-refresh or an extra widget
|
||||||
final ImageCacheManagerService _imageCacheManagerService;
|
final ImageCacheManagerService _imageCacheManagerService;
|
||||||
final LoggingService _loggingService;
|
|
||||||
|
|
||||||
final ValueNotifier<bool> _isDisplayingPressingPrompt = ValueNotifier(true);
|
final ValueNotifier<bool> _isDisplayingPressingPrompt = ValueNotifier(true);
|
||||||
ValueListenable<bool> get isDisplayingPressingPrompt => _isDisplayingPressingPrompt;
|
ValueListenable<bool> get isDisplayingPressingPrompt => _isDisplayingPressingPrompt;
|
||||||
|
@ -59,7 +55,7 @@ class GalleryViewModel extends BaseViewModel {
|
||||||
// If empty-string (from backspacing) -> reset state.
|
// If empty-string (from backspacing) -> reset state.
|
||||||
if (searchTerm.isEmpty) {
|
if (searchTerm.isEmpty) {
|
||||||
_imageSearchResultsNotifier.value = [];
|
_imageSearchResultsNotifier.value = [];
|
||||||
_loggingService.info('Clearing results on search string removal');
|
log.info('Clearing results on search string removal');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +78,7 @@ class GalleryViewModel extends BaseViewModel {
|
||||||
// If transitioning from 'Searching', clear previous results immediately
|
// If transitioning from 'Searching', clear previous results immediately
|
||||||
if (_isSearchingNotifier.value) {
|
if (_isSearchingNotifier.value) {
|
||||||
_imageSearchResultsNotifier.value = [];
|
_imageSearchResultsNotifier.value = [];
|
||||||
_loggingService.info('Clearing of results on view mode change');
|
log.info('Clearing of results on view mode change');
|
||||||
}
|
}
|
||||||
|
|
||||||
_isSearchingNotifier.flipValue();
|
_isSearchingNotifier.flipValue();
|
||||||
|
@ -92,10 +88,10 @@ class GalleryViewModel extends BaseViewModel {
|
||||||
|
|
||||||
void onSearchOptionChanged(SearchOption? option) {
|
void onSearchOptionChanged(SearchOption? option) {
|
||||||
_searchOptionNotifier.value = option!;
|
_searchOptionNotifier.value = option!;
|
||||||
_loggingService.info('Switched over to $option search');
|
log.info('Switched over to $option search');
|
||||||
|
|
||||||
_imageSearchResultsNotifier.value = [];
|
_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
|
//todo(mehul): Either redo search or force user to type in new (trigger) by clearing field
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.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/abstracts/base_view_model.dart';
|
||||||
import '/features/core/services/logging_service.dart';
|
|
||||||
import '/features/home/services/images_service.dart';
|
import '/features/home/services/images_service.dart';
|
||||||
import '/features/home/views/image_carousel/image_carousel_view.dart';
|
|
||||||
import '/locator.dart';
|
import '/locator.dart';
|
||||||
import '../../data/models/image_model.dart';
|
import '../../data/models/image_model.dart';
|
||||||
|
|
||||||
class ImageCarouselViewModel extends BaseViewModel {
|
class ImageCarouselViewModel extends BaseViewModel {
|
||||||
ImageCarouselViewModel({
|
ImageCarouselViewModel({
|
||||||
required ImagesService imagesService,
|
required ImagesService imagesService,
|
||||||
required LoggingService loggingService,
|
}) : _imagesService = imagesService;
|
||||||
}) : _imagesService = imagesService,
|
|
||||||
_loggingService = loggingService;
|
|
||||||
|
|
||||||
final ImagesService _imagesService;
|
final ImagesService _imagesService;
|
||||||
final LoggingService _loggingService;
|
|
||||||
|
|
||||||
late final ValueNotifier<ImageModel> _currentImageModelNotifier;
|
late final ValueNotifier<ImageModel> _currentImageModelNotifier;
|
||||||
ValueListenable<ImageModel> get currentImageModelListenable => _currentImageModelNotifier;
|
ValueListenable<ImageModel> get currentImageModelListenable => _currentImageModelNotifier;
|
||||||
|
@ -24,8 +20,8 @@ class ImageCarouselViewModel extends BaseViewModel {
|
||||||
@override
|
@override
|
||||||
Future<void> initialise(bool Function() mounted, [arguments]) async {
|
Future<void> initialise(bool Function() mounted, [arguments]) async {
|
||||||
_currentImageModelNotifier = ValueNotifier(_imagesService.imageModels
|
_currentImageModelNotifier = ValueNotifier(_imagesService.imageModels
|
||||||
.elementAt((arguments! as ImageCarouselViewArguments).imageIndexKey));
|
.elementAt((arguments as ImageCarouselViewArguments).imageIndexKey));
|
||||||
_loggingService.info('Initialized with image: ${_currentImageModelNotifier.value.imageIndex}');
|
log.info('Initialized with image: ${_currentImageModelNotifier.value.imageIndex}');
|
||||||
|
|
||||||
super.initialise(mounted, arguments);
|
super.initialise(mounted, arguments);
|
||||||
}
|
}
|
||||||
|
@ -37,7 +33,7 @@ class ImageCarouselViewModel extends BaseViewModel {
|
||||||
|
|
||||||
void swipedTo({required int newIndex}) {
|
void swipedTo({required int newIndex}) {
|
||||||
_currentImageModelNotifier.value = _imagesService.imageModelAt(index: 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();
|
String get currentImageUrl => currentImageModelListenable.value.uri.toString();
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:internet_connection_checker/internet_connection_checker.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/abstracts/router/app_router.dart';
|
||||||
import 'features/core/services/app_lifecycle_service.dart';
|
import 'features/core/services/app_lifecycle_service.dart';
|
||||||
|
@ -37,7 +38,7 @@ class Locator {
|
||||||
|
|
||||||
static void _registerAPIs() {
|
static void _registerAPIs() {
|
||||||
instance().registerFactory(
|
instance().registerFactory(
|
||||||
() => UnsplashImagesApi(),
|
() => UnsplashImagesApi(token: Env.unsplashApiKey),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,13 +48,11 @@ class Locator {
|
||||||
imagesService: ImagesService.locate,
|
imagesService: ImagesService.locate,
|
||||||
navigationService: NavigationService.locate,
|
navigationService: NavigationService.locate,
|
||||||
imageCacheManagerService: ImageCacheManagerService.locate,
|
imageCacheManagerService: ImageCacheManagerService.locate,
|
||||||
loggingService: LoggingService.locate,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
instance().registerFactory(
|
instance().registerFactory(
|
||||||
() => ImageCarouselViewModel(
|
() => ImageCarouselViewModel(
|
||||||
imagesService: ImagesService.locate,
|
imagesService: ImagesService.locate,
|
||||||
loggingService: LoggingService.locate,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
14
pubspec.lock
14
pubspec.lock
|
@ -232,6 +232,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.6"
|
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:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -29,6 +29,9 @@ dependencies:
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
hive_flutter: ^1.1.0
|
hive_flutter: ^1.1.0
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
envied: ^0.3.0
|
||||||
|
|
||||||
# Util backend
|
# Util backend
|
||||||
intl_utils: ^2.8.1
|
intl_utils: ^2.8.1
|
||||||
connectivity_plus: ^3.0.2
|
connectivity_plus: ^3.0.2
|
||||||
|
@ -58,6 +61,7 @@ dev_dependencies:
|
||||||
build_runner: ^2.3.3
|
build_runner: ^2.3.3
|
||||||
json_serializable: ^6.5.4
|
json_serializable: ^6.5.4
|
||||||
hive_generator: ^2.0.0
|
hive_generator: ^2.0.0
|
||||||
|
envied_generator: ^0.3.0
|
||||||
|
|
||||||
# Annotations
|
# Annotations
|
||||||
json_annotation: ^4.7.0
|
json_annotation: ^4.7.0
|
||||||
|
|
Loading…
Reference in a new issue