merge main
This commit is contained in:
@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/constants.dart';
|
||||
@ -197,45 +198,38 @@ class VideoIntroController extends GetxController {
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
}
|
||||
if (hasCoin.value) {
|
||||
SmartDialog.showToast('已投过币了');
|
||||
return;
|
||||
}
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('选择投币个数'),
|
||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 24),
|
||||
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [1, 2]
|
||||
.map(
|
||||
(e) => RadioListTile(
|
||||
value: e,
|
||||
title: Text('$e枚'),
|
||||
groupValue: _tempThemeValue,
|
||||
onChanged: (value) async {
|
||||
_tempThemeValue = value!;
|
||||
setState(() {});
|
||||
var res = await VideoHttp.coinVideo(
|
||||
bvid: bvid, multiply: _tempThemeValue);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('投币成功');
|
||||
hasCoin.value = true;
|
||||
videoDetail.value.stat!.coin =
|
||||
videoDetail.value.stat!.coin! + _tempThemeValue;
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
Get.back();
|
||||
},
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [1, 2]
|
||||
.map(
|
||||
(e) => ListTile(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 20),
|
||||
child: Text('$e 枚'),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}),
|
||||
onTap: () async {
|
||||
var res =
|
||||
await VideoHttp.coinVideo(bvid: bvid, multiply: e);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('投币成功');
|
||||
hasCoin.value = true;
|
||||
videoDetail.value.stat!.coin =
|
||||
videoDetail.value.stat!.coin! + e;
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appscheme/appscheme.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
@ -14,12 +16,14 @@ import 'package:pilipala/pages/main/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/reply_new/index.dart';
|
||||
import 'package:pilipala/plugin/pl_gallery/index.dart';
|
||||
import 'package:pilipala/plugin/pl_popup/index.dart';
|
||||
import 'package:pilipala/utils/app_scheme.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/id_utils.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/url_utils.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'reply_save.dart';
|
||||
import 'zan.dart';
|
||||
|
||||
Box setting = GStrorage.setting;
|
||||
@ -32,6 +36,7 @@ class ReplyItem extends StatelessWidget {
|
||||
this.showReplyRow = true,
|
||||
this.replyReply,
|
||||
this.replyType,
|
||||
this.replySave = false,
|
||||
super.key,
|
||||
});
|
||||
final ReplyItemModel? replyItem;
|
||||
@ -40,6 +45,7 @@ class ReplyItem extends StatelessWidget {
|
||||
final bool? showReplyRow;
|
||||
final Function? replyReply;
|
||||
final ReplyType? replyType;
|
||||
final bool? replySave;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -47,19 +53,28 @@ class ReplyItem extends StatelessWidget {
|
||||
child: InkWell(
|
||||
// 点击整个评论区 评论详情/回复
|
||||
onTap: () {
|
||||
if (replySave!) {
|
||||
return;
|
||||
}
|
||||
feedBack();
|
||||
if (replyReply != null) {
|
||||
replyReply!(replyItem, null, replyItem!.replies!.isNotEmpty);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (replySave!) {
|
||||
return;
|
||||
}
|
||||
feedBack();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
return MorePanel(item: replyItem);
|
||||
return MorePanel(
|
||||
item: replyItem,
|
||||
mainFloor: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -232,7 +247,7 @@ class ReplyItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
// 操作区域
|
||||
bottonAction(context, replyItem!.replyControl),
|
||||
bottonAction(context, replyItem!.replyControl, replySave),
|
||||
// 一楼的评论
|
||||
if ((replyItem!.replyControl!.isShow! ||
|
||||
replyItem!.replies!.isNotEmpty) &&
|
||||
@ -253,7 +268,7 @@ class ReplyItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
// 感谢、回复、复制
|
||||
Widget bottonAction(BuildContext context, replyControl) {
|
||||
Widget bottonAction(BuildContext context, replyControl, replySave) {
|
||||
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
TextTheme textTheme = Theme.of(context).textTheme;
|
||||
return Row(
|
||||
@ -286,16 +301,26 @@ class ReplyItem extends StatelessWidget {
|
||||
});
|
||||
},
|
||||
child: Row(children: [
|
||||
Icon(Icons.reply,
|
||||
size: 18, color: colorScheme.outline.withOpacity(0.8)),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
'回复',
|
||||
style: TextStyle(
|
||||
fontSize: textTheme.labelMedium!.fontSize,
|
||||
color: colorScheme.outline,
|
||||
if (!replySave!) ...[
|
||||
Icon(Icons.reply,
|
||||
size: 18, color: colorScheme.outline.withOpacity(0.8)),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
'回复',
|
||||
style: TextStyle(
|
||||
fontSize: textTheme.labelMedium!.fontSize,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
)
|
||||
],
|
||||
if (replySave!)
|
||||
Text(
|
||||
IdUtils.av2bv(replyItem!.oid!),
|
||||
style: TextStyle(
|
||||
fontSize: textTheme.labelMedium!.fontSize,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
@ -436,7 +461,8 @@ class ReplyItemRow extends StatelessWidget {
|
||||
if (extraRow == 1)
|
||||
InkWell(
|
||||
// 一楼点击【共xx条回复】展开评论详情
|
||||
onTap: () => replyReply!(replyItem),
|
||||
onTap: () => replyReply?.call(replyItem, null, true),
|
||||
onLongPress: () => {},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(8, 5, 8, 8),
|
||||
@ -549,7 +575,7 @@ InlineSpan buildContent(
|
||||
);
|
||||
}
|
||||
|
||||
void onPreviewImg(picList, initIndex) {
|
||||
void onPreviewImg(picList, initIndex, randomInt) {
|
||||
final MainController mainController = Get.find<MainController>();
|
||||
mainController.imgPreviewStatus = true;
|
||||
Navigator.of(context).push(
|
||||
@ -575,7 +601,7 @@ InlineSpan buildContent(
|
||||
},
|
||||
child: Center(
|
||||
child: Hero(
|
||||
tag: picList[index],
|
||||
tag: picList[index] + randomInt,
|
||||
child: CachedNetworkImage(
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
imageUrl: picList[index],
|
||||
@ -886,11 +912,12 @@ InlineSpan buildContent(
|
||||
pictureItem['img_width']))
|
||||
.truncateToDouble();
|
||||
} catch (_) {}
|
||||
String randomInt = Random().nextInt(101).toString();
|
||||
|
||||
return Hero(
|
||||
tag: picList[0],
|
||||
tag: picList[0] + randomInt,
|
||||
child: GestureDetector(
|
||||
onTap: () => onPreviewImg(picList, 0),
|
||||
onTap: () => onPreviewImg(picList, 0, randomInt),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
constraints: BoxConstraints(maxHeight: maxHeight),
|
||||
@ -927,13 +954,14 @@ InlineSpan buildContent(
|
||||
picList.add(content.pictures[i]['img_src']);
|
||||
}
|
||||
for (var i = 0; i < len; i++) {
|
||||
String randomInt = Random().nextInt(101).toString();
|
||||
list.add(
|
||||
LayoutBuilder(
|
||||
builder: (context, BoxConstraints box) {
|
||||
return Hero(
|
||||
tag: picList[i],
|
||||
tag: picList[i] + randomInt,
|
||||
child: GestureDetector(
|
||||
onTap: () => onPreviewImg(picList, i),
|
||||
onTap: () => onPreviewImg(picList, i, randomInt),
|
||||
child: NetworkImgLayer(
|
||||
src: picList[i],
|
||||
width: box.maxWidth,
|
||||
@ -1004,7 +1032,12 @@ InlineSpan buildContent(
|
||||
|
||||
class MorePanel extends StatelessWidget {
|
||||
final dynamic item;
|
||||
const MorePanel({super.key, required this.item});
|
||||
final bool mainFloor;
|
||||
const MorePanel({
|
||||
super.key,
|
||||
required this.item,
|
||||
this.mainFloor = false,
|
||||
});
|
||||
|
||||
Future<dynamic> menuActionHandler(String type) async {
|
||||
String message = item.content.message ?? item.content;
|
||||
@ -1026,6 +1059,13 @@ class MorePanel extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
break;
|
||||
case 'save':
|
||||
Get.back();
|
||||
Navigator.push(
|
||||
Get.context!,
|
||||
PlPopupRoute(child: ReplySave(replyItem: item)),
|
||||
);
|
||||
break;
|
||||
// case 'block':
|
||||
// SmartDialog.showToast('加入黑名单');
|
||||
// break;
|
||||
@ -1076,6 +1116,13 @@ class MorePanel extends StatelessWidget {
|
||||
leading: const Icon(Icons.copy_outlined, size: 19),
|
||||
title: Text('自由复制', style: textTheme.titleSmall),
|
||||
),
|
||||
if (mainFloor && item.content.pictures.isEmpty)
|
||||
ListTile(
|
||||
onTap: () async => await menuActionHandler('save'),
|
||||
minLeadingWidth: 0,
|
||||
leading: const Icon(Icons.save_alt_rounded, size: 19),
|
||||
title: Text('本地保存', style: textTheme.titleSmall),
|
||||
),
|
||||
// ListTile(
|
||||
// onTap: () async => await menuActionHandler('block'),
|
||||
// minLeadingWidth: 0,
|
||||
|
||||
148
lib/pages/video/detail/reply/widgets/reply_save.dart
Normal file
148
lib/pages/video/detail/reply/widgets/reply_save.dart
Normal file
@ -0,0 +1,148 @@
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
||||
import 'package:saver_gallery/saver_gallery.dart';
|
||||
|
||||
class ReplySave extends StatefulWidget {
|
||||
final ReplyItemModel? replyItem;
|
||||
const ReplySave({required this.replyItem, super.key});
|
||||
|
||||
@override
|
||||
State<ReplySave> createState() => _ReplySaveState();
|
||||
}
|
||||
|
||||
class _ReplySaveState extends State<ReplySave> {
|
||||
final _boundaryKey = GlobalKey();
|
||||
|
||||
void _generatePicWidget() async {
|
||||
SmartDialog.showLoading(msg: '保存中');
|
||||
try {
|
||||
RenderRepaintBoundary boundary = _boundaryKey.currentContext!
|
||||
.findRenderObject() as RenderRepaintBoundary;
|
||||
var image = await boundary.toImage(pixelRatio: 3);
|
||||
ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);
|
||||
Uint8List pngBytes = byteData!.buffer.asUint8List();
|
||||
String picName =
|
||||
"plpl_reply_${DateTime.now().toString().replaceAll(RegExp(r'[- :]'), '').split('.').first}";
|
||||
final result = await SaverGallery.saveImage(
|
||||
Uint8List.fromList(pngBytes),
|
||||
name: '$picName.png',
|
||||
androidRelativePath: "Pictures/PiliPala",
|
||||
androidExistNotSave: false,
|
||||
);
|
||||
if (result.isSuccess) {
|
||||
SmartDialog.showToast('保存成功');
|
||||
}
|
||||
} catch (err) {
|
||||
print(err);
|
||||
} finally {
|
||||
SmartDialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _createWidgets(int count, Widget Function() builder) {
|
||||
return List<Widget>.generate(count, (_) => Expanded(child: builder()));
|
||||
}
|
||||
|
||||
List<Widget> _createColumnWidgets() {
|
||||
return _createWidgets(3, () => Row(children: _createRowWidgets()));
|
||||
}
|
||||
|
||||
List<Widget> _createRowWidgets() {
|
||||
return _createWidgets(
|
||||
4,
|
||||
() => Center(
|
||||
child: Transform.rotate(
|
||||
angle: pi / 10,
|
||||
child: const Text(
|
||||
'PiliPala',
|
||||
style: TextStyle(
|
||||
color: Color(0x08000000),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 4.0, sigmaY: 4.0),
|
||||
child: Container(
|
||||
width: Get.width,
|
||||
height: Get.height,
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0,
|
||||
MediaQuery.of(context).padding.top + 4,
|
||||
0,
|
||||
MediaQuery.of(context).padding.bottom + 4,
|
||||
),
|
||||
color: Colors.black54,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: RepaintBoundary(
|
||||
key: _boundaryKey,
|
||||
child: IntrinsicHeight(
|
||||
child: Stack(
|
||||
children: [
|
||||
ReplyItem(
|
||||
replyItem: widget.replyItem,
|
||||
showReplyRow: false,
|
||||
replySave: true,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Column(
|
||||
children: _createColumnWidgets(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FilledButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
const SizedBox(width: 40),
|
||||
FilledButton(
|
||||
onPressed: _generatePicWidget,
|
||||
child: const Text('保存'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
|
||||
this.replyType,
|
||||
this.sheetHeight,
|
||||
this.currentReply,
|
||||
this.loadMore,
|
||||
this.loadMore = true,
|
||||
super.key,
|
||||
});
|
||||
final int? oid;
|
||||
@ -32,7 +32,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
|
||||
final ReplyType? replyType;
|
||||
final double? sheetHeight;
|
||||
final dynamic currentReply;
|
||||
final bool? loadMore;
|
||||
final bool loadMore;
|
||||
|
||||
@override
|
||||
State<VideoReplyReplyPanel> createState() => _VideoReplyReplyPanelState();
|
||||
@ -142,7 +142,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
||||
),
|
||||
),
|
||||
],
|
||||
widget.loadMore != null && widget.loadMore!
|
||||
widget.loadMore
|
||||
? FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (BuildContext context, snapshot) {
|
||||
|
||||
@ -63,7 +63,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
late bool autoPlayEnable;
|
||||
late bool autoPiP;
|
||||
late Floating floating;
|
||||
bool isShowing = true;
|
||||
RxBool isShowing = true.obs;
|
||||
// 生命周期监听
|
||||
late final AppLifecycleListener _lifecycleListener;
|
||||
late double statusHeight;
|
||||
@ -183,6 +183,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
plPlayerController!.addStatusLister(playerListener);
|
||||
vdCtr.autoPlay.value = true;
|
||||
vdCtr.isShowCover.value = false;
|
||||
isShowing.value = true;
|
||||
autoEnterPip(status: PlayerStatus.playing);
|
||||
}
|
||||
|
||||
@ -258,7 +259,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
plPlayerController!.pause();
|
||||
vdCtr.clearSubtitleContent();
|
||||
}
|
||||
setState(() => isShowing = false);
|
||||
isShowing.value = false;
|
||||
super.didPushNext();
|
||||
}
|
||||
|
||||
@ -272,10 +273,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
|
||||
if (plPlayerController != null &&
|
||||
plPlayerController!.videoPlayerController != null) {
|
||||
setState(() {
|
||||
vdCtr.setSubtitleContent();
|
||||
isShowing = true;
|
||||
});
|
||||
vdCtr.setSubtitleContent();
|
||||
isShowing.value = true;
|
||||
}
|
||||
vdCtr.isFirstTime = false;
|
||||
final bool autoplay = autoPlayEnable;
|
||||
@ -330,12 +329,14 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
plPlayerController?.danmakuController?.clear();
|
||||
break;
|
||||
case 'pause':
|
||||
vdCtr.hiddenReplyReplyPanel();
|
||||
if (vdCtr.videoType == SearchType.video) {
|
||||
videoIntroController.hiddenEpisodeBottomSheet();
|
||||
}
|
||||
if (vdCtr.videoType == SearchType.media_bangumi) {
|
||||
bangumiIntroController.hiddenEpisodeBottomSheet();
|
||||
if (autoPiP) {
|
||||
vdCtr.hiddenReplyReplyPanel();
|
||||
if (vdCtr.videoType == SearchType.video) {
|
||||
videoIntroController.hiddenEpisodeBottomSheet();
|
||||
}
|
||||
if (vdCtr.videoType == SearchType.media_bangumi) {
|
||||
bangumiIntroController.hiddenEpisodeBottomSheet();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -650,7 +651,11 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
tag: heroTag,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
if (isShowing) buildVideoPlayerPanel(),
|
||||
Obx(
|
||||
() => isShowing.value
|
||||
? buildVideoPlayerPanel()
|
||||
: const SizedBox(),
|
||||
),
|
||||
|
||||
/// 关闭自动播放时 手动播放
|
||||
Obx(
|
||||
|
||||
Reference in New Issue
Block a user