feat: custom horizontal preview

Closes #117

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-01-08 19:13:37 +08:00
parent 0b9d4d970a
commit 8d4294ba75
22 changed files with 255 additions and 97 deletions

View File

@@ -8,6 +8,7 @@ import 'package:flutter_html/flutter_html.dart';
Widget articleContent({ Widget articleContent({
required BuildContext context, required BuildContext context,
required List<ArticleContentModel> list, required List<ArticleContentModel> list,
Function(List<String>, int)? callback,
}) { }) {
List<String>? imgList = list List<String>? imgList = list
.where((item) => item.pic != null) .where((item) => item.pic != null)
@@ -59,10 +60,17 @@ Widget articleContent({
tag: item.pic!.pics!.first.url!, tag: item.pic!.pics!.first.url!,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
if (callback != null) {
callback(
imgList,
imgList.indexOf(item.pic!.pics!.first.url!),
);
} else {
context.imageView( context.imageView(
initialPage: imgList.indexOf(item.pic!.pics!.first.url!), initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
imgList: imgList, imgList: imgList,
); );
}
}, },
child: NetworkImgLayer( child: NetworkImgLayer(
width: constraints.maxWidth, width: constraints.maxWidth,

View File

@@ -9,6 +9,7 @@ Widget htmlRender({
int? imgCount, int? imgCount,
List<String>? imgList, List<String>? imgList,
required double constrainedWidth, required double constrainedWidth,
Function(List<String>, int)? callback,
}) { }) {
return SelectionArea( return SelectionArea(
child: Html( child: Html(
@@ -49,9 +50,13 @@ Widget htmlRender({
tag: imgUrl, tag: imgUrl,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
if (callback != null) {
callback([imgUrl], 0);
} else {
context.imageView( context.imageView(
imgList: [imgUrl], imgList: [imgUrl],
); );
}
}, },
child: NetworkImgLayer( child: NetworkImgLayer(
width: isEmote ? 22 : constrainedWidth, width: isEmote ? 22 : constrainedWidth,

View File

@@ -25,10 +25,11 @@ class ImageModel {
Widget imageview( Widget imageview(
double maxWidth, double maxWidth,
List<ImageModel> picArr, [ List<ImageModel> picArr, {
VoidCallback? onViewImage, VoidCallback? onViewImage,
ValueChanged<int>? onDismissed, ValueChanged<int>? onDismissed,
]) { Function(List<String>, int)? callback,
}) {
double imageWidth = (maxWidth - 2 * 5) / 3; double imageWidth = (maxWidth - 2 * 5) / 3;
double imageHeight = imageWidth; double imageHeight = imageWidth;
if (picArr.length == 1) { if (picArr.length == 1) {
@@ -59,12 +60,16 @@ Widget imageview(
tag: picArr[index].url, tag: picArr[index].url,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
if (callback != null) {
callback(picArr.map((item) => item.url).toList(), index);
} else {
onViewImage?.call(); onViewImage?.call();
context.imageView( context.imageView(
initialPage: index, initialPage: index,
imgList: picArr.map((item) => item.url).toList(), imgList: picArr.map((item) => item.url).toList(),
onDismissed: onDismissed, onDismissed: onDismissed,
); );
}
}, },
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,

View File

@@ -42,8 +42,11 @@ class InteractiveviewerGallery<T> extends StatefulWidget {
this.minScale = 1.0, this.minScale = 1.0,
this.onPageChanged, this.onPageChanged,
this.onDismissed, this.onDismissed,
this.setStatusBar,
}); });
final bool? setStatusBar;
/// The sources to show. /// The sources to show.
final List<String> sources; final List<String> sources;
@@ -108,8 +111,10 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}); });
currentIndex = widget.initIndex; currentIndex = widget.initIndex;
if (widget.setStatusBar != false) {
setStatusBar(); setStatusBar();
} }
}
setStatusBar() async { setStatusBar() async {
if (Platform.isIOS || Platform.isAndroid) { if (Platform.isIOS || Platform.isAndroid) {
@@ -125,9 +130,11 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_pageController?.dispose(); _pageController?.dispose();
_animationController.removeListener(() {}); _animationController.removeListener(() {});
_animationController.dispose(); _animationController.dispose();
if (widget.setStatusBar != false) {
if (Platform.isIOS || Platform.isAndroid) { if (Platform.isIOS || Platform.isAndroid) {
StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE);
} }
}
for (int index = 0; index < widget.sources.length; index++) { for (int index = 0; index < widget.sources.length; index++) {
CachedNetworkImageProvider(_getActualUrl(index)).evict(); CachedNetworkImageProvider(_getActualUrl(index)).evict();
} }

View File

@@ -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/http/loading_state.dart';
import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart';
import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/http/html.dart'; import 'package:PiliPlus/http/html.dart';
import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/http/reply.dart';
@@ -14,6 +15,8 @@ class DynamicDetailController extends ReplyController {
dynamic item; dynamic item;
int? floor; int? floor;
late final horizontalPreview = GStorage.horizontalPreview;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart'; 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/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.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.dart';
@@ -51,6 +52,29 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
late final List<double> _ratio = GStorage.dynamicDetailRatio; late final List<double> _ratio = GStorage.dynamicDetailRatio;
bool get _horizontalPreview =>
context.orientation == Orientation.landscape &&
_dynamicDetailController.horizontalPreview;
late final _key = GlobalKey<ScaffoldState>();
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 @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -299,6 +323,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
child: DynamicPanel( child: DynamicPanel(
item: _dynamicDetailController.item, item: _dynamicDetailController.item,
source: 'detail', source: 'detail',
callback: _getImageCallback,
), ),
), ),
replyPersistentHeader(context), replyPersistentHeader(context),
@@ -326,6 +351,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
child: DynamicPanel( child: DynamicPanel(
item: _dynamicDetailController.item, item: _dynamicDetailController.item,
source: 'detail', source: 'detail',
callback: _getImageCallback,
), ),
), ),
), ),
@@ -335,6 +361,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
Expanded( Expanded(
flex: _ratio[1].toInt(), flex: _ratio[1].toInt(),
child: Scaffold( child: Scaffold(
key: _key,
body: refreshIndicator( body: refreshIndicator(
onRefresh: () async { onRefresh: () async {
await _dynamicDetailController.onRefresh(); await _dynamicDetailController.onRefresh();
@@ -499,6 +526,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
isTop: isTop:
_dynamicDetailController.hasUpTop && index == 0, _dynamicDetailController.hasUpTop && index == 0,
upMid: loadingState.response.subjectControl.upMid, upMid: loadingState.response.subjectControl.upMid,
callback: _getImageCallback,
) )
: ReplyItem( : ReplyItem(
replyItem: loadingState.response.replies[index], replyItem: loadingState.response.replies[index],
@@ -515,6 +543,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
); );
}, },
onDelete: _dynamicDetailController.onMDelete, onDelete: _dynamicDetailController.onMDelete,
callback: _getImageCallback,
); );
} }
}, },

View File

@@ -4,7 +4,7 @@ import 'package:PiliPlus/utils/utils.dart';
import '../../../common/constants.dart'; import '../../../common/constants.dart';
import 'pic_panel.dart'; import 'pic_panel.dart';
Widget articlePanel(item, context, {floor = 1}) { Widget articlePanel(item, context, callback, {floor = 1}) {
TextStyle authorStyle = TextStyle authorStyle =
TextStyle(color: Theme.of(context).colorScheme.primary); TextStyle(color: Theme.of(context).colorScheme.primary);
return Padding( return Padding(
@@ -52,7 +52,7 @@ Widget articlePanel(item, context, {floor = 1}) {
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
], ],
picWidget(item, context) picWidget(item, context, callback)
], ],
), ),
); );

View File

@@ -4,15 +4,7 @@ import 'package:flutter/material.dart';
import 'rich_node_panel.dart'; import 'rich_node_panel.dart';
class Content extends StatelessWidget { Widget content(context, item, source, callback) {
final dynamic item;
final String? source;
const Content({
super.key,
this.item,
this.source,
});
InlineSpan picsNodes() { InlineSpan picsNodes() {
return WidgetSpan( return WidgetSpan(
child: LayoutBuilder( child: LayoutBuilder(
@@ -27,13 +19,12 @@ class Content extends StatelessWidget {
), ),
) )
.toList(), .toList(),
callback: callback,
), ),
), ),
); );
} }
@override
Widget build(BuildContext context) {
TextStyle authorStyle = TextStyle authorStyle =
TextStyle(color: Theme.of(context).colorScheme.primary); TextStyle(color: Theme.of(context).colorScheme.primary);
InlineSpan? richNodes = richNode(item, context); InlineSpan? richNodes = richNode(item, context);
@@ -83,4 +74,3 @@ class Content extends StatelessWidget {
), ),
); );
} }
}

View File

@@ -10,11 +10,13 @@ class DynamicPanel extends StatelessWidget {
final dynamic item; final dynamic item;
final String? source; final String? source;
final Function? onRemove; final Function? onRemove;
final Function(List<String>, int)? callback;
DynamicPanel({ DynamicPanel({
required this.item, required this.item,
this.source, this.source,
this.onRemove, this.onRemove,
this.callback,
super.key, super.key,
}); });
@@ -58,8 +60,8 @@ class DynamicPanel extends StatelessWidget {
), ),
if (item!.modules!.moduleDynamic!.desc != null || if (item!.modules!.moduleDynamic!.desc != null ||
item!.modules!.moduleDynamic!.major != null) item!.modules!.moduleDynamic!.major != null)
Content(item: item, source: source), content(context, item, source, callback),
forWard(item, context, _dynamicsController, source), forWard(item, context, _dynamicsController, source, callback),
const SizedBox(height: 2), const SizedBox(height: 2),
if (source == null) ActionPanel(item: item), if (source == null) ActionPanel(item: item),
], ],

View File

@@ -15,7 +15,7 @@ import 'pic_panel.dart';
import 'rich_node_panel.dart'; import 'rich_node_panel.dart';
import 'video_panel.dart'; import 'video_panel.dart';
InlineSpan picsNodes(List<OpusPicsModel> pics) { InlineSpan picsNodes(List<OpusPicsModel> pics, callback) {
return WidgetSpan( return WidgetSpan(
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) => imageview( builder: (context, constraints) => imageview(
@@ -29,12 +29,13 @@ InlineSpan picsNodes(List<OpusPicsModel> pics) {
), ),
) )
.toList(), .toList(),
callback: callback,
), ),
), ),
); );
} }
Widget forWard(item, context, ctr, source, {floor = 1}) { Widget forWard(item, context, ctr, source, callback, {floor = 1}) {
TextStyle authorStyle = TextStyle authorStyle =
TextStyle(color: Theme.of(context).colorScheme.primary); TextStyle(color: Theme.of(context).colorScheme.primary);
@@ -100,7 +101,7 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
), ),
if (hasPics) ...[ if (hasPics) ...[
Text.rich( Text.rich(
picsNodes(pics), picsNodes(pics, callback),
// semanticsLabel: '动态图片', // semanticsLabel: '动态图片',
), ),
], ],
@@ -110,7 +111,7 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
padding: floor == 2 padding: floor == 2
? EdgeInsets.zero ? EdgeInsets.zero
: const EdgeInsets.only(left: 12, right: 12), : 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': case 'DYNAMIC_TYPE_ARTICLE':
return item is ItemOrigModel return item is ItemOrigModel
? articlePanel(item, context, floor: floor) ? articlePanel(item, context, callback, floor: floor)
: const SizedBox.shrink(); : const SizedBox.shrink();
// return Container( // return Container(
// padding: // padding:
@@ -144,7 +145,8 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
padding: padding:
const EdgeInsets.only(left: 15, top: 10, right: 15, bottom: 8), const EdgeInsets.only(left: 15, top: 10, right: 15, bottom: 8),
color: Theme.of(context).dividerColor.withOpacity(0.08), 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),
), ),
); );
// 直播 // 直播

View File

@@ -1,7 +1,7 @@
import 'package:PiliPlus/common/widgets/imageview.dart'; import 'package:PiliPlus/common/widgets/imageview.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
Widget picWidget(item, context) { Widget picWidget(item, context, callback) {
String type = item.modules.moduleDynamic.major.type; String type = item.modules.moduleDynamic.major.type;
if (type == 'MAJOR_TYPE_OPUS') { if (type == 'MAJOR_TYPE_OPUS') {
/// fix 图片跟rich_node_panel重复 /// fix 图片跟rich_node_panel重复
@@ -20,6 +20,7 @@ Widget picWidget(item, context) {
), ),
) )
.toList(), .toList(),
callback: callback,
), ),
); );
} }

View File

@@ -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/http/loading_state.dart';
import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart';
import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/http/html.dart'; import 'package:PiliPlus/http/html.dart';
import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/http/reply.dart';
@@ -17,6 +18,8 @@ class HtmlRenderController extends ReplyController {
RxBool loaded = false.obs; RxBool loaded = false.obs;
late final horizontalPreview = GStorage.horizontalPreview;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();

View File

@@ -2,6 +2,7 @@ import 'dart:math';
import 'package:PiliPlus/common/widgets/article_content.dart'; import 'package:PiliPlus/common/widgets/article_content.dart';
import 'package:PiliPlus/common/widgets/http_error.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/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.dart';
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart';
@@ -44,6 +45,29 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
late final List<double> _ratio = GStorage.dynamicDetailRatio; late final List<double> _ratio = GStorage.dynamicDetailRatio;
bool get _horizontalPreview =>
context.orientation == Orientation.landscape &&
_htmlRenderCtr.horizontalPreview;
late final _key = GlobalKey<ScaffoldState>();
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 @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -336,6 +360,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
Expanded( Expanded(
flex: _ratio[1].toInt(), flex: _ratio[1].toInt(),
child: Scaffold( child: Scaffold(
key: _key,
body: CustomScrollView( body: CustomScrollView(
controller: _htmlRenderCtr.scrollController, controller: _htmlRenderCtr.scrollController,
slivers: [ slivers: [
@@ -442,6 +467,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
onDelete: _htmlRenderCtr.onMDelete, onDelete: _htmlRenderCtr.onMDelete,
isTop: _htmlRenderCtr.hasUpTop && index == 0, isTop: _htmlRenderCtr.hasUpTop && index == 0,
upMid: loadingState.response.subjectControl.upMid, upMid: loadingState.response.subjectControl.upMid,
callback: _getImageCallback,
) )
: ReplyItem( : ReplyItem(
replyItem: loadingState.response.replies[index], replyItem: loadingState.response.replies[index],
@@ -458,6 +484,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
); );
}, },
onDelete: _htmlRenderCtr.onMDelete, onDelete: _htmlRenderCtr.onMDelete,
callback: _getImageCallback,
); );
} }
}, },
@@ -534,11 +561,12 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(_htmlRenderCtr.response['uname'], Text(
_htmlRenderCtr.response['uname'],
style: TextStyle( style: TextStyle(
fontSize: fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
Theme.of(context).textTheme.titleSmall!.fontSize, ),
)), ),
Text( Text(
_htmlRenderCtr.response['updateTime'], _htmlRenderCtr.response['updateTime'],
style: TextStyle( style: TextStyle(
@@ -561,6 +589,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
? articleContent( ? articleContent(
context: context, context: context,
list: _htmlRenderCtr.response['content'], list: _htmlRenderCtr.response['content'],
callback: _getImageCallback,
) )
: SliverToBoxAdapter( : SliverToBoxAdapter(
child: LayoutBuilder( child: LayoutBuilder(
@@ -568,6 +597,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
context: context, context: context,
htmlContent: _htmlRenderCtr.response['content'], htmlContent: _htmlRenderCtr.response['content'],
constrainedWidth: constraints.maxWidth, constrainedWidth: constraints.maxWidth,
callback: _getImageCallback,
), ),
), ),
) )

View File

@@ -1655,6 +1655,13 @@ List<SettingsModel> get extraSettings => [
setKey: SettingBoxKey.continuePlayingPart, setKey: SettingBoxKey.continuePlayingPart,
defaultVal: true, defaultVal: true,
), ),
SettingsModel(
settingsType: SettingsType.sw1tch,
title: '横屏在侧栏打开图片预览',
leading: Icon(Icons.photo_outlined),
setKey: SettingBoxKey.horizontalPreview,
defaultVal: false,
),
SettingsModel( SettingsModel(
settingsType: SettingsType.sw1tch, settingsType: SettingsType.sw1tch,
enableFeedback: true, enableFeedback: true,

View File

@@ -246,6 +246,8 @@ class VideoDetailController extends GetxController
imageStatus = false; imageStatus = false;
} }
late final horizontalPreview = GStorage.horizontalPreview;
// 页面来源 稍后再看 收藏夹 // 页面来源 稍后再看 收藏夹
String sourceType = 'normal'; String sourceType = 'normal';
late bool _mediaDesc = false; late bool _mediaDesc = false;

View File

@@ -24,6 +24,7 @@ class VideoReplyPanel extends StatefulWidget {
final Function replyReply; final Function replyReply;
final VoidCallback? onViewImage; final VoidCallback? onViewImage;
final ValueChanged<int>? onDismissed; final ValueChanged<int>? onDismissed;
final Function(List<String>, int)? callback;
const VideoReplyPanel({ const VideoReplyPanel({
super.key, super.key,
@@ -35,6 +36,7 @@ class VideoReplyPanel extends StatefulWidget {
required this.replyReply, required this.replyReply,
this.onViewImage, this.onViewImage,
this.onDismissed, this.onDismissed,
this.callback,
}); });
@override @override
@@ -264,6 +266,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
getTag: () => heroTag, getTag: () => heroTag,
onViewImage: widget.onViewImage, onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed, onDismissed: widget.onDismissed,
callback: widget.callback,
) )
: ReplyItem( : ReplyItem(
replyItem: loadingState.response.replies[index], replyItem: loadingState.response.replies[index],
@@ -282,6 +285,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
onViewImage: widget.onViewImage, onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed, onDismissed: widget.onDismissed,
getTag: () => heroTag, getTag: () => heroTag,
callback: widget.callback,
); );
} }
}, },

View File

@@ -35,6 +35,7 @@ class ReplyItem extends StatelessWidget {
this.onViewImage, this.onViewImage,
this.onDismissed, this.onDismissed,
this.getTag, this.getTag,
this.callback,
}); });
final ReplyItemModel? replyItem; final ReplyItemModel? replyItem;
final String? replyLevel; final String? replyLevel;
@@ -47,6 +48,7 @@ class ReplyItem extends StatelessWidget {
final VoidCallback? onViewImage; final VoidCallback? onViewImage;
final ValueChanged<int>? onDismissed; final ValueChanged<int>? onDismissed;
final Function? getTag; final Function? getTag;
final Function(List<String>, int)? callback;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -975,8 +977,9 @@ class ReplyItem extends StatelessWidget {
), ),
) )
.toList(), .toList(),
onViewImage, onViewImage: onViewImage,
onDismissed, onDismissed: onDismissed,
callback: callback,
), ),
), ),
), ),

View File

@@ -38,6 +38,7 @@ class ReplyItemGrpc extends StatelessWidget {
this.getTag, this.getTag,
this.onViewImage, this.onViewImage,
this.onDismissed, this.onDismissed,
this.callback,
}); });
final ReplyInfo replyItem; final ReplyInfo replyItem;
final String? replyLevel; final String? replyLevel;
@@ -53,6 +54,7 @@ class ReplyItemGrpc extends StatelessWidget {
final Function? getTag; final Function? getTag;
final VoidCallback? onViewImage; final VoidCallback? onViewImage;
final ValueChanged<int>? onDismissed; final ValueChanged<int>? onDismissed;
final Function(List<String>, int)? callback;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -1006,8 +1008,9 @@ class ReplyItemGrpc extends StatelessWidget {
), ),
) )
.toList(), .toList(),
onViewImage, onViewImage: onViewImage,
onDismissed, onDismissed: onDismissed,
callback: callback,
), ),
), ),
), ),

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/video/reply/item.dart'; import 'package:PiliPlus/models/video/reply/item.dart';
import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/http/reply.dart';
@@ -37,11 +38,13 @@ class VideoReplyReplyController extends CommonController
RxInt count = (-1).obs; RxInt count = (-1).obs;
int? upMid; int? upMid;
dynamic firstFloor;
int? index; int? index;
AnimationController? controller; AnimationController? controller;
Animation<Color?>? colorAnimation; Animation<Color?>? colorAnimation;
dynamic firstFloor; late final horizontalPreview = GStorage.horizontalPreview;
@override @override
void onInit() { void onInit() {

View File

@@ -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/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
@@ -58,6 +59,10 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
dynamic get firstFloor => dynamic get firstFloor =>
widget.firstFloor ?? _videoReplyReplyController.firstFloor; widget.firstFloor ?? _videoReplyReplyController.firstFloor;
bool get _horizontalPreview =>
context.orientation == Orientation.landscape &&
_videoReplyReplyController.horizontalPreview;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -174,6 +179,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
isTop: widget.isTop, isTop: widget.isTop,
onViewImage: widget.onViewImage, onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed, onDismissed: widget.onDismissed,
callback: _getImageCallback,
) )
: ReplyItem( : ReplyItem(
replyItem: firstFloor, replyItem: firstFloor,
@@ -186,6 +192,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
}, },
onViewImage: widget.onViewImage, onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed, onDismissed: widget.onDismissed,
callback: _getImageCallback,
); );
} else if (index == 1) { } else if (index == 1) {
return Divider( return Divider(
@@ -271,6 +278,23 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
), ),
); );
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) { void _onReply(dynamic item, int index) {
dynamic oid = item?.oid.toInt(); dynamic oid = item?.oid.toInt();
dynamic root = GlobalData().grpcReply ? item?.id.toInt() : item?.rpid; dynamic root = GlobalData().grpcReply ? item?.id.toInt() : item?.rpid;
@@ -457,6 +481,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
}, },
onViewImage: widget.onViewImage, onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed, onDismissed: widget.onDismissed,
callback: _getImageCallback,
) )
: ReplyItem( : ReplyItem(
replyItem: replyItem, replyItem: replyItem,
@@ -477,6 +502,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
}, },
onViewImage: widget.onViewImage, onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed, onDismissed: widget.onDismissed,
callback: _getImageCallback,
); );
} }

View File

@@ -4,6 +4,7 @@ import 'dart:math';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/icon_button.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/list_sheet.dart';
import 'package:PiliPlus/common/widgets/segment_progress_bar.dart'; import 'package:PiliPlus/common/widgets/segment_progress_bar.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
@@ -94,6 +95,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
context.orientation == Orientation.landscape && context.orientation == Orientation.landscape &&
videoDetailController.horizontalSeasonPanel; videoDetailController.horizontalSeasonPanel;
bool get _horizontalPreview =>
context.orientation == Orientation.landscape &&
videoDetailController.horizontalPreview;
StreamSubscription? _listenerDetail; StreamSubscription? _listenerDetail;
StreamSubscription? _listenerLoadingState; StreamSubscription? _listenerLoadingState;
StreamSubscription? _listenerCid; StreamSubscription? _listenerCid;
@@ -1543,6 +1548,22 @@ class _VideoDetailPageState extends State<VideoDetailPage>
replyReply: replyReply, replyReply: replyReply,
onViewImage: videoDetailController.onViewImage, onViewImage: videoDetailController.onViewImage,
onDismissed: videoDetailController.onDismissed, 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,
), ),
); );

View File

@@ -304,6 +304,9 @@ class GStorage {
static bool get autoUpdate => static bool get autoUpdate =>
GStorage.setting.get(SettingBoxKey.autoUpdate, defaultValue: true); GStorage.setting.get(SettingBoxKey.autoUpdate, defaultValue: true);
static bool get horizontalPreview => GStorage.setting
.get(SettingBoxKey.horizontalPreview, defaultValue: false);
static List<double> get dynamicDetailRatio => List<double>.from(setting static List<double> get dynamicDetailRatio => List<double>.from(setting
.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0])); .get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]));
@@ -513,6 +516,7 @@ class SettingBoxKey {
badCertificateCallback = 'badCertificateCallback', badCertificateCallback = 'badCertificateCallback',
continuePlayingPart = 'continuePlayingPart', continuePlayingPart = 'continuePlayingPart',
cdnSpeedTest = 'cdnSpeedTest', cdnSpeedTest = 'cdnSpeedTest',
horizontalPreview = 'horizontalPreview',
// Sponsor Block // Sponsor Block
enableSponsorBlock = 'enableSponsorBlock', enableSponsorBlock = 'enableSponsorBlock',