Compare commits
2 commits
86dbf7e640
...
5aa6a58299
Author | SHA1 | Date | |
---|---|---|---|
|
5aa6a58299 | ||
|
2ff4d44d25 |
15 changed files with 252 additions and 97 deletions
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'const_colors.dart';
|
||||||
|
|
||||||
|
abstract class ConstText {
|
||||||
|
static const _imageOverlayTextStyle = TextStyle(
|
||||||
|
color: ConstColours.white,
|
||||||
|
fontSize: 28,
|
||||||
|
);
|
||||||
|
static TextStyle imageOverlayTextStyle(BuildContext context) => _imageOverlayTextStyle;
|
||||||
|
}
|
|
@ -11,16 +11,13 @@ class OverlayService {
|
||||||
|
|
||||||
final LoggingService _loggingService;
|
final LoggingService _loggingService;
|
||||||
|
|
||||||
final Map<int, AnimationController> _animationControllerMap = const {};
|
|
||||||
final Map<int, OverlayEntry> _overlayEntryMap = const {};
|
final Map<int, OverlayEntry> _overlayEntryMap = const {};
|
||||||
|
|
||||||
Future<void> playOverlayEntry({
|
Future<void> playOverlayEntry({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required AnimationController animationController,
|
|
||||||
required OverlayEntry overlayEntry,
|
required OverlayEntry overlayEntry,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
_animationControllerMap[animationController.hashCode] = animationController;
|
|
||||||
_overlayEntryMap[overlayEntry.hashCode] = overlayEntry;
|
_overlayEntryMap[overlayEntry.hashCode] = overlayEntry;
|
||||||
Overlay.of(
|
Overlay.of(
|
||||||
context,
|
context,
|
||||||
|
@ -28,12 +25,9 @@ class OverlayService {
|
||||||
)!
|
)!
|
||||||
.insert(overlayEntry);
|
.insert(overlayEntry);
|
||||||
|
|
||||||
await animationController.forward();
|
|
||||||
if (overlayEntry.mounted) overlayEntry.remove();
|
if (overlayEntry.mounted) overlayEntry.remove();
|
||||||
|
|
||||||
_overlayEntryMap.remove(overlayEntry.hashCode);
|
_overlayEntryMap.remove(overlayEntry.hashCode);
|
||||||
animationController.dispose();
|
|
||||||
_animationControllerMap.remove(animationController.hashCode);
|
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
_loggingService.handle(error, stackTrace);
|
_loggingService.handle(error, stackTrace);
|
||||||
}
|
}
|
||||||
|
@ -46,12 +40,6 @@ class OverlayService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_overlayEntryMap.clear();
|
_overlayEntryMap.clear();
|
||||||
for (final animationController in _animationControllerMap.values) {
|
|
||||||
if (animationController.isAnimating) {
|
|
||||||
animationController.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_animationControllerMap.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static OverlayService get locate => Locator.locate();
|
static OverlayService get locate => Locator.locate();
|
||||||
|
|
|
@ -1,29 +1,37 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:mc_gallery/features/core/services/logging_service.dart';
|
||||||
|
import 'package:mc_gallery/locator.dart';
|
||||||
|
|
||||||
import '/features/core/data/constants/const_values.dart';
|
import '/features/core/data/constants/const_values.dart';
|
||||||
import '/l10n/generated/l10n.dart';
|
import '/l10n/generated/l10n.dart';
|
||||||
import '../abstracts/images_api.dart';
|
import '../abstracts/images_api.dart';
|
||||||
import '../data/models/image_model.dart';
|
import '../data/models/image_model.dart';
|
||||||
|
|
||||||
class UnsplashImagesApi implements ImagesApi {
|
class UnsplashImagesApi with LoggingService implements ImagesApi {
|
||||||
@override
|
@override
|
||||||
FutureOr<Iterable<ImageModel>> fetchImageUri({required String token}) {
|
FutureOr<Iterable<ImageModel>> fetchImageUri({required String token}) {
|
||||||
final random = Random();
|
final random = Random();
|
||||||
|
|
||||||
return Iterable<int>.generate(ConstValues.numberOfImages).map((final imageIndex) {
|
try {
|
||||||
// Drawing from a normal distribution
|
return Iterable<int>.generate(ConstValues.numberOfImages).map((final imageIndex) {
|
||||||
final imageSide = ConstValues.minImageSize +
|
// Drawing from a normal distribution
|
||||||
random.nextInt((ConstValues.maxImageSize + 1) - ConstValues.minImageSize);
|
final imageSide = ConstValues.minImageSize +
|
||||||
|
random.nextInt((ConstValues.maxImageSize + 1) - ConstValues.minImageSize);
|
||||||
|
|
||||||
final imageUri = _imageUrlGenerator(imageSide: imageSide);
|
final imageUri = _imageUrlGenerator(imageSide: imageSide);
|
||||||
|
|
||||||
return ImageModel(
|
return ImageModel(
|
||||||
imageIndex: imageIndex,
|
imageIndex: imageIndex,
|
||||||
uri: imageUri,
|
uri: imageUri,
|
||||||
imageName: Strings.current.image,
|
imageName: '${Strings.current.image} ${imageIndex + 1}: size=$imageSide',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
} on Exception catch (ex, stackTrace) {
|
||||||
|
handleException(ex, stackTrace);
|
||||||
|
return const Iterable.empty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri _imageUrlGenerator({required int imageSide}) => Uri(
|
Uri _imageUrlGenerator({required int imageSide}) => Uri(
|
||||||
|
@ -31,4 +39,6 @@ class UnsplashImagesApi implements ImagesApi {
|
||||||
host: ConstValues.backendHost,
|
host: ConstValues.backendHost,
|
||||||
pathSegments: [...ConstValues.backendUrlPathSegments, '${imageSide}x$imageSide'],
|
pathSegments: [...ConstValues.backendUrlPathSegments, '${imageSide}x$imageSide'],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static UnsplashImagesApi get locate => Locator.locate();
|
||||||
}
|
}
|
||||||
|
|
37
lib/features/home/services/image_cache_manager_service.dart
Normal file
37
lib/features/home/services/image_cache_manager_service.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:mc_gallery/features/core/services/app_lifecycle_service.dart';
|
||||||
|
import 'package:mc_gallery/features/core/services/logging_service.dart';
|
||||||
|
import 'package:mc_gallery/locator.dart';
|
||||||
|
|
||||||
|
class ImageCacheManagerService with LoggingService {
|
||||||
|
ImageCacheManagerService({
|
||||||
|
required AppLifecycleService appLifecycleService,
|
||||||
|
}) : _appLifecycleService = appLifecycleService {
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
final AppLifecycleService _appLifecycleService;
|
||||||
|
|
||||||
|
Future<void> emptyCache() async => await DefaultCacheManager().emptyCache();
|
||||||
|
|
||||||
|
Future<void> _init() 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:
|
||||||
|
info('Discarding cached images');
|
||||||
|
await DefaultCacheManager().emptyCache();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ImageCacheManagerService get locate => Locator.locate();
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:mc_gallery/features/core/services/logging_service.dart';
|
||||||
import 'package:mc_gallery/features/home/data/models/image_model.dart';
|
import 'package:mc_gallery/features/home/data/models/image_model.dart';
|
||||||
import 'package:mc_gallery/locator.dart';
|
import 'package:mc_gallery/locator.dart';
|
||||||
|
|
||||||
|
@ -8,20 +9,36 @@ import '../abstracts/images_api.dart';
|
||||||
/// Since this is very simple use-case, this is the only interface. For complex (actual CRUD-based) I/O,
|
/// Since this is very simple use-case, this is the only interface. For complex (actual CRUD-based) I/O,
|
||||||
/// an additional Repository layer interface can be used between [ImagesService] and [ImagesApi].
|
/// an additional Repository layer interface can be used between [ImagesService] and [ImagesApi].
|
||||||
class ImagesService {
|
class ImagesService {
|
||||||
ImagesService({required ImagesApi imagesApi}) : _imagesApi = imagesApi {
|
ImagesService({
|
||||||
|
required ImagesApi imagesApi,
|
||||||
|
required LoggingService loggingService,
|
||||||
|
}) : _imagesApi = imagesApi,
|
||||||
|
_loggingService = loggingService {
|
||||||
_init();
|
_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
final ImagesApi _imagesApi;
|
final ImagesApi _imagesApi;
|
||||||
|
final LoggingService _loggingService;
|
||||||
|
|
||||||
late final Iterable<ImageModel> _imageModels;
|
late final Iterable<ImageModel> _imageModels;
|
||||||
Iterable<ImageModel> get imageModels => _imageModels;
|
Iterable<ImageModel> get imageModels => _imageModels;
|
||||||
|
|
||||||
Future<void> _init() async {
|
Future<void> _init() async {
|
||||||
|
_loggingService.info('Fetching and creating image models...');
|
||||||
_imageModels = await _imagesApi.fetchImageUri(token: '');
|
_imageModels = await _imagesApi.fetchImageUri(token: '');
|
||||||
|
|
||||||
|
_imageModels.isNotEmpty
|
||||||
|
? _loggingService.good("Created ${_imageModels.length} images' models")
|
||||||
|
: _loggingService.warning('No images found');
|
||||||
|
|
||||||
Locator.instance().signalReady(this);
|
Locator.instance().signalReady(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get firstAvailableImageIndex => 0;
|
||||||
|
int get lastAvailableImageIndex => _imageModels.length - 1;
|
||||||
|
int get numberOfImages => _imageModels.length;
|
||||||
|
|
||||||
|
ImageModel imageModelAt({required int index}) => _imageModels.elementAt(index);
|
||||||
|
|
||||||
static ImagesService get locate => Locator.locate();
|
static ImagesService get locate => Locator.locate();
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,15 +46,12 @@ class GalleryView extends StatelessWidget {
|
||||||
context,
|
context,
|
||||||
imageModel: imageModel,
|
imageModel: imageModel,
|
||||||
),
|
),
|
||||||
child: Hero(
|
child: CachedNetworkImage(
|
||||||
tag: imageModel.imageIndex.toString(),
|
imageUrl: imageModel.uri.toString(),
|
||||||
child: CachedNetworkImage(
|
cacheKey: imageModel.imageIndex.toString(),
|
||||||
imageUrl: imageModel.uri.toString(),
|
progressIndicatorBuilder: (_, __, final progress) =>
|
||||||
cacheKey: imageModel.imageIndex.toString(),
|
CircularProgressIndicator(
|
||||||
progressIndicatorBuilder: (_, __, final progress) =>
|
value: model.downloadProgressValue(progress: progress),
|
||||||
CircularProgressIndicator(
|
|
||||||
value: model.downloadProgressValue(progress: progress),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
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/core/services/app_lifecycle_service.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/core/services/logging_service.dart';
|
||||||
import '/features/core/services/navigation_service.dart';
|
import '/features/core/services/navigation_service.dart';
|
||||||
import '/features/home/data/models/image_model.dart';
|
import '/features/home/data/models/image_model.dart';
|
||||||
|
import '/features/home/services/image_cache_manager_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 '/features/home/views/image_carousel/image_carousel_view.dart';
|
||||||
import '/locator.dart';
|
import '/locator.dart';
|
||||||
|
@ -15,16 +15,16 @@ class GalleryViewModel extends BaseViewModel {
|
||||||
GalleryViewModel({
|
GalleryViewModel({
|
||||||
required ImagesService imagesService,
|
required ImagesService imagesService,
|
||||||
required NavigationService navigationService,
|
required NavigationService navigationService,
|
||||||
required AppLifecycleService appLifecycleService,
|
required ImageCacheManagerService imageCacheManagerService,
|
||||||
required LoggingService loggingService,
|
required LoggingService loggingService,
|
||||||
}) : _imagesService = imagesService,
|
}) : _imagesService = imagesService,
|
||||||
_navigationService = navigationService,
|
_navigationService = navigationService,
|
||||||
_appLifecycleService = appLifecycleService,
|
_imageCacheManagerService = imageCacheManagerService,
|
||||||
_loggingService = loggingService;
|
_loggingService = loggingService;
|
||||||
|
|
||||||
final ImagesService _imagesService;
|
final ImagesService _imagesService;
|
||||||
final NavigationService _navigationService;
|
final NavigationService _navigationService;
|
||||||
final AppLifecycleService _appLifecycleService;
|
final ImageCacheManagerService _imageCacheManagerService;
|
||||||
final LoggingService _loggingService;
|
final LoggingService _loggingService;
|
||||||
|
|
||||||
final ValueNotifier<bool> _isDisplayingPressingPrompt = ValueNotifier(true);
|
final ValueNotifier<bool> _isDisplayingPressingPrompt = ValueNotifier(true);
|
||||||
|
@ -32,27 +32,11 @@ class GalleryViewModel extends BaseViewModel {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> initialise(bool Function() mounted, [arguments]) async {
|
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);
|
super.initialise(mounted, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
await _appLifecycleService.removeListener(tag: runtimeType.toString());
|
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:carousel_slider/carousel_slider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
import 'package:mc_gallery/features/home/data/models/image_model.dart';
|
||||||
|
|
||||||
|
import '/features/core/data/constants/const_colors.dart';
|
||||||
|
import '/features/core/data/constants/const_text.dart';
|
||||||
|
import '/features/core/widgets/gap.dart';
|
||||||
import '/features/core/widgets/mcg_scaffold.dart';
|
import '/features/core/widgets/mcg_scaffold.dart';
|
||||||
import '/features/core/widgets/view_model_builder.dart';
|
import '/features/core/widgets/view_model_builder.dart';
|
||||||
import '/features/home/views/image_carousel/image_carousel_view_model.dart';
|
import '/features/home/views/image_carousel/image_carousel_view_model.dart';
|
||||||
|
@ -24,18 +31,78 @@ class ImageCarouselView extends StatelessWidget {
|
||||||
viewModelBuilder: () => ImageCarouselViewModel.locate,
|
viewModelBuilder: () => ImageCarouselViewModel.locate,
|
||||||
argumentBuilder: () => imageCarouselViewArguments,
|
argumentBuilder: () => imageCarouselViewArguments,
|
||||||
builder: (context, final model) => McgScaffold(
|
builder: (context, final model) => McgScaffold(
|
||||||
|
bodyBuilderWaiter: model.isInitialised,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(model.strings.imageCarousel),
|
title: Text(model.strings.imageCarousel),
|
||||||
),
|
),
|
||||||
body: Hero(
|
body: Column(
|
||||||
tag: model.currentImageKey,
|
children: [
|
||||||
child: CachedNetworkImage(
|
Expanded(
|
||||||
imageUrl: model.currentImageUrl,
|
child: Card(
|
||||||
cacheKey: model.currentImageKey,
|
elevation: 8,
|
||||||
progressIndicatorBuilder: (_, __, final progress) => CircularProgressIndicator(
|
child: CarouselSlider.builder(
|
||||||
value: model.downloadProgressValue(progress: progress),
|
itemCount: model.numberOfImages,
|
||||||
|
options: CarouselOptions(
|
||||||
|
enlargeFactor: 1,
|
||||||
|
enlargeCenterPage: true,
|
||||||
|
enlargeStrategy: CenterPageEnlargeStrategy.scale,
|
||||||
|
disableCenter: true,
|
||||||
|
aspectRatio: 1,
|
||||||
|
initialPage: model.currentImageIndex,
|
||||||
|
enableInfiniteScroll: false,
|
||||||
|
onPageChanged: (final index, _) => model.swipedTo(newIndex: index),
|
||||||
|
),
|
||||||
|
itemBuilder: (context, _, __) => Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
ValueListenableBuilder<ImageModel>(
|
||||||
|
valueListenable: model.currentImageModelListenable,
|
||||||
|
builder: (context, _, __) => CachedNetworkImage(
|
||||||
|
imageUrl: model.currentImageUrl,
|
||||||
|
cacheKey: model.currentImageKey,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
progressIndicatorBuilder: (_, __, final progress) =>
|
||||||
|
CircularProgressIndicator(
|
||||||
|
value: model.downloadProgressValue(progress: progress),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: ValueListenableBuilder<ImageModel>(
|
||||||
|
valueListenable: model.currentImageModelListenable,
|
||||||
|
builder: (context, _, __) => Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.chevron_left,
|
||||||
|
color: model.hasPreviousImage
|
||||||
|
? ConstColours.white
|
||||||
|
: ConstColours.black,
|
||||||
|
),
|
||||||
|
AutoSizeText(
|
||||||
|
model.currentImageName,
|
||||||
|
style: ConstText.imageOverlayTextStyle(context),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.chevron_right,
|
||||||
|
color: model.hasNextImage ? ConstColours.white : ConstColours.black,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const Gap(24),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: MarkdownBody(data: model.strings.imageDetails),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
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/core/services/app_lifecycle_service.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/core/services/logging_service.dart';
|
||||||
|
@ -16,53 +13,51 @@ class ImageCarouselViewModel extends BaseViewModel {
|
||||||
ImageCarouselViewModel({
|
ImageCarouselViewModel({
|
||||||
required ImagesService imagesService,
|
required ImagesService imagesService,
|
||||||
required NavigationService navigationService,
|
required NavigationService navigationService,
|
||||||
required AppLifecycleService appLifecycleService,
|
|
||||||
required LoggingService loggingService,
|
required LoggingService loggingService,
|
||||||
}) : _imagesService = imagesService,
|
}) : _imagesService = imagesService,
|
||||||
_navigationService = navigationService,
|
_navigationService = navigationService,
|
||||||
_appLifecycleService = appLifecycleService,
|
|
||||||
_loggingService = loggingService;
|
_loggingService = loggingService;
|
||||||
|
|
||||||
final ImagesService _imagesService;
|
final ImagesService _imagesService;
|
||||||
final NavigationService _navigationService;
|
final NavigationService _navigationService;
|
||||||
final AppLifecycleService _appLifecycleService;
|
|
||||||
final LoggingService _loggingService;
|
final LoggingService _loggingService;
|
||||||
|
|
||||||
late final ValueNotifier<ImageModel> _currentImageModel;
|
late final ValueNotifier<ImageModel> _currentImageModelNotifier;
|
||||||
ValueListenable<ImageModel> get currentImageModel => _currentImageModel;
|
ValueListenable<ImageModel> get currentImageModelListenable => _currentImageModelNotifier;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> initialise(bool Function() mounted, [arguments]) async {
|
Future<void> initialise(bool Function() mounted, [arguments]) async {
|
||||||
_appLifecycleService.addListener(
|
_currentImageModelNotifier = ValueNotifier(_imagesService.imageModels
|
||||||
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));
|
.elementAt((arguments! as ImageCarouselViewArguments).imageIndexKey));
|
||||||
|
_loggingService.info('Initialized with image: ${_currentImageModelNotifier.value.imageIndex}');
|
||||||
|
|
||||||
super.initialise(mounted, arguments);
|
super.initialise(mounted, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
await _appLifecycleService.removeListener(tag: runtimeType.toString());
|
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
String get currentImageUrl => currentImageModel.value.uri.toString();
|
void swipedTo({required int newIndex}) {
|
||||||
String get currentImageKey => currentImageModel.value.imageIndex.toString();
|
_currentImageModelNotifier.value = _imagesService.imageModelAt(index: newIndex);
|
||||||
|
_loggingService.info('Swiped to image: ${_currentImageModelNotifier.value.imageIndex}');
|
||||||
|
}
|
||||||
|
|
||||||
|
String get currentImageUrl => currentImageModelListenable.value.uri.toString();
|
||||||
|
String get currentImageKey => currentImageModelListenable.value.imageIndex.toString();
|
||||||
|
String get currentImageName => currentImageModelListenable.value.imageName;
|
||||||
|
int get currentImageIndex => currentImageModelListenable.value.imageIndex;
|
||||||
|
|
||||||
|
int get numberOfImages => _imagesService.numberOfImages;
|
||||||
|
|
||||||
double? downloadProgressValue({required DownloadProgress progress}) =>
|
double? downloadProgressValue({required DownloadProgress progress}) =>
|
||||||
progress.totalSize != null ? progress.downloaded / progress.totalSize! : null;
|
progress.totalSize != null ? progress.downloaded / progress.totalSize! : null;
|
||||||
|
|
||||||
|
bool get hasPreviousImage =>
|
||||||
|
currentImageModelListenable.value.imageIndex > _imagesService.firstAvailableImageIndex;
|
||||||
|
bool get hasNextImage =>
|
||||||
|
currentImageModelListenable.value.imageIndex < _imagesService.lastAvailableImageIndex;
|
||||||
|
|
||||||
static ImageCarouselViewModel get locate => Locator.locate();
|
static ImageCarouselViewModel get locate => Locator.locate();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"gallery": MessageLookupByLibrary.simpleMessage("Gallery"),
|
"gallery": MessageLookupByLibrary.simpleMessage("Gallery"),
|
||||||
"image": MessageLookupByLibrary.simpleMessage("Image"),
|
"image": MessageLookupByLibrary.simpleMessage("Image"),
|
||||||
"imageCarousel": MessageLookupByLibrary.simpleMessage("Image carousel"),
|
"imageCarousel": MessageLookupByLibrary.simpleMessage("Image carousel"),
|
||||||
|
"imageDetails": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Lorem ipsum dolor sit amet. A odio aliquam est sunt explicabo cum galisum asperiores qui voluptas tempora qui aliquid similique. Ut quam laborum ex nostrum recusandae ab sunt ratione quo tempore corporis 33 voluptas nulla aut obcaecati perspiciatis.\n\nAd eveniet exercitationem ad odit quidem aut omnis corporis ea nulla illum qui quisquam temporibus? Est obcaecati similique et quisquam unde ea impedit mollitia ea accusamus natus hic doloribus quis! Et dolorem rerum id doloribus sint ea porro quia ut reprehenderit ratione?"),
|
||||||
"somethingWentWrong":
|
"somethingWentWrong":
|
||||||
MessageLookupByLibrary.simpleMessage("Something went wrong"),
|
MessageLookupByLibrary.simpleMessage("Something went wrong"),
|
||||||
"startLoadingPrompt":
|
"startLoadingPrompt":
|
||||||
|
|
|
@ -99,6 +99,16 @@ class Strings {
|
||||||
args: [],
|
args: [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Lorem ipsum dolor sit amet. A odio aliquam est sunt explicabo cum galisum asperiores qui voluptas tempora qui aliquid similique. Ut quam laborum ex nostrum recusandae ab sunt ratione quo tempore corporis 33 voluptas nulla aut obcaecati perspiciatis.\n\nAd eveniet exercitationem ad odit quidem aut omnis corporis ea nulla illum qui quisquam temporibus? Est obcaecati similique et quisquam unde ea impedit mollitia ea accusamus natus hic doloribus quis! Et dolorem rerum id doloribus sint ea porro quia ut reprehenderit ratione?`
|
||||||
|
String get imageDetails {
|
||||||
|
return Intl.message(
|
||||||
|
'Lorem ipsum dolor sit amet. A odio aliquam est sunt explicabo cum galisum asperiores qui voluptas tempora qui aliquid similique. Ut quam laborum ex nostrum recusandae ab sunt ratione quo tempore corporis 33 voluptas nulla aut obcaecati perspiciatis.\n\nAd eveniet exercitationem ad odit quidem aut omnis corporis ea nulla illum qui quisquam temporibus? Est obcaecati similique et quisquam unde ea impedit mollitia ea accusamus natus hic doloribus quis! Et dolorem rerum id doloribus sint ea porro quia ut reprehenderit ratione?',
|
||||||
|
name: 'imageDetails',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppLocalizationDelegate extends LocalizationsDelegate<Strings> {
|
class AppLocalizationDelegate extends LocalizationsDelegate<Strings> {
|
||||||
|
|
|
@ -7,5 +7,6 @@
|
||||||
"gallery": "Gallery",
|
"gallery": "Gallery",
|
||||||
"startLoadingPrompt": "Press me to start loading",
|
"startLoadingPrompt": "Press me to start loading",
|
||||||
|
|
||||||
"imageCarousel": "Image carousel"
|
"imageCarousel": "Image carousel",
|
||||||
|
"imageDetails": "Lorem ipsum dolor sit amet. A odio aliquam est sunt explicabo cum galisum asperiores qui voluptas tempora qui aliquid similique. Ut quam laborum ex nostrum recusandae ab sunt ratione quo tempore corporis 33 voluptas nulla aut obcaecati perspiciatis.\n\nAd eveniet exercitationem ad odit quidem aut omnis corporis ea nulla illum qui quisquam temporibus? Est obcaecati similique et quisquam unde ea impedit mollitia ea accusamus natus hic doloribus quis! Et dolorem rerum id doloribus sint ea porro quia ut reprehenderit ratione?"
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ import 'package:mc_gallery/features/core/abstracts/router/app_router.dart';
|
||||||
import 'package:mc_gallery/features/core/services/logging_service.dart';
|
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/core/services/navigation_service.dart';
|
||||||
import 'package:mc_gallery/features/home/api/unsplash_images_api.dart';
|
import 'package:mc_gallery/features/home/api/unsplash_images_api.dart';
|
||||||
|
import 'package:mc_gallery/features/home/services/image_cache_manager_service.dart';
|
||||||
import 'package:mc_gallery/features/home/services/images_service.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/gallery/gallery_view_model.dart';
|
||||||
import 'package:mc_gallery/features/home/views/image_carousel/image_carousel_view_model.dart';
|
import 'package:mc_gallery/features/home/views/image_carousel/image_carousel_view_model.dart';
|
||||||
|
@ -44,7 +45,7 @@ class Locator {
|
||||||
() => GalleryViewModel(
|
() => GalleryViewModel(
|
||||||
imagesService: ImagesService.locate,
|
imagesService: ImagesService.locate,
|
||||||
navigationService: NavigationService.locate,
|
navigationService: NavigationService.locate,
|
||||||
appLifecycleService: AppLifecycleService.locate,
|
imageCacheManagerService: ImageCacheManagerService.locate,
|
||||||
loggingService: LoggingService.locate,
|
loggingService: LoggingService.locate,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -52,7 +53,6 @@ class Locator {
|
||||||
() => ImageCarouselViewModel(
|
() => ImageCarouselViewModel(
|
||||||
imagesService: ImagesService.locate,
|
imagesService: ImagesService.locate,
|
||||||
navigationService: NavigationService.locate,
|
navigationService: NavigationService.locate,
|
||||||
appLifecycleService: AppLifecycleService.locate,
|
|
||||||
loggingService: LoggingService.locate,
|
loggingService: LoggingService.locate,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -86,11 +86,14 @@ class Locator {
|
||||||
dispose: (param) async => await param.dispose(),
|
dispose: (param) async => await param.dispose(),
|
||||||
);
|
);
|
||||||
it.registerSingleton(
|
it.registerSingleton(
|
||||||
ImagesService(
|
ImagesService(imagesApi: UnsplashImagesApi.locate, loggingService: LoggingService.locate),
|
||||||
imagesApi: UnsplashImagesApi(),
|
|
||||||
),
|
|
||||||
signalsReady: true,
|
signalsReady: true,
|
||||||
);
|
);
|
||||||
|
it.registerSingleton(
|
||||||
|
ImageCacheManagerService(
|
||||||
|
appLifecycleService: AppLifecycleService.locate,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FutureOr<void> _registerRepos(GetIt locator) {}
|
static FutureOr<void> _registerRepos(GetIt locator) {}
|
||||||
|
|
32
pubspec.lock
32
pubspec.lock
|
@ -43,6 +43,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.9.0"
|
version: "2.9.0"
|
||||||
|
auto_size_text:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: auto_size_text
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -71,6 +78,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
carousel_slider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: carousel_slider
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -195,6 +209,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
|
flutter_markdown:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_markdown
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.13"
|
||||||
flutter_svg:
|
flutter_svg:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -289,6 +310,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
markdown:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: markdown
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.1"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -531,7 +559,7 @@ packages:
|
||||||
name: talker
|
name: talker
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0+1"
|
version: "2.2.0"
|
||||||
talker_dio_logger:
|
talker_dio_logger:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -545,7 +573,7 @@ packages:
|
||||||
name: talker_logger
|
name: talker_logger
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.2.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -30,8 +30,13 @@ dependencies:
|
||||||
connectivity_plus: ^3.0.2
|
connectivity_plus: ^3.0.2
|
||||||
internet_connection_checker: ^1.0.0+1
|
internet_connection_checker: ^1.0.0+1
|
||||||
|
|
||||||
|
# Util frontend
|
||||||
|
flutter_markdown: ^0.6.13
|
||||||
|
auto_size_text: ^3.0.0
|
||||||
|
carousel_slider: ^4.2.1
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
talker: ^2.1.0+1
|
talker: ^2.2.0
|
||||||
talker_dio_logger: ^1.0.0
|
talker_dio_logger: ^1.0.0
|
||||||
|
|
||||||
# Assets
|
# Assets
|
||||||
|
|
Loading…
Reference in a new issue