From 8d4294ba75cbc87ac92322459c1403244867bf7d Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Wed, 8 Jan 2025 19:13:37 +0800 Subject: [PATCH] feat: custom horizontal preview Closes #117 Signed-off-by: bggRGjQaUbCoE --- lib/common/widgets/article_content.dart | 16 ++- lib/common/widgets/html_render.dart | 11 +- lib/common/widgets/imageview.dart | 21 ++-- .../interactiveviewer_gallery.dart | 13 ++- lib/pages/dynamics/detail/controller.dart | 3 + lib/pages/dynamics/detail/view.dart | 29 +++++ lib/pages/dynamics/widgets/article_panel.dart | 4 +- lib/pages/dynamics/widgets/content_panel.dart | 104 ++++++++---------- lib/pages/dynamics/widgets/dynamic_panel.dart | 6 +- lib/pages/dynamics/widgets/forward_panel.dart | 14 ++- lib/pages/dynamics/widgets/pic_panel.dart | 3 +- lib/pages/html/controller.dart | 3 + lib/pages/html/view.dart | 40 ++++++- lib/pages/setting/widgets/model.dart | 7 ++ lib/pages/video/detail/controller.dart | 4 +- lib/pages/video/detail/reply/view.dart | 4 + .../detail/reply/widgets/reply_item.dart | 7 +- .../detail/reply/widgets/reply_item_grpc.dart | 7 +- .../video/detail/reply_reply/controller.dart | 5 +- lib/pages/video/detail/reply_reply/view.dart | 26 +++++ lib/pages/video/detail/view.dart | 21 ++++ lib/utils/storage.dart | 4 + 22 files changed, 255 insertions(+), 97 deletions(-) diff --git a/lib/common/widgets/article_content.dart b/lib/common/widgets/article_content.dart index bece8c83..aa24b744 100644 --- a/lib/common/widgets/article_content.dart +++ b/lib/common/widgets/article_content.dart @@ -8,6 +8,7 @@ import 'package:flutter_html/flutter_html.dart'; Widget articleContent({ required BuildContext context, required List list, + Function(List, int)? callback, }) { List? imgList = list .where((item) => item.pic != null) @@ -59,10 +60,17 @@ Widget articleContent({ tag: item.pic!.pics!.first.url!, child: GestureDetector( onTap: () { - context.imageView( - initialPage: imgList.indexOf(item.pic!.pics!.first.url!), - imgList: imgList, - ); + if (callback != null) { + callback( + imgList, + imgList.indexOf(item.pic!.pics!.first.url!), + ); + } else { + context.imageView( + initialPage: imgList.indexOf(item.pic!.pics!.first.url!), + imgList: imgList, + ); + } }, child: NetworkImgLayer( width: constraints.maxWidth, diff --git a/lib/common/widgets/html_render.dart b/lib/common/widgets/html_render.dart index 43edebfc..fc555301 100644 --- a/lib/common/widgets/html_render.dart +++ b/lib/common/widgets/html_render.dart @@ -9,6 +9,7 @@ Widget htmlRender({ int? imgCount, List? imgList, required double constrainedWidth, + Function(List, int)? callback, }) { return SelectionArea( child: Html( @@ -49,9 +50,13 @@ Widget htmlRender({ tag: imgUrl, child: GestureDetector( onTap: () { - context.imageView( - imgList: [imgUrl], - ); + if (callback != null) { + callback([imgUrl], 0); + } else { + context.imageView( + imgList: [imgUrl], + ); + } }, child: NetworkImgLayer( width: isEmote ? 22 : constrainedWidth, diff --git a/lib/common/widgets/imageview.dart b/lib/common/widgets/imageview.dart index ae769a6f..9da935cc 100644 --- a/lib/common/widgets/imageview.dart +++ b/lib/common/widgets/imageview.dart @@ -25,10 +25,11 @@ class ImageModel { Widget imageview( double maxWidth, - List picArr, [ + List picArr, { VoidCallback? onViewImage, ValueChanged? onDismissed, -]) { + Function(List, int)? callback, +}) { double imageWidth = (maxWidth - 2 * 5) / 3; double imageHeight = imageWidth; if (picArr.length == 1) { @@ -59,12 +60,16 @@ Widget imageview( tag: picArr[index].url, child: GestureDetector( onTap: () { - onViewImage?.call(); - context.imageView( - initialPage: index, - imgList: picArr.map((item) => item.url).toList(), - onDismissed: onDismissed, - ); + if (callback != null) { + callback(picArr.map((item) => item.url).toList(), index); + } else { + onViewImage?.call(); + context.imageView( + initialPage: index, + imgList: picArr.map((item) => item.url).toList(), + onDismissed: onDismissed, + ); + } }, child: Stack( alignment: Alignment.center, diff --git a/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart b/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart index fb708368..6cf69816 100644 --- a/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart +++ b/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart @@ -42,8 +42,11 @@ class InteractiveviewerGallery extends StatefulWidget { this.minScale = 1.0, this.onPageChanged, this.onDismissed, + this.setStatusBar, }); + final bool? setStatusBar; + /// The sources to show. final List sources; @@ -108,7 +111,9 @@ class _InteractiveviewerGalleryState extends State }); currentIndex = widget.initIndex; - setStatusBar(); + if (widget.setStatusBar != false) { + setStatusBar(); + } } setStatusBar() async { @@ -125,8 +130,10 @@ class _InteractiveviewerGalleryState extends State _pageController?.dispose(); _animationController.removeListener(() {}); _animationController.dispose(); - if (Platform.isIOS || Platform.isAndroid) { - StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); + if (widget.setStatusBar != false) { + if (Platform.isIOS || Platform.isAndroid) { + StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); + } } for (int index = 0; index < widget.sources.length; index++) { CachedNetworkImageProvider(_getActualUrl(index)).evict(); diff --git a/lib/pages/dynamics/detail/controller.dart b/lib/pages/dynamics/detail/controller.dart index 87081890..f214604a 100644 --- a/lib/pages/dynamics/detail/controller.dart +++ b/lib/pages/dynamics/detail/controller.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/utils/global_data.dart'; +import 'package:PiliPlus/utils/storage.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/html.dart'; import 'package:PiliPlus/http/reply.dart'; @@ -14,6 +15,8 @@ class DynamicDetailController extends ReplyController { dynamic item; int? floor; + late final horizontalPreview = GStorage.horizontalPreview; + @override void onInit() { super.onInit(); diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index ee19919c..02cb83ba 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart'; +import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item.dart'; @@ -51,6 +52,29 @@ class _DynamicDetailPageState extends State late final List _ratio = GStorage.dynamicDetailRatio; + bool get _horizontalPreview => + context.orientation == Orientation.landscape && + _dynamicDetailController.horizontalPreview; + + late final _key = GlobalKey(); + + get _getImageCallback => _horizontalPreview + ? (imgList, index) { + _key.currentState?.showBottomSheet( + (context) { + return InteractiveviewerGallery( + sources: imgList, + initIndex: index, + setStatusBar: false, + ); + }, + enableDrag: false, + elevation: 0, + backgroundColor: Colors.transparent, + ); + } + : null; + @override void initState() { super.initState(); @@ -299,6 +323,7 @@ class _DynamicDetailPageState extends State child: DynamicPanel( item: _dynamicDetailController.item, source: 'detail', + callback: _getImageCallback, ), ), replyPersistentHeader(context), @@ -326,6 +351,7 @@ class _DynamicDetailPageState extends State child: DynamicPanel( item: _dynamicDetailController.item, source: 'detail', + callback: _getImageCallback, ), ), ), @@ -335,6 +361,7 @@ class _DynamicDetailPageState extends State Expanded( flex: _ratio[1].toInt(), child: Scaffold( + key: _key, body: refreshIndicator( onRefresh: () async { await _dynamicDetailController.onRefresh(); @@ -499,6 +526,7 @@ class _DynamicDetailPageState extends State isTop: _dynamicDetailController.hasUpTop && index == 0, upMid: loadingState.response.subjectControl.upMid, + callback: _getImageCallback, ) : ReplyItem( replyItem: loadingState.response.replies[index], @@ -515,6 +543,7 @@ class _DynamicDetailPageState extends State ); }, onDelete: _dynamicDetailController.onMDelete, + callback: _getImageCallback, ); } }, diff --git a/lib/pages/dynamics/widgets/article_panel.dart b/lib/pages/dynamics/widgets/article_panel.dart index c39faeee..2f481056 100644 --- a/lib/pages/dynamics/widgets/article_panel.dart +++ b/lib/pages/dynamics/widgets/article_panel.dart @@ -4,7 +4,7 @@ import 'package:PiliPlus/utils/utils.dart'; import '../../../common/constants.dart'; import 'pic_panel.dart'; -Widget articlePanel(item, context, {floor = 1}) { +Widget articlePanel(item, context, callback, {floor = 1}) { TextStyle authorStyle = TextStyle(color: Theme.of(context).colorScheme.primary); return Padding( @@ -52,7 +52,7 @@ Widget articlePanel(item, context, {floor = 1}) { ), const SizedBox(height: 2), ], - picWidget(item, context) + picWidget(item, context, callback) ], ), ); diff --git a/lib/pages/dynamics/widgets/content_panel.dart b/lib/pages/dynamics/widgets/content_panel.dart index 600c23c2..d3716e1b 100644 --- a/lib/pages/dynamics/widgets/content_panel.dart +++ b/lib/pages/dynamics/widgets/content_panel.dart @@ -4,15 +4,7 @@ import 'package:flutter/material.dart'; import 'rich_node_panel.dart'; -class Content extends StatelessWidget { - final dynamic item; - final String? source; - const Content({ - super.key, - this.item, - this.source, - }); - +Widget content(context, item, source, callback) { InlineSpan picsNodes() { return WidgetSpan( child: LayoutBuilder( @@ -27,60 +19,58 @@ class Content extends StatelessWidget { ), ) .toList(), + callback: callback, ), ), ); } - @override - Widget build(BuildContext context) { - TextStyle authorStyle = - TextStyle(color: Theme.of(context).colorScheme.primary); - InlineSpan? richNodes = richNode(item, context); + TextStyle authorStyle = + TextStyle(color: Theme.of(context).colorScheme.primary); + InlineSpan? richNodes = richNode(item, context); - return Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(12, 0, 12, 6), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (item.modules.moduleDynamic.topic != null) ...[ - GestureDetector( - child: Text( - '#${item.modules.moduleDynamic.topic.name}', - style: authorStyle, - ), - ), - ], - if (richNodes != null) - IgnorePointer( - // 禁用SelectableRegion的触摸交互功能 - ignoring: source == 'detail' ? false : true, - child: SelectableRegion( - magnifierConfiguration: const TextMagnifierConfiguration(), - focusNode: FocusNode(), - selectionControls: MaterialTextSelectionControls(), - child: Text.rich( - /// fix 默认20px高度 - style: TextStyle( - height: 0, - fontSize: source == 'detail' ? 16 : 15, - ), - richNodes, - maxLines: source == 'detail' ? 999 : 6, - overflow: TextOverflow.ellipsis, - ), - ), - ), - if (item.modules.moduleDynamic.major != null && - item.modules.moduleDynamic.major.opus != null && - item.modules.moduleDynamic.major.opus.pics.isNotEmpty) - Text.rich( - picsNodes(), - // semanticsLabel: '动态图片', + return Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(12, 0, 12, 6), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (item.modules.moduleDynamic.topic != null) ...[ + GestureDetector( + child: Text( + '#${item.modules.moduleDynamic.topic.name}', + style: authorStyle, ), + ), ], - ), - ); - } + if (richNodes != null) + IgnorePointer( + // 禁用SelectableRegion的触摸交互功能 + ignoring: source == 'detail' ? false : true, + child: SelectableRegion( + magnifierConfiguration: const TextMagnifierConfiguration(), + focusNode: FocusNode(), + selectionControls: MaterialTextSelectionControls(), + child: Text.rich( + /// fix 默认20px高度 + style: TextStyle( + height: 0, + fontSize: source == 'detail' ? 16 : 15, + ), + richNodes, + maxLines: source == 'detail' ? 999 : 6, + overflow: TextOverflow.ellipsis, + ), + ), + ), + if (item.modules.moduleDynamic.major != null && + item.modules.moduleDynamic.major.opus != null && + item.modules.moduleDynamic.major.opus.pics.isNotEmpty) + Text.rich( + picsNodes(), + // semanticsLabel: '动态图片', + ), + ], + ), + ); } diff --git a/lib/pages/dynamics/widgets/dynamic_panel.dart b/lib/pages/dynamics/widgets/dynamic_panel.dart index 2766a2f5..036f36e2 100644 --- a/lib/pages/dynamics/widgets/dynamic_panel.dart +++ b/lib/pages/dynamics/widgets/dynamic_panel.dart @@ -10,11 +10,13 @@ class DynamicPanel extends StatelessWidget { final dynamic item; final String? source; final Function? onRemove; + final Function(List, int)? callback; DynamicPanel({ required this.item, this.source, this.onRemove, + this.callback, super.key, }); @@ -58,8 +60,8 @@ class DynamicPanel extends StatelessWidget { ), if (item!.modules!.moduleDynamic!.desc != null || item!.modules!.moduleDynamic!.major != null) - Content(item: item, source: source), - forWard(item, context, _dynamicsController, source), + content(context, item, source, callback), + forWard(item, context, _dynamicsController, source, callback), const SizedBox(height: 2), if (source == null) ActionPanel(item: item), ], diff --git a/lib/pages/dynamics/widgets/forward_panel.dart b/lib/pages/dynamics/widgets/forward_panel.dart index 809c2e76..2d0fc142 100644 --- a/lib/pages/dynamics/widgets/forward_panel.dart +++ b/lib/pages/dynamics/widgets/forward_panel.dart @@ -15,7 +15,7 @@ import 'pic_panel.dart'; import 'rich_node_panel.dart'; import 'video_panel.dart'; -InlineSpan picsNodes(List pics) { +InlineSpan picsNodes(List pics, callback) { return WidgetSpan( child: LayoutBuilder( builder: (context, constraints) => imageview( @@ -29,12 +29,13 @@ InlineSpan picsNodes(List pics) { ), ) .toList(), + callback: callback, ), ), ); } -Widget forWard(item, context, ctr, source, {floor = 1}) { +Widget forWard(item, context, ctr, source, callback, {floor = 1}) { TextStyle authorStyle = TextStyle(color: Theme.of(context).colorScheme.primary); @@ -100,7 +101,7 @@ Widget forWard(item, context, ctr, source, {floor = 1}) { ), if (hasPics) ...[ Text.rich( - picsNodes(pics), + picsNodes(pics, callback), // semanticsLabel: '动态图片', ), ], @@ -110,7 +111,7 @@ Widget forWard(item, context, ctr, source, {floor = 1}) { padding: floor == 2 ? EdgeInsets.zero : const EdgeInsets.only(left: 12, right: 12), - child: picWidget(item, context), + child: picWidget(item, context, callback), ), /// 附加内容 商品信息、直播预约等等 @@ -129,7 +130,7 @@ Widget forWard(item, context, ctr, source, {floor = 1}) { // 文章 case 'DYNAMIC_TYPE_ARTICLE': return item is ItemOrigModel - ? articlePanel(item, context, floor: floor) + ? articlePanel(item, context, callback, floor: floor) : const SizedBox.shrink(); // return Container( // padding: @@ -144,7 +145,8 @@ Widget forWard(item, context, ctr, source, {floor = 1}) { padding: const EdgeInsets.only(left: 15, top: 10, right: 15, bottom: 8), color: Theme.of(context).dividerColor.withOpacity(0.08), - child: forWard(item.orig, context, ctr, source, floor: floor + 1), + child: forWard(item.orig, context, ctr, source, callback, + floor: floor + 1), ), ); // 直播 diff --git a/lib/pages/dynamics/widgets/pic_panel.dart b/lib/pages/dynamics/widgets/pic_panel.dart index 7bd20bda..c061ae32 100644 --- a/lib/pages/dynamics/widgets/pic_panel.dart +++ b/lib/pages/dynamics/widgets/pic_panel.dart @@ -1,7 +1,7 @@ import 'package:PiliPlus/common/widgets/imageview.dart'; import 'package:flutter/material.dart'; -Widget picWidget(item, context) { +Widget picWidget(item, context, callback) { String type = item.modules.moduleDynamic.major.type; if (type == 'MAJOR_TYPE_OPUS') { /// fix 图片跟rich_node_panel重复 @@ -20,6 +20,7 @@ Widget picWidget(item, context) { ), ) .toList(), + callback: callback, ), ); } diff --git a/lib/pages/html/controller.dart b/lib/pages/html/controller.dart index 4ec93408..2d544655 100644 --- a/lib/pages/html/controller.dart +++ b/lib/pages/html/controller.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/utils/global_data.dart'; +import 'package:PiliPlus/utils/storage.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/html.dart'; import 'package:PiliPlus/http/reply.dart'; @@ -17,6 +18,8 @@ class HtmlRenderController extends ReplyController { RxBool loaded = false.obs; + late final horizontalPreview = GStorage.horizontalPreview; + @override void onInit() { super.onInit(); diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index 3331701e..08b19e22 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:PiliPlus/common/widgets/article_content.dart'; import 'package:PiliPlus/common/widgets/http_error.dart'; +import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item.dart'; import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart'; @@ -44,6 +45,29 @@ class _HtmlRenderPageState extends State late final List _ratio = GStorage.dynamicDetailRatio; + bool get _horizontalPreview => + context.orientation == Orientation.landscape && + _htmlRenderCtr.horizontalPreview; + + late final _key = GlobalKey(); + + get _getImageCallback => _horizontalPreview + ? (imgList, index) { + _key.currentState?.showBottomSheet( + (context) { + return InteractiveviewerGallery( + sources: imgList, + initIndex: index, + setStatusBar: false, + ); + }, + enableDrag: false, + elevation: 0, + backgroundColor: Colors.transparent, + ); + } + : null; + @override void initState() { super.initState(); @@ -336,6 +360,7 @@ class _HtmlRenderPageState extends State Expanded( flex: _ratio[1].toInt(), child: Scaffold( + key: _key, body: CustomScrollView( controller: _htmlRenderCtr.scrollController, slivers: [ @@ -442,6 +467,7 @@ class _HtmlRenderPageState extends State onDelete: _htmlRenderCtr.onMDelete, isTop: _htmlRenderCtr.hasUpTop && index == 0, upMid: loadingState.response.subjectControl.upMid, + callback: _getImageCallback, ) : ReplyItem( replyItem: loadingState.response.replies[index], @@ -458,6 +484,7 @@ class _HtmlRenderPageState extends State ); }, onDelete: _htmlRenderCtr.onMDelete, + callback: _getImageCallback, ); } }, @@ -534,11 +561,12 @@ class _HtmlRenderPageState extends State Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(_htmlRenderCtr.response['uname'], - style: TextStyle( - fontSize: - Theme.of(context).textTheme.titleSmall!.fontSize, - )), + Text( + _htmlRenderCtr.response['uname'], + style: TextStyle( + fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, + ), + ), Text( _htmlRenderCtr.response['updateTime'], style: TextStyle( @@ -561,6 +589,7 @@ class _HtmlRenderPageState extends State ? articleContent( context: context, list: _htmlRenderCtr.response['content'], + callback: _getImageCallback, ) : SliverToBoxAdapter( child: LayoutBuilder( @@ -568,6 +597,7 @@ class _HtmlRenderPageState extends State context: context, htmlContent: _htmlRenderCtr.response['content'], constrainedWidth: constraints.maxWidth, + callback: _getImageCallback, ), ), ) diff --git a/lib/pages/setting/widgets/model.dart b/lib/pages/setting/widgets/model.dart index 56e8d9d2..720691bd 100644 --- a/lib/pages/setting/widgets/model.dart +++ b/lib/pages/setting/widgets/model.dart @@ -1655,6 +1655,13 @@ List get extraSettings => [ setKey: SettingBoxKey.continuePlayingPart, defaultVal: true, ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '横屏在侧栏打开图片预览', + leading: Icon(Icons.photo_outlined), + setKey: SettingBoxKey.horizontalPreview, + defaultVal: false, + ), SettingsModel( settingsType: SettingsType.sw1tch, enableFeedback: true, diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 9c07c5be..7073a55a 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -246,7 +246,9 @@ class VideoDetailController extends GetxController imageStatus = false; } - // 页面来源 稍后再看 收藏夹 + late final horizontalPreview = GStorage.horizontalPreview; + +// 页面来源 稍后再看 收藏夹 String sourceType = 'normal'; late bool _mediaDesc = false; late RxList mediaList = [].obs; diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 8214bb93..45d5c7af 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -24,6 +24,7 @@ class VideoReplyPanel extends StatefulWidget { final Function replyReply; final VoidCallback? onViewImage; final ValueChanged? onDismissed; + final Function(List, int)? callback; const VideoReplyPanel({ super.key, @@ -35,6 +36,7 @@ class VideoReplyPanel extends StatefulWidget { required this.replyReply, this.onViewImage, this.onDismissed, + this.callback, }); @override @@ -264,6 +266,7 @@ class _VideoReplyPanelState extends State getTag: () => heroTag, onViewImage: widget.onViewImage, onDismissed: widget.onDismissed, + callback: widget.callback, ) : ReplyItem( replyItem: loadingState.response.replies[index], @@ -282,6 +285,7 @@ class _VideoReplyPanelState extends State onViewImage: widget.onViewImage, onDismissed: widget.onDismissed, getTag: () => heroTag, + callback: widget.callback, ); } }, diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 6eecce64..19a1de1f 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -35,6 +35,7 @@ class ReplyItem extends StatelessWidget { this.onViewImage, this.onDismissed, this.getTag, + this.callback, }); final ReplyItemModel? replyItem; final String? replyLevel; @@ -47,6 +48,7 @@ class ReplyItem extends StatelessWidget { final VoidCallback? onViewImage; final ValueChanged? onDismissed; final Function? getTag; + final Function(List, int)? callback; @override Widget build(BuildContext context) { @@ -975,8 +977,9 @@ class ReplyItem extends StatelessWidget { ), ) .toList(), - onViewImage, - onDismissed, + onViewImage: onViewImage, + onDismissed: onDismissed, + callback: callback, ), ), ), diff --git a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart index f46ef05b..882f94ae 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart @@ -38,6 +38,7 @@ class ReplyItemGrpc extends StatelessWidget { this.getTag, this.onViewImage, this.onDismissed, + this.callback, }); final ReplyInfo replyItem; final String? replyLevel; @@ -53,6 +54,7 @@ class ReplyItemGrpc extends StatelessWidget { final Function? getTag; final VoidCallback? onViewImage; final ValueChanged? onDismissed; + final Function(List, int)? callback; @override Widget build(BuildContext context) { @@ -1006,8 +1008,9 @@ class ReplyItemGrpc extends StatelessWidget { ), ) .toList(), - onViewImage, - onDismissed, + onViewImage: onViewImage, + onDismissed: onDismissed, + callback: callback, ), ), ), diff --git a/lib/pages/video/detail/reply_reply/controller.dart b/lib/pages/video/detail/reply_reply/controller.dart index 5f63c20a..c05c8add 100644 --- a/lib/pages/video/detail/reply_reply/controller.dart +++ b/lib/pages/video/detail/reply_reply/controller.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/video/reply/item.dart'; import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/utils/global_data.dart'; +import 'package:PiliPlus/utils/storage.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/reply.dart'; @@ -37,11 +38,13 @@ class VideoReplyReplyController extends CommonController RxInt count = (-1).obs; int? upMid; + dynamic firstFloor; + int? index; AnimationController? controller; Animation? colorAnimation; - dynamic firstFloor; + late final horizontalPreview = GStorage.horizontalPreview; @override void onInit() { diff --git a/lib/pages/video/detail/reply_reply/view.dart b/lib/pages/video/detail/reply_reply/view.dart index b7987176..c41247a0 100644 --- a/lib/pages/video/detail/reply_reply/view.dart +++ b/lib/pages/video/detail/reply_reply/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -58,6 +59,10 @@ class _VideoReplyReplyPanelState extends State { dynamic get firstFloor => widget.firstFloor ?? _videoReplyReplyController.firstFloor; + bool get _horizontalPreview => + context.orientation == Orientation.landscape && + _videoReplyReplyController.horizontalPreview; + @override void initState() { super.initState(); @@ -174,6 +179,7 @@ class _VideoReplyReplyPanelState extends State { isTop: widget.isTop, onViewImage: widget.onViewImage, onDismissed: widget.onDismissed, + callback: _getImageCallback, ) : ReplyItem( replyItem: firstFloor, @@ -186,6 +192,7 @@ class _VideoReplyReplyPanelState extends State { }, onViewImage: widget.onViewImage, onDismissed: widget.onDismissed, + callback: _getImageCallback, ); } else if (index == 1) { return Divider( @@ -271,6 +278,23 @@ class _VideoReplyReplyPanelState extends State { ), ); + get _getImageCallback => _horizontalPreview + ? (imgList, index) { + _key.currentState?.showBottomSheet( + (context) { + return InteractiveviewerGallery( + sources: imgList, + initIndex: index, + setStatusBar: false, + ); + }, + enableDrag: false, + elevation: 0, + backgroundColor: Colors.transparent, + ); + } + : null; + void _onReply(dynamic item, int index) { dynamic oid = item?.oid.toInt(); dynamic root = GlobalData().grpcReply ? item?.id.toInt() : item?.rpid; @@ -457,6 +481,7 @@ class _VideoReplyReplyPanelState extends State { }, onViewImage: widget.onViewImage, onDismissed: widget.onDismissed, + callback: _getImageCallback, ) : ReplyItem( replyItem: replyItem, @@ -477,6 +502,7 @@ class _VideoReplyReplyPanelState extends State { }, onViewImage: widget.onViewImage, onDismissed: widget.onDismissed, + callback: _getImageCallback, ); } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 89e4a7d5..c8b971ac 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart'; +import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'; import 'package:PiliPlus/common/widgets/list_sheet.dart'; import 'package:PiliPlus/common/widgets/segment_progress_bar.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -94,6 +95,10 @@ class _VideoDetailPageState extends State context.orientation == Orientation.landscape && videoDetailController.horizontalSeasonPanel; + bool get _horizontalPreview => + context.orientation == Orientation.landscape && + videoDetailController.horizontalPreview; + StreamSubscription? _listenerDetail; StreamSubscription? _listenerLoadingState; StreamSubscription? _listenerCid; @@ -1543,6 +1548,22 @@ class _VideoDetailPageState extends State replyReply: replyReply, onViewImage: videoDetailController.onViewImage, onDismissed: videoDetailController.onDismissed, + callback: _horizontalPreview + ? (imgList, index) { + videoDetailController.childKey.currentState?.showBottomSheet( + (context) { + return InteractiveviewerGallery( + sources: imgList, + initIndex: index, + setStatusBar: false, + ); + }, + enableDrag: false, + elevation: 0, + backgroundColor: Colors.transparent, + ); + } + : null, ), ); diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index e1876396..9657771f 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -304,6 +304,9 @@ class GStorage { static bool get autoUpdate => GStorage.setting.get(SettingBoxKey.autoUpdate, defaultValue: true); + static bool get horizontalPreview => GStorage.setting + .get(SettingBoxKey.horizontalPreview, defaultValue: false); + static List get dynamicDetailRatio => List.from(setting .get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0])); @@ -513,6 +516,7 @@ class SettingBoxKey { badCertificateCallback = 'badCertificateCallback', continuePlayingPart = 'continuePlayingPart', cdnSpeedTest = 'cdnSpeedTest', + horizontalPreview = 'horizontalPreview', // Sponsor Block enableSponsorBlock = 'enableSponsorBlock',