diff --git a/lib/Components/Carousel/carousel_controller.dart b/lib/Components/Carousel/carousel_controller.dart new file mode 100644 index 0000000..8fa3c04 --- /dev/null +++ b/lib/Components/Carousel/carousel_controller.dart @@ -0,0 +1,149 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'carousel_options.dart'; +import 'carousel_state.dart'; +import 'utils.dart'; + +abstract class CarouselController { + bool get ready; + + Future get onReady; + + Future nextPage({Duration? duration, Curve? curve}); + + Future previousPage({Duration? duration, Curve? curve}); + + void jumpToPage(int page); + + Future animateToPage(int page, {Duration? duration, Curve? curve}); + + void startAutoPlay(); + + void stopAutoPlay(); + + factory CarouselController() => CarouselControllerImpl(); +} + +class CarouselControllerImpl implements CarouselController { + final Completer _readyCompleter = Completer(); + + CarouselState? _state; + + set state(CarouselState? state) { + _state = state; + if (!_readyCompleter.isCompleted) { + _readyCompleter.complete(); + } + } + + void _setModeController() => + _state!.changeMode(CarouselPageChangedReason.controller); + + @override + bool get ready => _state != null; + + @override + Future get onReady => _readyCompleter.future; + + /// Animates the controlled [CarouselSlider] to the next page. + /// + /// The animation lasts for the given duration and follows the given curve. + /// The returned [Future] resolves when the animation completes. + Future nextPage( + {Duration? duration = const Duration(milliseconds: 300), + Curve? curve = Curves.linear}) async { + final bool isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate; + if (isNeedResetTimer) { + _state!.onResetTimer(); + } + _setModeController(); + await _state!.pageController!.nextPage(duration: duration!, curve: curve!); + if (isNeedResetTimer) { + _state!.onResumeTimer(); + } + } + + /// Animates the controlled [CarouselSlider] to the previous page. + /// + /// The animation lasts for the given duration and follows the given curve. + /// The returned [Future] resolves when the animation completes. + Future previousPage( + {Duration? duration = const Duration(milliseconds: 300), + Curve? curve = Curves.linear}) async { + final bool isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate; + if (isNeedResetTimer) { + _state!.onResetTimer(); + } + _setModeController(); + await _state!.pageController! + .previousPage(duration: duration!, curve: curve!); + if (isNeedResetTimer) { + _state!.onResumeTimer(); + } + } + + /// Changes which page is displayed in the controlled [CarouselSlider]. + /// + /// Jumps the page position from its current value to the given value, + /// without animation, and without checking if the new value is in range. + void jumpToPage(int page) { + final index = getRealIndex(_state!.pageController!.page!.toInt(), + _state!.realPage - _state!.initialPage, _state!.itemCount); + + _setModeController(); + final int pageToJump = _state!.pageController!.page!.toInt() + page - index; + return _state!.pageController!.jumpToPage(pageToJump); + } + + /// Animates the controlled [CarouselSlider] from the current page to the given page. + /// + /// The animation lasts for the given duration and follows the given curve. + /// The returned [Future] resolves when the animation completes. + Future animateToPage(int page, + {Duration? duration = const Duration(milliseconds: 300), + Curve? curve = Curves.linear}) async { + final bool isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate; + if (isNeedResetTimer) { + _state!.onResetTimer(); + } + final index = getRealIndex(_state!.pageController!.page!.toInt(), + _state!.realPage - _state!.initialPage, _state!.itemCount); + int smallestMovement = page - index; + if (_state!.options.enableInfiniteScroll && + _state!.itemCount != null && + _state!.options.animateToClosest) { + if ((page - index).abs() > (page + _state!.itemCount! - index).abs()) { + smallestMovement = page + _state!.itemCount! - index; + } else if ((page - index).abs() > + (page - _state!.itemCount! - index).abs()) { + smallestMovement = page - _state!.itemCount! - index; + } + } + _setModeController(); + await _state!.pageController!.animateToPage( + _state!.pageController!.page!.toInt() + smallestMovement, + duration: duration!, + curve: curve!); + if (isNeedResetTimer) { + _state!.onResumeTimer(); + } + } + + /// Starts the controlled [CarouselSlider] autoplay. + /// + /// The carousel will only autoPlay if the [autoPlay] parameter + /// in [CarouselOptions] is true. + void startAutoPlay() { + _state!.onResumeTimer(); + } + + /// Stops the controlled [CarouselSlider] from autoplaying. + /// + /// This is a more on-demand way of doing this. Use the [autoPlay] + /// parameter in [CarouselOptions] to specify the autoPlay behaviour of the carousel. + void stopAutoPlay() { + _state!.onResetTimer(); + } +} \ No newline at end of file diff --git a/lib/Components/Carousel/carousel_options.dart b/lib/Components/Carousel/carousel_options.dart new file mode 100644 index 0000000..77ae333 --- /dev/null +++ b/lib/Components/Carousel/carousel_options.dart @@ -0,0 +1,223 @@ +import 'package:flutter/material.dart'; + +enum CarouselPageChangedReason { timed, manual, controller } + +enum CenterPageEnlargeStrategy { scale, height, zoom } + +class CarouselOptions { + /// Set carousel height and overrides any existing [aspectRatio]. + final double? height; + + /// Aspect ratio is used if no height have been declared. + /// + /// Defaults to 16:9 aspect ratio. + final double aspectRatio; + + /// The fraction of the viewport that each page should occupy. + /// + /// Defaults to 0.8, which means each page fills 80% of the carousel. + final double viewportFraction; + + /// The initial page to show when first creating the [CarouselSlider]. + /// + /// Defaults to 0. + final int initialPage; + + ///Determines if carousel should loop infinitely or be limited to item length. + /// + ///Defaults to true, i.e. infinite loop. + final bool enableInfiniteScroll; + + ///Determines if carousel should loop to the closest occurence of requested page. + /// + ///Defaults to true. + final bool animateToClosest; + + /// Reverse the order of items if set to true. + /// + /// Defaults to false. + final bool reverse; + + /// Enables auto play, sliding one page at a time. + /// + /// Use [autoPlayInterval] to determent the frequency of slides. + /// Defaults to false. + final bool autoPlay; + + /// Sets Duration to determent the frequency of slides when + /// + /// [autoPlay] is set to true. + /// Defaults to 4 seconds. + final Duration autoPlayInterval; + + /// The animation duration between two transitioning pages while in auto playback. + /// + /// Defaults to 800 ms. + final Duration autoPlayAnimationDuration; + + /// Determines the animation curve physics. + /// + /// Defaults to [Curves.fastOutSlowIn]. + final Curve autoPlayCurve; + + /// Determines if current page should be larger than the side images, + /// creating a feeling of depth in the carousel. + /// + /// Defaults to false. + final bool? enlargeCenterPage; + + /// The axis along which the page view scrolls. + /// + /// Defaults to [Axis.horizontal]. + final Axis scrollDirection; + + /// Called whenever the page in the center of the viewport changes. + final Function(int index, CarouselPageChangedReason reason)? onPageChanged; + + /// Called whenever the carousel is scrolled + final ValueChanged? onScrolled; + + /// How the carousel should respond to user input. + /// + /// For example, determines how the items continues to animate after the + /// user stops dragging the page view. + /// + /// The physics are modified to snap to page boundaries using + /// [PageScrollPhysics] prior to being used. + /// + /// Defaults to matching platform conventions. + final ScrollPhysics? scrollPhysics; + + /// Set to false to disable page snapping, useful for custom scroll behavior. + /// + /// Default to `true`. + final bool pageSnapping; + + /// If `true`, the auto play function will be paused when user is interacting with + /// the carousel, and will be resumed when user finish interacting. + /// Default to `true`. + final bool pauseAutoPlayOnTouch; + + /// If `true`, the auto play function will be paused when user is calling + /// pageController's `nextPage` or `previousPage` or `animateToPage` method. + /// And after the animation complete, the auto play will be resumed. + /// Default to `true`. + final bool pauseAutoPlayOnManualNavigate; + + /// If `enableInfiniteScroll` is `false`, and `autoPlay` is `true`, this option + /// decide the carousel should go to the first item when it reach the last item or not. + /// If set to `true`, the auto play will be paused when it reach the last item. + /// If set to `false`, the auto play function will animate to the first item when it was + /// in the last item. + final bool pauseAutoPlayInFiniteScroll; + + /// Pass a `PageStoragekey` if you want to keep the pageview's position when it was recreated. + final PageStorageKey? pageViewKey; + + /// Use [enlargeStrategy] to determine which method to enlarge the center page. + final CenterPageEnlargeStrategy enlargeStrategy; + + /// How much the pages next to the center page will be scaled down. + /// If `enlargeCenterPage` is false, this property has no effect. + final double enlargeFactor; + + /// Whether or not to disable the `Center` widget for each slide. + final bool disableCenter; + + /// Whether to add padding to both ends of the list. + /// If this is set to true and [viewportFraction] < 1.0, padding will be added such that the first and last child slivers will be in the center of the viewport when scrolled all the way to the start or end, respectively. + /// If [viewportFraction] >= 1.0, this property has no effect. + /// This property defaults to true and must not be null. + final bool padEnds; + + /// Exposed clipBehavior of PageView + final Clip clipBehavior; + + CarouselOptions({ + this.height, + this.aspectRatio = 16 / 9, + this.viewportFraction = 0.8, + this.initialPage = 0, + this.enableInfiniteScroll = true, + this.animateToClosest = true, + this.reverse = false, + this.autoPlay = false, + this.autoPlayInterval = const Duration(seconds: 4), + this.autoPlayAnimationDuration = const Duration(milliseconds: 800), + this.autoPlayCurve = Curves.fastOutSlowIn, + this.enlargeCenterPage = false, + this.onPageChanged, + this.onScrolled, + this.scrollPhysics, + this.pageSnapping = true, + this.scrollDirection = Axis.horizontal, + this.pauseAutoPlayOnTouch = true, + this.pauseAutoPlayOnManualNavigate = true, + this.pauseAutoPlayInFiniteScroll = false, + this.pageViewKey, + this.enlargeStrategy = CenterPageEnlargeStrategy.scale, + this.enlargeFactor = 0.3, + this.disableCenter = false, + this.padEnds = true, + this.clipBehavior = Clip.hardEdge, + }); + + ///Generate new [CarouselOptions] based on old ones. + + CarouselOptions copyWith( + {double? height, + double? aspectRatio, + double? viewportFraction, + int? initialPage, + bool? enableInfiniteScroll, + bool? reverse, + bool? autoPlay, + Duration? autoPlayInterval, + Duration? autoPlayAnimationDuration, + Curve? autoPlayCurve, + bool? enlargeCenterPage, + Function(int index, CarouselPageChangedReason reason)? onPageChanged, + ValueChanged? onScrolled, + ScrollPhysics? scrollPhysics, + bool? pageSnapping, + Axis? scrollDirection, + bool? pauseAutoPlayOnTouch, + bool? pauseAutoPlayOnManualNavigate, + bool? pauseAutoPlayInFiniteScroll, + PageStorageKey? pageViewKey, + CenterPageEnlargeStrategy? enlargeStrategy, + double? enlargeFactor, + bool? disableCenter, + Clip? clipBehavior, + bool? padEnds}) => + CarouselOptions( + height: height ?? this.height, + aspectRatio: aspectRatio ?? this.aspectRatio, + viewportFraction: viewportFraction ?? this.viewportFraction, + initialPage: initialPage ?? this.initialPage, + enableInfiniteScroll: enableInfiniteScroll ?? this.enableInfiniteScroll, + reverse: reverse ?? this.reverse, + autoPlay: autoPlay ?? this.autoPlay, + autoPlayInterval: autoPlayInterval ?? this.autoPlayInterval, + autoPlayAnimationDuration: + autoPlayAnimationDuration ?? this.autoPlayAnimationDuration, + autoPlayCurve: autoPlayCurve ?? this.autoPlayCurve, + enlargeCenterPage: enlargeCenterPage ?? this.enlargeCenterPage, + onPageChanged: onPageChanged ?? this.onPageChanged, + onScrolled: onScrolled ?? this.onScrolled, + scrollPhysics: scrollPhysics ?? this.scrollPhysics, + pageSnapping: pageSnapping ?? this.pageSnapping, + scrollDirection: scrollDirection ?? this.scrollDirection, + pauseAutoPlayOnTouch: pauseAutoPlayOnTouch ?? this.pauseAutoPlayOnTouch, + pauseAutoPlayOnManualNavigate: + pauseAutoPlayOnManualNavigate ?? this.pauseAutoPlayOnManualNavigate, + pauseAutoPlayInFiniteScroll: + pauseAutoPlayInFiniteScroll ?? this.pauseAutoPlayInFiniteScroll, + pageViewKey: pageViewKey ?? this.pageViewKey, + enlargeStrategy: enlargeStrategy ?? this.enlargeStrategy, + enlargeFactor: enlargeFactor ?? this.enlargeFactor, + disableCenter: disableCenter ?? this.disableCenter, + clipBehavior: clipBehavior ?? this.clipBehavior, + padEnds: padEnds ?? this.padEnds, + ); +} \ No newline at end of file diff --git a/lib/Components/Carousel/carousel_slider.dart b/lib/Components/Carousel/carousel_slider.dart new file mode 100644 index 0000000..830fc1b --- /dev/null +++ b/lib/Components/Carousel/carousel_slider.dart @@ -0,0 +1,396 @@ +import 'dart:async'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import 'carousel_controller.dart' as cs; +import 'carousel_options.dart'; +import 'carousel_state.dart'; +import 'utils.dart'; + +export 'carousel_controller.dart'; +export 'carousel_options.dart'; + +typedef Widget ExtendedIndexedWidgetBuilder( + BuildContext context, int index, int realIndex); + +class CarouselSlider extends StatefulWidget { + /// [CarouselOptions] to create a [CarouselState] with + final CarouselOptions options; + + final bool? disableGesture; + + /// The widgets to be shown in the carousel of default constructor + final List? items; + + /// The widget item builder that will be used to build item on demand + /// The third argument is the PageView's real index, can be used to cooperate + /// with Hero. + final ExtendedIndexedWidgetBuilder? itemBuilder; + + /// A [MapController], used to control the map. + final cs.CarouselControllerImpl _carouselController; + + final int? itemCount; + + CarouselSlider( + {required this.items, + required this.options, + this.disableGesture, + cs.CarouselController? carouselController, + Key? key}) + : itemBuilder = null, + itemCount = items != null ? items.length : 0, + _carouselController = carouselController != null + ? carouselController as cs.CarouselControllerImpl + : cs.CarouselController() as cs.CarouselControllerImpl, + super(key: key); + + /// The on demand item builder constructor + CarouselSlider.builder( + {required this.itemCount, + required this.itemBuilder, + required this.options, + this.disableGesture, + cs.CarouselController? carouselController, + Key? key}) + : items = null, + _carouselController = carouselController != null + ? carouselController as cs.CarouselControllerImpl + : CarouselController() as cs.CarouselControllerImpl, + super(key: key); + + @override + CarouselSliderState createState() => CarouselSliderState(_carouselController); +} + +class CarouselSliderState extends State + with TickerProviderStateMixin { + final cs.CarouselControllerImpl carouselController; + Timer? timer; + + CarouselOptions get options => widget.options; + + CarouselState? carouselState; + + PageController? pageController; + + /// mode is related to why the page is being changed + CarouselPageChangedReason mode = CarouselPageChangedReason.controller; + + CarouselSliderState(this.carouselController); + + void changeMode(CarouselPageChangedReason _mode) { + mode = _mode; + } + + @override + void didUpdateWidget(CarouselSlider oldWidget) { + carouselState!.options = options; + carouselState!.itemCount = widget.itemCount; + + // pageController needs to be re-initialized to respond to state changes + pageController = PageController( + viewportFraction: options.viewportFraction, + initialPage: carouselState!.realPage, + ); + carouselState!.pageController = pageController; + + // handle autoplay when state changes + handleAutoPlay(); + + super.didUpdateWidget(oldWidget); + } + + @override + void initState() { + super.initState(); + carouselState = + CarouselState(this.options, clearTimer, resumeTimer, this.changeMode); + + carouselState!.itemCount = widget.itemCount; + carouselController.state = carouselState; + carouselState!.initialPage = widget.options.initialPage; + carouselState!.realPage = options.enableInfiniteScroll + ? carouselState!.realPage + carouselState!.initialPage + : carouselState!.initialPage; + handleAutoPlay(); + + pageController = PageController( + viewportFraction: options.viewportFraction, + initialPage: carouselState!.realPage, + ); + + carouselState!.pageController = pageController; + } + + Timer? getTimer() { + return widget.options.autoPlay + ? Timer.periodic(widget.options.autoPlayInterval, (_) { + if (!mounted) { + clearTimer(); + return; + } + + final route = ModalRoute.of(context); + if (route?.isCurrent == false) { + return; + } + + CarouselPageChangedReason previousReason = mode; + changeMode(CarouselPageChangedReason.timed); + int nextPage = carouselState!.pageController!.page!.round() + 1; + int itemCount = widget.itemCount ?? widget.items!.length; + + if (nextPage >= itemCount && + widget.options.enableInfiniteScroll == false) { + if (widget.options.pauseAutoPlayInFiniteScroll) { + clearTimer(); + return; + } + nextPage = 0; + } + + carouselState!.pageController! + .animateToPage(nextPage, + duration: widget.options.autoPlayAnimationDuration, + curve: widget.options.autoPlayCurve) + .then((_) => changeMode(previousReason)); + }) + : null; + } + + void clearTimer() { + if (timer != null) { + timer?.cancel(); + timer = null; + } + } + + void resumeTimer() { + if (timer == null) { + timer = getTimer(); + } + } + + void handleAutoPlay() { + bool autoPlayEnabled = widget.options.autoPlay; + + if (autoPlayEnabled && timer != null) return; + + clearTimer(); + if (autoPlayEnabled) { + resumeTimer(); + } + } + + Widget getGestureWrapper(Widget child) { + Widget wrapper; + if (widget.options.height != null) { + wrapper = Container(height: widget.options.height, child: child); + } else { + wrapper = + AspectRatio(aspectRatio: widget.options.aspectRatio, child: child); + } + + if (true == widget.disableGesture) { + return NotificationListener( + onNotification: (Notification notification) { + if (widget.options.onScrolled != null && + notification is ScrollUpdateNotification) { + widget.options.onScrolled!(carouselState!.pageController!.page); + } + return false; + }, + child: wrapper, + ); + } + + return RawGestureDetector( + behavior: HitTestBehavior.opaque, + gestures: { + _MultipleGestureRecognizer: + GestureRecognizerFactoryWithHandlers<_MultipleGestureRecognizer>( + () => _MultipleGestureRecognizer(), + (_MultipleGestureRecognizer instance) { + instance.onStart = (_) { + onStart(); + }; + instance.onDown = (_) { + onPanDown(); + }; + instance.onEnd = (_) { + onPanUp(); + }; + instance.onCancel = () { + onPanUp(); + }; + }), + }, + child: NotificationListener( + onNotification: (Notification notification) { + if (widget.options.onScrolled != null && + notification is ScrollUpdateNotification) { + widget.options.onScrolled!(carouselState!.pageController!.page); + } + return false; + }, + child: wrapper, + ), + ); + } + + Widget getCenterWrapper(Widget child) { + if (widget.options.disableCenter) { + return Container( + child: child, + ); + } + return Center(child: child); + } + + Widget getEnlargeWrapper(Widget? child, + {double? width, + double? height, + double? scale, + required double itemOffset}) { + if (widget.options.enlargeStrategy == CenterPageEnlargeStrategy.height) { + return SizedBox(child: child, width: width, height: height); + } + if (widget.options.enlargeStrategy == CenterPageEnlargeStrategy.zoom) { + late Alignment alignment; + final bool horizontal = options.scrollDirection == Axis.horizontal; + if (itemOffset > 0) { + alignment = horizontal ? Alignment.centerRight : Alignment.bottomCenter; + } else { + alignment = horizontal ? Alignment.centerLeft : Alignment.topCenter; + } + return Transform.scale(child: child, scale: scale!, alignment: alignment); + } + return Transform.scale( + scale: scale!, + child: Container(child: child, width: width, height: height)); + } + + void onStart() { + changeMode(CarouselPageChangedReason.manual); + } + + void onPanDown() { + if (widget.options.pauseAutoPlayOnTouch) { + clearTimer(); + } + + changeMode(CarouselPageChangedReason.manual); + } + + void onPanUp() { + if (widget.options.pauseAutoPlayOnTouch) { + resumeTimer(); + } + } + + @override + void dispose() { + super.dispose(); + clearTimer(); + } + + @override + Widget build(BuildContext context) { + return getGestureWrapper(PageView.builder( + padEnds: widget.options.padEnds, + scrollBehavior: ScrollConfiguration.of(context).copyWith( + scrollbars: false, + overscroll: false, + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }, + ), + clipBehavior: widget.options.clipBehavior, + physics: widget.options.scrollPhysics, + scrollDirection: widget.options.scrollDirection, + pageSnapping: widget.options.pageSnapping, + controller: carouselState!.pageController, + reverse: widget.options.reverse, + itemCount: widget.options.enableInfiniteScroll ? null : widget.itemCount, + key: widget.options.pageViewKey, + onPageChanged: (int index) { + int currentPage = getRealIndex(index + carouselState!.initialPage, + carouselState!.realPage, widget.itemCount); + if (widget.options.onPageChanged != null) { + widget.options.onPageChanged!(currentPage, mode); + } + }, + itemBuilder: (BuildContext context, int idx) { + final int index = getRealIndex(idx + carouselState!.initialPage, + carouselState!.realPage, widget.itemCount); + + return AnimatedBuilder( + animation: carouselState!.pageController!, + child: (widget.items != null) + ? (widget.items!.length > 0 ? widget.items![index] : Container()) + : widget.itemBuilder!(context, index, idx), + builder: (BuildContext context, child) { + double distortionValue = 1.0; + // if `enlargeCenterPage` is true, we must calculate the carousel item's height + // to display the visual effect + double itemOffset = 0; + if (widget.options.enlargeCenterPage != null && + widget.options.enlargeCenterPage == true) { + // pageController.page can only be accessed after the first build, + // so in the first build we calculate the itemoffset manually + var position = carouselState?.pageController?.position; + if (position != null && + position.hasPixels && + position.hasContentDimensions) { + var _page = carouselState?.pageController?.page; + if (_page != null) { + itemOffset = _page - idx; + } + } else { + BuildContext storageContext = carouselState! + .pageController!.position.context.storageContext; + final double? previousSavedPosition = + PageStorage.of(storageContext)?.readState(storageContext) + as double?; + if (previousSavedPosition != null) { + itemOffset = previousSavedPosition - idx.toDouble(); + } else { + itemOffset = + carouselState!.realPage.toDouble() - idx.toDouble(); + } + } + + final double enlargeFactor = + options.enlargeFactor.clamp(0.0, 1.0); + final num distortionRatio = + (1 - (itemOffset.abs() * enlargeFactor)).clamp(0.0, 1.0); + distortionValue = + Curves.easeOut.transform(distortionRatio as double); + } + + final double height = widget.options.height ?? + MediaQuery.of(context).size.width * + (1 / widget.options.aspectRatio); + + if (widget.options.scrollDirection == Axis.horizontal) { + return getCenterWrapper(getEnlargeWrapper(child, + height: distortionValue * height, + scale: distortionValue, + itemOffset: itemOffset)); + } else { + return getCenterWrapper(getEnlargeWrapper(child, + width: distortionValue * MediaQuery.of(context).size.width, + scale: distortionValue, + itemOffset: itemOffset)); + } + }, + ); + }, + )); + } +} + +class _MultipleGestureRecognizer extends PanGestureRecognizer {} diff --git a/lib/Components/Carousel/carousel_state.dart b/lib/Components/Carousel/carousel_state.dart new file mode 100644 index 0000000..63ac533 --- /dev/null +++ b/lib/Components/Carousel/carousel_state.dart @@ -0,0 +1,43 @@ + +import 'package:flutter/material.dart'; + +import 'carousel_options.dart'; + +class CarouselState { + /// The [CarouselOptions] to create this state + CarouselOptions options; + + /// [pageController] is created using the properties passed to the constructor + /// and can be used to control the [PageView] it is passed to. + PageController? pageController; + + /// The actual index of the [PageView]. + /// + /// This value can be ignored unless you know the carousel will be scrolled + /// backwards more then 10000 pages. + /// Defaults to 10000 to simulate infinite backwards scrolling. + int realPage = 10000; + + /// The initial index of the [PageView] on [CarouselSlider] init. + /// + int initialPage = 0; + + /// The widgets count that should be shown at carousel + int? itemCount; + + /// Will be called when using pageController to go to next page or + /// previous page. It will clear the autoPlay timer. + /// Internal use only + Function onResetTimer; + + /// Will be called when using pageController to go to next page or + /// previous page. It will restart the autoPlay timer. + /// Internal use only + Function onResumeTimer; + + /// The callback to set the Reason Carousel changed + Function(CarouselPageChangedReason) changeMode; + + CarouselState( + this.options, this.onResetTimer, this.onResumeTimer, this.changeMode); +} \ No newline at end of file diff --git a/lib/Components/Carousel/utils.dart b/lib/Components/Carousel/utils.dart new file mode 100644 index 0000000..4f9ede3 --- /dev/null +++ b/lib/Components/Carousel/utils.dart @@ -0,0 +1,23 @@ +/// Converts an index of a set size to the corresponding index of a collection of another size +/// as if they were circular. +/// +/// Takes a [position] from collection Foo, a [base] from where Foo's index originated +/// and the [length] of a second collection Baa, for which the correlating index is sought. +/// +/// For example; We have a Carousel of 10000(simulating infinity) but only 6 images. +/// We need to repeat the images to give the illusion of a never ending stream. +/// By calling _getRealIndex with position and base we get an offset. +/// This offset modulo our length, 6, will return a number between 0 and 5, which represent the image +/// to be placed in the given position. +int getRealIndex(int position, int base, int? length) { + final int offset = position - base; + return remainder(offset, length); +} + +/// Returns the remainder of the modulo operation [input] % [source], and adjust it for +/// negative values. +int remainder(int input, int? source) { + if (source == 0) return 0; + final int result = input % source!; + return result < 0 ? source + result : result; +} \ No newline at end of file diff --git a/lib/Components/video_viewer.dart b/lib/Components/video_viewer.dart index 3992013..d5786b0 100644 --- a/lib/Components/video_viewer.dart +++ b/lib/Components/video_viewer.dart @@ -1,9 +1,9 @@ import 'dart:io'; -import 'package:cached_video_player/cached_video_player.dart'; +//import 'package:cached_video_player/cached_video_player.dart'; import 'package:flutter/material.dart'; import 'package:tablet_app/Components/loading_common.dart'; -//import 'package:video_player/video_player.dart'; +import 'package:video_player/video_player.dart'; import '../../constants.dart'; class VideoViewer extends StatefulWidget { @@ -16,19 +16,19 @@ class VideoViewer extends StatefulWidget { } class _VideoViewer extends State { - late CachedVideoPlayerController _controller; + late VideoPlayerController _controller; // Cached @override void initState() { super.initState(); if(widget.file != null) { - _controller = CachedVideoPlayerController.file(widget.file!) // Uri.parse() + _controller = VideoPlayerController.file(widget.file!) // Uri.parse() // Cached ..initialize().then((_) { // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. setState(() {}); }); } else { - _controller = CachedVideoPlayerController.network(widget.videoUrl) // Uri.parse() + _controller = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl)) // Uri.parse() ..initialize().then((_) { // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. setState(() {}); @@ -62,7 +62,7 @@ class _VideoViewer extends State { child: _controller.value.isInitialized ? AspectRatio( aspectRatio: _controller.value.aspectRatio, - child: CachedVideoPlayer(_controller), + child: VideoPlayer(_controller), ) : Center( child: Container( diff --git a/lib/Helpers/DeviceInfoHelper.dart b/lib/Helpers/DeviceInfoHelper.dart index 75e7133..febaf3f 100644 --- a/lib/Helpers/DeviceInfoHelper.dart +++ b/lib/Helpers/DeviceInfoHelper.dart @@ -1,6 +1,7 @@ import 'dart:io'; -import 'package:device_info/device_info.dart'; +//import 'package:device_info/device_info.dart'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/services.dart'; class DeviceInfoHelper { @@ -12,15 +13,11 @@ class DeviceInfoHelper { try { if (Platform.isAndroid) { var build = await deviceInfoPlugin.androidInfo; - /*deviceName = build.model; - deviceVersion = build.version.toString();*/ - identifier = build.androidId; //UUID for Android + identifier = build.id; //UUID for Android print(identifier); } else if (Platform.isIOS) { var data = await deviceInfoPlugin.iosInfo; - /*deviceName = data.name; - deviceVersion = data.systemVersion;*/ identifier = data.identifierForVendor; //UUID for iOS } } on PlatformException { diff --git a/lib/Screens/Agenda/event_popup.dart b/lib/Screens/Agenda/event_popup.dart index c6d2e28..592b5b5 100644 --- a/lib/Screens/Agenda/event_popup.dart +++ b/lib/Screens/Agenda/event_popup.dart @@ -76,7 +76,7 @@ class _EventPopupState extends State { coordinates: mapBox.Position( position.longitude, position.latitude, - )).toJson(), + )), // .toJson() iconSize: 1.3, iconOffset: [0.0, 0.0], symbolSortKey: 10, @@ -299,7 +299,7 @@ class _EventPopupState extends State { _onMapCreated(maBoxMap, widget.mapIcon); }, cameraOptions: mapBox.CameraOptions( - center: mapBox.Point(coordinates: mapBox.Position(double.parse(widget.eventAgenda.address!.lng!.toString()), double.parse(widget.eventAgenda.address!.lat!.toString()))).toJson(), + center: mapBox.Point(coordinates: mapBox.Position(double.parse(widget.eventAgenda.address!.lng!.toString()), double.parse(widget.eventAgenda.address!.lat!.toString()))), // .toJson() zoom: 14 ), ), diff --git a/lib/Screens/MainView/main_view.dart b/lib/Screens/MainView/main_view.dart index b447b9f..b05e9df 100644 --- a/lib/Screens/MainView/main_view.dart +++ b/lib/Screens/MainView/main_view.dart @@ -375,7 +375,7 @@ class _MainViewWidget extends State { flex: 2, child: Center( child: HtmlWidget( - sectionsLocal![index].title!.firstWhere((translation) => translation.language == appContext.getContext().language).value ?? "", + sectionsLocal![index].title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value ?? "", customStylesBuilder: (element) { return {'text-align': 'center', 'font-family': "Roboto"}; }, diff --git a/lib/Screens/MainView/section_page_detail.dart b/lib/Screens/MainView/section_page_detail.dart index b35a30a..bb2ef66 100644 --- a/lib/Screens/MainView/section_page_detail.dart +++ b/lib/Screens/MainView/section_page_detail.dart @@ -132,15 +132,15 @@ class _SectionPageDetailState extends State { Align( alignment: Alignment.center, child: HtmlWidget( - widget.sectionDTO.title!.firstWhere((translation) => translation.language == appContext.getContext().language).value!, + widget.sectionDTO.title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value ?? "", textStyle: new TextStyle(fontSize: kIsWeb ? kWebSectionTitleDetailSize : kSectionTitleDetailSize, color: widget.textColor, fontFamily: 'Roboto'), ) ), - if(widget.sectionDTO.description!.firstWhere((translation) => translation.language == appContext.getContext().language).value != null && widget.sectionDTO.description!.firstWhere((translation) => translation.language == appContext.getContext().language).value!.trim().isNotEmpty) + if(widget.sectionDTO.description!.where((translation) => translation.language == appContext.getContext().language).firstOrNull != null && widget.sectionDTO.description!.firstWhere((translation) => translation.language == appContext.getContext().language).value!.trim().isNotEmpty) Align( //alignment: Alignment.center, child: HtmlWidget( - widget.sectionDTO.description!.firstWhere((translation) => translation.language == appContext.getContext().language).value!, + widget.sectionDTO.description!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value ?? "", textStyle: new TextStyle(fontSize: kIsWeb? kWebSectionDescriptionDetailSize : kSectionDescriptionDetailSize, color: widget.textColor, fontFamily: 'Roboto'), ) ) @@ -157,6 +157,7 @@ class _SectionPageDetailState extends State { backgroundColor: kBackgroundColor, focusColor: kBackgroundColor, splashColor: kBackgroundColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(tabletAppContext.configuration!.roundedValue?.toDouble() ?? 30.0)), onPressed: () { Navigator.pop(context); /*Navigator.of(context).pushReplacement( diff --git a/lib/Screens/Map/geo_point_filter.dart b/lib/Screens/Map/geo_point_filter.dart index f7ec6b3..12c6d1a 100644 --- a/lib/Screens/Map/geo_point_filter.dart +++ b/lib/Screens/Map/geo_point_filter.dart @@ -74,52 +74,56 @@ class _GeoPointFilterState extends State { // Pour chaque point sans categorie, créer un noeud for(var pointWithoutCat in geoPoints.where((gp) => gp.categorieId == null && gp.categorie == null)) { - - TreeNode nodeWithoutCat = TreeNode( - id: 000 + int.parse( - (pointWithoutCat.latitude ?? '').substring(0, min(pointWithoutCat.latitude!.length, 10)).replaceAll(".", "") + (pointWithoutCat.longitude ?? '').substring(0, min(pointWithoutCat.longitude!.length, 10)).replaceAll(".", "") - ), - title: parse(pointWithoutCat.title!.firstWhere((l) => l.language == widget.language).value!).documentElement!.text, - children: [], - checked: true, // default true - show: false, - pid: 0, - commonID: 0, - ); - nodes.add(nodeWithoutCat); + if(pointWithoutCat.title!.where((l) => l.language == widget.language).firstOrNull != null) { + TreeNode nodeWithoutCat = TreeNode( + id: 000 + int.parse( + (pointWithoutCat.latitude ?? '').substring(0, min(pointWithoutCat.latitude!.length, 10)).replaceAll(".", "") + (pointWithoutCat.longitude ?? '').substring(0, min(pointWithoutCat.longitude!.length, 10)).replaceAll(".", "") + ), + title: parse(pointWithoutCat.title!.firstWhere((l) => l.language == widget.language).value!).documentElement!.text, + children: [], + checked: true, // default true + show: false, + pid: 0, + commonID: 0, + ); + nodes.add(nodeWithoutCat); + } } // Pour chaque catégorie, créez un nœud parent for (var category in categories) { - TreeNode categoryNode = TreeNode( - id: 100 + (category.id ?? 0), - title: parse(category.label!.firstWhere((l) => l.language == widget.language).value!).documentElement!.text, - children: [], - checked: true, // default true - show: false, - pid: 0, - commonID: 0, - ); + if(category.label!.where((l) => l.language == widget.language).firstOrNull != null) + { + TreeNode categoryNode = TreeNode( + id: 100 + (category.id ?? 0), + title: parse(category.label!.firstWhere((l) => l.language == widget.language).value!).documentElement!.text, + children: [], + checked: true, // default true + show: false, + pid: 0, + commonID: 0, + ); - // Ajoutez les géopoints correspondant à cette catégorie en tant qu'enfants du nœud parent - for (var geoPoint in geoPoints.where((gp) => gp.categorie != null || gp.categorieId != null)) { - if (geoPoint.categorieId == category.id) { - TreeNode geoPointNode = TreeNode( - id: 000 + int.parse( - (geoPoint.latitude ?? '').substring(0, min(geoPoint.latitude!.length, 10)).replaceAll(".", "") + (geoPoint.longitude ?? '').substring(0, min(geoPoint.longitude!.length, 10)).replaceAll(".", "") - ), - title: parse(geoPoint.title!.firstWhere((l) => l.language == widget.language).value!).documentElement!.text, - checked: true, // default true - show: false, - children: [], - pid: 0, - commonID: 0, - ); - categoryNode.children.add(geoPointNode); + // Ajoutez les géopoints correspondant à cette catégorie en tant qu'enfants du nœud parent + for (var geoPoint in geoPoints.where((gp) => gp.categorie != null || gp.categorieId != null)) { + if (geoPoint.categorieId == category.id && geoPoint.title!.where((l) => l.language == widget.language).firstOrNull != null) { + TreeNode geoPointNode = TreeNode( + id: 000 + int.parse( + (geoPoint.latitude ?? '').substring(0, min(geoPoint.latitude!.length, 10)).replaceAll(".", "") + (geoPoint.longitude ?? '').substring(0, min(geoPoint.longitude!.length, 10)).replaceAll(".", "") + ), + title: parse(geoPoint.title!.firstWhere((l) => l.language == widget.language).value!).documentElement!.text, + checked: true, // default true + show: false, + children: [], + pid: 0, + commonID: 0, + ); + categoryNode.children.add(geoPointNode); + } } - } - nodes.add(categoryNode); + nodes.add(categoryNode); + } } nodes.sort((a, b) => a.title.compareTo(b.title)); diff --git a/lib/Screens/Map/map_box_view.dart b/lib/Screens/Map/map_box_view.dart index abd7166..a00da71 100644 --- a/lib/Screens/Map/map_box_view.dart +++ b/lib/Screens/Map/map_box_view.dart @@ -60,10 +60,11 @@ class _MapBoxViewState extends State { int i = 0; markersList = []; pointsToShow!.forEach((point) { - var textSansHTML = parse(point.title!.firstWhere((translation) => translation.language == widget.language).value); - point.id = i; - point.title = point.title!.where((t) => t.language == widget.language).toList(); - /*var mapMarker = new MapMarker( + if(point.title!.where((translation) => translation.language == widget.language).firstOrNull != null) { + var textSansHTML = parse(point.title!.firstWhere((translation) => translation.language == widget.language).value); + point.id = i; + point.title = point.title!.where((t) => t.language == widget.language).toList(); + /*var mapMarker = new MapMarker( id: i, title: parse(textSansHTML.body!.text).documentElement!.text, description: point.description!.firstWhere((translation) => translation.language == widget.language).value, @@ -71,24 +72,25 @@ class _MapBoxViewState extends State { latitude: point.latitude, contents: point.contents );*/ - markersList.add(point); - options.add(mapBox.PointAnnotationOptions( - geometry: mapBox.Point( - coordinates: mapBox.Position( - double.tryParse(point.longitude!)!, - double.tryParse(point.latitude!)!, - )).toJson(), - iconSize: 1.3, - textField: "${parse(textSansHTML.body!.text).documentElement!.text}${point.latitude}${point.longitude}", - textOpacity: 0.0, - iconOffset: [0.0, 0.0], - symbolSortKey: 10, - iconColor: 0, - iconImage: null, - image: point.categorie == null ? widget.icons.where((i) => i['id'] == null).first['icon'] : widget.icons.any((i) => i['id'] == point.categorieId) ? widget.icons.where((i) => i['id'] == point.categorieId).first['icon'] : widget.icons.where((i) => i['id'] == null).first['icon'], //widget.selectedMarkerIcon, - )); // , + markersList.add(point); + options.add(mapBox.PointAnnotationOptions( + geometry: mapBox.Point( + coordinates: mapBox.Position( + double.tryParse(point.longitude!)!, + double.tryParse(point.latitude!)!, + )), // .toJson() + iconSize: 1.3, + textField: "${parse(textSansHTML.body!.text).documentElement!.text}${point.latitude}${point.longitude}", + textOpacity: 0.0, + iconOffset: [0.0, 0.0], + symbolSortKey: 10, + iconColor: 0, + iconImage: null, + image: point.categorie == null ? widget.icons.where((i) => i['id'] == null).first['icon'] : widget.icons.any((i) => i['id'] == point.categorieId) ? widget.icons.where((i) => i['id'] == point.categorieId).first['icon'] : widget.icons.where((i) => i['id'] == null).first['icon'], //widget.selectedMarkerIcon, + )); // , - i++; + i++; + } }); print(options.length); @@ -117,7 +119,8 @@ class _MapBoxViewState extends State { @override void initState() { pointsToShow = widget.geoPoints;//widget.mapDTO!.points; - selectedCategories = widget.mapDTO!.categories!.map((categorie) => categorie.label!.firstWhere((element) => element.language == widget.language).value!).toList(); + var nonNullCat = widget.mapDTO!.categories!.where((c) => c.label!.where((element) => element.language == widget.language).firstOrNull != null); + selectedCategories = nonNullCat.map((categorie) => categorie.label!.firstWhere((element) => element.language == widget.language).value!).toList(); super.initState(); } @@ -183,7 +186,7 @@ class _MapBoxViewState extends State { mapContext.setSelectedPointForNavigate(null); }, cameraOptions: mapBox.CameraOptions( - center: mapBox.Point(coordinates: widget.mapDTO!.longitude != null && widget.mapDTO!.latitude != null ? mapBox.Position(double.tryParse(widget.mapDTO!.longitude!)!, double.tryParse(widget.mapDTO!.latitude!)!) : mapBox.Position(4.865105, 50.465503)).toJson(), + center: mapBox.Point(coordinates: widget.mapDTO!.longitude != null && widget.mapDTO!.latitude != null ? mapBox.Position(double.tryParse(widget.mapDTO!.longitude!)!, double.tryParse(widget.mapDTO!.latitude!)!) : mapBox.Position(4.865105, 50.465503)), //.toJson() zoom: widget.mapDTO!.zoom != null ? widget.mapDTO!.zoom!.toDouble() : 12), ) ), @@ -197,7 +200,7 @@ class _MapBoxViewState extends State { print("COUCOU IL FAUT NAVUGATE MAPBOX"); mapboxMap?.easeTo( mapBox.CameraOptions( - center: mapBox.Point(coordinates: mapBox.Position(double.tryParse(geoPoint.longitude!)!, double.tryParse(geoPoint.latitude!)!)).toJson(), + center: mapBox.Point(coordinates: mapBox.Position(double.tryParse(geoPoint.longitude!)!, double.tryParse(geoPoint.latitude!)!)), //.toJson() zoom: 16, bearing: 0, pitch: 3), diff --git a/lib/Screens/Map/marker_view.dart b/lib/Screens/Map/marker_view.dart index 973d2c5..6380a82 100644 --- a/lib/Screens/Map/marker_view.dart +++ b/lib/Screens/Map/marker_view.dart @@ -1,6 +1,5 @@ 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/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -9,6 +8,7 @@ import 'package:manager_api/api.dart'; import 'package:photo_view/photo_view.dart'; import 'package:provider/provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; +import 'package:tablet_app/Components/Carousel/carousel_slider.dart' as cs; import 'package:tablet_app/Components/loading_common.dart'; import 'package:tablet_app/Components/show_element_for_resource.dart'; import 'package:tablet_app/Helpers/ImageCustomProvider.dart'; @@ -29,12 +29,12 @@ class MarkerViewWidget extends StatefulWidget { } class _MarkerInfoWidget extends State { - CarouselController? sliderController; + cs.CarouselController? sliderController; ValueNotifier currentIndex = ValueNotifier(1); @override void initState() { - sliderController = CarouselController(); + sliderController = cs.CarouselController(); super.initState(); } @@ -53,6 +53,8 @@ class _MarkerInfoWidget extends State { var language = tabletAppContext.language; GeoPointDTO? selectedPoint = mapContext.getSelectedPoint() as GeoPointDTO?; + var isPointPrice = selectedPoint != null && selectedPoint.prices != null && selectedPoint.prices!.isNotEmpty && selectedPoint.prices!.any((d) => d.language == language) && selectedPoint.prices!.firstWhere((d) => d.language == language).value != null && selectedPoint.prices!.firstWhere((d) => d.language == language).value!.trim().length > 0; + Color primaryColor = new Color(int.parse(tabletAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)); Size sizeMarker = Size(size.width * 0.9, size.height * 0.8); @@ -163,39 +165,81 @@ class _MarkerInfoWidget extends State { children: [ Padding( padding: const EdgeInsets.only(left: 15), - child: Container( - height: size.height * 0.7, - width: size.width * 0.38, - decoration: BoxDecoration( - color: kBackgroundLight, - borderRadius: BorderRadius.all(Radius.circular(tabletAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)) - ), - child: Padding( - padding: const EdgeInsets.only(left: 20, right: 10, bottom: 10, top: 15), - child: Scrollbar( - thumbVisibility: true, - thickness: 2.0, - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - if(selectedPoint.description!.any((d) => d.language == language) && selectedPoint.description!.firstWhere((d) => d.language == language).value != null && selectedPoint.description!.firstWhere((d) => d.language == language).value!.trim().length > 0) - HtmlWidget( - selectedPoint.description!.firstWhere((d) => d.language == language).value!, - customStylesBuilder: (element) { - return {'text-align': 'left', 'font-family': "Roboto"}; - }, - textStyle: TextStyle(fontSize: kDescriptionSize), - ), - ], + child: Column( + children: [ + Container( + height: isPointPrice ? size.height * 0.45 : size.height * 0.7, + width: size.width * 0.38, + decoration: BoxDecoration( + color: kBackgroundLight, + borderRadius: BorderRadius.all(Radius.circular(tabletAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)) + ), + child: Padding( + padding: const EdgeInsets.only(left: 20, right: 10, bottom: 10, top: 15), + child: Scrollbar( + thumbVisibility: true, + thickness: 2.0, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if(selectedPoint.description!.any((d) => d.language == language) && selectedPoint.description!.firstWhere((d) => d.language == language).value != null && selectedPoint.description!.firstWhere((d) => d.language == language).value!.trim().length > 0) + HtmlWidget( + selectedPoint.description!.firstWhere((d) => d.language == language).value!, + customStylesBuilder: (element) { + return {'text-align': 'left', 'font-family': "Roboto"}; + }, + textStyle: TextStyle(fontSize: kDescriptionSize), + ), + ], + ), + ), ), ), ), ), - ), + if(isPointPrice) + Container( + height: size.height * 0.25, + width: size.width * 0.38, + decoration: BoxDecoration( + color: kBackgroundLight, + borderRadius: BorderRadius.all(Radius.circular(tabletAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)) + ), + child: Padding( + padding: const EdgeInsets.only(left: 20, right: 10, bottom: 10, top: 15), + child: Scrollbar( + thumbVisibility: true, + thickness: 2.0, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + //crossAxisAlignment: CrossAxisAlignment.center, + //mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Center(child: Padding( + padding: const EdgeInsets.all(5.0), + child: Icon(Icons.price_change_outlined, color: primaryColor, size: 25), + )), + HtmlWidget( + selectedPoint.prices!.firstWhere((d) => d.language == language).value!+"sdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsd sdkljsdkj sdklj sldkj klsdj klsdj klsd", + customStylesBuilder: (element) { + return {'text-align': 'left', 'font-family': "Roboto"}; + }, + textStyle: TextStyle(fontSize: kDescriptionSize), + ), + ], + ), + ), + ), + ), + ), + ), + ], ), ), SizedBox( @@ -209,10 +253,10 @@ class _MarkerInfoWidget extends State { Container( //color: Colors.green, height: size.height * 0.35, - child: CarouselSlider( + child: cs.CarouselSlider( carouselController: sliderController, - options: CarouselOptions( - onPageChanged: (int index, CarouselPageChangedReason reason) { + options: cs.CarouselOptions( + onPageChanged: (int index, cs.CarouselPageChangedReason reason) { currentIndex.value = index + 1; }, height: size.height *0.33, @@ -238,16 +282,6 @@ class _MarkerInfoWidget extends State { padding: const EdgeInsets.only(top: 10.0), child: Column( children: [ - selectedPoint.prices != null && selectedPoint.prices!.isNotEmpty && selectedPoint.prices!.any((d) => d.language == language) && selectedPoint.prices!.firstWhere((d) => d.language == language).value != null && selectedPoint.prices!.firstWhere((d) => d.language == language).value!.trim().length > 0 ? Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Icon(Icons.price_change_outlined, color: primaryColor, size: 13), - Padding( - padding: const EdgeInsets.all(4.0), - child: Text(parse(selectedPoint.prices!.firstWhere((p) => p.language == language).value!).documentElement!.text, style: TextStyle(fontSize: 12)), - ) - ], - ): SizedBox(), selectedPoint.phone != null && selectedPoint.phone!.isNotEmpty && selectedPoint.phone!.any((d) => d.language == language) && selectedPoint.phone!.firstWhere((d) => d.language == language).value != null && selectedPoint.phone!.firstWhere((d) => d.language == language).value!.trim().length > 0 ? Row( mainAxisAlignment: MainAxisAlignment.start, children: [ diff --git a/lib/Screens/Menu/menu_view.dart b/lib/Screens/Menu/menu_view.dart index af4e983..9f0f7c3 100644 --- a/lib/Screens/Menu/menu_view.dart +++ b/lib/Screens/Menu/menu_view.dart @@ -98,7 +98,7 @@ class _MenuView extends State { Align( alignment: Alignment.centerRight, child: HtmlWidget( - menuDTO.sections![index].title!.firstWhere((translation) => translation.language == appContext.getContext().language).value!, + menuDTO.sections![index].title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value ?? "", customStylesBuilder: (element) { return {'text-align': 'right', 'font-family': "Roboto"}; }, @@ -142,7 +142,7 @@ class _MenuView extends State { ), child: Center( child: HtmlWidget( - menuDTO.sections![index].title!.firstWhere((translation) => translation.language == appContext.getContext().language).value ?? "", + menuDTO.sections![index].title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value ?? "", customStylesBuilder: (element) { return {'text-align': 'center', 'font-family': "Roboto"}; }, diff --git a/lib/Screens/Puzzle/puzzle_view.dart b/lib/Screens/Puzzle/puzzle_view.dart index bef436d..708ec7d 100644 --- a/lib/Screens/Puzzle/puzzle_view.dart +++ b/lib/Screens/Puzzle/puzzle_view.dart @@ -47,7 +47,7 @@ class _PuzzleView extends State { TabletAppContext tabletAppContext = appContext.getContext(); print(puzzleDTO.messageDebut); - TranslationAndResourceDTO? messageDebut = puzzleDTO.messageDebut != null && puzzleDTO.messageDebut!.length > 0 ? puzzleDTO.messageDebut!.firstWhere((message) => message.language!.toUpperCase() == tabletAppContext.language!.toUpperCase()) : null; + TranslationAndResourceDTO? messageDebut = puzzleDTO.messageDebut != null && puzzleDTO.messageDebut!.length > 0 ? puzzleDTO.messageDebut!.where((message) => message.language!.toUpperCase() == tabletAppContext.language!.toUpperCase()).firstOrNull : null; if(messageDebut != null) { showMessage(messageDebut, appContext, context, size); @@ -191,7 +191,7 @@ class _PuzzleView extends State { Size size = MediaQuery.of(context).size; final appContext = Provider.of(context, listen: false); TabletAppContext tabletAppContext = appContext.getContext(); - TranslationAndResourceDTO? messageFin = puzzleDTO.messageFin != null && puzzleDTO.messageFin!.length > 0 ? puzzleDTO.messageFin!.firstWhere((message) => message.language!.toUpperCase() == tabletAppContext.language!.toUpperCase()) : null; + TranslationAndResourceDTO? messageFin = puzzleDTO.messageFin != null && puzzleDTO.messageFin!.length > 0 ? puzzleDTO.messageFin!.where((message) => message.language!.toUpperCase() == tabletAppContext.language!.toUpperCase()).firstOrNull : null; if(messageFin != null) { showMessage(messageFin, appContext, context, size); diff --git a/lib/Screens/Quizz/quizz_view.dart b/lib/Screens/Quizz/quizz_view.dart index b088582..ee786cd 100644 --- a/lib/Screens/Quizz/quizz_view.dart +++ b/lib/Screens/Quizz/quizz_view.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'dart:developer'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:carousel_slider/carousel_slider.dart'; import 'package:confetti/confetti.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -13,6 +12,7 @@ import 'package:manager_api/api.dart'; import 'package:photo_view/photo_view.dart'; import 'package:provider/provider.dart'; import 'package:tablet_app/Components/Buttons/rounded_button.dart'; +import 'package:tablet_app/Components/Carousel/carousel_slider.dart' as cs; import 'package:tablet_app/Components/show_element_for_resource.dart'; import 'package:tablet_app/Helpers/ImageCustomProvider.dart'; import 'package:tablet_app/Helpers/translationHelper.dart'; @@ -39,7 +39,7 @@ class _QuizzView extends State { ConfettiController? _controllerCenter; QuizzDTO quizzDTO = QuizzDTO(); List _questionsSubDTO = []; - CarouselController? sliderController; + cs.CarouselController? sliderController; int currentIndex = 1; bool showResult = false; bool showResponses = false; @@ -50,7 +50,7 @@ class _QuizzView extends State { _controllerCenter = ConfettiController(duration: const Duration(seconds: 10)); - sliderController = CarouselController(); + sliderController = cs.CarouselController(); quizzDTO = QuizzDTO.fromJson(jsonDecode(widget.section!.data!))!; @@ -264,10 +264,10 @@ class _QuizzView extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ if(_questionsSubDTO.length > 0) - CarouselSlider( + cs.CarouselSlider( carouselController: sliderController, - options: CarouselOptions( - onPageChanged: (int index, CarouselPageChangedReason reason) { + options: cs.CarouselOptions( + onPageChanged: (int index, cs.CarouselPageChangedReason reason) { setState(() { currentIndex = index + 1; }); @@ -338,7 +338,7 @@ class _QuizzView extends State { //height: size.height * 0.2, child: Row( children: [ - if(i.label!.firstWhere((translation) => translation.language == appContext.getContext().language).resourceId != null) + if(i.label!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.resourceId != null) Container( //height: size.height * 0.2, //width: size.width * 0.25, @@ -355,11 +355,11 @@ class _QuizzView extends State { SingleChildScrollView( child: Container( //color: Colors.green, - width: i.label!.firstWhere((translation) => translation.language == appContext.getContext().language).resourceId == null ? size.width * 0.65 : size.width * 0.5, + width: i.label!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.resourceId == null ? size.width * 0.65 : size.width * 0.5, child: Padding( padding: const EdgeInsets.all(10.0), child: HtmlWidget( - i.label!.firstWhere((translation) => translation.language == appContext.getContext().language).value != null ? i.label!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", + i.label!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value != null ? i.label!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", textStyle: TextStyle(fontSize: kIsWeb ? kWebDescriptionSize : kDescriptionSize), customStylesBuilder: (element) { return {'text-align': 'center', 'font-family': "Roboto"}; @@ -416,9 +416,9 @@ class _QuizzView extends State { child: Container( alignment: Alignment.center, child: Row(// just to use if else - mainAxisAlignment: i.responsesSubDTO![index].label!.firstWhere((translation) => translation.language == appContext.getContext().language).resourceId == null ? MainAxisAlignment.center : MainAxisAlignment.start, + mainAxisAlignment: i.responsesSubDTO![index].label!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.resourceId == null ? MainAxisAlignment.center : MainAxisAlignment.start, children: [ - if(i.responsesSubDTO![index].label!.firstWhere((translation) => translation.language == appContext.getContext().language).resourceId != null) + if(i.responsesSubDTO![index].label!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.resourceId != null) Container( //height: size.height * 0.2, //width: size.width * 0.25, @@ -434,10 +434,10 @@ class _QuizzView extends State { ), SingleChildScrollView( child: Container( - width: i.responsesSubDTO![index].label!.firstWhere((translation) => translation.language == appContext.getContext().language).resourceId == null ? size.width * 0.3 : size.width * 0.13, + width: i.responsesSubDTO![index].label!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.resourceId == null ? size.width * 0.3 : size.width * 0.13, //color: Colors.yellow, child: HtmlWidget( - i.responsesSubDTO![index].label!.firstWhere((translation) => translation.language == appContext.getContext().language).value != null ? i.responsesSubDTO![index].label!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", + i.responsesSubDTO![index].label!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value != null ? i.responsesSubDTO![index].label!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", textStyle: TextStyle(fontSize: kIsWeb ? kWebDescriptionSize : kDescriptionSize, color: i.chosen == index ? Colors.white : Colors.black), customStylesBuilder: (element) { return {'text-align': 'center', 'font-family': "Roboto"}; diff --git a/lib/Screens/Quizz/showResponses.dart b/lib/Screens/Quizz/showResponses.dart index cb6cea9..abae3c8 100644 --- a/lib/Screens/Quizz/showResponses.dart +++ b/lib/Screens/Quizz/showResponses.dart @@ -1,13 +1,13 @@ import 'dart:convert'; import 'dart:developer'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:manager_api/api.dart'; import 'package:provider/provider.dart'; +import 'package:tablet_app/Components/Carousel/carousel_slider.dart' as cs; import 'package:tablet_app/Helpers/ImageCustomProvider.dart'; import 'package:tablet_app/Models/ResponseSubDTO.dart'; import 'package:tablet_app/Models/tabletContext.dart'; @@ -26,13 +26,13 @@ class ShowReponsesWidget extends StatefulWidget { class _ShowReponsesWidget extends State { List _questionsSubDTO = []; - CarouselController? sliderController; + cs.CarouselController? sliderController; int currentIndex = 1; @override void initState() { super.initState(); - sliderController = CarouselController(); + sliderController = cs.CarouselController(); _questionsSubDTO = widget.questionsSubDTO!; } @@ -56,10 +56,10 @@ class _ShowReponsesWidget extends State { return Stack( children: [ if(_questionsSubDTO != null && _questionsSubDTO.length > 0) - CarouselSlider( + cs.CarouselSlider( carouselController: sliderController, - options: CarouselOptions( - onPageChanged: (int index, CarouselPageChangedReason reason) { + options: cs.CarouselOptions( + onPageChanged: (int index, cs.CarouselPageChangedReason reason) { setState(() { currentIndex = index + 1; }); diff --git a/lib/Screens/Slider/slider_view.dart b/lib/Screens/Slider/slider_view.dart index 6a3fa2c..41bbf0c 100644 --- a/lib/Screens/Slider/slider_view.dart +++ b/lib/Screens/Slider/slider_view.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -9,6 +8,7 @@ import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:manager_api/api.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; +import 'package:tablet_app/Components/Carousel/carousel_slider.dart' as cs; import 'package:tablet_app/Components/show_element_for_resource.dart'; import 'package:tablet_app/Components/video_viewer.dart'; import 'package:tablet_app/Helpers/ImageCustomProvider.dart'; @@ -27,14 +27,14 @@ class SliderView extends StatefulWidget { class _SliderView extends State { SliderDTO sliderDTO = SliderDTO(); - CarouselController? sliderController; + cs.CarouselController? sliderController; ValueNotifier currentIndex = ValueNotifier(1); late ConfigurationDTO configurationDTO; @override void initState() { - sliderController = CarouselController(); + sliderController = cs.CarouselController(); sliderDTO = SliderDTO.fromJson(jsonDecode(widget.section!.data!))!; sliderDTO.contents!.sort((a, b) => a.order!.compareTo(b.order!)); @@ -60,10 +60,10 @@ class _SliderView extends State { return Stack( children: [ if(sliderDTO.contents != null && sliderDTO.contents!.length > 0) - CarouselSlider( + cs.CarouselSlider( carouselController: sliderController, - options: CarouselOptions( - onPageChanged: (int index, CarouselPageChangedReason reason) { + options: cs.CarouselOptions( + onPageChanged: (int index, cs.CarouselPageChangedReason reason) { currentIndex.value = index + 1; }, height: MediaQuery.of(context).size.height * 0.8, @@ -119,7 +119,7 @@ class _SliderView extends State { child: Padding( padding: const EdgeInsets.all(15.0), child: HtmlWidget( - i.title!.firstWhere((translation) => translation.language == appContext.getContext().language).value != null ? i.title!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", + i.title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value != null ? i.title!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", textStyle: TextStyle(fontSize: kIsWeb ? kWebTitleSize : kTitleSize, color: kBackgroundLight), ), ) @@ -151,7 +151,7 @@ class _SliderView extends State { child: Padding( padding: const EdgeInsets.all(15.0), child: HtmlWidget( - i.description!.firstWhere((translation) => translation.language == appContext.getContext().language).value != null ? i.description!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", + i.description!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value != null ? i.description!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", textStyle: TextStyle(fontSize: kIsWeb ? kWebDescriptionSize : kDescriptionSize), customStylesBuilder: (element) { return {'text-align': 'center', 'font-family': "Roboto"}; diff --git a/lib/main.dart b/lib/main.dart index 7750afe..545d5ee 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -44,11 +44,11 @@ void main() async { print(localContext.instanceId); // Get config from manager DeviceDetailDTO? device = await localContext.clientAPI!.deviceApi!.deviceGetDetail(localContext.deviceId!); - localContext.configuration!.id = device!.configurationId; - localContext.instanceId = device.instanceId; + localContext.configuration!.id = device!.configurationId;//device!.configurationId; //Hardcoded for VisitNamur 65c5f0ee4c030e63ce16bff5 // DEV 65859c77d97d1b93ce301e91 + localContext.instanceId = device.instanceId;// device.instanceId; //Hardcoded for VisitNamur 65c5e576ad331aa079caf0a4 // DEV 63514fd67ed8c735aaa4b8f2 localContext.localPath = localPath; - if (device.configurationId == null) { + if (device.configurationId == null) { print("device.configurationId == null"); localContext.configuration = null; isConfig = false; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b416da9..018e127 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,25 +6,33 @@ import FlutterMacOS import Foundation import audio_session +import device_info_plus import firebase_core import firebase_storage +import flutter_inappwebview_macos import just_audio import package_info +import package_info_plus import path_provider_foundation import shared_preferences_foundation import sqflite import url_launcher_macos -import wakelock_macos +import video_player_avfoundation +import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin")) + InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) } diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index d2f3d74..6523310 100644 --- a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -3,8 +3,8 @@ FLUTTER_ROOT=C:\PROJ\flutter FLUTTER_APPLICATION_PATH=C:\Users\ThomasFransolet\Documents\Documents\Perso\MuseeDeLaFraise\tablet-app COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=2.0.6 -FLUTTER_BUILD_NUMBER=12 +FLUTTER_BUILD_NAME=2.0.7 +FLUTTER_BUILD_NUMBER=15 DART_OBFUSCATION=false TRACK_WIDGET_CREATION=true TREE_SHAKE_ICONS=false diff --git a/macos/Flutter/ephemeral/flutter_export_environment.sh b/macos/Flutter/ephemeral/flutter_export_environment.sh index dfd6748..dcfadd9 100644 --- a/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -4,8 +4,8 @@ export "FLUTTER_ROOT=C:\PROJ\flutter" export "FLUTTER_APPLICATION_PATH=C:\Users\ThomasFransolet\Documents\Documents\Perso\MuseeDeLaFraise\tablet-app" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=2.0.6" -export "FLUTTER_BUILD_NUMBER=12" +export "FLUTTER_BUILD_NAME=2.0.7" +export "FLUTTER_BUILD_NUMBER=15" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" diff --git a/pubspec.yaml b/pubspec.yaml index 53984ef..625d0e9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,13 +28,13 @@ dependencies: sqflite: # Specific mobile and macOS webview_flutter: ^4.4.1 # Specific mobile # old : ^3.0.4 google_maps_flutter: ^2.5.3 # Specific mobile - youtube_player_flutter: ^8.1.2 # Specific mobile + youtube_player_flutter: ^9.0.1 # Specific mobile # Specific Web google_maps_flutter_web: ^0.5.4+3 # Specific WEB - youtube_player_iframe: ^4.0.4 # Handle mobile and web here => TO TEST + youtube_player_iframe: ^5.1.2 # Handle mobile and web here => TO TEST - mapbox_maps_flutter: ^1.0.0 + mapbox_maps_flutter: ^2.0.0 ota_update: ^6.0.0 package_info: ^2.0.2 @@ -46,35 +46,35 @@ dependencies: http: ^1.2.0 auto_size_text: ^3.0.0 fluttertoast: - device_info: ^2.0.2 # DISCONTINUED - #device_info_plus: ^10.0.1 # chewie version casse couille, retourne à la 1.0.0 qui fout la merde + device_info_plus: ^10.1.0 enum_to_string: ^2.0.1 - carousel_slider: ^4.2.1 mqtt_client: ^10.0.0 - photo_view: ^0.14.0 + photo_view: ^0.15.0 confetti: ^0.7.0 flutter_launcher_icons: ^0.13.1 # All but web #flutter_svg_provider: ^1.0.6 - flutter_widget_from_html: ^0.14.10+1 + flutter_widget_from_html: ^0.15.1 flutter_pdfview: ^1.3.2 - firebase_storage: ^11.7.3 - firebase_core: ^2.30.1 - #video_player: ^2.8.1 - cached_video_player: ^2.0.4 + firebase_storage: ^12.0.1 + firebase_core: ^3.1.0 + video_player: ^2.8.7 + #cached_video_player: ^2.0.4 cached_network_image: ^3.3.1 just_audio_cache: ^0.1.2 - path_provider: ^2.1.2 + #path_provider: ^2.1.2 permission_handler: ^11.2.0 google_fonts: ^6.2.1 #animated_tree_view: ^2.2.0 generate_tree: ^2.2.2 - openapi_generator_cli: ^4.13.1 - openapi_generator: ^4.13.1 - openapi_generator_annotations: ^4.13.1 + openapi_generator_cli: ^5.0.2 + openapi_generator: ^5.0.2 + openapi_generator_annotations: ^5.0.2 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 + #win32: ^4.1.2 + #archive: ^3.6.1 manager_api: path: manager_api @@ -82,7 +82,7 @@ dev_dependencies: flutter_test: sdk: flutter build_runner: - openapi_generator: ^4.13.1 + openapi_generator: ^5.0.2 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec