Merge branch 'main' into feature-minePage

This commit is contained in:
guozhigq
2024-10-16 14:17:24 +08:00
parent 676b2f18eb
commit 174eff7151
62 changed files with 1738 additions and 1371 deletions

View File

@ -675,7 +675,6 @@ class VideoDetailController extends GetxController
@override
void onClose() {
super.onClose();
plPlayerController.dispose();
tabCtr.removeListener(() {
onTabChanged();
});

View File

@ -3,7 +3,6 @@ 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';
@ -154,11 +153,10 @@ class VideoIntroController extends GetxController {
}
if (hasLike.value && hasCoin.value && hasFav.value) {
// 已点赞、投币、收藏
SmartDialog.showToast('🙏 UP已经收到了');
SmartDialog.showToast('UP已经收到了');
return false;
}
var result = await VideoHttp.oneThree(bvid: bvid);
print('🤣🦴:${result["data"]}');
if (result['status']) {
hasLike.value = result["data"]["like"];
hasCoin.value = result["data"]["coin"];
@ -413,7 +411,12 @@ class VideoIntroController extends GetxController {
}
// 修改分P或番剧分集
Future changeSeasonOrbangu(bvid, cid, aid, cover) async {
Future changeSeasonOrbangu(
String bvid,
int cid,
int? aid,
String? cover,
) async {
// 重新获取视频资源
final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
@ -424,13 +427,14 @@ class VideoIntroController extends GetxController {
releatedCtr.queryRelatedVideo();
}
videoDetailCtr.bvid = bvid;
videoDetailCtr.oid.value = aid ?? IdUtils.bv2av(bvid);
videoDetailCtr.cid.value = cid;
videoDetailCtr.danmakuCid.value = cid;
videoDetailCtr.cover.value = cover;
videoDetailCtr.queryVideoUrl();
videoDetailCtr.clearSubtitleContent();
videoDetailCtr
..bvid = bvid
..oid.value = aid ?? IdUtils.bv2av(bvid)
..cid.value = cid
..danmakuCid.value = cid
..cover.value = cover ?? ''
..queryVideoUrl()
..clearSubtitleContent();
await videoDetailCtr.getSubtitle();
videoDetailCtr.setSubtitleContent();
// 重新请求评论
@ -480,7 +484,13 @@ class VideoIntroController extends GetxController {
final List episodes = [];
bool isPages = false;
late String cover;
if (videoDetail.value.ugcSeason != null) {
final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
/// 优先稍后再看、收藏夹
if (videoDetailCtr.isWatchLaterVisible.value) {
episodes.addAll(videoDetailCtr.mediaList);
} else if (videoDetail.value.ugcSeason != null) {
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
final List<SectionItem> sections = ugcSeason.sections!;
for (int i = 0; i < sections.length; i++) {
@ -497,10 +507,15 @@ class VideoIntroController extends GetxController {
episodes.indexWhere((e) => e.cid == lastPlayCid.value);
int nextIndex = currentIndex + 1;
cover = episodes[nextIndex].cover;
final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
final PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
int cid = episodes[nextIndex].cid!;
while (cid == -1) {
nextIndex += 1;
SmartDialog.showToast('当前视频暂不支持播放,自动跳过');
cid = episodes[nextIndex].cid!;
}
// 列表循环
if (nextIndex >= episodes.length) {
if (platRepeat == PlayRepeat.listCycle) {
@ -510,7 +525,6 @@ class VideoIntroController extends GetxController {
return;
}
}
final int cid = episodes[nextIndex].cid!;
final String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
changeSeasonOrbangu(rBvid, cid, rAid, cover);
@ -604,4 +618,34 @@ class VideoIntroController extends GetxController {
).buildShowContent(Get.context!),
);
}
//
oneThreeDialog() {
showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text('提示'),
content: const Text('是否一键三连'),
actions: [
TextButton(
onPressed: () => navigator!.pop(),
child: Text(
'取消',
style: TextStyle(
color: Theme.of(Get.context!).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
actionOneThree();
navigator!.pop();
},
child: const Text('确认'),
)
],
);
},
);
}
}

View File

@ -1,7 +1,4 @@
import 'dart:ffi';
import 'package:bottom_sheet/bottom_sheet.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@ -151,11 +148,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
RxBool isExpand = false.obs;
late ExpandableController _expandableCtr;
// 一键三连动画
late AnimationController _controller;
late Animation<double> _scaleTransition;
final RxDouble _progress = 0.0.obs;
void Function()? handleState(Future<dynamic> Function() action) {
return isProcessing
? null
@ -178,26 +170,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
owner = widget.videoDetail!.owner;
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
_expandableCtr = ExpandableController(initialExpanded: false);
/// 一键三连动画
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
reverseDuration: const Duration(milliseconds: 300),
vsync: this,
);
_scaleTransition = Tween<double>(begin: 0.5, end: 1.5).animate(_controller)
..addListener(() async {
_progress.value =
double.parse((_scaleTransition.value - 0.5).toStringAsFixed(3));
if (_progress.value == 1) {
if (_controller.status == AnimationStatus.completed) {
await videoIntroController.actionOneThree();
}
_progress.value = 0;
_scaleTransition.removeListener(() {});
_controller.stop();
}
});
}
// 收藏
@ -279,8 +251,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
@override
void dispose() {
_expandableCtr.dispose();
_controller.dispose();
_scaleTransition.removeListener(() {});
super.dispose();
}
@ -573,131 +543,34 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
Widget actionGrid(BuildContext context, videoIntroController) {
final actionTypeSort = GlobalDataCache().actionTypeSort;
Widget progressWidget(progress) {
return SizedBox(
width: const IconThemeData.fallback().size! + 5,
height: const IconThemeData.fallback().size! + 5,
child: CircularProgressIndicator(
value: progress.value,
strokeWidth: 2,
),
);
}
Map<String, Widget> menuListWidgets = {
'like': Obx(
() {
bool likeStatus = videoIntroController.hasLike.value;
ColorScheme colorScheme = Theme.of(context).colorScheme;
return Stack(
children: [
Positioned(
top: ((Get.size.width - 24) / 5) / 2 -
(const IconThemeData.fallback().size!),
left: ((Get.size.width - 24) / 5) / 2 -
(const IconThemeData.fallback().size! + 5) / 2,
child: progressWidget(_progress)),
InkWell(
onTapDown: (details) {
feedBack();
if (videoIntroController.userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
_controller.forward();
},
onTapUp: (TapUpDetails details) {
if (_progress.value == 0) {
feedBack();
EasyThrottle.throttle(
'my-throttler', const Duration(milliseconds: 200), () {
videoIntroController.actionLikeVideo();
});
}
_controller.reverse();
},
onTapCancel: () {
_controller.reverse();
},
borderRadius: StyleString.mdRadius,
child: SizedBox(
width: (Get.size.width - 24) / 5,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 4),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Icon(
key: ValueKey<bool>(likeStatus),
likeStatus
? FontAwesomeIcons.solidThumbsUp
: FontAwesomeIcons.thumbsUp,
color: likeStatus
? colorScheme.primary
: colorScheme.outline,
size: 21,
),
),
const SizedBox(height: 6),
Text(
widget.videoDetail!.stat!.like!.toString(),
style: TextStyle(
color: likeStatus ? colorScheme.primary : null,
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
),
)
],
),
),
),
],
);
},
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: handleState(videoIntroController.actionLikeVideo),
onLongPress: () => videoIntroController.oneThreeDialog(),
selectStatus: videoIntroController.hasLike.value,
text: widget.videoDetail!.stat!.like!.toString(),
),
),
'coin': Obx(
() => Stack(
children: [
Positioned(
top: ((Get.size.width - 24) / 5) / 2 -
(const IconThemeData.fallback().size!),
left: ((Get.size.width - 24) / 5) / 2 -
(const IconThemeData.fallback().size! + 5) / 2,
child: progressWidget(_progress)),
ActionItem(
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: handleState(videoIntroController.actionCoinVideo),
selectStatus: videoIntroController.hasCoin.value,
text: widget.videoDetail!.stat!.coin!.toString(),
),
],
() => ActionItem(
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: handleState(videoIntroController.actionCoinVideo),
selectStatus: videoIntroController.hasCoin.value,
text: widget.videoDetail!.stat!.coin!.toString(),
),
),
'collect': Obx(
() => Stack(
children: [
Positioned(
top: ((Get.size.width - 24) / 5) / 2 -
(const IconThemeData.fallback().size!),
left: ((Get.size.width - 24) / 5) / 2 -
(const IconThemeData.fallback().size! + 5) / 2,
child: progressWidget(_progress)),
ActionItem(
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
text: widget.videoDetail!.stat!.favorite!.toString(),
),
],
() => ActionItem(
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
text: widget.videoDetail!.stat!.favorite!.toString(),
),
),
'watchLater': ActionItem(

View File

@ -153,7 +153,17 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
child: Container(
height: 40,
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
color: Theme.of(context).colorScheme.surface,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
boxShadow: [
BoxShadow(
color: Theme.of(context).colorScheme.surface,
blurRadius: 0.0,
spreadRadius: 0.0,
offset: const Offset(2, 0),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [

View File

@ -9,6 +9,7 @@ import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/http/reply.dart';
import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/pages/main/index.dart';
@ -18,6 +19,7 @@ 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/global_data_cache.dart';
import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/url_utils.dart';
@ -48,6 +50,8 @@ class ReplyItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final bool isOwner = int.parse(replyItem!.member!.mid!) ==
(GlobalDataCache().userInfo?.mid ?? -1);
return Material(
child: InkWell(
// 点击整个评论区 评论详情/回复
@ -73,6 +77,7 @@ class ReplyItem extends StatelessWidget {
return MorePanel(
item: replyItem,
mainFloor: true,
isOwner: isOwner,
);
},
);
@ -195,25 +200,36 @@ class ReplyItem extends StatelessWidget {
),
],
),
Row(
children: <Widget>[
Text(
Utils.dateFormat(replyItem!.ctime),
style: TextStyle(
fontSize: textTheme.labelSmall!.fontSize,
color: colorScheme.outline,
),
),
if (replyItem!.replyControl != null &&
replyItem!.replyControl!.location != '')
Text(
'${replyItem!.replyControl!.location!}',
RichText(
text: TextSpan(
children: [
TextSpan(
text: Utils.dateFormat(replyItem!.ctime),
style: TextStyle(
fontSize: textTheme.labelSmall!.fontSize,
color: colorScheme.outline),
fontSize: textTheme.labelSmall!.fontSize,
color: colorScheme.outline,
),
),
],
)
if (replyItem!.replyControl != null &&
replyItem!.replyControl!.location != '')
TextSpan(
text: '${replyItem!.replyControl!.location!}',
style: TextStyle(
fontSize: textTheme.labelSmall!.fontSize,
color: colorScheme.outline,
),
),
if (replyItem!.invisible!)
TextSpan(
text: ' • 隐藏的评论',
style: TextStyle(
color: colorScheme.outline,
fontSize: textTheme.labelSmall!.fontSize,
),
),
],
),
),
],
),
],
@ -698,14 +714,11 @@ InlineSpan buildContent(
'',
);
} else if (RegExp(r'^cv\d+$').hasMatch(matchStr)) {
Get.toNamed(
'/webview',
parameters: {
'url': 'https://www.bilibili.com/read/$matchStr',
'type': 'url',
'pageTitle': title
},
);
Get.toNamed('/read', parameters: {
'title': title,
'id': Utils.matchNum(matchStr).first.toString(),
'articleType': 'read',
});
} else {
Uri uri = Uri.parse(matchStr.replaceAll('/?', '?'));
SchemeEntity scheme = SchemeEntity(
@ -717,7 +730,7 @@ InlineSpan buildContent(
source: '',
dataString: matchStr,
);
PiliSchame.fullPathPush(scheme);
PiliSchame.httpsScheme(scheme);
}
} else {
if (appUrlSchema.startsWith('bilibili://search')) {
@ -1004,10 +1017,12 @@ InlineSpan buildContent(
class MorePanel extends StatelessWidget {
final dynamic item;
final bool mainFloor;
final bool isOwner;
const MorePanel({
super.key,
required this.item,
this.mainFloor = false,
this.isOwner = false,
});
Future<dynamic> menuActionHandler(String type) async {
@ -1043,9 +1058,43 @@ class MorePanel extends StatelessWidget {
// case 'report':
// SmartDialog.showToast('举报');
// break;
// case 'delete':
// SmartDialog.showToast('删除');
// break;
case 'delete':
// 删除评论提示
await showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text('删除评论'),
content: const Text('删除评论后,评论下所有回复将被删除,确定删除吗?'),
actions: <Widget>[
TextButton(
onPressed: () => Get.back(),
child: Text('取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline)),
),
TextButton(
onPressed: () async {
Get.back();
var result = await ReplyHttp.replyDel(
type: item.type!,
oid: item.oid!,
rpid: item.rpid!,
);
if (result['status']) {
SmartDialog.showToast('评论删除成功,需手动刷新');
Get.back();
} else {
SmartDialog.showToast(result['msg']);
}
},
child: const Text('确定'),
),
],
);
},
);
break;
default:
}
}
@ -1054,6 +1103,7 @@ class MorePanel extends StatelessWidget {
Widget build(BuildContext context) {
ColorScheme colorScheme = Theme.of(context).colorScheme;
TextTheme textTheme = Theme.of(context).textTheme;
Color errorColor = colorScheme.error;
return Container(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: Column(
@ -1106,12 +1156,14 @@ class MorePanel extends StatelessWidget {
// leading: Icon(Icons.report_outlined, color: errorColor),
// title: Text('举报', style: TextStyle(color: errorColor)),
// ),
// ListTile(
// onTap: () async => await menuActionHandler('del'),
// minLeadingWidth: 0,
// leading: Icon(Icons.delete_outline, color: errorColor),
// title: Text('删除', style: TextStyle(color: errorColor)),
// ),
if (isOwner)
ListTile(
onTap: () async => await menuActionHandler('delete'),
minLeadingWidth: 0,
leading: Icon(Icons.delete_outline, color: errorColor),
title: Text('删除评论',
style: textTheme.titleSmall!.copyWith(color: errorColor)),
),
],
),
);

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:floating/floating.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_svg/svg.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@ -68,10 +69,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
late final AppLifecycleListener _lifecycleListener;
late double statusHeight;
// 稍后再看控制器
// late AnimationController _laterCtr;
// late Animation<Offset> _laterOffsetAni;
@override
void initState() {
super.initState();
@ -85,14 +82,16 @@ class _VideoDetailPageState extends State<VideoDetailPage>
videoIntroController.videoDetail.listen((value) {
videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value);
});
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
bangumiIntroController.bangumiDetail.listen((value) {
videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value);
});
vdCtr.cid.listen((p0) {
videoPlayerServiceHandler.onVideoDetailChange(
bangumiIntroController.bangumiDetail.value, p0);
});
if (vdCtr.videoType == SearchType.media_bangumi) {
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
bangumiIntroController.bangumiDetail.listen((value) {
videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value);
});
vdCtr.cid.listen((p0) {
videoPlayerServiceHandler.onVideoDetailChange(
bangumiIntroController.bangumiDetail.value, p0);
});
}
statusBarHeight = localCache.get('statusBarHeight');
autoExitFullcreen =
setting.get(SettingBoxKey.enableAutoExit, defaultValue: false);
@ -108,7 +107,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
}
WidgetsBinding.instance.addObserver(this);
lifecycleListener();
// watchLaterControllerInit();
}
// 获取视频资源,初始化播放器
@ -242,8 +240,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
appbarStream.close();
WidgetsBinding.instance.removeObserver(this);
_lifecycleListener.dispose();
// _laterCtr.dispose();
// _laterOffsetAni.removeListener(() {});
super.dispose();
}
@ -297,6 +293,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
plPlayerController?.play();
}
plPlayerController?.addStatusLister(playerListener);
appbarStream.add(0);
super.didPopNext();
}
@ -490,21 +487,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
);
}
/// 稍后再看控制器初始化
// void watchLaterControllerInit() {
// _laterCtr = AnimationController(
// duration: const Duration(milliseconds: 300),
// vsync: this,
// );
// _laterOffsetAni = Tween<Offset>(
// begin: const Offset(0.0, 1.0),
// end: Offset.zero,
// ).animate(CurvedAnimation(
// parent: _laterCtr,
// curve: Curves.easeInOut,
// ));
// }
@override
Widget build(BuildContext context) {
final sizeContext = MediaQuery.sizeOf(context);
@ -529,6 +511,14 @@ class _VideoDetailPageState extends State<VideoDetailPage>
exitFullScreen();
}
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness:
Get.isDarkMode ? Brightness.light : Brightness.dark,
),
);
Widget buildLoadingWidget() {
return Center(child: Lottie.asset('assets/loading.json', width: 200));
}
@ -615,10 +605,16 @@ class _VideoDetailPageState extends State<VideoDetailPage>
key: vdCtr.scaffoldKey,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(0),
child: AppBar(
backgroundColor: Colors.black,
elevation: 0,
scrolledUnderElevation: 0,
child: StreamBuilder(
stream: appbarStream.stream.distinct(),
initialData: 0,
builder: ((context, snapshot) {
return AppBar(
backgroundColor: Colors.black,
elevation: 0,
scrolledUnderElevation: 0,
);
}),
),
),
body: ExtendedNestedScrollView(

View File

@ -109,7 +109,7 @@ class _MediaListPanelState extends State<MediaListPanel> {
var item = mediaList[index];
return InkWell(
onTap: () async {
String bvid = item.bvId!;
String bvid = item.bvid!;
int? aid = item.id;
String cover = item.cover ?? '';
final int cid =
@ -173,7 +173,7 @@ class _MediaListPanelState extends State<MediaListPanel> {
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w500,
color: item.bvId == widget.bvid
color: item.bvid == widget.bvid
? Theme.of(context)
.colorScheme
.primary