From 74edd90669c0a40dd4f4cb781466a47c233c9ed9 Mon Sep 17 00:00:00 2001 From: CTalvio Date: Wed, 18 Dec 2024 22:21:10 +0200 Subject: [PATCH] update new image fix stuff --- lib/shared/image_viewer.dart | 619 +++++++++++++++++++---------------- 1 file changed, 335 insertions(+), 284 deletions(-) diff --git a/lib/shared/image_viewer.dart b/lib/shared/image_viewer.dart index c15ae28b0..9adb9b07c 100644 --- a/lib/shared/image_viewer.dart +++ b/lib/shared/image_viewer.dart @@ -219,312 +219,363 @@ class _ImageViewerState extends State with TickerProviderStateMixin } : null, child: Listener( - // Start watching for double tap zoom - onPointerDown: (details) { - downCoord = details.position; - }, - onPointerUp: (details) { - delta = (downCoord - details.position).distance; - if (!slideZooming && delta < 0.5) { - _maybeSlide(context); - } - }, - child: ExtendedImageSlidePage( - key: slidePagekey, - slideAxis: SlideAxis.both, - slideType: SlideType.onlyImage, - slidePageBackgroundHandler: (offset, pageSize) { - return Colors.transparent; - }, - onSlidingPage: (state) { - // Fade out image and background when sliding to dismiss - var offset = state.offset; - var pageSize = state.pageSize; - - var scale = offset.distance / Offset(pageSize.width, pageSize.height).distance; - - if (state.isSliding) { - setState(() { - slideTransparency = 0.9 - min(0.9, scale * 0.5); - imageTransparency = 1.0 - min(1.0, scale * 10); - }); - } - }, - slideEndHandler: ( - // Decrease slide to dismiss threshold so it can be done easier - Offset offset, { - ExtendedImageSlidePageState? state, - ScaleEndDetails? details, - }) { - if (state != null) { - var offset = state.offset; - var pageSize = state.pageSize; - return offset.distance.greaterThan(Offset(pageSize.width, pageSize.height).distance / 10); - } - return true; - }, - child: widget.url != null - ? ExtendedImage.network( - widget.url!, - color: Colors.white.withOpacity(imageTransparency), - colorBlendMode: BlendMode.dstIn, - enableSlideOutPage: true, - mode: ExtendedImageMode.gesture, - extendedImageGestureKey: gestureKey, - cache: true, - clearMemoryCacheWhenDispose: thunderState.imageCachingMode == ImageCachingMode.relaxed, - layoutInsets: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 50, top: MediaQuery.of(context).padding.top + 50), - initGestureConfigHandler: (ExtendedImageState state) { - return GestureConfig( - minScale: 0.8, - animationMinScale: 0.8, - maxScale: maxZoomLevel.toDouble(), - animationMaxScale: maxZoomLevel.toDouble(), - speed: 1.0, - inertialSpeed: 250.0, - initialScale: 1.0, - inPageView: false, - initialAlignment: InitialAlignment.center, - reverseMousePointerScrollDirection: true, - gestureDetailsIsChanged: (GestureDetails? details) {}, - ); - }, - onDoubleTap: (ExtendedImageGestureState state) { - var pointerDownPosition = state.pointerDownPosition; - double begin = state.gestureDetails!.totalScale!; - double end; - - animation?.removeListener(animationListener); - animationController.stop(); - animationController.reset(); - - if (begin == 1) { - end = 2; - } else if (begin > 1.99 && begin < 2.01) { - end = 4; - } else { - end = 1; - } - animationListener = () { - state.handleDoubleTap(scale: animation!.value, doubleTapPosition: pointerDownPosition); - }; - animation = animationController.drive(Tween(begin: begin, end: end)); - - animation!.addListener(animationListener); - - animationController.forward(); - }, - loadStateChanged: (state) { - if (state.extendedImageLoadState == LoadState.loading) { - return Center( - child: CircularProgressIndicator( - color: Colors.white.withOpacity(0.90), - ), - ); - } - return null; - }, - ) - : ExtendedImage.memory( - widget.bytes!, - color: Colors.white.withOpacity(imageTransparency), - colorBlendMode: BlendMode.dstIn, - enableSlideOutPage: true, - mode: ExtendedImageMode.gesture, - extendedImageGestureKey: gestureKey, - clearMemoryCacheWhenDispose: true, - initGestureConfigHandler: (ExtendedImageState state) { - return GestureConfig( - minScale: 0.8, - animationMinScale: 0.8, - maxScale: 4.0, - animationMaxScale: 4.0, - speed: 1.0, - inertialSpeed: 250.0, - initialScale: 1.0, - inPageView: false, - initialAlignment: InitialAlignment.center, - reverseMousePointerScrollDirection: true, - gestureDetailsIsChanged: (GestureDetails? details) {}, - ); - }, - onDoubleTap: (ExtendedImageGestureState state) { - var pointerDownPosition = state.pointerDownPosition; - double begin = state.gestureDetails!.totalScale!; - double end; - - animation?.removeListener(animationListener); - animationController.stop(); - animationController.reset(); - - if (begin == 1) { - end = 2; - } else if (begin > 1.99 && begin < 2.01) { - end = 4; - } else { - end = 1; - } - animationListener = () { - state.handleDoubleTap(scale: animation!.value, doubleTapPosition: pointerDownPosition); - }; - animation = animationController.drive(Tween(begin: begin, end: end)); - - animation!.addListener(animationListener); - - animationController.forward(); - }, - loadStateChanged: (state) { - if (state.extendedImageLoadState == LoadState.loading) { - return Center( - child: CircularProgressIndicator( - color: Colors.white.withOpacity(0.90), - ), - ); - } - return null; - }, - ), + // Start watching for double tap zoom + onPointerDown: (details) { + downCoord = details.position; + }, + onPointerUp: (details) { + delta = (downCoord - details.position).distance; + if (!slideZooming && delta < 0.5) { + _maybeSlide(context); + } + }, + child: ExtendedImageSlidePage( + key: slidePagekey, + slideAxis: SlideAxis.both, + slideType: SlideType.onlyImage, + slidePageBackgroundHandler: (offset, pageSize) { + return Colors.transparent; + }, + onSlidingPage: (state) { + // Fade out image and background when sliding to dismiss + var offset = state.offset; + var pageSize = state.pageSize; + + var scale = offset.distance / Offset(pageSize.width, pageSize.height).distance; + + if (state.isSliding) { + setState(() { + slideTransparency = 0.9 - min(0.9, scale * 0.5); + imageTransparency = 1.0 - min(1.0, scale * 10); + }); + } + }, + slideEndHandler: ( + // Decrease slide to dismiss threshold so it can be done easier + Offset offset, { + ExtendedImageSlidePageState? state, + ScaleEndDetails? details, + }) { + if (state != null) { + var offset = state.offset; + var pageSize = state.pageSize; + return offset.distance.greaterThan(Offset(pageSize.width, pageSize.height).distance / 10); + } + return true; + }, + child: widget.url != null + ? ExtendedImage.network( + widget.url!, + color: Colors.white.withOpacity(imageTransparency), + colorBlendMode: BlendMode.dstIn, + enableSlideOutPage: true, + mode: ExtendedImageMode.gesture, + extendedImageGestureKey: gestureKey, + cache: true, + clearMemoryCacheWhenDispose: thunderState.imageCachingMode == ImageCachingMode.relaxed, + layoutInsets: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 50, top: MediaQuery.of(context).padding.top + 50), + initGestureConfigHandler: (ExtendedImageState state) { + return GestureConfig( + minScale: 0.8, + animationMinScale: 0.8, + maxScale: maxZoomLevel.toDouble(), + animationMaxScale: maxZoomLevel.toDouble(), + speed: 1.0, + inertialSpeed: 250.0, + initialScale: 1.0, + inPageView: false, + initialAlignment: InitialAlignment.center, + reverseMousePointerScrollDirection: true, + gestureDetailsIsChanged: (GestureDetails? details) {}, + ); + }, + onDoubleTap: (ExtendedImageGestureState state) { + var pointerDownPosition = state.pointerDownPosition; + double begin = state.gestureDetails!.totalScale!; + double end; + + animation?.removeListener(animationListener); + animationController.stop(); + animationController.reset(); + + if (begin == 1) { + end = 2; + } else if (begin > 1.99 && begin < 2.01) { + end = 4; + } else { + end = 1; + } + animationListener = () { + state.handleDoubleTap(scale: animation!.value, doubleTapPosition: pointerDownPosition); + }; + animation = animationController.drive(Tween(begin: begin, end: end)); + + animation!.addListener(animationListener); + + animationController.forward(); + }, + loadStateChanged: (state) { + if (state.extendedImageLoadState == LoadState.loading) { + return Center( + child: CircularProgressIndicator( + color: Colors.white.withOpacity(0.90), + ), + ); + } + return null; + }, + ) + : ExtendedImage.memory( + widget.bytes!, + color: Colors.white.withOpacity(imageTransparency), + colorBlendMode: BlendMode.dstIn, + enableSlideOutPage: true, + mode: ExtendedImageMode.gesture, + extendedImageGestureKey: gestureKey, + clearMemoryCacheWhenDispose: true, + initGestureConfigHandler: (ExtendedImageState state) { + return GestureConfig( + minScale: 0.8, + animationMinScale: 0.8, + maxScale: 4.0, + animationMaxScale: 4.0, + speed: 1.0, + inertialSpeed: 250.0, + initialScale: 1.0, + inPageView: false, + initialAlignment: InitialAlignment.center, + reverseMousePointerScrollDirection: true, + gestureDetailsIsChanged: (GestureDetails? details) {}, + ); + }, + onDoubleTap: (ExtendedImageGestureState state) { + var pointerDownPosition = state.pointerDownPosition; + double begin = state.gestureDetails!.totalScale!; + double end; + + animation?.removeListener(animationListener); + animationController.stop(); + animationController.reset(); + + if (begin == 1) { + end = 2; + } else if (begin > 1.99 && begin < 2.01) { + end = 4; + } else { + end = 1; + } + animationListener = () { + state.handleDoubleTap(scale: animation!.value, doubleTapPosition: pointerDownPosition); + }; + animation = animationController.drive(Tween(begin: begin, end: end)); + + animation!.addListener(animationListener); + + animationController.forward(); + }, + loadStateChanged: (state) { + if (state.extendedImageLoadState == LoadState.loading) { + return Center( + child: CircularProgressIndicator( + color: Colors.white.withOpacity(0.90), + ), + ); + } + return null; + }, + ), + ), + ), + ), + ), + if (!widget.isPeek) + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AnimatedOpacity( + opacity: fullscreen ? 0.0 : 1.0, + duration: const Duration(milliseconds: 200), + child: Container( + padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + stops: [0, 0.3, 1], + colors: [ + Colors.transparent, + Colors.black26, + Colors.black45, + ], ), ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(4.0), + child: IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon( + Icons.arrow_back, + semanticLabel: "Back", + color: Colors.white.withOpacity(0.90), + ), + ), + ), + ], + ), ), ), - if (!widget.isPeek) - AnimatedOpacity( - opacity: fullscreen ? 0.0 : 1.0, - duration: const Duration(milliseconds: 200), - child: Container( - decoration: const BoxDecoration(color: Colors.transparent), - padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - if (widget.url != null) - Padding( - padding: const EdgeInsets.all(4.0), - child: IconButton( - onPressed: fullscreen - ? null - : () async { - try { - // Try to get the cached image first - var media = await DefaultCacheManager().getFileFromCache(widget.url!); - File? mediaFile = media?.file; - - if (media == null) { - setState(() => isDownloadingMedia = true); - - // Download - mediaFile = await DefaultCacheManager().getSingleFile(widget.url!); + const Spacer(), + AnimatedOpacity( + opacity: fullscreen ? 0.0 : 1.0, + duration: const Duration(milliseconds: 200), + child: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: [0, 0.3, 1], + colors: [ + Colors.transparent, + Colors.black26, + Colors.black45, + ], + ), + ), + padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (widget.url != null) + Padding( + padding: const EdgeInsets.all(4.0), + child: IconButton( + onPressed: fullscreen + ? null + : () async { + try { + // Try to get the cached image first + var media = await DefaultCacheManager().getFileFromCache(widget.url!); + File? mediaFile = media?.file; + + if (media == null) { + setState(() => isDownloadingMedia = true); + + // Download + mediaFile = await DefaultCacheManager().getSingleFile(widget.url!); + } + + // Share + await Share.shareXFiles([XFile(mediaFile!.path)]); + } catch (e) { + // Tell the user that the download failed + showSnackbar(AppLocalizations.of(context)!.errorDownloadingMedia(e)); + } finally { + setState(() => isDownloadingMedia = false); } - - // Share - await Share.shareXFiles([XFile(mediaFile!.path)]); - } catch (e) { - // Tell the user that the download failed - showSnackbar(AppLocalizations.of(context)!.errorDownloadingMedia(e)); - } finally { - setState(() => isDownloadingMedia = false); - } - }, - icon: isDownloadingMedia - ? SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( + }, + icon: isDownloadingMedia + ? SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.white.withOpacity(0.90), + ), + ) + : Icon( + Icons.share_rounded, + semanticLabel: "Share", color: Colors.white.withOpacity(0.90), ), - ) - : Icon( - Icons.share_rounded, - semanticLabel: "Share", - color: Colors.white.withOpacity(0.90), - shadows: const [Shadow(color: Colors.black, blurRadius: 50.0), Shadow(color: Colors.black, blurRadius: 50.0)], - ), + ), ), - ), - if (widget.url != null) - Padding( - padding: const EdgeInsets.all(4.0), - child: IconButton( - onPressed: (fullscreen || widget.url == null || kIsWeb) - ? null - : () async { - File file = await DefaultCacheManager().getSingleFile(widget.url!); - bool hasPermission = await _requestPermission(); - - if (!hasPermission) { - if (context.mounted) showPermissionDeniedDialog(context); - return; - } + if (widget.url != null) + Padding( + padding: const EdgeInsets.all(4.0), + child: IconButton( + onPressed: (downloaded || fullscreen || widget.url == null || kIsWeb) + ? null + : () async { + File file = await DefaultCacheManager().getSingleFile(widget.url!); + bool hasPermission = await _requestPermission(); + + if (!hasPermission) { + if (context.mounted) showPermissionDeniedDialog(context); + return; + } - setState(() => isSavingMedia = true); + setState(() => isSavingMedia = true); - try { - // Save image on Linux platform - if (Platform.isLinux) { - final filePath = '${(await getApplicationDocumentsDirectory()).path}/Thunder/${basename(file.path)}'; + try { + // Save image on Linux platform + if (Platform.isLinux) { + final filePath = '${(await getApplicationDocumentsDirectory()).path}/Thunder/${basename(file.path)}'; - File(filePath) - ..createSync(recursive: true) - ..writeAsBytesSync(file.readAsBytesSync()); + File(filePath) + ..createSync(recursive: true) + ..writeAsBytesSync(file.readAsBytesSync()); - return setState(() => downloaded = true); - } + return setState(() => downloaded = true); + } - // Save image on all other supported platforms (Android, iOS, macOS, Windows) - try { - await Gal.putImage(file.path, album: "Thunder"); - setState(() => downloaded = true); - } on GalException catch (e) { - if (context.mounted) showSnackbar(e.type.message); - setState(() => downloaded = false); + // Save image on all other supported platforms (Android, iOS, macOS, Windows) + try { + await Gal.putImage(file.path, album: "Thunder"); + setState(() => downloaded = true); + } on GalException catch (e) { + if (context.mounted) showSnackbar(e.type.message); + setState(() => downloaded = false); + } + } finally { + setState(() => isSavingMedia = false); } - } finally { - setState(() => isSavingMedia = false); - } - }, - icon: isSavingMedia - ? SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - color: Colors.white.withOpacity(0.90), - ), - ) - : downloaded - ? Icon( - Icons.check_circle, - semanticLabel: 'Downloaded', - color: Colors.white.withOpacity(0.90), - ) - : Icon( - Icons.download, - semanticLabel: "Download", + }, + icon: isSavingMedia + ? SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( color: Colors.white.withOpacity(0.90), ), + ) + : downloaded + ? Icon( + Icons.check_circle, + semanticLabel: 'Downloaded', + color: Colors.white.withOpacity(0.90), + ) + : Icon( + Icons.download, + semanticLabel: "Download", + color: Colors.white.withOpacity(0.90), + ), + ), ), - ), - if (widget.navigateToPost != null) - Padding( - padding: const EdgeInsets.all(4.0), - child: IconButton( - onPressed: () { - Navigator.pop(context); - widget.navigateToPost!(); - }, - icon: Icon( - Icons.chat_rounded, - semanticLabel: "Comments", - color: Colors.white.withOpacity(0.90), + if (widget.navigateToPost != null) + Padding( + padding: const EdgeInsets.all(4.0), + child: IconButton( + onPressed: () { + Navigator.pop(context); + widget.navigateToPost!(); + }, + icon: Icon( + Icons.chat_rounded, + semanticLabel: "Comments", + color: Colors.white.withOpacity(0.90), + ), ), ), - ), ], ), ), - ), - ], - ), + ), + ], + ), if (widget.altText?.isNotEmpty == true) Positioned( bottom: kBottomNavigationBarHeight + 25,