From 127a09b5970123c21608a52c9358224343ab932a Mon Sep 17 00:00:00 2001 From: Mehul Ahal <112100480+MehulAhal@users.noreply.github.com> Date: Thu, 22 Dec 2022 12:17:08 +0100 Subject: [PATCH] added swiping add swiping --- .../core/data/constants/const_colors.dart | 1 + .../core/services/connection_service.dart | 19 +++- .../core/services/overlay_service.dart | 35 +++--- .../home/api/unsplash_images_api.dart | 2 +- .../home/services/images_service.dart | 3 + .../home/views/gallery/gallery_view.dart | 16 ++- .../image_carousel/image_carousel_view.dart | 106 ++++++++++++------ .../image_carousel_view_model.dart | 32 ++++-- pubspec.lock | 18 ++- pubspec.yaml | 4 +- 10 files changed, 154 insertions(+), 82 deletions(-) diff --git a/lib/features/core/data/constants/const_colors.dart b/lib/features/core/data/constants/const_colors.dart index cd6f427..47465c2 100644 --- a/lib/features/core/data/constants/const_colors.dart +++ b/lib/features/core/data/constants/const_colors.dart @@ -7,6 +7,7 @@ abstract class ConstColours { static const white = Colors.white; static const black = Colors.black; + static const transparent = Colors.transparent; } abstract class ConstThemes { diff --git a/lib/features/core/services/connection_service.dart b/lib/features/core/services/connection_service.dart index 97ddd14..20216d4 100644 --- a/lib/features/core/services/connection_service.dart +++ b/lib/features/core/services/connection_service.dart @@ -4,11 +4,12 @@ import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:internet_connection_checker/internet_connection_checker.dart'; +import 'package:mc_gallery/features/core/services/logging_service.dart'; import '/locator.dart'; /// Used to observe the current connection type. -class ConnectionService { +class ConnectionService with LoggingService { ConnectionService({ required Connectivity connectivity, required InternetConnectionChecker internetConnectionChecker, @@ -80,7 +81,9 @@ class ConnectionService { } }); _isInitialized.complete(); - } catch (error, stackTrace) {} + } catch (error, stackTrace) { + handle(error, stackTrace); + } } Future dispose() async { @@ -109,7 +112,9 @@ class ConnectionService { await listener( connectivityResult: connectivityResult, hasInternet: await hasInternetConnection); }); - } catch (error, stackTrace) {} + } catch (error, stackTrace) { + handle(error, stackTrace); + } } Future removeListener({required String tag}) async { @@ -118,13 +123,17 @@ class ConnectionService { if (subscription != null) { await subscription.cancel(); } else {} - } catch (error, stackTrace) {} + } catch (error, stackTrace) { + handle(error, stackTrace); + } } Future _onConnectivityChanged(ConnectivityResult connectivityResult) async { try { _connectivityResult = connectivityResult; - } catch (error, stackTrace) {} + } catch (error, stackTrace) { + handle(error, stackTrace); + } } static ConnectionService get locate => Locator.locate(); diff --git a/lib/features/core/services/overlay_service.dart b/lib/features/core/services/overlay_service.dart index 1514c75..ab49ab7 100644 --- a/lib/features/core/services/overlay_service.dart +++ b/lib/features/core/services/overlay_service.dart @@ -1,36 +1,33 @@ -import 'dart:async'; - import 'package:flutter/widgets.dart'; import 'package:mc_gallery/features/core/services/logging_service.dart'; import 'package:mc_gallery/locator.dart'; class OverlayService { - const OverlayService({ + OverlayService({ required LoggingService loggingService, }) : _loggingService = loggingService; final LoggingService _loggingService; - final Map _overlayEntryMap = const {}; + final Map _overlayEntryMap = {}; - Future playOverlayEntry({ - required BuildContext context, + void insertOverlayEntry( + BuildContext context, { + required String tag, required OverlayEntry overlayEntry, - }) async { - try { - _overlayEntryMap[overlayEntry.hashCode] = overlayEntry; - Overlay.of( - context, - rootOverlay: true, - )! - .insert(overlayEntry); + }) { + _overlayEntryMap.addEntries([MapEntry(tag.hashCode, overlayEntry)]); - if (overlayEntry.mounted) overlayEntry.remove(); + Overlay.of(context, rootOverlay: true)?.insert(overlayEntry); + _loggingService.info('Overlay inserted with tag: $tag'); + } - _overlayEntryMap.remove(overlayEntry.hashCode); - } catch (error, stackTrace) { - _loggingService.handle(error, stackTrace); - } + void removeOverlayEntry({ + required String tag, + }) { + _overlayEntryMap[tag.hashCode]?.remove(); + _overlayEntryMap.remove(tag.hashCode); + _loggingService.info('Overlay removed with tag: $tag'); } void dispose() { diff --git a/lib/features/home/api/unsplash_images_api.dart b/lib/features/home/api/unsplash_images_api.dart index e563367..82b8f84 100644 --- a/lib/features/home/api/unsplash_images_api.dart +++ b/lib/features/home/api/unsplash_images_api.dart @@ -25,7 +25,7 @@ class UnsplashImagesApi with LoggingService implements ImagesApi { return ImageModel( imageIndex: imageIndex, uri: imageUri, - imageName: '${Strings.current.image} $imageIndex: size=$imageSide', + imageName: '${Strings.current.image} ${imageIndex + 1}: size=$imageSide', ); }); } on Exception catch (ex, stackTrace) { diff --git a/lib/features/home/services/images_service.dart b/lib/features/home/services/images_service.dart index d26e5c1..ce04a3a 100644 --- a/lib/features/home/services/images_service.dart +++ b/lib/features/home/services/images_service.dart @@ -36,6 +36,9 @@ class ImagesService { 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(); } diff --git a/lib/features/home/views/gallery/gallery_view.dart b/lib/features/home/views/gallery/gallery_view.dart index fb436cb..b517f53 100644 --- a/lib/features/home/views/gallery/gallery_view.dart +++ b/lib/features/home/views/gallery/gallery_view.dart @@ -16,6 +16,7 @@ class GalleryView extends StatelessWidget { viewModelBuilder: () => GalleryViewModel.locate, builder: (context, final model) => McgScaffold( bodyBuilderWaiter: model.isInitialised, + forceInternetCheck: true, appBar: AppBar( title: Text(model.strings.gallery), ), @@ -46,15 +47,12 @@ class GalleryView extends StatelessWidget { 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), - ), + child: CachedNetworkImage( + imageUrl: imageModel.uri.toString(), + cacheKey: imageModel.imageIndex.toString(), + progressIndicatorBuilder: (_, __, final progress) => + CircularProgressIndicator( + value: model.downloadProgressValue(progress: progress), ), ), ), diff --git a/lib/features/home/views/image_carousel/image_carousel_view.dart b/lib/features/home/views/image_carousel/image_carousel_view.dart index 13dae54..e87a700 100644 --- a/lib/features/home/views/image_carousel/image_carousel_view.dart +++ b/lib/features/home/views/image_carousel/image_carousel_view.dart @@ -1,9 +1,12 @@ +import 'package:auto_size_text/auto_size_text.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_markdown/flutter_markdown.dart'; -import 'package:mc_gallery/features/core/data/constants/const_colors.dart'; -import 'package:mc_gallery/features/core/data/constants/const_text.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/view_model_builder.dart'; @@ -28,49 +31,78 @@ class ImageCarouselView extends StatelessWidget { viewModelBuilder: () => ImageCarouselViewModel.locate, argumentBuilder: () => imageCarouselViewArguments, builder: (context, final model) => McgScaffold( + bodyBuilderWaiter: model.isInitialised, appBar: AppBar( title: Text(model.strings.imageCarousel), ), body: Column( children: [ Expanded( - child: Card( - elevation: 8, - child: Stack( - fit: StackFit.expand, - children: [ - Hero( - tag: model.currentImageKey, - child: CachedNetworkImage( - imageUrl: model.currentImageUrl, - cacheKey: model.currentImageKey, - fit: BoxFit.fill, - progressIndicatorBuilder: (_, __, final progress) => - CircularProgressIndicator( - value: model.downloadProgressValue(progress: progress), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + elevation: 8, + surfaceTintColor: ConstColours.transparent, + child: CarouselSlider.builder( + itemCount: model.numberOfImages, + carouselController: model.carouselController, + options: CarouselOptions( + enlargeFactor: 1, + enlargeCenterPage: true, + enlargeStrategy: CenterPageEnlargeStrategy.scale, + disableCenter: true, + viewportFraction: 1, + initialPage: model.currentImageIndex, + enableInfiniteScroll: false, + onPageChanged: (final index, _) => model.swipedTo(newIndex: index), + ), + itemBuilder: (context, _, __) => Stack( + fit: StackFit.expand, + children: [ + ValueListenableBuilder( + valueListenable: model.currentImageModelListenable, + builder: (context, _, __) => CachedNetworkImage( + imageUrl: model.currentImageUrl, + cacheKey: model.currentImageKey, + fit: BoxFit.contain, + progressIndicatorBuilder: (_, __, final progress) => + CircularProgressIndicator( + value: model.downloadProgressValue(progress: progress), + ), + ), ), - ), + ValueListenableBuilder( + valueListenable: model.currentImageModelListenable, + builder: (context, _, __) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon( + Icons.chevron_left, + color: model.hasPreviousImage + ? ConstColours.white + : ConstColours.black, + ), + onPressed: model.onPreviousPressed, + ), + AutoSizeText( + model.currentImageName, + style: ConstText.imageOverlayTextStyle(context), + ), + IconButton( + icon: Icon( + Icons.chevron_right, + color: + model.hasNextImage ? ConstColours.white : ConstColours.black, + ), + onPressed: model.onNextPressed, + ), + ], + ), + ), + ], ), - Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Icon( - Icons.chevron_left, - color: model.hasPreviousImage ? ConstColours.white : ConstColours.black, - ), - Text( - model.currentImageName, - style: ConstText.imageOverlayTextStyle(context), - ), - Icon( - Icons.chevron_right, - color: model.hasNextImage ? ConstColours.white : ConstColours.black, - ), - ], - ), - ), - ], + ), ), ), ), 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 a9579bc..63dcdcc 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,3 +1,4 @@ +import 'package:carousel_slider/carousel_controller.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; @@ -22,13 +23,16 @@ class ImageCarouselViewModel extends BaseViewModel { final NavigationService _navigationService; final LoggingService _loggingService; - late final ValueNotifier _currentImageModel; - ValueListenable get currentImageModel => _currentImageModel; + late final ValueNotifier _currentImageModelNotifier; + ValueListenable get currentImageModelListenable => _currentImageModelNotifier; + + final CarouselController carouselController = CarouselController(); @override Future initialise(bool Function() mounted, [arguments]) async { - _currentImageModel = ValueNotifier(_imagesService.imageModels + _currentImageModelNotifier = ValueNotifier(_imagesService.imageModels .elementAt((arguments! as ImageCarouselViewArguments).imageIndexKey)); + _loggingService.info('Initialized with image: ${_currentImageModelNotifier.value.imageIndex}'); super.initialise(mounted, arguments); } @@ -38,17 +42,29 @@ class ImageCarouselViewModel extends BaseViewModel { super.dispose(); } - String get currentImageUrl => currentImageModel.value.uri.toString(); - String get currentImageKey => currentImageModel.value.imageIndex.toString(); - String get currentImageName => currentImageModel.value.imageName; + void swipedTo({required int newIndex}) { + _currentImageModelNotifier.value = _imagesService.imageModelAt(index: newIndex); + _loggingService.info('Swiped to image: ${_currentImageModelNotifier.value.imageIndex}'); + } + + void onPreviousPressed() => carouselController.previousPage(); + + void onNextPressed() => carouselController.nextPage(); + + 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}) => progress.totalSize != null ? progress.downloaded / progress.totalSize! : null; bool get hasPreviousImage => - currentImageModel.value.imageIndex > _imagesService.firstAvailableImageIndex; + currentImageModelListenable.value.imageIndex > _imagesService.firstAvailableImageIndex; bool get hasNextImage => - currentImageModel.value.imageIndex < _imagesService.lastAvailableImageIndex; + currentImageModelListenable.value.imageIndex < _imagesService.lastAvailableImageIndex; static ImageCarouselViewModel get locate => Locator.locate(); } diff --git a/pubspec.lock b/pubspec.lock index 9cbd01a..ca2f9cb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -43,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted 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: dependency: transitive description: @@ -71,6 +78,13 @@ packages: url: "https://pub.dartlang.org" source: hosted 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: dependency: transitive description: @@ -545,7 +559,7 @@ packages: name: talker url: "https://pub.dartlang.org" source: hosted - version: "2.1.0+1" + version: "2.2.0" talker_dio_logger: dependency: "direct main" description: @@ -559,7 +573,7 @@ packages: name: talker_logger url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 637ddb8..1a242bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,9 +32,11 @@ dependencies: # Util frontend flutter_markdown: ^0.6.13 + auto_size_text: ^3.0.0 + carousel_slider: ^4.2.1 # Logging - talker: ^2.1.0+1 + talker: ^2.2.0 talker_dio_logger: ^1.0.0 # Assets