From 2503d5cbb4833ad19d63eb71919b097d10052026 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 16 Jun 2024 19:09:05 +0800 Subject: [PATCH 001/152] =?UTF-8?q?opt:=20=E6=90=9C=E7=B4=A2=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/http_error.dart | 38 +++++++++------ lib/http/search.dart | 16 +++++-- lib/models/search/result.dart | 10 ++-- lib/pages/search_panel/controller.dart | 4 +- .../search_panel/widgets/video_panel.dart | 47 ++++++++++++------- 5 files changed, 72 insertions(+), 43 deletions(-) diff --git a/lib/common/widgets/http_error.dart b/lib/common/widgets/http_error.dart index cbc6659b..0381319e 100644 --- a/lib/common/widgets/http_error.dart +++ b/lib/common/widgets/http_error.dart @@ -2,12 +2,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; class HttpError extends StatelessWidget { - const HttpError( - {required this.errMsg, required this.fn, this.btnText, super.key}); + const HttpError({ + required this.errMsg, + required this.fn, + this.btnText, + this.isShowBtn = true, + super.key, + }); final String? errMsg; final Function()? fn; final String? btnText; + final bool isShowBtn; @override Widget build(BuildContext context) { @@ -29,20 +35,22 @@ class HttpError extends StatelessWidget { style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 20), - FilledButton.tonal( - onPressed: () { - fn!(); - }, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith((states) { - return Theme.of(context).colorScheme.primary.withAlpha(20); - }), + if (isShowBtn) + FilledButton.tonal( + onPressed: () { + fn!(); + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith((states) { + return Theme.of(context).colorScheme.primary.withAlpha(20); + }), + ), + child: Text( + btnText ?? '点击重试', + style: + TextStyle(color: Theme.of(context).colorScheme.primary), + ), ), - child: Text( - btnText ?? '点击重试', - style: TextStyle(color: Theme.of(context).colorScheme.primary), - ), - ), ], ), ), diff --git a/lib/http/search.dart b/lib/http/search.dart index 075defc7..403e6a37 100644 --- a/lib/http/search.dart +++ b/lib/http/search.dart @@ -88,7 +88,11 @@ class SearchHttp { if (tids != null && tids != -1) 'tids': tids, }; var res = await Request().get(Api.searchByType, data: reqData); - if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) { + if (res.data['code'] == 0) { + if (res.data['data']['numPages'] == 0) { + // 我想返回数据,使得可以通过data.list 取值,结果为[] + return {'status': true, 'data': Data()}; + } Object data; try { switch (searchType) { @@ -125,9 +129,7 @@ class SearchHttp { return { 'status': false, 'data': [], - 'msg': res.data['data'] != null && res.data['data']['numPages'] == 0 - ? '没有相关数据' - : res.data['message'], + 'msg': res.data['message'], }; } } @@ -206,3 +208,9 @@ class SearchHttp { } } } + +class Data { + List list; + + Data({this.list = const []}); +} diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart index 81917b72..b903c873 100644 --- a/lib/models/search/result.dart +++ b/lib/models/search/result.dart @@ -5,10 +5,12 @@ class SearchVideoModel { SearchVideoModel({this.list}); List? list; SearchVideoModel.fromJson(Map json) { - list = json['result'] - .where((e) => e['available'] == true) - .map((e) => SearchVideoItemModel.fromJson(e)) - .toList(); + list = json['result'] == null + ? [] + : json['result'] + .where((e) => e['available'] == true) + .map((e) => SearchVideoItemModel.fromJson(e)) + .toList(); } } diff --git a/lib/pages/search_panel/controller.dart b/lib/pages/search_panel/controller.dart index 35113198..dc0b2bac 100644 --- a/lib/pages/search_panel/controller.dart +++ b/lib/pages/search_panel/controller.dart @@ -30,9 +30,9 @@ class SearchPanelController extends GetxController { ); if (result['status']) { if (type == 'onRefresh') { - resultList.value = result['data'].list; + resultList.value = result['data'].list ?? []; } else { - resultList.addAll(result['data'].list); + resultList.addAll(result['data'].list ?? []); } page.value++; onPushDetail(keyword, resultList); diff --git a/lib/pages/search_panel/widgets/video_panel.dart b/lib/pages/search_panel/widgets/video_panel.dart index 15745bde..f43e2eec 100644 --- a/lib/pages/search_panel/widgets/video_panel.dart +++ b/lib/pages/search_panel/widgets/video_panel.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/video_card_h.dart'; import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/pages/search/widgets/search_text.dart'; @@ -25,25 +26,35 @@ class SearchVideoPanel extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.only(top: 36), - child: ListView.builder( - controller: ctr!.scrollController, - addAutomaticKeepAlives: false, - addRepaintBoundaries: false, - itemCount: list!.length, - itemBuilder: (context, index) { - var i = list![index]; - return Padding( - padding: index == 0 - ? const EdgeInsets.only(top: 2) - : EdgeInsets.zero, - child: VideoCardH( - videoItem: i, - showPubdate: true, - source: 'search', + child: list!.isNotEmpty + ? ListView.builder( + controller: ctr!.scrollController, + addAutomaticKeepAlives: false, + addRepaintBoundaries: false, + itemCount: list!.length, + itemBuilder: (context, index) { + var i = list![index]; + return Padding( + padding: index == 0 + ? const EdgeInsets.only(top: 2) + : EdgeInsets.zero, + child: VideoCardH( + videoItem: i, + showPubdate: true, + source: 'search', + ), + ); + }, + ) + : CustomScrollView( + slivers: [ + HttpError( + errMsg: '没有数据', + isShowBtn: false, + fn: () => {}, + ) + ], ), - ); - }, - ), ), // 分类筛选 Container( From 75525595c6258a9561d1b88845babb626aaf07ad Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 16 Jun 2024 20:59:55 +0800 Subject: [PATCH 002/152] =?UTF-8?q?opt:=20=E9=A6=96=E9=A1=B5tab=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/home/view.dart | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index a25389bd..e485fe41 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -357,25 +357,29 @@ class CustomChip extends StatelessWidget { Widget build(BuildContext context) { final ColorScheme colorTheme = Theme.of(context).colorScheme; final Color secondaryContainer = colorTheme.secondaryContainer; + final Color onPrimary = colorTheme.onPrimary; + final Color primary = colorTheme.primary; final TextStyle chipTextStyle = selected - ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 13) - : const TextStyle(fontSize: 13); - final ColorScheme colorScheme = Theme.of(context).colorScheme; + ? TextStyle(fontSize: 13, color: onPrimary) + : TextStyle(fontSize: 13, color: colorTheme.onSecondaryContainer); const VisualDensity visualDensity = VisualDensity(horizontal: -4.0, vertical: -2.0); return InputChip( - side: BorderSide( - color: selected - ? colorScheme.onSecondaryContainer.withOpacity(0.2) - : Colors.transparent, - ), + side: BorderSide.none, backgroundColor: secondaryContainer, - selectedColor: secondaryContainer, - color: MaterialStateProperty.resolveWith( - (Set states) => secondaryContainer.withAlpha(200)), - padding: const EdgeInsets.fromLTRB(7, 1, 7, 1), + color: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.selected) || + states.contains(MaterialState.hovered)) { + return primary; + } + return colorTheme.secondaryContainer; + }), + padding: const EdgeInsets.fromLTRB(6, 1, 6, 1), label: Text(label, style: chipTextStyle), onPressed: () => onTap(), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), selected: selected, showCheckmark: false, visualDensity: visualDensity, From b61d2305a93cbd12d910f83e7f7ed263fe938b47 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 17 Jun 2024 23:51:01 +0800 Subject: [PATCH 003/152] =?UTF-8?q?opt:=20=E5=AD=97=E5=B9=95=E7=B1=BB?= =?UTF-8?q?=E5=88=AB=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/common/subtitle_type.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/models/common/subtitle_type.dart b/lib/models/common/subtitle_type.dart index 54b52e8e..3db43dd2 100644 --- a/lib/models/common/subtitle_type.dart +++ b/lib/models/common/subtitle_type.dart @@ -9,6 +9,8 @@ enum SubtitleType { zhHans, // 英文(美国) enUS, + // 中文繁体 + zhTW, } extension SubtitleTypeExtension on SubtitleType { @@ -24,6 +26,8 @@ extension SubtitleTypeExtension on SubtitleType { return '中文(简体)'; case SubtitleType.enUS: return '英文(美国)'; + case SubtitleType.zhTW: + return '中文(繁体)'; } } } @@ -41,6 +45,8 @@ extension SubtitleIdExtension on SubtitleType { return 'zh-Hans'; case SubtitleType.enUS: return 'en-US'; + case SubtitleType.zhTW: + return 'zh-TW'; } } } @@ -58,6 +64,8 @@ extension SubtitleCodeExtension on SubtitleType { return 4; case SubtitleType.enUS: return 5; + case SubtitleType.zhTW: + return 6; } } } From 35d2d92480be22636e80b091ba381f237df9ff0b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 18 Jun 2024 23:35:38 +0800 Subject: [PATCH 004/152] =?UTF-8?q?fix:=20=E5=88=86=E9=9B=86=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=AD=97=E5=B9=95=E6=9C=AA=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/bangumi/introduction/controller.dart | 2 ++ lib/pages/video/detail/introduction/controller.dart | 1 + 2 files changed, 3 insertions(+) diff --git a/lib/pages/bangumi/introduction/controller.dart b/lib/pages/bangumi/introduction/controller.dart index 65cd5dd8..208a85e4 100644 --- a/lib/pages/bangumi/introduction/controller.dart +++ b/lib/pages/bangumi/introduction/controller.dart @@ -225,6 +225,8 @@ class BangumiIntroController extends GetxController { videoDetailCtr.oid.value = aid; videoDetailCtr.cover.value = cover; videoDetailCtr.queryVideoUrl(); + videoDetailCtr.getSubtitle(); + videoDetailCtr.setSubtitleContent(); // 重新请求评论 try { /// 未渲染回复组件时可能异常 diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 9c542f21..1a1b1b74 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -436,6 +436,7 @@ class VideoIntroController extends GetxController { videoDetailCtr.cover.value = cover; videoDetailCtr.queryVideoUrl(); videoDetailCtr.getSubtitle(); + videoDetailCtr.setSubtitleContent(); // 重新请求评论 try { /// 未渲染回复组件时可能异常 From 54c38d8683acee6e6c81c75afaf0e4ec7573bfd6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 19 Jun 2024 23:17:36 +0800 Subject: [PATCH 005/152] =?UTF-8?q?opt:=20=E5=AD=97=E5=B9=95=E7=B1=BB?= =?UTF-8?q?=E5=88=AB=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/common/subtitle_type.dart | 24 ++++++ .../video/detail/widgets/header_control.dart | 75 ++++++++++--------- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/lib/models/common/subtitle_type.dart b/lib/models/common/subtitle_type.dart index 3db43dd2..ac3ee3e0 100644 --- a/lib/models/common/subtitle_type.dart +++ b/lib/models/common/subtitle_type.dart @@ -11,6 +11,12 @@ enum SubtitleType { enUS, // 中文繁体 zhTW, + // + en, + // + pt, + // + es, } extension SubtitleTypeExtension on SubtitleType { @@ -28,6 +34,12 @@ extension SubtitleTypeExtension on SubtitleType { return '英文(美国)'; case SubtitleType.zhTW: return '中文(繁体)'; + case SubtitleType.en: + return '英文'; + case SubtitleType.pt: + return '葡萄牙语'; + case SubtitleType.es: + return '西班牙语'; } } } @@ -47,6 +59,12 @@ extension SubtitleIdExtension on SubtitleType { return 'en-US'; case SubtitleType.zhTW: return 'zh-TW'; + case SubtitleType.en: + return 'en'; + case SubtitleType.pt: + return 'pt'; + case SubtitleType.es: + return 'es'; } } } @@ -66,6 +84,12 @@ extension SubtitleCodeExtension on SubtitleType { return 5; case SubtitleType.zhTW: return 6; + case SubtitleType.en: + return 7; + case SubtitleType.pt: + return 8; + case SubtitleType.es: + return 9; } } } diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index f51edff1..5072377a 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -433,42 +433,47 @@ class _HeaderControlState extends State { return AlertDialog( title: const Text('选择字幕'), contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 18), - content: StatefulBuilder(builder: (context, StateSetter setState) { - return len == 0 - ? const SizedBox( - height: 60, - child: Center( - child: Text('没有字幕'), - ), - ) - : Column( - mainAxisSize: MainAxisSize.min, - children: [ - RadioListTile( - value: -1, - title: const Text('关闭字幕'), - groupValue: tempThemeValue, - onChanged: (value) { - tempThemeValue = value!; - widget.controller?.toggleSubtitle(value); - Get.back(); - }, + content: StatefulBuilder( + builder: (context, StateSetter setState) { + return len == 0 + ? const SizedBox( + height: 60, + child: Center( + child: Text('没有字幕'), ), - ...widget.videoDetailCtr!.subtitles - .map((e) => RadioListTile( - value: e.code, - title: Text(e.title), - groupValue: tempThemeValue, - onChanged: (value) { - tempThemeValue = value!; - widget.controller?.toggleSubtitle(value); - Get.back(); - }, - )) - .toList(), - ], - ); - }), + ) + : SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + RadioListTile( + value: -1, + title: const Text('关闭字幕'), + groupValue: tempThemeValue, + onChanged: (value) { + tempThemeValue = value!; + widget.controller?.toggleSubtitle(value); + Get.back(); + }, + ), + ...widget.videoDetailCtr!.subtitles + .map((e) => RadioListTile( + value: e.code, + title: Text(e.title), + groupValue: tempThemeValue, + onChanged: (value) { + tempThemeValue = value!; + widget.controller + ?.toggleSubtitle(value); + Get.back(); + }, + )) + .toList(), + ], + ), + ); + }, + ), ); }); } From 1db1d8f598ac4b7236f2abe8e9fc7eab3074602c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 20 Jun 2024 23:38:49 +0800 Subject: [PATCH 006/152] =?UTF-8?q?feat:=20=E7=82=B9=E5=87=BBtab=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E9=A1=B6=E9=83=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/controller.dart | 12 ++++++++++++ lib/pages/video/detail/reply/view.dart | 3 +++ lib/pages/video/detail/view.dart | 2 ++ 3 files changed, 17 insertions(+) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 38c62d7e..ea85a5b9 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -109,6 +109,7 @@ class VideoDetailController extends GetxController ].obs; RxDouble sheetHeight = 0.0.obs; RxString archiveSourceType = 'dash'.obs; + ScrollController? replyScrillController; @override void onInit() { @@ -551,4 +552,15 @@ class VideoDetailController extends GetxController cover.value = videoItem['pic'] = pic; } } + + void onControllerCreated(ScrollController controller) { + replyScrillController = controller; + } + + void onTapTabbar(int index) { + if (index == 1 && tabCtr.index == 1) { + replyScrillController?.animateTo(0, + duration: const Duration(milliseconds: 300), curve: Curves.ease); + } + } } diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 653fe7e0..be1bd331 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -19,12 +19,14 @@ class VideoReplyPanel extends StatefulWidget { final int? oid; final int rpid; final String? replyLevel; + final Function(ScrollController)? onControllerCreated; const VideoReplyPanel({ this.bvid, this.oid, this.rpid = 0, this.replyLevel, + this.onControllerCreated, super.key, }); @@ -68,6 +70,7 @@ class _VideoReplyPanelState extends State _futureBuilderFuture = _videoReplyController.queryReplyList(); scrollController = ScrollController(); + widget.onControllerCreated?.call(scrollController); fabAnimationCtr.forward(); scrollListener(); } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 22271c2b..ea29bf78 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -387,6 +387,7 @@ class _VideoDetailPageState extends State dividerColor: Colors.transparent, tabs: vdCtr.tabs.map((String name) => Tab(text: name)).toList(), + onTap: (index) => vdCtr.onTapTabbar(index), ), ), ), @@ -676,6 +677,7 @@ class _VideoDetailPageState extends State () => VideoReplyPanel( bvid: vdCtr.bvid, oid: vdCtr.oid.value, + onControllerCreated: vdCtr.onControllerCreated, ), ) ], From c5247b27c7b478bd15912d18521f1f13448a078b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 23 Jun 2024 23:12:04 +0800 Subject: [PATCH 007/152] =?UTF-8?q?mod:=20=E6=B6=88=E6=81=AF=E8=AE=A1?= =?UTF-8?q?=E6=95=B0=E6=B8=85=E9=9B=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/whisper/view.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index fccdd844..e31e942e 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -69,7 +69,14 @@ class _WhisperPageState extends State { children: [ ..._whisperController.noticesList.map((element) { return InkWell( - onTap: () => Get.toNamed(element['path']), + onTap: () { + Get.toNamed(element['path']); + + if (element['count'] > 0) { + element['count'] = 0; + } + _whisperController.noticesList.refresh(); + }, onLongPress: () {}, borderRadius: StyleString.mdRadius, child: Column( From 4fa9bdc3804c4ca3f4bd2e33973b42d7969c532a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 23 Jun 2024 23:23:05 +0800 Subject: [PATCH 008/152] =?UTF-8?q?mod:=20chatItem=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../whisper_detail/widget/chat_item.dart | 90 ++++++++----------- 1 file changed, 39 insertions(+), 51 deletions(-) diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index f64cf223..77e38073 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -1,4 +1,5 @@ // ignore_for_file: must_be_immutable +// ignore_for_file: constant_identifier_names import 'dart:convert'; import 'package:flutter/material.dart'; @@ -8,7 +9,6 @@ import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/storage.dart'; - import '../../../http/search.dart'; enum MsgType { @@ -409,12 +409,6 @@ class ChatItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(width: safeDistanceval), - if (isOwner) - Text( - Utils.dateFormat(item.timestamp), - style: Theme.of(context).textTheme.labelSmall!.copyWith( - color: Theme.of(context).colorScheme.outline), - ), Container( constraints: const BoxConstraints( maxWidth: 300.0, // 设置最大宽度为200.0 @@ -444,51 +438,45 @@ class ChatItem extends StatelessWidget { right: 8, ), padding: const EdgeInsets.all(paddingVal), - child: messageContent(context), - // child: Column( - // crossAxisAlignment: isOwner - // ? CrossAxisAlignment.end - // : CrossAxisAlignment.start, - // children: [ - // messageContent(context), - // SizedBox(height: isPic ? 7 : 2), - // Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Text( - // Utils.dateFormat(item.timestamp), - // style: Theme.of(context) - // .textTheme - // .labelSmall! - // .copyWith( - // color: isOwner - // ? Theme.of(context) - // .colorScheme - // .onPrimary - // .withOpacity(0.8) - // : Theme.of(context) - // .colorScheme - // .onSecondaryContainer - // .withOpacity(0.8)), - // ), - // item.msgStatus == 1 - // ? Text( - // ' 已撤回', - // style: - // Theme.of(context).textTheme.labelSmall!, - // ) - // : const SizedBox() - // ], - // ) - // ], - // ), - ), - if (!isOwner) - Text( - Utils.dateFormat(item.timestamp), - style: Theme.of(context).textTheme.labelSmall!.copyWith( - color: Theme.of(context).colorScheme.outline), + child: Column( + crossAxisAlignment: isOwner + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + messageContent(context), + SizedBox(height: isPic ? 7 : 4), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + Utils.dateFormat(item.timestamp), + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith( + color: isOwner + ? Theme.of(context) + .colorScheme + .onPrimary + .withOpacity(0.8) + : Theme.of(context) + .colorScheme + .onSecondaryContainer + .withOpacity(0.8)), + ), + item.msgStatus == 1 + ? Text( + ' 已撤回', + style: Theme.of(context) + .textTheme + .labelSmall!, + ) + : const SizedBox() + ], + ) + ], ), + ), const SizedBox(width: safeDistanceval), ], ), From abfb7a14391513474d30a7f19875b008c04b0931 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 24 Jun 2024 23:04:32 +0800 Subject: [PATCH 009/152] =?UTF-8?q?fix:=20=E8=AF=84=E8=AE=BA=E5=8C=BA=20/?= =?UTF-8?q?=3F=20=E9=93=BE=E6=8E=A5=E8=B7=B3=E8=BD=AC=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/reply/widgets/reply_item.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 08e4d405..02d004e8 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -645,7 +645,7 @@ InlineSpan buildContent( '', ); } else { - Uri uri = Uri.parse(matchStr); + Uri uri = Uri.parse(matchStr.replaceAll('/?', '?')); SchemeEntity scheme = SchemeEntity( scheme: uri.scheme, host: uri.host, From cc32224daf5c2304999d1dfe0952b6b41395c924 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 24 Jun 2024 23:43:37 +0800 Subject: [PATCH 010/152] =?UTF-8?q?feat:=20=E9=9F=B3=E9=A2=91=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E6=96=B9=E5=BC=8F=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/video/play/ao_output.dart | 6 ++++++ lib/pages/setting/play_setting.dart | 29 ++++++++++++++++++++++++++++ lib/plugin/pl_player/controller.dart | 9 ++++++++- lib/utils/storage.dart | 2 ++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 lib/models/video/play/ao_output.dart diff --git a/lib/models/video/play/ao_output.dart b/lib/models/video/play/ao_output.dart new file mode 100644 index 00000000..170a78c5 --- /dev/null +++ b/lib/models/video/play/ao_output.dart @@ -0,0 +1,6 @@ +final List aoOutputList = [ + {'title': 'audiotrack,opensles', 'value': '0'}, + {'title': 'opensles,audiotrack', 'value': '1'}, + {'title': 'audiotrack', 'value': '2'}, + {'title': 'opensles', 'value': '3'}, +]; diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index 4a8495e5..0f7dcdc3 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/models/video/play/ao_output.dart'; import 'package:pilipala/models/video/play/quality.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; @@ -28,6 +29,7 @@ class _PlaySettingState extends State { late dynamic defaultDecode; late int defaultFullScreenMode; late int defaultBtmProgressBehavior; + late String defaultAoOutput; @override void initState() { @@ -44,6 +46,8 @@ class _PlaySettingState extends State { defaultValue: FullScreenMode.values.first.code); defaultBtmProgressBehavior = setting.get(SettingBoxKey.btmProgressBehavior, defaultValue: BtmProgresBehavior.values.first.code); + defaultAoOutput = + setting.get(SettingBoxKey.defaultAoOutput, defaultValue: '0'); } @override @@ -263,6 +267,31 @@ class _PlaySettingState extends State { } }, ), + ListTile( + dense: false, + title: Text('音频输出方式', style: titleStyle), + subtitle: Text( + '当前输出方式 ${aoOutputList.firstWhere((element) => element['value'] == defaultAoOutput)['title']}', + style: subTitleStyle, + ), + onTap: () async { + String? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '音频输出方式', + value: defaultAoOutput, + values: aoOutputList, + ); + }, + ); + if (result != null) { + defaultAoOutput = result; + setting.put(SettingBoxKey.defaultAoOutput, result); + setState(() {}); + } + }, + ), ListTile( dense: false, title: Text('默认全屏方式', style: titleStyle), diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 2865a117..a614d75d 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -13,6 +13,7 @@ import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:ns_danmaku/ns_danmaku.dart'; import 'package:pilipala/http/video.dart'; +import 'package:pilipala/models/video/play/ao_output.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/services/service_locator.dart'; @@ -453,7 +454,13 @@ class PlPlayerController { // 音量不一致 if (Platform.isAndroid) { await pp.setProperty("volume-max", "100"); - await pp.setProperty("ao", "audiotrack,opensles"); + String defaultAoOutput = + setting.get(SettingBoxKey.defaultAoOutput, defaultValue: '0'); + await pp.setProperty( + "ao", + aoOutputList + .where((e) => e['value'] == defaultAoOutput) + .first['title']); } await player.setAudioTrack( diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 4a163446..bf9074e3 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -102,6 +102,8 @@ class SettingBoxKey { autoPiP = 'autoPiP', enableAutoLongPressSpeed = 'enableAutoLongPressSpeed', enablePlayerControlAnimation = 'enablePlayerControlAnimation', + // 默认音频输出方式 + defaultAoOutput = 'defaultAoOutput', // youtube 双击快进快退 enableQuickDouble = 'enableQuickDouble', From 7aa02be25197df858b32c5bfe7eb3fbc8f8dd1f8 Mon Sep 17 00:00:00 2001 From: guozhibin Date: Tue, 25 Jun 2024 23:51:32 +0800 Subject: [PATCH 011/152] mod --- assets/images/coin.png | Bin 7159 -> 1701 bytes lib/pages/message/reply/view.dart | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/images/coin.png b/assets/images/coin.png index bc2952a7a5cf519a331b83c0f268eef88769d471..afca87b286cdc1f677aef1cf5c80f5d4fc4d980e 100644 GIT binary patch literal 1701 zcmV;W23q-vP)JA z04>JA0Xnmvc>n+gS4l)cRCt{2n|p}WMHI(BdpC6}ZBf&-4K=OYinQEHi?X*;TU1Cz z_9C*9!U&_VEdMF!K}3J_pe1^1DN$`pN*ku;J0GpRU-Zz3-nwmB`;G(W!ms1}?(cVZ z=H9)pc(i}+b-Z+ zU_G!1xC595RPrCi!N5FV1JDk1_pr4BtALwbOBbg*G|sW7VkHKppUL!otY^EddVf z4a3XSn3f#Mi9@>q80j(3XkaezUXI214Y-sdVjtkS7-c$umw=iAEci)4Lym=b92h`> zF*q1LcoHZ`E`y--YgNOHdM)y)o+#FFkUDFo^c3&HITz83v_h$29`PWe9~9& z)efPJfs8*3_!`&_oEK=Pu5jpkMNb8eQU@0m_EErX|7Aq(_e!OGAveIMM(15>OWt_h z2olnBIN`i_i{TIb0?9rS_|xbvZ-~4w%|SSXqK3j4bA`#{>5zc(Z*&}m>1hXN^vxa} zygI-Z6=yd?iInTLC=0$Bm>FQMa@9F1e{))gJKYQvWsmh#Xt3i*^s=WS`|0X;=h;38Kx>y6nQtWx7^My=x-BPrD)EBDxK2H0!ZDuCaV%c(oO_ay zC$^2tHQ-h=V3Vf;$2**b%x;p5rm548I;u9a{SU$q_5pQfiZLgcF%m8I!2u>PE5|_oZ4aF7a6&%FXtX9ev4p{r zuov)mWL$fIYjKmY?Lt?lI~@@gAIP2igV|AO<9Ayl<37V54;kC;b#;2tbh<9U#4C-= z+ft4@LW8Z#0!5iFOGAw}nog$#nD`bWQ|Rv*6d%W>Io>-=C*kY95Xbtnk=w4vYqGv9 z3m(W+rx(4Be|2%^W^-PJ!G*vjIU>28nj74%r04PwQJk27NvoOnU+AxjWIMD4R%C7- z>9TJE1xTzzwgJ?lE=q^QjVEp^pFqZiYGvZQAQLo(PncGLddUWD6G`BP7WO!;D>W1H{_3!Avd% z4nC-JoM&NWaS&Skjv2GiQ-@hbo)lZeVxH+Nu{s}2anRtk`r5NN6nNXg2Tu3Y!7ixy zx}4>0QOeq-fkhuIav0U2i(98*v(+)AsNOSqI>CGZvF)&=L5)TUi4*vsQny`qo=w7O z`+Ls4(8!eJX(DPJU&}WrF(gGbS+p$=zyK$j>r~qLX$EFYNUB@Urf(i!1;I$*3&$i|ls)zq zsx`RbB2mGsMMuXh!cNV+6th$|qnWFg7~>1}>yj!5Ns3-I@El|L#{G6{ zv-yCewpBAtC53WjbZdI>E3h=JL(AN1OYvuOu6o_Of%iRsL)f9u=lMzHZKaGV-5K|5 vP(+T3@c-z-$lt%{Lq-xQ_W&0Z@=E&$KxEtUmHWF=00000NkvXXu0mjfN2xS6 literal 7159 zcmai3XEa<vI?2oo<*AX$uJ|9 zbE)c;5wTPqLBNLDXyI`F_$sdFW^)=nesPzJh7-d7Tk-$I#4N)}`MRL7DXk^Ce63JJ z$@|x~Dt)%t|F(sSB|<)Jwbk{iJW~8(_(!4PBksrHCfo7KN9jac37>Zv1N(QM&AyBK zr6Q{qIyv=nqi|5paxTn8tu9Ps`T@6=^q%B)@V@A&uz#M=&%bz?S5^*F<32*K3>Lq3 zVXCYAgH*bFr5u6k<-WPOe*5v6)S&0*WP^jaVU`j396F&bw*nD7yp@`KZ zf_Bfei3C%9{_H8dcljj8=IFOlR=nC(1>f%J2`k5Joj9#YKV0weokY&;Ba0H6&ZavJ z-`kJUtqiVj-R64NcxCU+tIcgOuD!4d!s@}{U5PNqxU%EJs~0&lomZ&`ck18=J=A); zb?~oe*?p-K*30XWEGdh}g@sRLjkpq!)vYGro@xZx9t*R_g`PET$k5@)GQTZ9J^Ip3 zhi=jp_$_R~_P|jrsiCnAy?(ms>v?kAYr%H%_Zc&~C#(w6&{!%z0lH5u2k{X5QrdV? z>xSUJRZ<+M1Tb_)0bVO6@f26cL}69`JOaG|*3rf&If+G(*Y-T_=f3;wHgy@fWS)E& zHluGHXednEwVc-1VNG-0^Lk9BHQQqGkTFM?w(8#C9bi$K>jVvL?rb^II?Ez=s94`P z|DpD2@$y-3KQj|!lYV#<|FExhgWWbZY_vAw`%md~ed)g9;CYj2=Oa~3!(Ri{A-X8v zpx$XkRN%?|H7(bPeTDT$U2Rn5Pt08M4%Dr=hUddQ3bO}vgt~_!e--cCO+GX`r6@mF z4)W?sy!%7r`bKCnc77>HZbRWXU4Qf!i{94oamm_O@#VKk*x?6i_IbSE{(qqL@pX>6g6tMuMySM63bXjKKQW`zugF43{dpMj=NYW(uw%8RJ+=#W zI?~pKSmdm9yVR-Bt?)~To7;!c|EzB$#BF(@wND79z8upagtno+y&?wL1Om$oC)Mkz?W>meIH|OtG$Eu09UW5x;^+{3Pb=)_j+O07=RCt<= zvDJ3?+-3010YS=Mw`{sCT}H(Vj8IsLVh$DT(sSUnyxOSFSSV_e;wcryI^ye~*LfKWB*0r%2$vcM19va@SGLQVv5rKjb7b-dKIVwZtba{LV?Nauw z4~NDSM}5AC{0d;OV4IsBZW5tZ;O)rno#Li7l~M2ob00O-f|9b_@h@czYKS^W*^iiF z8Esz>@pwWp6&sLaJH&b$=P@Q_@`O+0$9gxKdvw2I16+{7=P&FjN*)+bsfk0h^`^>Y$s z1Jl{Rze6gqdDR;=?7fNt1SYa`L%)B^8jU_a>8lF|`!Pxs@J@1uy!=`;zD(PFYkhQm zhqhbq(~@455`m}W;==ln7oTFf1hALu97-*24?!-b8%C-e2;|NQ_$GFB(7W9kU59@N z)6IT0UUMUwXIY{111C|1&)!n|fcE;n)ld*K>*r#LNCb zNDed2a>}ny*$^chgGqo-gJcIKPu7@TPaB?3sSNzuW`>pPiJa;N)ml43RU@x~Hp$PD<*cS3_SGnAH+P{(iOgzWiuelm zy;)VB^g6EwzBe=8r-$}bJ#h>aWW=$aC%E=(R+|mAJJx?}+2nS&yHE|}r}<_O$y7J4+{c!?hJv;GL$Qx-qLBx5v2?txK2h=+ z#IJ9zS)3%_ZJg0of3vwC)xJ?KRVM)R#<{Bf{4r!98A36E&b~=zN8rh9apQ~mfI4Q& zl9EWHY{TE+aQnRZ(ZYtnlilpbM`D~A4WP5JcJ8<{ngMj24hFJ2N!k0C=cAl0K_eTZ zt8|s*nLV>nHyCjHRFUN@+2|^Xsh8`ja9RhsWa#$(OkM0^OMr3Y;K>6dp&Ht75<@Zu z!i%bN`?*JQ>S830vo~#l&QomI(bRIh+cRbQtH7p$vyzgKBnH%0<3Uf&ad9=e!JCdq z3GoodNF`M%Vg$Llzj0}f4?8ZoS{E893BBNrRYj*bm2z1af$&Yz8ZC2_;QFcc`ApgfE@m_6@j3NSowGS>vcBjyf(feK zguOKg%X=Epv;?eJ+yLx6jm>F-Z|6@5{qjzVj^P>QWv%w?sg#7s6!o6mOL9(b0!`1eQFzt7 z{wCnv&gTR~k6phrGQZ$|4P~nvHPiM!DZ~n_A z4>-!;5yStQTKT3*2Kz~0@txrO*UwltY>P7MX&8gB&2Mv=Hi}hpSgS2nMz!YE`f5Ef z{To&zQPW?zohc;Mu)k6;!g$Kb=dAW1yDb~x4;J!$>T4B-g1L<;8z3Gtz~?ZVwo}`` z=ZR*?Qcv>#U{5ZNoB%Q-6uz2$(=haqpperZP%$u8q0qYzlW+dzeMb5Y7yMbAqHDPV z(B&ZfhXSTXKyp*q95nLy;jE|on>S!zy+fOTNawa$7Y>dMfN84C=(Sq_a%JpNs?7K` zirCMB2yi#NhlHr9!=s0!G-)VgFvhdPgvzLtSD?9(#7 znZYH~qFt1M0SiOU-+rLWm4ie#d<9N|E^=bIWoiab4f_7G_PYrz?5(ra+nNWJai!Kc z3aAA~oIkx_?jG~w{0cA(e4zp1eZV@bu|{J=jG4VOfemZCpr|~y(d)%EkFFp$ZlJ*z zoLsvDr!_gZTSohkQ^p57p>ok9&S$YJ&5y|noJ*tq%AeBFvt6sx#r{cN`tjxMZ*B}V z{_5IGl{Fr$+g(bV$EEZyw^fc$uNf-4KfC9~T(opPy!jgNsMDM6eRCdt$MBo+Lwm(6 ziUmXZY%ch<kP$X5Z#1YG)4EL>;v@HohmWj$TvEO`G}z} zYVkwBeo&MYQ2CQ&A=sgbW-$xE#U&$-Z@>PagwI9ia<@<66W-Ac(9Vwddl_R5h!CBc zG3K{kL(FVG?M8#RxCzhOZWWJJ04VgU`hks8Ysy(&VTKk<4&!z~2Y_BQSX~)E1!xDw zjZhS5iqhhNJa9`oI76`NxlhZBOVLv^26!eH;Xb15DkKN2{*^+x6nt@)5w3)i&RHwt zQboiRTkG>>88hLm&*R^zDW)<%LjjT^#IZmUj4A#$wo7v>s@Dt)x+uDf{hLTspnLvV6%+(ehY}w; zmDXjAH9@v_To}8w#qmpTPoIYf&1}4u>wMgSLK%m!^H6Hvqp>?#&;ipCmB69~I= zP@-4DjsyVY4qWP|N}u$C6Pa zyr3P!P383j*$5`MBdZ8j*h!B!{YKGMTkJI?Nr;+j0dNJVw(v2(QWOtGChlmb^a6lg zC*Vy`187H|qcuIw&&>zKib3?2@B4d=I3f|tE=TS#Lb6{FoXnnV8(!p+{|LB5$;Az{ z2A+ikYYI^lb@B4v0??(6Z)6L!Nz#a(@Wm~_d`BeR`P(5|EYG6-v%-`dpfFoZx|R)= zhuTCF&VuQe4f!WSUSJRO1L?LT@x~wn`^b_8B1pf-g^J8Y=6W3qP?Ix+MF#y5GYXVs zzKgYC?AM?$J5&V5U}?^{KJMlrn%Mf;XlVM-kfb*+_j1mf#LdW(1kI9glLva(V}Ras zF{)ERm`uhjH$m8G=je1&Wr!^4y>g`>nKHEF2GZ3)d-^%Q6Cjra<`%`BR@x6yDmBy{ zHxkQrZYkWy{uxyWN-Ck&iKNU~( zWp)o*2#?mOWTYj1%yvQZY>#qXKaek|QheO(^CysR0;xjTK}l3c){!iQu@FM}trG|C zqY5YrCR2^jqeeTmTMjzX|*d>wip!(kJ z!p}ZGWEgbn^dT6EXIQ{dF&V4f$)GFc1>o6F*#N?qI-mq9NVB>2W%IJvD((_Ov>zC; zu{ehOwMC3t{exWrr>uB6A3XckT-Ko<(By@ar-Nz~l#rN2z7VMueFfhr{wL{>qc8VT za4)%7;@3;z(18rs?VL0QEcpD3zelnAO%41*Jxptp02w{oQt!yIhddsb>ZGuGBcG;& zCe_KaJv4fb1KcqwV3SW|970g^gl~qV3WF7Ipei19>cs%90ZzXU1|?=Vpp*nBPwXgP8yQTOq*RrTP3P>%daQQbea)PLeks}Xrr+6LA*Ka(u5c}R5qmD52lqO8pTp!r6U29u7)!;n}G=UUpKi0-g@d0#GkcZcR&@3mbP6tml39i)NU^5%gvTlEWZ)^j?U!1zv=LD z!cwU^j(5y)=1!4??g7o-RxJ@awi=BZ4I`dZMR1?u?4Yfu((sKL{p)@z`uM8nN^5c^K%u4`D8*BuHq@?_UtvzAyTb`C1qB-=ha`Nx9t3_oxpo6^h1 zLM2H4wb#om6&YhU*B0U$ZCn{SJPNu9LdmLBv_B-54aox;Io4ZZr9HA#Llq594)q}s z+cQB$`Ze5Z{@TNP4~)m;&mmcs2b5Y(vE&1i%W@XGUhgHH_fpNnD!&_Ng!Rvttj~Ri zNGcPl#OH#pRBRk(9sH(aeY|t8Fn{1(j_%`RG1ESs!f+A&cf!ONSwps)>!a zVZ*@Vw=oUde@rIMA9m=!>^7h6(R}C;9$^*&KBeOG^$(0(%Gjin-dU9vwE021SmV3A z3pQF;IZ#Oc!*hWuKZ1EpRWFD{=m*1t#}vz7#veSv>qt)Ccay~l3?j}M_W^Sb> zanl})m_^+3yBa@tnNLoh!B$qRt9@J-uDM^O)Yw_e5fK!-#Hhf<4~$C1tYDZ+VR*j2 z{AXn_JknafKU@GSfFq;bO$J00#NCOj=YfX{vw@|#%eclfp=l9mVYiTjtw?C3fz@>` z64MaIIvWz}K}O^ZXYc3Z0QPjEN28^=OFvS~3bHRx_2$Opi5)U+-2V1_n7e#)<_Bm& zPx&(X!U8W>knt#XH`GULXw_N{>k=#HTX{r7$qnCfh zx#>2(>CSNv;(8i+@0HuB6L)}((vj_K;ACWkoW_Gkca-~j4<3I0x4&E;JFSuX-cPN1 zPTh(Ela&}+8UF9VvIY8T5A@WKUU-)00i*sUX0gY_>-?{$-IJLMSGW$I2ivT{Oio1U z9K+mo%PCIl%UPt$X72CNc{zD~9%J>anzm~xLt4ovwS|KGIL&hCdKI7CUGl6pHbbD{R_;~c|h%se}} zrD8#cvGbkY-XL9k?27}j;imls*~h=d@@E$;iY=ulsFvNxE(CR+#E=Z4$~mA#@or`07naUm?|czbR&_QxkoW|i^+tLG?WF2;EYGN+Xu zq{gQryB(ta#Js$)LG(M@QD^CaMY$*DJ?*4ZBwR+z1Jj!7e9TGb-_M0z8I+;Gv^lR? z)8teeQF2KBc46zCd*)gEhIaC%ql<(yDSIs+^?_Cn;Kf;qd{u&}$2A5?Bs==_6Q>lD z{wGM17^~LvGEx5({$?LdGm?b%>LS}?NmC1#2Asc6Xn(J@63(7#_mS#17$zM&d#6xK zRZYf(wPmrB);c{Isu~g(iD{%>353^R*Ho(LUkQ;?-yx zwd~$Igjqu3uA7dwQG^J?w=jk!&+A2roChWuZy(m~Q(KKkC8DPWUb}`eKP5Kkiu(Ac ztUt)act5wB=+d~|3OuN7HtL$%QB0$rT6|^H@07(_lJNDBf7^$r+dNc$%~nDcwuQAx zsqVpgo+C)J1?jgwX)jscx&ElNw3f?%S@~`1<8Xo56>eiau0M1B+ZQO?|Gpn1&hT(C l{=<}~mvg`V-P`>?i}iJ^lzFK-(e&S({%sST25l$Qe*pGzPR;-T diff --git a/lib/pages/message/reply/view.dart b/lib/pages/message/reply/view.dart index 881f8650..9776c7f7 100644 --- a/lib/pages/message/reply/view.dart +++ b/lib/pages/message/reply/view.dart @@ -196,7 +196,7 @@ class ReplyItem extends StatelessWidget { style: TextStyle(color: outline), ), const SizedBox(width: 16), - Text('回复', style: TextStyle(color: outline)), + // Text('回复', style: TextStyle(color: outline)), ], ) ], From 0031b229021429318174eed48303312837c89499 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 26 Jun 2024 22:18:49 +0800 Subject: [PATCH 012/152] =?UTF-8?q?mod:=20=E6=9C=AA=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=85=B3=E9=97=AD=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/whisper/controller.dart | 2 +- lib/pages/whisper/view.dart | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pages/whisper/controller.dart b/lib/pages/whisper/controller.dart index 2614bf5a..749a3482 100644 --- a/lib/pages/whisper/controller.dart +++ b/lib/pages/whisper/controller.dart @@ -17,7 +17,7 @@ class WhisperController extends GetxController { }, { 'icon': Icons.alternate_email, - 'title': '@ 我的', + 'title': '@我的', 'path': '/messageAt', 'count': 0, }, diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index e31e942e..9436e2be 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -1,5 +1,6 @@ import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/skeleton/skeleton.dart'; @@ -70,6 +71,11 @@ class _WhisperPageState extends State { ..._whisperController.noticesList.map((element) { return InkWell( onTap: () { + if (['/messageAt', '/messageSystem'] + .contains(element['path'])) { + SmartDialog.showToast('功能开发中'); + return; + } Get.toNamed(element['path']); if (element['count'] > 0) { From dbfc31a1dff498d11d4e76f8fc3ec1f5f88ea728 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 26 Jun 2024 22:29:11 +0800 Subject: [PATCH 013/152] =?UTF-8?q?v1.0.24=20=E6=9B=B4=E6=96=B0=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- change_log/1.0.24.0626.md | 23 +++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 change_log/1.0.24.0626.md diff --git a/change_log/1.0.24.0626.md b/change_log/1.0.24.0626.md new file mode 100644 index 00000000..d9a8892f --- /dev/null +++ b/change_log/1.0.24.0626.md @@ -0,0 +1,23 @@ +## 1.0.24 + +### 功能 ++ 私信功能 ++ 回复我的、收到的赞查看 ++ 新的登录方式 ++ 全屏选集 ++ 一键三连 ++ 按分区搜索 + +### 优化 ++ 页面跳转动画 ++ 评论区跳转 + +### 修复 ++ 音画不同步问题 ++ 分集字幕未同步 ++ 多语言字幕 ++ 弹幕设置未生效 ++ + + +问题反馈、功能建议请查看「关于」页面。 diff --git a/pubspec.yaml b/pubspec.yaml index 66187abf..09169473 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.23+1023 +version: 1.0.24+1024 environment: sdk: ">=3.0.0 <4.0.0" From 7301673783dfde6c6ef6114a77ff57cc4db8e782 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 28 Jun 2024 23:24:42 +0800 Subject: [PATCH 014/152] =?UTF-8?q?mod:=20=E8=A7=86=E9=A2=91=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E5=8C=BA=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/introduction/view.dart | 21 +++++++++++-------- .../introduction/widgets/action_item.dart | 1 + 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 93cc26c9..1e8d97f1 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -2,6 +2,7 @@ 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'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; @@ -629,11 +630,12 @@ class _VideoInfoState extends State with TickerProviderStateMixin { child: Icon( key: ValueKey(likeStatus), likeStatus - ? Icons.thumb_up - : Icons.thumb_up_alt_outlined, + ? FontAwesomeIcons.solidThumbsUp + : FontAwesomeIcons.thumbsUp, color: likeStatus ? colorScheme.primary : colorScheme.outline, + size: 21, ), ), const SizedBox(height: 6), @@ -663,7 +665,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { (const IconThemeData.fallback().size! + 5) / 2, child: progressWidget(_progress)), ActionItem( - icon: Image.asset('assets/images/coin.png', width: 30), + icon: const Icon(FontAwesomeIcons.b), + selectIcon: const Icon(FontAwesomeIcons.b), onTap: handleState(videoIntroController.actionCoinVideo), selectStatus: videoIntroController.hasCoin.value, text: widget.videoDetail!.stat!.coin!.toString(), @@ -681,8 +684,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { (const IconThemeData.fallback().size! + 5) / 2, child: progressWidget(_progress)), ActionItem( - icon: const Icon(Icons.star_border), - selectIcon: const Icon(Icons.star), + icon: const Icon(FontAwesomeIcons.star), + selectIcon: const Icon(FontAwesomeIcons.solidStar), onTap: () => showFavBottomSheet(), onLongPress: () => showFavBottomSheet(type: 'longPress'), selectStatus: videoIntroController.hasFav.value, @@ -692,7 +695,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ), ), 'watchLater': ActionItem( - icon: const Icon(Icons.watch_later_outlined), + icon: const Icon(FontAwesomeIcons.clock), onTap: () async { final res = await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid); @@ -702,15 +705,15 @@ class _VideoInfoState extends State with TickerProviderStateMixin { text: '稍后看', ), 'share': ActionItem( - icon: const Icon(Icons.share), + icon: const Icon(FontAwesomeIcons.shareFromSquare), onTap: () => videoIntroController.actionShareVideo(), selectStatus: false, text: '分享', ), 'dislike': Obx( () => ActionItem( - icon: const Icon(Icons.thumb_down_alt_outlined), - selectIcon: const Icon(Icons.thumb_down), + icon: const Icon(FontAwesomeIcons.thumbsDown), + selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown), onTap: () {}, selectStatus: videoIntroController.hasDisLike.value, text: '不喜欢', diff --git a/lib/pages/video/detail/introduction/widgets/action_item.dart b/lib/pages/video/detail/introduction/widgets/action_item.dart index 3288b2fa..fd0b9fef 100644 --- a/lib/pages/video/detail/introduction/widgets/action_item.dart +++ b/lib/pages/video/detail/introduction/widgets/action_item.dart @@ -51,6 +51,7 @@ class ActionItem extends StatelessWidget { color: selectStatus ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.outline, + size: 20, ) : Image.asset( key: ValueKey(selectStatus), From 6a28ccbf64f1f56382209ddfb84bf41ed8e49b52 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 28 Jun 2024 23:44:48 +0800 Subject: [PATCH 015/152] =?UTF-8?q?fix:=20=E8=AF=84=E8=AE=BA=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/reply/widgets/reply_item.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 02d004e8..ebb266cd 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -700,9 +700,10 @@ InlineSpan buildContent( ); // 只显示一次 matchedStrs.add(matchStr); - } else if (content - .topicsMeta[matchStr.substring(1, matchStr.length - 1)] != - null) { + } else if (content.topicsMeta.keys.isNotEmpty && + matchStr.length > 1 && + content.topicsMeta[matchStr.substring(1, matchStr.length - 1)] != + null) { spanChilds.add( TextSpan( text: matchStr, From a63a9a28d7df50d38a359373fc9dcd2a680a4e3f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 29 Jun 2024 13:53:43 +0800 Subject: [PATCH 016/152] =?UTF-8?q?feat:=20=E6=9C=80=E8=BF=91=E7=82=B9?= =?UTF-8?q?=E8=B5=9E=E7=9A=84=E8=A7=86=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/member.dart | 5 +- lib/models/member/like.dart | 210 ++++++++++++++++++++++++ lib/pages/member/controller.dart | 10 ++ lib/pages/member/view.dart | 62 +++++-- lib/pages/member/widgets/conis.dart | 8 +- lib/pages/member/widgets/like.dart | 31 ++++ lib/pages/member_like/widgets/item.dart | 96 +++++++++++ 7 files changed, 400 insertions(+), 22 deletions(-) create mode 100644 lib/models/member/like.dart create mode 100644 lib/pages/member/widgets/like.dart create mode 100644 lib/pages/member_like/widgets/item.dart diff --git a/lib/http/member.dart b/lib/http/member.dart index 1af0f9a4..20a2c728 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -1,5 +1,6 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/models/member/like.dart'; import '../common/constants.dart'; import '../models/dynamics/result.dart'; import '../models/follow/result.dart'; @@ -328,7 +329,9 @@ class MemberHttp { if (res.data['code'] == 0) { return { 'status': true, - 'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists']) + 'data': res.data['data']['list'] + .map((e) => MemberLikeDataModel.fromJson(e)) + .toList(), }; } else { return { diff --git a/lib/models/member/like.dart b/lib/models/member/like.dart new file mode 100644 index 00000000..df71e2ec --- /dev/null +++ b/lib/models/member/like.dart @@ -0,0 +1,210 @@ +class MemberLikeDataModel { + MemberLikeDataModel({ + this.aid, + this.videos, + this.tid, + this.tname, + this.pic, + this.title, + this.pubdate, + this.ctime, + this.desc, + this.state, + this.duration, + this.redirectUrl, + this.rights, + this.owner, + this.stat, + this.dimension, + this.cover43, + this.bvid, + this.interVideo, + this.resourceType, + this.subtitle, + this.enableVt, + }); + + final int? aid; + final int? videos; + final int? tid; + final String? tname; + final String? pic; + final String? title; + final int? pubdate; + final int? ctime; + final String? desc; + final int? state; + final int? duration; + final String? redirectUrl; + final Rights? rights; + final Owner? owner; + final Stat? stat; + final Dimension? dimension; + final String? cover43; + final String? bvid; + final bool? interVideo; + final String? resourceType; + final String? subtitle; + final int? enableVt; + + factory MemberLikeDataModel.fromJson(Map json) => + MemberLikeDataModel( + aid: json["aid"], + videos: json["videos"], + tid: json["tid"], + tname: json["tname"], + pic: json["pic"], + title: json["title"], + pubdate: json["pubdate"], + ctime: json["ctime"], + desc: json["desc"], + state: json["state"], + duration: json["duration"], + redirectUrl: json["redirect_url"], + rights: Rights.fromJson(json["rights"]), + owner: Owner.fromJson(json["owner"]), + stat: Stat.fromJson(json["stat"]), + dimension: Dimension.fromJson(json["dimension"]), + cover43: json["cover43"], + bvid: json["bvid"], + interVideo: json["inter_video"], + resourceType: json["resource_type"], + subtitle: json["subtitle"], + enableVt: json["enable_vt"], + ); +} + +class Dimension { + Dimension({ + required this.width, + required this.height, + required this.rotate, + }); + + final int width; + final int height; + final int rotate; + + factory Dimension.fromJson(Map json) => Dimension( + width: json["width"], + height: json["height"], + rotate: json["rotate"], + ); +} + +class Owner { + Owner({ + required this.mid, + required this.name, + required this.face, + }); + + final int mid; + final String name; + final String face; + + factory Owner.fromJson(Map json) => Owner( + mid: json["mid"], + name: json["name"], + face: json["face"], + ); +} + +class Rights { + Rights({ + required this.bp, + required this.elec, + required this.download, + required this.movie, + required this.pay, + required this.hd5, + required this.noReprint, + required this.autoplay, + required this.ugcPay, + required this.isCooperation, + required this.ugcPayPreview, + required this.noBackground, + required this.arcPay, + required this.payFreeWatch, + }); + + final int bp; + final int elec; + final int download; + final int movie; + final int pay; + final int hd5; + final int noReprint; + final int autoplay; + final int ugcPay; + final int isCooperation; + final int ugcPayPreview; + final int noBackground; + final int arcPay; + final int payFreeWatch; + + factory Rights.fromJson(Map json) => Rights( + bp: json["bp"], + elec: json["elec"], + download: json["download"], + movie: json["movie"], + pay: json["pay"], + hd5: json["hd5"], + noReprint: json["no_reprint"], + autoplay: json["autoplay"], + ugcPay: json["ugc_pay"], + isCooperation: json["is_cooperation"], + ugcPayPreview: json["ugc_pay_preview"], + noBackground: json["no_background"], + arcPay: json["arc_pay"], + payFreeWatch: json["pay_free_watch"], + ); +} + +class Stat { + Stat({ + required this.aid, + required this.view, + required this.danmaku, + required this.reply, + required this.favorite, + required this.coin, + required this.share, + required this.nowRank, + required this.hisRank, + required this.like, + required this.dislike, + required this.vt, + required this.vv, + }); + + final int aid; + final int view; + final int danmaku; + final int reply; + final int favorite; + final int coin; + final int share; + final int nowRank; + final int hisRank; + final int like; + final int dislike; + final int vt; + final int vv; + + factory Stat.fromJson(Map json) => Stat( + aid: json["aid"], + view: json["view"], + danmaku: json["danmaku"], + reply: json["reply"], + favorite: json["favorite"], + coin: json["coin"], + share: json["share"], + nowRank: json["now_rank"], + hisRank: json["his_rank"], + like: json["like"], + dislike: json["dislike"], + vt: json["vt"], + vv: json["vv"], + ); +} diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 0aa7166f..7db046d4 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -8,6 +8,7 @@ import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/member/archive.dart'; import 'package:pilipala/models/member/coin.dart'; import 'package:pilipala/models/member/info.dart'; +import 'package:pilipala/models/member/like.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:share_plus/share_plus.dart'; @@ -25,6 +26,7 @@ class MemberController extends GetxController { RxInt attribute = (-1).obs; RxString attributeText = '关注'.obs; RxList recentCoinsList = [].obs; + RxList recentLikeList = [].obs; @override void onInit() { @@ -208,6 +210,14 @@ class MemberController extends GetxController { return res; } + // 请求点赞视频 + Future getRecentLikeVideo() async { + if (userInfo == null) return; + var res = await MemberHttp.getRecentLikeVideo(mid: mid); + recentLikeList.value = res['data']; + return res; + } + // 跳转查看动态 void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid'); diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index bb0d92be..9c0da652 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -9,6 +9,7 @@ import 'package:pilipala/pages/member/index.dart'; import 'package:pilipala/utils/utils.dart'; import 'widgets/conis.dart'; +import 'widgets/like.dart'; import 'widgets/profile.dart'; import 'widgets/seasons.dart'; @@ -26,6 +27,7 @@ class _MemberPageState extends State late Future _futureBuilderFuture; late Future _memberSeasonsFuture; late Future _memberCoinsFuture; + late Future _memberLikeFuture; final ScrollController _extendNestCtr = ScrollController(); final StreamController appbarStream = StreamController(); late int mid; @@ -39,6 +41,7 @@ class _MemberPageState extends State _futureBuilderFuture = _memberController.getInfo(); _memberSeasonsFuture = _memberController.getMemberSeasons(); _memberCoinsFuture = _memberController.getRecentCoinVideo(); + _memberLikeFuture = _memberController.getRecentLikeVideo(); _extendNestCtr.addListener( () { final double offset = _extendNestCtr.position.pixels; @@ -162,6 +165,7 @@ class _MemberPageState extends State trailing: const Icon(Icons.arrow_forward_outlined, size: 19), ), + const Divider(height: 1, thickness: 0.1), /// 视频 ListTile( @@ -170,12 +174,10 @@ class _MemberPageState extends State trailing: const Icon(Icons.arrow_forward_outlined, size: 19), ), + const Divider(height: 1, thickness: 0.1), /// 专栏 - ListTile( - onTap: () {}, - title: const Text('Ta的专栏'), - ), + const ListTile(title: Text('Ta的专栏')), MediaQuery.removePadding( removeTop: true, removeBottom: true, @@ -218,12 +220,7 @@ class _MemberPageState extends State /// 最近投币 Obx( () => _memberController.recentCoinsList.isNotEmpty - ? ListTile( - onTap: () {}, - title: const Text('最近投币的视频'), - // trailing: const Icon(Icons.arrow_forward_outlined, - // size: 19), - ) + ? const ListTile(title: Text('最近投币的视频')) : const SizedBox(), ), MediaQuery.removePadding( @@ -257,13 +254,44 @@ class _MemberPageState extends State ), ), ), - // 最近点赞 - // ListTile( - // onTap: () {}, - // title: const Text('最近点赞的视频'), - // trailing: - // const Icon(Icons.arrow_forward_outlined, size: 19), - // ), + + /// 最近点赞 + Obx( + () => _memberController.recentLikeList.isNotEmpty + ? const ListTile(title: Text('最近点赞的视频')) + : const SizedBox(), + ), + MediaQuery.removePadding( + removeTop: true, + removeBottom: true, + context: context, + child: Padding( + padding: const EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + ), + child: FutureBuilder( + future: _memberLikeFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + Map data = snapshot.data as Map; + return MemberLikePanel(data: data['data']); + } else { + // 请求错误 + return const SizedBox(); + } + } else { + return const SizedBox(); + } + }, + ), + ), + ), ], ), ), diff --git a/lib/pages/member/widgets/conis.dart b/lib/pages/member/widgets/conis.dart index 57a8a583..bdff2120 100644 --- a/lib/pages/member/widgets/conis.dart +++ b/lib/pages/member/widgets/conis.dart @@ -4,8 +4,8 @@ import 'package:pilipala/models/member/coin.dart'; import 'package:pilipala/pages/member_coin/widgets/item.dart'; class MemberCoinsPanel extends StatelessWidget { - final List? data; - const MemberCoinsPanel({super.key, this.data}); + final List data; + const MemberCoinsPanel({super.key, required this.data}); @override Widget build(BuildContext context) { @@ -20,9 +20,9 @@ class MemberCoinsPanel extends StatelessWidget { ), physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, - itemCount: data!.length, + itemCount: data.length, itemBuilder: (context, i) { - return MemberCoinsItem(coinItem: data![i]); + return MemberCoinsItem(coinItem: data[i]); }, ); }, diff --git a/lib/pages/member/widgets/like.dart b/lib/pages/member/widgets/like.dart new file mode 100644 index 00000000..6342c274 --- /dev/null +++ b/lib/pages/member/widgets/like.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/models/member/like.dart'; +import 'package:pilipala/pages/member_like/widgets/item.dart'; + +class MemberLikePanel extends StatelessWidget { + final List data; + const MemberLikePanel({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, boxConstraints) { + return GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // Use a fixed count for GridView + crossAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: StyleString.safeSpace, + childAspectRatio: 0.94, + ), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: data.length, + itemBuilder: (context, i) { + return MemberLikeItem(likeItem: data[i]); + }, + ); + }, + ); + } +} diff --git a/lib/pages/member_like/widgets/item.dart b/lib/pages/member_like/widgets/item.dart new file mode 100644 index 00000000..57798bb7 --- /dev/null +++ b/lib/pages/member_like/widgets/item.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/widgets/badge.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/common/widgets/stat/view.dart'; +import 'package:pilipala/http/search.dart'; +import 'package:pilipala/models/member/like.dart'; +import 'package:pilipala/utils/utils.dart'; + +class MemberLikeItem extends StatelessWidget { + final MemberLikeDataModel likeItem; + + const MemberLikeItem({ + Key? key, + required this.likeItem, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + String heroTag = Utils.makeHeroTag(likeItem.aid); + return Card( + elevation: 0, + clipBehavior: Clip.hardEdge, + margin: EdgeInsets.zero, + child: InkWell( + onTap: () async { + int cid = + await SearchHttp.ab2c(aid: likeItem.aid, bvid: likeItem.bvid); + Get.toNamed('/video?bvid=${likeItem.bvid}&cid=$cid', + arguments: {'videoItem': likeItem, 'heroTag': heroTag}); + }, + child: Column( + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder(builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + NetworkImgLayer( + src: likeItem.pic, + width: maxWidth, + height: maxHeight, + ), + if (likeItem.duration != null) + PBadge( + bottom: 6, + right: 6, + type: 'gray', + text: Utils.timeFormat(likeItem.duration), + ) + ], + ); + }), + ), + Padding( + padding: const EdgeInsets.fromLTRB(5, 6, 0, 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + likeItem.title!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Row( + children: [ + StatView( + view: likeItem.stat!.view, + theme: 'gray', + ), + const Spacer(), + Text( + Utils.CustomStamp_str( + timestamp: likeItem.pubdate, date: 'MM-DD'), + style: TextStyle( + fontSize: 11, + color: Theme.of(context).colorScheme.outline, + ), + ), + const SizedBox(width: 6) + ], + ), + ], + ), + ), + ], + ), + ), + ); + } +} From ea7ae15384660071505332282154a6d8f2037ed9 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 29 Jun 2024 14:29:43 +0800 Subject: [PATCH 017/152] =?UTF-8?q?mod:=20=E5=90=88=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member/controller.dart | 7 +++- lib/pages/member/view.dart | 52 +++++++++++++-------------- lib/pages/member/widgets/seasons.dart | 44 +++++++++++++---------- lib/pages/member_seasons/view.dart | 3 +- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 7db046d4..cc928a8d 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -192,12 +192,17 @@ class MemberController extends GetxController { Share.share('${memberInfo.value.name} - https://space.bilibili.com/$mid'); } - // 请求专栏 + // 请求合集 Future getMemberSeasons() async { if (userInfo == null) return; var res = await MemberHttp.getMemberSeasons(mid, 1, 10); if (!res['status']) { SmartDialog.showToast("用户专栏请求异常:${res['msg']}"); + } else { + // 只取前四个专栏 + res['data'].seasonsList.map((e) { + e.archives = e.archives!.sublist(0, 4); + }).toList(); } return res; } diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 9c0da652..f62ffacc 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -178,39 +178,37 @@ class _MemberPageState extends State /// 专栏 const ListTile(title: Text('Ta的专栏')), + const Divider(height: 1, thickness: 0.1), + + /// 合集 + const ListTile(title: Text('Ta的合集')), MediaQuery.removePadding( removeTop: true, removeBottom: true, context: context, - child: Padding( - padding: const EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - ), - child: FutureBuilder( - future: _memberSeasonsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data['status']) { - Map data = snapshot.data as Map; - if (data['data'].seasonsList.isEmpty) { - return commenWidget('用户没有设置专栏'); - } else { - return MemberSeasonsPanel(data: data['data']); - } - } else { - // 请求错误 - return const SizedBox(); - } - } else { + child: FutureBuilder( + future: _memberSeasonsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.done) { + if (snapshot.data == null) { return const SizedBox(); } - }, - ), + if (snapshot.data['status']) { + Map data = snapshot.data as Map; + if (data['data'].seasonsList.isEmpty) { + return commenWidget('用户没有设置合集'); + } else { + return MemberSeasonsPanel(data: data['data']); + } + } else { + // 请求错误 + return const SizedBox(); + } + } else { + return const SizedBox(); + } + }, ), ), diff --git a/lib/pages/member/widgets/seasons.dart b/lib/pages/member/widgets/seasons.dart index 125c978f..1367d6bd 100644 --- a/lib/pages/member/widgets/seasons.dart +++ b/lib/pages/member/widgets/seasons.dart @@ -25,7 +25,7 @@ class MemberSeasonsPanel extends StatelessWidget { children: [ ListTile( onTap: () => Get.toNamed( - '/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'), + '/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}&seasonName=${item.meta!.name}'), title: Text( item.meta!.name!, maxLines: 1, @@ -44,24 +44,30 @@ class MemberSeasonsPanel extends StatelessWidget { ), ), const SizedBox(height: 10), - LayoutBuilder( - builder: (context, boxConstraints) { - return GridView.builder( - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, // Use a fixed count for GridView - crossAxisSpacing: StyleString.safeSpace, - mainAxisSpacing: StyleString.safeSpace, - childAspectRatio: 0.94, - ), - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: item.archives!.length, - itemBuilder: (context, i) { - return MemberSeasonsItem(seasonItem: item.archives![i]); - }, - ); - }, + Padding( + padding: const EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + ), + child: LayoutBuilder( + builder: (context, boxConstraints) { + return GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // Use a fixed count for GridView + crossAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: StyleString.safeSpace, + childAspectRatio: 0.94, + ), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: item.archives!.length, + itemBuilder: (context, i) { + return MemberSeasonsItem(seasonItem: item.archives![i]); + }, + ); + }, + ), ), ], ), diff --git a/lib/pages/member_seasons/view.dart b/lib/pages/member_seasons/view.dart index 06944f10..556e2ec5 100644 --- a/lib/pages/member_seasons/view.dart +++ b/lib/pages/member_seasons/view.dart @@ -43,7 +43,8 @@ class _MemberSeasonsPageState extends State { appBar: AppBar( titleSpacing: 0, centerTitle: false, - title: Text('他的专栏', style: Theme.of(context).textTheme.titleMedium), + title: Text(Get.parameters['seasonName']!, + style: Theme.of(context).textTheme.titleMedium), ), body: Padding( padding: const EdgeInsets.only( From eaae622f95b582bfd1f511e0ce1f70ecd08514ba Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 29 Jun 2024 17:12:06 +0800 Subject: [PATCH 018/152] =?UTF-8?q?opt:=20=E5=AD=97=E5=B9=95=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/common/subtitle_type.dart | 95 ------------------- lib/models/video/subTitile/result.dart | 8 -- lib/pages/video/detail/controller.dart | 9 -- .../video/detail/introduction/controller.dart | 3 +- .../video/detail/widgets/header_control.dart | 9 +- lib/plugin/pl_player/controller.dart | 27 +----- 6 files changed, 11 insertions(+), 140 deletions(-) delete mode 100644 lib/models/common/subtitle_type.dart diff --git a/lib/models/common/subtitle_type.dart b/lib/models/common/subtitle_type.dart deleted file mode 100644 index ac3ee3e0..00000000 --- a/lib/models/common/subtitle_type.dart +++ /dev/null @@ -1,95 +0,0 @@ -enum SubtitleType { - // 中文(中国) - zhCN, - // 中文(自动翻译) - aizh, - // 英语(自动生成) - aien, - // 中文(简体) - zhHans, - // 英文(美国) - enUS, - // 中文繁体 - zhTW, - // - en, - // - pt, - // - es, -} - -extension SubtitleTypeExtension on SubtitleType { - String get description { - switch (this) { - case SubtitleType.zhCN: - return '中文(中国)'; - case SubtitleType.aizh: - return '中文(自动翻译)'; - case SubtitleType.aien: - return '英语(自动生成)'; - case SubtitleType.zhHans: - return '中文(简体)'; - case SubtitleType.enUS: - return '英文(美国)'; - case SubtitleType.zhTW: - return '中文(繁体)'; - case SubtitleType.en: - return '英文'; - case SubtitleType.pt: - return '葡萄牙语'; - case SubtitleType.es: - return '西班牙语'; - } - } -} - -extension SubtitleIdExtension on SubtitleType { - String get id { - switch (this) { - case SubtitleType.zhCN: - return 'zh-CN'; - case SubtitleType.aizh: - return 'ai-zh'; - case SubtitleType.aien: - return 'ai-en'; - case SubtitleType.zhHans: - return 'zh-Hans'; - case SubtitleType.enUS: - return 'en-US'; - case SubtitleType.zhTW: - return 'zh-TW'; - case SubtitleType.en: - return 'en'; - case SubtitleType.pt: - return 'pt'; - case SubtitleType.es: - return 'es'; - } - } -} - -extension SubtitleCodeExtension on SubtitleType { - int get code { - switch (this) { - case SubtitleType.zhCN: - return 1; - case SubtitleType.aizh: - return 2; - case SubtitleType.aien: - return 3; - case SubtitleType.zhHans: - return 4; - case SubtitleType.enUS: - return 5; - case SubtitleType.zhTW: - return 6; - case SubtitleType.en: - return 7; - case SubtitleType.pt: - return 8; - case SubtitleType.es: - return 9; - } - } -} diff --git a/lib/models/video/subTitile/result.dart b/lib/models/video/subTitile/result.dart index d3e32e55..e137439f 100644 --- a/lib/models/video/subTitile/result.dart +++ b/lib/models/video/subTitile/result.dart @@ -1,6 +1,3 @@ -import 'package:get/get.dart'; -import '../../common/subtitle_type.dart'; - class SubTitlteModel { SubTitlteModel({ this.aid, @@ -78,11 +75,6 @@ class SubTitlteItemModel { aiType: json["ai_type"], aiStatus: json["ai_status"], title: json["lan_doc"], - code: SubtitleType.values - .firstWhereOrNull( - (element) => element.id.toString() == json["lan"]) - ?.index ?? - -1, content: '', body: [], ); diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index ea85a5b9..901c4014 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -446,15 +446,6 @@ class VideoDetailController extends GetxController } } - // 获取字幕内容 - // Future getSubtitleContent(String url) async { - // var res = await Request().get('https:$url'); - // subtitleContents.value = res.data['body'].map((e) { - // return SubTitileContentModel.fromJson(e); - // }).toList(); - // setSubtitleContent(); - // } - setSubtitleContent() { plPlayerController.subtitleContent.value = ''; plPlayerController.subtitles.value = subtitles; diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 1a1b1b74..50aac4cd 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -435,7 +435,8 @@ class VideoIntroController extends GetxController { videoDetailCtr.danmakuCid.value = cid; videoDetailCtr.cover.value = cover; videoDetailCtr.queryVideoUrl(); - videoDetailCtr.getSubtitle(); + videoDetailCtr.clearSubtitleContent(); + await videoDetailCtr.getSubtitle(); videoDetailCtr.setSubtitleContent(); // 重新请求评论 try { diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 5072377a..45a9684a 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -426,7 +426,12 @@ class _HeaderControlState extends State { /// 选择字幕 void showSubtitleDialog() async { int tempThemeValue = widget.controller!.subTitleCode.value; - int len = widget.videoDetailCtr!.subtitles.length; + final List subtitles = widget.videoDetailCtr!.subtitles; + int len = subtitles.length; + if (subtitles.firstWhereOrNull((element) => element.id == tempThemeValue) == + null) { + tempThemeValue = -1; + } showDialog( context: context, builder: (BuildContext context) { @@ -458,7 +463,7 @@ class _HeaderControlState extends State { ), ...widget.videoDetailCtr!.subtitles .map((e) => RadioListTile( - value: e.code, + value: e.id, title: Text(e.title), groupValue: tempThemeValue, onChanged: (value) { diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index a614d75d..a5de33fd 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -123,7 +123,7 @@ class PlPlayerController { PreferredSizeWidget? headerControl; PreferredSizeWidget? bottomControl; Widget? danmuWidget; - late RxList subtitles; + RxList subtitles = [].obs; String videoType = 'archive'; /// 数据加载监听 @@ -642,10 +642,6 @@ class PlPlayerController { const Duration(seconds: 1), () => videoPlayerServiceHandler.onPositionChange(event)); }), - - // onSubTitleOpenChanged.listen((bool event) { - // toggleSubtitle(event ? subTitleCode.value : -1); - // }) ], ); } @@ -1049,25 +1045,6 @@ class PlPlayerController { void toggleSubtitle(int code) { _subTitleOpen.value = code != -1; _subTitleCode.value = code; - // if (code == -1) { - // // 关闭字幕 - // _subTitleOpen.value = false; - // _subTitleCode.value = code; - // _videoPlayerController?.setSubtitleTrack(SubtitleTrack.no()); - // return; - // } - // final SubTitlteItemModel? subtitle = subtitles?.firstWhereOrNull( - // (element) => element.code == code, - // ); - // _subTitleOpen.value = true; - // _subTitleCode.value = code; - // _videoPlayerController?.setSubtitleTrack( - // SubtitleTrack.data( - // subtitle!.content!, - // title: subtitle.title, - // language: subtitle.lan, - // ), - // ); } void querySubtitleContent(double progress) { @@ -1079,7 +1056,7 @@ class PlPlayerController { return; } final SubTitlteItemModel? subtitle = subtitles.firstWhereOrNull( - (element) => element.code == subTitleCode.value, + (element) => element.id == subTitleCode.value, ); if (subtitle != null && subtitle.body!.isNotEmpty) { for (var content in subtitle.body!) { From de86dba39dee578a77c54c938397552cc962f56d Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 29 Jun 2024 18:47:04 +0800 Subject: [PATCH 019/152] =?UTF-8?q?feat:=20=E6=B8=AF=E6=BE=B3=E5=8F=B0?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/constants.dart | 1 + lib/http/init.dart | 40 ++++++++++++++++++++--------- lib/pages/setting/play_setting.dart | 9 +++++++ lib/utils/storage.dart | 2 ++ 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/lib/http/constants.dart b/lib/http/constants.dart index 3d749ee8..cf6e00f2 100644 --- a/lib/http/constants.dart +++ b/lib/http/constants.dart @@ -5,6 +5,7 @@ class HttpString { static const String appBaseUrl = 'https://app.bilibili.com'; static const String liveBaseUrl = 'https://api.live.bilibili.com'; static const String passBaseUrl = 'https://passport.bilibili.com'; + static const String bangumiBaseUrl = 'https://bili.meark.me'; static const List validateStatusCodes = [ 302, 304, diff --git a/lib/http/init.dart b/lib/http/init.dart index a0b36369..cb9d6f39 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -27,11 +27,13 @@ class Request { late bool enableSystemProxy; late String systemProxyHost; late String systemProxyPort; - static final RegExp spmPrefixExp = RegExp(r''); + static final RegExp spmPrefixExp = + RegExp(r''); /// 设置cookie static setCookie() async { Box userInfoCache = GStrorage.userInfo; + Box setting = GStrorage.setting; final String cookiePath = await Utils.getCookiePath(); final PersistCookieJar cookieJar = PersistCookieJar( ignoreExpires: true, @@ -54,7 +56,11 @@ class Request { } } setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null); - + String baseUrlType = 'default'; + if (setting.get(SettingBoxKey.enableGATMode, defaultValue: false)) { + baseUrlType = 'bangumi'; + } + setBaseUrl(type: baseUrlType); try { await buvidActivate(); } catch (e) { @@ -95,11 +101,10 @@ class Request { String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!; Random rand = Random(); String rand_png_end = base64.encode( - List.generate(32, (_) => rand.nextInt(256)) + - List.filled(4, 0) + - [73, 69, 78, 68] + - List.generate(4, (_) => rand.nextInt(256)) - ); + List.generate(32, (_) => rand.nextInt(256)) + + List.filled(4, 0) + + [73, 69, 78, 68] + + List.generate(4, (_) => rand.nextInt(256))); String jsonData = json.encode({ '3064': 1, @@ -110,11 +115,9 @@ class Request { }, }); - await Request().post( - Api.activateBuvidApi, - data: {'payload': jsonData}, - options: Options(contentType: 'application/json') - ); + await Request().post(Api.activateBuvidApi, + data: {'payload': jsonData}, + options: Options(contentType: 'application/json')); } /* @@ -294,4 +297,17 @@ class Request { } return headerUa; } + + static setBaseUrl({String type = 'default'}) { + switch (type) { + case 'default': + dio.options.baseUrl = HttpString.apiBaseUrl; + break; + case 'bangumi': + dio.options.baseUrl = HttpString.bangumiBaseUrl; + break; + default: + dio.options.baseUrl = HttpString.apiBaseUrl; + } + } } diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index 0f7dcdc3..cb8a3996 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/http/init.dart'; import 'package:pilipala/models/video/play/ao_output.dart'; import 'package:pilipala/models/video/play/quality.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; @@ -163,6 +164,14 @@ class _PlaySettingState extends State { callFn: (bool val) { GlobalData().enablePlayerControlAnimation = val; }), + SetSwitchItem( + title: '港澳台模式', + setKey: SettingBoxKey.enableGATMode, + defaultVal: false, + callFn: (bool val) { + Request.setBaseUrl(type: val ? 'bangumi' : 'default'); + }, + ), ListTile( dense: false, title: Text('默认视频画质', style: titleStyle), diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index bf9074e3..dca5a158 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -104,6 +104,8 @@ class SettingBoxKey { enablePlayerControlAnimation = 'enablePlayerControlAnimation', // 默认音频输出方式 defaultAoOutput = 'defaultAoOutput', + // 港澳台模式 + enableGATMode = 'enableGATMode', // youtube 双击快进快退 enableQuickDouble = 'enableQuickDouble', From 9f1b74b7e23f2b09c0c506dfbb5dd26a8217b506 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 30 Jun 2024 00:28:26 +0800 Subject: [PATCH 020/152] mod --- lib/pages/video/detail/reply/view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index be1bd331..4fe69481 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -188,7 +188,7 @@ class _VideoReplyPanelState extends State if (snapshot.connectionState == ConnectionState.done) { var data = snapshot.data; if (_videoReplyController.replyList.isNotEmpty || - (data && data['status'])) { + (data != null && data['status'])) { // 请求成功 return Obx( () => _videoReplyController.isLoadingMore && From a19129c59615c5eda9750ffd0d881528d1054d66 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 30 Jun 2024 13:02:50 +0800 Subject: [PATCH 021/152] =?UTF-8?q?opt:=20=E5=AD=97=E5=B9=95=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E6=98=BE=E9=9A=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/controller.dart | 46 +++++++++++-------- .../video/detail/widgets/header_control.dart | 17 ++++--- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 901c4014..c5e37be1 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -141,13 +141,7 @@ class VideoDetailController extends GetxController if (Platform.isAndroid) { floating = Floating(); } - headerControl = HeaderControl( - controller: plPlayerController, - videoDetailCtr: this, - floating: floating, - bvid: bvid, - videoType: videoType, - ); + // CDN优化 enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); // 预设的画质 @@ -158,7 +152,18 @@ class VideoDetailController extends GetxController defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa, defaultValue: AudioQuality.hiRes.code); oid.value = IdUtils.bv2av(Get.parameters['bvid']!); - getSubtitle(); + getSubtitle().then( + (subtitles) { + headerControl = HeaderControl( + controller: plPlayerController, + videoDetailCtr: this, + floating: floating, + bvid: bvid, + videoType: videoType, + showSubtitleBtn: subtitles.isNotEmpty, + ); + }, + ); } showReplyReplyPanel(oid, fRpid, firstFloor) { @@ -432,17 +437,22 @@ class VideoDetailController extends GetxController if (result['status']) { if (result['data'].subtitles.isNotEmpty) { subtitles = result['data'].subtitles; - if (subtitles.isNotEmpty) { - for (var i in subtitles) { - final Map res = await VideoHttp.getSubtitleContent( - i.subtitleUrl, - ); - i.content = res['content']; - i.body = res['body']; - } - } + getDanmaku(subtitles); + } + return subtitles; + } + } + + // 获取弹幕 + Future getDanmaku(List subtitles) async { + if (subtitles.isNotEmpty) { + for (var i in subtitles) { + final Map res = await VideoHttp.getSubtitleContent( + i.subtitleUrl, + ); + i.content = res['content']; + i.body = res['body']; } - return result['data']; } } diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 45a9684a..f3b9549a 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -30,6 +30,7 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget { this.floating, this.bvid, this.videoType, + this.showSubtitleBtn, super.key, }); final PlPlayerController? controller; @@ -37,6 +38,7 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget { final Floating? floating; final String? bvid; final SearchType? videoType; + final bool? showSubtitleBtn; @override State createState() => _HeaderControlState(); @@ -1327,14 +1329,15 @@ class _HeaderControlState extends State { ], /// 字幕 - ComBtn( - icon: const Icon( - Icons.closed_caption_off, - size: 22, - color: Colors.white, + if (widget.showSubtitleBtn!) + ComBtn( + icon: const Icon( + Icons.closed_caption_off, + size: 22, + color: Colors.white, + ), + fuc: () => showSubtitleDialog(), ), - fuc: () => showSubtitleDialog(), - ), SizedBox(width: buttonSpace), Obx( () => SizedBox( From c7ba9dc97bd9adb0042ded14858638ede7050ae9 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 30 Jun 2024 22:01:12 +0800 Subject: [PATCH 022/152] =?UTF-8?q?feat:=20=E7=B3=BB=E7=BB=9F=E6=B6=88?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 4 + lib/http/constants.dart | 1 + lib/http/msg.dart | 27 +++++- lib/models/msg/system.dart | 77 +++++++++++++++++ lib/pages/message/system/controller.dart | 14 ++- lib/pages/message/system/view.dart | 104 +++++++++++++++++++++++ lib/pages/whisper/view.dart | 2 +- 7 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 lib/models/msg/system.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index f20b8bcf..46bbb6ac 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -548,4 +548,8 @@ class Api { /// 收到的赞 static const String messageLikeAPi = '/x/msgfeed/like'; + + /// 系统通知 + static const String messageSystemAPi = + '${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify'; } diff --git a/lib/http/constants.dart b/lib/http/constants.dart index 3d749ee8..cad413ef 100644 --- a/lib/http/constants.dart +++ b/lib/http/constants.dart @@ -5,6 +5,7 @@ class HttpString { static const String appBaseUrl = 'https://app.bilibili.com'; static const String liveBaseUrl = 'https://api.live.bilibili.com'; static const String passBaseUrl = 'https://passport.bilibili.com'; + static const String messageBaseUrl = 'https://message.bilibili.com'; static const List validateStatusCodes = [ 302, 304, diff --git a/lib/http/msg.dart b/lib/http/msg.dart index 7c168230..86789fd1 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:dio/dio.dart'; import 'package:pilipala/models/msg/like.dart'; import 'package:pilipala/models/msg/reply.dart'; +import 'package:pilipala/models/msg/system.dart'; import '../models/msg/account.dart'; import '../models/msg/session.dart'; import '../utils/wbi_sign.dart'; @@ -149,7 +150,7 @@ class MsgHttp { 'msg[msg_status]': 0, 'msg[content]': jsonEncode(content), 'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000, - 'msg[new_face_version]': 0, + 'msg[new_face_version]': 1, 'msg[dev_id]': getDevId(), 'from_firework': 0, 'build': 0, @@ -287,4 +288,28 @@ class MsgHttp { return {'status': false, 'date': [], 'msg': res.data['message']}; } } + + static Future messageSystem() async { + var res = await Request().get(Api.messageSystemAPi, data: { + 'csrf': await Request.getCsrf(), + 'page_size': 20, + 'build': 0, + 'mobi_app': 'web', + }); + if (res.data['code'] == 0) { + try { + print(res.data['data']['system_notify_list']); + return { + 'status': true, + 'data': res.data['data']['system_notify_list'] + .map((e) => MessageSystemModel.fromJson(e)) + .toList(), + }; + } catch (err) { + return {'status': false, 'date': [], 'msg': err.toString()}; + } + } else { + return {'status': false, 'date': [], 'msg': res.data['message']}; + } + } } diff --git a/lib/models/msg/system.dart b/lib/models/msg/system.dart new file mode 100644 index 00000000..20427707 --- /dev/null +++ b/lib/models/msg/system.dart @@ -0,0 +1,77 @@ +import 'dart:convert'; + +class MessageSystemModel { + int? id; + int? cursor; + int? type; + String? title; + Map? content; + Source? source; + String? timeAt; + int? cardType; + String? cardBrief; + String? cardMsgBrief; + String? cardCover; + String? cardStoryTitle; + String? cardLink; + String? mc; + int? isStation; + int? isSend; + int? notifyCursor; + + MessageSystemModel({ + this.id, + this.cursor, + this.type, + this.title, + this.content, + this.source, + this.timeAt, + this.cardType, + this.cardBrief, + this.cardMsgBrief, + this.cardCover, + this.cardStoryTitle, + this.cardLink, + this.mc, + this.isStation, + this.isSend, + this.notifyCursor, + }); + + factory MessageSystemModel.fromJson(Map jsons) => + MessageSystemModel( + id: jsons["id"], + cursor: jsons["cursor"], + type: jsons["type"], + title: jsons["title"], + content: json.decode(jsons["content"]), + source: Source.fromJson(jsons["source"]), + timeAt: jsons["time_at"], + cardType: jsons["card_type"], + cardBrief: jsons["card_brief"], + cardMsgBrief: jsons["card_msg_brief"], + cardCover: jsons["card_cover"], + cardStoryTitle: jsons["card_story_title"], + cardLink: jsons["card_link"], + mc: jsons["mc"], + isStation: jsons["is_station"], + isSend: jsons["is_send"], + notifyCursor: jsons["notify_cursor"], + ); +} + +class Source { + String? name; + String? logo; + + Source({ + this.name, + this.logo, + }); + + factory Source.fromJson(Map json) => Source( + name: json["name"], + logo: json["logo"], + ); +} diff --git a/lib/pages/message/system/controller.dart b/lib/pages/message/system/controller.dart index ad28af56..bf31f6bc 100644 --- a/lib/pages/message/system/controller.dart +++ b/lib/pages/message/system/controller.dart @@ -1,3 +1,15 @@ import 'package:get/get.dart'; +import 'package:pilipala/http/msg.dart'; +import 'package:pilipala/models/msg/system.dart'; -class MessageSystemController extends GetxController {} +class MessageSystemController extends GetxController { + RxList systemItems = [].obs; + + Future queryMessageSystem({String type = 'init'}) async { + var res = await MsgHttp.messageSystem(); + if (res['status']) { + systemItems.addAll(res['data']); + } + return res; + } +} diff --git a/lib/pages/message/system/view.dart b/lib/pages/message/system/view.dart index da0f1219..f7b94e5a 100644 --- a/lib/pages/message/system/view.dart +++ b/lib/pages/message/system/view.dart @@ -1,4 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; +import 'package:pilipala/models/msg/system.dart'; +import 'controller.dart'; class MessageSystemPage extends StatefulWidget { const MessageSystemPage({super.key}); @@ -8,12 +12,112 @@ class MessageSystemPage extends StatefulWidget { } class _MessageSystemPageState extends State { + final MessageSystemController _messageSystemCtr = + Get.put(MessageSystemController()); + late Future _futureBuilderFuture; + final ScrollController scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _messageSystemCtr.queryMessageSystem(); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('系统通知'), ), + body: RefreshIndicator( + onRefresh: () async { + await _messageSystemCtr.queryMessageSystem(); + }, + child: FutureBuilder( + future: _futureBuilderFuture, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + final systemItems = _messageSystemCtr.systemItems; + print(systemItems.length); + return Obx( + () => ListView.separated( + controller: scrollController, + itemBuilder: (context, index) => SystemItem( + item: systemItems[index], + index: index, + messageSystemCtr: _messageSystemCtr, + ), + itemCount: systemItems.length, + separatorBuilder: (BuildContext context, int index) { + return Divider( + indent: 14, + endIndent: 14, + height: 1, + color: Colors.grey.withOpacity(0.1), + ); + }, + ), + ); + } else { + // 请求错误 + return CustomScrollView( + slivers: [ + HttpError( + errMsg: snapshot.data['msg'], + fn: () { + setState(() { + _futureBuilderFuture = + _messageSystemCtr.queryMessageSystem(); + }); + }, + ) + ], + ); + } + } else { + return const SizedBox(); + } + }, + ), + ), + ); + } +} + +class SystemItem extends StatelessWidget { + final MessageSystemModel item; + final int index; + final MessageSystemController messageSystemCtr; + + const SystemItem( + {super.key, + required this.item, + required this.index, + required this.messageSystemCtr}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(item.title!, + style: + const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const SizedBox(height: 4), + Text( + item.timeAt!, + style: TextStyle(color: Theme.of(context).colorScheme.outline), + ), + const SizedBox(height: 6), + Text(item.content!['web']), + ], + ), ); } } diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 9436e2be..1814c274 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -71,7 +71,7 @@ class _WhisperPageState extends State { ..._whisperController.noticesList.map((element) { return InkWell( onTap: () { - if (['/messageAt', '/messageSystem'] + if (['/messageAt'] .contains(element['path'])) { SmartDialog.showToast('功能开发中'); return; From 0d96327f344b99424c54be2e395c71ef9debeb08 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 1 Jul 2024 23:11:11 +0800 Subject: [PATCH 023/152] =?UTF-8?q?fix:=20=E5=AD=97=E5=B9=95=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E8=B6=8A=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/danmaku/controller.dart | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/pages/danmaku/controller.dart b/lib/pages/danmaku/controller.dart index 11e097e1..52c423d7 100644 --- a/lib/pages/danmaku/controller.dart +++ b/lib/pages/danmaku/controller.dart @@ -17,7 +17,11 @@ class PlDanmakuController { int segCount = (videoDuration / segmentLength).ceil(); requestedSeg = List.generate(segCount, (index) => false); } - queryDanmaku(calcSegment(progress)); + try { + queryDanmaku(calcSegment(progress)); + } catch (e) { + print(e); + } } void dispose() { @@ -31,16 +35,18 @@ class PlDanmakuController { void queryDanmaku(int segmentIndex) async { assert(requestedSeg[segmentIndex] == false); - requestedSeg[segmentIndex] = true; - final DmSegMobileReply result = await DanmakaHttp.queryDanmaku( - cid: cid, segmentIndex: segmentIndex + 1); - if (result.elems.isNotEmpty) { - for (var element in result.elems) { - int pos = element.progress ~/ 100; //每0.1秒存储一次 - if (dmSegMap[pos] == null) { - dmSegMap[pos] = []; + if (requestedSeg.length > segmentIndex) { + requestedSeg[segmentIndex] = true; + final DmSegMobileReply result = await DanmakaHttp.queryDanmaku( + cid: cid, segmentIndex: segmentIndex + 1); + if (result.elems.isNotEmpty) { + for (var element in result.elems) { + int pos = element.progress ~/ 100; //每0.1秒存储一次 + if (dmSegMap[pos] == null) { + dmSegMap[pos] = []; + } + dmSegMap[pos]!.add(element); } - dmSegMap[pos]!.add(element); } } } From 739e1d9ada6d7fddd0dedf9a2a6def33c45bdb53 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 5 Jul 2024 22:54:21 +0800 Subject: [PATCH 024/152] =?UTF-8?q?opt:=20AppBar=E6=B8=90=E5=8F=98?= =?UTF-8?q?=E8=83=8C=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/home/view.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index e485fe41..74072b2f 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -78,16 +78,16 @@ class _HomePageState extends State Theme.of(context) .colorScheme .primary - .withOpacity(0.9), + .withOpacity(0.4), Theme.of(context) .colorScheme - .primary + .surface .withOpacity(0.5), Theme.of(context).colorScheme.surface ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - stops: const [0, 0.0034, 0.34]), + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.1, 0.3, 0.5]), ), ), ), From b3f1400c044e6505e62936df8fc2b021b1e39e54 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 6 Jul 2024 00:07:02 +0800 Subject: [PATCH 025/152] =?UTF-8?q?fix:=20=E8=AF=84=E8=AE=BA=E5=8C=BA?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/reply/widgets/reply_item.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index ebb266cd..d8f696d4 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -644,6 +644,15 @@ InlineSpan buildContent( title, '', ); + } else if (RegExp(r'^cv\d+$').hasMatch(matchStr)) { + Get.toNamed( + '/webview', + parameters: { + 'url': 'https://www.bilibili.com/read/$matchStr', + 'type': 'url', + 'pageTitle': title + }, + ); } else { Uri uri = Uri.parse(matchStr.replaceAll('/?', '?')); SchemeEntity scheme = SchemeEntity( From 7ae637ed6acd7454723dc4e45821efa8ba5e95a6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 6 Jul 2024 15:37:50 +0800 Subject: [PATCH 026/152] =?UTF-8?q?feat:=20=E6=9F=A5=E7=9C=8Bup=E4=B8=BB?= =?UTF-8?q?=E6=94=B6=E8=97=8F=E8=AE=A2=E9=98=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/fav/controller.dart | 8 +++- lib/pages/fav/view.dart | 22 ++++++--- lib/pages/fav/widgets/item.dart | 5 +- lib/pages/fav_detail/controller.dart | 2 + lib/pages/fav_detail/view.dart | 1 + .../fav_detail/widget/fav_video_card.dart | 7 ++- lib/pages/fav_search/view.dart | 1 + lib/pages/member/controller.dart | 9 +++- lib/pages/member/view.dart | 46 +++++++++++++------ lib/pages/subscription/controller.dart | 8 +++- lib/pages/subscription/view.dart | 9 ++-- lib/pages/subscription/widgets/item.dart | 45 +++++++++++------- 12 files changed, 115 insertions(+), 48 deletions(-) diff --git a/lib/pages/fav/controller.dart b/lib/pages/fav/controller.dart index 8fcbf971..5be46bf0 100644 --- a/lib/pages/fav/controller.dart +++ b/lib/pages/fav/controller.dart @@ -16,10 +16,16 @@ class FavController extends GetxController { int currentPage = 1; int pageSize = 60; RxBool hasMore = true.obs; + late int mid; + late int ownerMid; + RxBool isOwner = false.obs; @override void onInit() { + mid = int.parse(Get.parameters['mid'] ?? '-1'); userInfo = userInfoCache.get('userInfoCache'); + ownerMid = userInfo != null ? userInfo!.mid! : -1; + isOwner.value = mid == -1 || mid == ownerMid; super.onInit(); } @@ -33,7 +39,7 @@ class FavController extends GetxController { var res = await UserHttp.userfavFolder( pn: currentPage, ps: pageSize, - mid: userInfo!.mid!, + mid: isOwner.value ? ownerMid : mid, ); if (res['status']) { if (type == 'init') { diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 4f48213e..7010ba0d 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -42,17 +42,25 @@ class _FavPageState extends State { appBar: AppBar( centerTitle: false, titleSpacing: 0, - title: Text( - '我的收藏', - style: Theme.of(context).textTheme.titleMedium, - ), + title: Obx(() => Text( + '${_favController.isOwner.value ? '我' : 'Ta'}的收藏', + style: Theme.of(context).textTheme.titleMedium, + )), actions: [ + Obx(() => !_favController.isOwner.value + ? IconButton( + onPressed: () => + Get.toNamed('/subscription?mid=${_favController.mid}'), + icon: const Icon(Icons.subscriptions_outlined, size: 21), + tooltip: 'Ta的订阅', + ) + : const SizedBox.shrink()), IconButton( onPressed: () => Get.toNamed( '/favSearch?searchType=1&mediaId=${_favController.favFolderData.value.list!.first.id}'), icon: const Icon(Icons.search_outlined), ), - const SizedBox(width: 6), + const SizedBox(width: 14), ], ), body: FutureBuilder( @@ -67,7 +75,9 @@ class _FavPageState extends State { itemCount: _favController.favFolderList.length, itemBuilder: (context, index) { return FavItem( - favFolderItem: _favController.favFolderList[index]); + favFolderItem: _favController.favFolderList[index], + isOwner: _favController.isOwner.value, + ); }, ), ); diff --git a/lib/pages/fav/widgets/item.dart b/lib/pages/fav/widgets/item.dart index 3c44ec9d..9d453fb5 100644 --- a/lib/pages/fav/widgets/item.dart +++ b/lib/pages/fav/widgets/item.dart @@ -7,7 +7,9 @@ import 'package:pilipala/utils/utils.dart'; class FavItem extends StatelessWidget { // ignore: prefer_typing_uninitialized_variables final favFolderItem; - const FavItem({super.key, required this.favFolderItem}); + final bool isOwner; + const FavItem( + {super.key, required this.favFolderItem, required this.isOwner}); @override Widget build(BuildContext context) { @@ -20,6 +22,7 @@ class FavItem extends StatelessWidget { parameters: { 'heroTag': heroTag, 'mediaId': favFolderItem.id.toString(), + 'isOwner': isOwner ? '1' : '0', }, ); }, diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 7af398e8..3f87c226 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -19,6 +19,7 @@ class FavDetailController extends GetxController { RxList favList = [].obs; RxString loadingText = '加载中...'.obs; RxInt mediaCount = 0.obs; + late String isOwner; @override void onInit() { @@ -26,6 +27,7 @@ class FavDetailController extends GetxController { if (Get.parameters.keys.isNotEmpty) { mediaId = int.parse(Get.parameters['mediaId']!); heroTag = Get.parameters['heroTag']!; + isOwner = Get.parameters['isOwner']!; } super.onInit(); } diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 1bf5cb6f..cb9d7e7b 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -212,6 +212,7 @@ class _FavDetailPageState extends State { SliverChildBuilderDelegate((context, index) { return FavVideoCardH( videoItem: favList[index], + isOwner: _favDetailController.isOwner, callFn: () => _favDetailController .onCancelFav(favList[index].id), ); diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index 79e5c073..9779c549 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -18,12 +18,14 @@ class FavVideoCardH extends StatelessWidget { final dynamic videoItem; final Function? callFn; final int? searchType; + final String isOwner; const FavVideoCardH({ Key? key, required this.videoItem, this.callFn, this.searchType, + required this.isOwner, }) : super(key: key); @override @@ -123,6 +125,7 @@ class FavVideoCardH extends StatelessWidget { videoItem: videoItem, callFn: callFn, searchType: searchType, + isOwner: isOwner, ) ], ), @@ -140,11 +143,13 @@ class VideoContent extends StatelessWidget { final dynamic videoItem; final Function? callFn; final int? searchType; + final String isOwner; const VideoContent({ super.key, required this.videoItem, this.callFn, this.searchType, + required this.isOwner, }); @override @@ -211,7 +216,7 @@ class VideoContent extends StatelessWidget { ), ], ), - searchType != 1 + searchType != 1 && isOwner == '1' ? Positioned( right: 0, bottom: -4, diff --git a/lib/pages/fav_search/view.dart b/lib/pages/fav_search/view.dart index 9b2ab15d..2654ccb1 100644 --- a/lib/pages/fav_search/view.dart +++ b/lib/pages/fav_search/view.dart @@ -100,6 +100,7 @@ class _FavSearchPageState extends State { return FavVideoCardH( videoItem: _favSearchCtr.favList[index], searchType: searchType, + isOwner: '0', callFn: () => searchType != 1 ? _favSearchCtr .onCancelFav(_favSearchCtr.favList[index].id!) diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index cc928a8d..ada869b5 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -27,6 +27,7 @@ class MemberController extends GetxController { RxString attributeText = '关注'.obs; RxList recentCoinsList = [].obs; RxList recentLikeList = [].obs; + RxBool isOwner = false.obs; @override void onInit() { @@ -34,6 +35,7 @@ class MemberController extends GetxController { mid = int.parse(Get.parameters['mid']!); userInfo = userInfoCache.get('userInfoCache'); ownerMid = userInfo != null ? userInfo.mid : -1; + isOwner.value = mid == ownerMid; face.value = Get.arguments['face'] ?? ''; heroTag = Get.arguments['heroTag'] ?? ''; relationSearch(); @@ -197,11 +199,12 @@ class MemberController extends GetxController { if (userInfo == null) return; var res = await MemberHttp.getMemberSeasons(mid, 1, 10); if (!res['status']) { - SmartDialog.showToast("用户专栏请求异常:${res['msg']}"); + SmartDialog.showToast("用户合集请求异常:${res['msg']}"); } else { // 只取前四个专栏 res['data'].seasonsList.map((e) { - e.archives = e.archives!.sublist(0, 4); + e.archives = + e.archives!.length > 4 ? e.archives!.sublist(0, 4) : e.archives!; }).toList(); } return res; @@ -235,4 +238,6 @@ class MemberController extends GetxController { void pushRecentCoinsPage() async { if (recentCoinsList.isNotEmpty) {} } + + void pushfavPage() => Get.toNamed('/fav?mid=$mid'); } diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index f62ffacc..c721d638 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -159,29 +159,47 @@ class _MemberPageState extends State profileWidget(), /// 动态链接 - ListTile( - onTap: _memberController.pushDynamicsPage, - title: const Text('Ta的动态'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), + Obx( + () => ListTile( + onTap: _memberController.pushDynamicsPage, + title: Text( + '${_memberController.isOwner.value ? '我' : 'Ta'}的动态'), + trailing: + const Icon(Icons.arrow_forward_outlined, size: 19), + ), ), const Divider(height: 1, thickness: 0.1), /// 视频 - ListTile( - onTap: _memberController.pushArchivesPage, - title: const Text('Ta的投稿'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), + Obx(() => ListTile( + onTap: _memberController.pushArchivesPage, + title: Text( + '${_memberController.isOwner.value ? '我' : 'Ta'}的投稿'), + trailing: const Icon(Icons.arrow_forward_outlined, + size: 19), + )), + const Divider(height: 1, thickness: 0.1), + + /// 他的收藏夹 + Obx(() => ListTile( + onTap: _memberController.pushfavPage, + title: Text( + '${_memberController.isOwner.value ? '我' : 'Ta'}的收藏'), + trailing: const Icon(Icons.arrow_forward_outlined, + size: 19), + )), const Divider(height: 1, thickness: 0.1), /// 专栏 - const ListTile(title: Text('Ta的专栏')), + Obx(() => ListTile( + title: Text( + '${_memberController.isOwner.value ? '我' : 'Ta'}的专栏'))), const Divider(height: 1, thickness: 0.1), /// 合集 - const ListTile(title: Text('Ta的合集')), + Obx(() => ListTile( + title: Text( + '${_memberController.isOwner.value ? '我' : 'Ta'}的合集'))), MediaQuery.removePadding( removeTop: true, removeBottom: true, @@ -212,8 +230,6 @@ class _MemberPageState extends State ), ), - /// 收藏 - /// 追番 /// 最近投币 Obx( diff --git a/lib/pages/subscription/controller.dart b/lib/pages/subscription/controller.dart index d8a76d44..b59a42f0 100644 --- a/lib/pages/subscription/controller.dart +++ b/lib/pages/subscription/controller.dart @@ -16,11 +16,17 @@ class SubController extends GetxController { int currentPage = 1; int pageSize = 20; RxBool hasMore = true.obs; + late int mid; + late int ownerMid; + RxBool isOwner = false.obs; @override void onInit() { super.onInit(); + mid = int.parse(Get.parameters['mid'] ?? '-1'); userInfo = userInfoCache.get('userInfoCache'); + ownerMid = userInfo != null ? userInfo!.mid! : -1; + isOwner.value = mid == -1 || mid == ownerMid; } Future querySubFolder({type = 'init'}) async { @@ -30,7 +36,7 @@ class SubController extends GetxController { var res = await UserHttp.userSubFolder( pn: currentPage, ps: pageSize, - mid: userInfo!.mid!, + mid: isOwner.value ? ownerMid : mid, ); if (res['status']) { if (type == 'init') { diff --git a/lib/pages/subscription/view.dart b/lib/pages/subscription/view.dart index e1d1820d..cb9993b0 100644 --- a/lib/pages/subscription/view.dart +++ b/lib/pages/subscription/view.dart @@ -42,10 +42,10 @@ class _SubPageState extends State { appBar: AppBar( centerTitle: false, titleSpacing: 0, - title: Text( - '我的订阅', - style: Theme.of(context).textTheme.titleMedium, - ), + title: Obx(() => Text( + '${_subController.isOwner.value ? '我' : 'Ta'}的订阅', + style: Theme.of(context).textTheme.titleMedium, + )), ), body: FutureBuilder( future: _futureBuilderFuture, @@ -62,6 +62,7 @@ class _SubPageState extends State { return SubItem( subFolderItem: _subController.subFolderData.value.list![index], + isOwner: _subController.isOwner.value, cancelSub: _subController.cancelSub); }, ), diff --git a/lib/pages/subscription/widgets/item.dart b/lib/pages/subscription/widgets/item.dart index b244d3c7..0389b4a6 100644 --- a/lib/pages/subscription/widgets/item.dart +++ b/lib/pages/subscription/widgets/item.dart @@ -8,10 +8,12 @@ import '../../../models/user/sub_folder.dart'; class SubItem extends StatelessWidget { final SubFolderItemData subFolderItem; + final bool isOwner; final Function(SubFolderItemData) cancelSub; const SubItem({ super.key, required this.subFolderItem, + required this.isOwner, required this.cancelSub, }); @@ -59,6 +61,7 @@ class SubItem extends StatelessWidget { ), VideoContent( subFolderItem: subFolderItem, + isOwner: isOwner, cancelSub: cancelSub, ) ], @@ -73,8 +76,14 @@ class SubItem extends StatelessWidget { class VideoContent extends StatelessWidget { final SubFolderItemData subFolderItem; + final bool isOwner; final Function(SubFolderItemData)? cancelSub; - const VideoContent({super.key, required this.subFolderItem, this.cancelSub}); + const VideoContent({ + super.key, + required this.subFolderItem, + required this.isOwner, + this.cancelSub, + }); @override Widget build(BuildContext context) { @@ -111,22 +120,24 @@ class VideoContent extends StatelessWidget { ), ), const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - IconButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => cancelSub?.call(subFolderItem), - icon: Icon( - Icons.clear_outlined, - color: Theme.of(context).colorScheme.outline, - size: 18, - ), - ) - ], - ) + isOwner + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => cancelSub?.call(subFolderItem), + icon: Icon( + Icons.clear_outlined, + color: Theme.of(context).colorScheme.outline, + size: 18, + ), + ) + ], + ) + : const SizedBox() ], ), ), From ba430eaa2eb1038eaf5155293e5046beb636e7c4 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 6 Jul 2024 15:56:25 +0800 Subject: [PATCH 027/152] =?UTF-8?q?mod:=20up=E4=B8=BB=E5=90=88=E9=9B=86?= =?UTF-8?q?=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/member/seasons.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/models/member/seasons.dart b/lib/models/member/seasons.dart index 70230367..88b93c78 100644 --- a/lib/models/member/seasons.dart +++ b/lib/models/member/seasons.dart @@ -9,11 +9,18 @@ class MemberSeasonsDataModel { MemberSeasonsDataModel.fromJson(Map json) { page = json['page']; - seasonsList = json['seasons_list'] != null + var tempList1 = json['seasons_list'] != null ? json['seasons_list'] .map((e) => MemberSeasonsList.fromJson(e)) .toList() : []; + var tempList2 = json['series_list'] != null + ? json['series_list'] + .map((e) => MemberSeasonsList.fromJson(e)) + .toList() + : []; + + seasonsList = [...tempList1, ...tempList2]; } } From a23a18f3bc8c995d5043fec9fa66c50d6c567c51 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 6 Jul 2024 19:31:28 +0800 Subject: [PATCH 028/152] =?UTF-8?q?fix:=20=E5=AD=97=E5=B9=95=E5=AF=BC?= =?UTF-8?q?=E8=87=B4headControl=E5=88=9D=E5=A7=8B=E5=8C=96=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/video.dart | 2 +- lib/pages/video/detail/controller.dart | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/http/video.dart b/lib/http/video.dart index 7c1d9ba6..834a0102 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -492,7 +492,7 @@ class VideoHttp { return {'status': false, 'data': [], 'msg': res.data['msg']}; } } catch (err) { - print(err); + return {'status': false, 'data': [], 'msg': res.data['msg']}; } } diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index c5e37be1..dbdd9fce 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -440,6 +440,8 @@ class VideoDetailController extends GetxController getDanmaku(subtitles); } return subtitles; + } else { + return []; } } From dac0898729862fc8ae25d31f4ecf0fdf939015ed Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 6 Jul 2024 23:43:50 +0800 Subject: [PATCH 029/152] =?UTF-8?q?opt:=20AppBar=E6=B8=90=E5=8F=98?= =?UTF-8?q?=E8=83=8C=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/dynamics/view.dart | 7 +- lib/pages/home/view.dart | 149 ++++++++++++------------------- lib/pages/main/controller.dart | 3 + lib/pages/main/view.dart | 49 ++++++++-- lib/pages/media/view.dart | 13 ++- lib/pages/rank/controller.dart | 3 - lib/pages/rank/view.dart | 124 +++++++++---------------- lib/pages/video/detail/view.dart | 1 + 8 files changed, 162 insertions(+), 187 deletions(-) diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 1775798e..495eb770 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/skeleton/dynamic_card.dart'; @@ -77,10 +78,14 @@ class _DynamicsPageState extends State Widget build(BuildContext context) { super.build(context); return Scaffold( + backgroundColor: Colors.transparent, appBar: AppBar( elevation: 0, scrolledUnderElevation: 0, - titleSpacing: 0, + backgroundColor: Colors.transparent, + systemOverlayStyle: Theme.of(context).brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, title: SizedBox( height: 34, child: Stack( diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 74072b2f..0163723b 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -46,104 +46,66 @@ class _HomePageState extends State @override Widget build(BuildContext context) { super.build(context); - Brightness currentBrightness = MediaQuery.of(context).platformBrightness; - // 设置状态栏图标的亮度 - if (_homeController.enableGradientBg) { - SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( - statusBarIconBrightness: currentBrightness == Brightness.light - ? Brightness.dark - : Brightness.light, - )); - } return Scaffold( extendBody: true, extendBodyBehindAppBar: true, - appBar: _homeController.enableGradientBg - ? null - : AppBar(toolbarHeight: 0, elevation: 0), - body: Stack( + backgroundColor: Colors.transparent, + appBar: AppBar( + toolbarHeight: 0, + elevation: 0, + backgroundColor: Colors.transparent, + systemOverlayStyle: Theme.of(context).brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, + ), + body: Column( children: [ - // gradient background - if (_homeController.enableGradientBg) ...[ - Align( - alignment: Alignment.topLeft, - child: Opacity( - opacity: 0.6, - child: Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Theme.of(context) - .colorScheme - .primary - .withOpacity(0.4), - Theme.of(context) - .colorScheme - .surface - .withOpacity(0.5), - Theme.of(context).colorScheme.surface - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - stops: const [0.1, 0.3, 0.5]), + CustomAppBar( + stream: _homeController.hideSearchBar + ? stream + : StreamController.broadcast().stream, + ctr: _homeController, + callback: showUserBottomSheet, + ), + if (_homeController.tabs.length > 1) ...[ + if (_homeController.enableGradientBg) ...[ + const CustomTabs(), + ] else ...[ + Container( + width: double.infinity, + height: 42, + padding: const EdgeInsets.only(top: 4), + child: Align( + alignment: Alignment.center, + child: TabBar( + controller: _homeController.tabController, + tabs: [ + for (var i in _homeController.tabs) Tab(text: i['label']) + ], + isScrollable: true, + dividerColor: Colors.transparent, + enableFeedback: true, + splashBorderRadius: BorderRadius.circular(10), + tabAlignment: TabAlignment.center, + onTap: (value) { + feedBack(); + if (_homeController.initialIndex.value == value) { + _homeController.tabsCtrList[value]().animateToTop(); + } + _homeController.initialIndex.value = value; + }, ), ), ), - ), - ], - Column( - children: [ - CustomAppBar( - stream: _homeController.hideSearchBar - ? stream - : StreamController.broadcast().stream, - ctr: _homeController, - callback: showUserBottomSheet, - ), - if (_homeController.tabs.length > 1) ...[ - if (_homeController.enableGradientBg) ...[ - const CustomTabs(), - ] else ...[ - const SizedBox(height: 4), - SizedBox( - width: double.infinity, - height: 42, - child: Align( - alignment: Alignment.center, - child: TabBar( - controller: _homeController.tabController, - tabs: [ - for (var i in _homeController.tabs) - Tab(text: i['label']) - ], - isScrollable: true, - dividerColor: Colors.transparent, - enableFeedback: true, - splashBorderRadius: BorderRadius.circular(10), - tabAlignment: TabAlignment.center, - onTap: (value) { - feedBack(); - if (_homeController.initialIndex.value == value) { - _homeController.tabsCtrList[value]().animateToTop(); - } - _homeController.initialIndex.value = value; - }, - ), - ), - ), - ], - ] else ...[ - const SizedBox(height: 6), - ], - Expanded( - child: TabBarView( - controller: _homeController.tabController, - children: _homeController.tabsPageList, - ), - ), ], + ] else ...[ + const SizedBox(height: 6), + ], + Expanded( + child: TabBarView( + controller: _homeController.tabController, + children: _homeController.tabsPageList, + ), ), ], ), @@ -280,7 +242,10 @@ class DefaultUser extends StatelessWidget { style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), backgroundColor: MaterialStateProperty.resolveWith((states) { - return Theme.of(context).colorScheme.onInverseSurface; + return Theme.of(context) + .colorScheme + .onSecondaryContainer + .withOpacity(0.05); }), ), onPressed: () => callback?.call(), @@ -317,7 +282,7 @@ class _CustomTabsState extends State { Widget build(BuildContext context) { return Container( height: 44, - margin: const EdgeInsets.only(top: 4), + margin: const EdgeInsets.only(top: 8), child: Obx( () => ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 14.0), diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index 2ef2bf7f..849e16d5 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -26,6 +26,7 @@ class MainController extends GetxController { Box userInfoCache = GStrorage.userInfo; RxBool userLogin = false.obs; late Rx dynamicBadgeType = DynamicBadgeMode.number.obs; + late bool enableGradientBg; @override void onInit() { @@ -44,6 +45,8 @@ class MainController extends GetxController { if (dynamicBadgeType.value != DynamicBadgeMode.hidden) { getUnreadDynamic(); } + enableGradientBg = + setting.get(SettingBoxKey.enableGradientBg, defaultValue: true); } void onBackPressed(BuildContext context) { diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 29573501..20a12d35 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -117,14 +117,47 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { }, child: Scaffold( extendBody: true, - body: PageView( - physics: const NeverScrollableScrollPhysics(), - controller: _mainController.pageController, - onPageChanged: (index) { - _mainController.selectedIndex = index; - setState(() {}); - }, - children: _mainController.pages, + body: Stack( + children: [ + if (_mainController.enableGradientBg) + Align( + alignment: Alignment.topLeft, + child: Opacity( + opacity: 0.6, + child: Container( + width: MediaQuery.of(context).size.width, + // height: MediaQuery.of(context).size.height, + height: MediaQuery.of(context).padding.top + 400, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context) + .colorScheme + .primary + .withOpacity(0.6), + Theme.of(context) + .colorScheme + .surface + .withOpacity(0.3), + Theme.of(context).colorScheme.surface + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.1, 0.8, 1]), + ), + ), + ), + ), + PageView( + physics: const NeverScrollableScrollPhysics(), + controller: _mainController.pageController, + onPageChanged: (index) { + _mainController.selectedIndex = index; + setState(() {}); + }, + children: _mainController.pages, + ), + ], ), bottomNavigationBar: _mainController.navigationBars.length > 1 ? StreamBuilder( diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index cc413e59..3694fa68 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -1,11 +1,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/user/fav_folder.dart'; import 'package:pilipala/pages/media/index.dart'; -import 'package:pilipala/utils/main_stream.dart'; import 'package:pilipala/utils/utils.dart'; class MediaPage extends StatefulWidget { @@ -46,7 +46,16 @@ class _MediaPageState extends State super.build(context); Color primary = Theme.of(context).colorScheme.primary; return Scaffold( - appBar: AppBar(toolbarHeight: 30), + backgroundColor: Colors.transparent, + appBar: AppBar( + elevation: 0, + scrolledUnderElevation: 0, + toolbarHeight: 30, + backgroundColor: Colors.transparent, + systemOverlayStyle: Theme.of(context).brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, + ), body: SingleChildScrollView( controller: mediaController.scrollController, child: Column( diff --git a/lib/pages/rank/controller.dart b/lib/pages/rank/controller.dart index 64395ec7..bb34b13f 100644 --- a/lib/pages/rank/controller.dart +++ b/lib/pages/rank/controller.dart @@ -17,13 +17,10 @@ class RankController extends GetxController with GetTickerProviderStateMixin { Box setting = GStrorage.setting; late final StreamController searchBarStream = StreamController.broadcast(); - late bool enableGradientBg; @override void onInit() { super.onInit(); - enableGradientBg = - setting.get(SettingBoxKey.enableGradientBg, defaultValue: true); // 进行tabs配置 setTabConfig(); } diff --git a/lib/pages/rank/view.dart b/lib/pages/rank/view.dart index 4efa2b4e..218e60cf 100644 --- a/lib/pages/rank/view.dart +++ b/lib/pages/rank/view.dart @@ -31,94 +31,56 @@ class _RankPageState extends State @override Widget build(BuildContext context) { super.build(context); - Brightness currentBrightness = MediaQuery.of(context).platformBrightness; - // 设置状态栏图标的亮度 - if (_rankController.enableGradientBg) { - SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( - statusBarIconBrightness: currentBrightness == Brightness.light - ? Brightness.dark - : Brightness.light, - )); - } return Scaffold( extendBody: true, - extendBodyBehindAppBar: false, - appBar: _rankController.enableGradientBg - ? null - : AppBar(toolbarHeight: 0, elevation: 0), - body: Stack( + extendBodyBehindAppBar: true, + backgroundColor: Colors.transparent, + appBar: AppBar( + toolbarHeight: 0, + elevation: 0, + backgroundColor: Colors.transparent, + systemOverlayStyle: Theme.of(context).brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, + ), + body: Column( children: [ - // gradient background - if (_rankController.enableGradientBg) ...[ - Align( - alignment: Alignment.topLeft, - child: Opacity( - opacity: 0.6, - child: Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Theme.of(context) - .colorScheme - .primary - .withOpacity(0.9), - Theme.of(context) - .colorScheme - .primary - .withOpacity(0.5), - Theme.of(context).colorScheme.surface - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - stops: const [0, 0.0034, 0.34]), - ), + const CustomAppBar(), + if (_rankController.tabs.length > 1) ...[ + const SizedBox(height: 4), + SizedBox( + width: double.infinity, + height: 42, + child: Align( + alignment: Alignment.center, + child: TabBar( + controller: _rankController.tabController, + tabs: [ + for (var i in _rankController.tabs) Tab(text: i['label']) + ], + isScrollable: true, + dividerColor: Colors.transparent, + enableFeedback: true, + splashBorderRadius: BorderRadius.circular(10), + tabAlignment: TabAlignment.center, + onTap: (value) { + feedBack(); + if (_rankController.initialIndex.value == value) { + _rankController.tabsCtrList[value].animateToTop(); + } + _rankController.initialIndex.value = value; + }, ), ), ), + ] else ...[ + const SizedBox(height: 6), ], - Column( - children: [ - const CustomAppBar(), - if (_rankController.tabs.length > 1) ...[ - const SizedBox(height: 4), - SizedBox( - width: double.infinity, - height: 42, - child: Align( - alignment: Alignment.center, - child: TabBar( - controller: _rankController.tabController, - tabs: [ - for (var i in _rankController.tabs) - Tab(text: i['label']) - ], - isScrollable: true, - dividerColor: Colors.transparent, - enableFeedback: true, - splashBorderRadius: BorderRadius.circular(10), - tabAlignment: TabAlignment.center, - onTap: (value) { - feedBack(); - if (_rankController.initialIndex.value == value) { - _rankController.tabsCtrList[value].animateToTop(); - } - _rankController.initialIndex.value = value; - }, - ), - ), - ), - ] else ...[ - const SizedBox(height: 6), - ], - Expanded( - child: TabBarView( - controller: _rankController.tabController, - children: _rankController.tabsPageList, - ), - ), - ], + Expanded( + child: TabBarView( + controller: _rankController.tabController, + children: _rankController.tabsPageList, + ), ), ], ), diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 02a0bbe1..beb9dca1 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -4,6 +4,7 @@ import 'dart:ui'; 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'; From c248d33109c053ecef5bdb895e6a3ea2c9d857af Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 7 Jul 2024 11:01:09 +0800 Subject: [PATCH 030/152] mod --- lib/pages/dynamics/view.dart | 5 ----- lib/pages/main/view.dart | 13 +++++++------ lib/pages/media/view.dart | 11 +---------- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 495eb770..0fc16dcf 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -78,14 +78,9 @@ class _DynamicsPageState extends State Widget build(BuildContext context) { super.build(context); return Scaffold( - backgroundColor: Colors.transparent, appBar: AppBar( elevation: 0, scrolledUnderElevation: 0, - backgroundColor: Colors.transparent, - systemOverlayStyle: Theme.of(context).brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, title: SizedBox( height: 34, child: Stack( diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 20a12d35..00eafa0d 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -123,27 +123,28 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { Align( alignment: Alignment.topLeft, child: Opacity( - opacity: 0.6, + opacity: Theme.of(context).brightness == Brightness.dark + ? 0.3 + : 0.6, child: Container( width: MediaQuery.of(context).size.width, - // height: MediaQuery.of(context).size.height, - height: MediaQuery.of(context).padding.top + 400, + height: MediaQuery.of(context).size.height, decoration: BoxDecoration( gradient: LinearGradient( colors: [ Theme.of(context) .colorScheme .primary - .withOpacity(0.6), + .withOpacity(0.7), + Theme.of(context).colorScheme.surface, Theme.of(context) .colorScheme .surface .withOpacity(0.3), - Theme.of(context).colorScheme.surface ], begin: Alignment.topCenter, end: Alignment.bottomCenter, - stops: const [0.1, 0.8, 1]), + stops: const [0.1, 0.3, 5]), ), ), ), diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index 3694fa68..eb6d139c 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -46,16 +46,7 @@ class _MediaPageState extends State super.build(context); Color primary = Theme.of(context).colorScheme.primary; return Scaffold( - backgroundColor: Colors.transparent, - appBar: AppBar( - elevation: 0, - scrolledUnderElevation: 0, - toolbarHeight: 30, - backgroundColor: Colors.transparent, - systemOverlayStyle: Theme.of(context).brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, - ), + appBar: AppBar(toolbarHeight: 30), body: SingleChildScrollView( controller: mediaController.scrollController, child: Column( From aab51443717b4fb7bdf3b2284ee3bb7ecbbc3270 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 7 Jul 2024 22:27:28 +0800 Subject: [PATCH 031/152] =?UTF-8?q?fix:=20=E5=AD=97=E5=B9=95req=E5=AF=BC?= =?UTF-8?q?=E8=87=B4headerControl=E5=88=9D=E5=A7=8B=E5=8C=96=E5=BC=82?= =?UTF-8?q?=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/controller.dart | 21 +++++++------------ .../video/detail/widgets/header_control.dart | 2 +- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index dbdd9fce..662f45f2 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -152,17 +152,13 @@ class VideoDetailController extends GetxController defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa, defaultValue: AudioQuality.hiRes.code); oid.value = IdUtils.bv2av(Get.parameters['bvid']!); - getSubtitle().then( - (subtitles) { - headerControl = HeaderControl( - controller: plPlayerController, - videoDetailCtr: this, - floating: floating, - bvid: bvid, - videoType: videoType, - showSubtitleBtn: subtitles.isNotEmpty, - ); - }, + getSubtitle(); + headerControl = HeaderControl( + controller: plPlayerController, + videoDetailCtr: this, + floating: floating, + bvid: bvid, + videoType: videoType, ); } @@ -439,9 +435,6 @@ class VideoDetailController extends GetxController subtitles = result['data'].subtitles; getDanmaku(subtitles); } - return subtitles; - } else { - return []; } } diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index f3b9549a..b2bed21c 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -1329,7 +1329,7 @@ class _HeaderControlState extends State { ], /// 字幕 - if (widget.showSubtitleBtn!) + if (widget.showSubtitleBtn ?? true) ComBtn( icon: const Icon( Icons.closed_caption_off, From 6f69212952d840a409cf153ff6a3f05c3a4b639f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 8 Jul 2024 23:44:39 +0800 Subject: [PATCH 032/152] =?UTF-8?q?opt:=20=E8=A7=86=E9=A2=91=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=BC=82=E5=B8=B8=E6=8D=95=E8=8E=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/view.dart | 116 ++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 42 deletions(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index beb9dca1..a9353546 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -1,16 +1,15 @@ import 'dart:async'; import 'dart:io'; -import 'dart:ui'; 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'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; +import 'package:lottie/lottie.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/models/common/search_type.dart'; @@ -29,6 +28,7 @@ import 'package:status_bar_control/status_bar_control.dart'; import '../../../plugin/pl_player/models/bottom_control_type.dart'; import '../../../services/shutdown_timer_service.dart'; import 'widgets/app_bar.dart'; +import 'widgets/header_control.dart'; class VideoDetailPage extends StatefulWidget { const VideoDetailPage({Key? key}) : super(key: key); @@ -494,45 +494,77 @@ class _VideoDetailPageState extends State exitFullScreen(); } + Widget buildLoadingWidget() { + return Center(child: Lottie.asset('assets/loading.json', width: 200)); + } + + Widget buildVideoPlayerWidget(AsyncSnapshot snapshot) { + return Obx(() => !vdCtr.autoPlay.value + ? const SizedBox() + : PLVideoPlayer( + controller: plPlayerController!, + headerControl: vdCtr.headerControl, + danmuWidget: PlDanmaku( + key: Key(vdCtr.danmakuCid.value.toString()), + cid: vdCtr.danmakuCid.value, + playerController: plPlayerController!, + ), + bottomList: vdCtr.bottomList, + showEposideCb: () => vdCtr.videoType == SearchType.video + ? videoIntroController.showEposideHandler() + : bangumiIntroController.showEposideHandler(), + fullScreenCb: (bool status) { + videoHeight.value = + status ? Get.size.height : defaultVideoHeight; + }, + )); + } + + Widget buildErrorWidget(dynamic error) { + return Obx( + () => SizedBox( + height: videoHeight.value, + width: Get.size.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Text('加载失败', style: TextStyle(color: Colors.white)), + Text('$error', style: const TextStyle(color: Colors.white)), + const SizedBox(height: 10), + IconButton.filled( + onPressed: () { + setState(() { + _futureBuilderFuture = vdCtr.queryVideoUrl(); + }); + }, + icon: const Icon(Icons.refresh), + ) + ], + ), + ), + ); + } + /// 播放器面板 - Widget videoPlayerPanel = FutureBuilder( - future: _futureBuilderFuture, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData && snapshot.data['status']) { - return Obx( - () { - return !vdCtr.autoPlay.value - ? const SizedBox() - : Obx( - () => PLVideoPlayer( - controller: plPlayerController!, - headerControl: vdCtr.headerControl, - danmuWidget: PlDanmaku( - key: Key(vdCtr.danmakuCid.value.toString()), - cid: vdCtr.danmakuCid.value, - playerController: plPlayerController!, - ), - bottomList: vdCtr.bottomList, - showEposideCb: () => vdCtr.videoType == SearchType.video - ? videoIntroController.showEposideHandler() - : bangumiIntroController.showEposideHandler(), - fullScreenCb: (bool status) { - if (status) { - videoHeight.value = Get.size.height; - } else { - videoHeight.value = defaultVideoHeight; - } - }, - ), - ); - }, - ); - } else { - // 加载失败异常处理 - return const SizedBox(); - } - }, - ); + Widget buildVideoPlayerPanel() { + return FutureBuilder( + future: _futureBuilderFuture, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return buildLoadingWidget(); + } else if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasData && snapshot.data['status']) { + return buildVideoPlayerWidget(snapshot); + } else { + return buildErrorWidget(snapshot.error); + } + } else { + return buildErrorWidget('未知错误'); + } + }, + ); + } Widget childWhenDisabled = SafeArea( top: MediaQuery.of(context).orientation == Orientation.portrait && @@ -607,7 +639,7 @@ class _VideoDetailPageState extends State tag: heroTag, child: Stack( children: [ - if (isShowing) videoPlayerPanel, + if (isShowing) buildVideoPlayerPanel(), /// 关闭自动播放时 手动播放 Obx( @@ -717,7 +749,7 @@ class _VideoDetailPageState extends State if (Platform.isAndroid) { return PiPSwitcher( childWhenDisabled: childWhenDisabled, - childWhenEnabled: videoPlayerPanel, + childWhenEnabled: buildVideoPlayerPanel(), floating: floating, ); } else { From 189abbc747b2887a94d8fb57b45eb0a59e62344e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 10 Jul 2024 23:58:55 +0800 Subject: [PATCH 033/152] =?UTF-8?q?fix:=20=E5=AF=BC=E8=88=AA=E6=A0=8F?= =?UTF-8?q?=E8=83=8C=E6=99=AF=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/home/view.dart | 14 +++++++++++--- lib/pages/rank/view.dart | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 0163723b..187ed6c3 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -54,9 +55,16 @@ class _HomePageState extends State toolbarHeight: 0, elevation: 0, backgroundColor: Colors.transparent, - systemOverlayStyle: Theme.of(context).brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, + systemOverlayStyle: Platform.isAndroid + ? SystemUiOverlayStyle( + statusBarIconBrightness: + Theme.of(context).brightness == Brightness.dark + ? Brightness.light + : Brightness.dark, + ) + : Theme.of(context).brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, ), body: Column( children: [ diff --git a/lib/pages/rank/view.dart b/lib/pages/rank/view.dart index 218e60cf..cf7789a2 100644 --- a/lib/pages/rank/view.dart +++ b/lib/pages/rank/view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -39,9 +40,16 @@ class _RankPageState extends State toolbarHeight: 0, elevation: 0, backgroundColor: Colors.transparent, - systemOverlayStyle: Theme.of(context).brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, + systemOverlayStyle: Platform.isAndroid + ? SystemUiOverlayStyle( + statusBarIconBrightness: + Theme.of(context).brightness == Brightness.dark + ? Brightness.light + : Brightness.dark, + ) + : Theme.of(context).brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, ), body: Column( children: [ From cc2595fd7b9246e97691569184bf03ed16e84850 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 13 Jul 2024 16:47:44 +0800 Subject: [PATCH 034/152] =?UTF-8?q?opt:=20=E5=9B=BE=E7=89=87=E9=A2=84?= =?UTF-8?q?=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/pull_to_refresh_header.dart | 130 ------ lib/pages/dynamics/widgets/content_panel.dart | 92 ++-- lib/pages/dynamics/widgets/pic_panel.dart | 68 ++- lib/pages/main/controller.dart | 1 + lib/pages/preview/controller.dart | 50 --- lib/pages/preview/index.dart | 4 - lib/pages/preview/view.dart | 290 ------------- .../detail/reply/widgets/reply_item.dart | 137 +++--- lib/pages/video/detail/view.dart | 11 + lib/plugin/pl_gallery/custom_dismissible.dart | 156 +++++++ lib/plugin/pl_gallery/hero_dialog_route.dart | 63 +++ lib/plugin/pl_gallery/index.dart | 6 + .../interactive_viewer_boundary.dart | 117 +++++ .../pl_gallery/interactiveviewer_gallery.dart | 399 ++++++++++++++++++ lib/router/app_pages.dart | 8 - pubspec.lock | 24 -- pubspec.yaml | 1 - 17 files changed, 958 insertions(+), 599 deletions(-) delete mode 100644 lib/common/widgets/pull_to_refresh_header.dart delete mode 100644 lib/pages/preview/controller.dart delete mode 100644 lib/pages/preview/index.dart delete mode 100644 lib/pages/preview/view.dart create mode 100644 lib/plugin/pl_gallery/custom_dismissible.dart create mode 100644 lib/plugin/pl_gallery/hero_dialog_route.dart create mode 100644 lib/plugin/pl_gallery/index.dart create mode 100644 lib/plugin/pl_gallery/interactive_viewer_boundary.dart create mode 100644 lib/plugin/pl_gallery/interactiveviewer_gallery.dart diff --git a/lib/common/widgets/pull_to_refresh_header.dart b/lib/common/widgets/pull_to_refresh_header.dart deleted file mode 100644 index 46db5138..00000000 --- a/lib/common/widgets/pull_to_refresh_header.dart +++ /dev/null @@ -1,130 +0,0 @@ -// ignore_for_file: depend_on_referenced_packages - -import 'dart:math'; -import 'dart:ui' as ui show Image; - -import 'package:extended_image/extended_image.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart'; - -double get maxDragOffset => 100; -double hideHeight = maxDragOffset / 2.3; -double refreshHeight = maxDragOffset / 1.5; - -class PullToRefreshHeader extends StatelessWidget { - const PullToRefreshHeader( - this.info, - this.lastRefreshTime, { - this.color, - super.key, - }); - - final PullToRefreshScrollNotificationInfo? info; - final DateTime? lastRefreshTime; - final Color? color; - - @override - Widget build(BuildContext context) { - final PullToRefreshScrollNotificationInfo? infos = info; - if (infos == null) { - return const SizedBox(); - } - String text = ''; - if (infos.mode == PullToRefreshIndicatorMode.armed) { - text = 'Release to refresh'; - } else if (infos.mode == PullToRefreshIndicatorMode.refresh || - infos.mode == PullToRefreshIndicatorMode.snap) { - text = 'Loading...'; - } else if (infos.mode == PullToRefreshIndicatorMode.done) { - text = 'Refresh completed.'; - } else if (infos.mode == PullToRefreshIndicatorMode.drag) { - text = 'Pull to refresh'; - } else if (infos.mode == PullToRefreshIndicatorMode.canceled) { - text = 'Cancel refresh'; - } - - final TextStyle ts = const TextStyle( - color: Colors.grey, - ).copyWith(fontSize: 14); - - final double dragOffset = info?.dragOffset ?? 0.0; - - final DateTime time = lastRefreshTime ?? DateTime.now(); - final double top = -hideHeight + dragOffset; - return Container( - height: dragOffset, - color: color ?? Colors.transparent, - // padding: EdgeInsets.only(top: dragOffset / 3), - // padding: EdgeInsets.only(bottom: 5.0), - child: Stack( - children: [ - Positioned( - left: 0.0, - right: 0.0, - top: top, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - alignment: Alignment.centerRight, - margin: const EdgeInsets.only(right: 12.0), - child: RefreshImage(top, null), - ), - ), - Column( - children: [ - Text(text, style: ts), - Text( - 'Last updated:${DateFormat('yyyy-MM-dd hh:mm').format(time)}', - style: ts.copyWith(fontSize: 14), - ) - ], - ), - const Spacer(), - ], - ), - ) - ], - ), - ); - } -} - -class RefreshImage extends StatelessWidget { - const RefreshImage(this.top, Key? key) : super(key: key); - - final double top; - - @override - Widget build(BuildContext context) { - const double imageSize = 30; - return ExtendedImage.asset( - 'assets/flutterCandies_grey.png', - width: imageSize, - height: imageSize, - afterPaintImage: (Canvas canvas, Rect rect, ui.Image image, Paint paint) { - final double imageHeight = image.height.toDouble(); - final double imageWidth = image.width.toDouble(); - final Size size = rect.size; - final double y = - (1 - min(top / (refreshHeight - hideHeight), 1)) * imageHeight; - - canvas.drawImageRect( - image, - Rect.fromLTWH(0.0, y, imageWidth, imageHeight - y), - Rect.fromLTWH(rect.left, rect.top + y / imageHeight * size.height, - size.width, (imageHeight - y) / imageHeight * size.height), - Paint() - ..colorFilter = - const ColorFilter.mode(Color(0xFFea5504), BlendMode.srcIn) - ..isAntiAlias = false - ..filterQuality = FilterQuality.low, - ); - - //canvas.restore(); - }, - ); - } -} diff --git a/lib/pages/dynamics/widgets/content_panel.dart b/lib/pages/dynamics/widgets/content_panel.dart index e1beaeb2..28451dde 100644 --- a/lib/pages/dynamics/widgets/content_panel.dart +++ b/lib/pages/dynamics/widgets/content_panel.dart @@ -1,11 +1,11 @@ // 内容 +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/dynamics/result.dart'; -import 'package:pilipala/pages/preview/index.dart'; - +import 'package:pilipala/plugin/pl_gallery/index.dart'; import 'rich_node_panel.dart'; // ignore: must_be_immutable @@ -59,17 +59,15 @@ class _ContentState extends State { (pictureItem.height != null && pictureItem.width != null ? pictureItem.height! / pictureItem.width! : 1); - return GestureDetector( - onTap: () { - showDialog( - useSafeArea: false, - context: context, - builder: (context) { - return ImagePreview(initialPage: 0, imgList: picList); - }, - ); + return Hero( + tag: pictureItem.url!, + placeholderBuilder: + (BuildContext context, Size heroSize, Widget child) { + return child; }, - child: Container( + child: GestureDetector( + onTap: () => onPreviewImg(picList, 1, context), + child: Container( padding: const EdgeInsets.only(top: 4), constraints: BoxConstraints(maxHeight: maxHeight), width: box.maxWidth / 2, @@ -91,7 +89,9 @@ class _ContentState extends State { ) : const SizedBox(), ], - )), + ), + ), + ), ); }, ), @@ -102,26 +102,23 @@ class _ContentState extends State { List list = []; for (var i = 0; i < len; i++) { picList.add(pics[i].url!); + } + for (var i = 0; i < len; i++) { list.add( LayoutBuilder( builder: (context, BoxConstraints box) { double maxWidth = box.maxWidth.truncateToDouble(); - return GestureDetector( - onTap: () { - showDialog( - useSafeArea: false, - context: context, - builder: (context) { - return ImagePreview(initialPage: i, imgList: picList); - }, - ); - }, - child: NetworkImgLayer( - src: pics[i].url, - width: maxWidth, - height: maxWidth, - origAspectRatio: - pics[i].width!.toInt() / pics[i].height!.toInt(), + return Hero( + tag: picList[i], + child: GestureDetector( + onTap: () => onPreviewImg(picList, i, context), + child: NetworkImgLayer( + src: pics[i].url, + width: maxWidth, + height: maxWidth, + origAspectRatio: + pics[i].width!.toInt() / pics[i].height!.toInt(), + ), ), ); }, @@ -163,6 +160,43 @@ class _ContentState extends State { ); } + void onPreviewImg(picList, initIndex, context) { + Navigator.of(context).push( + HeroDialogRoute( + builder: (BuildContext context) => InteractiveviewerGallery( + sources: picList, + initIndex: initIndex, + itemBuilder: ( + BuildContext context, + int index, + bool isFocus, + bool enablePageView, + ) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (enablePageView) { + Navigator.of(context).pop(); + } + }, + child: Center( + child: Hero( + tag: picList[index], + child: CachedNetworkImage( + fadeInDuration: const Duration(milliseconds: 0), + imageUrl: picList[index], + fit: BoxFit.contain, + ), + ), + ), + ); + }, + onPageChanged: (int pageIndex) {}, + ), + ), + ); + } + @override Widget build(BuildContext context) { TextStyle authorStyle = diff --git a/lib/pages/dynamics/widgets/pic_panel.dart b/lib/pages/dynamics/widgets/pic_panel.dart index 4e94e6fd..783fe89b 100644 --- a/lib/pages/dynamics/widgets/pic_panel.dart +++ b/lib/pages/dynamics/widgets/pic_panel.dart @@ -1,9 +1,47 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; -import 'package:pilipala/pages/preview/index.dart'; +import 'package:pilipala/plugin/pl_gallery/index.dart'; + +void onPreviewImg(currentUrl, picList, initIndex, context) { + Navigator.of(context).push( + HeroDialogRoute( + builder: (BuildContext context) => InteractiveviewerGallery( + sources: picList, + initIndex: initIndex, + itemBuilder: ( + BuildContext context, + int index, + bool isFocus, + bool enablePageView, + ) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (enablePageView) { + Navigator.of(context).pop(); + } + }, + child: Center( + child: Hero( + tag: picList[index], + child: CachedNetworkImage( + fadeInDuration: const Duration(milliseconds: 0), + imageUrl: picList[index], + fit: BoxFit.contain, + ), + ), + ), + ); + }, + onPageChanged: (int pageIndex) {}, + ), + ), + ); +} Widget picWidget(item, context) { String type = item.modules.moduleDynamic.major.type; @@ -21,25 +59,25 @@ Widget picWidget(item, context) { List list = []; for (var i = 0; i < len; i++) { picList.add(pictures[i].src ?? pictures[i].url); + } + for (var i = 0; i < len; i++) { list.add( LayoutBuilder( builder: (context, BoxConstraints box) { - return GestureDetector( - onTap: () { - showDialog( - useSafeArea: false, - context: context, - builder: (context) { - return ImagePreview(initialPage: i, imgList: picList); - }, - ); + return Hero( + tag: picList[i], + placeholderBuilder: + (BuildContext context, Size heroSize, Widget child) { + return child; }, - child: NetworkImgLayer( - src: pictures[i].src ?? pictures[i].url, - width: box.maxWidth, - height: box.maxWidth, + child: GestureDetector( + onTap: () => onPreviewImg(picList[i], picList, i, context), + child: NetworkImgLayer( + src: pictures[i].src ?? pictures[i].url, + width: box.maxWidth, + height: box.maxWidth, + ), ), - // ), ); }, ), diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index 849e16d5..ad2a7781 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -27,6 +27,7 @@ class MainController extends GetxController { RxBool userLogin = false.obs; late Rx dynamicBadgeType = DynamicBadgeMode.number.obs; late bool enableGradientBg; + bool imgPreviewStatus = false; @override void onInit() { diff --git a/lib/pages/preview/controller.dart b/lib/pages/preview/controller.dart deleted file mode 100644 index bb06b275..00000000 --- a/lib/pages/preview/controller.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:io'; - -import 'package:device_info_plus/device_info_plus.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:dio/dio.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:share_plus/share_plus.dart'; - -class PreviewController extends GetxController { - DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); - RxInt initialPage = 0.obs; - RxInt currentPage = 1.obs; - RxList imgList = [].obs; - bool storage = true; - bool videos = true; - bool photos = true; - String currentImgUrl = ''; - - requestPermission() async { - Map statuses = await [ - Permission.storage, - // Permission.photos - ].request(); - - statuses[Permission.storage].toString(); - // final photosInfo = statuses[Permission.photos].toString(); - } - - // 图片分享 - void onShareImg() async { - SmartDialog.showLoading(); - var response = await Dio().get(imgList[initialPage.value], - options: Options(responseType: ResponseType.bytes)); - final temp = await getTemporaryDirectory(); - SmartDialog.dismiss(); - String imgName = - "plpl_pic_${DateTime.now().toString().split('-').join()}.jpg"; - var path = '${temp.path}/$imgName'; - File(path).writeAsBytesSync(response.data); - Share.shareXFiles([XFile(path)], subject: imgList[initialPage.value]); - } - - void onChange(int index) { - initialPage.value = index; - currentPage.value = index + 1; - currentImgUrl = imgList[index]; - } -} diff --git a/lib/pages/preview/index.dart b/lib/pages/preview/index.dart deleted file mode 100644 index 9fb82e8d..00000000 --- a/lib/pages/preview/index.dart +++ /dev/null @@ -1,4 +0,0 @@ -library preview; - -export './controller.dart'; -export './view.dart'; diff --git a/lib/pages/preview/view.dart b/lib/pages/preview/view.dart deleted file mode 100644 index 13868d37..00000000 --- a/lib/pages/preview/view.dart +++ /dev/null @@ -1,290 +0,0 @@ -// ignore_for_file: library_private_types_in_public_api - -import 'dart:io'; - -import 'package:dismissible_page/dismissible_page.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:flutter/material.dart'; -import 'package:extended_image/extended_image.dart'; -import 'package:pilipala/utils/download.dart'; -import 'controller.dart'; -import 'package:status_bar_control/status_bar_control.dart'; - -typedef DoubleClickAnimationListener = void Function(); - -class ImagePreview extends StatefulWidget { - final int? initialPage; - final List? imgList; - const ImagePreview({ - Key? key, - this.initialPage, - this.imgList, - }) : super(key: key); - - @override - _ImagePreviewState createState() => _ImagePreviewState(); -} - -class _ImagePreviewState extends State - with TickerProviderStateMixin { - final PreviewController _previewController = Get.put(PreviewController()); - // late AnimationController animationController; - late AnimationController _doubleClickAnimationController; - Animation? _doubleClickAnimation; - late DoubleClickAnimationListener _doubleClickAnimationListener; - List doubleTapScales = [1.0, 2.0]; - bool _dismissDisabled = false; - - @override - void initState() { - super.initState(); - - _previewController.initialPage.value = widget.initialPage!; - _previewController.currentPage.value = widget.initialPage! + 1; - _previewController.imgList.value = widget.imgList!; - _previewController.currentImgUrl = widget.imgList![widget.initialPage!]; - // animationController = AnimationController( - // vsync: this, duration: const Duration(milliseconds: 400)); - setStatusBar(); - _doubleClickAnimationController = AnimationController( - duration: const Duration(milliseconds: 250), vsync: this); - } - - onOpenMenu() { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - clipBehavior: Clip.hardEdge, - contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - onTap: () { - _previewController.onShareImg(); - Get.back(); - }, - dense: true, - title: const Text('分享', style: TextStyle(fontSize: 14)), - ), - ListTile( - onTap: () { - Clipboard.setData( - ClipboardData(text: _previewController.currentImgUrl)) - .then((value) { - Get.back(); - SmartDialog.showToast('已复制到粘贴板'); - }).catchError((err) { - SmartDialog.showNotify( - msg: err.toString(), - notifyType: NotifyType.error, - ); - }); - }, - dense: true, - title: const Text('复制链接', style: TextStyle(fontSize: 14)), - ), - ListTile( - onTap: () { - Get.back(); - DownloadUtils.downloadImg(_previewController.currentImgUrl); - }, - dense: true, - title: const Text('保存到手机', style: TextStyle(fontSize: 14)), - ), - ], - ), - ); - }, - ); - } - - // 隐藏状态栏,避免遮挡图片内容 - setStatusBar() async { - if (Platform.isIOS || Platform.isAndroid) { - await StatusBarControl.setHidden(true, - animation: StatusBarAnimation.SLIDE); - } - } - - @override - void dispose() { - // animationController.dispose(); - try { - StatusBarControl.setHidden(false, animation: StatusBarAnimation.SLIDE); - } catch (_) {} - _doubleClickAnimationController.dispose(); - clearGestureDetailsCache(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - primary: false, - extendBody: true, - appBar: AppBar( - primary: false, - toolbarHeight: 0, - backgroundColor: Colors.black, - systemOverlayStyle: SystemUiOverlayStyle.dark, - ), - body: Stack( - children: [ - GestureDetector( - onLongPress: () => onOpenMenu(), - child: ExtendedImageGesturePageView.builder( - controller: ExtendedPageController( - initialPage: _previewController.initialPage.value, - pageSpacing: 0, - ), - onPageChanged: (int index) => _previewController.onChange(index), - canScrollPage: (GestureDetails? gestureDetails) => - gestureDetails!.totalScale! <= 1.0, - itemCount: widget.imgList!.length, - itemBuilder: (BuildContext context, int index) { - return ExtendedImage.network( - widget.imgList![index], - fit: BoxFit.contain, - mode: ExtendedImageMode.gesture, - onDoubleTap: (ExtendedImageGestureState state) { - final Offset? pointerDownPosition = - state.pointerDownPosition; - final double? begin = state.gestureDetails!.totalScale; - double end; - - //remove old - _doubleClickAnimation - ?.removeListener(_doubleClickAnimationListener); - - //stop pre - _doubleClickAnimationController.stop(); - - //reset to use - _doubleClickAnimationController.reset(); - - if (begin == doubleTapScales[0]) { - setState(() { - _dismissDisabled = true; - }); - end = doubleTapScales[1]; - } else { - setState(() { - _dismissDisabled = false; - }); - end = doubleTapScales[0]; - } - - _doubleClickAnimationListener = () { - state.handleDoubleTap( - scale: _doubleClickAnimation!.value, - doubleTapPosition: pointerDownPosition); - }; - _doubleClickAnimation = _doubleClickAnimationController - .drive(Tween(begin: begin, end: end)); - - _doubleClickAnimation! - .addListener(_doubleClickAnimationListener); - - _doubleClickAnimationController.forward(); - }, - // ignore: body_might_complete_normally_nullable - loadStateChanged: (ExtendedImageState state) { - if (state.extendedImageLoadState == LoadState.loading) { - final ImageChunkEvent? loadingProgress = - state.loadingProgress; - final double? progress = - loadingProgress?.expectedTotalBytes != null - ? loadingProgress!.cumulativeBytesLoaded / - loadingProgress.expectedTotalBytes! - : null; - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: 150.0, - child: LinearProgressIndicator( - value: progress, - color: Colors.white, - ), - ), - // const SizedBox(height: 10.0), - // Text('${((progress ?? 0.0) * 100).toInt()}%',), - ], - ), - ); - } - }, - initGestureConfigHandler: (ExtendedImageState state) { - return GestureConfig( - inPageView: true, - initialScale: 1.0, - maxScale: 5.0, - animationMaxScale: 6.0, - initialAlignment: InitialAlignment.center, - ); - }, - ); - }, - ), - ), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: Container( - padding: EdgeInsets.only( - left: 20, - right: 20, - bottom: MediaQuery.of(context).padding.bottom + 30), - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.transparent, - Colors.black87, - ], - tileMode: TileMode.mirror, - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - widget.imgList!.length > 1 - ? Obx( - () => Text.rich( - textAlign: TextAlign.center, - TextSpan( - style: const TextStyle( - color: Colors.white, fontSize: 16), - children: [ - TextSpan( - text: _previewController.currentPage - .toString()), - const TextSpan(text: ' / '), - TextSpan( - text: - widget.imgList!.length.toString()), - ]), - ), - ) - : const SizedBox(), - IconButton( - onPressed: () => Get.back(), - icon: const Icon(Icons.close, color: Colors.white), - ), - ], - )), - ), - ], - ), - ); - } -} diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index d8f696d4..c4c26e6b 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -1,4 +1,5 @@ import 'package:appscheme/appscheme.dart'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -9,9 +10,10 @@ import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/common/reply_type.dart'; import 'package:pilipala/models/video/reply/item.dart'; -import 'package:pilipala/pages/preview/index.dart'; +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/utils/app_scheme.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/id_utils.dart'; @@ -540,6 +542,53 @@ InlineSpan buildContent( ); } + void onPreviewImg(picList, initIndex) { + final MainController mainController = Get.find(); + mainController.imgPreviewStatus = true; + Navigator.of(context).push( + HeroDialogRoute( + builder: (BuildContext context) => InteractiveviewerGallery( + sources: picList, + initIndex: initIndex, + itemBuilder: ( + BuildContext context, + int index, + bool isFocus, + bool enablePageView, + ) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (enablePageView) { + Navigator.of(context).pop(); + final MainController mainController = + Get.find(); + mainController.imgPreviewStatus = false; + } + }, + child: Center( + child: Hero( + tag: picList[index], + child: CachedNetworkImage( + fadeInDuration: const Duration(milliseconds: 0), + imageUrl: picList[index], + fit: BoxFit.contain, + ), + ), + ), + ); + }, + onPageChanged: (int pageIndex) {}, + onDismissed: (int value) { + print('onDismissed'); + final MainController mainController = Get.find(); + mainController.imgPreviewStatus = false; + }, + ), + ), + ); + } + // 分割文本并处理每个部分 content.message.splitMapJoin( pattern, @@ -831,38 +880,33 @@ InlineSpan buildContent( .truncateToDouble(); } catch (_) {} - return GestureDetector( - onTap: () { - showDialog( - useSafeArea: false, - context: context, - builder: (BuildContext context) { - return ImagePreview(initialPage: 0, imgList: picList); - }, - ); - }, - child: Container( - padding: const EdgeInsets.only(top: 4), - constraints: BoxConstraints(maxHeight: maxHeight), - width: box.maxWidth / 2, - height: height, - child: Stack( - children: [ - Positioned.fill( - child: NetworkImgLayer( - src: pictureItem['img_src'], - width: box.maxWidth / 2, - height: height, + return Hero( + tag: picList[0], + child: GestureDetector( + onTap: () => onPreviewImg(picList, 0), + child: Container( + padding: const EdgeInsets.only(top: 4), + constraints: BoxConstraints(maxHeight: maxHeight), + width: box.maxWidth / 2, + height: height, + child: Stack( + children: [ + Positioned.fill( + child: NetworkImgLayer( + src: picList[0], + width: box.maxWidth / 2, + height: height, + ), ), - ), - height > Get.size.height * 0.9 - ? const PBadge( - text: '长图', - right: 8, - bottom: 8, - ) - : const SizedBox(), - ], + height > Get.size.height * 0.9 + ? const PBadge( + text: '长图', + right: 8, + bottom: 8, + ) + : const SizedBox(), + ], + ), ), ), ); @@ -874,25 +918,22 @@ InlineSpan buildContent( List list = []; for (var i = 0; i < len; i++) { picList.add(content.pictures[i]['img_src']); + } + for (var i = 0; i < len; i++) { list.add( LayoutBuilder( builder: (context, BoxConstraints box) { - return GestureDetector( - onTap: () { - showDialog( - useSafeArea: false, - context: context, - builder: (context) { - return ImagePreview(initialPage: i, imgList: picList); - }, - ); - }, - child: NetworkImgLayer( - src: content.pictures[i]['img_src'], - width: box.maxWidth, - height: box.maxWidth, - origAspectRatio: content.pictures[i]['img_width'] / - content.pictures[i]['img_height']), + return Hero( + tag: picList[i], + child: GestureDetector( + onTap: () => onPreviewImg(picList, i), + child: NetworkImgLayer( + src: picList[i], + width: box.maxWidth, + height: box.maxWidth, + origAspectRatio: content.pictures[i]['img_width'] / + content.pictures[i]['img_height']), + ), ); }, ), diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index a9353546..331986dd 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -15,6 +15,7 @@ import 'package:pilipala/http/user.dart'; import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/pages/bangumi/introduction/index.dart'; import 'package:pilipala/pages/danmaku/view.dart'; +import 'package:pilipala/pages/main/index.dart'; import 'package:pilipala/pages/video/detail/reply/index.dart'; import 'package:pilipala/pages/video/detail/controller.dart'; import 'package:pilipala/pages/video/detail/introduction/index.dart'; @@ -240,6 +241,11 @@ class _VideoDetailPageState extends State @override // 离开当前页面时 void didPushNext() async { + final MainController mainController = Get.find(); + if (mainController.imgPreviewStatus) { + return; + } + /// 开启 if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false) as bool) { @@ -259,6 +265,11 @@ class _VideoDetailPageState extends State @override // 返回当前页面时 void didPopNext() async { + final MainController mainController = Get.find(); + if (mainController.imgPreviewStatus) { + return; + } + if (plPlayerController != null && plPlayerController!.videoPlayerController != null) { setState(() { diff --git a/lib/plugin/pl_gallery/custom_dismissible.dart b/lib/plugin/pl_gallery/custom_dismissible.dart new file mode 100644 index 00000000..05761cac --- /dev/null +++ b/lib/plugin/pl_gallery/custom_dismissible.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; + +/// A widget used to dismiss its [child]. +/// +/// Similar to [Dismissible] with some adjustments. +class CustomDismissible extends StatefulWidget { + const CustomDismissible({ + required this.child, + this.onDismissed, + this.dismissThreshold = 0.2, + this.enabled = true, + Key? key, + }) : super(key: key); + + final Widget child; + final double dismissThreshold; + final VoidCallback? onDismissed; + final bool enabled; + + @override + State createState() => _CustomDismissibleState(); +} + +class _CustomDismissibleState extends State + with SingleTickerProviderStateMixin { + late AnimationController _animateController; + late Animation _moveAnimation; + late Animation _scaleAnimation; + late Animation _opacityAnimation; + + double _dragExtent = 0; + bool _dragUnderway = false; + + bool get _isActive => _dragUnderway || _animateController.isAnimating; + + @override + void initState() { + super.initState(); + + _animateController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + + _updateMoveAnimation(); + } + + @override + void dispose() { + _animateController.dispose(); + + super.dispose(); + } + + void _updateMoveAnimation() { + final double end = _dragExtent.sign; + + _moveAnimation = _animateController.drive( + Tween( + begin: Offset.zero, + end: Offset(0, end), + ), + ); + + _scaleAnimation = _animateController.drive(Tween( + begin: 1, + end: 0.5, + )); + + _opacityAnimation = DecorationTween( + begin: const BoxDecoration(color: Color(0xFF000000)), + end: const BoxDecoration(color: Color(0x00000000)), + ).animate(_animateController); + } + + void _handleDragStart(DragStartDetails details) { + _dragUnderway = true; + + if (_animateController.isAnimating) { + _dragExtent = + _animateController.value * context.size!.height * _dragExtent.sign; + _animateController.stop(); + } else { + _dragExtent = 0.0; + _animateController.value = 0.0; + } + setState(_updateMoveAnimation); + } + + void _handleDragUpdate(DragUpdateDetails details) { + if (!_isActive || _animateController.isAnimating) { + return; + } + + final double delta = details.primaryDelta!; + final double oldDragExtent = _dragExtent; + + if (_dragExtent + delta < 0) { + _dragExtent += delta; + } else if (_dragExtent + delta > 0) { + _dragExtent += delta; + } + + if (oldDragExtent.sign != _dragExtent.sign) { + setState(_updateMoveAnimation); + } + + if (!_animateController.isAnimating) { + _animateController.value = _dragExtent.abs() / context.size!.height; + } + } + + void _handleDragEnd(DragEndDetails details) { + if (!_isActive || _animateController.isAnimating) { + return; + } + + _dragUnderway = false; + + if (_animateController.isCompleted) { + return; + } + + if (!_animateController.isDismissed) { + // if the dragged value exceeded the dismissThreshold, call onDismissed + // else animate back to initial position. + if (_animateController.value > widget.dismissThreshold) { + widget.onDismissed?.call(); + } else { + _animateController.reverse(); + } + } + } + + @override + Widget build(BuildContext context) { + final Widget content = DecoratedBoxTransition( + decoration: _opacityAnimation, + child: SlideTransition( + position: _moveAnimation, + child: ScaleTransition( + scale: _scaleAnimation, + child: widget.child, + ), + ), + ); + + return GestureDetector( + behavior: HitTestBehavior.translucent, + onVerticalDragStart: widget.enabled ? _handleDragStart : null, + onVerticalDragUpdate: widget.enabled ? _handleDragUpdate : null, + onVerticalDragEnd: widget.enabled ? _handleDragEnd : null, + child: content, + ); + } +} diff --git a/lib/plugin/pl_gallery/hero_dialog_route.dart b/lib/plugin/pl_gallery/hero_dialog_route.dart new file mode 100644 index 00000000..d9f129d2 --- /dev/null +++ b/lib/plugin/pl_gallery/hero_dialog_route.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +/// A [PageRoute] with a semi transparent background. +/// +/// Similar to calling [showDialog] except it can be used with a [Navigator] to +/// show a [Hero] animation. +class HeroDialogRoute extends PageRoute { + HeroDialogRoute({ + required this.builder, + this.onBackgroundTap, + }) : super(); + + final WidgetBuilder builder; + + /// Called when the background is tapped. + final VoidCallback? onBackgroundTap; + + @override + bool get opaque => false; + + @override + bool get barrierDismissible => true; + + @override + String? get barrierLabel => null; + + @override + Duration get transitionDuration => const Duration(milliseconds: 300); + + @override + bool get maintainState => true; + + @override + Color? get barrierColor => null; + + @override + Widget buildTransitions( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return FadeTransition( + opacity: CurvedAnimation(parent: animation, curve: Curves.easeOut), + child: child, + ); + } + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + final Widget child = builder(context); + final Widget result = Semantics( + scopesRoute: true, + explicitChildNodes: true, + child: child, + ); + return result; + } +} diff --git a/lib/plugin/pl_gallery/index.dart b/lib/plugin/pl_gallery/index.dart new file mode 100644 index 00000000..5a6f04e1 --- /dev/null +++ b/lib/plugin/pl_gallery/index.dart @@ -0,0 +1,6 @@ +library pl_gallery; + +export './hero_dialog_route.dart'; +export './custom_dismissible.dart'; +export './interactiveviewer_gallery.dart'; +export './interactive_viewer_boundary.dart'; diff --git a/lib/plugin/pl_gallery/interactive_viewer_boundary.dart b/lib/plugin/pl_gallery/interactive_viewer_boundary.dart new file mode 100644 index 00000000..929b7a6d --- /dev/null +++ b/lib/plugin/pl_gallery/interactive_viewer_boundary.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; + +/// A callback for the [InteractiveViewerBoundary] that is called when the scale +/// changed. +typedef ScaleChanged = void Function(double scale); + +/// Builds an [InteractiveViewer] and provides callbacks that are called when a +/// horizontal boundary has been hit. +/// +/// The callbacks are called when an interaction ends by listening to the +/// [InteractiveViewer.onInteractionEnd] callback. +class InteractiveViewerBoundary extends StatefulWidget { + const InteractiveViewerBoundary({ + required this.child, + required this.boundaryWidth, + this.controller, + this.onScaleChanged, + this.onLeftBoundaryHit, + this.onRightBoundaryHit, + this.onNoBoundaryHit, + this.maxScale, + this.minScale, + Key? key, + }) : super(key: key); + + final Widget child; + + /// The max width this widget can have. + /// + /// If the [InteractiveViewer] can take up the entire screen width, this + /// should be set to `MediaQuery.of(context).size.width`. + final double boundaryWidth; + + /// The [TransformationController] for the [InteractiveViewer]. + final TransformationController? controller; + + /// Called when the scale changed after an interaction ended. + final ScaleChanged? onScaleChanged; + + /// Called when the left boundary has been hit after an interaction ended. + final VoidCallback? onLeftBoundaryHit; + + /// Called when the right boundary has been hit after an interaction ended. + final VoidCallback? onRightBoundaryHit; + + /// Called when no boundary has been hit after an interaction ended. + final VoidCallback? onNoBoundaryHit; + + final double? maxScale; + + final double? minScale; + + @override + InteractiveViewerBoundaryState createState() => + InteractiveViewerBoundaryState(); +} + +class InteractiveViewerBoundaryState extends State { + TransformationController? _controller; + + double? _scale; + + @override + void initState() { + super.initState(); + + _controller = widget.controller ?? TransformationController(); + } + + @override + void dispose() { + _controller!.dispose(); + + super.dispose(); + } + + void _updateBoundaryDetection() { + final double scale = _controller!.value.row0[0]; + + if (_scale != scale) { + // the scale changed + _scale = scale; + widget.onScaleChanged?.call(scale); + } + + if (scale <= 1.01) { + // cant hit any boundaries when the child is not scaled + return; + } + + final double xOffset = _controller!.value.row0[3]; + final double boundaryWidth = widget.boundaryWidth; + final double boundaryEnd = boundaryWidth * scale; + final double xPos = boundaryEnd + xOffset; + + if (boundaryEnd.round() == xPos.round()) { + // left boundary hit + widget.onLeftBoundaryHit?.call(); + } else if (boundaryWidth.round() == xPos.round()) { + // right boundary hit + widget.onRightBoundaryHit?.call(); + } else { + widget.onNoBoundaryHit?.call(); + } + } + + @override + Widget build(BuildContext context) { + return InteractiveViewer( + maxScale: widget.maxScale!, + minScale: widget.minScale!, + transformationController: _controller, + onInteractionEnd: (_) => _updateBoundaryDetection(), + child: widget.child, + ); + } +} diff --git a/lib/plugin/pl_gallery/interactiveviewer_gallery.dart b/lib/plugin/pl_gallery/interactiveviewer_gallery.dart new file mode 100644 index 00000000..03ff4642 --- /dev/null +++ b/lib/plugin/pl_gallery/interactiveviewer_gallery.dart @@ -0,0 +1,399 @@ +library interactiveviewer_gallery; + +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:pilipala/utils/download.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:status_bar_control/status_bar_control.dart'; +import 'custom_dismissible.dart'; +import 'interactive_viewer_boundary.dart'; + +/// Builds a carousel controlled by a [PageView] for the tweet media sources. +/// +/// Used for showing a full screen view of the [TweetMedia] sources. +/// +/// The sources can be panned and zoomed interactively using an +/// [InteractiveViewer]. +/// An [InteractiveViewerBoundary] is used to detect when the boundary of the +/// source is hit after zooming in to disable or enable the swiping gesture of +/// the [PageView]. +/// +typedef IndexedFocusedWidgetBuilder = Widget Function( + BuildContext context, int index, bool isFocus, bool enablePageView); + +typedef IndexedTagStringBuilder = String Function(int index); + +class InteractiveviewerGallery extends StatefulWidget { + const InteractiveviewerGallery({ + required this.sources, + required this.initIndex, + required this.itemBuilder, + this.maxScale = 4.5, + this.minScale = 1.0, + this.onPageChanged, + this.onDismissed, + Key? key, + }) : super(key: key); + + /// The sources to show. + final List sources; + + /// The index of the first source in [sources] to show. + final int initIndex; + + /// The item content + final IndexedFocusedWidgetBuilder itemBuilder; + + final double maxScale; + + final double minScale; + + final ValueChanged? onPageChanged; + + final ValueChanged? onDismissed; + + @override + State createState() => + _InteractiveviewerGalleryState(); +} + +class _InteractiveviewerGalleryState extends State + with SingleTickerProviderStateMixin { + PageController? _pageController; + TransformationController? _transformationController; + + /// The controller to animate the transformation value of the + /// [InteractiveViewer] when it should reset. + late AnimationController _animationController; + Animation? _animation; + + /// `true` when an source is zoomed in and not at the at a horizontal boundary + /// to disable the [PageView]. + bool _enablePageView = true; + + /// `true` when an source is zoomed in to disable the [CustomDismissible]. + bool _enableDismiss = true; + + late Offset _doubleTapLocalPosition; + + int? currentIndex; + + @override + void initState() { + super.initState(); + + _pageController = PageController(initialPage: widget.initIndex); + + _transformationController = TransformationController(); + + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 300), + ) + ..addListener(() { + _transformationController!.value = + _animation?.value ?? Matrix4.identity(); + }) + ..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed && !_enableDismiss) { + setState(() { + _enableDismiss = true; + }); + } + }); + + currentIndex = widget.initIndex; + setStatusBar(); + } + + setStatusBar() async { + if (Platform.isIOS || Platform.isAndroid) { + await StatusBarControl.setHidden(true, + animation: StatusBarAnimation.FADE); + } + } + + @override + void dispose() { + _pageController!.dispose(); + _animationController.dispose(); + try { + StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); + } catch (_) {} + super.dispose(); + } + + /// When the source gets scaled up, the swipe up / down to dismiss gets + /// disabled. + /// + /// When the scale resets, the dismiss and the page view swiping gets enabled. + void _onScaleChanged(double scale) { + final bool initialScale = scale <= widget.minScale; + + if (initialScale) { + if (!_enableDismiss) { + setState(() { + _enableDismiss = true; + }); + } + + if (!_enablePageView) { + setState(() { + _enablePageView = true; + }); + } + } else { + if (_enableDismiss) { + setState(() { + _enableDismiss = false; + }); + } + + if (_enablePageView) { + setState(() { + _enablePageView = false; + }); + } + } + } + + /// When the left boundary has been hit after scaling up the source, the page + /// view swiping gets enabled if it has a page to swipe to. + void _onLeftBoundaryHit() { + if (!_enablePageView && _pageController!.page!.floor() > 0) { + setState(() { + _enablePageView = true; + }); + } + } + + /// When the right boundary has been hit after scaling up the source, the page + /// view swiping gets enabled if it has a page to swipe to. + void _onRightBoundaryHit() { + if (!_enablePageView && + _pageController!.page!.floor() < widget.sources.length - 1) { + setState(() { + _enablePageView = true; + }); + } + } + + /// When the source has been scaled up and no horizontal boundary has been hit, + /// the page view swiping gets disabled. + void _onNoBoundaryHit() { + if (_enablePageView) { + setState(() { + _enablePageView = false; + }); + } + } + + /// When the page view changed its page, the source will animate back into the + /// original scale if it was scaled up. + /// + /// Additionally the swipe up / down to dismiss gets enabled. + void _onPageChanged(int page) { + setState(() { + currentIndex = page; + }); + widget.onPageChanged?.call(page); + if (_transformationController!.value != Matrix4.identity()) { + // animate the reset for the transformation of the interactive viewer + + _animation = Matrix4Tween( + begin: _transformationController!.value, + end: Matrix4.identity(), + ).animate( + CurveTween(curve: Curves.easeOut).animate(_animationController), + ); + + _animationController.forward(from: 0); + } + } + + @override + Widget build(BuildContext context) { + return InteractiveViewerBoundary( + controller: _transformationController, + boundaryWidth: MediaQuery.of(context).size.width, + onScaleChanged: _onScaleChanged, + onLeftBoundaryHit: _onLeftBoundaryHit, + onRightBoundaryHit: _onRightBoundaryHit, + onNoBoundaryHit: _onNoBoundaryHit, + maxScale: widget.maxScale, + minScale: widget.minScale, + child: Stack(children: [ + CustomDismissible( + onDismissed: () { + Navigator.of(context).pop(); + widget.onDismissed?.call(_pageController!.page!.floor()); + }, + enabled: _enableDismiss, + child: PageView.builder( + onPageChanged: _onPageChanged, + controller: _pageController, + physics: + _enablePageView ? null : const NeverScrollableScrollPhysics(), + itemCount: widget.sources.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onDoubleTapDown: (TapDownDetails details) { + _doubleTapLocalPosition = details.localPosition; + }, + onDoubleTap: onDoubleTap, + child: widget.itemBuilder( + context, + index, + index == currentIndex, + _enablePageView, + ), + ); + }, + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + padding: EdgeInsets.fromLTRB( + 12, 8, 20, MediaQuery.of(context).padding.bottom + 8), + decoration: _enablePageView + ? BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black.withOpacity(0.3) + ], + ), + ) + : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.close, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + widget.onDismissed?.call(_pageController!.page!.floor()); + }, + ), + widget.sources.length > 1 + ? Text( + "${currentIndex! + 1}/${widget.sources.length}", + style: const TextStyle(color: Colors.white), + ) + : const SizedBox(), + PopupMenuButton( + itemBuilder: (context) { + return [ + PopupMenuItem( + value: 0, + onTap: () => onShareImg(widget.sources[currentIndex!]), + child: const Text("分享图片"), + ), + PopupMenuItem( + value: 1, + onTap: () { + Clipboard.setData(ClipboardData( + text: + widget.sources[currentIndex!].toString())) + .then((value) { + SmartDialog.showToast('已复制到粘贴板'); + }).catchError((err) { + SmartDialog.showNotify( + msg: err.toString(), + notifyType: NotifyType.error, + ); + }); + }, + child: const Text("复制图片"), + ), + PopupMenuItem( + value: 2, + onTap: () { + DownloadUtils.downloadImg( + widget.sources[currentIndex!]); + }, + child: const Text("保存图片"), + ), + ]; + }, + child: const Icon(Icons.more_horiz, color: Colors.white), + ), + ], + ), + ), + ), + ]), + ); + } + + // 图片分享 + void onShareImg(String imgUrl) async { + SmartDialog.showLoading(); + var response = await Dio() + .get(imgUrl, options: Options(responseType: ResponseType.bytes)); + final temp = await getTemporaryDirectory(); + SmartDialog.dismiss(); + String imgName = + "plpl_pic_${DateTime.now().toString().split('-').join()}.jpg"; + var path = '${temp.path}/$imgName'; + File(path).writeAsBytesSync(response.data); + Share.shareXFiles([XFile(path)], subject: imgUrl); + } + + onDoubleTap() { + Matrix4 matrix = _transformationController!.value.clone(); + double currentScale = matrix.row0.x; + + double targetScale = widget.minScale; + + if (currentScale <= widget.minScale) { + targetScale = widget.maxScale * 0.7; + } + + double offSetX = targetScale == 1.0 + ? 0.0 + : -_doubleTapLocalPosition.dx * (targetScale - 1); + double offSetY = targetScale == 1.0 + ? 0.0 + : -_doubleTapLocalPosition.dy * (targetScale - 1); + + matrix = Matrix4.fromList([ + targetScale, + matrix.row1.x, + matrix.row2.x, + matrix.row3.x, + matrix.row0.y, + targetScale, + matrix.row2.y, + matrix.row3.y, + matrix.row0.z, + matrix.row1.z, + targetScale, + matrix.row3.z, + offSetX, + offSetY, + matrix.row2.w, + matrix.row3.w + ]); + + _animation = Matrix4Tween( + begin: _transformationController!.value, + end: matrix, + ).animate( + CurveTween(curve: Curves.easeOut).animate(_animationController), + ); + _animationController + .forward(from: 0) + .whenComplete(() => _onScaleChanged(targetScale)); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index a6b48f0d..7840c126 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -70,14 +70,6 @@ class Routes { CustomGetPage(name: '/hot', page: () => const HotPage()), // 视频详情 CustomGetPage(name: '/video', page: () => const VideoDetailPage()), - // 图片预览 - // GetPage( - // name: '/preview', - // page: () => const ImagePreview(), - // transition: Transition.fade, - // transitionDuration: const Duration(milliseconds: 300), - // showCupertinoParallax: false, - // ), // CustomGetPage(name: '/webview', page: () => const WebviewPage()), // 设置 diff --git a/pubspec.lock b/pubspec.lock index c73ce395..a46127f9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -441,22 +441,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.0.1" - extended_image: - dependency: "direct main" - description: - name: extended_image - sha256: d7f091d068fcac7246c4b22a84b8dac59a62e04d29a5c172710c696e67a22f94 - url: "https://pub.flutter-io.cn" - source: hosted - version: "8.2.0" - extended_image_library: - dependency: transitive - description: - name: extended_image_library - sha256: c9caee8fe9b6547bd41c960c4f2d1ef8e34321804de6a1777f1d614a24247ad6 - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.0.4" extended_list: dependency: transitive description: @@ -726,14 +710,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" - http_client_helper: - dependency: transitive - description: - name: http_client_helper - sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.0" http_multi_server: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 09169473..b5d45714 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,7 +49,6 @@ dependencies: # 图片 cached_network_image: ^3.3.0 - extended_image: ^8.2.0 saver_gallery: ^3.0.1 # 存储 From b489a45481915b37452b2d9f7bce7e4f5d0f44ee Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 14 Jul 2024 00:41:59 +0800 Subject: [PATCH 035/152] =?UTF-8?q?fix:=20=E6=94=B6=E8=97=8F=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E3=80=81=E6=94=B6=E5=88=B0=E7=9A=84=E8=B5=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/media/view.dart | 8 +++++--- lib/pages/message/like/view.dart | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index 3694fa68..420d7c44 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -245,9 +245,11 @@ class FavFolderItem extends StatelessWidget { return Container( margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14), child: GestureDetector( - onTap: () => Get.toNamed('/favDetail', - arguments: item, - parameters: {'mediaId': item!.id.toString(), 'heroTag': heroTag}), + onTap: () => Get.toNamed('/favDetail', arguments: item, parameters: { + 'mediaId': item!.id.toString(), + 'heroTag': heroTag, + 'isOwner': '1', + }), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/pages/message/like/view.dart b/lib/pages/message/like/view.dart index e677fb25..80b20117 100644 --- a/lib/pages/message/like/view.dart +++ b/lib/pages/message/like/view.dart @@ -209,7 +209,7 @@ class LikeItem extends StatelessWidget { style: TextStyle(color: outline), ), TextSpan( - text: '赞了我的评论', + text: '赞了我的${item.item!.business}', style: TextStyle(color: outline), ), ])), From 04bdf5c2934316b8ff2dce20a0a418700e9ba7c0 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 14 Jul 2024 21:47:37 +0800 Subject: [PATCH 036/152] =?UTF-8?q?feat:=20=E8=BD=AE=E6=92=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8Bup=E5=8A=A8=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/dynamics/controller.dart | 10 +- lib/pages/dynamics/up_dynamic/controller.dart | 46 +++++ lib/pages/dynamics/up_dynamic/index.dart | 4 + .../dynamics/up_dynamic/route_panel.dart | 151 +++++++++++++++ lib/pages/dynamics/up_dynamic/view.dart | 178 ++++++++++++++++++ lib/pages/dynamics/view.dart | 19 +- lib/pages/dynamics/widgets/up_panel.dart | 49 ++--- lib/plugin/pl_popup/index.dart | 43 +++++ 8 files changed, 472 insertions(+), 28 deletions(-) create mode 100644 lib/pages/dynamics/up_dynamic/controller.dart create mode 100644 lib/pages/dynamics/up_dynamic/index.dart create mode 100644 lib/pages/dynamics/up_dynamic/route_panel.dart create mode 100644 lib/pages/dynamics/up_dynamic/view.dart create mode 100644 lib/plugin/pl_popup/index.dart diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart index 9bed3685..1cf9db9f 100644 --- a/lib/pages/dynamics/controller.dart +++ b/lib/pages/dynamics/controller.dart @@ -6,9 +6,7 @@ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/http/dynamics.dart'; import 'package:pilipala/http/search.dart'; -import 'package:pilipala/models/bangumi/info.dart'; import 'package:pilipala/models/common/dynamics_type.dart'; -import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/dynamics/result.dart'; import 'package:pilipala/models/dynamics/up.dart'; import 'package:pilipala/models/live/item.dart'; @@ -16,7 +14,6 @@ import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/utils/storage.dart'; -import 'package:pilipala/utils/utils.dart'; class DynamicsController extends GetxController { int page = 1; @@ -282,4 +279,11 @@ class DynamicsController extends GetxController { dynamicsList.value = []; queryFollowDynamic(); } + + // 点击up主 + void onTapUp(data) { + mid.value = data.mid; + upInfo.value = data; + onSelectUp(data.mid); + } } diff --git a/lib/pages/dynamics/up_dynamic/controller.dart b/lib/pages/dynamics/up_dynamic/controller.dart new file mode 100644 index 00000000..a30a0148 --- /dev/null +++ b/lib/pages/dynamics/up_dynamic/controller.dart @@ -0,0 +1,46 @@ +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/http/dynamics.dart'; +import 'package:pilipala/models/dynamics/result.dart'; +import 'package:pilipala/models/dynamics/up.dart'; + +class UpDynamicsController extends GetxController { + UpDynamicsController(this.upInfo); + UpItem upInfo; + RxList dynamicsList = [].obs; + RxBool isLoadingDynamic = false.obs; + String? offset = ''; + int page = 1; + + Future queryFollowDynamic({type = 'init'}) async { + if (type == 'init') { + dynamicsList.clear(); + } + // 下拉刷新数据渲染时会触发onLoad + if (type == 'onLoad' && page == 1) { + return; + } + isLoadingDynamic.value = true; + var res = await DynamicsHttp.followDynamic( + page: type == 'init' ? 1 : page, + type: 'all', + offset: offset, + mid: upInfo.mid, + ); + isLoadingDynamic.value = false; + if (res['status']) { + if (type == 'onLoad' && res['data'].items.isEmpty) { + SmartDialog.showToast('没有更多了'); + return; + } + if (type == 'init') { + dynamicsList.value = res['data'].items; + } else { + dynamicsList.addAll(res['data'].items); + } + offset = res['data'].offset; + page++; + } + return res; + } +} diff --git a/lib/pages/dynamics/up_dynamic/index.dart b/lib/pages/dynamics/up_dynamic/index.dart new file mode 100644 index 00000000..2de4c7e4 --- /dev/null +++ b/lib/pages/dynamics/up_dynamic/index.dart @@ -0,0 +1,4 @@ +library up_dynamics; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/dynamics/up_dynamic/route_panel.dart b/lib/pages/dynamics/up_dynamic/route_panel.dart new file mode 100644 index 00000000..40c725a8 --- /dev/null +++ b/lib/pages/dynamics/up_dynamic/route_panel.dart @@ -0,0 +1,151 @@ +import 'package:easy_debounce/easy_throttle.dart'; +import 'package:get/get.dart'; +import 'package:flutter/material.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/models/dynamics/up.dart'; +import 'package:pilipala/utils/feed_back.dart'; +import '../controller.dart'; +import 'index.dart'; + +class OverlayPanel extends StatefulWidget { + const OverlayPanel({super.key, required this.ctr, required this.upInfo}); + + final DynamicsController ctr; + final UpItem upInfo; + + @override + State createState() => _OverlayPanelState(); +} + +class _OverlayPanelState extends State + with SingleTickerProviderStateMixin { + static const itemPadding = EdgeInsets.symmetric(horizontal: 6, vertical: 0); + final PageController pageController = PageController(); + late double contentWidth = 50; + late List upList; + late RxInt currentMid = (-1).obs; + TabController? _tabController; + + @override + void initState() { + super.initState(); + upList = widget.ctr.upData.value.upList! + .map((element) => element) + .toList(); + upList.removeAt(0); + _tabController = TabController(length: upList.length, vsync: this); + + currentMid.value = widget.upInfo.mid!; + + pageController.addListener(() { + int index = pageController.page!.round(); + int mid = upList[index].mid!; + if (mid != currentMid.value) { + currentMid.value = mid; + _tabController?.animateTo(index, + duration: Duration.zero, curve: Curves.linear); + onClickUp(upList[index], index, type: 'pageChange'); + } + }); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + int index = + upList.indexWhere((element) => element.mid == widget.upInfo.mid); + pageController.jumpToPage(index); + onClickUp(widget.upInfo, index); + _tabController?.animateTo(index, + duration: Duration.zero, curve: Curves.linear); + onClickUp(upList[index], index, type: 'pageChange'); + }); + } + + void onClickUp(data, i, {type = 'click'}) { + if (type == 'click') { + pageController.jumpToPage(i); + } + } + + @override + Widget build(BuildContext context) { + return Container( + width: Get.width, + height: Get.height, + clipBehavior: Clip.antiAlias, + margin: EdgeInsets.fromLTRB( + 0, + MediaQuery.of(context).padding.top + 4, + 0, + MediaQuery.of(context).padding.bottom + 4, + ), + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + children: [ + SizedBox( + height: 50, + child: TabBar( + controller: _tabController, + dividerColor: Colors.transparent, + automaticIndicatorColorAdjustment: false, + tabAlignment: TabAlignment.start, + padding: const EdgeInsets.only(left: 12, right: 12), + indicatorPadding: EdgeInsets.zero, + indicatorSize: TabBarIndicatorSize.label, + indicator: const BoxDecoration(), + labelPadding: itemPadding, + indicatorWeight: 1, + isScrollable: true, + tabs: upList.map((e) => Tab(child: upItemBuild(e))).toList(), + onTap: (index) { + feedBack(); + EasyThrottle.throttle( + 'follow', const Duration(milliseconds: 200), () { + onClickUp(upList[index], index); + }); + }, + ), + ), + Expanded( + child: PageView.builder( + itemCount: upList.length, + controller: pageController, + itemBuilder: (BuildContext context, int index) { + return Container( + clipBehavior: Clip.antiAlias, + margin: const EdgeInsets.fromLTRB(10, 12, 10, 0), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(20), + ), + child: UpDyanmicsPage(upInfo: upList[index], ctr: widget.ctr), + ); + }, + ), + ), + ], + ), + ); + } + + Widget upItemBuild(data) { + return Obx( + () => AnimatedOpacity( + opacity: currentMid == data.mid ? 1 : 0.3, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + child: AnimatedScale( + duration: const Duration(milliseconds: 200), + scale: currentMid == data.mid ? 1 : 0.9, + child: NetworkImgLayer( + width: contentWidth, + height: contentWidth, + src: data.face, + type: 'avatar', + ), + ), + ), + ); + } +} diff --git a/lib/pages/dynamics/up_dynamic/view.dart b/lib/pages/dynamics/up_dynamic/view.dart new file mode 100644 index 00000000..4b3fb0c7 --- /dev/null +++ b/lib/pages/dynamics/up_dynamic/view.dart @@ -0,0 +1,178 @@ +import 'package:easy_debounce/easy_throttle.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/skeleton/dynamic_card.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; +import 'package:pilipala/common/widgets/no_data.dart'; +import 'package:pilipala/models/dynamics/result.dart'; +import 'package:pilipala/models/dynamics/up.dart'; +import 'package:pilipala/pages/dynamics/up_dynamic/index.dart'; + +import '../index.dart'; +import '../widgets/dynamic_panel.dart'; + +class UpDyanmicsPage extends StatefulWidget { + final UpItem upInfo; + final DynamicsController ctr; + + const UpDyanmicsPage({ + required this.upInfo, + required this.ctr, + Key? key, + }) : super(key: key); + + @override + State createState() => _UpDyanmicsPageState(); +} + +class _UpDyanmicsPageState extends State + with AutomaticKeepAliveClientMixin { + late UpDynamicsController _upDynamicsController; + final ScrollController scrollController = ScrollController(); + late Future _futureBuilderFuture; + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + _upDynamicsController = Get.put(UpDynamicsController(widget.upInfo), + tag: widget.upInfo.mid.toString()); + _futureBuilderFuture = _upDynamicsController.queryFollowDynamic(); + + scrollController.addListener( + () async { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { + EasyThrottle.throttle( + 'queryFollowDynamic', const Duration(seconds: 1), () { + _upDynamicsController.queryFollowDynamic(type: 'onLoad'); + }); + } + }, + ); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return CustomScrollView( + controller: scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPersistentHeader( + pinned: true, + floating: true, + delegate: _MySliverPersistentHeaderDelegate( + child: Container( + height: 50, + padding: const EdgeInsets.fromLTRB(20, 4, 4, 4), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + border: Border( + bottom: BorderSide( + color: Theme.of(context).colorScheme.onSurface, + width: 0.1, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.upInfo.uname!, + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(fontWeight: FontWeight.bold), + ), + IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Icons.close), + ) + ], + ), + ), + ), + ), + FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SliverToBoxAdapter(child: SizedBox()); + } + Map? data = snapshot.data; + if (data != null && data['status']) { + List list = + _upDynamicsController.dynamicsList; + return Obx( + () { + if (list.isEmpty) { + if (_upDynamicsController.isLoadingDynamic.value) { + return skeleton(); + } else { + return const NoData(); + } + } else { + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return DynamicPanel(item: list[index]); + }, + childCount: list.length, + ), + ); + } + }, + ); + } else { + return HttpError( + errMsg: data?['msg'] ?? '请求异常', + btnText: data?['code'] == -101 ? '去登录' : null, + fn: () {}, + ); + } + } else { + // 骨架屏 + return skeleton(); + } + }, + ), + ], + ); + } + + Widget skeleton() { + return SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return const DynamicCardSkeleton(); + }, childCount: 5), + ); + } +} + +class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { + _MySliverPersistentHeaderDelegate({required this.child}); + final double _minExtent = 50; + final double _maxExtent = 50; + final Widget child; + + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + return child; + } + + @override + double get maxExtent => _maxExtent; + + @override + double get minExtent => _minExtent; + + @override + bool shouldRebuild(covariant _MySliverPersistentHeaderDelegate oldDelegate) { + return true; + } +} diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 0fc16dcf..38411613 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -3,13 +3,13 @@ import 'dart:async'; import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/skeleton/dynamic_card.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/no_data.dart'; import 'package:pilipala/models/dynamics/result.dart'; +import 'package:pilipala/plugin/pl_popup/index.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/main_stream.dart'; import 'package:pilipala/utils/route_push.dart'; @@ -18,6 +18,7 @@ import 'package:pilipala/utils/storage.dart'; import '../mine/controller.dart'; import 'controller.dart'; import 'widgets/dynamic_panel.dart'; +import 'up_dynamic/route_panel.dart'; import 'widgets/up_panel.dart'; class DynamicsPage extends StatefulWidget { @@ -202,7 +203,21 @@ class _DynamicsPageState extends State } Map data = snapshot.data; if (data['status']) { - return Obx(() => UpPanel(_dynamicsController.upData.value)); + return Obx( + () => UpPanel( + upData: _dynamicsController.upData.value, + onClickUpCb: (data) { + // _dynamicsController.onTapUp(data); + Navigator.push( + context, + PlPopupRoute( + child: OverlayPanel( + ctr: _dynamicsController, upInfo: data), + ), + ); + }, + ), + ); } else { return const SliverToBoxAdapter( child: SizedBox(height: 80), diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index f8c973a0..3a0edcf6 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -4,13 +4,18 @@ import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/dynamics/up.dart'; import 'package:pilipala/models/live/item.dart'; -import 'package:pilipala/pages/dynamics/controller.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/utils.dart'; class UpPanel extends StatefulWidget { final FollowUpModel upData; - const UpPanel(this.upData, {Key? key}) : super(key: key); + final Function? onClickUpCb; + + const UpPanel({ + super.key, + required this.upData, + this.onClickUpCb, + }); @override State createState() => _UpPanelState(); @@ -33,27 +38,25 @@ class _UpPanelState extends State { void onClickUp(data, i) { currentMid = data.mid; - Get.find().mid.value = data.mid; - Get.find().upInfo.value = data; - Get.find().onSelectUp(data.mid); - int liveLen = liveList.length; - int upLen = upList.length; - double itemWidth = contentWidth + itemPadding.horizontal; - double screenWidth = MediaQuery.sizeOf(context).width; - double moveDistance = 0.0; - if (itemWidth * (upList.length + liveList.length) <= screenWidth) { - } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) { - moveDistance = (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2; - } else { - moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth; - } - data.hasUpdate = false; - scrollController.animateTo( - moveDistance, - duration: const Duration(milliseconds: 200), - curve: Curves.linear, - ); - setState(() {}); + widget.onClickUpCb?.call(data); + // int liveLen = liveList.length; + // int upLen = upList.length; + // double itemWidth = contentWidth + itemPadding.horizontal; + // double screenWidth = MediaQuery.sizeOf(context).width; + // double moveDistance = 0.0; + // if (itemWidth * (upList.length + liveList.length) <= screenWidth) { + // } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) { + // moveDistance = (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2; + // } else { + // moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth; + // } + // data.hasUpdate = false; + // scrollController.animateTo( + // moveDistance, + // duration: const Duration(milliseconds: 200), + // curve: Curves.linear, + // ); + // setState(() {}); } @override diff --git a/lib/plugin/pl_popup/index.dart b/lib/plugin/pl_popup/index.dart new file mode 100644 index 00000000..678eb049 --- /dev/null +++ b/lib/plugin/pl_popup/index.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class PlPopupRoute extends PopupRoute { + PlPopupRoute({ + this.backgroudColor, + this.alignment = Alignment.center, + required this.child, + this.onClick, + }); + + /// backgroudColor + final Color? backgroudColor; + + /// child'alignment, default value: [Alignment.center] + final Alignment alignment; + + /// child + final Widget child; + + /// backgroudView action + final Function? onClick; + + @override + Duration get transitionDuration => const Duration(milliseconds: 300); + + @override + bool get barrierDismissible => false; + + @override + Color get barrierColor => Colors.black54; + + @override + String? get barrierLabel => null; + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return child; + } +} From 9f676f8ef9ca13e6f6d93f01e9b2554e6ef3657f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 17 Jul 2024 23:37:55 +0800 Subject: [PATCH 037/152] =?UTF-8?q?fix:=20=E9=A6=96=E9=A1=B5=E6=8E=A8?= =?UTF-8?q?=E8=8D=90=E6=95=B0=E6=8D=AE=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/badge.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common/widgets/badge.dart b/lib/common/widgets/badge.dart index a8f2fc67..1e518f39 100644 --- a/lib/common/widgets/badge.dart +++ b/lib/common/widgets/badge.dart @@ -66,7 +66,7 @@ class PBadge extends StatelessWidget { border: Border.all(color: borderColor), ), child: Text( - text!, + text ?? '', style: TextStyle(fontSize: fs ?? fontSize, color: color), ), ); From 9c00d3c4d36c6ccd07d084095dc44eab4de2057d Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 18 Jul 2024 00:52:17 +0800 Subject: [PATCH 038/152] =?UTF-8?q?feat:=20up=E6=8A=95=E7=A8=BF=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=85=85=E7=94=B5=E4=B8=93=E5=B1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/member.dart | 7 ++++ lib/pages/member_archive/controller.dart | 9 +++-- lib/pages/member_archive/view.dart | 46 +++++++++++++++++++----- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/lib/http/member.dart b/lib/http/member.dart index 20a2c728..0fb010b0 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -96,7 +96,14 @@ class MemberHttp { 'dm_img_str': dmImgStr.substring(0, dmImgStr.length - 2), 'dm_cover_img_str': dmCoverImgStr.substring(0, dmCoverImgStr.length - 2), 'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}', + ...order == 'charge' + ? { + 'order': 'pubdate', + 'special_type': 'charging', + } + : {} }); + var res = await Request().get( Api.memberArchive, data: params, diff --git a/lib/pages/member_archive/controller.dart b/lib/pages/member_archive/controller.dart index 4c41de4c..667d16c5 100644 --- a/lib/pages/member_archive/controller.dart +++ b/lib/pages/member_archive/controller.dart @@ -9,12 +9,14 @@ class MemberArchiveController extends GetxController { int pn = 1; int count = 0; RxMap currentOrder = {}.obs; - List> orderList = [ + RxList> orderList = [ {'type': 'pubdate', 'label': '最新发布'}, {'type': 'click', 'label': '最多播放'}, {'type': 'stow', 'label': '最多收藏'}, - ]; + {'type': 'charge', 'label': '充电专属'}, + ].obs; RxList archivesList = [].obs; + RxBool isLoading = false.obs; @override void onInit() { @@ -27,6 +29,8 @@ class MemberArchiveController extends GetxController { Future getMemberArchive(type) async { if (type == 'init') { pn = 1; + archivesList.clear(); + isLoading.value = true; } var res = await MemberHttp.memberArchive( mid: mid, @@ -43,6 +47,7 @@ class MemberArchiveController extends GetxController { count = res['data'].page['count']; pn += 1; } + isLoading.value = false; return res; } diff --git a/lib/pages/member_archive/view.dart b/lib/pages/member_archive/view.dart index f38ca0cb..898aa915 100644 --- a/lib/pages/member_archive/view.dart +++ b/lib/pages/member_archive/view.dart @@ -1,6 +1,8 @@ import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:pilipala/common/skeleton/video_card_h.dart'; +import 'package:pilipala/common/widgets/no_data.dart'; import 'package:pilipala/common/widgets/video_card_h.dart'; import 'package:pilipala/utils/utils.dart'; import '../../common/widgets/http_error.dart'; @@ -47,14 +49,29 @@ class _MemberArchivePageState extends State { appBar: AppBar( titleSpacing: 0, centerTitle: false, - title: Text('他的投稿', style: Theme.of(context).textTheme.titleMedium), + title: Obx( + () => Text( + '他的投稿 - ${_memberArchivesController.currentOrder['label']}', + style: Theme.of(context).textTheme.titleMedium), + ), actions: [ - Obx( - () => TextButton.icon( - icon: const Icon(Icons.sort, size: 20), - onPressed: _memberArchivesController.toggleSort, - label: Text(_memberArchivesController.currentOrder['label']!), - ), + // Obx( + PopupMenuButton( + icon: const Icon(Icons.more_vert), + onSelected: (value) { + // 这里处理选择逻辑 + _memberArchivesController.currentOrder.value = value; + _memberArchivesController.getMemberArchive('init'); + }, + itemBuilder: (BuildContext context) => + _memberArchivesController.orderList.map( + (e) { + return PopupMenuItem( + value: e, + child: Text(e['label']!), + ); + }, + ).toList(), ), const SizedBox(width: 6), ], @@ -85,7 +102,14 @@ class _MemberArchivePageState extends State { childCount: list.length, ), ) - : const SliverToBoxAdapter(), + : _memberArchivesController.isLoading.value + ? SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return const VideoCardHSkeleton(); + }, childCount: 10), + ) + : const NoData(), ); } else { return HttpError( @@ -100,7 +124,11 @@ class _MemberArchivePageState extends State { ); } } else { - return const SliverToBoxAdapter(); + return SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return const VideoCardHSkeleton(); + }, childCount: 10), + ); } }, ), From 75859fc9cf687a3c86a1ba9060d741f8e56bed14 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 18 Jul 2024 01:11:26 +0800 Subject: [PATCH 039/152] =?UTF-8?q?mod:=20=E5=8F=96=E6=B6=88=E6=8A=95?= =?UTF-8?q?=E5=B8=81=E9=99=90=E5=88=B6&=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/detail/introduction/controller.dart | 58 +++++++++---------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 50aac4cd..cb9ad3c1 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -2,6 +2,7 @@ import 'dart:async'; 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'; @@ -196,45 +197,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(), + ), ); }); } From 38b6b6b4e09cf8e2db9b969f8e525b9abd43ae92 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 21 Jul 2024 18:50:36 +0800 Subject: [PATCH 040/152] =?UTF-8?q?fix:=20=E6=B6=88=E6=81=AFmid=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/whisper/view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 1814c274..1f69fa64 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -214,7 +214,7 @@ class SessionItem extends StatelessWidget { @override Widget build(BuildContext context) { - final String heroTag = Utils.makeHeroTag(sessionItem.accountInfo.mid); + final String heroTag = Utils.makeHeroTag(sessionItem.accountInfo?.mid ?? 0); final content = sessionItem.lastMsg.content; final msgStatus = sessionItem.lastMsg.msgStatus; From bc3ce33f78329d4cf0985d57ab8fa3a1a80b1753 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 21 Jul 2024 22:40:43 +0800 Subject: [PATCH 041/152] =?UTF-8?q?feat:=20=E8=AF=84=E8=AE=BA=E4=BA=8C?= =?UTF-8?q?=E6=A5=BC=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/dynamics/detail/view.dart | 6 +- lib/pages/video/detail/controller.dart | 4 +- lib/pages/video/detail/reply/view.dart | 10 +- .../detail/reply/widgets/reply_item.dart | 21 ++- .../video/detail/reply_reply/controller.dart | 13 +- lib/pages/video/detail/reply_reply/view.dart | 176 ++++++++++-------- 6 files changed, 139 insertions(+), 91 deletions(-) diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index e83b5547..6b3d969d 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -106,7 +106,7 @@ class _DynamicDetailPageState extends State } // 查看二级评论 - void replyReply(replyItem) { + void replyReply(replyItem, currentReply) { int oid = replyItem.oid; int rpid = replyItem.rpid!; Get.to( @@ -324,8 +324,8 @@ class _DynamicDetailPageState extends State replyItem: replyList[index], showReplyRow: true, replyLevel: '1', - replyReply: (replyItem) => - replyReply(replyItem), + replyReply: (replyItem, currentReply) => + replyReply(replyItem, currentReply), replyType: ReplyType.values[replyType], addReply: (replyItem) { replyList[index] diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 662f45f2..2c81140d 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -162,7 +162,7 @@ class VideoDetailController extends GetxController ); } - showReplyReplyPanel(oid, fRpid, firstFloor) { + showReplyReplyPanel(oid, fRpid, firstFloor, currentReply, loadMore) { replyReplyBottomSheetCtr = scaffoldKey.currentState?.showBottomSheet((BuildContext context) { return VideoReplyReplyPanel( @@ -175,6 +175,8 @@ class VideoDetailController extends GetxController replyType: ReplyType.video, source: 'videoDetail', sheetHeight: sheetHeight.value, + currentReply: currentReply, + loadMore: loadMore, ); }); replyReplyBottomSheetCtr?.closed.then((value) { diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 4fe69481..f91ef625 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -112,7 +112,7 @@ class _VideoReplyPanelState extends State } // 展示二级回复 - void replyReply(replyItem) { + void replyReply(replyItem, currentReply, loadMore) { final VideoDetailController videoDetailCtr = Get.find(tag: heroTag); if (replyItem != null) { @@ -120,7 +120,7 @@ class _VideoReplyPanelState extends State videoDetailCtr.fRpid = replyItem.rpid!; videoDetailCtr.firstFloor = replyItem; videoDetailCtr.showReplyReplyPanel( - replyItem.oid, replyItem.rpid!, replyItem); + replyItem.oid, replyItem.rpid!, replyItem, currentReply, loadMore); } } @@ -232,8 +232,10 @@ class _VideoReplyPanelState extends State .replyList[index], showReplyRow: true, replyLevel: replyLevel, - replyReply: (replyItem) => - replyReply(replyItem), + replyReply: (replyItem, currentReply, + loadMore) => + replyReply(replyItem, currentReply, + loadMore), replyType: ReplyType.video, ); } diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index c4c26e6b..bba5ddbb 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -49,7 +49,7 @@ class ReplyItem extends StatelessWidget { onTap: () { feedBack(); if (replyReply != null) { - replyReply!(replyItem); + replyReply!(replyItem, null, replyItem!.replies!.isNotEmpty); } }, onLongPress: () { @@ -362,9 +362,13 @@ class ReplyItemRow extends StatelessWidget { for (int i = 0; i < replies!.length; i++) ...[ InkWell( // 一楼点击评论展开评论详情 - // onTap: () { - // replyReply?.call(replyItem); - // }, + onTap: () { + replyReply?.call( + replyItem, + replies![i], + replyItem!.replies!.isNotEmpty, + ); + }, onLongPress: () { feedBack(); showModalBottomSheet( @@ -535,9 +539,12 @@ InlineSpan buildContent( spanChilds.add( TextSpan( text: str, - recognizer: TapGestureRecognizer() - ..onTap = () => - replyReply?.call(replyItem.root == 0 ? replyItem : fReplyItem), + // recognizer: TapGestureRecognizer() + // ..onTap = () => replyReply?.call( + // replyItem.root == 0 ? replyItem : fReplyItem, + // replyItem, + // fReplyItem!.replies!.isNotEmpty, + // ), ), ); } diff --git a/lib/pages/video/detail/reply_reply/controller.dart b/lib/pages/video/detail/reply_reply/controller.dart index e94aaea5..3d5644e8 100644 --- a/lib/pages/video/detail/reply_reply/controller.dart +++ b/lib/pages/video/detail/reply_reply/controller.dart @@ -26,7 +26,7 @@ class VideoReplyReplyController extends GetxController { currentPage = 0; } - Future queryReplyList({type = 'init'}) async { + Future queryReplyList({type = 'init', currentReply}) async { if (type == 'init') { currentPage = 0; } @@ -63,6 +63,17 @@ class VideoReplyReplyController extends GetxController { // res['data'].replies.addAll(replyList); } } + if (replyList.isNotEmpty && currentReply != null) { + int indexToRemove = + replyList.indexWhere((item) => currentReply.rpid == item.rpid); + // 如果找到了指定ID的项,则移除 + if (indexToRemove != -1) { + replyList.removeAt(indexToRemove); + } + if (currentPage == 1 && type == 'init') { + replyList.insert(0, currentReply); + } + } isLoadingMore = false; return res; } diff --git a/lib/pages/video/detail/reply_reply/view.dart b/lib/pages/video/detail/reply_reply/view.dart index 6dda9512..439f5d1d 100644 --- a/lib/pages/video/detail/reply_reply/view.dart +++ b/lib/pages/video/detail/reply_reply/view.dart @@ -20,6 +20,8 @@ class VideoReplyReplyPanel extends StatefulWidget { this.source, this.replyType, this.sheetHeight, + this.currentReply, + this.loadMore, super.key, }); final int? oid; @@ -29,6 +31,8 @@ class VideoReplyReplyPanel extends StatefulWidget { final String? source; final ReplyType? replyType; final double? sheetHeight; + final dynamic currentReply; + final bool? loadMore; @override State createState() => _VideoReplyReplyPanelState(); @@ -63,7 +67,9 @@ class _VideoReplyReplyPanelState extends State { }, ); - _futureBuilderFuture = _videoReplyReplyController.queryReplyList(); + _futureBuilderFuture = _videoReplyReplyController.queryReplyList( + currentReply: widget.currentReply, + ); } void replyReply(replyItem) {} @@ -107,7 +113,9 @@ class _VideoReplyReplyPanelState extends State { onRefresh: () async { setState(() {}); _videoReplyReplyController.currentPage = 0; - return await _videoReplyReplyController.queryReplyList(); + return await _videoReplyReplyController.queryReplyList( + currentReply: widget.currentReply, + ); }, child: CustomScrollView( controller: _videoReplyReplyController.scrollController, @@ -134,84 +142,102 @@ class _VideoReplyReplyPanelState extends State { ), ), ], - FutureBuilder( - future: _futureBuilderFuture, - builder: (BuildContext context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map? data = snapshot.data; - if (data != null && data['status']) { - // 请求成功 - return Obx( - () => SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - if (index == - _videoReplyReplyController - .replyList.length) { - return Container( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context) - .padding - .bottom), - height: MediaQuery.of(context) - .padding - .bottom + - 100, - child: Center( - child: Obx( - () => Text( + widget.loadMore != null && widget.loadMore! + ? FutureBuilder( + future: _futureBuilderFuture, + builder: (BuildContext context, snapshot) { + if (snapshot.connectionState == + ConnectionState.done) { + Map? data = snapshot.data; + if (data != null && data['status']) { + // 请求成功 + return Obx( + () => SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + if (index == _videoReplyReplyController - .noMore.value, - style: TextStyle( - fontSize: 12, - color: Theme.of(context) - .colorScheme - .outline, + .replyList.length) { + return Container( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context) + .padding + .bottom), + height: MediaQuery.of(context) + .padding + .bottom + + 100, + child: Center( + child: Obx( + () => Text( + _videoReplyReplyController + .noMore.value, + style: TextStyle( + fontSize: 12, + color: Theme.of(context) + .colorScheme + .outline, + ), + ), + ), ), - ), - ), - ), - ); - } else { - return ReplyItem( - replyItem: _videoReplyReplyController - .replyList[index], - replyLevel: '2', - showReplyRow: false, - addReply: (replyItem) { - _videoReplyReplyController.replyList - .add(replyItem); + ); + } else { + return ReplyItem( + replyItem: + _videoReplyReplyController + .replyList[index], + replyLevel: '2', + showReplyRow: false, + addReply: (replyItem) { + _videoReplyReplyController + .replyList + .add(replyItem); + }, + replyType: widget.replyType, + replyReply: (replyItem) => + replyReply(replyItem), + ); + } }, - replyType: widget.replyType, - replyReply: (replyItem) => - replyReply(replyItem), - ); - } - }, - childCount: _videoReplyReplyController - .replyList.length + - 1, + childCount: _videoReplyReplyController + .replyList.length + + 1, + ), + ), + ); + } else { + // 请求错误 + return HttpError( + errMsg: data?['msg'] ?? '请求错误', + fn: () => setState(() {}), + ); + } + } else { + // 骨架屏 + return SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return const VideoReplySkeleton(); + }, childCount: 8), + ); + } + }, + ) + : SliverToBoxAdapter( + child: SizedBox( + height: 200, + child: Center( + child: Text( + '还没有评论', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), ), ), - ); - } else { - // 请求错误 - return HttpError( - errMsg: data?['msg'] ?? '请求错误', - fn: () => setState(() {}), - ); - } - } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - return const VideoReplySkeleton(); - }, childCount: 8), - ); - } - }, - ) + ), + ) ], ), ), From bc36493e60eb254c9e19e76a07f8aec4e8e37259 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 23 Jul 2024 23:25:06 +0800 Subject: [PATCH 042/152] =?UTF-8?q?fix:=20=E9=A6=96=E9=A1=B5=E6=8E=A8?= =?UTF-8?q?=E8=8D=90=E6=95=B0=E6=8D=AE=E6=A0=BC=E5=BC=8F=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/video_card_v.dart | 5 +- lib/http/video.dart | 72 ++++++------ lib/models/home/rcmd/result.dart | 12 +- lib/models/model_owner.dart | 9 +- lib/models/model_owner.g.dart | 47 -------- lib/models/model_rec_video_item.dart | 50 +------- lib/models/model_rec_video_item.g.dart | 154 ------------------------- lib/models/search/hot.dart | 11 -- lib/models/search/hot.g.dart | 84 -------------- lib/models/user/stat.dart | 8 -- lib/models/user/stat.g.dart | 47 -------- lib/utils/storage.dart | 5 - 12 files changed, 45 insertions(+), 459 deletions(-) delete mode 100644 lib/models/model_owner.g.dart delete mode 100644 lib/models/model_rec_video_item.g.dart delete mode 100644 lib/models/search/hot.g.dart delete mode 100644 lib/models/user/stat.g.dart diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 14476cdf..d8e1bb2c 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -215,9 +215,8 @@ class VideoContent extends StatelessWidget { children: [ if (videoItem.goto == 'bangumi') _buildBadge(videoItem.bangumiBadge, 'line', 9), - if (videoItem.rcmdReason?.content != null && - videoItem.rcmdReason.content != '') - _buildBadge(videoItem.rcmdReason.content, 'color'), + if (videoItem.rcmdReason != null) + _buildBadge(videoItem.rcmdReason, 'color'), if (videoItem.goto == 'picture') _buildBadge('动态', 'line', 9), if (videoItem.isFollowed == 1) _buildBadge('已关注', 'color'), Expanded( diff --git a/lib/http/video.dart b/lib/http/video.dart index 834a0102..160f5db2 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -70,47 +70,43 @@ class VideoHttp { // 添加额外的loginState变量模拟未登录状态 static Future rcmdVideoListApp( {bool loginStatus = true, required int freshIdx}) async { - try { - var res = await Request().get( - Api.recommendListApp, - data: { - 'idx': freshIdx, - 'flush': '5', - 'column': '4', - 'device': 'pad', - 'device_type': 0, - 'device_name': 'vivo', - 'pull': freshIdx == 0 ? 'true' : 'false', - 'appkey': Constants.appKey, - 'access_key': loginStatus - ? (localCache.get(LocalCacheKey.accessKey, - defaultValue: {})['value'] ?? - '') - : '' - }, - ); - if (res.data['code'] == 0) { - List list = []; - List blackMidsList = - setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]); - for (var i in res.data['data']['items']) { - // 屏蔽推广和拉黑用户 - if (i['card_goto'] != 'ad_av' && - (!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) && - (i['args'] != null && - !blackMidsList.contains(i['args']['up_mid']))) { - RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i); - if (!RecommendFilter.filter(videoItem)) { - list.add(videoItem); - } + var res = await Request().get( + Api.recommendListApp, + data: { + 'idx': freshIdx, + 'flush': '5', + 'column': '4', + 'device': 'pad', + 'device_type': 0, + 'device_name': 'vivo', + 'pull': freshIdx == 0 ? 'true' : 'false', + 'appkey': Constants.appKey, + 'access_key': loginStatus + ? (localCache + .get(LocalCacheKey.accessKey, defaultValue: {})['value'] ?? + '') + : '' + }, + ); + if (res.data['code'] == 0) { + List list = []; + List blackMidsList = + setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]); + for (var i in res.data['data']['items']) { + // 屏蔽推广和拉黑用户 + if (i['card_goto'] != 'ad_av' && + (!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) && + (i['args'] != null && + !blackMidsList.contains(i['args']['up_mid']))) { + RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i); + if (!RecommendFilter.filter(videoItem)) { + list.add(videoItem); } } - return {'status': true, 'data': list}; - } else { - return {'status': false, 'data': [], 'msg': res.data['message']}; } - } catch (err) { - return {'status': false, 'data': [], 'msg': err.toString()}; + return {'status': true, 'data': list}; + } else { + return {'status': false, 'data': [], 'msg': res.data['message']}; } } diff --git a/lib/models/home/rcmd/result.dart b/lib/models/home/rcmd/result.dart index 0098fe95..98beda59 100644 --- a/lib/models/home/rcmd/result.dart +++ b/lib/models/home/rcmd/result.dart @@ -34,7 +34,7 @@ class RecVideoItemAppModel { String? title; int? isFollowed; RcmdOwner? owner; - RcmdReason? rcmdReason; + String? rcmdReason; String? goto; int? param; String? uri; @@ -64,17 +64,11 @@ class RecVideoItemAppModel { //duration = json['cover_right_text']; title = json['title']; owner = RcmdOwner.fromJson(json); - rcmdReason = json['rcmd_reason_style'] != null - ? RcmdReason.fromJson(json['rcmd_reason_style']) - : null; + rcmdReason = json['bottom_rcmd_reason'] ?? json['top_rcmd_reason']; // 由于app端api并不会直接返回与owner的关注状态 // 所以借用推荐原因是否为“已关注”、“新关注”等判别关注状态,从而与web端接口等效 RegExp regex = RegExp(r'已关注|新关注'); - isFollowed = rcmdReason != null && - rcmdReason!.content != null && - regex.hasMatch(rcmdReason!.content!) - ? 1 - : 0; + isFollowed = regex.hasMatch(rcmdReason ?? '') ? 1 : 0; // 如果是,就无需再显示推荐原因,交由view统一处理即可 if (isFollowed == 1) { rcmdReason = null; diff --git a/lib/models/model_owner.dart b/lib/models/model_owner.dart index 70396cbb..6ef425eb 100644 --- a/lib/models/model_owner.dart +++ b/lib/models/model_owner.dart @@ -1,19 +1,12 @@ -import 'package:hive/hive.dart'; - -part 'model_owner.g.dart'; - -@HiveType(typeId: 3) class Owner { Owner({ this.mid, this.name, this.face, }); - @HiveField(0) + int? mid; - @HiveField(1) String? name; - @HiveField(2) String? face; Owner.fromJson(Map json) { diff --git a/lib/models/model_owner.g.dart b/lib/models/model_owner.g.dart deleted file mode 100644 index de452713..00000000 --- a/lib/models/model_owner.g.dart +++ /dev/null @@ -1,47 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'model_owner.dart'; - -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class OwnerAdapter extends TypeAdapter { - @override - final int typeId = 3; - - @override - Owner read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return Owner( - mid: fields[0] as int?, - name: fields[1] as String?, - face: fields[2] as String?, - ); - } - - @override - void write(BinaryWriter writer, Owner obj) { - writer - ..writeByte(3) - ..writeByte(0) - ..write(obj.mid) - ..writeByte(1) - ..write(obj.name) - ..writeByte(2) - ..write(obj.face); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is OwnerAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} diff --git a/lib/models/model_rec_video_item.dart b/lib/models/model_rec_video_item.dart index 1503f192..b82510db 100644 --- a/lib/models/model_rec_video_item.dart +++ b/lib/models/model_rec_video_item.dart @@ -1,9 +1,5 @@ import './model_owner.dart'; -import 'package:hive/hive.dart'; -part 'model_rec_video_item.g.dart'; - -@HiveType(typeId: 0) class RecVideoItemModel { RecVideoItemModel({ this.id, @@ -21,32 +17,19 @@ class RecVideoItemModel { this.rcmdReason, }); - @HiveField(0) int? id = -1; - @HiveField(1) String? bvid = ''; - @HiveField(2) int? cid = -1; - @HiveField(3) String? goto = ''; - @HiveField(4) String? uri = ''; - @HiveField(5) String? pic = ''; - @HiveField(6) String? title = ''; - @HiveField(7) int? duration = -1; - @HiveField(8) int? pubdate = -1; - @HiveField(9) Owner? owner; - @HiveField(10) Stat? stat; - @HiveField(11) int? isFollowed; - @HiveField(12) - RcmdReason? rcmdReason; + String? rcmdReason; RecVideoItemModel.fromJson(Map json) { id = json["id"]; @@ -61,26 +44,20 @@ class RecVideoItemModel { owner = Owner.fromJson(json["owner"]); stat = Stat.fromJson(json["stat"]); isFollowed = json["is_followed"] ?? 0; - rcmdReason = json["rcmd_reason"] != null - ? RcmdReason.fromJson(json["rcmd_reason"]) - : RcmdReason(content: ''); + rcmdReason = json["rcmd_reason"]?['content']; } } -@HiveType(typeId: 1) class Stat { Stat({ this.view, this.like, this.danmu, }); - @HiveField(0) - int? view; - @HiveField(1) - int? like; - @HiveField(2) - int? danmu; + int? view; + int? like; + int? danmu; Stat.fromJson(Map json) { // 无需在model中转换以保留原始数据,在view层处理即可 view = json["view"]; @@ -88,20 +65,3 @@ class Stat { danmu = json['danmaku']; } } - -@HiveType(typeId: 2) -class RcmdReason { - RcmdReason({ - this.reasonType, - this.content, - }); - @HiveField(0) - int? reasonType; - @HiveField(1) - String? content = ''; - - RcmdReason.fromJson(Map json) { - reasonType = json["reason_type"]; - content = json["content"] ?? ''; - } -} diff --git a/lib/models/model_rec_video_item.g.dart b/lib/models/model_rec_video_item.g.dart deleted file mode 100644 index dc614354..00000000 --- a/lib/models/model_rec_video_item.g.dart +++ /dev/null @@ -1,154 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'model_rec_video_item.dart'; - -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class RecVideoItemModelAdapter extends TypeAdapter { - @override - final int typeId = 0; - - @override - RecVideoItemModel read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return RecVideoItemModel( - id: fields[0] as int?, - bvid: fields[1] as String?, - cid: fields[2] as int?, - goto: fields[3] as String?, - uri: fields[4] as String?, - pic: fields[5] as String?, - title: fields[6] as String?, - duration: fields[7] as int?, - pubdate: fields[8] as int?, - owner: fields[9] as Owner?, - stat: fields[10] as Stat?, - isFollowed: fields[11] as int?, - rcmdReason: fields[12] as RcmdReason?, - ); - } - - @override - void write(BinaryWriter writer, RecVideoItemModel obj) { - writer - ..writeByte(13) - ..writeByte(0) - ..write(obj.id) - ..writeByte(1) - ..write(obj.bvid) - ..writeByte(2) - ..write(obj.cid) - ..writeByte(3) - ..write(obj.goto) - ..writeByte(4) - ..write(obj.uri) - ..writeByte(5) - ..write(obj.pic) - ..writeByte(6) - ..write(obj.title) - ..writeByte(7) - ..write(obj.duration) - ..writeByte(8) - ..write(obj.pubdate) - ..writeByte(9) - ..write(obj.owner) - ..writeByte(10) - ..write(obj.stat) - ..writeByte(11) - ..write(obj.isFollowed) - ..writeByte(12) - ..write(obj.rcmdReason); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is RecVideoItemModelAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} - -class StatAdapter extends TypeAdapter { - @override - final int typeId = 1; - - @override - Stat read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return Stat( - view: fields[0] as int?, - like: fields[1] as int?, - danmu: fields[2] as int?, - ); - } - - @override - void write(BinaryWriter writer, Stat obj) { - writer - ..writeByte(3) - ..writeByte(0) - ..write(obj.view) - ..writeByte(1) - ..write(obj.like) - ..writeByte(2) - ..write(obj.danmu); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is StatAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} - -class RcmdReasonAdapter extends TypeAdapter { - @override - final int typeId = 2; - - @override - RcmdReason read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return RcmdReason( - reasonType: fields[0] as int?, - content: fields[1] as String?, - ); - } - - @override - void write(BinaryWriter writer, RcmdReason obj) { - writer - ..writeByte(2) - ..writeByte(0) - ..write(obj.reasonType) - ..writeByte(1) - ..write(obj.content); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is RcmdReasonAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} diff --git a/lib/models/search/hot.dart b/lib/models/search/hot.dart index ce09b2ea..59d7e749 100644 --- a/lib/models/search/hot.dart +++ b/lib/models/search/hot.dart @@ -1,14 +1,8 @@ -import 'package:hive/hive.dart'; - -part 'hot.g.dart'; - -@HiveType(typeId: 6) class HotSearchModel { HotSearchModel({ this.list, }); - @HiveField(0) List? list; HotSearchModel.fromJson(Map json) { @@ -18,7 +12,6 @@ class HotSearchModel { } } -@HiveType(typeId: 7) class HotSearchItem { HotSearchItem({ this.keyword, @@ -27,14 +20,10 @@ class HotSearchItem { this.icon, }); - @HiveField(0) String? keyword; - @HiveField(1) String? showName; // 4/5热 11话题 8普通 7直播 - @HiveField(2) int? wordType; - @HiveField(3) String? icon; HotSearchItem.fromJson(Map json) { diff --git a/lib/models/search/hot.g.dart b/lib/models/search/hot.g.dart deleted file mode 100644 index a06dd475..00000000 --- a/lib/models/search/hot.g.dart +++ /dev/null @@ -1,84 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'hot.dart'; - -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class HotSearchModelAdapter extends TypeAdapter { - @override - final int typeId = 6; - - @override - HotSearchModel read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return HotSearchModel( - list: (fields[0] as List?)?.cast(), - ); - } - - @override - void write(BinaryWriter writer, HotSearchModel obj) { - writer - ..writeByte(1) - ..writeByte(0) - ..write(obj.list); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is HotSearchModelAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} - -class HotSearchItemAdapter extends TypeAdapter { - @override - final int typeId = 7; - - @override - HotSearchItem read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return HotSearchItem( - keyword: fields[0] as String?, - showName: fields[1] as String?, - wordType: fields[2] as int?, - icon: fields[3] as String?, - ); - } - - @override - void write(BinaryWriter writer, HotSearchItem obj) { - writer - ..writeByte(4) - ..writeByte(0) - ..write(obj.keyword) - ..writeByte(1) - ..write(obj.showName) - ..writeByte(2) - ..write(obj.wordType) - ..writeByte(3) - ..write(obj.icon); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is HotSearchItemAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} diff --git a/lib/models/user/stat.dart b/lib/models/user/stat.dart index 3b09acb1..0b56a499 100644 --- a/lib/models/user/stat.dart +++ b/lib/models/user/stat.dart @@ -1,8 +1,3 @@ -import 'package:hive/hive.dart'; - -part 'stat.g.dart'; - -@HiveType(typeId: 1) class UserStat { UserStat({ this.following, @@ -10,11 +5,8 @@ class UserStat { this.dynamicCount, }); - @HiveField(0) int? following; - @HiveField(1) int? follower; - @HiveField(2) int? dynamicCount; UserStat.fromJson(Map json) { diff --git a/lib/models/user/stat.g.dart b/lib/models/user/stat.g.dart deleted file mode 100644 index 290fe026..00000000 --- a/lib/models/user/stat.g.dart +++ /dev/null @@ -1,47 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'stat.dart'; - -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class UserStatAdapter extends TypeAdapter { - @override - final int typeId = 1; - - @override - UserStat read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return UserStat( - following: fields[0] as int?, - follower: fields[1] as int?, - dynamicCount: fields[2] as int?, - ); - } - - @override - void write(BinaryWriter writer, UserStat obj) { - writer - ..writeByte(3) - ..writeByte(0) - ..write(obj.following) - ..writeByte(1) - ..write(obj.follower) - ..writeByte(2) - ..write(obj.dynamicCount); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is UserStatAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index dca5a158..a132c5da 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -1,8 +1,6 @@ import 'dart:io'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:pilipala/models/model_owner.dart'; -import 'package:pilipala/models/search/hot.dart'; import 'package:pilipala/models/user/info.dart'; import '../models/common/gesture_mode.dart'; import 'global_data.dart'; @@ -54,11 +52,8 @@ class GStrorage { } static void regAdapter() { - Hive.registerAdapter(OwnerAdapter()); Hive.registerAdapter(UserInfoDataAdapter()); Hive.registerAdapter(LevelInfoAdapter()); - Hive.registerAdapter(HotSearchModelAdapter()); - Hive.registerAdapter(HotSearchItemAdapter()); } static Future close() async { From 5b78810ba43cd795b2bff953b08f41e0b4e680d3 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 23 Jul 2024 23:29:53 +0800 Subject: [PATCH 043/152] =?UTF-8?q?fix:=20=E6=B6=88=E6=81=AFmid=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/whisper/view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 1f69fa64..7082619f 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -228,7 +228,7 @@ class SessionItem extends StatelessWidget { 'talkerId': sessionItem.talkerId.toString(), 'name': sessionItem.accountInfo.name, 'face': sessionItem.accountInfo.face, - 'mid': sessionItem.accountInfo.mid.toString(), + 'mid': (sessionItem.accountInfo?.mid ?? 0).toString(), 'heroTag': heroTag, }, ); From a0bc0314bf51c1da040e4a6f0326ab96737fbd28 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 25 Jul 2024 00:08:18 +0800 Subject: [PATCH 044/152] =?UTF-8?q?fix:=20=E8=AF=84=E8=AE=BA=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/reply/widgets/reply_item.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index bba5ddbb..5b7b0659 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -16,7 +16,6 @@ import 'package:pilipala/pages/video/detail/reply_new/index.dart'; import 'package:pilipala/plugin/pl_gallery/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'; @@ -436,7 +435,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), From 80d0b15ac88d4b9bdcb576d45519fc296c036b2a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 26 Jul 2024 22:47:14 +0800 Subject: [PATCH 045/152] =?UTF-8?q?fix:=20=E5=8A=A8=E6=80=81=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E6=9F=A5=E7=9C=8B=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/dynamics/detail/view.dart | 9 ++++++--- lib/pages/video/detail/reply_reply/view.dart | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index 6b3d969d..56af68fc 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -106,7 +106,7 @@ class _DynamicDetailPageState extends State } // 查看二级评论 - void replyReply(replyItem, currentReply) { + void replyReply(replyItem, currentReply, loadMore) { int oid = replyItem.oid; int rpid = replyItem.rpid!; Get.to( @@ -125,6 +125,7 @@ class _DynamicDetailPageState extends State source: 'dynamic', replyType: ReplyType.values[replyType], firstFloor: replyItem, + loadMore: loadMore, ), ), ); @@ -324,8 +325,10 @@ class _DynamicDetailPageState extends State replyItem: replyList[index], showReplyRow: true, replyLevel: '1', - replyReply: (replyItem, currentReply) => - replyReply(replyItem, currentReply), + replyReply: + (replyItem, currentReply, loadMore) => + replyReply(replyItem, + currentReply, loadMore), replyType: ReplyType.values[replyType], addReply: (replyItem) { replyList[index] diff --git a/lib/pages/video/detail/reply_reply/view.dart b/lib/pages/video/detail/reply_reply/view.dart index 439f5d1d..06a40cd6 100644 --- a/lib/pages/video/detail/reply_reply/view.dart +++ b/lib/pages/video/detail/reply_reply/view.dart @@ -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 createState() => _VideoReplyReplyPanelState(); @@ -142,7 +142,7 @@ class _VideoReplyReplyPanelState extends State { ), ), ], - widget.loadMore != null && widget.loadMore! + widget.loadMore ? FutureBuilder( future: _futureBuilderFuture, builder: (BuildContext context, snapshot) { From 5e6f082ade33223e30fc522ec6fb78f428a713a3 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 27 Jul 2024 11:00:19 +0800 Subject: [PATCH 046/152] =?UTF-8?q?feat:=20=E8=AF=84=E8=AE=BA=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E4=B8=BA=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/reply/widgets/reply_item.dart | 28 +++- .../detail/reply/widgets/reply_save.dart | 140 ++++++++++++++++++ 2 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 lib/pages/video/detail/reply/widgets/reply_save.dart diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 5b7b0659..9fc8b11e 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -14,11 +14,13 @@ 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/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; @@ -58,7 +60,10 @@ class ReplyItem extends StatelessWidget { useRootNavigator: true, isScrollControlled: true, builder: (context) { - return MorePanel(item: replyItem); + return MorePanel( + item: replyItem, + mainFloor: true, + ); }, ); }, @@ -1004,7 +1009,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 menuActionHandler(String type) async { String message = item.content.message ?? item.content; @@ -1026,6 +1036,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 +1093,13 @@ class MorePanel extends StatelessWidget { leading: const Icon(Icons.copy_outlined, size: 19), title: Text('自由复制', style: textTheme.titleSmall), ), + if (mainFloor) + 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, diff --git a/lib/pages/video/detail/reply/widgets/reply_save.dart b/lib/pages/video/detail/reply/widgets/reply_save.dart new file mode 100644 index 00000000..c083806f --- /dev/null +++ b/lib/pages/video/detail/reply/widgets/reply_save.dart @@ -0,0 +1,140 @@ +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 createState() => _ReplySaveState(); +} + +class _ReplySaveState extends State { + 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 _createWidgets(int count, Widget Function() builder) { + return List.generate(count, (_) => Expanded(child: builder())); + } + + List _createColumnWidgets() { + return _createWidgets(3, () => Row(children: _createRowWidgets())); + } + + List _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 Container( + width: Get.width, + height: Get.height, + margin: EdgeInsets.fromLTRB( + 0, + MediaQuery.of(context).padding.top + 4, + 0, + MediaQuery.of(context).padding.bottom + 4, + ), + color: Colors.transparent, + 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, + ), + 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('保存'), + ), + ], + ), + ], + ), + ); + } +} From a2cbcca9091ce10dd041bb99fe6aba2f303df1eb Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 27 Jul 2024 11:02:42 +0800 Subject: [PATCH 047/152] =?UTF-8?q?fix:=20=E9=A6=96=E9=A1=B5=E5=BC=82?= =?UTF-8?q?=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/home/rcmd/result.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/models/home/rcmd/result.dart b/lib/models/home/rcmd/result.dart index 98beda59..88657b33 100644 --- a/lib/models/home/rcmd/result.dart +++ b/lib/models/home/rcmd/result.dart @@ -62,7 +62,7 @@ class RecVideoItemAppModel { duration = json['player_args'] != null ? json['player_args']['duration'] : -1; //duration = json['cover_right_text']; - title = json['title']; + title = json['title'] ?? '获取标题失败'; owner = RcmdOwner.fromJson(json); rcmdReason = json['bottom_rcmd_reason'] ?? json['top_rcmd_reason']; // 由于app端api并不会直接返回与owner的关注状态 @@ -74,7 +74,7 @@ class RecVideoItemAppModel { rcmdReason = null; } goto = json['goto']; - param = int.parse(json['param']); + param = int.parse(json['param'] ?? '-1'); uri = json['uri']; talkBack = json['talk_back']; From 8e0fbb2a54391dd84d536ab6ef6d78f8bfbf850f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 27 Jul 2024 22:08:12 +0800 Subject: [PATCH 048/152] =?UTF-8?q?opt:=20=E9=9D=9Epip=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E8=87=B3=E5=90=8E=E5=8F=B0=E4=B8=8D=E5=85=B3?= =?UTF-8?q?=E9=97=ADBottomSheet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/view.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 331986dd..0388e962 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -330,12 +330,14 @@ class _VideoDetailPageState extends State 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; } From 66edac428b99b1ff45860f9c6ed5b2ae66e51925 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 4 Aug 2024 00:35:42 +0800 Subject: [PATCH 049/152] =?UTF-8?q?mod:=20=E8=A1=A5=E5=85=85=E5=90=88?= =?UTF-8?q?=E9=9B=86=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 2 ++ lib/http/member.dart | 36 ++++++++++++++++++++ lib/models/member/seasons.dart | 13 ++++++++ lib/pages/member/widgets/seasons.dart | 23 +++++++++++-- lib/pages/member_seasons/controller.dart | 42 +++++++++++++++++++++--- lib/pages/member_seasons/view.dart | 7 ++-- 6 files changed, 114 insertions(+), 9 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index 46bbb6ac..31e5a38b 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -487,6 +487,8 @@ class Api { static const getSeasonDetailApi = '/x/polymer/web-space/seasons_archives_list'; + static const getSeriesDetailApi = '/x/series/archives'; + /// 获取未读动态数 static const getUnreadDynamic = '/x/web-interface/dynamic/entrance'; diff --git a/lib/http/member.dart b/lib/http/member.dart index 0fb010b0..e87aa42e 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -520,4 +520,40 @@ class MemberHttp { }; } } + + static Future getSeriesDetail({ + required int mid, + required int currentMid, + required int seriesId, + required int pn, + }) async { + var res = await Request().get( + Api.getSeriesDetailApi, + data: { + 'mid': mid, + 'series_id': seriesId, + 'only_normal': true, + 'sort': 'desc', + 'pn': pn, + 'ps': 30, + 'current_mid': currentMid, + }, + ); + if (res.data['code'] == 0) { + try { + return { + 'status': true, + 'data': MemberSeasonsDataModel.fromJson(res.data['data']) + }; + } catch (err) { + print(err); + } + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/models/member/seasons.dart b/lib/models/member/seasons.dart index 88b93c78..275466a6 100644 --- a/lib/models/member/seasons.dart +++ b/lib/models/member/seasons.dart @@ -2,10 +2,12 @@ class MemberSeasonsDataModel { MemberSeasonsDataModel({ this.page, this.seasonsList, + this.seriesList, }); Map? page; List? seasonsList; + List? seriesList; MemberSeasonsDataModel.fromJson(Map json) { page = json['page']; @@ -19,6 +21,11 @@ class MemberSeasonsDataModel { .map((e) => MemberSeasonsList.fromJson(e)) .toList() : []; + seriesList = json['archives'] != null + ? json['archives'] + .map((e) => MemberArchiveItem.fromJson(e)) + .toList() + : []; seasonsList = [...tempList1, ...tempList2]; } @@ -93,6 +100,8 @@ class MamberMeta { this.ptime, this.seasonId, this.total, + this.seriesId, + this.category, }); String? cover; @@ -102,6 +111,8 @@ class MamberMeta { int? ptime; int? seasonId; int? total; + int? seriesId; + int? category; MamberMeta.fromJson(Map json) { cover = json['cover']; @@ -111,5 +122,7 @@ class MamberMeta { ptime = json['ptime']; seasonId = json['season_id']; total = json['total']; + seriesId = json['series_id']; + category = json['category']; } } diff --git a/lib/pages/member/widgets/seasons.dart b/lib/pages/member/widgets/seasons.dart index 1367d6bd..1749ff45 100644 --- a/lib/pages/member/widgets/seasons.dart +++ b/lib/pages/member/widgets/seasons.dart @@ -24,8 +24,27 @@ class MemberSeasonsPanel extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( - onTap: () => Get.toNamed( - '/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}&seasonName=${item.meta!.name}'), + onTap: () { + final int category = item.meta!.category!; + Map parameters = {}; + if (category == 0) { + parameters = { + 'category': '0', + 'mid': item.meta!.mid.toString(), + 'seasonId': item.meta!.seasonId.toString(), + 'seasonName': item.meta!.name!, + }; + } + if (category == 1) { + parameters = { + 'category': '1', + 'mid': item.meta!.mid.toString(), + 'seriesId': item.meta!.seriesId.toString(), + 'seasonName': item.meta!.name!, + }; + } + Get.toNamed('/memberSeasons', parameters: parameters); + }, title: Text( item.meta!.name!, maxLines: 1, diff --git a/lib/pages/member_seasons/controller.dart b/lib/pages/member_seasons/controller.dart index 82ef0af0..58a9035f 100644 --- a/lib/pages/member_seasons/controller.dart +++ b/lib/pages/member_seasons/controller.dart @@ -6,7 +6,9 @@ import 'package:pilipala/models/member/seasons.dart'; class MemberSeasonsController extends GetxController { final ScrollController scrollController = ScrollController(); late int mid; - late int seasonId; + int? seasonId; + int? seriesId; + late String category; int pn = 1; int ps = 30; int count = 0; @@ -17,17 +19,23 @@ class MemberSeasonsController extends GetxController { void onInit() { super.onInit(); mid = int.parse(Get.parameters['mid']!); - seasonId = int.parse(Get.parameters['seasonId']!); + category = Get.parameters['category']!; + if (category == '0') { + seasonId = int.parse(Get.parameters['seriesId']!); + } + if (category == '1') { + seriesId = int.parse(Get.parameters['seriesId']!); + } } - // 获取专栏详情 + // 获取专栏详情 0: 专栏 1: 系列 Future getSeasonDetail(type) async { if (type == 'onRefresh') { pn = 1; } var res = await MemberHttp.getSeasonDetail( mid: mid, - seasonId: seasonId, + seasonId: seasonId!, pn: pn, ps: ps, sortReverse: false, @@ -40,8 +48,32 @@ class MemberSeasonsController extends GetxController { return res; } + // 获取系列详情 0: 专栏 1: 系列 + Future getSeriesDetail(type) async { + if (type == 'onRefresh') { + pn = 1; + } + var res = await MemberHttp.getSeriesDetail( + mid: mid, + seriesId: seriesId!, + pn: pn, + currentMid: 17340771, + ); + if (res['status']) { + seasonsList.addAll(res['data'].seriesList); + page = res['data'].page; + pn += 1; + } + return res; + } + // 上拉加载 Future onLoad() async { - getSeasonDetail('onLoad'); + if (category == '0') { + getSeasonDetail('onLoad'); + } + if (category == '1') { + getSeriesDetail('onLoad'); + } } } diff --git a/lib/pages/member_seasons/view.dart b/lib/pages/member_seasons/view.dart index 556e2ec5..b8c0407d 100644 --- a/lib/pages/member_seasons/view.dart +++ b/lib/pages/member_seasons/view.dart @@ -17,12 +17,15 @@ class _MemberSeasonsPageState extends State { Get.put(MemberSeasonsController()); late Future _futureBuilderFuture; late ScrollController scrollController; + late String category; @override void initState() { super.initState(); - _futureBuilderFuture = - _memberSeasonsController.getSeasonDetail('onRefresh'); + category = Get.parameters['category']!; + _futureBuilderFuture = category == '0' + ? _memberSeasonsController.getSeasonDetail('onRefresh') + : _memberSeasonsController.getSeriesDetail('onRefresh'); scrollController = _memberSeasonsController.scrollController; scrollController.addListener( () { From 0bb9a68cc55cb65bbc1a5bf41c683a47f8d9827d Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 9 Aug 2024 18:39:16 +0800 Subject: [PATCH 050/152] =?UTF-8?q?mod:=20=E4=BF=9D=E5=AD=98=E5=B0=81?= =?UTF-8?q?=E9=9D=A2=E6=97=B6=E8=A7=86=E9=A2=91=E6=A0=87=E9=A2=98=E5=8F=AF?= =?UTF-8?q?=E9=80=89=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/image_save.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/image_save.dart b/lib/utils/image_save.dart index 0b77b7cc..99513ac8 100644 --- a/lib/utils/image_save.dart +++ b/lib/utils/image_save.dart @@ -57,7 +57,7 @@ Future imageSaveDialog(context, videoItem, closeFn) { child: Row( children: [ Expanded( - child: Text( + child: SelectableText( videoItem.title! as String, style: Theme.of(context).textTheme.titleSmall, ), From 61337338bd42d0e43ffcee264a952b5d52318c6a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 10 Aug 2024 23:11:21 +0800 Subject: [PATCH 051/152] =?UTF-8?q?fix:=20=E5=9B=BE=E7=89=87=E9=A2=84?= =?UTF-8?q?=E8=A7=88Hero=20tag=E9=87=8D=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/detail/reply/widgets/reply_item.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 5b7b0659..5f53b398 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -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'; @@ -549,7 +551,7 @@ InlineSpan buildContent( ); } - void onPreviewImg(picList, initIndex) { + void onPreviewImg(picList, initIndex, randomInt) { final MainController mainController = Get.find(); mainController.imgPreviewStatus = true; Navigator.of(context).push( @@ -575,7 +577,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 +888,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 +930,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, From b188675faf2607e9e5d3eb5c60da0ae3863203fa Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 11 Aug 2024 18:43:21 +0800 Subject: [PATCH 052/152] opt --- .../detail/reply/widgets/reply_item.dart | 43 +++++-- .../detail/reply/widgets/reply_save.dart | 106 ++++++++++-------- 2 files changed, 88 insertions(+), 61 deletions(-) diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 9fc8b11e..ae691f97 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -17,6 +17,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/id_utils.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/url_utils.dart'; import 'package:pilipala/utils/utils.dart'; @@ -33,6 +34,7 @@ class ReplyItem extends StatelessWidget { this.showReplyRow = true, this.replyReply, this.replyType, + this.replySave = false, super.key, }); final ReplyItemModel? replyItem; @@ -41,6 +43,7 @@ class ReplyItem extends StatelessWidget { final bool? showReplyRow; final Function? replyReply; final ReplyType? replyType; + final bool? replySave; @override Widget build(BuildContext context) { @@ -48,12 +51,18 @@ 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, @@ -236,7 +245,7 @@ class ReplyItem extends StatelessWidget { ), ), // 操作区域 - bottonAction(context, replyItem!.replyControl), + bottonAction(context, replyItem!.replyControl, replySave), // 一楼的评论 if ((replyItem!.replyControl!.isShow! || replyItem!.replies!.isNotEmpty) && @@ -257,7 +266,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( @@ -290,16 +299,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, + ), ), - ), ]), ), ), @@ -1093,7 +1112,7 @@ class MorePanel extends StatelessWidget { leading: const Icon(Icons.copy_outlined, size: 19), title: Text('自由复制', style: textTheme.titleSmall), ), - if (mainFloor) + if (mainFloor && item.content.pictures.isEmpty) ListTile( onTap: () async => await menuActionHandler('save'), minLeadingWidth: 0, diff --git a/lib/pages/video/detail/reply/widgets/reply_save.dart b/lib/pages/video/detail/reply/widgets/reply_save.dart index c083806f..9eed4923 100644 --- a/lib/pages/video/detail/reply/widgets/reply_save.dart +++ b/lib/pages/video/detail/reply/widgets/reply_save.dart @@ -76,64 +76,72 @@ class _ReplySaveState extends State { @override Widget build(BuildContext context) { - return Container( - width: Get.width, - height: Get.height, - margin: EdgeInsets.fromLTRB( - 0, - MediaQuery.of(context).padding.top + 4, - 0, - MediaQuery.of(context).padding.bottom + 4, - ), - color: Colors.transparent, - 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, + 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(), + ), + ), + ], ), - 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('保存'), + 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('保存'), + ), + ], ), ], ), - ], + ), ), ); } From ec30235421b13dfba26b3b7a7685d1402eff0709 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 11 Aug 2024 23:16:33 +0800 Subject: [PATCH 053/152] =?UTF-8?q?opt:=20=E4=B8=80=E9=94=AE=E4=B8=89?= =?UTF-8?q?=E8=BF=9E=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/introduction/view.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 1e8d97f1..760976ae 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -613,6 +613,9 @@ class _VideoInfoState extends State with TickerProviderStateMixin { } _controller.reverse(); }, + onTapCancel: () { + _controller.reverse(); + }, borderRadius: StyleString.mdRadius, child: SizedBox( width: (Get.size.width - 24) / 5, From 1012b5d00907e05616833a4f71f53300913d31ed Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 18 Aug 2024 23:30:51 +0800 Subject: [PATCH 054/152] =?UTF-8?q?feat:=20=E7=9B=B4=E6=92=AD=E5=BC=B9?= =?UTF-8?q?=E5=B9=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 4 + lib/http/init.dart | 3 + lib/http/live.dart | 19 ++ lib/models/live/message.dart | 101 +++++++++++ lib/pages/live_room/controller.dart | 78 ++++++++ lib/pages/live_room/view.dart | 269 ++++++++++++++++++++++++++-- lib/plugin/pl_socket/index.dart | 107 +++++++++++ lib/utils/binary_writer.dart | 117 ++++++++++++ lib/utils/live.dart | 196 ++++++++++++++++++++ pubspec.lock | 14 +- pubspec.yaml | 2 + 11 files changed, 890 insertions(+), 20 deletions(-) create mode 100644 lib/models/live/message.dart create mode 100644 lib/plugin/pl_socket/index.dart create mode 100644 lib/utils/binary_writer.dart create mode 100644 lib/utils/live.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 31e5a38b..08a20382 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -554,4 +554,8 @@ class Api { /// 系统通知 static const String messageSystemAPi = '${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify'; + + /// 直播间弹幕信息 + static const String getDanmuInfo = + '${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getDanmuInfo'; } diff --git a/lib/http/init.dart b/lib/http/init.dart index cb9d6f39..faa57dd5 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -29,6 +29,7 @@ class Request { late String systemProxyPort; static final RegExp spmPrefixExp = RegExp(r''); + static late String buvid; /// 设置cookie static setCookie() async { @@ -70,6 +71,8 @@ class Request { final String cookieString = cookie .map((Cookie cookie) => '${cookie.name}=${cookie.value}') .join('; '); + + buvid = cookie.firstWhere((e) => e.name == 'buvid3').value; dio.options.headers['cookie'] = cookieString; } diff --git a/lib/http/live.dart b/lib/http/live.dart index e624120e..a405fd58 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -65,4 +65,23 @@ class LiveHttp { }; } } + + // 获取弹幕信息 + static Future liveDanmakuInfo({roomId}) async { + var res = await Request().get(Api.getDanmuInfo, data: { + 'id': roomId, + }); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/models/live/message.dart b/lib/models/live/message.dart new file mode 100644 index 00000000..cd0f4b75 --- /dev/null +++ b/lib/models/live/message.dart @@ -0,0 +1,101 @@ +class LiveMessageModel { + // 消息类型 + final LiveMessageType type; + + // 用户名 + final String userName; + + // 信息 + final String? message; + + // 数据 + final dynamic data; + + final String? face; + final int? uid; + final Map? emots; + + // 颜色 + final LiveMessageColor color; + + LiveMessageModel({ + required this.type, + required this.userName, + required this.message, + required this.color, + this.data, + this.face, + this.uid, + this.emots, + }); +} + +class LiveSuperChatMessage { + final String backgroundBottomColor; + final String backgroundColor; + final DateTime endTime; + final String face; + final String message; + final String price; + final DateTime startTime; + final String userName; + + LiveSuperChatMessage({ + required this.backgroundBottomColor, + required this.backgroundColor, + required this.endTime, + required this.face, + required this.message, + required this.price, + required this.startTime, + required this.userName, + }); +} + +enum LiveMessageType { + // 普通留言 + chat, + // 醒目留言 + superChat, + // + online, + // 加入 + join, + // 关注 + follow, +} + +class LiveMessageColor { + final int r, g, b; + LiveMessageColor(this.r, this.g, this.b); + static LiveMessageColor get white => LiveMessageColor(255, 255, 255); + static LiveMessageColor numberToColor(int intColor) { + var obj = intColor.toRadixString(16); + + LiveMessageColor color = LiveMessageColor.white; + if (obj.length == 4) { + obj = "00$obj"; + } + if (obj.length == 6) { + var R = int.parse(obj.substring(0, 2), radix: 16); + var G = int.parse(obj.substring(2, 4), radix: 16); + var B = int.parse(obj.substring(4, 6), radix: 16); + + color = LiveMessageColor(R, G, B); + } + if (obj.length == 8) { + var R = int.parse(obj.substring(2, 4), radix: 16); + var G = int.parse(obj.substring(4, 6), radix: 16); + var B = int.parse(obj.substring(6, 8), radix: 16); + //var A = int.parse(obj.substring(0, 2), radix: 16); + color = LiveMessageColor(R, G, B); + } + + return color; + } + + @override + String toString() { + return "#${r.toRadixString(16).padLeft(2, '0')}${g.toRadixString(16).padLeft(2, '0')}${b.toRadixString(16).padLeft(2, '0')}"; + } +} diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 4e67fa2c..fa95ce63 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -1,10 +1,16 @@ +import 'dart:convert'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:hive/hive.dart'; import 'package:pilipala/http/constants.dart'; +import 'package:pilipala/http/init.dart'; import 'package:pilipala/http/live.dart'; +import 'package:pilipala/models/live/message.dart'; import 'package:pilipala/models/live/quality.dart'; import 'package:pilipala/models/live/room_info.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; +import 'package:pilipala/plugin/pl_socket/index.dart'; +import 'package:pilipala/utils/live.dart'; import '../../models/live/room_info_h5.dart'; import '../../utils/storage.dart'; import '../../utils/video_utils.dart'; @@ -24,6 +30,13 @@ class LiveRoomController extends GetxController { int? tempCurrentQn; late List> acceptQnList; RxString currentQnDesc = ''.obs; + Box userInfoCache = GStrorage.userInfo; + int userId = 0; + PlSocket? plSocket; + List danmuHostList = []; + String token = ''; + // 弹幕消息列表 + RxList messageList = [].obs; @override void onInit() { @@ -43,6 +56,11 @@ class LiveRoomController extends GetxController { } // CDN优化 enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); + final userInfo = userInfoCache.get('userInfoCache'); + if (userInfo != null && userInfo.mid != null) { + userId = userInfo.mid; + } + liveDanmakuInfo().then((value) => initSocket()); } playerInit(source) async { @@ -127,4 +145,64 @@ class LiveRoomController extends GetxController { .description; await queryLiveInfo(); } + + Future liveDanmakuInfo() async { + var res = await LiveHttp.liveDanmakuInfo(roomId: roomId); + if (res['status']) { + danmuHostList = (res["data"]["host_list"] as List) + .map((e) => '${e["host"]}:${e['wss_port']}') + .toList(); + token = res["data"]["token"]; + return res; + } + } + + // 建立socket + void initSocket() async { + final wsUrl = danmuHostList.isNotEmpty + ? danmuHostList.first + : "broadcastlv.chat.bilibili.com"; + plSocket = PlSocket( + url: 'wss://$wsUrl/sub', + heartTime: 30, + onReadyCb: () { + joinRoom(); + }, + onMessageCb: (message) { + final List? liveMsg = + LiveUtils.decodeMessage(message); + if (liveMsg != null) { + messageList.addAll(liveMsg + .where((msg) => msg.type == LiveMessageType.chat) + .toList()); + } + }, + onErrorCb: (e) { + print('error: $e'); + }, + ); + await plSocket?.connect(); + } + + void joinRoom() async { + var joinData = LiveUtils.encodeData( + json.encode({ + "uid": userId, + "roomid": roomId, + "protover": 3, + "buvid": Request.buvid, + "platform": "web", + "type": 2, + "key": token, + }), + 7, + ); + plSocket?.sendMessage(joinData); + } + + @override + void onClose() { + plSocket?.onClose(); + super.onClose(); + } } diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 37981b1d..d9b316e9 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -1,9 +1,12 @@ import 'dart:io'; import 'package:floating/floating.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/models/live/message.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; import 'controller.dart'; @@ -16,7 +19,8 @@ class LiveRoomPage extends StatefulWidget { State createState() => _LiveRoomPageState(); } -class _LiveRoomPageState extends State { +class _LiveRoomPageState extends State + with TickerProviderStateMixin { final LiveRoomController _liveRoomController = Get.put(LiveRoomController()); PlPlayerController? plPlayerController; late Future? _futureBuilder; @@ -25,6 +29,9 @@ class _LiveRoomPageState extends State { bool isShowCover = true; bool isPlay = true; Floating? floating; + final ScrollController _scrollController = ScrollController(); + late AnimationController fabAnimationCtr; + bool _shouldAutoScroll = true; @override void initState() { @@ -34,6 +41,13 @@ class _LiveRoomPageState extends State { } videoSourceInit(); _futureBuilderFuture = _liveRoomController.queryLiveInfo(); + // 监听滚动事件 + _scrollController.addListener(_onScroll); + fabAnimationCtr = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 300), + value: 0.0, + ); } Future videoSourceInit() async { @@ -41,12 +55,52 @@ class _LiveRoomPageState extends State { plPlayerController = _liveRoomController.plPlayerController; } + void _onScroll() { + // 反向时,展示按钮 + if (_scrollController.position.userScrollDirection == + ScrollDirection.forward) { + _shouldAutoScroll = false; + fabAnimationCtr.forward(); + } else { + _shouldAutoScroll = true; + fabAnimationCtr.reverse(); + } + } + + // 监听messageList的变化,自动滚动到底部 + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _liveRoomController.messageList.listen((_) { + if (_shouldAutoScroll) { + _scrollToBottom(); + } + }); + } + + void _scrollToBottom() { + if (_scrollController.hasClients) { + _scrollController + .animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ) + .then((value) { + _shouldAutoScroll = true; + // fabAnimationCtr.forward(); + }); + } + } + @override void dispose() { plPlayerController!.dispose(); if (floating != null) { floating!.dispose(); } + _scrollController.dispose(); + fabAnimationCtr.dispose(); super.dispose(); } @@ -80,20 +134,6 @@ class _LiveRoomPageState extends State { backgroundColor: Colors.black, body: Stack( children: [ - Positioned( - left: 0, - right: 0, - bottom: 0, - child: Opacity( - opacity: 0.8, - child: Image.asset( - 'assets/images/live/default_bg.webp', - fit: BoxFit.cover, - // width: Get.width, - // height: Get.height, - ), - ), - ), Obx( () => Positioned( left: 0, @@ -106,7 +146,7 @@ class _LiveRoomPageState extends State { .roomInfoH5.value.roomInfo?.appBackground != null ? Opacity( - opacity: 0.8, + opacity: 0.6, child: NetworkImgLayer( width: Get.width, height: Get.height, @@ -116,7 +156,15 @@ class _LiveRoomPageState extends State { '', ), ) - : const SizedBox(), + : Opacity( + opacity: 0.6, + child: Image.asset( + 'assets/images/live/default_bg.webp', + fit: BoxFit.cover, + // width: Get.width, + // height: Get.height, + ), + ), ), ), Column( @@ -198,8 +246,45 @@ class _LiveRoomPageState extends State { child: videoPlayerPanel, ), ), + const SizedBox(height: 20), + // 显示消息的列表 + buildMessageListUI( + context, + _liveRoomController, + _scrollController, + ), + // 底部安全距离 + SizedBox( + height: MediaQuery.of(context).padding.bottom + 20, + ) ], ), + // 定位 快速滑动到底部 + Positioned( + right: 20, + bottom: MediaQuery.of(context).padding.bottom + 20, + child: SlideTransition( + position: Tween( + begin: const Offset(0, 2), + end: const Offset(0, 0), + ).animate(CurvedAnimation( + parent: fabAnimationCtr, + curve: Curves.easeInOut, + )), + child: ElevatedButton.icon( + onPressed: () { + _scrollToBottom(); + }, + icon: const Icon(Icons.keyboard_arrow_down), // 图标 + label: const Text('新消息'), // 文字 + style: ElevatedButton.styleFrom( + // primary: Colors.blue, // 按钮背景颜色 + // onPrimary: Colors.white, // 按钮文字颜色 + padding: const EdgeInsets.fromLTRB(14, 12, 20, 12), // 按钮内边距 + ), + ), + ), + ), ], ), ); @@ -214,3 +299,153 @@ class _LiveRoomPageState extends State { } } } + +Widget buildMessageListUI( + BuildContext context, + LiveRoomController liveRoomController, + ScrollController scrollController, +) { + return Expanded( + child: Obx( + () => MediaQuery.removePadding( + context: context, + removeTop: true, + removeBottom: true, + child: ShaderMask( + shaderCallback: (Rect bounds) { + return const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black, + Colors.black, + ], + stops: [0.0, 0.1, 1.0], + ).createShader(bounds); + }, + blendMode: BlendMode.dstIn, + child: ListView.builder( + controller: scrollController, + itemCount: liveRoomController.messageList.length, + itemBuilder: (context, index) { + final LiveMessageModel liveMsgItem = + liveRoomController.messageList[index]; + return Padding( + padding: EdgeInsets.only( + top: index == 0 ? 40.0 : 4.0, + bottom: 4.0, + left: 20.0, + right: 20.0, + ), + child: Text.rich( + TextSpan( + style: const TextStyle(color: Colors.white), + children: [ + TextSpan( + text: '${liveMsgItem.userName}: ', + style: TextStyle( + color: Colors.white.withOpacity(0.6), + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // 处理点击事件 + print('Text clicked'); + }, + ), + TextSpan( + children: [ + ...buildMessageTextSpan(context, liveMsgItem) + ], + // text: liveMsgItem.message, + ), + ], + ), + ), + ); + }, + ), + ), + ), + ), + ); +} + +List buildMessageTextSpan( + BuildContext context, + LiveMessageModel liveMsgItem, +) { + final List inlineSpanList = []; + + // 是否包含表情包 + if (liveMsgItem.emots == null) { + // 没有表情包的消息 + inlineSpanList.add( + TextSpan( + text: liveMsgItem.message ?? '', + style: const TextStyle( + shadows: [ + Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 3.0, + color: Colors.black, + ), + Shadow( + offset: Offset(-1.0, -1.0), + blurRadius: 3.0, + color: Colors.black, + ), + ], + ), + ), + ); + } else { + // 有表情包的消息 使用正则匹配 表情包用图片渲染 + final List emotsKeys = liveMsgItem.emots!.keys.toList(); + final RegExp pattern = RegExp(emotsKeys.map(RegExp.escape).join('|')); + + liveMsgItem.message?.splitMapJoin( + pattern, + onMatch: (Match match) { + final emoteItem = liveMsgItem.emots![match.group(0)]; + if (emoteItem != null) { + inlineSpanList.add( + WidgetSpan( + child: NetworkImgLayer( + width: emoteItem['width'].toDouble(), + height: emoteItem['height'].toDouble(), + type: 'emote', + src: emoteItem['url'], + ), + ), + ); + } + return ''; + }, + onNonMatch: (String nonMatch) { + inlineSpanList.add( + TextSpan( + text: nonMatch, + style: const TextStyle( + shadows: [ + Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 3.0, + color: Colors.black, + ), + Shadow( + offset: Offset(-1.0, -1.0), + blurRadius: 3.0, + color: Colors.black, + ), + ], + ), + ), + ); + return nonMatch; + }, + ); + } + + return inlineSpanList; +} diff --git a/lib/plugin/pl_socket/index.dart b/lib/plugin/pl_socket/index.dart new file mode 100644 index 00000000..1ad6af94 --- /dev/null +++ b/lib/plugin/pl_socket/index.dart @@ -0,0 +1,107 @@ +import 'dart:async'; + +import 'package:pilipala/utils/live.dart'; +import 'package:web_socket_channel/io.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +enum SocketStatus { + connected, + failed, + closed, +} + +class PlSocket { + SocketStatus status = SocketStatus.closed; + // 链接 + final String url; + // 心跳时间 + final int heartTime; + // 监听初始化完成 + final Function? onReadyCb; + // 监听关闭 + final Function? onCloseCb; + // 监听异常 + final Function? onErrorCb; + // 监听消息 + final Function? onMessageCb; + // 请求头 + final Map? headers; + + PlSocket({ + required this.url, + required this.heartTime, + this.onReadyCb, + this.onCloseCb, + this.onErrorCb, + this.onMessageCb, + this.headers, + }); + + WebSocketChannel? channel; + StreamSubscription? channelStreamSub; + + // 建立连接 + Future connect() async { + // 连接之前关闭上次连接 + onClose(); + try { + channel = IOWebSocketChannel.connect( + url, + connectTimeout: const Duration(seconds: 15), + headers: null, + ); + await channel?.ready; + onReady(); + } catch (err) { + connect(); + onError(err); + } + } + + // 初始化完成 + void onReady() { + status = SocketStatus.connected; + onReadyCb?.call(); + channelStreamSub = channel?.stream.listen((message) { + onMessageCb?.call(message); + }, onDone: () { + // 流被关闭 + print('结束了'); + }, onError: (err) { + onError(err); + }); + // 每30s发送心跳 + Timer.periodic(Duration(seconds: heartTime), (timer) { + if (status == SocketStatus.connected) { + sendMessage(LiveUtils.encodeData( + "", + 2, + )); + } else { + timer.cancel(); + } + }); + } + + // 连接关闭 + void onClose() { + status = SocketStatus.closed; + onCloseCb?.call(); + channelStreamSub?.cancel(); + channel?.sink.close(); + } + + // 连接异常 + void onError(err) { + onErrorCb?.call(err); + } + + // 接收消息 + void onMessage() {} + + void sendMessage(dynamic message) { + if (status == SocketStatus.connected) { + channel?.sink.add(message); + } + } +} diff --git a/lib/utils/binary_writer.dart b/lib/utils/binary_writer.dart new file mode 100644 index 00000000..929bc573 --- /dev/null +++ b/lib/utils/binary_writer.dart @@ -0,0 +1,117 @@ +import 'dart:typed_data'; + +class BinaryWriter { + List buffer; + int position = 0; + BinaryWriter(this.buffer); + int get length => buffer.length; + + void writeBytes(List list) { + buffer.addAll(list); + position += list.length; + } + + void writeInt(int value, int len, {Endian endian = Endian.big}) { + var bytes = _createByteData(len); + switch (len) { + case 1: + bytes.setUint8(0, value.toUnsigned(8)); + break; + case 2: + bytes.setInt16(0, value, endian); + break; + case 4: + bytes.setInt32(0, value, endian); + break; + case 8: + bytes.setInt64(0, value, endian); + break; + default: + throw ArgumentError('Invalid length for writeInt: $len'); + } + _addBytesToBuffer(bytes, len); + } + + void writeDouble(double value, int len, {Endian endian = Endian.big}) { + var bytes = _createByteData(len); + switch (len) { + case 4: + bytes.setFloat32(0, value, endian); + break; + case 8: + bytes.setFloat64(0, value, endian); + break; + default: + throw ArgumentError('Invalid length for writeDouble: $len'); + } + _addBytesToBuffer(bytes, len); + } + + ByteData _createByteData(int len) { + var b = Uint8List(len).buffer; + return ByteData.view(b); + } + + void _addBytesToBuffer(ByteData bytes, int len) { + buffer.addAll(bytes.buffer.asUint8List()); + position += len; + } +} + +class BinaryReader { + Uint8List buffer; + int position = 0; + BinaryReader(this.buffer); + int get length => buffer.length; + + int read() { + return buffer[position++]; + } + + int readInt(int len, {Endian endian = Endian.big}) { + var bytes = _getBytes(len); + var data = ByteData.view(bytes.buffer); + switch (len) { + case 1: + return data.getUint8(0); + case 2: + return data.getInt16(0, endian); + case 4: + return data.getInt32(0, endian); + case 8: + return data.getInt64(0, endian); + default: + throw ArgumentError('Invalid length for readInt: $len'); + } + } + + int readByte({Endian endian = Endian.big}) => readInt(1, endian: endian); + int readShort({Endian endian = Endian.big}) => readInt(2, endian: endian); + int readInt32({Endian endian = Endian.big}) => readInt(4, endian: endian); + int readLong({Endian endian = Endian.big}) => readInt(8, endian: endian); + + Uint8List readBytes(int len) { + var bytes = _getBytes(len); + return bytes; + } + + double readFloat(int len, {Endian endian = Endian.big}) { + var bytes = _getBytes(len); + var data = ByteData.view(bytes.buffer); + switch (len) { + case 4: + return data.getFloat32(0, endian); + case 8: + return data.getFloat64(0, endian); + default: + throw ArgumentError('Invalid length for readFloat: $len'); + } + } + + Uint8List _getBytes(int len) { + var bytes = + Uint8List.fromList(buffer.getRange(position, position + len).toList()); + position += len; + return bytes; + } +} diff --git a/lib/utils/live.dart b/lib/utils/live.dart new file mode 100644 index 00000000..dd56616e --- /dev/null +++ b/lib/utils/live.dart @@ -0,0 +1,196 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:brotli/brotli.dart'; +import 'package:pilipala/models/live/message.dart'; +import 'package:pilipala/utils/binary_writer.dart'; + +class LiveUtils { + static List encodeData(String msg, int action) { + var data = utf8.encode(msg); + //头部长度固定16 + var length = data.length + 16; + var buffer = Uint8List(length); + + var writer = BinaryWriter([]); + + //数据包长度 + writer.writeInt(buffer.length, 4); + //数据包头部长度,固定16 + writer.writeInt(16, 2); + + //协议版本,0=JSON,1=Int32,2=Buffer + writer.writeInt(0, 2); + + //操作类型 + writer.writeInt(action, 4); + + //数据包头部长度,固定1 + + writer.writeInt(1, 4); + + writer.writeBytes(data); + + return writer.buffer; + } + + static List? decodeMessage(List data) { + try { + //操作类型。3=心跳回应,内容为房间人气值;5=通知,弹幕、广播等全部信息;8=进房回应,空 + int operation = readInt(data, 8, 4); + //内容 + var body = data.skip(16).toList(); + if (operation == 3) { + var online = readInt(body, 0, 4); + final LiveMessageModel liveMsg = LiveMessageModel( + type: LiveMessageType.online, + userName: '', + message: '', + color: LiveMessageColor.white, + data: online, + ); + return [liveMsg]; + } else if (operation == 5) { + //协议版本。0为JSON,可以直接解析;1为房间人气值,Body为4位Int32;2为压缩过Buffer,需要解压再处理 + int protocolVersion = readInt(data, 6, 2); + if (protocolVersion == 2) { + body = zlib.decode(body); + } else if (protocolVersion == 3) { + body = brotli.decode(body); + } + + var text = utf8.decode(body, allowMalformed: true); + + var group = + text.split(RegExp(r"[\x00-\x1f]+", unicode: true, multiLine: true)); + List messages = []; + for (var item + in group.where((x) => x.length > 2 && x.startsWith('{'))) { + if (parseMessage(item) is LiveMessageModel) { + messages.add(parseMessage(item)!); + } + } + return messages; + } + } catch (e) { + print(e); + } + return null; + } + + static LiveMessageModel? parseMessage(String jsonMessage) { + try { + var obj = json.decode(jsonMessage); + var cmd = obj["cmd"].toString(); + if (cmd.contains("DANMU_MSG")) { + if (obj["info"] != null && obj["info"].length != 0) { + var message = obj["info"][1].toString(); + var color = asT(obj["info"][0][3]) ?? 0; + if (obj["info"][2] != null && obj["info"][2].length != 0) { + var extra = obj["info"][0][15]['extra']; + var user = obj["info"][0][15]['user']['base']; + Map extraMap = jsonDecode(extra); + final int userId = obj["info"][2][0]; + final LiveMessageModel liveMsg = LiveMessageModel( + type: LiveMessageType.chat, + userName: user['name'], + message: message, + color: color == 0 + ? LiveMessageColor.white + : LiveMessageColor.numberToColor(color), + face: user['face'], + uid: userId, + emots: extraMap['emots'], + ); + return liveMsg; + } + } + } else if (cmd == "SUPER_CHAT_MESSAGE") { + if (obj["data"] == null) { + return null; + } + final data = obj["data"]; + final userInfo = data["user_info"]; + final String backgroundBottomColor = + data["background_bottom_color"].toString(); + final String backgroundColor = data["background_color"].toString(); + final DateTime endTime = + DateTime.fromMillisecondsSinceEpoch(data["end_time"] * 1000); + final String face = "${userInfo["face"]}@200w.jpg"; + final String message = data["message"].toString(); + final String price = data["price"]; + final DateTime startTime = + DateTime.fromMillisecondsSinceEpoch(data["start_time"] * 1000); + final String userName = userInfo["uname"].toString(); + + final LiveMessageModel liveMsg = LiveMessageModel( + type: LiveMessageType.superChat, + userName: "SUPER_CHAT_MESSAGE", + message: "SUPER_CHAT_MESSAGE", + color: LiveMessageColor.white, + data: { + "backgroundBottomColor": backgroundBottomColor, + "backgroundColor": backgroundColor, + "endTime": endTime, + "face": face, + "message": message, + "price": price, + "startTime": startTime, + "userName": userName, + }, + ); + return liveMsg; + } else if (cmd == 'INTERACT_WORD') { + if (obj["data"] == null) { + return null; + } + final data = obj["data"]; + final String userName = data['uname']; + final int msgType = data['msg_type']; + final LiveMessageModel liveMsg = LiveMessageModel( + type: msgType == 1 ? LiveMessageType.join : LiveMessageType.follow, + userName: userName, + message: msgType == 1 ? '进入直播间' : '关注了主播', + color: LiveMessageColor.white, + ); + return liveMsg; + } + } catch (e) { + print(e); + } + return null; + } + + static T? asT(dynamic value) { + if (value is T) { + return value; + } + return null; + } + + static int readInt(List buffer, int start, int len) { + var data = _getByteData(buffer, start, len); + return _readIntFromByteData(data, len); + } + + static ByteData _getByteData(List buffer, int start, int len) { + var bytes = + Uint8List.fromList(buffer.getRange(start, start + len).toList()); + return ByteData.view(bytes.buffer); + } + + static int _readIntFromByteData(ByteData data, int len) { + switch (len) { + case 1: + return data.getUint8(0); + case 2: + return data.getInt16(0, Endian.big); + case 4: + return data.getInt32(0, Endian.big); + case 8: + return data.getInt64(0, Endian.big); + default: + throw ArgumentError('Invalid length: $len'); + } + } +} diff --git a/pubspec.lock b/pubspec.lock index a46127f9..2dfaa961 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,6 +113,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" + brotli: + dependency: "direct main" + description: + name: brotli + sha256: "7f891558ed779aab2bed874f0a36b8123f9ff3f19cf6efbee89e18ed294945ae" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.0" build: dependency: transitive description: @@ -1656,13 +1664,13 @@ packages: source: hosted version: "0.5.1" web_socket_channel: - dependency: transitive + dependency: "direct main" description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.0" + version: "2.4.5" webview_cookie_manager: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index b5d45714..23c1426c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -146,6 +146,8 @@ dependencies: lottie: ^3.1.2 # 二维码 qr_flutter: ^4.1.0 + web_socket_channel: ^2.4.5 + brotli: ^0.6.0 dev_dependencies: flutter_test: From 869e49bec2369e85a3ade02768888a7620b90924 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 21 Aug 2024 00:09:26 +0800 Subject: [PATCH 055/152] =?UTF-8?q?feat:=20=E7=9B=B4=E6=92=AD=E5=BC=B9?= =?UTF-8?q?=E5=B9=952?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/danmaku/controller.dart | 3 ++- lib/pages/danmaku/view.dart | 32 +++++++++++++++++------------ lib/pages/live_room/controller.dart | 28 ++++++++++++++++++++++--- lib/pages/live_room/view.dart | 15 ++++++++++++-- 4 files changed, 59 insertions(+), 19 deletions(-) diff --git a/lib/pages/danmaku/controller.dart b/lib/pages/danmaku/controller.dart index 52c423d7..ed259b96 100644 --- a/lib/pages/danmaku/controller.dart +++ b/lib/pages/danmaku/controller.dart @@ -2,8 +2,9 @@ import 'package:pilipala/http/danmaku.dart'; import 'package:pilipala/models/danmaku/dm.pb.dart'; class PlDanmakuController { - PlDanmakuController(this.cid); + PlDanmakuController(this.cid, this.type); final int cid; + final String type; Map> dmSegMap = {}; // 已请求的段落标记 List requestedSeg = []; diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart index 109f0206..e669b881 100644 --- a/lib/pages/danmaku/view.dart +++ b/lib/pages/danmaku/view.dart @@ -12,11 +12,15 @@ import 'package:pilipala/utils/storage.dart'; class PlDanmaku extends StatefulWidget { final int cid; final PlPlayerController playerController; + final String type; + final Function(DanmakuController)? createdController; const PlDanmaku({ super.key, required this.cid, required this.playerController, + this.type = 'video', + this.createdController, }); @override @@ -43,9 +47,9 @@ class _PlDanmakuState extends State { super.initState(); enableShowDanmaku = setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false); - _plDanmakuController = PlDanmakuController(widget.cid); - if (mounted) { - playerController = widget.playerController; + _plDanmakuController = PlDanmakuController(widget.cid, widget.type); + playerController = widget.playerController; + if (mounted && widget.type == 'video') { if (enableShowDanmaku || playerController.isOpenDanmu.value) { _plDanmakuController.initiate( playerController.duration.value.inMilliseconds, @@ -55,13 +59,15 @@ class _PlDanmakuState extends State { ..addStatusLister(playerListener) ..addPositionListener(videoPositionListen); } - playerController.isOpenDanmu.listen((p0) { - if (p0 && !_plDanmakuController.initiated) { - _plDanmakuController.initiate( - playerController.duration.value.inMilliseconds, - playerController.position.value.inMilliseconds); - } - }); + if (widget.type == 'video') { + playerController.isOpenDanmu.listen((p0) { + if (p0 && !_plDanmakuController.initiated) { + _plDanmakuController.initiate( + playerController.duration.value.inMilliseconds, + playerController.position.value.inMilliseconds); + } + }); + } blockTypes = playerController.blockTypes; showArea = playerController.showArea; opacityVal = playerController.opacityVal; @@ -123,11 +129,12 @@ class _PlDanmakuState extends State { // double initDuration = box.maxWidth / 12; return Obx( () => AnimatedOpacity( - opacity: playerController.isOpenDanmu.value ? 1 : 0, + opacity: playerController.isOpenDanmu.value ? 1 : 1, duration: const Duration(milliseconds: 100), child: DanmakuView( createdController: (DanmakuController e) async { playerController.danmakuController = _controller = e; + widget.createdController?.call(e); }, option: DanmakuOption( fontSize: 15 * fontSizeVal, @@ -136,8 +143,7 @@ class _PlDanmakuState extends State { hideTop: blockTypes.contains(5), hideScroll: blockTypes.contains(2), hideBottom: blockTypes.contains(4), - duration: - danmakuDurationVal / playerController.playbackSpeed, + duration: danmakuDurationVal / playerController.playbackSpeed, strokeWidth: strokeWidth, // initDuration / // (danmakuSpeedVal * widget.playerController.playbackSpeed), diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index fa95ce63..6a3525e3 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -1,7 +1,9 @@ import 'dart:convert'; +import 'dart:ui'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; +import 'package:ns_danmaku/ns_danmaku.dart'; import 'package:pilipala/http/constants.dart'; import 'package:pilipala/http/init.dart'; import 'package:pilipala/http/live.dart'; @@ -37,6 +39,7 @@ class LiveRoomController extends GetxController { String token = ''; // 弹幕消息列表 RxList messageList = [].obs; + DanmakuController? danmakuController; @override void onInit() { @@ -172,9 +175,28 @@ class LiveRoomController extends GetxController { final List? liveMsg = LiveUtils.decodeMessage(message); if (liveMsg != null) { - messageList.addAll(liveMsg - .where((msg) => msg.type == LiveMessageType.chat) - .toList()); + // 过滤出聊天消息 + var chatMessages = + liveMsg.where((msg) => msg.type == LiveMessageType.chat).toList(); + + // 添加到 messageList + messageList.addAll(chatMessages); + + // 将 chatMessages 转换为 danmakuItems 列表 + List danmakuItems = chatMessages.map((e) { + return DanmakuItem( + e.message ?? '', + color: Color.fromARGB( + 255, + e.color.r, + e.color.g, + e.color.b, + ), + ); + }).toList(); + + // 添加到 danmakuController + danmakuController?.addItems(danmakuItems); } }, onErrorCb: (e) { diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index d9b316e9..d1fc6e07 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/live/message.dart'; +import 'package:pilipala/pages/danmaku/index.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; import 'controller.dart'; @@ -22,7 +23,7 @@ class LiveRoomPage extends StatefulWidget { class _LiveRoomPageState extends State with TickerProviderStateMixin { final LiveRoomController _liveRoomController = Get.put(LiveRoomController()); - PlPlayerController? plPlayerController; + late PlPlayerController plPlayerController; late Future? _futureBuilder; late Future? _futureBuilderFuture; @@ -32,6 +33,7 @@ class _LiveRoomPageState extends State final ScrollController _scrollController = ScrollController(); late AnimationController fabAnimationCtr; bool _shouldAutoScroll = true; + final int roomId = int.parse(Get.parameters['roomid']!); @override void initState() { @@ -110,8 +112,9 @@ class _LiveRoomPageState extends State future: _futureBuilderFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData && snapshot.data['status']) { + plPlayerController = _liveRoomController.plPlayerController; return PLVideoPlayer( - controller: plPlayerController!, + controller: plPlayerController, bottomControl: BottomControl( controller: plPlayerController, liveRoomCtr: _liveRoomController, @@ -122,6 +125,14 @@ class _LiveRoomPageState extends State }); }, ), + danmuWidget: PlDanmaku( + cid: roomId, + playerController: plPlayerController, + type: 'live', + createdController: (e) { + _liveRoomController.danmakuController = e; + }, + ), ); } else { return const SizedBox(); From a074a360f2f1370a554d9248aa72909b841b268c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 21 Aug 2024 00:14:45 +0800 Subject: [PATCH 056/152] =?UTF-8?q?feat:=20=E7=9B=B4=E6=92=AD=E5=BC=B9?= =?UTF-8?q?=E5=B9=952?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/danmaku/controller.dart | 3 ++- lib/pages/danmaku/view.dart | 30 +++++++++++++++++------------ lib/pages/live_room/controller.dart | 28 ++++++++++++++++++++++++--- lib/pages/live_room/view.dart | 15 +++++++++++++-- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/lib/pages/danmaku/controller.dart b/lib/pages/danmaku/controller.dart index 52c423d7..ed259b96 100644 --- a/lib/pages/danmaku/controller.dart +++ b/lib/pages/danmaku/controller.dart @@ -2,8 +2,9 @@ import 'package:pilipala/http/danmaku.dart'; import 'package:pilipala/models/danmaku/dm.pb.dart'; class PlDanmakuController { - PlDanmakuController(this.cid); + PlDanmakuController(this.cid, this.type); final int cid; + final String type; Map> dmSegMap = {}; // 已请求的段落标记 List requestedSeg = []; diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart index 109f0206..3cf1ed8a 100644 --- a/lib/pages/danmaku/view.dart +++ b/lib/pages/danmaku/view.dart @@ -12,11 +12,15 @@ import 'package:pilipala/utils/storage.dart'; class PlDanmaku extends StatefulWidget { final int cid; final PlPlayerController playerController; + final String type; + final Function(DanmakuController)? createdController; const PlDanmaku({ super.key, required this.cid, required this.playerController, + this.type = 'video', + this.createdController, }); @override @@ -43,9 +47,9 @@ class _PlDanmakuState extends State { super.initState(); enableShowDanmaku = setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false); - _plDanmakuController = PlDanmakuController(widget.cid); - if (mounted) { - playerController = widget.playerController; + _plDanmakuController = PlDanmakuController(widget.cid, widget.type); + playerController = widget.playerController; + if (mounted && widget.type == 'video') { if (enableShowDanmaku || playerController.isOpenDanmu.value) { _plDanmakuController.initiate( playerController.duration.value.inMilliseconds, @@ -55,13 +59,15 @@ class _PlDanmakuState extends State { ..addStatusLister(playerListener) ..addPositionListener(videoPositionListen); } - playerController.isOpenDanmu.listen((p0) { - if (p0 && !_plDanmakuController.initiated) { - _plDanmakuController.initiate( - playerController.duration.value.inMilliseconds, - playerController.position.value.inMilliseconds); - } - }); + if (widget.type == 'video') { + playerController.isOpenDanmu.listen((p0) { + if (p0 && !_plDanmakuController.initiated) { + _plDanmakuController.initiate( + playerController.duration.value.inMilliseconds, + playerController.position.value.inMilliseconds); + } + }); + } blockTypes = playerController.blockTypes; showArea = playerController.showArea; opacityVal = playerController.opacityVal; @@ -128,6 +134,7 @@ class _PlDanmakuState extends State { child: DanmakuView( createdController: (DanmakuController e) async { playerController.danmakuController = _controller = e; + widget.createdController?.call(e); }, option: DanmakuOption( fontSize: 15 * fontSizeVal, @@ -136,8 +143,7 @@ class _PlDanmakuState extends State { hideTop: blockTypes.contains(5), hideScroll: blockTypes.contains(2), hideBottom: blockTypes.contains(4), - duration: - danmakuDurationVal / playerController.playbackSpeed, + duration: danmakuDurationVal / playerController.playbackSpeed, strokeWidth: strokeWidth, // initDuration / // (danmakuSpeedVal * widget.playerController.playbackSpeed), diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index fa95ce63..6a3525e3 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -1,7 +1,9 @@ import 'dart:convert'; +import 'dart:ui'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; +import 'package:ns_danmaku/ns_danmaku.dart'; import 'package:pilipala/http/constants.dart'; import 'package:pilipala/http/init.dart'; import 'package:pilipala/http/live.dart'; @@ -37,6 +39,7 @@ class LiveRoomController extends GetxController { String token = ''; // 弹幕消息列表 RxList messageList = [].obs; + DanmakuController? danmakuController; @override void onInit() { @@ -172,9 +175,28 @@ class LiveRoomController extends GetxController { final List? liveMsg = LiveUtils.decodeMessage(message); if (liveMsg != null) { - messageList.addAll(liveMsg - .where((msg) => msg.type == LiveMessageType.chat) - .toList()); + // 过滤出聊天消息 + var chatMessages = + liveMsg.where((msg) => msg.type == LiveMessageType.chat).toList(); + + // 添加到 messageList + messageList.addAll(chatMessages); + + // 将 chatMessages 转换为 danmakuItems 列表 + List danmakuItems = chatMessages.map((e) { + return DanmakuItem( + e.message ?? '', + color: Color.fromARGB( + 255, + e.color.r, + e.color.g, + e.color.b, + ), + ); + }).toList(); + + // 添加到 danmakuController + danmakuController?.addItems(danmakuItems); } }, onErrorCb: (e) { diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index d9b316e9..d1fc6e07 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/live/message.dart'; +import 'package:pilipala/pages/danmaku/index.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; import 'controller.dart'; @@ -22,7 +23,7 @@ class LiveRoomPage extends StatefulWidget { class _LiveRoomPageState extends State with TickerProviderStateMixin { final LiveRoomController _liveRoomController = Get.put(LiveRoomController()); - PlPlayerController? plPlayerController; + late PlPlayerController plPlayerController; late Future? _futureBuilder; late Future? _futureBuilderFuture; @@ -32,6 +33,7 @@ class _LiveRoomPageState extends State final ScrollController _scrollController = ScrollController(); late AnimationController fabAnimationCtr; bool _shouldAutoScroll = true; + final int roomId = int.parse(Get.parameters['roomid']!); @override void initState() { @@ -110,8 +112,9 @@ class _LiveRoomPageState extends State future: _futureBuilderFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData && snapshot.data['status']) { + plPlayerController = _liveRoomController.plPlayerController; return PLVideoPlayer( - controller: plPlayerController!, + controller: plPlayerController, bottomControl: BottomControl( controller: plPlayerController, liveRoomCtr: _liveRoomController, @@ -122,6 +125,14 @@ class _LiveRoomPageState extends State }); }, ), + danmuWidget: PlDanmaku( + cid: roomId, + playerController: plPlayerController, + type: 'live', + createdController: (e) { + _liveRoomController.danmakuController = e; + }, + ), ); } else { return const SizedBox(); From 44910b35d97fd29c7ad30fe60f497a50872f662c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 22 Aug 2024 20:24:21 +0800 Subject: [PATCH 057/152] =?UTF-8?q?feat:=20=E7=9B=B4=E6=92=AD=E5=BC=B9?= =?UTF-8?q?=E5=B9=95=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 3 + lib/http/live.dart | 33 ++++++ lib/pages/live_room/controller.dart | 21 +++- lib/pages/live_room/view.dart | 163 +++++++++++++++------------- 4 files changed, 145 insertions(+), 75 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index 08a20382..65b74d2d 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -558,4 +558,7 @@ class Api { /// 直播间弹幕信息 static const String getDanmuInfo = '${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getDanmuInfo'; + + /// 直播间发送弹幕 + static const String sendLiveMsg = '${HttpString.liveBaseUrl}/msg/send'; } diff --git a/lib/http/live.dart b/lib/http/live.dart index a405fd58..f6fc4ea4 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -84,4 +84,37 @@ class LiveHttp { }; } } + + // 发送弹幕 + static Future sendDanmaku({roomId, msg}) async { + var res = await Request().post(Api.sendLiveMsg, queryParameters: { + 'bubble': 0, + 'msg': msg, + 'color': 16777215, // 颜色 + 'mode': 1, // 模式 + 'room_type': 0, + 'jumpfrom': 71001, // 直播间来源 + 'reply_mid': 0, + 'reply_attr': 0, + 'replay_dmid': '', + 'statistics': {"appId": 100, "platform": 5}, + 'fontsize': 25, // 字体大小 + 'rnd': DateTime.now().millisecondsSinceEpoch ~/ 1000, // 时间戳 + 'roomid': roomId, + 'csrf': await Request.getCsrf(), + 'csrf_token': await Request.getCsrf(), + }); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 6a3525e3..e8db54fb 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -1,5 +1,5 @@ import 'dart:convert'; -import 'dart:ui'; +import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; @@ -40,6 +40,8 @@ class LiveRoomController extends GetxController { // 弹幕消息列表 RxList messageList = [].obs; DanmakuController? danmakuController; + // 输入控制器 + TextEditingController inputController = TextEditingController(); @override void onInit() { @@ -222,6 +224,23 @@ class LiveRoomController extends GetxController { plSocket?.sendMessage(joinData); } + // 发送弹幕 + void sendMsg() async { + final msg = inputController.text; + if (msg.isEmpty) { + return; + } + final res = await LiveHttp.sendDanmaku( + roomId: roomId, + msg: msg, + ); + if (res['status']) { + inputController.clear(); + } else { + SmartDialog.showToast(res['msg']); + } + } + @override void onClose() { plSocket?.onClose(); diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index d1fc6e07..92ea8fd8 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -97,7 +97,7 @@ class _LiveRoomPageState extends State @override void dispose() { - plPlayerController!.dispose(); + plPlayerController.dispose(); if (floating != null) { floating!.dispose(); } @@ -238,10 +238,10 @@ class _LiveRoomPageState extends State ), ), PopScope( - canPop: plPlayerController?.isFullScreen.value != true, + canPop: plPlayerController.isFullScreen.value != true, onPopInvoked: (bool didPop) { - if (plPlayerController?.isFullScreen.value == true) { - plPlayerController!.triggerFullScreen(status: false); + if (plPlayerController.isFullScreen.value == true) { + plPlayerController.triggerFullScreen(status: false); } if (MediaQuery.of(context).orientation == Orientation.landscape) { @@ -257,17 +257,53 @@ class _LiveRoomPageState extends State child: videoPlayerPanel, ), ), - const SizedBox(height: 20), // 显示消息的列表 buildMessageListUI( context, _liveRoomController, _scrollController, ), - // 底部安全距离 - SizedBox( - height: MediaQuery.of(context).padding.bottom + 20, - ) + // 弹幕输入框 + Container( + padding: EdgeInsets.only( + left: 14, + right: 14, + top: 4, + bottom: MediaQuery.of(context).padding.bottom + 20), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + border: Border( + top: BorderSide( + color: Colors.white.withOpacity(0.1), + ), + ), + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _liveRoomController.inputController, + style: + const TextStyle(color: Colors.white, fontSize: 13), + decoration: InputDecoration( + hintText: '发送弹幕', + hintStyle: TextStyle( + color: Colors.white.withOpacity(0.6), + ), + border: InputBorder.none, + ), + ), + ), + IconButton( + onPressed: () => _liveRoomController.sendMsg(), + icon: const Icon( + Icons.send, + color: Colors.white, + ), + ), + ], + ), + ), ], ), // 定位 快速滑动到底部 @@ -324,15 +360,15 @@ Widget buildMessageListUI( removeBottom: true, child: ShaderMask( shaderCallback: (Rect bounds) { - return const LinearGradient( + return LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, - Colors.black, + Colors.black.withOpacity(0.5), Colors.black, ], - stops: [0.0, 0.1, 1.0], + stops: const [0.01, 0.05, 0.2], ).createShader(bounds); }, blendMode: BlendMode.dstIn, @@ -342,35 +378,46 @@ Widget buildMessageListUI( itemBuilder: (context, index) { final LiveMessageModel liveMsgItem = liveRoomController.messageList[index]; - return Padding( - padding: EdgeInsets.only( - top: index == 0 ? 40.0 : 4.0, - bottom: 4.0, - left: 20.0, - right: 20.0, - ), - child: Text.rich( - TextSpan( - style: const TextStyle(color: Colors.white), - children: [ - TextSpan( - text: '${liveMsgItem.userName}: ', - style: TextStyle( - color: Colors.white.withOpacity(0.6), + return Align( + alignment: Alignment.centerLeft, + child: Container( + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + margin: EdgeInsets.only( + top: index == 0 ? 20.0 : 0.0, + bottom: 6.0, + left: 14.0, + right: 14.0, + ), + padding: const EdgeInsets.symmetric( + vertical: 3.0, + horizontal: 10.0, + ), + child: Text.rich( + TextSpan( + style: const TextStyle(color: Colors.white), + children: [ + TextSpan( + text: '${liveMsgItem.userName}: ', + style: TextStyle( + color: Colors.white.withOpacity(0.6), + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // 处理点击事件 + print('Text clicked'); + }, ), - recognizer: TapGestureRecognizer() - ..onTap = () { - // 处理点击事件 - print('Text clicked'); - }, - ), - TextSpan( - children: [ - ...buildMessageTextSpan(context, liveMsgItem) - ], - // text: liveMsgItem.message, - ), - ], + TextSpan( + children: [ + ...buildMessageTextSpan(context, liveMsgItem) + ], + // text: liveMsgItem.message, + ), + ], + ), ), ), ); @@ -392,23 +439,7 @@ List buildMessageTextSpan( if (liveMsgItem.emots == null) { // 没有表情包的消息 inlineSpanList.add( - TextSpan( - text: liveMsgItem.message ?? '', - style: const TextStyle( - shadows: [ - Shadow( - offset: Offset(2.0, 2.0), - blurRadius: 3.0, - color: Colors.black, - ), - Shadow( - offset: Offset(-1.0, -1.0), - blurRadius: 3.0, - color: Colors.black, - ), - ], - ), - ), + TextSpan(text: liveMsgItem.message ?? ''), ); } else { // 有表情包的消息 使用正则匹配 表情包用图片渲染 @@ -435,23 +466,7 @@ List buildMessageTextSpan( }, onNonMatch: (String nonMatch) { inlineSpanList.add( - TextSpan( - text: nonMatch, - style: const TextStyle( - shadows: [ - Shadow( - offset: Offset(2.0, 2.0), - blurRadius: 3.0, - color: Colors.black, - ), - Shadow( - offset: Offset(-1.0, -1.0), - blurRadius: 3.0, - color: Colors.black, - ), - ], - ), - ), + TextSpan(text: nonMatch), ); return nonMatch; }, From 844db213a56d480934aecf8b786ab5028d8e2cbc Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 22 Aug 2024 23:57:11 +0800 Subject: [PATCH 058/152] =?UTF-8?q?mod:=20=E8=BE=93=E5=85=A5=E6=A1=86?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/live_room/controller.dart | 27 ++++++++++++++- lib/pages/live_room/view.dart | 52 +++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index e8db54fb..a56a5459 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -42,6 +43,8 @@ class LiveRoomController extends GetxController { DanmakuController? danmakuController; // 输入控制器 TextEditingController inputController = TextEditingController(); + // 加入直播间提示 + RxMap joinRoomTip = {'userName': '', 'message': ''}.obs; @override void onInit() { @@ -176,7 +179,29 @@ class LiveRoomController extends GetxController { onMessageCb: (message) { final List? liveMsg = LiveUtils.decodeMessage(message); - if (liveMsg != null) { + if (liveMsg != null && liveMsg.isNotEmpty) { + if (liveMsg.first.type == LiveMessageType.online) { + print('当前直播间人气:${liveMsg.first.data}'); + } else if (liveMsg.first.type == LiveMessageType.join || + liveMsg.first.type == LiveMessageType.follow) { + // 每隔一秒依次liveMsg中的每一项赋给activeUserName + int index = 0; + Timer.periodic(const Duration(seconds: 2), (timer) { + if (index < liveMsg.length) { + if (liveMsg[index].type == LiveMessageType.join || + liveMsg[index].type == LiveMessageType.follow) { + joinRoomTip.value = { + 'userName': liveMsg[index].userName, + 'message': liveMsg[index].message!, + }; + } + index++; + } else { + timer.cancel(); + } + }); + return; + } // 过滤出聊天消息 var chatMessages = liveMsg.where((msg) => msg.type == LiveMessageType.chat).toList(); diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 92ea8fd8..3f563ad6 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -179,6 +179,7 @@ class _LiveRoomPageState extends State ), ), Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ AppBar( centerTitle: false, @@ -263,6 +264,49 @@ class _LiveRoomPageState extends State _liveRoomController, _scrollController, ), + // Container( + // padding: const EdgeInsets.only( + // left: 14, right: 14, top: 4, bottom: 4), + // margin: const EdgeInsets.only( + // bottom: 6, + // left: 14, + // ), + // decoration: BoxDecoration( + // color: Colors.grey.withOpacity(0.1), + // borderRadius: const BorderRadius.all(Radius.circular(20)), + // ), + // child: Obx( + // () => AnimatedSwitcher( + // duration: const Duration(milliseconds: 300), + // transitionBuilder: + // (Widget child, Animation animation) { + // return FadeTransition(opacity: animation, child: child); + // }, + // child: Text.rich( + // key: + // ValueKey(_liveRoomController.joinRoomTip['userName']), + // TextSpan( + // style: const TextStyle(color: Colors.white), + // children: [ + // TextSpan( + // text: + // '${_liveRoomController.joinRoomTip['userName']} ', + // style: TextStyle( + // color: Colors.white.withOpacity(0.6), + // ), + // ), + // TextSpan( + // text: + // '${_liveRoomController.joinRoomTip['message']}', + // style: const TextStyle(color: Colors.white), + // ), + // ], + // ), + // ), + // ), + // ), + // ), + const SizedBox(height: 10), // 弹幕输入框 Container( padding: EdgeInsets.only( @@ -271,7 +315,8 @@ class _LiveRoomPageState extends State top: 4, bottom: MediaQuery.of(context).padding.bottom + 20), decoration: BoxDecoration( - color: Colors.black.withOpacity(0.5), + color: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(20)), border: Border( top: BorderSide( color: Colors.white.withOpacity(0.1), @@ -280,6 +325,7 @@ class _LiveRoomPageState extends State ), child: Row( children: [ + const SizedBox(width: 4), Expanded( child: TextField( controller: _liveRoomController.inputController, @@ -309,10 +355,10 @@ class _LiveRoomPageState extends State // 定位 快速滑动到底部 Positioned( right: 20, - bottom: MediaQuery.of(context).padding.bottom + 20, + bottom: MediaQuery.of(context).padding.bottom + 80, child: SlideTransition( position: Tween( - begin: const Offset(0, 2), + begin: const Offset(0, 4), end: const Offset(0, 0), ).animate(CurvedAnimation( parent: fabAnimationCtr, From 61e019f458542e8f25a14d8153044df9e14f361f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 23 Aug 2024 00:20:20 +0800 Subject: [PATCH 059/152] =?UTF-8?q?fix:=20=E5=90=88=E9=9B=86=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E8=A1=A5=E5=85=85=EF=BC=88=E7=9B=B4=E6=92=AD=E5=9B=9E?= =?UTF-8?q?=E6=94=BE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member/widgets/seasons.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pages/member/widgets/seasons.dart b/lib/pages/member/widgets/seasons.dart index 1749ff45..615fc44c 100644 --- a/lib/pages/member/widgets/seasons.dart +++ b/lib/pages/member/widgets/seasons.dart @@ -35,7 +35,8 @@ class MemberSeasonsPanel extends StatelessWidget { 'seasonName': item.meta!.name!, }; } - if (category == 1) { + // 2为直播回放 + if (category == 1 || category == 2) { parameters = { 'category': '1', 'mid': item.meta!.mid.toString(), From fc22834e4a66e4d3fd803c3ec0455f13203bfcde Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 23 Aug 2024 23:47:15 +0800 Subject: [PATCH 060/152] =?UTF-8?q?mod:=20=E7=9B=B4=E6=92=AD=E5=BC=B9?= =?UTF-8?q?=E5=B9=95=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/danmaku/view.dart | 2 +- lib/pages/live_room/controller.dart | 12 ++- lib/pages/live_room/view.dart | 146 ++++++++++++++++++---------- 3 files changed, 105 insertions(+), 55 deletions(-) diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart index e669b881..3cf1ed8a 100644 --- a/lib/pages/danmaku/view.dart +++ b/lib/pages/danmaku/view.dart @@ -129,7 +129,7 @@ class _PlDanmakuState extends State { // double initDuration = box.maxWidth / 12; return Obx( () => AnimatedOpacity( - opacity: playerController.isOpenDanmu.value ? 1 : 1, + opacity: playerController.isOpenDanmu.value ? 1 : 0, duration: const Duration(milliseconds: 100), child: DanmakuView( createdController: (DanmakuController e) async { diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index a56a5459..99025dce 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -45,6 +45,8 @@ class LiveRoomController extends GetxController { TextEditingController inputController = TextEditingController(); // 加入直播间提示 RxMap joinRoomTip = {'userName': '', 'message': ''}.obs; + // 直播间弹幕开关 默认打开 + RxBool danmakuSwitch = true.obs; @override void onInit() { @@ -69,6 +71,9 @@ class LiveRoomController extends GetxController { userId = userInfo.mid; } liveDanmakuInfo().then((value) => initSocket()); + danmakuSwitch.listen((p0) { + plPlayerController.isOpenDanmu.value = p0; + }); } playerInit(source) async { @@ -87,6 +92,7 @@ class LiveRoomController extends GetxController { enableHA: true, autoplay: true, ); + plPlayerController.isOpenDanmu.value = danmakuSwitch.value; } Future queryLiveInfo() async { @@ -185,6 +191,7 @@ class LiveRoomController extends GetxController { } else if (liveMsg.first.type == LiveMessageType.join || liveMsg.first.type == LiveMessageType.follow) { // 每隔一秒依次liveMsg中的每一项赋给activeUserName + int index = 0; Timer.periodic(const Duration(seconds: 2), (timer) { if (index < liveMsg.length) { @@ -200,6 +207,7 @@ class LiveRoomController extends GetxController { timer.cancel(); } }); + return; } // 过滤出聊天消息 @@ -223,7 +231,9 @@ class LiveRoomController extends GetxController { }).toList(); // 添加到 danmakuController - danmakuController?.addItems(danmakuItems); + if (danmakuSwitch.value) { + danmakuController?.addItems(danmakuItems); + } } }, onErrorCb: (e) { diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 3f563ad6..abbcce13 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -325,7 +325,33 @@ class _LiveRoomPageState extends State ), child: Row( children: [ - const SizedBox(width: 4), + SizedBox( + width: 34, + height: 34, + child: Obx( + () => IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + backgroundColor: MaterialStateProperty.resolveWith( + (Set states) { + return Colors.grey.withOpacity(0.1); + }), + ), + onPressed: () { + _liveRoomController.danmakuSwitch.value = + !_liveRoomController.danmakuSwitch.value; + }, + icon: Icon( + _liveRoomController.danmakuSwitch.value + ? Icons.subtitles_outlined + : Icons.subtitles_off_outlined, + size: 19, + color: Colors.white, + ), + ), + ), + ), + const SizedBox(width: 8), Expanded( child: TextField( controller: _liveRoomController.inputController, @@ -340,11 +366,19 @@ class _LiveRoomPageState extends State ), ), ), - IconButton( - onPressed: () => _liveRoomController.sendMsg(), - icon: const Icon( - Icons.send, - color: Colors.white, + SizedBox( + width: 34, + height: 34, + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => _liveRoomController.sendMsg(), + icon: const Icon( + Icons.send, + color: Colors.white, + size: 20, + ), ), ), ], @@ -418,56 +452,62 @@ Widget buildMessageListUI( ).createShader(bounds); }, blendMode: BlendMode.dstIn, - child: ListView.builder( - controller: scrollController, - itemCount: liveRoomController.messageList.length, - itemBuilder: (context, index) { - final LiveMessageModel liveMsgItem = - liveRoomController.messageList[index]; - return Align( - alignment: Alignment.centerLeft, - child: Container( - decoration: BoxDecoration( - color: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - margin: EdgeInsets.only( - top: index == 0 ? 20.0 : 0.0, - bottom: 6.0, - left: 14.0, - right: 14.0, - ), - padding: const EdgeInsets.symmetric( - vertical: 3.0, - horizontal: 10.0, - ), - child: Text.rich( - TextSpan( - style: const TextStyle(color: Colors.white), - children: [ - TextSpan( - text: '${liveMsgItem.userName}: ', - style: TextStyle( - color: Colors.white.withOpacity(0.6), + child: GestureDetector( + onTap: () { + // 键盘失去焦点 + FocusScope.of(context).requestFocus(FocusNode()); + }, + child: ListView.builder( + controller: scrollController, + itemCount: liveRoomController.messageList.length, + itemBuilder: (context, index) { + final LiveMessageModel liveMsgItem = + liveRoomController.messageList[index]; + return Align( + alignment: Alignment.centerLeft, + child: Container( + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + margin: EdgeInsets.only( + top: index == 0 ? 20.0 : 0.0, + bottom: 6.0, + left: 14.0, + right: 14.0, + ), + padding: const EdgeInsets.symmetric( + vertical: 3.0, + horizontal: 10.0, + ), + child: Text.rich( + TextSpan( + style: const TextStyle(color: Colors.white), + children: [ + TextSpan( + text: '${liveMsgItem.userName}: ', + style: TextStyle( + color: Colors.white.withOpacity(0.6), + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // 处理点击事件 + print('Text clicked'); + }, ), - recognizer: TapGestureRecognizer() - ..onTap = () { - // 处理点击事件 - print('Text clicked'); - }, - ), - TextSpan( - children: [ - ...buildMessageTextSpan(context, liveMsgItem) - ], - // text: liveMsgItem.message, - ), - ], + TextSpan( + children: [ + ...buildMessageTextSpan(context, liveMsgItem) + ], + // text: liveMsgItem.message, + ), + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ), ), From 15e1cb5d4763a3b63b4c44e861b1dd79df7e6c6e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 24 Aug 2024 00:07:08 +0800 Subject: [PATCH 061/152] =?UTF-8?q?feat:=20=E7=A7=81=E4=BF=A1=E5=8F=AF?= =?UTF-8?q?=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../whisper_detail/widget/chat_item.dart | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index 77e38073..94347aff 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -84,7 +84,7 @@ class ChatItem extends StatelessWidget { emojiMap[e['text']] = e['url']; } text.splitMapJoin( - RegExp(r"\[.+?\]"), + RegExp(r"\[[^\[\]]+\]"), onMatch: (Match match) { final String emojiKey = match[0]!; if (emojiMap.containsKey(emojiKey)) { @@ -95,6 +95,17 @@ class ChatItem extends StatelessWidget { src: emojiMap[emojiKey]!, ), )); + } else { + children.add( + TextSpan( + text: emojiKey, + style: TextStyle( + color: textColor(context), + letterSpacing: 0.6, + height: 1.5, + ), + ), + ); } return ''; }, @@ -109,13 +120,13 @@ class ChatItem extends StatelessWidget { return ''; }, ); - return RichText( - text: TextSpan( + return SelectableText.rich( + TextSpan( children: children, ), ); } else { - return Text( + return SelectableText( text, style: TextStyle( letterSpacing: 0.6, @@ -133,7 +144,7 @@ class ChatItem extends StatelessWidget { case MsgType.pic_card: return SystemNotice2(item: item); case MsgType.notify_text: - return Text( + return SelectableText( jsonDecode(content['content']) .map((m) => m['text'] as String) .join("\n"), @@ -530,7 +541,7 @@ class SystemNotice extends StatelessWidget { Divider( color: Theme.of(context).colorScheme.primary.withOpacity(0.05), ), - Text( + SelectableText( content['text'], ) ], From b96f35bae2f2f3e1458a0fdb66e1976fddc02f91 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 24 Aug 2024 00:28:07 +0800 Subject: [PATCH 062/152] =?UTF-8?q?mod:=20=E7=A7=BB=E9=99=A4up=E4=B8=BB?= =?UTF-8?q?=E9=A1=B5=E5=88=86=E5=89=B2=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member/view.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index c721d638..7d2e4bed 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -168,7 +168,6 @@ class _MemberPageState extends State const Icon(Icons.arrow_forward_outlined, size: 19), ), ), - const Divider(height: 1, thickness: 0.1), /// 视频 Obx(() => ListTile( @@ -178,7 +177,6 @@ class _MemberPageState extends State trailing: const Icon(Icons.arrow_forward_outlined, size: 19), )), - const Divider(height: 1, thickness: 0.1), /// 他的收藏夹 Obx(() => ListTile( @@ -188,13 +186,11 @@ class _MemberPageState extends State trailing: const Icon(Icons.arrow_forward_outlined, size: 19), )), - const Divider(height: 1, thickness: 0.1), /// 专栏 Obx(() => ListTile( title: Text( '${_memberController.isOwner.value ? '我' : 'Ta'}的专栏'))), - const Divider(height: 1, thickness: 0.1), /// 合集 Obx(() => ListTile( From 8810a74ebfcfa4cc9e52226089f0e6c79a70a399 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 24 Aug 2024 23:33:19 +0800 Subject: [PATCH 063/152] =?UTF-8?q?fix:=20=E6=9C=AA=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E6=97=B6=E8=BF=94=E5=9B=9E=E5=BD=93=E5=89=8D=E9=A1=B5=E5=BC=82?= =?UTF-8?q?=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/view.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 0388e962..91c5ae00 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -63,7 +63,7 @@ class _VideoDetailPageState extends State 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 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 plPlayerController!.pause(); vdCtr.clearSubtitleContent(); } - setState(() => isShowing = false); + isShowing.value = false; super.didPushNext(); } @@ -272,10 +273,8 @@ class _VideoDetailPageState extends State if (plPlayerController != null && plPlayerController!.videoPlayerController != null) { - setState(() { - vdCtr.setSubtitleContent(); - isShowing = true; - }); + vdCtr.setSubtitleContent(); + isShowing.value = true; } vdCtr.isFirstTime = false; final bool autoplay = autoPlayEnable; @@ -652,7 +651,11 @@ class _VideoDetailPageState extends State tag: heroTag, child: Stack( children: [ - if (isShowing) buildVideoPlayerPanel(), + Obx( + () => isShowing.value + ? buildVideoPlayerPanel() + : const SizedBox(), + ), /// 关闭自动播放时 手动播放 Obx( From 8fd3bfae5f9a88a8ce72670b2499f035e51b4f0e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 25 Aug 2024 22:07:42 +0800 Subject: [PATCH 064/152] =?UTF-8?q?fix:=20=E7=B3=BB=E7=BB=9F=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E6=A0=87=E8=AE=B0=E5=B7=B2=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 4 ++++ lib/http/msg.dart | 20 +++++++++++++++++++- lib/pages/message/system/controller.dart | 14 +++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index 31e5a38b..d9286e47 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -554,4 +554,8 @@ class Api { /// 系统通知 static const String messageSystemAPi = '${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify'; + + /// 系统通知标记已读 + static const String systemMarkRead = + '${HttpString.messageBaseUrl}/x/sys-msg/update_cursor'; } diff --git a/lib/http/msg.dart b/lib/http/msg.dart index 86789fd1..2de9cd49 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -298,7 +298,6 @@ class MsgHttp { }); if (res.data['code'] == 0) { try { - print(res.data['data']['system_notify_list']); return { 'status': true, 'data': res.data['data']['system_notify_list'] @@ -312,4 +311,23 @@ class MsgHttp { return {'status': false, 'date': [], 'msg': res.data['message']}; } } + + // 系统消息标记已读 + static Future systemMarkRead(int cursor) async { + String csrf = await Request.getCsrf(); + var res = await Request().get(Api.systemMarkRead, data: { + 'csrf': csrf, + 'cursor': cursor, + }); + if (res.data['code'] == 0) { + return { + 'status': true, + }; + } else { + return { + 'status': false, + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/pages/message/system/controller.dart b/lib/pages/message/system/controller.dart index bf31f6bc..f63a659a 100644 --- a/lib/pages/message/system/controller.dart +++ b/lib/pages/message/system/controller.dart @@ -8,8 +8,20 @@ class MessageSystemController extends GetxController { Future queryMessageSystem({String type = 'init'}) async { var res = await MsgHttp.messageSystem(); if (res['status']) { - systemItems.addAll(res['data']); + if (type == 'init') { + systemItems.value = res['data']; + } else { + systemItems.addAll(res['data']); + } + if (systemItems.isNotEmpty) { + systemMarkRead(systemItems.first.cursor!); + } } return res; } + + // 标记已读 + void systemMarkRead(int cursor) async { + await MsgHttp.systemMarkRead(cursor); + } } From d85e9446e3d634ad5c1cb3eecec45301a10a73e8 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 26 Aug 2024 00:12:35 +0800 Subject: [PATCH 065/152] opt: fav follow bottomSheet --- lib/pages/follow/widgets/follow_item.dart | 19 +++++-- .../video/detail/introduction/controller.dart | 19 +++++-- lib/pages/video/detail/introduction/view.dart | 49 ++++++++++--------- .../introduction/widgets/fav_panel.dart | 43 +++++++++------- .../introduction/widgets/group_panel.dart | 16 ++++-- pubspec.lock | 16 ++++++ pubspec.yaml | 1 + 7 files changed, 111 insertions(+), 52 deletions(-) diff --git a/lib/pages/follow/widgets/follow_item.dart b/lib/pages/follow/widgets/follow_item.dart index d21a89bc..3d393277 100644 --- a/lib/pages/follow/widgets/follow_item.dart +++ b/lib/pages/follow/widgets/follow_item.dart @@ -1,3 +1,4 @@ +import 'package:bottom_sheet/bottom_sheet.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; @@ -47,9 +48,21 @@ class FollowItem extends StatelessWidget { height: 34, child: TextButton( onPressed: () async { - await Get.bottomSheet( - GroupPanel(mid: item.mid!), - isScrollControlled: true, + await showFlexibleBottomSheet( + bottomSheetColor: Colors.transparent, + minHeight: 1, + initHeight: 1, + maxHeight: 1, + context: Get.context!, + builder: (BuildContext context, + ScrollController scrollController, double offset) { + return GroupPanel( + mid: item.mid!, + scrollController: scrollController, + ); + }, + anchors: [1], + isSafeArea: true, ); }, style: TextButton.styleFrom( diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 50aac4cd..d564aba4 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -1,5 +1,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:get/get.dart'; @@ -523,9 +524,21 @@ class VideoIntroController extends GetxController { // 设置关注分组 void setFollowGroup() { - Get.bottomSheet( - GroupPanel(mid: videoDetail.value.owner!.mid!), - isScrollControlled: true, + showFlexibleBottomSheet( + bottomSheetColor: Colors.transparent, + minHeight: 0.6, + initHeight: 0.6, + maxHeight: 1, + context: Get.context!, + builder: (BuildContext context, ScrollController scrollController, + double offset) { + return GroupPanel( + mid: videoDetail.value.owner!.mid!, + scrollController: scrollController, + ); + }, + anchors: [0.6, 1], + isSafeArea: true, ); } diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 760976ae..a89b4d28 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -1,3 +1,6 @@ +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'; @@ -215,37 +218,35 @@ class _VideoInfoState extends State with TickerProviderStateMixin { if (!videoIntroController.hasFav.value) { videoIntroController.actionFavVideo(type: 'default'); } else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - isScrollControlled: true, - builder: (BuildContext context) { - return FavPanel(ctr: videoIntroController); - }, - ); + _showFavPanel(); } } else { - showModalBottomSheet( - context: context, - useRootNavigator: true, - isScrollControlled: true, - builder: (BuildContext context) { - return FavPanel(ctr: videoIntroController); - }, - ); + _showFavPanel(); } } else if (type != 'longPress') { - showModalBottomSheet( - context: context, - useRootNavigator: true, - isScrollControlled: true, - builder: (BuildContext context) { - return FavPanel(ctr: videoIntroController); - }, - ); + _showFavPanel(); } } + void _showFavPanel() { + showFlexibleBottomSheet( + bottomSheetColor: Colors.transparent, + minHeight: 0.6, + initHeight: 0.6, + maxHeight: 1, + context: context, + builder: (BuildContext context, ScrollController scrollController, + double offset) { + return FavPanel( + ctr: videoIntroController, + scrollController: scrollController, + ); + }, + anchors: [0.6, 1], + isSafeArea: true, + ); + } + // 视频介绍 showIntroDetail() { feedBack(); diff --git a/lib/pages/video/detail/introduction/widgets/fav_panel.dart b/lib/pages/video/detail/introduction/widgets/fav_panel.dart index 5ef78967..a4e98df7 100644 --- a/lib/pages/video/detail/introduction/widgets/fav_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/fav_panel.dart @@ -6,8 +6,9 @@ import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; class FavPanel extends StatefulWidget { - const FavPanel({super.key, this.ctr}); + const FavPanel({super.key, this.ctr, this.scrollController}); final dynamic ctr; + final ScrollController? scrollController; @override State createState() => _FavPanelState(); @@ -15,31 +16,39 @@ class FavPanel extends StatefulWidget { class _FavPanelState extends State { final Box localCache = GStrorage.localCache; - late double sheetHeight; late Future _futureBuilderFuture; @override void initState() { super.initState(); - sheetHeight = localCache.get('sheetHeight'); _futureBuilderFuture = widget.ctr!.queryVideoInFolder(); } @override Widget build(BuildContext context) { return Container( - height: sheetHeight, - color: Theme.of(context).colorScheme.surface, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), child: Column( children: [ AppBar( centerTitle: false, elevation: 0, - leading: IconButton( - onPressed: () => Get.back(), - icon: const Icon(Icons.close_outlined)), - title: - Text('添加到收藏夹', style: Theme.of(context).textTheme.titleMedium), + automaticallyImplyLeading: false, + leadingWidth: 0, + title: Text( + '选择收藏夹', + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(fontWeight: FontWeight.bold), + ), ), Expanded( child: Material( @@ -51,22 +60,22 @@ class _FavPanelState extends State { if (data['status']) { return Obx( () => ListView.builder( + controller: widget.scrollController, itemCount: widget.ctr!.favFolderData.value.list!.length, itemBuilder: (context, index) { + final item = + widget.ctr!.favFolderData.value.list![index]; return ListTile( - onTap: () => widget.ctr!.onChoose( - widget.ctr!.favFolderData.value.list![index] - .favState != - 1, - index), + onTap: () => widget.ctr! + .onChoose(item.favState != 1, index), dense: true, leading: const Icon(Icons.folder_outlined), minLeadingWidth: 0, title: Text(widget.ctr!.favFolderData.value .list![index].title!), subtitle: Text( - '${widget.ctr!.favFolderData.value.list![index].mediaCount}个内容', + '${item.mediaCount}个内容 ', ), trailing: Transform.scale( scale: 0.9, @@ -132,7 +141,7 @@ class _FavPanelState extends State { backgroundColor: Theme.of(context).colorScheme.primary, // 设置按钮背景色 ), - child: const Text('完成'), + child: const Text('确认选择'), ), ], ), diff --git a/lib/pages/video/detail/introduction/widgets/group_panel.dart b/lib/pages/video/detail/introduction/widgets/group_panel.dart index dcdaf9c5..4bb0980c 100644 --- a/lib/pages/video/detail/introduction/widgets/group_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/group_panel.dart @@ -10,7 +10,8 @@ import 'package:pilipala/utils/storage.dart'; class GroupPanel extends StatefulWidget { final int? mid; - const GroupPanel({super.key, this.mid}); + final ScrollController scrollController; + const GroupPanel({super.key, this.mid, required this.scrollController}); @override State createState() => _GroupPanelState(); @@ -18,7 +19,6 @@ class GroupPanel extends StatefulWidget { class _GroupPanelState extends State { final Box localCache = GStrorage.localCache; - late double sheetHeight; late Future _futureBuilderFuture; late List tagsList; bool showDefault = true; @@ -26,7 +26,6 @@ class _GroupPanelState extends State { @override void initState() { super.initState(); - sheetHeight = localCache.get('sheetHeight'); _futureBuilderFuture = MemberHttp.followUpTags(); } @@ -56,8 +55,14 @@ class _GroupPanelState extends State { @override Widget build(BuildContext context) { return Container( - height: sheetHeight, - color: Theme.of(context).colorScheme.surface, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), child: Column( children: [ AppBar( @@ -79,6 +84,7 @@ class _GroupPanelState extends State { if (data['status']) { tagsList = data['data']; return ListView.builder( + controller: widget.scrollController, itemCount: data['data'].length, itemBuilder: (context, index) { return ListTile( diff --git a/pubspec.lock b/pubspec.lock index a46127f9..a42df7bb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,6 +113,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" + bottom_inset_observer: + dependency: transitive + description: + name: bottom_inset_observer + sha256: cbfb01e0e07cc4922052701786d5e607765a6f54e1844f41061abf8744519a7d + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.0" + bottom_sheet: + dependency: "direct main" + description: + name: bottom_sheet + sha256: efd28f52357d23e1c01eaeb45466b407f1e29318305bd6d10baf814fda18bd7e + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.4" build: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b5d45714..fb4cc2c0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -146,6 +146,7 @@ dependencies: lottie: ^3.1.2 # 二维码 qr_flutter: ^4.1.0 + bottom_sheet: ^4.0.4 dev_dependencies: flutter_test: From f598b6adadf00c0d6f2f7a3d0e088f9faabd952d Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 26 Aug 2024 23:14:37 +0800 Subject: [PATCH 066/152] opt: liveRoom getBuvid --- lib/http/init.dart | 27 +++++++++++++++++++++++++-- lib/pages/live_room/controller.dart | 4 +++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/http/init.dart b/lib/http/init.dart index faa57dd5..6a90a87d 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -29,7 +29,7 @@ class Request { late String systemProxyPort; static final RegExp spmPrefixExp = RegExp(r''); - static late String buvid; + static String? buvid; /// 设置cookie static setCookie() async { @@ -72,7 +72,6 @@ class Request { .map((Cookie cookie) => '${cookie.name}=${cookie.value}') .join('; '); - buvid = cookie.firstWhere((e) => e.name == 'buvid3').value; dio.options.headers['cookie'] = cookieString; } @@ -87,6 +86,30 @@ class Request { return token; } + static Future getBuvid() async { + if (buvid != null) { + return buvid!; + } + + final List cookies = await cookieManager.cookieJar + .loadForRequest(Uri.parse(HttpString.baseUrl)); + buvid = cookies.firstWhere((cookie) => cookie.name == 'buvid3').value; + if (buvid == null) { + try { + var result = await Request().get( + "${HttpString.apiBaseUrl}/x/frontend/finger/spi", + ); + buvid = result["data"]["b_3"].toString(); + } catch (e) { + // 处理请求错误 + buvid = ''; + print("Error fetching buvid: $e"); + } + } + + return buvid!; + } + static setOptionsHeaders(userInfo, bool status) { if (status) { dio.options.headers['x-bili-mid'] = userInfo.mid.toString(); diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 99025dce..5d4e2b67 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -47,6 +47,7 @@ class LiveRoomController extends GetxController { RxMap joinRoomTip = {'userName': '', 'message': ''}.obs; // 直播间弹幕开关 默认打开 RxBool danmakuSwitch = true.obs; + late String buvid; @override void onInit() { @@ -63,6 +64,7 @@ class LiveRoomController extends GetxController { if (liveItem != null && liveItem.cover != null && liveItem.cover != '') { cover = liveItem.cover; } + Request.getBuvid().then((value) => buvid = value); } // CDN优化 enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); @@ -249,7 +251,7 @@ class LiveRoomController extends GetxController { "uid": userId, "roomid": roomId, "protover": 3, - "buvid": Request.buvid, + "buvid": buvid, "platform": "web", "type": 2, "key": token, From 2e329553d914d4203e776d27c48194fc32fb2fdd Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 26 Aug 2024 23:23:43 +0800 Subject: [PATCH 067/152] =?UTF-8?q?opt:=20=E6=95=B0=E6=8D=AE=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/network_img_layer.dart | 4 +- lib/main.dart | 8 +- lib/pages/main/view.dart | 5 +- lib/pages/setting/pages/action_menu_set.dart | 4 +- lib/pages/setting/pages/play_gesture_set.dart | 6 +- lib/pages/setting/play_setting.dart | 4 +- lib/pages/setting/style_setting.dart | 4 +- lib/pages/video/detail/introduction/view.dart | 4 +- lib/plugin/pl_player/controller.dart | 58 +++------- lib/plugin/pl_player/view.dart | 6 +- lib/utils/global_data.dart | 24 ----- lib/utils/global_data_cache.dart | 100 ++++++++++++++++++ lib/utils/storage.dart | 9 -- 13 files changed, 137 insertions(+), 99 deletions(-) delete mode 100644 lib/utils/global_data.dart create mode 100644 lib/utils/global_data_cache.dart diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart index d2772478..0b715a89 100644 --- a/lib/common/widgets/network_img_layer.dart +++ b/lib/common/widgets/network_img_layer.dart @@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/utils/extension.dart'; -import 'package:pilipala/utils/global_data.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import '../../utils/storage.dart'; import '../constants.dart'; @@ -33,7 +33,7 @@ class NetworkImgLayer extends StatelessWidget { @override Widget build(BuildContext context) { - final int defaultImgQuality = GlobalData().imgQuality; + final int defaultImgQuality = GlobalDataCache().imgQuality; if (src == '' || src == null) { return placeholder(context); } diff --git a/lib/main.dart b/lib/main.dart index 05c0476c..78747279 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,7 +20,7 @@ import 'package:pilipala/pages/main/view.dart'; import 'package:pilipala/services/service_locator.dart'; import 'package:pilipala/utils/app_scheme.dart'; import 'package:pilipala/utils/data.dart'; -import 'package:pilipala/utils/global_data.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:media_kit/media_kit.dart'; import 'package:pilipala/utils/recommend_filter.dart'; @@ -33,7 +33,6 @@ void main() async { await SystemChrome.setPreferredOrientations( [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); await GStrorage.init(); - await setupServiceLocator(); clearLogs(); Request(); await Request.setCookie(); @@ -266,10 +265,11 @@ class BuildMainApp extends StatelessWidget { VideoDetailPage.routeObserver, SearchPage.routeObserver, ], - onInit: () { + onReady: () async { RecommendFilter(); Data.init(); - GlobalData(); + await GlobalDataCache().initialize(); + setupServiceLocator(); }, ); } diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 00eafa0d..3da667e8 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -10,7 +10,6 @@ import 'package:pilipala/pages/media/index.dart'; import 'package:pilipala/pages/rank/index.dart'; import 'package:pilipala/utils/event_bus.dart'; import 'package:pilipala/utils/feed_back.dart'; -import 'package:pilipala/utils/global_data.dart'; import 'package:pilipala/utils/storage.dart'; import './controller.dart'; @@ -30,6 +29,7 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { int? _lastSelectTime; //上次点击时间 Box setting = GStrorage.setting; + late bool enableMYBar; @override void initState() { @@ -37,6 +37,7 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { _lastSelectTime = DateTime.now().millisecondsSinceEpoch; _mainController.pageController = PageController(initialPage: _mainController.selectedIndex); + enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true); } void setIndex(int value) async { @@ -171,7 +172,7 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { curve: Curves.easeInOutCubicEmphasized, duration: const Duration(milliseconds: 500), offset: Offset(0, snapshot.data ? 0 : 1), - child: GlobalData().enableMYBar + child: enableMYBar ? Obx( () => NavigationBar( onDestinationSelected: (value) => setIndex(value), diff --git a/lib/pages/setting/pages/action_menu_set.dart b/lib/pages/setting/pages/action_menu_set.dart index 7a4fd9ba..3655df73 100644 --- a/lib/pages/setting/pages/action_menu_set.dart +++ b/lib/pages/setting/pages/action_menu_set.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/models/common/action_type.dart'; -import 'package:pilipala/utils/global_data.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import '../../../utils/storage.dart'; class ActionMenuSetPage extends StatefulWidget { @@ -38,7 +38,7 @@ class _ActionMenuSetPageState extends State { .map((i) => (i['value'] as ActionType).value) .toList(); setting.put(SettingBoxKey.actionTypeSort, sortedTabbar); - GlobalData().actionTypeSort = sortedTabbar; + GlobalDataCache().actionTypeSort = sortedTabbar; SmartDialog.showToast('操作成功'); } diff --git a/lib/pages/setting/pages/play_gesture_set.dart b/lib/pages/setting/pages/play_gesture_set.dart index f688c43c..e671bfb2 100644 --- a/lib/pages/setting/pages/play_gesture_set.dart +++ b/lib/pages/setting/pages/play_gesture_set.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; -import 'package:pilipala/utils/global_data.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import '../../../models/common/gesture_mode.dart'; import '../../../utils/storage.dart'; @@ -64,11 +64,11 @@ class _PlayGesturePageState extends State { }, ); if (result != null) { - GlobalData().fullScreenGestureMode = FullScreenGestureMode + GlobalDataCache().fullScreenGestureMode = FullScreenGestureMode .values .firstWhere((element) => element.values == result); fullScreenGestureMode = - GlobalData().fullScreenGestureMode.index; + GlobalDataCache().fullScreenGestureMode.index; setting.put( SettingBoxKey.fullScreenGestureMode, fullScreenGestureMode); setState(() {}); diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index cb8a3996..a3c75ab5 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -9,7 +9,7 @@ import 'package:pilipala/models/video/play/quality.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/services/service_locator.dart'; -import 'package:pilipala/utils/global_data.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/storage.dart'; import '../../models/live/quality.dart'; @@ -162,7 +162,7 @@ class _PlaySettingState extends State { setKey: SettingBoxKey.enablePlayerControlAnimation, defaultVal: true, callFn: (bool val) { - GlobalData().enablePlayerControlAnimation = val; + GlobalDataCache().enablePlayerControlAnimation = val; }), SetSwitchItem( title: '港澳台模式', diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 5fca0c86..544dd4e8 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -8,7 +8,7 @@ import 'package:pilipala/models/common/theme_type.dart'; import 'package:pilipala/pages/setting/pages/color_select.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; import 'package:pilipala/pages/setting/widgets/slide_dialog.dart'; -import 'package:pilipala/utils/global_data.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/storage.dart'; import '../../models/common/dynamic_badge_mode.dart'; @@ -176,7 +176,7 @@ class _StyleSettingState extends State { SettingBoxKey.defaultPicQa, picQuality); Get.back(); settingController.picQuality.value = picQuality; - GlobalData().imgQuality = picQuality; + GlobalDataCache().imgQuality = picQuality; SmartDialog.showToast('设置成功'); }, child: const Text('确定'), diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index a89b4d28..4a8feaf2 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -20,7 +20,7 @@ import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/pages/video/detail/introduction/controller.dart'; import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart'; import 'package:pilipala/utils/feed_back.dart'; -import 'package:pilipala/utils/global_data.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; import '../../../../http/user.dart'; @@ -569,7 +569,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { } Widget actionGrid(BuildContext context, videoIntroController) { - final actionTypeSort = GlobalData().actionTypeSort; + final actionTypeSort = GlobalDataCache().actionTypeSort; Widget progressWidget(progress) { return SizedBox( diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index a5de33fd..8dff954a 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -18,6 +18,7 @@ import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/services/service_locator.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:screen_brightness/screen_brightness.dart'; import 'package:status_bar_control/status_bar_control.dart'; @@ -277,50 +278,19 @@ class PlPlayerController { // 添加一个私有构造函数 PlPlayerController._internal(this.videoType) { - isOpenDanmu.value = - setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false); - blockTypes = - localCache.get(LocalCacheKey.danmakuBlockType, defaultValue: []); - showArea = localCache.get(LocalCacheKey.danmakuShowArea, defaultValue: 0.5); - // 不透明度 - opacityVal = - localCache.get(LocalCacheKey.danmakuOpacity, defaultValue: 1.0); - // 字体大小 - fontSizeVal = - localCache.get(LocalCacheKey.danmakuFontScale, defaultValue: 1.0); - // 弹幕时间 - danmakuDurationVal = - localCache.get(LocalCacheKey.danmakuDuration, defaultValue: 4.0); - // 描边粗细 - strokeWidth = localCache.get(LocalCacheKey.strokeWidth, defaultValue: 1.5); - playRepeat = PlayRepeat.values.toList().firstWhere( - (e) => - e.value == - videoStorage.get(VideoBoxKey.playRepeat, - defaultValue: PlayRepeat.pause.value), - ); - _playbackSpeed.value = - videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0); - enableAutoLongPressSpeed = setting - .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); - if (!enableAutoLongPressSpeed) { - _longPressSpeed.value = videoStorage - .get(VideoBoxKey.longPressSpeedDefault, defaultValue: 2.0); - } - // 自定义倍速集合 - speedsList = List.from(videoStorage - .get(VideoBoxKey.customSpeedsList, defaultValue: [])); - // 默认倍速 - speedsList = List.from(videoStorage - .get(VideoBoxKey.customSpeedsList, defaultValue: [])); - //playSpeedSystem - final List playSpeedSystem = - videoStorage.get(VideoBoxKey.playSpeedSystem, defaultValue: playSpeed); - - // for (final PlaySpeed i in PlaySpeed.values) { - speedsList.addAll(playSpeedSystem); - // } - + final cache = GlobalDataCache(); + isOpenDanmu.value = cache.isOpenDanmu; + blockTypes = cache.blockTypes; + showArea = cache.showArea; + opacityVal = cache.opacityVal; + fontSizeVal = cache.fontSizeVal; + danmakuDurationVal = cache.danmakuDurationVal; + strokeWidth = cache.strokeWidth; + playRepeat = cache.playRepeat; + _playbackSpeed.value = cache.playbackSpeed; + enableAutoLongPressSpeed = cache.enableAutoLongPressSpeed; + _longPressSpeed.value = cache.longPressSpeed; + speedsList = cache.speedsList; // _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) { // if (status == PlayerStatus.playing) { // WakelockPlus.enable(); diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index f3e0946b..e3e14caf 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -19,7 +19,7 @@ import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:screen_brightness/screen_brightness.dart'; -import '../../utils/global_data.dart'; +import '../../utils/global_data_cache.dart'; import 'models/bottom_control_type.dart'; import 'models/bottom_progress_behavior.dart'; import 'widgets/app_bar_ani.dart'; @@ -87,7 +87,7 @@ class _PLVideoPlayerState extends State late bool enableBackgroundPlay; late double screenWidth; final FullScreenGestureMode fullScreenGestureMode = - GlobalData().fullScreenGestureMode; + GlobalDataCache().fullScreenGestureMode; // 用于记录上一次全屏切换手势触发时间,避免误触 DateTime? lastFullScreenToggleTime; @@ -132,7 +132,7 @@ class _PLVideoPlayerState extends State screenWidth = Get.size.width; animationController = AnimationController( vsync: this, - duration: GlobalData().enablePlayerControlAnimation + duration: GlobalDataCache().enablePlayerControlAnimation ? const Duration(milliseconds: 150) : const Duration(milliseconds: 10), ); diff --git a/lib/utils/global_data.dart b/lib/utils/global_data.dart deleted file mode 100644 index 97bff5a5..00000000 --- a/lib/utils/global_data.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:hive/hive.dart'; -import 'package:pilipala/utils/storage.dart'; -import '../models/common/index.dart'; - -Box setting = GStrorage.setting; - -class GlobalData { - int imgQuality = 10; - FullScreenGestureMode fullScreenGestureMode = - FullScreenGestureMode.values.last; - bool enablePlayerControlAnimation = true; - final bool enableMYBar = - setting.get(SettingBoxKey.enableMYBar, defaultValue: true); - List actionTypeSort = setting.get(SettingBoxKey.actionTypeSort, - defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']); - // 私有构造函数 - GlobalData._(); - - // 单例实例 - static final GlobalData _instance = GlobalData._(); - - // 获取全局实例 - factory GlobalData() => _instance; -} diff --git a/lib/utils/global_data_cache.dart b/lib/utils/global_data_cache.dart new file mode 100644 index 00000000..c5884e79 --- /dev/null +++ b/lib/utils/global_data_cache.dart @@ -0,0 +1,100 @@ +import 'package:hive/hive.dart'; +import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; +import 'package:pilipala/plugin/pl_player/models/play_speed.dart'; +import 'package:pilipala/utils/storage.dart'; +import '../models/common/index.dart'; + +Box setting = GStrorage.setting; +Box localCache = GStrorage.localCache; +Box videoStorage = GStrorage.video; + +class GlobalDataCache { + late int imgQuality; + late FullScreenGestureMode fullScreenGestureMode; + late bool enablePlayerControlAnimation; + late List actionTypeSort; + + /// 播放器相关 + // 弹幕开关 + late bool isOpenDanmu; + // 弹幕屏蔽类型 + late List blockTypes; + // 弹幕展示区域 + late double showArea; + // 弹幕透明度 + late double opacityVal; + // 弹幕字体大小 + late double fontSizeVal; + // 弹幕显示时间 + late double danmakuDurationVal; + // 弹幕描边宽度 + late double strokeWidth; + // 播放器循环模式 + late PlayRepeat playRepeat; + // 播放器默认播放速度 + late double playbackSpeed; + // 播放器自动长按速度 + late bool enableAutoLongPressSpeed; + // 播放器长按速度 + late double longPressSpeed; + // 播放器速度列表 + late List speedsList; + + // 私有构造函数 + GlobalDataCache._(); + + // 单例实例 + static final GlobalDataCache _instance = GlobalDataCache._(); + + // 获取全局实例 + factory GlobalDataCache() => _instance; + + // 异步初始化方法 + Future initialize() async { + imgQuality = await setting.get(SettingBoxKey.defaultPicQa, + defaultValue: 10); // 设置全局变量 + fullScreenGestureMode = FullScreenGestureMode.values[setting.get( + SettingBoxKey.fullScreenGestureMode, + defaultValue: FullScreenGestureMode.values.last.index) as int]; + enablePlayerControlAnimation = setting + .get(SettingBoxKey.enablePlayerControlAnimation, defaultValue: true); + actionTypeSort = await setting.get(SettingBoxKey.actionTypeSort, + defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']); + + isOpenDanmu = + await setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false); + blockTypes = + await localCache.get(LocalCacheKey.danmakuBlockType, defaultValue: []); + showArea = + await localCache.get(LocalCacheKey.danmakuShowArea, defaultValue: 0.5); + opacityVal = + await localCache.get(LocalCacheKey.danmakuOpacity, defaultValue: 1.0); + fontSizeVal = + await localCache.get(LocalCacheKey.danmakuFontScale, defaultValue: 1.0); + danmakuDurationVal = + await localCache.get(LocalCacheKey.danmakuDuration, defaultValue: 4.0); + strokeWidth = + await localCache.get(LocalCacheKey.strokeWidth, defaultValue: 1.5); + + var defaultPlayRepeat = await videoStorage.get(VideoBoxKey.playRepeat, + defaultValue: PlayRepeat.pause.value); + playRepeat = PlayRepeat.values + .toList() + .firstWhere((e) => e.value == defaultPlayRepeat); + playbackSpeed = + await videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0); + enableAutoLongPressSpeed = await setting + .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); + if (!enableAutoLongPressSpeed) { + longPressSpeed = await videoStorage.get(VideoBoxKey.longPressSpeedDefault, + defaultValue: 2.0); + } else { + longPressSpeed = 2.0; + } + speedsList = List.from(await videoStorage + .get(VideoBoxKey.customSpeedsList, defaultValue: [])); + final List playSpeedSystem = await videoStorage + .get(VideoBoxKey.playSpeedSystem, defaultValue: playSpeed); + speedsList.addAll(playSpeedSystem); + } +} diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index a132c5da..d9b9e100 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -2,8 +2,6 @@ import 'dart:io'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pilipala/models/user/info.dart'; -import '../models/common/gesture_mode.dart'; -import 'global_data.dart'; class GStrorage { static late final Box userInfo; @@ -42,13 +40,6 @@ class GStrorage { ); // 视频设置 video = await Hive.openBox('video'); - GlobalData().imgQuality = - setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); // 设置全局变量 - GlobalData().fullScreenGestureMode = FullScreenGestureMode.values[ - setting.get(SettingBoxKey.fullScreenGestureMode, - defaultValue: FullScreenGestureMode.values.last.index) as int]; - GlobalData().enablePlayerControlAnimation = setting - .get(SettingBoxKey.enablePlayerControlAnimation, defaultValue: true); } static void regAdapter() { From 478dd39b205e0992d244fa3cc5759c6a54f24143 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 26 Aug 2024 23:48:12 +0800 Subject: [PATCH 068/152] =?UTF-8?q?mod:=20bottomSheet=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/follow/widgets/follow_item.dart | 5 +- .../video/detail/introduction/controller.dart | 5 +- lib/pages/video/detail/introduction/view.dart | 5 +- .../introduction/widgets/fav_panel.dart | 218 +++++++++--------- .../introduction/widgets/group_panel.dart | 183 +++++++-------- 5 files changed, 201 insertions(+), 215 deletions(-) diff --git a/lib/pages/follow/widgets/follow_item.dart b/lib/pages/follow/widgets/follow_item.dart index 3d393277..6ee93a98 100644 --- a/lib/pages/follow/widgets/follow_item.dart +++ b/lib/pages/follow/widgets/follow_item.dart @@ -49,7 +49,10 @@ class FollowItem extends StatelessWidget { child: TextButton( onPressed: () async { await showFlexibleBottomSheet( - bottomSheetColor: Colors.transparent, + bottomSheetBorderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), minHeight: 1, initHeight: 1, maxHeight: 1, diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index d564aba4..3d22cb87 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -525,7 +525,10 @@ class VideoIntroController extends GetxController { // 设置关注分组 void setFollowGroup() { showFlexibleBottomSheet( - bottomSheetColor: Colors.transparent, + bottomSheetBorderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), minHeight: 0.6, initHeight: 0.6, maxHeight: 1, diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index a89b4d28..1ee0744b 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -230,7 +230,10 @@ class _VideoInfoState extends State with TickerProviderStateMixin { void _showFavPanel() { showFlexibleBottomSheet( - bottomSheetColor: Colors.transparent, + bottomSheetBorderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), minHeight: 0.6, initHeight: 0.6, maxHeight: 1, diff --git a/lib/pages/video/detail/introduction/widgets/fav_panel.dart b/lib/pages/video/detail/introduction/widgets/fav_panel.dart index a4e98df7..c8c71e40 100644 --- a/lib/pages/video/detail/introduction/widgets/fav_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/fav_panel.dart @@ -26,128 +26,116 @@ class _FavPanelState extends State { @override Widget build(BuildContext context) { - return Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), + return Column( + children: [ + AppBar( + centerTitle: false, + elevation: 0, + automaticallyImplyLeading: false, + leadingWidth: 0, + title: Text( + '选择收藏夹', + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(fontWeight: FontWeight.bold), + ), ), - ), - child: Column( - children: [ - AppBar( - centerTitle: false, - elevation: 0, - automaticallyImplyLeading: false, - leadingWidth: 0, - title: Text( - '选择收藏夹', - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontWeight: FontWeight.bold), - ), - ), - Expanded( - child: Material( - child: FutureBuilder( - future: _futureBuilderFuture, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - return Obx( - () => ListView.builder( - controller: widget.scrollController, - itemCount: - widget.ctr!.favFolderData.value.list!.length, - itemBuilder: (context, index) { - final item = - widget.ctr!.favFolderData.value.list![index]; - return ListTile( - onTap: () => widget.ctr! - .onChoose(item.favState != 1, index), - dense: true, - leading: const Icon(Icons.folder_outlined), - minLeadingWidth: 0, - title: Text(widget.ctr!.favFolderData.value - .list![index].title!), - subtitle: Text( - '${item.mediaCount}个内容 ', + Expanded( + child: Material( + child: FutureBuilder( + future: _futureBuilderFuture, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + return Obx( + () => ListView.builder( + controller: widget.scrollController, + itemCount: widget.ctr!.favFolderData.value.list!.length, + itemBuilder: (context, index) { + final item = + widget.ctr!.favFolderData.value.list![index]; + return ListTile( + onTap: () => + widget.ctr!.onChoose(item.favState != 1, index), + dense: true, + leading: const Icon(Icons.folder_outlined), + minLeadingWidth: 0, + title: Text(widget + .ctr!.favFolderData.value.list![index].title!), + subtitle: Text( + '${item.mediaCount}个内容 ', + ), + trailing: Transform.scale( + scale: 0.9, + child: Checkbox( + value: widget.ctr!.favFolderData.value + .list![index].favState == + 1, + onChanged: (bool? checkValue) => + widget.ctr!.onChoose(checkValue!, index), ), - trailing: Transform.scale( - scale: 0.9, - child: Checkbox( - value: widget.ctr!.favFolderData.value - .list![index].favState == - 1, - onChanged: (bool? checkValue) => - widget.ctr!.onChoose(checkValue!, index), - ), - ), - ); - }, - ), - ); - } else { - return HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), - ); - } + ), + ); + }, + ), + ); } else { - // 骨架屏 - return const Text('请求中'); + return HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), + ); } - }, + } else { + // 骨架屏 + return const Text('请求中'); + } + }, + ), + ), + ), + Divider( + height: 1, + color: Theme.of(context).disabledColor.withOpacity(0.08), + ), + Padding( + padding: EdgeInsets.only( + left: 20, + right: 20, + top: 12, + bottom: MediaQuery.of(context).padding.bottom + 12, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Get.back(), + style: TextButton.styleFrom( + padding: const EdgeInsets.only(left: 30, right: 30), + backgroundColor: + Theme.of(context).colorScheme.onInverseSurface, // 设置按钮背景色 + ), + child: const Text('取消'), ), - ), - ), - Divider( - height: 1, - color: Theme.of(context).disabledColor.withOpacity(0.08), - ), - Padding( - padding: EdgeInsets.only( - left: 20, - right: 20, - top: 12, - bottom: MediaQuery.of(context).padding.bottom + 12, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Get.back(), - style: TextButton.styleFrom( - padding: const EdgeInsets.only(left: 30, right: 30), - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, // 设置按钮背景色 - ), - child: const Text('取消'), + const SizedBox(width: 10), + TextButton( + onPressed: () async { + feedBack(); + await widget.ctr!.actionFavVideo(); + }, + style: TextButton.styleFrom( + padding: const EdgeInsets.only(left: 30, right: 30), + foregroundColor: Theme.of(context).colorScheme.onPrimary, + backgroundColor: + Theme.of(context).colorScheme.primary, // 设置按钮背景色 ), - const SizedBox(width: 10), - TextButton( - onPressed: () async { - feedBack(); - await widget.ctr!.actionFavVideo(); - }, - style: TextButton.styleFrom( - padding: const EdgeInsets.only(left: 30, right: 30), - foregroundColor: Theme.of(context).colorScheme.onPrimary, - backgroundColor: - Theme.of(context).colorScheme.primary, // 设置按钮背景色 - ), - child: const Text('确认选择'), - ), - ], - ), + child: const Text('确认'), + ), + ], ), - ], - ), + ), + ], ); } } diff --git a/lib/pages/video/detail/introduction/widgets/group_panel.dart b/lib/pages/video/detail/introduction/widgets/group_panel.dart index 4bb0980c..c007d52d 100644 --- a/lib/pages/video/detail/introduction/widgets/group_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/group_panel.dart @@ -54,112 +54,101 @@ class _GroupPanelState extends State { @override Widget build(BuildContext context) { - return Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), + return Column( + children: [ + AppBar( + centerTitle: false, + elevation: 0, + leading: IconButton( + onPressed: () => Get.back(), + icon: const Icon(Icons.close_outlined)), + title: Text('设置关注分组', style: Theme.of(context).textTheme.titleMedium), ), - ), - child: Column( - children: [ - AppBar( - centerTitle: false, - elevation: 0, - leading: IconButton( - onPressed: () => Get.back(), - icon: const Icon(Icons.close_outlined)), - title: - Text('设置关注分组', style: Theme.of(context).textTheme.titleMedium), - ), - Expanded( - child: Material( - child: FutureBuilder( - future: _futureBuilderFuture, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - tagsList = data['data']; - return ListView.builder( - controller: widget.scrollController, - itemCount: data['data'].length, - itemBuilder: (context, index) { - return ListTile( - onTap: () { - data['data'][index].checked = - !data['data'][index].checked; - showDefault = - !data['data'].any((e) => e.checked == true); - setState(() {}); - }, - dense: true, - leading: const Icon(Icons.group_outlined), - minLeadingWidth: 0, - title: Text(data['data'][index].name), - subtitle: data['data'][index].tip != '' - ? Text(data['data'][index].tip) - : null, - trailing: Transform.scale( - scale: 0.9, - child: Checkbox( - value: data['data'][index].checked, - onChanged: (bool? checkValue) { - data['data'][index].checked = checkValue; - showDefault = !data['data'] - .any((e) => e.checked == true); - setState(() {}); - }, - ), + Expanded( + child: Material( + child: FutureBuilder( + future: _futureBuilderFuture, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + tagsList = data['data']; + return ListView.builder( + controller: widget.scrollController, + itemCount: data['data'].length, + itemBuilder: (context, index) { + return ListTile( + onTap: () { + data['data'][index].checked = + !data['data'][index].checked; + showDefault = + !data['data'].any((e) => e.checked == true); + setState(() {}); + }, + dense: true, + leading: const Icon(Icons.group_outlined), + minLeadingWidth: 0, + title: Text(data['data'][index].name), + subtitle: data['data'][index].tip != '' + ? Text(data['data'][index].tip) + : null, + trailing: Transform.scale( + scale: 0.9, + child: Checkbox( + value: data['data'][index].checked, + onChanged: (bool? checkValue) { + data['data'][index].checked = checkValue; + showDefault = + !data['data'].any((e) => e.checked == true); + setState(() {}); + }, ), - ); - }, - ); - } else { - return HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), - ); - } + ), + ); + }, + ); } else { - // 骨架屏 - return const Text('请求中'); + return HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), + ); } - }, - ), + } else { + // 骨架屏 + return const Text('请求中'); + } + }, ), ), - Divider( - height: 1, - color: Theme.of(context).disabledColor.withOpacity(0.08), + ), + Divider( + height: 1, + color: Theme.of(context).disabledColor.withOpacity(0.08), + ), + Padding( + padding: EdgeInsets.only( + left: 20, + right: 20, + top: 12, + bottom: MediaQuery.of(context).padding.bottom + 12, ), - Padding( - padding: EdgeInsets.only( - left: 20, - right: 20, - top: 12, - bottom: MediaQuery.of(context).padding.bottom + 12, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => onSave(), - style: TextButton.styleFrom( - padding: const EdgeInsets.only(left: 30, right: 30), - foregroundColor: Theme.of(context).colorScheme.onPrimary, - backgroundColor: - Theme.of(context).colorScheme.primary, // 设置按钮背景色 - ), - child: Text(showDefault ? '保存至默认分组' : '保存'), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => onSave(), + style: TextButton.styleFrom( + padding: const EdgeInsets.only(left: 30, right: 30), + foregroundColor: Theme.of(context).colorScheme.onPrimary, + backgroundColor: + Theme.of(context).colorScheme.primary, // 设置按钮背景色 ), - ], - ), + child: Text(showDefault ? '保存至默认分组' : '保存'), + ), + ], ), - ], - ), + ), + ], ); } } From 01cae5e280921c8acc19ed2fc0c1f1747b905650 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 27 Aug 2024 23:46:01 +0800 Subject: [PATCH 069/152] mod: GlobalDataCache init --- lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 78747279..fe93da22 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -64,6 +64,7 @@ void main() async { } PiliSchame.init(); + await GlobalDataCache().initialize(); } class MyApp extends StatelessWidget { @@ -268,7 +269,6 @@ class BuildMainApp extends StatelessWidget { onReady: () async { RecommendFilter(); Data.init(); - await GlobalDataCache().initialize(); setupServiceLocator(); }, ); From 67aa95c027f1373322402e905dbbeb342f2ae87d Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 27 Aug 2024 23:57:19 +0800 Subject: [PATCH 070/152] =?UTF-8?q?fix:=20=E9=83=A8=E5=88=86=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=BF=BD=E7=95=A5error=20response=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/interceptor.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart index 10b5aee5..259a3bf9 100644 --- a/lib/http/interceptor.dart +++ b/lib/http/interceptor.dart @@ -46,7 +46,8 @@ class ApiInterceptor extends Interceptor { // 处理网络请求错误 // handler.next(err); String url = err.requestOptions.uri.toString(); - if (!url.contains('heartbeat')) { + final excludedPatterns = RegExp(r'heartbeat|seg\.so|online/total'); + if (!excludedPatterns.hasMatch(url)) { SmartDialog.showToast( await dioError(err), displayType: SmartToastType.onlyRefresh, From 5c7f477ad8d467f93a056794165ef30e15e4584a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 31 Aug 2024 14:26:03 +0800 Subject: [PATCH 071/152] =?UTF-8?q?mod=EF=BC=9A=E6=94=B6=E8=97=8F=E5=A4=B9?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=B1=95=E7=A4=BA(=E5=85=AC=E5=BC=80/?= =?UTF-8?q?=E7=A7=81=E5=AF=86)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/fav/widgets/item.dart | 11 ++++++++++- .../detail/introduction/widgets/fav_panel.dart | 17 ++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/pages/fav/widgets/item.dart b/lib/pages/fav/widgets/item.dart index 9d453fb5..25b92a5c 100644 --- a/lib/pages/fav/widgets/item.dart +++ b/lib/pages/fav/widgets/item.dart @@ -74,7 +74,7 @@ class VideoContent extends StatelessWidget { Widget build(BuildContext context) { return Expanded( child: Padding( - padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), + padding: const EdgeInsets.fromLTRB(10, 2, 6, 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -94,6 +94,15 @@ class VideoContent extends StatelessWidget { color: Theme.of(context).colorScheme.outline, ), ), + const Spacer(), + Text( + [23, 1].contains(favFolderItem.attr) ? '私密' : '公开', + textAlign: TextAlign.start, + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, + ), + ), ], ), ), diff --git a/lib/pages/video/detail/introduction/widgets/fav_panel.dart b/lib/pages/video/detail/introduction/widgets/fav_panel.dart index c8c71e40..2cf7d236 100644 --- a/lib/pages/video/detail/introduction/widgets/fav_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/fav_panel.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/widgets/http_error.dart'; @@ -60,19 +61,21 @@ class _FavPanelState extends State { onTap: () => widget.ctr!.onChoose(item.favState != 1, index), dense: true, - leading: const Icon(Icons.folder_outlined), + leading: Icon([23, 1].contains(item.attr) + ? Icons.lock_outline + : Icons.folder_outlined), minLeadingWidth: 0, - title: Text(widget - .ctr!.favFolderData.value.list![index].title!), + title: Text(item.title!), subtitle: Text( - '${item.mediaCount}个内容 ', + '${item.mediaCount}个内容 - ${[ + 23, + 1 + ].contains(item.attr) ? '私密' : '公开'}', ), trailing: Transform.scale( scale: 0.9, child: Checkbox( - value: widget.ctr!.favFolderData.value - .list![index].favState == - 1, + value: item.favState == 1, onChanged: (bool? checkValue) => widget.ctr!.onChoose(checkValue!, index), ), From e4a820268a5068418d3fa39ecaa706ac07c1b7d6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 4 Sep 2024 09:42:08 +0800 Subject: [PATCH 072/152] =?UTF-8?q?fix:=20iOS=E9=9D=99=E9=9F=B3=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E6=97=A0=E5=A3=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner/AppDelegate.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4a..dfa078c3 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,5 +1,6 @@ import UIKit import Flutter +import AVFoundation @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { @@ -8,6 +9,14 @@ import Flutter didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) + + // 设置音频会话类别,确保在静音模式下播放音频 + do { + try AVAudioSession.sharedInstance().setCategory(.playback, options: [.duckOthers]) + } catch { + print("Failed to set audio session category: \(error)") + } + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } -} +} \ No newline at end of file From c3595a0dcd91430a498ef660d162b001df684382 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 8 Sep 2024 15:53:56 +0800 Subject: [PATCH 073/152] =?UTF-8?q?fix:=20=E6=90=9C=E7=B4=A2=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E7=AD=9B=E9=80=89=E9=9D=A2=E6=9D=BF=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=BB=9A=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search_panel/widgets/video_panel.dart | 163 +++++++++--------- 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/lib/pages/search_panel/widgets/video_panel.dart b/lib/pages/search_panel/widgets/video_panel.dart index f43e2eec..7da6aaaa 100644 --- a/lib/pages/search_panel/widgets/video_panel.dart +++ b/lib/pages/search_panel/widgets/video_panel.dart @@ -261,90 +261,97 @@ class VideoPanelController extends GetxController { onShowFilterSheet(searchPanelCtr) { showModalBottomSheet( context: Get.context!, + isScrollControlled: true, builder: (context) { return StatefulBuilder( builder: (context, StateSetter setState) { - return Container( - color: Theme.of(Get.context!).colorScheme.surface, - padding: const EdgeInsets.only(top: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + return Padding( + padding: EdgeInsets.only( + top: 12, bottom: MediaQuery.of(context).padding.bottom + 20), + child: Wrap( children: [ - const ListTile( - title: Text('内容时长'), - ), - Padding( - padding: const EdgeInsets.only( - left: 14, - right: 14, - bottom: 14, - ), - child: Wrap( - spacing: 10, - runSpacing: 10, - direction: Axis.horizontal, - textDirection: TextDirection.ltr, - children: [ - for (var i in timeFiltersList) - Obx( - () => SearchText( - searchText: i['label'], - searchTextIdx: i['value'], - isSelect: - currentTimeFilterval.value == i['value'], - onSelect: (value) async { - currentTimeFilterval.value = i['value']; - setState(() {}); - SmartDialog.showToast("「${i['label']}」的筛选结果"); - SearchPanelController ctr = - Get.find( + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const ListTile( + title: Text('内容时长'), + ), + Padding( + padding: const EdgeInsets.only( + left: 14, + right: 14, + bottom: 14, + ), + child: Wrap( + spacing: 10, + runSpacing: 10, + direction: Axis.horizontal, + textDirection: TextDirection.ltr, + children: [ + for (var i in timeFiltersList) + Obx( + () => SearchText( + searchText: i['label'], + searchTextIdx: i['value'], + isSelect: + currentTimeFilterval.value == i['value'], + onSelect: (value) async { + currentTimeFilterval.value = i['value']; + setState(() {}); + SmartDialog.showToast( + "「${i['label']}」的筛选结果"); + SearchPanelController ctr = Get.find< + SearchPanelController>( tag: 'video${searchPanelCtr.keyword!}'); - ctr.duration.value = i['value']; - Get.back(); - SmartDialog.showLoading(msg: '获取中'); - await ctr.onRefresh(); - SmartDialog.dismiss(); - }, - onLongSelect: (value) => {}, - ), - ) - ], - ), - ), - const ListTile( - title: Text('内容分区'), - ), - Padding( - padding: const EdgeInsets.only(left: 14, right: 14), - child: Wrap( - spacing: 10, - runSpacing: 10, - direction: Axis.horizontal, - textDirection: TextDirection.ltr, - children: [ - for (var i in partFiltersList) - SearchText( - searchText: i['label'], - searchTextIdx: i['value'], - isSelect: currentPartFilterval.value == i['value'], - onSelect: (value) async { - currentPartFilterval.value = i['value']; - setState(() {}); - SmartDialog.showToast("「${i['label']}」的筛选结果"); - SearchPanelController ctr = - Get.find( + ctr.duration.value = i['value']; + Get.back(); + SmartDialog.showLoading(msg: '获取中'); + await ctr.onRefresh(); + SmartDialog.dismiss(); + }, + onLongSelect: (value) => {}, + ), + ) + ], + ), + ), + const ListTile( + title: Text('内容分区'), + ), + Padding( + padding: const EdgeInsets.only(left: 14, right: 14), + child: Wrap( + spacing: 10, + runSpacing: 10, + direction: Axis.horizontal, + textDirection: TextDirection.ltr, + children: [ + for (var i in partFiltersList) + SearchText( + searchText: i['label'], + searchTextIdx: i['value'], + isSelect: + currentPartFilterval.value == i['value'], + onSelect: (value) async { + currentPartFilterval.value = i['value']; + setState(() {}); + SmartDialog.showToast("「${i['label']}」的筛选结果"); + SearchPanelController ctr = Get.find< + SearchPanelController>( tag: 'video${searchPanelCtr.keyword!}'); - ctr.tids.value = i['value']; - Get.back(); - SmartDialog.showLoading(msg: '获取中'); - await ctr.onRefresh(); - SmartDialog.dismiss(); - }, - onLongSelect: (value) => {}, - ) - ], - ), - ) + ctr.tids.value = i['value']; + Get.back(); + SmartDialog.showLoading(msg: '获取中'); + await ctr.onRefresh(); + SmartDialog.dismiss(); + }, + onLongSelect: (value) => {}, + ) + ], + ), + ) + ], + ), ], ), ); From df1866286314de2d890557415dd09a86ceb9dcd6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 8 Sep 2024 17:46:08 +0800 Subject: [PATCH 074/152] =?UTF-8?q?fix:=20=E7=94=A8=E6=88=B7=E5=90=88?= =?UTF-8?q?=E9=9B=86=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member_seasons/controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/member_seasons/controller.dart b/lib/pages/member_seasons/controller.dart index 58a9035f..4e7c9762 100644 --- a/lib/pages/member_seasons/controller.dart +++ b/lib/pages/member_seasons/controller.dart @@ -21,7 +21,7 @@ class MemberSeasonsController extends GetxController { mid = int.parse(Get.parameters['mid']!); category = Get.parameters['category']!; if (category == '0') { - seasonId = int.parse(Get.parameters['seriesId']!); + seasonId = int.parse(Get.parameters['seasonId']!); } if (category == '1') { seriesId = int.parse(Get.parameters['seriesId']!); From 1d87e66433fdfc04aab9bd3f9ba1a0eab55da45f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 8 Sep 2024 17:46:23 +0800 Subject: [PATCH 075/152] =?UTF-8?q?fix:=20=E7=BF=BB=E9=A1=B5=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member_archive/controller.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/member_archive/controller.dart b/lib/pages/member_archive/controller.dart index 667d16c5..20cf38d3 100644 --- a/lib/pages/member_archive/controller.dart +++ b/lib/pages/member_archive/controller.dart @@ -27,10 +27,13 @@ class MemberArchiveController extends GetxController { // 获取用户投稿 Future getMemberArchive(type) async { + if (isLoading.value) { + return; + } + isLoading.value = true; if (type == 'init') { pn = 1; archivesList.clear(); - isLoading.value = true; } var res = await MemberHttp.memberArchive( mid: mid, From 012b4152337faa9db1d7d6d1ecc48b4fc6dda010 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 9 Sep 2024 00:05:22 +0800 Subject: [PATCH 076/152] =?UTF-8?q?fix:=20=E7=82=B9=E8=B5=9E=E8=AE=A1?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/message/like/view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/message/like/view.dart b/lib/pages/message/like/view.dart index 80b20117..8b061505 100644 --- a/lib/pages/message/like/view.dart +++ b/lib/pages/message/like/view.dart @@ -203,9 +203,9 @@ class LikeItem extends StatelessWidget { Text.rich(TextSpan(children: [ TextSpan(text: nickNameList.join('、')), const TextSpan(text: ' '), - if (item.users!.length > 1) + if (item.counts! > 1) TextSpan( - text: '等总计${item.users!.length}人', + text: '等总计${item.counts}人', style: TextStyle(color: outline), ), TextSpan( From 5651466881cbffe0879e69d5da66a28f70bd454f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 9 Sep 2024 00:12:38 +0800 Subject: [PATCH 077/152] =?UTF-8?q?fix:=20=E6=9A=97=E9=BB=91=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E4=B8=8B=E4=B8=AA=E4=BA=BA=E8=AE=A4=E8=AF=81=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member/view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 7d2e4bed..be0ddedc 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -406,7 +406,7 @@ class _MemberPageState extends State ? '个人认证:' : '企业认证:', style: TextStyle( - color: Theme.of(context).primaryColor, + color: Theme.of(context).colorScheme.primary, ), children: [ TextSpan( From 10f464aab15885fba15bd9e9f1782a08bebf8344 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 9 Sep 2024 00:47:16 +0800 Subject: [PATCH 078/152] =?UTF-8?q?mod:=20=E5=8A=A8=E6=80=81=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=A0=87=E7=AD=BE=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/dynamics/widgets/video_panel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/dynamics/widgets/video_panel.dart b/lib/pages/dynamics/widgets/video_panel.dart index 828fb283..75f16a3b 100644 --- a/lib/pages/dynamics/widgets/video_panel.dart +++ b/lib/pages/dynamics/widgets/video_panel.dart @@ -86,7 +86,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) { height: width / StyleString.aspectRatio, src: content.cover, ), - if (content.badge != null && type == 'pgc') + if (content.badge != null && content.badge['text'] != null) PBadge( text: content.badge['text'], top: 8.0, From ba1704fa2af51adaadef33f841b852e2d78fb5fe Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 11 Sep 2024 00:03:14 +0800 Subject: [PATCH 079/152] =?UTF-8?q?feat:=20=E6=94=B6=E8=97=8F=E5=A4=B9?= =?UTF-8?q?=E6=96=B0=E5=BB=BA/=E7=BC=96=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 10 +++ lib/http/fav.dart | 67 ++++++++++++++++ lib/pages/fav/view.dart | 16 ++++ lib/pages/fav_detail/controller.dart | 13 ++++ lib/pages/fav_detail/view.dart | 5 ++ lib/pages/fav_edit/controller.dart | 77 +++++++++++++++++++ lib/pages/fav_edit/index.dart | 4 + lib/pages/fav_edit/view.dart | 111 +++++++++++++++++++++++++++ lib/router/app_pages.dart | 3 + 9 files changed, 306 insertions(+) create mode 100644 lib/http/fav.dart create mode 100644 lib/pages/fav_edit/controller.dart create mode 100644 lib/pages/fav_edit/index.dart create mode 100644 lib/pages/fav_edit/view.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 46bbb6ac..d34bfa0e 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -552,4 +552,14 @@ class Api { /// 系统通知 static const String messageSystemAPi = '${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify'; + + /// 系统通知标记已读 + static const String systemMarkRead = + '${HttpString.messageBaseUrl}/x/sys-msg/update_cursor'; + + /// 编辑收藏夹 + static const String editFavFolder = '/x/v3/fav/folder/edit'; + + /// 新建收藏夹 + static const String addFavFolder = '/x/v3/fav/folder/add'; } diff --git a/lib/http/fav.dart b/lib/http/fav.dart new file mode 100644 index 00000000..6f49d68a --- /dev/null +++ b/lib/http/fav.dart @@ -0,0 +1,67 @@ +import 'index.dart'; + +class FavHttp { + /// 编辑收藏夹 + static Future editFolder({ + required String title, + required String intro, + required String mediaId, + String? cover, + int? privacy, + }) async { + var res = await Request().post( + Api.editFavFolder, + queryParameters: { + 'title': title, + 'intro': intro, + 'media_id': mediaId, + 'cover': cover ?? '', + 'privacy': privacy ?? 0, + 'csrf': await Request.getCsrf(), + }, + ); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } + + /// 新建收藏夹 + static Future addFolder({ + required String title, + required String intro, + String? cover, + int? privacy, + }) async { + var res = await Request().post( + Api.addFavFolder, + queryParameters: { + 'title': title, + 'intro': intro, + 'cover': cover ?? '', + 'privacy': privacy ?? 0, + 'csrf': await Request.getCsrf(), + }, + ); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } +} diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 7010ba0d..f6164609 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -55,6 +55,22 @@ class _FavPageState extends State { tooltip: 'Ta的订阅', ) : const SizedBox.shrink()), + + // 新建收藏夹 + Obx(() => _favController.isOwner.value + ? IconButton( + onPressed: () async { + await Get.toNamed('/favEdit'); + _favController.hasMore.value = true; + _favController.currentPage = 1; + setState(() { + _futureBuilderFuture = _favController.queryFavFolder(); + }); + }, + icon: const Icon(Icons.add_outlined), + tooltip: '新建收藏夹', + ) + : const SizedBox.shrink()), IconButton( onPressed: () => Get.toNamed( '/favSearch?searchType=1&mediaId=${_favController.favFolderData.value.list!.first.id}'), diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 3f87c226..e0158587 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -115,4 +115,17 @@ class FavDetailController extends GetxController { }, ); } + + onEditFavFolder() async { + Get.toNamed( + '/favEdit', + arguments: { + 'mediaId': mediaId.toString(), + 'title': item!.title, + 'intro': item!.intro, + 'cover': item!.cover, + 'privacy': item!.attr, + }, + ); + } } diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index cb9d7e7b..c1865355 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -106,6 +106,11 @@ class _FavDetailPageState extends State { position: PopupMenuPosition.under, onSelected: (String type) {}, itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + onTap: () => _favDetailController.onEditFavFolder(), + value: 'edit', + child: const Text('编辑收藏夹'), + ), PopupMenuItem( onTap: () => _favDetailController.onDelFavFolder(), value: 'pause', diff --git a/lib/pages/fav_edit/controller.dart b/lib/pages/fav_edit/controller.dart new file mode 100644 index 00000000..4772caee --- /dev/null +++ b/lib/pages/fav_edit/controller.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/http/fav.dart'; + +class FavEditController extends GetxController { + final GlobalKey formKey = GlobalKey(); + final TextEditingController titleController = TextEditingController(); + final TextEditingController contentController = TextEditingController(); + + final FocusNode titleTextFieldNode = FocusNode(); + final FocusNode contentTextFieldNode = FocusNode(); + + // 默认新建 + RxString type = 'add'.obs; + + String? mediaId; + String cover = ''; // 封面 + String title = ''; // 名称 + String intro = ''; // 简介 + RxInt privacy = 0.obs; // 是否公开 0公开 1私密 + + @override + void onInit() { + super.onInit(); + var args = Get.arguments; + if (args != null) { + type.value = 'edit'; + mediaId = args['mediaId']; + title = args['title']; + intro = args['intro']; + cover = args['cover']; + privacy.value = args['privacy']; + titleController.text = title; + contentController.text = intro; + } + } + + void onSubmit() async { + // 表单验证 + if ((formKey.currentState as FormState).validate()) { + if (type.value == 'edit') { + await editFolder(); + } else { + await addFolder(); + } + } + } + + Future editFolder() async { + var res = await FavHttp.editFolder( + title: title, + intro: intro, + mediaId: mediaId!, + cover: cover, + ); + if (res['status']) { + SmartDialog.showToast('编辑成功'); + Get.back(); + } else { + SmartDialog.showToast(res['msg']); + } + } + + Future addFolder() async { + var res = await FavHttp.addFolder( + title: title, + intro: intro, + ); + if (res['status']) { + SmartDialog.showToast('新建成功'); + Get.back(); + } else { + SmartDialog.showToast(res['msg']); + } + } +} diff --git a/lib/pages/fav_edit/index.dart b/lib/pages/fav_edit/index.dart new file mode 100644 index 00000000..872df8d4 --- /dev/null +++ b/lib/pages/fav_edit/index.dart @@ -0,0 +1,4 @@ +library fav_edit; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/fav_edit/view.dart b/lib/pages/fav_edit/view.dart new file mode 100644 index 00000000..3ef7cda3 --- /dev/null +++ b/lib/pages/fav_edit/view.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'controller.dart'; + +class FavEditPage extends StatefulWidget { + const FavEditPage({super.key}); + + @override + State createState() => _FavEditPageState(); +} + +class _FavEditPageState extends State { + final FavEditController _favEditController = Get.put(FavEditController()); + String title = ''; + String content = ''; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0, + scrolledUnderElevation: 0, + title: Obx( + () => _favEditController.type.value == 'add' + ? Text( + '新建收藏夹', + style: Theme.of(context).textTheme.titleMedium, + ) + : Text( + '编辑收藏夹', + style: Theme.of(context).textTheme.titleMedium, + ), + ), + centerTitle: false, + actions: [ + Obx( + () => _favEditController.privacy.value == 0 + ? IconButton( + onPressed: () { + _favEditController.privacy.value = 1; + }, + icon: const Icon(Icons.lock_open_outlined)) + : IconButton( + onPressed: () { + _favEditController.privacy.value = 0; + }, + icon: Icon( + Icons.lock_outlined, + color: Theme.of(context).colorScheme.error, + )), + ), + TextButton( + onPressed: _favEditController.onSubmit, child: const Text('保存')), + const SizedBox(width: 14), + ], + ), + body: Form( + key: _favEditController.formKey, //设置globalKey,用于后面获取FormState + autovalidateMode: AutovalidateMode.disabled, + child: Column( + children: [ + Container( + padding: const EdgeInsets.fromLTRB(14, 10, 14, 5), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context).dividerColor.withOpacity(0.2), + ), + ), + ), + child: TextFormField( + autofocus: true, + controller: _favEditController.titleController, + focusNode: _favEditController.titleTextFieldNode, + decoration: const InputDecoration( + hintText: "收藏夹名称", + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + // 校验标题 + validator: (v) { + return v!.trim().isNotEmpty ? null : "请输入收藏夹名称"; + }, + onChanged: (val) { + _favEditController.title = val; + }, + ), + ), + Expanded( + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 14, vertical: 5), + child: TextFormField( + controller: _favEditController.contentController, + minLines: 1, + maxLines: 5, + decoration: const InputDecoration( + hintText: '输入收藏夹简介', border: InputBorder.none), + style: Theme.of(context).textTheme.bodyLarge, + onChanged: (val) { + _favEditController.intro = val; + }, + )), + ), + ], + ), + ), + ); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 7840c126..136de91f 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/pages/fav_edit/index.dart'; import 'package:pilipala/pages/follow_search/view.dart'; import 'package:pilipala/pages/message/at/index.dart'; import 'package:pilipala/pages/message/like/index.dart'; @@ -183,6 +184,8 @@ class Routes { // 系统通知 CustomGetPage( name: '/messageSystem', page: () => const MessageSystemPage()), + // 收藏夹编辑 + CustomGetPage(name: '/favEdit', page: () => const FavEditPage()), ]; } From 95a452dea8be36ff969f1d05b4186e2b2feec161 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 11 Sep 2024 20:25:40 +0800 Subject: [PATCH 080/152] =?UTF-8?q?opt:=20stat=E7=BB=84=E4=BB=B6=E4=BC=A0?= =?UTF-8?q?=E5=8F=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/stat/danmu.dart | 33 +++++++++++++++-- lib/common/widgets/stat/view.dart | 37 +++++++++++++++++-- lib/common/widgets/video_card_h.dart | 10 +---- lib/common/widgets/video_card_v.dart | 4 +- lib/pages/bangumi/introduction/view.dart | 2 - .../introduction/widgets/intro_detail.dart | 2 - .../fav_detail/widget/fav_video_card.dart | 8 +--- lib/pages/member_coin/widgets/item.dart | 5 +-- lib/pages/member_like/widgets/item.dart | 5 +-- lib/pages/member_seasons/widgets/item.dart | 5 +-- .../widget/sub_video_card.dart | 8 +--- lib/pages/video/detail/introduction/view.dart | 2 - 12 files changed, 73 insertions(+), 48 deletions(-) diff --git a/lib/common/widgets/stat/danmu.dart b/lib/common/widgets/stat/danmu.dart index 511839a0..9ea05301 100644 --- a/lib/common/widgets/stat/danmu.dart +++ b/lib/common/widgets/stat/danmu.dart @@ -6,7 +6,7 @@ class StatDanMu extends StatelessWidget { final dynamic danmu; final String? size; - const StatDanMu({Key? key, this.theme, this.danmu, this.size}) + const StatDanMu({Key? key, this.theme = 'gray', this.danmu, this.size}) : super(key: key); @override @@ -17,21 +17,46 @@ class StatDanMu extends StatelessWidget { 'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.8), }; Color color = colorObject[theme]!; + return StatIconText( + icon: Icons.subtitles_outlined, + text: Utils.numFormat(danmu!), + color: color, + size: size, + ); + } +} + +class StatIconText extends StatelessWidget { + final IconData icon; + final String text; + final Color color; + final String? size; + + const StatIconText({ + Key? key, + required this.icon, + required this.text, + required this.color, + this.size, + }) : super(key: key); + + @override + Widget build(BuildContext context) { return Row( children: [ Icon( - Icons.subtitles_outlined, + icon, size: 14, color: color, ), const SizedBox(width: 2), Text( - Utils.numFormat(danmu!), + text, style: TextStyle( fontSize: size == 'medium' ? 12 : 11, color: color, ), - ) + ), ], ); } diff --git a/lib/common/widgets/stat/view.dart b/lib/common/widgets/stat/view.dart index 5359c979..85bec816 100644 --- a/lib/common/widgets/stat/view.dart +++ b/lib/common/widgets/stat/view.dart @@ -6,8 +6,12 @@ class StatView extends StatelessWidget { final dynamic view; final String? size; - const StatView({Key? key, this.theme, this.view, this.size}) - : super(key: key); + const StatView({ + Key? key, + this.theme = 'gray', + this.view, + this.size, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -17,16 +21,41 @@ class StatView extends StatelessWidget { 'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.8), }; Color color = colorObject[theme]!; + return StatIconText( + icon: Icons.play_circle_outlined, + text: Utils.numFormat(view!), + color: color, + size: size, + ); + } +} + +class StatIconText extends StatelessWidget { + final IconData icon; + final String text; + final Color color; + final String? size; + + const StatIconText({ + Key? key, + required this.icon, + required this.text, + required this.color, + this.size, + }) : super(key: key); + + @override + Widget build(BuildContext context) { return Row( children: [ Icon( - Icons.play_circle_outlined, + icon, size: 13, color: color, ), const SizedBox(width: 2), Text( - Utils.numFormat(view!), + text, style: TextStyle( fontSize: size == 'medium' ? 12 : 11, color: color, diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index 1265477f..78c4ba87 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -266,17 +266,11 @@ class VideoContent extends StatelessWidget { Row( children: [ if (showView) ...[ - StatView( - theme: 'gray', - view: videoItem.stat.view as int, - ), + StatView(view: videoItem.stat.view as int), const SizedBox(width: 8), ], if (showDanmaku) - StatDanMu( - theme: 'gray', - danmu: videoItem.stat.danmaku as int, - ), + StatDanMu(danmu: videoItem.stat.danmaku as int), const Spacer(), if (source == 'normal') SizedBox( diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index d8e1bb2c..7a9ef39c 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -287,9 +287,9 @@ class VideoStat extends StatelessWidget { Widget build(BuildContext context) { return Row( children: [ - StatView(theme: 'gray', view: videoItem.stat.view), + StatView(view: videoItem.stat.view), const SizedBox(width: 8), - StatDanMu(theme: 'gray', danmu: videoItem.stat.danmu), + StatDanMu(danmu: videoItem.stat.danmu), if (videoItem is RecVideoItemModel) ...[ crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8), RichText( diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index 95d4d898..94ee24de 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -255,13 +255,11 @@ class _BangumiInfoState extends State { Row( children: [ StatView( - theme: 'gray', view: widget.bangumiDetail!.stat!['views'], size: 'medium', ), const SizedBox(width: 6), StatDanMu( - theme: 'gray', danmu: widget.bangumiDetail!.stat!['danmakus'], size: 'medium', ), diff --git a/lib/pages/bangumi/introduction/widgets/intro_detail.dart b/lib/pages/bangumi/introduction/widgets/intro_detail.dart index 07684a86..409474a9 100644 --- a/lib/pages/bangumi/introduction/widgets/intro_detail.dart +++ b/lib/pages/bangumi/introduction/widgets/intro_detail.dart @@ -60,13 +60,11 @@ class IntroDetail extends StatelessWidget { Row( children: [ StatView( - theme: 'gray', view: bangumiDetail!.stat!['views'], size: 'medium', ), const SizedBox(width: 6), StatDanMu( - theme: 'gray', danmu: bangumiDetail!.stat!['danmakus'], size: 'medium', ), diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index 9779c549..ecb4dd4a 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -203,13 +203,9 @@ class VideoContent extends StatelessWidget { padding: const EdgeInsets.only(top: 2), child: Row( children: [ - StatView( - theme: 'gray', - view: videoItem.cntInfo['play'], - ), + StatView(view: videoItem.cntInfo['play']), const SizedBox(width: 8), - StatDanMu( - theme: 'gray', danmu: videoItem.cntInfo['danmaku']), + StatDanMu(danmu: videoItem.cntInfo['danmaku']), const Spacer(), ], ), diff --git a/lib/pages/member_coin/widgets/item.dart b/lib/pages/member_coin/widgets/item.dart index de28585c..6d732694 100644 --- a/lib/pages/member_coin/widgets/item.dart +++ b/lib/pages/member_coin/widgets/item.dart @@ -69,10 +69,7 @@ class MemberCoinsItem extends StatelessWidget { const SizedBox(height: 4), Row( children: [ - StatView( - view: coinItem.view, - theme: 'gray', - ), + StatView(view: coinItem.view), const Spacer(), Text( Utils.CustomStamp_str( diff --git a/lib/pages/member_like/widgets/item.dart b/lib/pages/member_like/widgets/item.dart index 57798bb7..3b3c81da 100644 --- a/lib/pages/member_like/widgets/item.dart +++ b/lib/pages/member_like/widgets/item.dart @@ -69,10 +69,7 @@ class MemberLikeItem extends StatelessWidget { const SizedBox(height: 4), Row( children: [ - StatView( - view: likeItem.stat!.view, - theme: 'gray', - ), + StatView(view: likeItem.stat!.view), const Spacer(), Text( Utils.CustomStamp_str( diff --git a/lib/pages/member_seasons/widgets/item.dart b/lib/pages/member_seasons/widgets/item.dart index 85b763b7..8d877773 100644 --- a/lib/pages/member_seasons/widgets/item.dart +++ b/lib/pages/member_seasons/widgets/item.dart @@ -78,10 +78,7 @@ class MemberSeasonsItem extends StatelessWidget { const SizedBox(height: 4), Row( children: [ - StatView( - view: seasonItem.view, - theme: 'gray', - ), + StatView(view: seasonItem.view), const Spacer(), Text( Utils.CustomStamp_str( diff --git a/lib/pages/subscription_detail/widget/sub_video_card.dart b/lib/pages/subscription_detail/widget/sub_video_card.dart index dcdee4ef..268c4a00 100644 --- a/lib/pages/subscription_detail/widget/sub_video_card.dart +++ b/lib/pages/subscription_detail/widget/sub_video_card.dart @@ -154,13 +154,9 @@ class VideoContent extends StatelessWidget { padding: const EdgeInsets.only(top: 2), child: Row( children: [ - StatView( - theme: 'gray', - view: videoItem.cntInfo['play'], - ), + StatView(view: videoItem.cntInfo['play']), const SizedBox(width: 8), - StatDanMu( - theme: 'gray', danmu: videoItem.cntInfo['danmaku']), + StatDanMu(danmu: videoItem.cntInfo['danmaku']), const Spacer(), ], ), diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index a89b4d28..f56b28e0 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -346,13 +346,11 @@ class _VideoInfoState extends State with TickerProviderStateMixin { child: Row( children: [ StatView( - theme: 'gray', view: widget.videoDetail!.stat!.view, size: 'medium', ), const SizedBox(width: 10), StatDanMu( - theme: 'gray', danmu: widget.videoDetail!.stat!.danmaku, size: 'medium', ), From 0bec084becbf1762922fa5e3d4fcc9b4b8435a4c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 12 Sep 2024 09:40:08 +0800 Subject: [PATCH 081/152] =?UTF-8?q?opt:=20=E6=94=B6=E8=97=8F=E5=A4=B9?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/user.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/http/user.dart b/lib/http/user.dart index 972acfdd..7d0b29a3 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -57,6 +57,8 @@ class UserHttp { if (res.data['data'] != null) { data = FavFolderData.fromJson(res.data['data']); return {'status': true, 'data': data}; + } else { + return {'status': false, 'msg': '收藏夹为空'}; } } else { return { From 8aebf463e5f9a4ddf08325847f270f1bb9bfaf06 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 15 Sep 2024 01:16:30 +0800 Subject: [PATCH 082/152] =?UTF-8?q?opt:=20=E6=92=AD=E6=94=BE=E5=99=A8?= =?UTF-8?q?=E9=9F=B3=E9=87=8F&=E4=BA=AE=E5=BA=A6=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/plugin/pl_player/view.dart | 110 +++--------------- lib/plugin/pl_player/widgets/control_bar.dart | 50 ++++++++ 2 files changed, 67 insertions(+), 93 deletions(-) create mode 100644 lib/plugin/pl_player/widgets/control_bar.dart diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index f3e0946b..75693445 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -26,6 +26,7 @@ import 'widgets/app_bar_ani.dart'; import 'widgets/backward_seek.dart'; import 'widgets/bottom_control.dart'; import 'widgets/common_btn.dart'; +import 'widgets/control_bar.dart'; import 'widgets/forward_seek.dart'; import 'widgets/play_pause_btn.dart'; @@ -484,104 +485,27 @@ class _PLVideoPlayerState extends State /// 音量🔊 控制条展示 Obx( - () => Align( - child: AnimatedOpacity( - curve: Curves.easeInOut, - opacity: _volumeIndicator.value ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - child: Container( - alignment: Alignment.center, - decoration: BoxDecoration( - color: const Color(0x88000000), - borderRadius: BorderRadius.circular(64.0), - ), - height: 34.0, - width: 70.0, - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - height: 34.0, - width: 28.0, - alignment: Alignment.centerRight, - child: Icon( - _volumeValue.value == 0.0 - ? Icons.volume_off - : _volumeValue.value < 0.5 - ? Icons.volume_down - : Icons.volume_up, - color: const Color(0xFFFFFFFF), - size: 20.0, - ), - ), - Expanded( - child: Text( - '${(_volumeValue.value * 100.0).round()}%', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 13.0, - color: Color(0xFFFFFFFF), - ), - ), - ), - const SizedBox(width: 6.0), - ], - ), - ), - ), + () => ControlBar( + visible: _volumeIndicator.value, + icon: _volumeValue.value < 1.0 / 3.0 + ? Icons.volume_mute + : _volumeValue.value < 2.0 / 3.0 + ? Icons.volume_down + : Icons.volume_up, + value: _volumeValue.value, ), ), /// 亮度🌞 控制条展示 Obx( - () => Align( - child: AnimatedOpacity( - curve: Curves.easeInOut, - opacity: _brightnessIndicator.value ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - child: Container( - alignment: Alignment.center, - decoration: BoxDecoration( - color: const Color(0x88000000), - borderRadius: BorderRadius.circular(64.0), - ), - height: 34.0, - width: 70.0, - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - height: 30.0, - width: 28.0, - alignment: Alignment.centerRight, - child: Icon( - _brightnessValue.value < 1.0 / 3.0 - ? Icons.brightness_low - : _brightnessValue.value < 2.0 / 3.0 - ? Icons.brightness_medium - : Icons.brightness_high, - color: const Color(0xFFFFFFFF), - size: 18.0, - ), - ), - const SizedBox(width: 2.0), - Expanded( - child: Text( - '${(_brightnessValue.value * 100.0).round()}%', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 13.0, - color: Color(0xFFFFFFFF), - ), - ), - ), - const SizedBox(width: 6.0), - ], - ), - ), - ), + () => ControlBar( + visible: _brightnessIndicator.value, + icon: _brightnessValue.value < 1.0 / 3.0 + ? Icons.brightness_low + : _brightnessValue.value < 2.0 / 3.0 + ? Icons.brightness_medium + : Icons.brightness_high, + value: _brightnessValue.value, ), ), diff --git a/lib/plugin/pl_player/widgets/control_bar.dart b/lib/plugin/pl_player/widgets/control_bar.dart new file mode 100644 index 00000000..44e2abfa --- /dev/null +++ b/lib/plugin/pl_player/widgets/control_bar.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; + +class ControlBar extends StatelessWidget { + final bool visible; + final IconData icon; + final double value; + + const ControlBar({ + Key? key, + required this.visible, + required this.icon, + required this.value, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Color color = const Color(0xFFFFFFFF); + return Align( + child: AnimatedOpacity( + curve: Curves.easeInOut, + opacity: visible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + child: IntrinsicWidth( + child: Container( + padding: const EdgeInsets.fromLTRB(10, 2, 10, 2), + decoration: BoxDecoration( + color: const Color(0x88000000), + borderRadius: BorderRadius.circular(64.0), + ), + height: 34.0, + child: Row( + children: [ + Icon(icon, color: color, size: 18.0), + const SizedBox(width: 4.0), + Container( + constraints: const BoxConstraints(minWidth: 30.0), + child: Text( + '${(value * 100.0).round()}%', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 13.0, color: color), + ), + ) + ], + ), + ), + ), + ), + ); + } +} From 389ca286a76b50c30b25b0fd2d20e05f6be90529 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 15 Sep 2024 02:03:53 +0800 Subject: [PATCH 083/152] mod: userInfo cache --- lib/utils/global_data_cache.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/utils/global_data_cache.dart b/lib/utils/global_data_cache.dart index c5884e79..9d925a0f 100644 --- a/lib/utils/global_data_cache.dart +++ b/lib/utils/global_data_cache.dart @@ -1,4 +1,5 @@ import 'package:hive/hive.dart'; +import 'package:pilipala/models/user/info.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/plugin/pl_player/models/play_speed.dart'; import 'package:pilipala/utils/storage.dart'; @@ -7,6 +8,7 @@ import '../models/common/index.dart'; Box setting = GStrorage.setting; Box localCache = GStrorage.localCache; Box videoStorage = GStrorage.video; +Box userInfoCache = GStrorage.userInfo; class GlobalDataCache { late int imgQuality; @@ -39,6 +41,8 @@ class GlobalDataCache { late double longPressSpeed; // 播放器速度列表 late List speedsList; + // 用户信息 + UserInfoData? userInfo; // 私有构造函数 GlobalDataCache._(); @@ -96,5 +100,7 @@ class GlobalDataCache { final List playSpeedSystem = await videoStorage .get(VideoBoxKey.playSpeedSystem, defaultValue: playSpeed); speedsList.addAll(playSpeedSystem); + + userInfo = userInfoCache.get('userInfoCache'); } } From d2640f230c34ce58ceaebcfc3e1731e78757ce55 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 15 Sep 2024 02:30:35 +0800 Subject: [PATCH 084/152] =?UTF-8?q?feat:=20=E8=A7=86=E9=A2=91=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 3 + lib/http/reply.dart | 21 ++++++ .../detail/reply/widgets/reply_item.dart | 64 ++++++++++++++++--- 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index 93226946..ce8279d6 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -565,4 +565,7 @@ class Api { /// 直播间发送弹幕 static const String sendLiveMsg = '${HttpString.liveBaseUrl}/msg/send'; + + /// 删除评论 + static const String replyDel = '/x/v2/reply/del'; } diff --git a/lib/http/reply.dart b/lib/http/reply.dart index 880f9072..8702abb7 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -115,4 +115,25 @@ class ReplyHttp { }; } } + + static Future replyDel({ + required int type, //replyType + required int oid, + required int rpid, + }) async { + var res = await Request().post( + Api.replyDel, + queryParameters: { + 'type': type, //type.index + 'oid': oid, + 'rpid': rpid, + 'csrf': await Request.getCsrf(), + }, + ); + if (res.data['code'] == 0) { + return {'status': true, 'msg': '删除成功'}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } } diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 26dc2e5a..c0d41069 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -10,6 +10,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'; @@ -19,6 +20,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'; @@ -49,6 +51,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( // 点击整个评论区 评论详情/回复 @@ -74,6 +78,7 @@ class ReplyItem extends StatelessWidget { return MorePanel( item: replyItem, mainFloor: true, + isOwner: isOwner, ); }, ); @@ -1033,10 +1038,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 menuActionHandler(String type) async { @@ -1072,9 +1079,45 @@ class MorePanel extends StatelessWidget { // case 'report': // SmartDialog.showToast('举报'); // break; - // case 'delete': - // SmartDialog.showToast('删除'); - // break; + case 'delete': + // 删除评论提示 + bool? isConfirm = await showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('删除评论'), + content: const Text('删除评论后,评论下所有回复将被删除,确定删除吗?'), + actions: [ + TextButton( + onPressed: () => Get.back(result: false), + child: Text('取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline)), + ), + TextButton( + onPressed: () { + Get.back(result: true); + SmartDialog.showToast('删除成功'); + }, + child: const Text('确定'), + ), + ], + ); + }, + ); + if (isConfirm == null || !isConfirm) { + return; + } + SmartDialog.showLoading(msg: '删除中...'); + var result = await ReplyHttp.replyDel( + type: item.type!, + oid: item.oid!, + rpid: item.rpid!, + ); + SmartDialog.dismiss(); + SmartDialog.showToast(result["msg"]); + + break; default: } } @@ -1083,6 +1126,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( @@ -1135,12 +1179,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)), + ), ], ), ); From 5f02c17a2d8d31c8999a6bc816f4ad79990fb84b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 18 Sep 2024 00:29:43 +0800 Subject: [PATCH 085/152] =?UTF-8?q?fix:=20=E5=85=B3=E6=B3=A8up=E4=B8=BB?= =?UTF-8?q?=E6=90=9C=E7=B4=A2ps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/follow_search/controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/follow_search/controller.dart b/lib/pages/follow_search/controller.dart index 9fd1590d..7dc47a45 100644 --- a/lib/pages/follow_search/controller.dart +++ b/lib/pages/follow_search/controller.dart @@ -48,7 +48,7 @@ class FollowSearchController extends GetxController { return {'status': true, 'data': [].obs}; } if (type == 'init') { - ps = 1; + pn = 1; } var res = await MemberHttp.getfollowSearch( mid: mid, From cf66d3be4c04eb0810576eae0ead9b14e3d007dc Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 21 Sep 2024 15:14:38 +0800 Subject: [PATCH 086/152] =?UTF-8?q?feat:=20=E7=A8=8D=E5=90=8E=E5=86=8D?= =?UTF-8?q?=E7=9C=8B&=E6=94=B6=E8=97=8F=E5=A4=B9=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E5=85=A8=E9=83=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 3 + lib/http/user.dart | 108 +++++++ lib/models/video/later.dart | 270 ++++++++++++++++++ lib/pages/fav_detail/controller.dart | 22 +- lib/pages/fav_detail/view.dart | 9 + lib/pages/later/controller.dart | 20 +- lib/pages/later/view.dart | 9 + lib/pages/video/detail/controller.dart | 127 +++++++- .../video/detail/introduction/controller.dart | 4 +- lib/pages/video/detail/introduction/view.dart | 17 +- lib/pages/video/detail/view.dart | 80 ++++++ .../detail/widgets/watch_later_list.dart | 229 +++++++++++++++ 12 files changed, 880 insertions(+), 18 deletions(-) create mode 100644 lib/models/video/later.dart create mode 100644 lib/pages/video/detail/widgets/watch_later_list.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 93226946..f24918b3 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -565,4 +565,7 @@ class Api { /// 直播间发送弹幕 static const String sendLiveMsg = '${HttpString.liveBaseUrl}/msg/send'; + + /// 稍后再看&收藏夹视频列表 + static const String mediaList = '/x/v2/medialist/resource/list'; } diff --git a/lib/http/user.dart b/lib/http/user.dart index 972acfdd..333c4038 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -1,4 +1,9 @@ +import 'dart:convert'; +import 'dart:developer'; + import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:html/parser.dart'; +import 'package:pilipala/models/video/later.dart'; import '../common/constants.dart'; import '../models/model_hot_video_item.dart'; import '../models/user/fav_detail.dart'; @@ -428,4 +433,107 @@ class UserHttp { return {'status': false, 'msg': res.data['message']}; } } + + // 稍后再看播放全部 + // static Future toViewPlayAll({required int oid, required String bvid}) async { + // var res = await Request().get( + // Api.watchLaterHtml, + // data: { + // 'oid': oid, + // 'bvid': bvid, + // }, + // ); + // String scriptContent = + // extractScriptContents(parse(res.data).body!.outerHtml)[0]; + // int startIndex = scriptContent.indexOf('{'); + // int endIndex = scriptContent.lastIndexOf('};'); + // String jsonContent = scriptContent.substring(startIndex, endIndex + 1); + // // 解析JSON字符串为Map + // Map jsonData = json.decode(jsonContent); + // // 输出解析后的数据 + // return { + // 'status': true, + // 'data': jsonData['resourceList'] + // .map((e) => MediaVideoItemModel.fromJson(e)) + // .toList() + // }; + // } + + static List extractScriptContents(String htmlContent) { + RegExp scriptRegExp = RegExp(r''); + if (headContent != null) { + final match = regex.firstMatch(headContent); + if (match != null && match.groupCount >= 1) { + final content = match.group(1); + String decodedString = Uri.decodeComponent(content!); + Map map = jsonDecode(decodedString); + return {'status': true, 'data': map['access_id']}; + } else { + return {'status': false, 'data': '请检查登录状态'}; + } + } + return {'status': false, 'data': '请检查登录状态'}; + } + + // 获取用户专栏 + static Future getMemberArticle({ + required int mid, + required int pn, + required String wWebid, + String? offset, + }) async { + Map params = await WbiSign().makSign({ + 'host_mid': mid, + 'page': pn, + 'offset': offset, + 'web_location': 333.999, + 'w_webid': wWebid, + }); + var res = await Request().get(Api.opusList, data: { + 'host_mid': mid, + 'page': pn, + 'offset': offset, + 'web_location': 333.999, + 'w_webid': wWebid, + 'w_rid': params['w_rid'], + 'wts': params['wts'], + }); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': MemberArticleDataModel.fromJson(res.data['data']) + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'] ?? '请求异常', + }; + } + } } diff --git a/lib/models/member/article.dart b/lib/models/member/article.dart new file mode 100644 index 00000000..8489385e --- /dev/null +++ b/lib/models/member/article.dart @@ -0,0 +1,46 @@ +class MemberArticleDataModel { + MemberArticleDataModel({ + this.hasMore, + this.items, + this.offset, + this.updateNum, + }); + + bool? hasMore; + List? items; + String? offset; + int? updateNum; + + MemberArticleDataModel.fromJson(Map json) { + hasMore = json['has_more']; + items = json['items'] + .map((e) => MemberArticleItemModel.fromJson(e)) + .toList(); + offset = json['offset']; + updateNum = json['update_num']; + } +} + +class MemberArticleItemModel { + MemberArticleItemModel({ + this.content, + this.cover, + this.jumpUrl, + this.opusId, + this.stat, + }); + + String? content; + Map? cover; + String? jumpUrl; + String? opusId; + Map? stat; + + MemberArticleItemModel.fromJson(Map json) { + content = json['content']; + cover = json['cover']; + jumpUrl = json['jump_url']; + opusId = json['opus_id']; + stat = json['stat']; + } +} diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index ada869b5..3b7f24a4 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -240,4 +240,6 @@ class MemberController extends GetxController { } void pushfavPage() => Get.toNamed('/fav?mid=$mid'); + // 跳转图文专栏 + void pushArticlePage() => Get.toNamed('/memberArticle?mid=$mid'); } diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index be0ddedc..2939628c 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -170,32 +170,44 @@ class _MemberPageState extends State ), /// 视频 - Obx(() => ListTile( - onTap: _memberController.pushArchivesPage, - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的投稿'), - trailing: const Icon(Icons.arrow_forward_outlined, - size: 19), - )), + Obx( + () => ListTile( + onTap: _memberController.pushArchivesPage, + title: Text( + '${_memberController.isOwner.value ? '我' : 'Ta'}的投稿'), + trailing: + const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), /// 他的收藏夹 - Obx(() => ListTile( - onTap: _memberController.pushfavPage, - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的收藏'), - trailing: const Icon(Icons.arrow_forward_outlined, - size: 19), - )), + Obx( + () => ListTile( + onTap: _memberController.pushfavPage, + title: Text( + '${_memberController.isOwner.value ? '我' : 'Ta'}的收藏'), + trailing: + const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), /// 专栏 - Obx(() => ListTile( + Obx( + () => ListTile( + onTap: _memberController.pushArticlePage, title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的专栏'))), + '${_memberController.isOwner.value ? '我' : 'Ta'}的专栏'), + trailing: + const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), /// 合集 - Obx(() => ListTile( - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的合集'))), + Obx( + () => ListTile( + title: Text( + '${_memberController.isOwner.value ? '我' : 'Ta'}的合集')), + ), MediaQuery.removePadding( removeTop: true, removeBottom: true, diff --git a/lib/pages/member_article/controller.dart b/lib/pages/member_article/controller.dart new file mode 100644 index 00000000..cffce2fe --- /dev/null +++ b/lib/pages/member_article/controller.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/http/member.dart'; +import 'package:pilipala/models/member/article.dart'; + +class MemberArticleController extends GetxController { + final ScrollController scrollController = ScrollController(); + late int mid; + int pn = 1; + String? offset; + bool hasMore = true; + String? wWebid; + RxBool isLoading = false.obs; + RxList articleList = [].obs; + + @override + void onInit() { + super.onInit(); + mid = int.parse(Get.parameters['mid']!); + } + + // 获取wWebid + Future getWWebid() async { + var res = await MemberHttp.getWWebid(mid: mid); + if (res['status']) { + wWebid = res['data']; + } else { + wWebid = '-1'; + SmartDialog.showToast(res['msg']); + } + } + + Future getMemberArticle(type) async { + if (isLoading.value) { + return; + } + isLoading.value = true; + if (wWebid == null) { + await getWWebid(); + } + if (type == 'init') { + pn = 1; + articleList.clear(); + } + var res = await MemberHttp.getMemberArticle( + mid: mid, + pn: pn, + offset: offset, + wWebid: wWebid!, + ); + if (res['status']) { + offset = res['data'].offset; + hasMore = res['data'].hasMore!; + if (type == 'init') { + articleList.value = res['data'].items; + } + if (type == 'onLoad') { + articleList.addAll(res['data'].items); + } + pn += 1; + } else { + SmartDialog.showToast(res['msg']); + } + isLoading.value = false; + return res; + } +} diff --git a/lib/pages/member_article/index.dart b/lib/pages/member_article/index.dart new file mode 100644 index 00000000..bfc2f344 --- /dev/null +++ b/lib/pages/member_article/index.dart @@ -0,0 +1,4 @@ +library member_article; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/member_article/view.dart b/lib/pages/member_article/view.dart new file mode 100644 index 00000000..e23e208d --- /dev/null +++ b/lib/pages/member_article/view.dart @@ -0,0 +1,176 @@ +import 'package:easy_debounce/easy_throttle.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/skeleton/skeleton.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/common/widgets/no_data.dart'; +import 'package:pilipala/utils/utils.dart'; + +import 'controller.dart'; + +class MemberArticlePage extends StatefulWidget { + const MemberArticlePage({super.key}); + + @override + State createState() => _MemberArticlePageState(); +} + +class _MemberArticlePageState extends State { + late MemberArticleController _memberArticleController; + late Future _futureBuilderFuture; + late ScrollController scrollController; + late int mid; + + @override + void initState() { + super.initState(); + mid = int.parse(Get.parameters['mid']!); + final String heroTag = Utils.makeHeroTag(mid); + _memberArticleController = Get.put(MemberArticleController(), tag: heroTag); + _futureBuilderFuture = _memberArticleController.getMemberArticle('init'); + scrollController = _memberArticleController.scrollController; + + scrollController.addListener(_scrollListener); + } + + void _scrollListener() { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { + EasyThrottle.throttle( + 'member_archives', const Duration(milliseconds: 500), () { + _memberArticleController.getMemberArticle('onLoad'); + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + titleSpacing: 0, + centerTitle: false, + title: const Text('Ta的图文', style: TextStyle(fontSize: 16)), + ), + body: FutureBuilder( + future: _futureBuilderFuture, + builder: (BuildContext context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data != null) { + return _buildContent(snapshot.data as Map); + } else { + return _buildError(snapshot.data['msg']); + } + } else { + return ListView.builder( + itemCount: 10, + itemBuilder: (BuildContext context, int index) { + return _buildSkeleton(); + }, + ); + } + }, + ), + ); + } + + Widget _buildContent(Map data) { + RxList list = _memberArticleController.articleList; + if (data['status']) { + return Obx( + () => list.isNotEmpty + ? ListView.separated( + controller: scrollController, + itemCount: list.length, + separatorBuilder: (BuildContext context, int index) { + return Divider( + height: 10, + color: Theme.of(context).dividerColor.withOpacity(0.15), + ); + }, + itemBuilder: (BuildContext context, int index) { + return _buildListItem(list[index]); + }, + ) + : const CustomScrollView( + physics: NeverScrollableScrollPhysics(), + slivers: [ + NoData(), + ], + ), + ); + } else { + return _buildError(data['msg']); + } + } + + Widget _buildListItem(dynamic item) { + return ListTile( + onTap: () { + Get.toNamed('/opus', parameters: { + 'title': item.content, + 'id': item.opusId, + 'articleType': 'opus', + }); + }, + leading: NetworkImgLayer( + width: 50, + height: 50, + type: 'emote', + src: item.cover['url'], + ), + title: Text( + item.content, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + subtitle: Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + '${item.stat["like"]}人点赞', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + ); + } + + Widget _buildError(String errMsg) { + return CustomScrollView( + physics: const NeverScrollableScrollPhysics(), + slivers: [ + SliverToBoxAdapter( + child: HttpError( + errMsg: errMsg, + fn: () {}, + ), + ), + ], + ); + } + + Widget _buildSkeleton() { + return Skeleton( + child: ListTile( + leading: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onInverseSurface, + borderRadius: BorderRadius.circular(4), + ), + ), + title: Container( + height: 16, + color: Theme.of(context).colorScheme.onInverseSurface, + ), + subtitle: Container( + height: 11, + color: Theme.of(context).colorScheme.onInverseSurface, + ), + ), + ); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 136de91f..a679fd79 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/pages/fav_edit/index.dart'; import 'package:pilipala/pages/follow_search/view.dart'; +import 'package:pilipala/pages/member_article/index.dart'; import 'package:pilipala/pages/message/at/index.dart'; import 'package:pilipala/pages/message/like/index.dart'; import 'package:pilipala/pages/message/reply/index.dart'; @@ -186,6 +187,9 @@ class Routes { name: '/messageSystem', page: () => const MessageSystemPage()), // 收藏夹编辑 CustomGetPage(name: '/favEdit', page: () => const FavEditPage()), + // 用户专栏 + CustomGetPage( + name: '/memberArticle', page: () => const MemberArticlePage()), ]; } From aa6b5af05df17894541511be4dbfd6dc3b4a72b9 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 28 Sep 2024 17:24:23 +0800 Subject: [PATCH 105/152] =?UTF-8?q?fix:=20=E9=87=8D=E5=AE=9A=E5=90=91cv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/read.dart | 25 +++++++++++++++++++++++++ lib/pages/opus/controller.dart | 11 ++++++++++- lib/pages/opus/view.dart | 3 +++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/http/read.dart b/lib/http/read.dart index 4fff4547..cc522505 100644 --- a/lib/http/read.dart +++ b/lib/http/read.dart @@ -20,6 +20,29 @@ class ReadHttp { var res = await Request().get('https://www.bilibili.com/opus/$id', extra: { 'ua': 'pc', }); + String? headContent = parse(res.data).head?.outerHtml; + var document = parse(headContent); + var linkTags = document.getElementsByTagName('link'); + bool isCv = false; + String cvId = ''; + for (var linkTag in linkTags) { + var attributes = linkTag.attributes; + if (attributes.containsKey('rel') && + attributes['rel'] == 'canonical' && + attributes.containsKey('data-vue-meta') && + attributes['data-vue-meta'] == 'true') { + final String cvHref = linkTag.attributes['href']!; + RegExp regex = RegExp(r'cv(\d+)'); + RegExpMatch? match = regex.firstMatch(cvHref); + if (match != null) { + cvId = match.group(1)!; + } else { + print('No match found.'); + } + isCv = true; + break; + } + } String scriptContent = extractScriptContents(parse(res.data).body!.outerHtml)[0]; int startIndex = scriptContent.indexOf('{'); @@ -30,6 +53,8 @@ class ReadHttp { return { 'status': true, 'data': OpusDataModel.fromJson(jsonData), + 'isCv': isCv, + 'cvId': cvId, }; } } diff --git a/lib/pages/opus/controller.dart b/lib/pages/opus/controller.dart index f5c35770..86dd67a5 100644 --- a/lib/pages/opus/controller.dart +++ b/lib/pages/opus/controller.dart @@ -31,7 +31,16 @@ class OpusController extends GetxController { Future fetchOpusData() async { var res = await ReadHttp.parseArticleOpus(id: id); if (res['status']) { - opusData.value = res['data']; + List keys = res.keys.toList(); + if (keys.contains('isCv') && res['isCv']) { + Get.offNamed('/read', parameters: { + 'id': res['cvId'], + 'title': title.value, + 'articleType': 'cv', + }); + } else { + opusData.value = res['data']; + } } return res; } diff --git a/lib/pages/opus/view.dart b/lib/pages/opus/view.dart index a36b4813..8535230f 100644 --- a/lib/pages/opus/view.dart +++ b/lib/pages/opus/view.dart @@ -107,6 +107,9 @@ class _OpusPageState extends State { } Widget _buildContent(OpusDataModel opusData) { + if (opusData.detail == null) { + return const SizedBox(); + } final modules = opusData.detail!.modules!; late ModuleContent moduleContent; // 获取所有的图片链接 From 26a57336750d08a48813650f202d6fdd180e6b4a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 28 Sep 2024 18:22:42 +0800 Subject: [PATCH 106/152] =?UTF-8?q?feat:=20cv=E4=B8=93=E6=A0=8F=E8=AE=B0?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 3 +++ lib/http/read.dart | 39 ++++++++++++++++++++++++++++++++-- lib/pages/read/controller.dart | 5 +++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index 42819d7d..ff49b314 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -578,4 +578,7 @@ class Api { /// 稍后再看&收藏夹视频列表 static const String mediaList = '/x/v2/medialist/resource/list'; + + /// + static const String getViewInfo = '/x/article/viewinfo'; } diff --git a/lib/http/read.dart b/lib/http/read.dart index 22ca3503..558985b4 100644 --- a/lib/http/read.dart +++ b/lib/http/read.dart @@ -1,9 +1,9 @@ import 'dart:convert'; -import 'dart:developer'; import 'package:html/parser.dart'; import 'package:pilipala/models/read/opus.dart'; import 'package:pilipala/models/read/read.dart'; -import 'init.dart'; +import 'package:pilipala/utils/wbi_sign.dart'; +import 'index.dart'; class ReadHttp { static List extractScriptContents(String htmlContent) { @@ -53,4 +53,39 @@ class ReadHttp { 'data': ReadDataModel.fromJson(jsonData), }; } + + // + static Future getViewInfo({required String id}) async { + Map params = await WbiSign().makSign({ + 'id': id, + 'mobi_app': 'pc', + 'from': 'web', + 'gaia_source': 'main_web', + 'web_location': 333.976, + }); + var res = await Request().get( + Api.getViewInfo, + data: { + 'id': id, + 'mobi_app': 'pc', + 'from': 'web', + 'gaia_source': 'main_web', + 'web_location': 333.976, + 'w_rid': params['w_rid'], + 'wts': params['wts'], + }, + ); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/pages/read/controller.dart b/lib/pages/read/controller.dart index d2e942fc..a0e4ef8e 100644 --- a/lib/pages/read/controller.dart +++ b/lib/pages/read/controller.dart @@ -23,6 +23,7 @@ class ReadPageController extends GetxController { id = Get.parameters['id']!; articleType = Get.parameters['articleType']!; scrollController.addListener(_scrollListener); + fetchViewInfo(); } Future fetchCvData() async { @@ -80,6 +81,10 @@ class ReadPageController extends GetxController { ); } + void fetchViewInfo() { + ReadHttp.getViewInfo(id: id); + } + @override void onClose() { scrollController.removeListener(_scrollListener); From b34fc2ff1f9b796c1e399335c4f71b7d307d263c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 28 Sep 2024 23:16:00 +0800 Subject: [PATCH 107/152] =?UTF-8?q?mod:=20=E7=B3=BB=E7=BB=9F=E6=B6=88?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 4 +++ lib/http/msg.dart | 23 +++++++++++++ lib/models/msg/system.dart | 15 +++++++-- lib/pages/message/system/controller.dart | 43 +++++++++++++++++++----- lib/pages/message/system/view.dart | 7 ++-- 5 files changed, 78 insertions(+), 14 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index 6c5374dd..588bd058 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -555,6 +555,10 @@ class Api { static const String messageSystemAPi = '${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify'; + /// 系统通知 个人 + static const String userMessageSystemAPi = + '${HttpString.messageBaseUrl}/x/sys-msg/query_user_notify'; + /// 系统通知标记已读 static const String systemMarkRead = '${HttpString.messageBaseUrl}/x/sys-msg/update_cursor'; diff --git a/lib/http/msg.dart b/lib/http/msg.dart index 2de9cd49..869b5a28 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -330,4 +330,27 @@ class MsgHttp { }; } } + + static Future messageSystemAccount() async { + var res = await Request().get(Api.userMessageSystemAPi, data: { + 'csrf': await Request.getCsrf(), + 'page_size': 20, + 'build': 0, + 'mobi_app': 'web', + }); + if (res.data['code'] == 0) { + try { + return { + 'status': true, + 'data': res.data['data']['system_notify_list'] + .map((e) => MessageSystemModel.fromJson(e)) + .toList(), + }; + } catch (err) { + return {'status': false, 'date': [], 'msg': err.toString()}; + } + } else { + return {'status': false, 'date': [], 'msg': res.data['message']}; + } + } } diff --git a/lib/models/msg/system.dart b/lib/models/msg/system.dart index 20427707..12b1ac77 100644 --- a/lib/models/msg/system.dart +++ b/lib/models/msg/system.dart @@ -5,7 +5,7 @@ class MessageSystemModel { int? cursor; int? type; String? title; - Map? content; + dynamic content; Source? source; String? timeAt; int? cardType; @@ -45,7 +45,9 @@ class MessageSystemModel { cursor: jsons["cursor"], type: jsons["type"], title: jsons["title"], - content: json.decode(jsons["content"]), + content: isValidJson(jsons["content"]) + ? json.decode(jsons["content"]) + : jsons["content"], source: Source.fromJson(jsons["source"]), timeAt: jsons["time_at"], cardType: jsons["card_type"], @@ -75,3 +77,12 @@ class Source { logo: json["logo"], ); } + +bool isValidJson(String str) { + try { + json.decode(str); + } catch (e) { + return false; + } + return true; +} diff --git a/lib/pages/message/system/controller.dart b/lib/pages/message/system/controller.dart index f63a659a..c4731120 100644 --- a/lib/pages/message/system/controller.dart +++ b/lib/pages/message/system/controller.dart @@ -1,3 +1,4 @@ +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/http/msg.dart'; import 'package:pilipala/models/msg/system.dart'; @@ -5,18 +6,44 @@ import 'package:pilipala/models/msg/system.dart'; class MessageSystemController extends GetxController { RxList systemItems = [].obs; - Future queryMessageSystem({String type = 'init'}) async { - var res = await MsgHttp.messageSystem(); - if (res['status']) { - if (type == 'init') { - systemItems.value = res['data']; - } else { - systemItems.addAll(res['data']); - } + Future queryAndProcessMessages({String type = 'init'}) async { + // 并行调用两个接口 + var results = await Future.wait([ + queryMessageSystem(type: type), + queryMessageSystemAccount(type: type), + ]); + + // 对返回的数据进行处理 + var systemRes = results[0]; + var accountRes = results[1]; + + if (systemRes['status'] || accountRes['status']) { + // 处理返回的数据 + List combinedData = [ + ...systemRes['data'], + ...accountRes['data'] + ]; + combinedData.sort((a, b) => b.cursor!.compareTo(a.cursor!)); + systemItems.addAll(combinedData); + systemItems.refresh(); if (systemItems.isNotEmpty) { systemMarkRead(systemItems.first.cursor!); } + } else { + SmartDialog.showToast(systemRes['msg'] ?? accountRes['msg']); } + return systemRes; + } + + // 获取系统消息 + Future queryMessageSystem({String type = 'init'}) async { + var res = await MsgHttp.messageSystem(); + return res; + } + + // 获取系统消息 个人 + Future queryMessageSystemAccount({String type = 'init'}) async { + var res = await MsgHttp.messageSystemAccount(); return res; } diff --git a/lib/pages/message/system/view.dart b/lib/pages/message/system/view.dart index f7b94e5a..ee10b639 100644 --- a/lib/pages/message/system/view.dart +++ b/lib/pages/message/system/view.dart @@ -20,7 +20,7 @@ class _MessageSystemPageState extends State { @override void initState() { super.initState(); - _futureBuilderFuture = _messageSystemCtr.queryMessageSystem(); + _futureBuilderFuture = _messageSystemCtr.queryAndProcessMessages(); } @override @@ -31,7 +31,7 @@ class _MessageSystemPageState extends State { ), body: RefreshIndicator( onRefresh: () async { - await _messageSystemCtr.queryMessageSystem(); + await _messageSystemCtr.queryAndProcessMessages(); }, child: FutureBuilder( future: _futureBuilderFuture, @@ -42,7 +42,6 @@ class _MessageSystemPageState extends State { } if (snapshot.data['status']) { final systemItems = _messageSystemCtr.systemItems; - print(systemItems.length); return Obx( () => ListView.separated( controller: scrollController, @@ -115,7 +114,7 @@ class SystemItem extends StatelessWidget { style: TextStyle(color: Theme.of(context).colorScheme.outline), ), const SizedBox(height: 6), - Text(item.content!['web']), + Text(item.content is String ? item.content : item.content!['web']), ], ), ); From 166dceb1bd6a04ea92cb5ae8845d5805a9cf3436 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 28 Sep 2024 23:21:17 +0800 Subject: [PATCH 108/152] typo --- lib/pages/setting/style_setting.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 5fca0c86..64e4bdb8 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -103,7 +103,7 @@ class _StyleSettingState extends State { needReboot: true, ), const SetSwitchItem( - title: '首页底栏背景渐变', + title: '首页顶部背景渐变', setKey: SettingBoxKey.enableGradientBg, defaultVal: true, needReboot: true, From f95bcb2e6cc3c05608c025a53fa49d7c65d67f2b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 28 Sep 2024 23:52:50 +0800 Subject: [PATCH 109/152] =?UTF-8?q?mod:=20=E8=AF=84=E8=AE=BA=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/reply/widgets/reply_item.dart | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index c0d41069..3d6de86a 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -1081,7 +1081,7 @@ class MorePanel extends StatelessWidget { // break; case 'delete': // 删除评论提示 - bool? isConfirm = await showDialog( + await showDialog( context: Get.context!, builder: (context) { return AlertDialog( @@ -1089,15 +1089,25 @@ class MorePanel extends StatelessWidget { content: const Text('删除评论后,评论下所有回复将被删除,确定删除吗?'), actions: [ TextButton( - onPressed: () => Get.back(result: false), + onPressed: () => Get.back(), child: Text('取消', style: TextStyle( color: Theme.of(context).colorScheme.outline)), ), TextButton( - onPressed: () { - Get.back(result: true); - SmartDialog.showToast('删除成功'); + 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('确定'), ), @@ -1105,18 +1115,6 @@ class MorePanel extends StatelessWidget { ); }, ); - if (isConfirm == null || !isConfirm) { - return; - } - SmartDialog.showLoading(msg: '删除中...'); - var result = await ReplyHttp.replyDel( - type: item.type!, - oid: item.oid!, - rpid: item.rpid!, - ); - SmartDialog.dismiss(); - SmartDialog.showToast(result["msg"]); - break; default: } From ec5cc6ff91d42a89759a1d6b06568c7ee96ebaa4 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 29 Sep 2024 01:01:45 +0800 Subject: [PATCH 110/152] =?UTF-8?q?feat:=20=E7=9B=B4=E6=92=AD=E9=97=B4?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 4 ++++ lib/http/live.dart | 11 +++++++++++ lib/pages/live_room/controller.dart | 13 +++++++++++++ 3 files changed, 28 insertions(+) diff --git a/lib/http/api.dart b/lib/http/api.dart index 93226946..9cff2644 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -565,4 +565,8 @@ class Api { /// 直播间发送弹幕 static const String sendLiveMsg = '${HttpString.liveBaseUrl}/msg/send'; + + /// 直播间记录 + static const String liveRoomEntry = + '${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction'; } diff --git a/lib/http/live.dart b/lib/http/live.dart index f6fc4ea4..3935f154 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -117,4 +117,15 @@ class LiveHttp { }; } } + + // 直播历史记录 + static Future liveRoomEntry({required int roomId}) async { + await Request().post(Api.liveRoomEntry, queryParameters: { + 'room_id': roomId, + 'platform': 'pc', + 'csrf_token': await Request.getCsrf(), + 'csrf': await Request.getCsrf(), + 'visit_id': '', + }); + } } diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 5d4e2b67..fdc25216 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -95,6 +95,7 @@ class LiveRoomController extends GetxController { autoplay: true, ); plPlayerController.isOpenDanmu.value = danmakuSwitch.value; + heartBeat(); } Future queryLiveInfo() async { @@ -278,8 +279,20 @@ class LiveRoomController extends GetxController { } } + // 历史记录 + void heartBeat() { + LiveHttp.liveRoomEntry(roomId: roomId); + } + + String encodeToBase64(String input) { + List bytes = utf8.encode(input); + String base64Str = base64.encode(bytes); + return base64Str; + } + @override void onClose() { + heartBeat(); plSocket?.onClose(); super.onClose(); } From 79c148adebce3dc4dff3512f75daf6e964420c55 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 29 Sep 2024 01:12:43 +0800 Subject: [PATCH 111/152] =?UTF-8?q?fix:=20=E9=93=BE=E6=8E=A5=E5=85=9C?= =?UTF-8?q?=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/webview/controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/webview/controller.dart b/lib/pages/webview/controller.dart index e0ff113c..0fa24dea 100644 --- a/lib/pages/webview/controller.dart +++ b/lib/pages/webview/controller.dart @@ -95,6 +95,6 @@ class WebviewController extends GetxController { }, ), ) - ..loadRequest(Uri.parse(url)); + ..loadRequest(Uri.parse(url.startsWith('http') ? url : 'https://$url')); } } From 400b87395a1e8442765e83a922548e5d38758048 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 29 Sep 2024 12:00:38 +0800 Subject: [PATCH 112/152] =?UTF-8?q?mod:=20=E6=A0=87=E9=A2=98=E6=9C=80?= =?UTF-8?q?=E5=A4=A7=E8=A1=8C=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/introduction/view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 27e39760..ef49a2ef 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -324,7 +324,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { expanded: Text( widget.videoDetail!.title!, softWrap: true, - maxLines: 4, + maxLines: 10, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, From 11aa465a8f9aee7fdc63476df59d4e42d7d2d724 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 29 Sep 2024 14:01:26 +0800 Subject: [PATCH 113/152] =?UTF-8?q?feat:=20=E7=A7=81=E4=BF=A1=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/whisper/view.dart | 17 +++--- lib/pages/whisper_detail/controller.dart | 13 +++++ lib/pages/whisper_detail/view.dart | 28 ++++------ .../whisper_detail/widget/chat_item.dart | 54 +++++++++++++++++-- 4 files changed, 83 insertions(+), 29 deletions(-) diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 7082619f..e97aa79b 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -217,6 +217,7 @@ class SessionItem extends StatelessWidget { final String heroTag = Utils.makeHeroTag(sessionItem.accountInfo?.mid ?? 0); final content = sessionItem.lastMsg.content; final msgStatus = sessionItem.lastMsg.msgStatus; + final int msgType = sessionItem.lastMsg.msgType; return ListTile( onTap: () { @@ -251,13 +252,15 @@ class SessionItem extends StatelessWidget { subtitle: Text( msgStatus == 1 ? '你撤回了一条消息' - : content != null && content != '' - ? (content['text'] ?? - content['content'] ?? - content['title'] ?? - content['reply_content'] ?? - '不支持的消息类型') - : '不支持的消息类型', + : msgType == 2 + ? '[图片]' + : content != null && content != '' + ? (content['text'] ?? + content['content'] ?? + content['title'] ?? + content['reply_content'] ?? + '不支持的消息类型') + : '不支持的消息类型', maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context) diff --git a/lib/pages/whisper_detail/controller.dart b/lib/pages/whisper_detail/controller.dart index 32e0ceb0..ec828afb 100644 --- a/lib/pages/whisper_detail/controller.dart +++ b/lib/pages/whisper_detail/controller.dart @@ -22,6 +22,7 @@ class WhisperDetailController extends GetxController { final TextEditingController replyContentController = TextEditingController(); Box userInfoCache = GStrorage.userInfo; List emoteList = []; + List picList = []; @override void onInit() { @@ -41,6 +42,18 @@ class WhisperDetailController extends GetxController { var res = await MsgHttp.sessionMsg(talkerId: talkerId); if (res['status']) { messageList.value = res['data'].messages; + // 找出图片 + try { + for (var item in messageList) { + if (item.msgType == 2) { + picList.add(item.content['url']); + } + } + picList = picList.reversed.toList(); + } catch (e) { + print('e: $e'); + } + if (messageList.isNotEmpty) { ackSessionMsg(); if (res['data'].eInfos != null) { diff --git a/lib/pages/whisper_detail/view.dart b/lib/pages/whisper_detail/view.dart index 912b5dc5..3ea59343 100644 --- a/lib/pages/whisper_detail/view.dart +++ b/lib/pages/whisper_detail/view.dart @@ -193,27 +193,21 @@ class _WhisperDetailPageState extends State ? const SizedBox() : Align( alignment: Alignment.topCenter, - child: ListView.builder( + child: ListView.separated( itemCount: messageList.length, shrinkWrap: true, reverse: true, itemBuilder: (_, int i) { - if (i == 0) { - return Column( - children: [ - ChatItem( - item: messageList[i], - e_infos: _whisperDetailController - .eInfos), - const SizedBox(height: 20), - ], - ); - } else { - return ChatItem( - item: messageList[i], - e_infos: - _whisperDetailController.eInfos); - } + return ChatItem( + item: messageList[i], + e_infos: _whisperDetailController.eInfos, + ctr: _whisperDetailController, + ); + }, + separatorBuilder: (_, int i) { + return i == 0 + ? const SizedBox(height: 20) + : const SizedBox.shrink(); }, ), ), diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index 94347aff..7ddd1d83 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -2,14 +2,18 @@ // ignore_for_file: constant_identifier_names import 'dart:convert'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart'; +import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart'; import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/storage.dart'; import '../../../http/search.dart'; +import '../controller.dart'; enum MsgType { invalid(value: 0, label: "空空的~"), @@ -42,10 +46,12 @@ enum MsgType { class ChatItem extends StatelessWidget { dynamic item; List? e_infos; + WhisperDetailController ctr; ChatItem({ super.key, - this.item, + required this.item, + required this.ctr, this.e_infos, }); @@ -157,10 +163,48 @@ class ChatItem extends StatelessWidget { case MsgType.text: return richTextMessage(context); case MsgType.pic: - return NetworkImgLayer( - width: 220, - height: 220 * content['height'] / content['width'], - src: content['url'], + return InkWell( + onTap: () { + Navigator.of(context).push( + HeroDialogRoute( + builder: (BuildContext context) => InteractiveviewerGallery( + sources: ctr.picList, + initIndex: ctr.picList.indexOf(content['url']), + itemBuilder: ( + BuildContext context, + int index, + bool isFocus, + bool enablePageView, + ) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (enablePageView) { + Navigator.of(context).pop(); + } + }, + child: Center( + child: Hero( + tag: ctr.picList[index], + child: CachedNetworkImage( + fadeInDuration: const Duration(milliseconds: 0), + imageUrl: ctr.picList[index], + fit: BoxFit.contain, + ), + ), + ), + ); + }, + onPageChanged: (int pageIndex) {}, + ), + ), + ); + }, + child: NetworkImgLayer( + width: 220, + height: 220 * content['height'] / content['width'], + src: content['url'], + ), ); case MsgType.share_v2: return Column( From b6390ea626f3aaa0e02b17761b867f17095cd612 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 29 Sep 2024 14:07:21 +0800 Subject: [PATCH 114/152] =?UTF-8?q?fix:=20read=E4=B8=93=E6=A0=8F=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E6=B5=8F=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/html_render.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/common/widgets/html_render.dart b/lib/common/widgets/html_render.dart index c626978e..b2aa75ff 100644 --- a/lib/common/widgets/html_render.dart +++ b/lib/common/widgets/html_render.dart @@ -1,11 +1,9 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; -import 'package:get/get.dart'; import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart'; import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart'; import 'package:pilipala/utils/highlight.dart'; -import 'network_img_layer.dart'; // ignore: must_be_immutable class HtmlRender extends StatelessWidget { @@ -85,11 +83,11 @@ class HtmlRender extends StatelessWidget { }, child: Center( child: Hero( - tag: imgUrl, + tag: imgList?[index] ?? imgUrl, child: CachedNetworkImage( fadeInDuration: const Duration(milliseconds: 0), - imageUrl: imgUrl, + imageUrl: imgList?[index] ?? imgUrl, fit: BoxFit.contain, ), ), From c3d23dfdba23325ec3b93c79054bfc0f656580c3 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 29 Sep 2024 15:29:08 +0800 Subject: [PATCH 115/152] typo --- lib/pages/video/detail/controller.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index c8be4f43..d7b1529a 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -115,7 +115,7 @@ class VideoDetailController extends GetxController ].obs; RxDouble sheetHeight = 0.0.obs; RxString archiveSourceType = 'dash'.obs; - ScrollController? replyScrillController; + ScrollController? replyScrollController; List mediaList = []; RxBool isWatchLaterVisible = false.obs; RxString watchLaterTitle = ''.obs; @@ -574,12 +574,12 @@ class VideoDetailController extends GetxController } void onControllerCreated(ScrollController controller) { - replyScrillController = controller; + replyScrollController = controller; } void onTapTabbar(int index) { if (index == 1 && tabCtr.index == 1) { - replyScrillController?.animateTo(0, + replyScrollController?.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.ease); } } From ed9a75ecd3c46bf91af15fbe30b264f2f6ebcc5c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 30 Sep 2024 11:23:40 +0800 Subject: [PATCH 116/152] =?UTF-8?q?fix:=20=E8=A7=86=E9=A2=91=E6=80=BB?= =?UTF-8?q?=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/video/ai.dart | 8 ++++---- .../video/detail/introduction/controller.dart | 4 ++-- lib/pages/video/detail/widgets/ai_detail.dart | 19 +++++++++++-------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/models/video/ai.dart b/lib/models/video/ai.dart index a06fa79d..0816c09d 100644 --- a/lib/models/video/ai.dart +++ b/lib/models/video/ai.dart @@ -39,11 +39,11 @@ class ModelResult { ModelResult.fromJson(Map json) { resultType = json['result_type']; summary = json['summary']; - outline = json['result_type'] == 2 - ? json['outline'] + outline = json['result_type'] == 0 + ? [] + : json['outline'] .map((e) => OutlineItem.fromJson(e)) - .toList() - : []; + .toList(); } } diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 04a1249a..c0f6eebb 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -541,7 +541,7 @@ class VideoIntroController extends GetxController { // ai总结 Future aiConclusion() async { - SmartDialog.showLoading(msg: '正在生产ai总结'); + SmartDialog.showLoading(msg: '正在生成ai总结'); final res = await VideoHttp.aiConclusion( bvid: bvid, cid: lastPlayCid.value, @@ -551,7 +551,7 @@ class VideoIntroController extends GetxController { if (res['status']) { modelResult = res['data'].modelResult; } else { - SmartDialog.showToast("当前视频可能暂不支持AI视频总结"); + SmartDialog.showToast("当前视频暂不支持AI视频总结"); } return res; } diff --git a/lib/pages/video/detail/widgets/ai_detail.dart b/lib/pages/video/detail/widgets/ai_detail.dart index 37d51106..b71b026d 100644 --- a/lib/pages/video/detail/widgets/ai_detail.dart +++ b/lib/pages/video/detail/widgets/ai_detail.dart @@ -49,15 +49,18 @@ class AiDetail extends StatelessWidget { child: SingleChildScrollView( child: Column( children: [ - SelectableText( - modelResult!.summary!, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - height: 1.5, + if (modelResult!.resultType != 0 && + modelResult!.summary != '') ...[ + SelectableText( + modelResult!.summary!, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + height: 1.5, + ), ), - ), - const SizedBox(height: 20), + const SizedBox(height: 20), + ], ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), From 66d2ac9777bb3038e792d3d6e0ef7cc0c5eac921 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 30 Sep 2024 11:38:39 +0800 Subject: [PATCH 117/152] =?UTF-8?q?fix:=20=20=E7=B2=89=E4=B8=9D=E4=B8=8D?= =?UTF-8?q?=E5=8F=AF=E8=A7=81=E6=97=B6=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/fan/controller.dart | 1 - lib/pages/fan/view.dart | 14 +++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/pages/fan/controller.dart b/lib/pages/fan/controller.dart index c1c2a427..6661d9fe 100644 --- a/lib/pages/fan/controller.dart +++ b/lib/pages/fan/controller.dart @@ -49,7 +49,6 @@ class FansController extends GetxController { } else if (type == 'onLoad') { fansList.addAll(res['data'].list); } - print(total); if ((pn == 1 && total < ps) || res['data'].list.isEmpty) { loadingText.value = '没有更多了'; } diff --git a/lib/pages/fan/view.dart b/lib/pages/fan/view.dart index 47372057..5d5c02a7 100644 --- a/lib/pages/fan/view.dart +++ b/lib/pages/fan/view.dart @@ -103,9 +103,17 @@ class _FansPageState extends State { ), ); } else { - return HttpError( - errMsg: data['msg'], - fn: () => _fansController.queryFans('init'), + return CustomScrollView( + physics: const NeverScrollableScrollPhysics(), + slivers: [ + HttpError( + errMsg: data['msg'], + fn: () { + _futureBuilderFuture = + _fansController.queryFans('init'); + }, + ) + ], ); } } else { From 19866fe08054fe16b418f49017b41dff27a5c82a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 30 Sep 2024 12:00:30 +0800 Subject: [PATCH 118/152] =?UTF-8?q?fix:=20=E8=A7=86=E9=A2=91=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E5=9B=9E=E9=A1=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index d7b1529a..a57ec1de 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -578,7 +578,7 @@ class VideoDetailController extends GetxController } void onTapTabbar(int index) { - if (index == 1 && tabCtr.index == 1) { + if (tabCtr.animation!.isCompleted && index == 1 && tabCtr.index == 1) { replyScrollController?.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.ease); } From 877eeded37c0f0cc5d08db915a1933e3b687433c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 30 Sep 2024 13:09:40 +0800 Subject: [PATCH 119/152] =?UTF-8?q?fix:=20=E9=BB=91=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/blacklist/index.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/pages/blacklist/index.dart b/lib/pages/blacklist/index.dart index 402790f5..fb7e0891 100644 --- a/lib/pages/blacklist/index.dart +++ b/lib/pages/blacklist/index.dart @@ -61,7 +61,7 @@ class _BlackListPageState extends State { centerTitle: false, title: Obx( () => Text( - '黑名单管理 - ${_blackListController.total.value}', + '黑名单管理 ${_blackListController.total.value == 0 ? '' : '- ${_blackListController.total.value}'}', style: Theme.of(context).textTheme.titleMedium, ), ), @@ -76,8 +76,12 @@ class _BlackListPageState extends State { if (data['status']) { List list = _blackListController.blackList; return Obx( - () => list.length == 1 - ? const SizedBox() + () => list.isEmpty + ? CustomScrollView( + slivers: [ + HttpError(errMsg: '你没有拉黑任何人哦~_~', fn: () => {}) + ], + ) : ListView.builder( controller: scrollController, itemCount: list.length, From 31f87bd24cb8f6fff9860dde2ff0833a0412092e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 2 Oct 2024 22:27:20 +0800 Subject: [PATCH 120/152] =?UTF-8?q?fix:=20=E7=99=BB=E5=BD=95=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/login.dart | 23 ++++++++++--- lib/pages/login/controller.dart | 10 +++++- lib/pages/login/view.dart | 59 ++++++++++++++++++++++----------- 3 files changed, 67 insertions(+), 25 deletions(-) diff --git a/lib/http/login.dart b/lib/http/login.dart index 2437b72a..71d61b3f 100644 --- a/lib/http/login.dart +++ b/lib/http/login.dart @@ -244,12 +244,25 @@ class LoginHttp { ), ); if (res.data['code'] == 0) { - return { - 'status': true, - 'data': res.data['data'], - }; + if (res.data['data']['status'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'code': 1, + 'data': res.data['data'], + 'msg': res.data['data']['message'], + }; + } } else { - return {'status': false, 'data': [], 'msg': res.data['message']}; + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; } } diff --git a/lib/pages/login/controller.dart b/lib/pages/login/controller.dart index fbb06e2f..9e7fb339 100644 --- a/lib/pages/login/controller.dart +++ b/lib/pages/login/controller.dart @@ -45,6 +45,7 @@ class LoginPageController extends GetxController { RxInt validSeconds = 180.obs; Timer? validTimer; late String qrcodeKey; + RxBool passwordVisible = false.obs; // 监听pageView切换 void onPageChange(int index) { @@ -128,7 +129,14 @@ class LoginPageController extends GetxController { if (res['status']) { await LoginUtils.confirmLogin('', null); } else { - SmartDialog.showToast(res['msg']); + await SmartDialog.showToast(res['msg']); + if (res.containsKey('code') && res['code'] == 1) { + Get.toNamed('/webview', parameters: { + 'url': res['data']['data']['url'], + 'type': 'url', + 'pageTitle': '登录验证', + }); + } } } else { SmartDialog.showToast(webKeyRes['msg']); diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index 85a8adf0..4fe21792 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -269,25 +269,46 @@ class _LoginPageState extends State { ), Container( margin: const EdgeInsets.only(top: 38, bottom: 15), - child: TextFormField( - controller: _loginPageCtr.passwordTextController, - focusNode: _loginPageCtr.passwordTextFieldNode, - keyboardType: TextInputType.visiblePassword, - decoration: InputDecoration( - isDense: true, - labelText: '输入密码', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(6.0), - ), - ), - // 校验用户名 - validator: (v) { - return v!.trim().isNotEmpty ? null : "密码不能为空"; - }, - onSaved: (val) { - print(val); - }, - ), + child: Obx(() => TextFormField( + controller: + _loginPageCtr.passwordTextController, + focusNode: + _loginPageCtr.passwordTextFieldNode, + keyboardType: TextInputType.visiblePassword, + obscureText: + _loginPageCtr.passwordVisible.value, + decoration: InputDecoration( + isDense: true, + labelText: '输入密码', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(6.0), + ), + suffixIcon: IconButton( + icon: Icon( + _loginPageCtr.passwordVisible.value + ? Icons.visibility + : Icons.visibility_off, + color: Theme.of(context) + .colorScheme + .primary, + ), + onPressed: () { + _loginPageCtr.passwordVisible.value = + !_loginPageCtr + .passwordVisible.value; + }, + ), + ), + // 校验用户名 + validator: (v) { + return v!.trim().isNotEmpty + ? null + : "密码不能为空"; + }, + onSaved: (val) { + print(val); + }, + )), ), const Spacer(), Row( From 2a436cfcfcb0efa0b82c57f443c6c6e2846a258a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 3 Oct 2024 16:30:47 +0800 Subject: [PATCH 121/152] =?UTF-8?q?feat:=20=E5=8E=9F=E7=BD=91=E9=A1=B5?= =?UTF-8?q?=E6=9F=A5=E7=9C=8B=E4=B8=93=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/opus/controller.dart | 10 ++++++++++ lib/pages/opus/view.dart | 11 ++++++++--- lib/pages/read/controller.dart | 10 ++++++++++ lib/pages/read/view.dart | 11 ++++++++--- lib/pages/webview/controller.dart | 11 ----------- lib/utils/app_scheme.dart | 2 +- 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/lib/pages/opus/controller.dart b/lib/pages/opus/controller.dart index 86dd67a5..3cf844cc 100644 --- a/lib/pages/opus/controller.dart +++ b/lib/pages/opus/controller.dart @@ -39,6 +39,7 @@ class OpusController extends GetxController { 'articleType': 'cv', }); } else { + title.value = res['data'].detail!.basic!.title!; opusData.value = res['data']; } } @@ -91,6 +92,15 @@ class OpusController extends GetxController { ); } + // 跳转webview + void onJumpWebview() { + Get.toNamed('/webview', parameters: { + 'url': url, + 'type': 'webview', + 'pageTitle': title.value, + }); + } + @override void onClose() { scrollController.removeListener(_scrollListener); diff --git a/lib/pages/opus/view.dart b/lib/pages/opus/view.dart index 8535230f..434a9405 100644 --- a/lib/pages/opus/view.dart +++ b/lib/pages/opus/view.dart @@ -60,9 +60,14 @@ class _OpusPageState extends State { }, ), actions: [ - IconButton( - icon: const Icon(Icons.more_vert_rounded), - onPressed: () {}, + PopupMenuButton( + icon: const Icon(Icons.more_vert_outlined), + itemBuilder: (BuildContext context) => [ + PopupMenuItem( + onTap: controller.onJumpWebview, + child: const Text('查看原网页'), + ) + ], ), const SizedBox(width: 16), ], diff --git a/lib/pages/read/controller.dart b/lib/pages/read/controller.dart index a0e4ef8e..efa43c98 100644 --- a/lib/pages/read/controller.dart +++ b/lib/pages/read/controller.dart @@ -22,6 +22,7 @@ class ReadPageController extends GetxController { title.value = Get.parameters['title'] ?? ''; id = Get.parameters['id']!; articleType = Get.parameters['articleType']!; + url = 'https://www.bilibili.com/read/cv$id'; scrollController.addListener(_scrollListener); fetchViewInfo(); } @@ -85,6 +86,15 @@ class ReadPageController extends GetxController { ReadHttp.getViewInfo(id: id); } + // 跳转webview + void onJumpWebview() { + Get.toNamed('/webview', parameters: { + 'url': url, + 'type': 'webview', + 'pageTitle': title.value, + }); + } + @override void onClose() { scrollController.removeListener(_scrollListener); diff --git a/lib/pages/read/view.dart b/lib/pages/read/view.dart index 7c1e0601..d37eeae5 100644 --- a/lib/pages/read/view.dart +++ b/lib/pages/read/view.dart @@ -72,9 +72,14 @@ class _ReadPageState extends State { }, ), actions: [ - IconButton( - icon: const Icon(Icons.more_vert_rounded), - onPressed: () {}, + PopupMenuButton( + icon: const Icon(Icons.more_vert_outlined), + itemBuilder: (BuildContext context) => [ + PopupMenuItem( + onTap: controller.onJumpWebview, + child: const Text('查看原网页'), + ) + ], ), const SizedBox(width: 16), ], diff --git a/lib/pages/webview/controller.dart b/lib/pages/webview/controller.dart index e0ff113c..53e76231 100644 --- a/lib/pages/webview/controller.dart +++ b/lib/pages/webview/controller.dart @@ -1,19 +1,8 @@ -// ignore_for_file: avoid_print - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; import 'package:pilipala/http/init.dart'; -import 'package:pilipala/http/user.dart'; -import 'package:pilipala/pages/home/index.dart'; -import 'package:pilipala/pages/media/index.dart'; -import 'package:pilipala/utils/cookie.dart'; import 'package:pilipala/utils/event_bus.dart'; import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/login.dart'; -import 'package:pilipala/utils/storage.dart'; import 'package:webview_flutter/webview_flutter.dart'; class WebviewController extends GetxController { diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index 67b8a5d5..22184139 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -82,7 +82,7 @@ class PiliSchame { case 'opus': if (path.startsWith('/detail')) { var opusId = path.split('/').last; - Get.toNamed('/opus', arguments: { + Get.toNamed('/opus', parameters: { 'title': '', 'id': opusId, 'articleType': 'opus', From e949dd60ce30b448cb0d6888d12b21987d7c2f07 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 3 Oct 2024 20:35:33 +0800 Subject: [PATCH 122/152] =?UTF-8?q?opt:=20=E5=9B=BE=E7=89=87=E9=A2=84?= =?UTF-8?q?=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/dynamics/widgets/content_panel.dart | 26 ---- lib/pages/dynamics/widgets/pic_panel.dart | 26 ---- lib/pages/opus/controller.dart | 26 ---- lib/pages/read/controller.dart | 26 ---- .../detail/reply/widgets/reply_item.dart | 29 ---- .../whisper_detail/widget/chat_item.dart | 26 ---- .../pl_gallery/interactiveviewer_gallery.dart | 125 +++++++++++++++--- 7 files changed, 106 insertions(+), 178 deletions(-) diff --git a/lib/pages/dynamics/widgets/content_panel.dart b/lib/pages/dynamics/widgets/content_panel.dart index 28451dde..c1a6185e 100644 --- a/lib/pages/dynamics/widgets/content_panel.dart +++ b/lib/pages/dynamics/widgets/content_panel.dart @@ -1,5 +1,4 @@ // 内容 -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/badge.dart'; @@ -166,31 +165,6 @@ class _ContentState extends State { builder: (BuildContext context) => InteractiveviewerGallery( sources: picList, initIndex: initIndex, - itemBuilder: ( - BuildContext context, - int index, - bool isFocus, - bool enablePageView, - ) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - if (enablePageView) { - Navigator.of(context).pop(); - } - }, - child: Center( - child: Hero( - tag: picList[index], - child: CachedNetworkImage( - fadeInDuration: const Duration(milliseconds: 0), - imageUrl: picList[index], - fit: BoxFit.contain, - ), - ), - ), - ); - }, onPageChanged: (int pageIndex) {}, ), ), diff --git a/lib/pages/dynamics/widgets/pic_panel.dart b/lib/pages/dynamics/widgets/pic_panel.dart index 783fe89b..bd3f91f6 100644 --- a/lib/pages/dynamics/widgets/pic_panel.dart +++ b/lib/pages/dynamics/widgets/pic_panel.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; @@ -12,31 +11,6 @@ void onPreviewImg(currentUrl, picList, initIndex, context) { builder: (BuildContext context) => InteractiveviewerGallery( sources: picList, initIndex: initIndex, - itemBuilder: ( - BuildContext context, - int index, - bool isFocus, - bool enablePageView, - ) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - if (enablePageView) { - Navigator.of(context).pop(); - } - }, - child: Center( - child: Hero( - tag: picList[index], - child: CachedNetworkImage( - fadeInDuration: const Duration(milliseconds: 0), - imageUrl: picList[index], - fit: BoxFit.contain, - ), - ), - ), - ); - }, onPageChanged: (int pageIndex) {}, ), ), diff --git a/lib/pages/opus/controller.dart b/lib/pages/opus/controller.dart index 3cf844cc..f00c45b5 100644 --- a/lib/pages/opus/controller.dart +++ b/lib/pages/opus/controller.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/http/read.dart'; import 'package:pilipala/models/read/opus.dart'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart'; import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart'; @@ -61,31 +60,6 @@ class OpusController extends GetxController { builder: (BuildContext context) => InteractiveviewerGallery( sources: picList, initIndex: initIndex, - itemBuilder: ( - BuildContext context, - int index, - bool isFocus, - bool enablePageView, - ) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - if (enablePageView) { - Navigator.of(context).pop(); - } - }, - child: Center( - child: Hero( - tag: picList[index], - child: CachedNetworkImage( - fadeInDuration: const Duration(milliseconds: 0), - imageUrl: picList[index], - fit: BoxFit.contain, - ), - ), - ), - ); - }, onPageChanged: (int pageIndex) {}, ), ), diff --git a/lib/pages/read/controller.dart b/lib/pages/read/controller.dart index efa43c98..178ebfda 100644 --- a/lib/pages/read/controller.dart +++ b/lib/pages/read/controller.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/http/read.dart'; @@ -51,31 +50,6 @@ class ReadPageController extends GetxController { builder: (BuildContext context) => InteractiveviewerGallery( sources: picList, initIndex: initIndex, - itemBuilder: ( - BuildContext context, - int index, - bool isFocus, - bool enablePageView, - ) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - if (enablePageView) { - Navigator.of(context).pop(); - } - }, - child: Center( - child: Hero( - tag: picList[index], - child: CachedNetworkImage( - fadeInDuration: const Duration(milliseconds: 0), - imageUrl: picList[index], - fit: BoxFit.contain, - ), - ), - ), - ); - }, onPageChanged: (int pageIndex) {}, ), ), diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 26dc2e5a..8bb6992a 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -1,7 +1,6 @@ import 'dart:math'; import 'package:appscheme/appscheme.dart'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -583,34 +582,6 @@ InlineSpan buildContent( builder: (BuildContext context) => InteractiveviewerGallery( sources: picList, initIndex: initIndex, - itemBuilder: ( - BuildContext context, - int index, - bool isFocus, - bool enablePageView, - ) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - if (enablePageView) { - Navigator.of(context).pop(); - final MainController mainController = - Get.find(); - mainController.imgPreviewStatus = false; - } - }, - child: Center( - child: Hero( - tag: picList[index] + randomInt, - child: CachedNetworkImage( - fadeInDuration: const Duration(milliseconds: 0), - imageUrl: picList[index], - fit: BoxFit.contain, - ), - ), - ), - ); - }, onPageChanged: (int pageIndex) {}, onDismissed: (int value) { print('onDismissed'); diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index 7ddd1d83..01ede374 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -2,7 +2,6 @@ // ignore_for_file: constant_identifier_names import 'dart:convert'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -170,31 +169,6 @@ class ChatItem extends StatelessWidget { builder: (BuildContext context) => InteractiveviewerGallery( sources: ctr.picList, initIndex: ctr.picList.indexOf(content['url']), - itemBuilder: ( - BuildContext context, - int index, - bool isFocus, - bool enablePageView, - ) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - if (enablePageView) { - Navigator.of(context).pop(); - } - }, - child: Center( - child: Hero( - tag: ctr.picList[index], - child: CachedNetworkImage( - fadeInDuration: const Duration(milliseconds: 0), - imageUrl: ctr.picList[index], - fit: BoxFit.contain, - ), - ), - ), - ); - }, onPageChanged: (int pageIndex) {}, ), ), diff --git a/lib/plugin/pl_gallery/interactiveviewer_gallery.dart b/lib/plugin/pl_gallery/interactiveviewer_gallery.dart index 03ff4642..cd13194e 100644 --- a/lib/plugin/pl_gallery/interactiveviewer_gallery.dart +++ b/lib/plugin/pl_gallery/interactiveviewer_gallery.dart @@ -2,10 +2,12 @@ library interactiveviewer_gallery; import 'dart:io'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pilipala/utils/download.dart'; import 'package:share_plus/share_plus.dart'; @@ -32,7 +34,7 @@ class InteractiveviewerGallery extends StatefulWidget { const InteractiveviewerGallery({ required this.sources, required this.initIndex, - required this.itemBuilder, + this.itemBuilder, this.maxScale = 4.5, this.minScale = 1.0, this.onPageChanged, @@ -47,7 +49,7 @@ class InteractiveviewerGallery extends StatefulWidget { final int initIndex; /// The item content - final IndexedFocusedWidgetBuilder itemBuilder; + final IndexedFocusedWidgetBuilder? itemBuilder; final double maxScale; @@ -246,12 +248,15 @@ class _InteractiveviewerGalleryState extends State _doubleTapLocalPosition = details.localPosition; }, onDoubleTap: onDoubleTap, - child: widget.itemBuilder( - context, - index, - index == currentIndex, - _enablePageView, - ), + onLongPress: onLongPress, + child: widget.itemBuilder != null + ? widget.itemBuilder!( + context, + index, + index == currentIndex, + _enablePageView, + ) + : _itemBuilder(widget.sources, index), ); }, ), @@ -302,17 +307,7 @@ class _InteractiveviewerGalleryState extends State PopupMenuItem( value: 1, onTap: () { - Clipboard.setData(ClipboardData( - text: - widget.sources[currentIndex!].toString())) - .then((value) { - SmartDialog.showToast('已复制到粘贴板'); - }).catchError((err) { - SmartDialog.showNotify( - msg: err.toString(), - notifyType: NotifyType.error, - ); - }); + onCopyImg(widget.sources[currentIndex!].toString()); }, child: const Text("复制图片"), ), @@ -350,6 +345,41 @@ class _InteractiveviewerGalleryState extends State Share.shareXFiles([XFile(path)], subject: imgUrl); } + // 复制图片 + void onCopyImg(String imgUrl) { + Clipboard.setData( + ClipboardData(text: widget.sources[currentIndex!].toString())) + .then((value) { + SmartDialog.showToast('已复制到粘贴板'); + }).catchError((err) { + SmartDialog.showNotify( + msg: err.toString(), + notifyType: NotifyType.error, + ); + }); + } + + Widget _itemBuilder(sources, index) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (_enablePageView) { + Navigator.of(context).pop(); + } + }, + child: Center( + child: Hero( + tag: sources[index], + child: CachedNetworkImage( + fadeInDuration: const Duration(milliseconds: 0), + imageUrl: sources[index], + fit: BoxFit.contain, + ), + ), + ), + ); + } + onDoubleTap() { Matrix4 matrix = _transformationController!.value.clone(); double currentScale = matrix.row0.x; @@ -396,4 +426,61 @@ class _InteractiveviewerGalleryState extends State .forward(from: 0) .whenComplete(() => _onScaleChanged(targetScale)); } + + onLongPress() { + showModalBottomSheet( + context: context, + useRootNavigator: true, + isScrollControlled: true, + builder: (context) { + return Container( + padding: + EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () => Get.back(), + child: Container( + height: 35, + padding: const EdgeInsets.only(bottom: 2), + child: Center( + child: Container( + width: 32, + height: 3, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.outline, + borderRadius: + const BorderRadius.all(Radius.circular(3))), + ), + ), + ), + ), + ListTile( + onTap: () { + onShareImg(widget.sources[currentIndex!]); + Navigator.of(context).pop(); + }, + title: const Text('分享图片'), + ), + ListTile( + onTap: () { + onCopyImg(widget.sources[currentIndex!].toString()); + Navigator.of(context).pop(); + }, + title: const Text('复制图片'), + ), + ListTile( + onTap: () { + DownloadUtils.downloadImg(widget.sources[currentIndex!]); + Navigator.of(context).pop(); + }, + title: const Text('保存图片'), + ), + ], + ), + ); + }, + ); + } } From 4cac02a4c0c6cd47ad78786dc5adde4fa37d5fb8 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 3 Oct 2024 21:28:12 +0800 Subject: [PATCH 123/152] opt: Request().post --- lib/http/black.dart | 2 +- lib/http/danmaku.dart | 3 -- lib/http/dynamics.dart | 48 ++++++++++-------- lib/http/fav.dart | 4 +- lib/http/init.dart | 3 +- lib/http/live.dart | 54 ++++++++++++--------- lib/http/login.dart | 15 ------ lib/http/member.dart | 29 ++++++----- lib/http/msg.dart | 4 -- lib/http/reply.dart | 2 +- lib/http/user.dart | 16 +++--- lib/http/video.dart | 108 ++++++++++++++++++++++++----------------- 12 files changed, 151 insertions(+), 137 deletions(-) diff --git a/lib/http/black.dart b/lib/http/black.dart index 0c6a63ab..67356a92 100644 --- a/lib/http/black.dart +++ b/lib/http/black.dart @@ -28,7 +28,7 @@ class BlackHttp { static Future removeBlack({required int fid}) async { var res = await Request().post( Api.removeBlack, - queryParameters: { + data: { 'act': 6, 'csrf': await Request.getCsrf(), 'fid': fid, diff --git a/lib/http/danmaku.dart b/lib/http/danmaku.dart index 0b108755..2ed2a415 100644 --- a/lib/http/danmaku.dart +++ b/lib/http/danmaku.dart @@ -67,9 +67,6 @@ class DanmakaHttp { var response = await Request().post( Api.shootDanmaku, data: params, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), ); if (response.statusCode != 200) { return { diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index 69619361..5ba5675e 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -1,4 +1,5 @@ import 'dart:math'; +import 'package:dio/dio.dart'; import '../models/dynamics/result.dart'; import '../models/dynamics/up.dart'; import 'index.dart'; @@ -69,7 +70,7 @@ class DynamicsHttp { }) async { var res = await Request().post( Api.likeDynamic, - queryParameters: { + data: { 'dynamic_id': dynamicId, 'up': up, 'csrf': await Request.getCsrf(), @@ -175,27 +176,32 @@ class DynamicsHttp { 'revs_id': {'dyn_type': 8, 'rid': oid} }; } - var res = await Request().post(Api.dynamicCreate, queryParameters: { - 'platform': 'web', - 'csrf': await Request.getCsrf(), - 'x-bili-device-req-json': {'platform': 'web', 'device': 'pc'}, - 'x-bili-web-req-json': {'spm_id': '333.999'}, - }, data: { - 'dyn_req': { - 'content': { - 'contents': [ - {'raw_text': rawText ?? '', 'type': 1, 'biz_id': ''} - ] - }, - 'scene': scene, - 'attach_card': null, - 'upload_id': uploadId, - 'meta': { - 'app_meta': {'from': 'create.dynamic.web', 'mobi_app': 'web'} - } + var res = await Request().post( + Api.dynamicCreate, + queryParameters: { + 'platform': 'web', + 'csrf': await Request.getCsrf(), + 'x-bili-device-req-json': {'platform': 'web', 'device': 'pc'}, + 'x-bili-web-req-json': {'spm_id': '333.999'}, }, - 'web_repost_src': webRepostSrc - }); + data: { + 'dyn_req': { + 'content': { + 'contents': [ + {'raw_text': rawText ?? '', 'type': 1, 'biz_id': ''} + ] + }, + 'scene': scene, + 'attach_card': null, + 'upload_id': uploadId, + 'meta': { + 'app_meta': {'from': 'create.dynamic.web', 'mobi_app': 'web'} + } + }, + 'web_repost_src': webRepostSrc + }, + options: Options(contentType: 'application/json'), + ); if (res.data['code'] == 0) { return { 'status': true, diff --git a/lib/http/fav.dart b/lib/http/fav.dart index 6f49d68a..69577e7e 100644 --- a/lib/http/fav.dart +++ b/lib/http/fav.dart @@ -11,7 +11,7 @@ class FavHttp { }) async { var res = await Request().post( Api.editFavFolder, - queryParameters: { + data: { 'title': title, 'intro': intro, 'media_id': mediaId, @@ -43,7 +43,7 @@ class FavHttp { }) async { var res = await Request().post( Api.addFavFolder, - queryParameters: { + data: { 'title': title, 'intro': intro, 'cover': cover ?? '', diff --git a/lib/http/init.dart b/lib/http/init.dart index 6a90a87d..eae94ae4 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -260,7 +260,8 @@ class Request { url, data: data, queryParameters: queryParameters, - options: options, + options: + options ?? Options(contentType: Headers.formUrlEncodedContentType), cancelToken: cancelToken, ); // print('post success: ${response.data}'); diff --git a/lib/http/live.dart b/lib/http/live.dart index 1405e9ea..259f86fc 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -89,23 +89,26 @@ class LiveHttp { // 发送弹幕 static Future sendDanmaku({roomId, msg}) async { - var res = await Request().post(Api.sendLiveMsg, queryParameters: { - 'bubble': 0, - 'msg': msg, - 'color': 16777215, // 颜色 - 'mode': 1, // 模式 - 'room_type': 0, - 'jumpfrom': 71001, // 直播间来源 - 'reply_mid': 0, - 'reply_attr': 0, - 'replay_dmid': '', - 'statistics': {"appId": 100, "platform": 5}, - 'fontsize': 25, // 字体大小 - 'rnd': DateTime.now().millisecondsSinceEpoch ~/ 1000, // 时间戳 - 'roomid': roomId, - 'csrf': await Request.getCsrf(), - 'csrf_token': await Request.getCsrf(), - }); + var res = await Request().post( + Api.sendLiveMsg, + data: { + 'bubble': 0, + 'msg': msg, + 'color': 16777215, // 颜色 + 'mode': 1, // 模式 + 'room_type': 0, + 'jumpfrom': 71001, // 直播间来源 + 'reply_mid': 0, + 'reply_attr': 0, + 'replay_dmid': '', + 'statistics': {"appId": 100, "platform": 5}, + 'fontsize': 25, // 字体大小 + 'rnd': DateTime.now().millisecondsSinceEpoch ~/ 1000, // 时间戳 + 'roomid': roomId, + 'csrf': await Request.getCsrf(), + 'csrf_token': await Request.getCsrf(), + }, + ); if (res.data['code'] == 0) { return { 'status': true, @@ -145,12 +148,15 @@ class LiveHttp { // 直播历史记录 static Future liveRoomEntry({required int roomId}) async { - await Request().post(Api.liveRoomEntry, queryParameters: { - 'room_id': roomId, - 'platform': 'pc', - 'csrf_token': await Request.getCsrf(), - 'csrf': await Request.getCsrf(), - 'visit_id': '', - }); + await Request().post( + Api.liveRoomEntry, + data: { + 'room_id': roomId, + 'platform': 'pc', + 'csrf_token': await Request.getCsrf(), + 'csrf': await Request.getCsrf(), + 'visit_id': '', + }, + ); } } diff --git a/lib/http/login.dart b/lib/http/login.dart index 2437b72a..32eda04d 100644 --- a/lib/http/login.dart +++ b/lib/http/login.dart @@ -71,9 +71,6 @@ class LoginHttp { var res = await Request().post( Api.webSmsCode, data: formData, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), ); if (res.data['code'] == 0) { return { @@ -106,9 +103,6 @@ class LoginHttp { var res = await Request().post( Api.webSmsLogin, data: formData, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), ); if (res.data['code'] == 0) { return { @@ -155,9 +149,6 @@ class LoginHttp { var res = await Request().post( Api.appSmsCode, data: data, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), ); print(res); } @@ -208,9 +199,6 @@ class LoginHttp { var res = await Request().post( Api.loginInByPwdApi, data: data, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), ); print(res); } @@ -239,9 +227,6 @@ class LoginHttp { var res = await Request().post( Api.loginInByWebPwd, data: formData, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), ); if (res.data['code'] == 0) { return { diff --git a/lib/http/member.dart b/lib/http/member.dart index 459d6747..c7b22359 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -198,13 +198,15 @@ class MemberHttp { // 设置分组 static Future addUsers(int? fids, String? tagids) async { - var res = await Request().post(Api.addUsers, queryParameters: { - 'fids': fids, - 'tagids': tagids ?? '0', - 'csrf': await Request.getCsrf(), - }, data: { - 'cross_domain': true - }); + var res = await Request().post( + Api.addUsers, + data: { + 'fids': fids, + 'tagids': tagids ?? '0', + 'csrf': await Request.getCsrf(), + }, + queryParameters: {'cross_domain': true}, + ); if (res.data['code'] == 0) { return {'status': true, 'data': [], 'msg': '操作成功'}; } else { @@ -422,11 +424,14 @@ class MemberHttp { static Future cookieToKey() async { var authCodeRes = await getTVCode(); if (authCodeRes['status']) { - var res = await Request().post(Api.cookieToKey, queryParameters: { - 'auth_code': authCodeRes['data'], - 'build': 708200, - 'csrf': await Request.getCsrf(), - }); + var res = await Request().post( + Api.cookieToKey, + data: { + 'auth_code': authCodeRes['data'], + 'build': 708200, + 'csrf': await Request.getCsrf(), + }, + ); await Future.delayed(const Duration(milliseconds: 300)); await qrcodePoll(authCodeRes['data']); if (res.data['code'] == 0) { diff --git a/lib/http/msg.dart b/lib/http/msg.dart index 869b5a28..6426a6f2 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:math'; -import 'package:dio/dio.dart'; import 'package:pilipala/models/msg/like.dart'; import 'package:pilipala/models/msg/reply.dart'; import 'package:pilipala/models/msg/system.dart'; @@ -158,9 +157,6 @@ class MsgHttp { 'csrf_token': csrf, 'csrf': csrf, }, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), ); if (res.data['code'] == 0) { return { diff --git a/lib/http/reply.dart b/lib/http/reply.dart index 880f9072..fc00f06b 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -78,7 +78,7 @@ class ReplyHttp { }) async { var res = await Request().post( Api.likeReply, - queryParameters: { + data: { 'type': type, 'oid': oid, 'rpid': rpid, diff --git a/lib/http/user.dart b/lib/http/user.dart index f4535905..26b79523 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -153,7 +153,7 @@ class UserHttp { // 暂停switchStatus传true 否则false var res = await Request().post( Api.pauseHistory, - queryParameters: { + data: { 'switch': switchStatus, 'jsonp': 'jsonp', 'csrf': await Request.getCsrf(), @@ -172,7 +172,7 @@ class UserHttp { static Future clearHistory() async { var res = await Request().post( Api.clearHistory, - queryParameters: { + data: { 'jsonp': 'jsonp', 'csrf': await Request.getCsrf(), }, @@ -190,7 +190,7 @@ class UserHttp { } var res = await Request().post( Api.toViewLater, - queryParameters: data, + data: data, ); if (res.data['code'] == 0) { return {'status': true, 'msg': 'yeah!稍后再看'}; @@ -209,7 +209,7 @@ class UserHttp { params[aid != null ? 'aid' : 'viewed'] = aid ?? true; var res = await Request().post( Api.toViewDel, - queryParameters: params, + data: params, ); if (res.data['code'] == 0) { return {'status': true, 'msg': 'yeah!成功移除'}; @@ -241,7 +241,7 @@ class UserHttp { static Future toViewClear() async { var res = await Request().post( Api.toViewClear, - queryParameters: { + data: { 'jsonp': 'jsonp', 'csrf': await Request.getCsrf(), }, @@ -257,7 +257,7 @@ class UserHttp { static Future delHistory(kid) async { var res = await Request().post( Api.delHistory, - queryParameters: { + data: { 'kid': kid, 'jsonp': 'jsonp', 'csrf': await Request.getCsrf(), @@ -406,7 +406,7 @@ class UserHttp { static Future cancelSub({required int seasonId}) async { var res = await Request().post( Api.cancelSub, - queryParameters: { + data: { 'platform': 'web', 'season_id': seasonId, 'csrf': await Request.getCsrf(), @@ -423,7 +423,7 @@ class UserHttp { static Future delFavFolder({required int mediaIds}) async { var res = await Request().post( Api.delFavFolder, - queryParameters: { + data: { 'media_ids': mediaIds, 'platform': 'web', 'csrf': await Request.getCsrf(), diff --git a/lib/http/video.dart b/lib/http/video.dart index 160f5db2..95ea6782 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -243,7 +243,7 @@ class VideoHttp { static Future coinVideo({required String bvid, required int multiply}) async { var res = await Request().post( Api.coinVideo, - queryParameters: { + data: { 'bvid': bvid, 'multiply': multiply, 'select_like': 0, @@ -271,7 +271,7 @@ class VideoHttp { static Future oneThree({required String bvid}) async { var res = await Request().post( Api.oneThree, - queryParameters: { + data: { 'bvid': bvid, 'csrf': await Request.getCsrf(), }, @@ -287,7 +287,7 @@ class VideoHttp { static Future likeVideo({required String bvid, required bool type}) async { var res = await Request().post( Api.likeVideo, - queryParameters: { + data: { 'bvid': bvid, 'like': type ? 1 : 2, 'csrf': await Request.getCsrf(), @@ -303,13 +303,16 @@ class VideoHttp { // (取消)收藏 static Future favVideo( {required int aid, String? addIds, String? delIds}) async { - var res = await Request().post(Api.favVideo, queryParameters: { - 'rid': aid, - 'type': 2, - 'add_media_ids': addIds ?? '', - 'del_media_ids': delIds ?? '', - 'csrf': await Request.getCsrf(), - }); + var res = await Request().post( + Api.favVideo, + data: { + 'rid': aid, + 'type': 2, + 'add_media_ids': addIds ?? '', + 'del_media_ids': delIds ?? '', + 'csrf': await Request.getCsrf(), + }, + ); if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; } else { @@ -347,14 +350,17 @@ class VideoHttp { if (message == '') { return {'status': false, 'data': [], 'msg': '请输入评论内容'}; } - var res = await Request().post(Api.replyAdd, queryParameters: { - 'type': type.index, - 'oid': oid, - 'root': root == null || root == 0 ? '' : root, - 'parent': parent == null || parent == 0 ? '' : parent, - 'message': message, - 'csrf': await Request.getCsrf(), - }); + var res = await Request().post( + Api.replyAdd, + data: { + 'type': type.index, + 'oid': oid, + 'root': root == null || root == 0 ? '' : root, + 'parent': parent == null || parent == 0 ? '' : parent, + 'message': message, + 'csrf': await Request.getCsrf(), + }, + ); log(res.toString()); if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; @@ -376,12 +382,15 @@ class VideoHttp { // 操作用户关系 static Future relationMod( {required int mid, required int act, required int reSrc}) async { - var res = await Request().post(Api.relationMod, queryParameters: { - 'fid': mid, - 'act': act, - 're_src': reSrc, - 'csrf': await Request.getCsrf(), - }); + var res = await Request().post( + Api.relationMod, + data: { + 'fid': mid, + 'act': act, + 're_src': reSrc, + 'csrf': await Request.getCsrf(), + }, + ); if (res.data['code'] == 0) { if (act == 5) { List blackMidsList = @@ -397,27 +406,33 @@ class VideoHttp { // 视频播放进度 static Future heartBeat({bvid, cid, progress, realtime}) async { - await Request().post(Api.heartBeat, queryParameters: { - // 'aid': aid, - 'bvid': bvid, - 'cid': cid, - // 'epid': '', - // 'sid': '', - // 'mid': '', - 'played_time': progress, - // 'realtime': realtime, - // 'type': '', - // 'sub_type': '', - 'csrf': await Request.getCsrf(), - }); + await Request().post( + Api.heartBeat, + data: { + // 'aid': aid, + 'bvid': bvid, + 'cid': cid, + // 'epid': '', + // 'sid': '', + // 'mid': '', + 'played_time': progress, + // 'realtime': realtime, + // 'type': '', + // 'sub_type': '', + 'csrf': await Request.getCsrf(), + }, + ); } // 添加追番 static Future bangumiAdd({int? seasonId}) async { - var res = await Request().post(Api.bangumiAdd, queryParameters: { - 'season_id': seasonId, - 'csrf': await Request.getCsrf(), - }); + var res = await Request().post( + Api.bangumiAdd, + data: { + 'season_id': seasonId, + 'csrf': await Request.getCsrf(), + }, + ); if (res.data['code'] == 0) { return {'status': true, 'msg': res.data['result']['toast']}; } else { @@ -427,10 +442,13 @@ class VideoHttp { // 取消追番 static Future bangumiDel({int? seasonId}) async { - var res = await Request().post(Api.bangumiDel, queryParameters: { - 'season_id': seasonId, - 'csrf': await Request.getCsrf(), - }); + var res = await Request().post( + Api.bangumiDel, + data: { + 'season_id': seasonId, + 'csrf': await Request.getCsrf(), + }, + ); if (res.data['code'] == 0) { return {'status': true, 'msg': res.data['result']['toast']}; } else { From f1441ac97e19e8c4b52b079fd8275e005985564d Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 4 Oct 2024 11:36:17 +0800 Subject: [PATCH 124/152] =?UTF-8?q?fix:=20=E6=94=B6=E8=97=8F=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/fav/view.dart | 108 +++++++++++++++------------ lib/pages/fav_detail/controller.dart | 8 +- lib/pages/fav_detail/view.dart | 26 ++++--- lib/pages/fav_edit/controller.dart | 2 +- 4 files changed, 82 insertions(+), 62 deletions(-) diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index f6164609..317ba4d5 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -79,56 +79,68 @@ class _FavPageState extends State { const SizedBox(width: 14), ], ), - body: FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map? data = snapshot.data; - if (data != null && data['status']) { - return Obx( - () => ListView.builder( - controller: scrollController, - itemCount: _favController.favFolderList.length, - itemBuilder: (context, index) { - return FavItem( - favFolderItem: _favController.favFolderList[index], - isOwner: _favController.isOwner.value, - ); - }, - ), - ); - } else { - return CustomScrollView( - physics: const NeverScrollableScrollPhysics(), - slivers: [ - HttpError( - errMsg: data?['msg'] ?? '请求异常', - btnText: data?['code'] == -101 ? '去登录' : null, - fn: () { - if (data?['code'] == -101) { - RoutePush.loginRedirectPush(); - } else { - setState(() { - _futureBuilderFuture = - _favController.queryFavFolder(); - }); - } - }, - ), - ], - ); - } - } else { - // 骨架屏 - return ListView.builder( - itemBuilder: (context, index) { - return const VideoCardHSkeleton(); - }, - itemCount: 10, - ); - } + body: RefreshIndicator( + onRefresh: () async { + _favController.hasMore.value = true; + _favController.currentPage = 1; + setState(() { + _futureBuilderFuture = _favController.queryFavFolder(type: 'init'); + }); }, + child: _buildBody(), ), ); } + + Widget _buildBody() { + return FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map? data = snapshot.data; + if (data != null && data['status']) { + return Obx( + () => ListView.builder( + controller: scrollController, + itemCount: _favController.favFolderList.length, + itemBuilder: (context, index) { + return FavItem( + favFolderItem: _favController.favFolderList[index], + isOwner: _favController.isOwner.value, + ); + }, + ), + ); + } else { + return CustomScrollView( + physics: const NeverScrollableScrollPhysics(), + slivers: [ + HttpError( + errMsg: data?['msg'] ?? '请求异常', + btnText: data?['code'] == -101 ? '去登录' : null, + fn: () { + if (data?['code'] == -101) { + RoutePush.loginRedirectPush(); + } else { + setState(() { + _futureBuilderFuture = _favController.queryFavFolder(); + }); + } + }, + ), + ], + ); + } + } else { + // 骨架屏 + return ListView.builder( + itemBuilder: (context, index) { + return const VideoCardHSkeleton(); + }, + itemCount: 10, + ); + } + }, + ); + } } diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 4d639bff..ba722481 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -10,6 +10,7 @@ import 'package:pilipala/utils/utils.dart'; class FavDetailController extends GetxController { FavFolderItemData? item; + RxString title = ''.obs; int? mediaId; late String heroTag; @@ -24,6 +25,7 @@ class FavDetailController extends GetxController { @override void onInit() { item = Get.arguments; + title.value = item!.title!; if (Get.parameters.keys.isNotEmpty) { mediaId = int.parse(Get.parameters['mediaId']!); heroTag = Get.parameters['heroTag']!; @@ -117,16 +119,18 @@ class FavDetailController extends GetxController { } onEditFavFolder() async { - Get.toNamed( + var res = await Get.toNamed( '/favEdit', arguments: { 'mediaId': mediaId.toString(), 'title': item!.title, 'intro': item!.intro, 'cover': item!.cover, - 'privacy': item!.attr, + 'privacy': [23, 1].contains(item!.attr) ? 1 : 0, }, ); + title.value = res['title']; + print(title); } Future toViewPlayAll() async { diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 8f0d3cd1..d4c10d31 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -80,9 +80,11 @@ class _FavDetailPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - _favDetailController.item!.title!, - style: Theme.of(context).textTheme.titleMedium, + Obx( + () => Text( + _favDetailController.title.value, + style: Theme.of(context).textTheme.titleMedium, + ), ), Text( '共${_favDetailController.mediaCount}条视频', @@ -156,14 +158,16 @@ class _FavDetailPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), - Text( - _favDetailController.item!.title!, - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .titleMedium! - .fontSize, - fontWeight: FontWeight.bold), + Obx( + () => Text( + _favDetailController.title.value, + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .titleMedium! + .fontSize, + fontWeight: FontWeight.bold), + ), ), const SizedBox(height: 4), Text( diff --git a/lib/pages/fav_edit/controller.dart b/lib/pages/fav_edit/controller.dart index 4772caee..bf310389 100644 --- a/lib/pages/fav_edit/controller.dart +++ b/lib/pages/fav_edit/controller.dart @@ -56,7 +56,7 @@ class FavEditController extends GetxController { ); if (res['status']) { SmartDialog.showToast('编辑成功'); - Get.back(); + Get.back(result: {'title': title}); } else { SmartDialog.showToast(res['msg']); } From 722ba06787d9013dfdb6c70a267d101649ab7d00 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 4 Oct 2024 23:07:34 +0800 Subject: [PATCH 125/152] =?UTF-8?q?feat:=20=E4=B8=93=E6=A0=8F=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/common/search_type.dart | 20 +- lib/pages/search_panel/controller.dart | 4 +- lib/pages/search_panel/view.dart | 13 +- .../search_panel/widgets/article_panel.dart | 317 +++++++++++++----- 4 files changed, 263 insertions(+), 91 deletions(-) diff --git a/lib/models/common/search_type.dart b/lib/models/common/search_type.dart index d7d13aec..843e7954 100644 --- a/lib/models/common/search_type.dart +++ b/lib/models/common/search_type.dart @@ -28,7 +28,7 @@ extension SearchTypeExtension on SearchType { String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index]; } -// 搜索类型为视频、专栏及相簿时 +// 搜索类型为视频时 enum ArchiveFilterType { totalrank, click, @@ -44,3 +44,21 @@ extension ArchiveFilterTypeExtension on ArchiveFilterType { String get description => ['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index]; } + +// 搜索类型为专栏时 +enum ArticleFilterType { + // 综合排序 + totalrank, + // 最新发布 + pubdate, + // 最多点击 + click, + // 最多喜欢 + attention, + // 最多评论 + scores, +} + +extension ArticleFilterTypeExtension on ArticleFilterType { + String get description => ['综合排序', '最新发布', '最多点击', '最多喜欢', '最多评论'][index]; +} diff --git a/lib/pages/search_panel/controller.dart b/lib/pages/search_panel/controller.dart index dc0b2bac..2d1aa228 100644 --- a/lib/pages/search_panel/controller.dart +++ b/lib/pages/search_panel/controller.dart @@ -24,7 +24,9 @@ class SearchPanelController extends GetxController { searchType: searchType!, keyword: keyword!, page: page.value, - order: searchType!.type != 'video' ? null : order.value, + order: !['video', 'article'].contains(searchType!.type) + ? null + : (order.value == '' ? null : order.value), duration: searchType!.type != 'video' ? null : duration.value, tids: searchType!.type != 'video' ? null : tids.value, ); diff --git a/lib/pages/search_panel/view.dart b/lib/pages/search_panel/view.dart index c5824d70..fa669489 100644 --- a/lib/pages/search_panel/view.dart +++ b/lib/pages/search_panel/view.dart @@ -1,3 +1,5 @@ +// ignore_for_file: invalid_use_of_protected_member + import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -45,6 +47,11 @@ class _SearchPanelState extends State ), tag: widget.searchType!.type + widget.keyword!, ); + + /// 专栏默认排序 + if (widget.searchType == SearchType.article) { + _searchPanelController.order.value = 'totalrank'; + } scrollController = _searchPanelController.scrollController; scrollController.addListener(() async { if (scrollController.position.pixels >= @@ -84,7 +91,6 @@ class _SearchPanelState extends State case SearchType.video: return SearchVideoPanel( ctr: _searchPanelController, - // ignore: invalid_use_of_protected_member list: list.value, ); case SearchType.media_bangumi: @@ -94,7 +100,10 @@ class _SearchPanelState extends State case SearchType.live_room: return searchLivePanel(context, ctr, list); case SearchType.article: - return searchArticlePanel(context, ctr, list); + return SearchArticlePanel( + ctr: _searchPanelController, + list: list.value, + ); default: return const SizedBox(); } diff --git a/lib/pages/search_panel/widgets/article_panel.dart b/lib/pages/search_panel/widgets/article_panel.dart index dd53de66..be08ed56 100644 --- a/lib/pages/search_panel/widgets/article_panel.dart +++ b/lib/pages/search_panel/widgets/article_panel.dart @@ -1,106 +1,249 @@ import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/models/common/search_type.dart'; +import 'package:pilipala/pages/search_panel/index.dart'; import 'package:pilipala/utils/utils.dart'; +class SearchArticlePanel extends StatelessWidget { + SearchArticlePanel({ + required this.ctr, + this.list, + Key? key, + }) : super(key: key); + + final SearchPanelController ctr; + final List? list; + + final ArticlePanelController controller = Get.put(ArticlePanelController()); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.topCenter, + children: [ + searchArticlePanel(context, ctr, list), + Container( + width: double.infinity, + height: 36, + padding: const EdgeInsets.only(left: 8, top: 0, right: 8), + child: Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Obx( + () => Wrap( + // spacing: , + children: [ + for (var i in controller.filterList) ...[ + CustomFilterChip( + label: i['label'], + type: i['type'], + selectedType: controller.selectedType.value, + callFn: (bool selected) async { + controller.selectedType.value = i['type']; + ctr.order.value = + i['type'].toString().split('.').last; + SmartDialog.showLoading(msg: 'loading'); + await ctr.onRefresh(); + SmartDialog.dismiss(); + }, + ), + ] + ], + ), + ), + ), + ), + ], + ), + ), + ], + ); + } +} + Widget searchArticlePanel(BuildContext context, ctr, list) { TextStyle textStyle = TextStyle( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, color: Theme.of(context).colorScheme.outline); - return ListView.builder( - controller: ctr!.scrollController, - itemCount: list.length, - itemBuilder: (context, index) { - return InkWell( - onTap: () { - Get.toNamed('/read', parameters: { - 'title': list[index].subTitle, - 'id': list[index].id.toString(), - 'articleType': 'read' - }); - }, - child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 5, StyleString.safeSpace, 5), - child: LayoutBuilder(builder: (context, boxConstraints) { - final double width = (boxConstraints.maxWidth - - StyleString.cardSpace * - 6 / - MediaQuery.textScalerOf(context).scale(1.0)) / - 2; - return Container( - constraints: const BoxConstraints(minHeight: 88), - height: width / StyleString.aspectRatio, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (list[index].imageUrls != null && - list[index].imageUrls.isNotEmpty) - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder(builder: (context, boxConstraints) { - double maxWidth = boxConstraints.maxWidth; - double maxHeight = boxConstraints.maxHeight; - return NetworkImgLayer( - width: maxWidth, - height: maxHeight, - src: list[index].imageUrls.first, - ); - }), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), - child: Column( - mainAxisSize: MainAxisSize.min, + return Padding( + padding: const EdgeInsets.only(top: 36), + child: list!.isNotEmpty + ? ListView.builder( + controller: ctr!.scrollController, + addAutomaticKeepAlives: false, + addRepaintBoundaries: false, + itemCount: list.length, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + Get.toNamed('/read', parameters: { + 'title': list[index].subTitle, + 'id': list[index].id.toString(), + 'articleType': 'read' + }); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 5, StyleString.safeSpace, 5), + child: LayoutBuilder(builder: (context, boxConstraints) { + final double width = (boxConstraints.maxWidth - + StyleString.cardSpace * + 6 / + MediaQuery.textScalerOf(context).scale(1.0)) / + 2; + return Container( + constraints: const BoxConstraints(minHeight: 88), + height: width / StyleString.aspectRatio, + child: Row( crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - maxLines: 2, - text: TextSpan( - children: [ - for (var i in list[index].title) ...[ - TextSpan( - text: i['text'], - style: TextStyle( - fontWeight: FontWeight.w500, - letterSpacing: 0.3, - color: i['type'] == 'em' - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme - .onSurface, + children: [ + if (list[index].imageUrls != null && + list[index].imageUrls.isNotEmpty) + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return NetworkImgLayer( + width: maxWidth, + height: maxHeight, + src: list[index].imageUrls.first, + ); + }), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + maxLines: 2, + text: TextSpan( + children: [ + for (var i in list[index].title) ...[ + TextSpan( + text: i['text'], + style: TextStyle( + fontWeight: FontWeight.w500, + letterSpacing: 0.3, + color: i['type'] == 'em' + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .onSurface, + ), + ), + ] + ], ), ), - ] - ], + const Spacer(), + Text( + Utils.dateFormat(list[index].pubTime, + formatType: 'detail'), + style: textStyle), + Row( + children: [ + Text('${list[index].view}浏览', + style: textStyle), + Text(' • ', style: textStyle), + Text('${list[index].reply}评论', + style: textStyle), + ], + ), + ], + ), ), ), - const Spacer(), - Text( - Utils.dateFormat(list[index].pubTime, - formatType: 'detail'), - style: textStyle), - Row( - children: [ - Text('${list[index].view}浏览', style: textStyle), - Text(' • ', style: textStyle), - Text('${list[index].reply}评论', style: textStyle), - ], - ), ], ), - ), - ), - ], - ), - ); - }), - ), - ); - }, + ); + }), + ), + ); + }, + ) + : CustomScrollView( + slivers: [ + HttpError( + errMsg: '没有数据', + isShowBtn: false, + fn: () => {}, + ) + ], + ), ); } + +class CustomFilterChip extends StatelessWidget { + const CustomFilterChip({ + this.label, + this.type, + this.selectedType, + this.callFn, + Key? key, + }) : super(key: key); + + final String? label; + final ArticleFilterType? type; + final ArticleFilterType? selectedType; + final Function? callFn; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 34, + child: FilterChip( + padding: const EdgeInsets.only(left: 11, right: 11), + labelPadding: EdgeInsets.zero, + label: Text( + label!, + style: const TextStyle(fontSize: 13), + ), + labelStyle: TextStyle( + color: type == selectedType + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline), + selected: type == selectedType, + showCheckmark: false, + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + selectedColor: Colors.transparent, + // backgroundColor: + // Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5), + backgroundColor: Colors.transparent, + side: BorderSide.none, + onSelected: (bool selected) => callFn!(selected), + ), + ); + } +} + +class ArticlePanelController extends GetxController { + RxList filterList = [{}].obs; + Rx selectedType = ArticleFilterType.values.first.obs; + + @override + void onInit() { + List> list = ArticleFilterType.values + .map((type) => { + 'label': type.description, + 'type': type, + }) + .toList(); + filterList.value = list; + super.onInit(); + } +} From 00423be5a951a3c88f2d1331cf7c12c07f48f1ff Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 5 Oct 2024 14:18:24 +0800 Subject: [PATCH 126/152] =?UTF-8?q?mod:=20=E4=B8=80=E9=94=AE=E4=B8=89?= =?UTF-8?q?=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/detail/introduction/controller.dart | 34 +++- lib/pages/video/detail/introduction/view.dart | 169 +++--------------- 2 files changed, 52 insertions(+), 151 deletions(-) diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index c0f6eebb..75e08145 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -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"]; @@ -604,4 +602,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('确认'), + ) + ], + ); + }, + ); + } } diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 04cf92fe..31edab2f 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -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 with TickerProviderStateMixin { RxBool isExpand = false.obs; late ExpandableController _expandableCtr; - // 一键三连动画 - late AnimationController _controller; - late Animation _scaleTransition; - final RxDouble _progress = 0.0.obs; - void Function()? handleState(Future Function() action) { return isProcessing ? null @@ -178,26 +170,6 @@ class _VideoInfoState extends State 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(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 with TickerProviderStateMixin { @override void dispose() { _expandableCtr.dispose(); - _controller.dispose(); - _scaleTransition.removeListener(() {}); super.dispose(); } @@ -573,131 +543,34 @@ class _VideoInfoState extends State 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 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 animation) { - return ScaleTransition( - scale: animation, child: child); - }, - child: Icon( - key: ValueKey(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( From 2dca3759011beed7e4e145b285458295b05b4fb5 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 6 Oct 2024 16:38:50 +0800 Subject: [PATCH 127/152] mod --- lib/pages/video/detail/view.dart | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 5476adc9..d155509f 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -68,10 +68,6 @@ class _VideoDetailPageState extends State late final AppLifecycleListener _lifecycleListener; late double statusHeight; - // 稍后再看控制器 - // late AnimationController _laterCtr; - // late Animation _laterOffsetAni; - @override void initState() { super.initState(); @@ -108,7 +104,6 @@ class _VideoDetailPageState extends State } WidgetsBinding.instance.addObserver(this); lifecycleListener(); - // watchLaterControllerInit(); } // 获取视频资源,初始化播放器 @@ -242,8 +237,6 @@ class _VideoDetailPageState extends State appbarStream.close(); WidgetsBinding.instance.removeObserver(this); _lifecycleListener.dispose(); - // _laterCtr.dispose(); - // _laterOffsetAni.removeListener(() {}); super.dispose(); } @@ -490,21 +483,6 @@ class _VideoDetailPageState extends State ); } - /// 稍后再看控制器初始化 - // void watchLaterControllerInit() { - // _laterCtr = AnimationController( - // duration: const Duration(milliseconds: 300), - // vsync: this, - // ); - // _laterOffsetAni = Tween( - // 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); From fa53c1214cc198ade7dd0b155949bde38ee2113f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 6 Oct 2024 16:53:06 +0800 Subject: [PATCH 128/152] =?UTF-8?q?mod:=20=E8=BF=94=E5=9B=9E=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=97=B6stream=E9=87=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index d155509f..7693dee0 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -290,6 +290,7 @@ class _VideoDetailPageState extends State plPlayerController?.play(); } plPlayerController?.addStatusLister(playerListener); + appbarStream.add(0); super.didPopNext(); } From 64c2bcc6502e8b7117e9bee5aea040bab2d1db84 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 7 Oct 2024 17:55:39 +0800 Subject: [PATCH 129/152] =?UTF-8?q?opt:=20=E6=8E=A7=E5=88=B6=E5=99=A8?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/main/controller.dart | 5 +++- lib/pages/main/view.dart | 44 ++++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index ad2a7781..fd18d728 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -13,6 +13,7 @@ import '../../models/common/nav_bar_config.dart'; class MainController extends GetxController { List pages = []; + List pagesIds = []; RxList navigationBars = [].obs; late List defaultNavTabs; late List navBarSort; @@ -43,7 +44,8 @@ class MainController extends GetxController { SettingBoxKey.dynamicBadgeMode, defaultValue: DynamicBadgeMode.number.code)]; setNavBarConfig(); - if (dynamicBadgeType.value != DynamicBadgeMode.hidden) { + if (dynamicBadgeType.value != DynamicBadgeMode.hidden && + pagesIds.contains(2)) { getUnreadDynamic(); } enableGradientBg = @@ -104,6 +106,7 @@ class MainController extends GetxController { // 如果找不到匹配项,默认索引设置为0或其他合适的值 selectedIndex = defaultIndex != -1 ? defaultIndex : 0; pages = navigationBars.map((e) => e['page']).toList(); + pagesIds = navigationBars.map((e) => e['id']).toList(); } @override diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 3da667e8..4399bf30 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -22,10 +22,10 @@ class MainApp extends StatefulWidget { class _MainAppState extends State with SingleTickerProviderStateMixin { final MainController _mainController = Get.put(MainController()); - final HomeController _homeController = Get.put(HomeController()); - final RankController _rankController = Get.put(RankController()); - final DynamicsController _dynamicController = Get.put(DynamicsController()); - final MediaController _mediaController = Get.put(MediaController()); + late HomeController _homeController; + RankController? _rankController; + DynamicsController? _dynamicController; + MediaController? _mediaController; int? _lastSelectTime; //上次点击时间 Box setting = GStrorage.setting; @@ -38,6 +38,7 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { _mainController.pageController = PageController(initialPage: _mainController.selectedIndex); enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true); + controllerInit(); } void setIndex(int value) async { @@ -60,38 +61,51 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { } if (currentPage is RankPage) { - if (_rankController.flag) { + if (_rankController!.flag) { // 单击返回顶部 双击并刷新 if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { - _rankController.onRefresh(); + _rankController!.onRefresh(); } else { - _rankController.animateToTop(); + _rankController!.animateToTop(); } _lastSelectTime = DateTime.now().millisecondsSinceEpoch; } - _rankController.flag = true; + _rankController!.flag = true; } else { - _rankController.flag = false; + _rankController?.flag = false; } if (currentPage is DynamicsPage) { - if (_dynamicController.flag) { + if (_dynamicController!.flag) { // 单击返回顶部 双击并刷新 if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { - _dynamicController.onRefresh(); + _dynamicController!.onRefresh(); } else { - _dynamicController.animateToTop(); + _dynamicController!.animateToTop(); } _lastSelectTime = DateTime.now().millisecondsSinceEpoch; } - _dynamicController.flag = true; + _dynamicController!.flag = true; _mainController.clearUnread(); } else { - _dynamicController.flag = false; + _dynamicController?.flag = false; } if (currentPage is MediaPage) { - _mediaController.queryFavFolder(); + _mediaController!.queryFavFolder(); + } + } + + void controllerInit() { + _homeController = Get.put(HomeController()); + if (_mainController.pagesIds.contains(1)) { + _rankController = Get.put(RankController()); + } + if (_mainController.pagesIds.contains(2)) { + _dynamicController = Get.put(DynamicsController()); + } + if (_mainController.pagesIds.contains(3)) { + _mediaController = Get.put(MediaController()); } } From e94f0081683344d0b22e8636e8adde141f400086 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 7 Oct 2024 21:46:00 +0800 Subject: [PATCH 130/152] =?UTF-8?q?opt:=20appScheme=20=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/app_scheme.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index 22184139..f26eff77 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -175,6 +175,11 @@ class PiliSchame { if (lastPathSegment.contains('ep')) { RoutePush.bangumiPush(null, Utils.matchNum(lastPathSegment).first); } + } else if (path.startsWith('/BV')) { + final String bvid = path.split('?').first.split('/').last; + _videoPush(null, bvid); + } else if (path.startsWith('/av')) { + _videoPush(Utils.matchNum(path.split('?').first).first, null); } } else if (host.contains('live')) { int roomId = int.parse(path!.split('/').last); From 16847b8a92c3d41bd727439989b2fe62a2ef73ed Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 7 Oct 2024 22:39:48 +0800 Subject: [PATCH 131/152] =?UTF-8?q?mod:=20=E8=AF=84=E8=AE=BA=E8=B7=B3?= =?UTF-8?q?=E8=BD=ACcv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/detail/reply/widgets/reply_item.dart | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 26dc2e5a..2ba97a20 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -727,14 +727,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( From a366f5d3ce06552632a28f3a4f296a7a43039793 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 7 Oct 2024 22:51:02 +0800 Subject: [PATCH 132/152] =?UTF-8?q?opt:=20=E4=B8=93=E6=A0=8F=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=8F=AF=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/opus/view.dart | 2 +- lib/pages/read/controller.dart | 2 +- lib/pages/read/view.dart | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/pages/opus/view.dart b/lib/pages/opus/view.dart index 434a9405..42c0c419 100644 --- a/lib/pages/opus/view.dart +++ b/lib/pages/opus/view.dart @@ -157,7 +157,7 @@ class _OpusPageState extends State { Container( alignment: TextHelper.getAlignment(paragraph.align), margin: const EdgeInsets.only(bottom: 10), - child: Text.rich( + child: SelectableText.rich( TextSpan( children: paragraph.text?.nodes?.map((node) { return TextHelper.buildTextSpan( diff --git a/lib/pages/read/controller.dart b/lib/pages/read/controller.dart index efa43c98..ac81aca8 100644 --- a/lib/pages/read/controller.dart +++ b/lib/pages/read/controller.dart @@ -21,7 +21,7 @@ class ReadPageController extends GetxController { super.onInit(); title.value = Get.parameters['title'] ?? ''; id = Get.parameters['id']!; - articleType = Get.parameters['articleType']!; + articleType = Get.parameters['articleType'] ?? 'read'; url = 'https://www.bilibili.com/read/cv$id'; scrollController.addListener(_scrollListener); fetchViewInfo(); diff --git a/lib/pages/read/view.dart b/lib/pages/read/view.dart index d37eeae5..710934eb 100644 --- a/lib/pages/read/view.dart +++ b/lib/pages/read/view.dart @@ -126,7 +126,6 @@ class _ReadPageState extends State { Widget _buildContent(ReadDataModel cvData) { final List picList = _extractPicList(cvData); final List imgList = extractDataSrc(cvData.readInfo!.content!); - return Padding( padding: EdgeInsets.fromLTRB( 16, 0, 16, MediaQuery.of(context).padding.bottom + 40), @@ -163,9 +162,11 @@ class _ReadPageState extends State { padding: const EdgeInsets.only(bottom: 20), child: _buildAuthorWidget(cvData), ), - HtmlRender( - htmlContent: cvData.readInfo!.content!, - imgList: imgList, + SelectionArea( + child: HtmlRender( + htmlContent: cvData.readInfo!.content!, + imgList: imgList, + ), ), ], ); @@ -206,7 +207,7 @@ class _ReadPageState extends State { return Container( alignment: TextHelper.getAlignment(paragraph.align), margin: const EdgeInsets.only(bottom: 10), - child: Text.rich( + child: SelectableText.rich( TextSpan( children: paragraph.text?.nodes?.map((node) { return TextHelper.buildTextSpan(node, paragraph.align, context); From 9d965aae2f8916ec1db7b15c4124cee30a3393ce Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 7 Oct 2024 23:15:17 +0800 Subject: [PATCH 133/152] =?UTF-8?q?mod:=20=E7=95=AA=E5=89=A7=E5=B0=81?= =?UTF-8?q?=E9=9D=A2=E6=AF=94=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/bangumi/introduction/view.dart | 6 +++--- lib/pages/bangumi/view.dart | 8 ++++---- lib/pages/bangumi/widgets/bangumu_card_v.dart | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index 94ee24de..188debef 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -189,8 +189,8 @@ class _BangumiInfoState extends State { Stack( children: [ NetworkImgLayer( - width: 105, - height: 160, + width: 115, + height: 115 / 0.75, src: widget.bangumiDetail!.cover!, ), PBadge( @@ -208,7 +208,7 @@ class _BangumiInfoState extends State { child: InkWell( onTap: () => showIntroDetail(), child: SizedBox( - height: 158, + height: 115 / 0.75, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index 3adfdc1f..9ec72350 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -96,7 +96,8 @@ class _BangumiPageState extends State ), ), SizedBox( - height: 268, + height: Get.size.width / 3 / 0.75 + + MediaQuery.textScalerOf(context).scale(50.0), child: FutureBuilder( future: _futureBuilderFutureFollow, builder: @@ -117,7 +118,6 @@ class _BangumiPageState extends State itemBuilder: (context, index) { return Container( width: Get.size.width / 3, - height: 254, margin: EdgeInsets.only( left: StyleString.safeSpace, right: index == @@ -208,8 +208,8 @@ class _BangumiPageState extends State crossAxisSpacing: StyleString.cardSpace, // 列数 crossAxisCount: 3, - mainAxisExtent: Get.size.width / 3 / 0.65 + - MediaQuery.textScalerOf(context).scale(32.0), + mainAxisExtent: Get.size.width / 3 / 0.75 + + MediaQuery.textScalerOf(context).scale(42.0), ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { diff --git a/lib/pages/bangumi/widgets/bangumu_card_v.dart b/lib/pages/bangumi/widgets/bangumu_card_v.dart index a1d7a931..10d95a1c 100644 --- a/lib/pages/bangumi/widgets/bangumu_card_v.dart +++ b/lib/pages/bangumi/widgets/bangumu_card_v.dart @@ -37,7 +37,7 @@ class BangumiCardV extends StatelessWidget { StyleString.imgRadius, ), child: AspectRatio( - aspectRatio: 0.65, + aspectRatio: 0.75, child: LayoutBuilder(builder: (context, boxConstraints) { final double maxWidth = boxConstraints.maxWidth; final double maxHeight = boxConstraints.maxHeight; From 48d869a6c0d2a255691e02efa1d199f86afb3297 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 7 Oct 2024 23:46:55 +0800 Subject: [PATCH 134/152] =?UTF-8?q?opt:=20=E8=A7=86=E9=A2=91=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E9=A1=B5=E7=8A=B6=E6=80=81=E6=A0=8F=E5=89=8D=E6=99=AF?= =?UTF-8?q?=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/view.dart | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 7693dee0..50e94cb1 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -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'; @@ -594,10 +595,21 @@ class _VideoDetailPageState extends State 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, + systemOverlayStyle: Get.isDarkMode + ? SystemUiOverlayStyle.light + : snapshot.data!.toDouble() > kToolbarHeight + ? SystemUiOverlayStyle.dark + : SystemUiOverlayStyle.light, + ); + }), ), ), body: ExtendedNestedScrollView( From 3830380172c26c299b1132a33a0633fb7c3432f6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 8 Oct 2024 23:46:40 +0800 Subject: [PATCH 135/152] =?UTF-8?q?feat:=20=E9=A1=BA=E5=BA=8F=E6=92=AD?= =?UTF-8?q?=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/video/later.dart | 12 ++++-- .../video/detail/introduction/controller.dart | 40 +++++++++++++------ .../detail/widgets/watch_later_list.dart | 4 +- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/lib/models/video/later.dart b/lib/models/video/later.dart index f722a231..7829e1c7 100644 --- a/lib/models/video/later.dart +++ b/lib/models/video/later.dart @@ -1,6 +1,7 @@ class MediaVideoItemModel { MediaVideoItemModel({ this.id, + this.aid, this.offset, this.index, this.intro, @@ -14,12 +15,13 @@ class MediaVideoItemModel { this.likeState, this.favState, this.page, + this.cid, this.pages, this.title, this.type, this.upper, this.link, - this.bvId, + this.bvid, this.shortLink, this.rights, this.elecInfo, @@ -32,6 +34,7 @@ class MediaVideoItemModel { }); int? id; + int? aid; int? offset; int? index; String? intro; @@ -45,12 +48,13 @@ class MediaVideoItemModel { int? likeState; int? favState; int? page; + int? cid; List? pages; String? title; int? type; Upper? upper; String? link; - String? bvId; + String? bvid; String? shortLink; Rights? rights; dynamic elecInfo; @@ -64,6 +68,7 @@ class MediaVideoItemModel { factory MediaVideoItemModel.fromJson(Map json) => MediaVideoItemModel( id: json["id"], + aid: json["id"], offset: json["offset"], index: json["index"], intro: json["intro"], @@ -77,6 +82,7 @@ class MediaVideoItemModel { likeState: json["like_state"], favState: json["fav_state"], page: json["page"], + cid: json["pages"] == null ? -1 : json["pages"].first['id'], // json["pages"] 可能为null pages: json["pages"] == null ? [] @@ -85,7 +91,7 @@ class MediaVideoItemModel { type: json["type"], upper: Upper.fromJson(json["upper"]), link: json["link"], - bvId: json["bv_id"], + bvid: json["bv_id"], shortLink: json["short_link"], rights: Rights.fromJson(json["rights"]), elecInfo: json["elec_info"], diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 786d8172..83e5b012 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -413,7 +413,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(tag: heroTag); @@ -424,13 +429,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 +486,13 @@ class VideoIntroController extends GetxController { final List episodes = []; bool isPages = false; late String cover; - if (videoDetail.value.ugcSeason != null) { + final VideoDetailController videoDetailCtr = + Get.find(tag: heroTag); + + /// 优先稍后再看、收藏夹 + if (videoDetailCtr.isWatchLaterVisible.value) { + episodes.addAll(videoDetailCtr.mediaList); + } else if (videoDetail.value.ugcSeason != null) { final UgcSeason ugcSeason = videoDetail.value.ugcSeason!; final List sections = ugcSeason.sections!; for (int i = 0; i < sections.length; i++) { @@ -497,10 +509,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(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 +527,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); diff --git a/lib/pages/video/detail/widgets/watch_later_list.dart b/lib/pages/video/detail/widgets/watch_later_list.dart index b367ae40..8e83af4e 100644 --- a/lib/pages/video/detail/widgets/watch_later_list.dart +++ b/lib/pages/video/detail/widgets/watch_later_list.dart @@ -109,7 +109,7 @@ class _MediaListPanelState extends State { 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 { overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: FontWeight.w500, - color: item.bvId == widget.bvid + color: item.bvid == widget.bvid ? Theme.of(context) .colorScheme .primary From 51654ac54e554e0cfe579483d5e26ca7802e8f35 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 9 Oct 2024 22:03:58 +0800 Subject: [PATCH 136/152] =?UTF-8?q?opt:=20=E8=AF=84=E8=AE=BA=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E6=A0=8F=E9=97=B4=E9=9A=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/reply/view.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index f91ef625..1b70015c 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -153,7 +153,17 @@ class _VideoReplyPanelState extends State 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: [ From a44c0ae51ecb3758ecb8bb9c7f840920eb34cf89 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 9 Oct 2024 22:41:12 +0800 Subject: [PATCH 137/152] =?UTF-8?q?opt:=20=E8=AF=84=E8=AE=BA=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/reply/widgets/reply_item.dart | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 18347a30..18242fea 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -200,25 +200,36 @@ class ReplyItem extends StatelessWidget { ), ], ), - Row( - children: [ - 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, + ), + ), + ], + ), + ), ], ), ], From c634e602b9ba88f55b726d75fde186914e2656d0 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 9 Oct 2024 23:33:52 +0800 Subject: [PATCH 138/152] =?UTF-8?q?fix:=20=E5=AD=97=E5=B9=95=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=BF=87=E5=A4=A7=E6=97=B6=E9=A1=B5=E9=9D=A2=E5=8D=A1?= =?UTF-8?q?=E6=AD=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/video.dart | 3 ++- lib/utils/subtitle.dart | 56 +++++++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/lib/http/video.dart b/lib/http/video.dart index 95ea6782..406e59f6 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -536,7 +536,8 @@ class VideoHttp { // 获取字幕内容 static Future> getSubtitleContent(url) async { var res = await Request().get('https:$url'); - final String content = SubTitleUtils.convertToWebVTT(res.data['body']); + final String content = + await SubTitleUtils.convertToWebVTT(res.data['body']); final List body = res.data['body']; return {'content': content, 'body': body}; } diff --git a/lib/utils/subtitle.dart b/lib/utils/subtitle.dart index 1b4088f3..9743711c 100644 --- a/lib/utils/subtitle.dart +++ b/lib/utils/subtitle.dart @@ -1,21 +1,51 @@ +import 'dart:async'; +import 'dart:isolate'; + class SubTitleUtils { // 格式整理 - static String convertToWebVTT(List jsonData) { - String webVTTContent = 'WEBVTT FILE\n\n'; + static Future convertToWebVTT(List jsonData) async { + final receivePort = ReceivePort(); + await Isolate.spawn(_convertToWebVTTIsolate, receivePort.sendPort); - for (int i = 0; i < jsonData.length; i++) { - final item = jsonData[i]; - double from = double.parse(item['from'].toString()); - double to = double.parse(item['to'].toString()); - int sid = (item['sid'] ?? 0) as int; - String content = item['content'] as String; + final sendPort = await receivePort.first as SendPort; + final response = ReceivePort(); + sendPort.send([jsonData, response.sendPort]); - webVTTContent += '$sid\n'; - webVTTContent += '${formatTime(from)} --> ${formatTime(to)}\n'; - webVTTContent += '$content\n\n'; + return await response.first as String; + } + + static void _convertToWebVTTIsolate(SendPort sendPort) async { + final port = ReceivePort(); + sendPort.send(port.sendPort); + + await for (final message in port) { + final List jsonData = message[0]; + final SendPort replyTo = message[1]; + + String webVTTContent = 'WEBVTT FILE\n\n'; + int chunkSize = 100; // 每次处理100条数据 + int totalChunks = (jsonData.length / chunkSize).ceil(); + + for (int chunk = 0; chunk < totalChunks; chunk++) { + int start = chunk * chunkSize; + int end = start + chunkSize; + if (end > jsonData.length) end = jsonData.length; + + for (int i = start; i < end; i++) { + final item = jsonData[i]; + double from = double.parse(item['from'].toString()); + double to = double.parse(item['to'].toString()); + int sid = (item['sid'] ?? 0) as int; + String content = item['content'] as String; + + webVTTContent += '$sid\n'; + webVTTContent += '${formatTime(from)} --> ${formatTime(to)}\n'; + webVTTContent += '$content\n\n'; + } + } + + replyTo.send(webVTTContent); } - - return webVTTContent; } static String formatTime(num seconds) { From fadefa2ddc75024db345e3e29748cff0828e3018 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 10 Oct 2024 22:26:46 +0800 Subject: [PATCH 139/152] =?UTF-8?q?opt:=20bangumiIntroController=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/bangumi/widgets/bangumi_panel.dart | 8 +++++--- lib/pages/video/detail/view.dart | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/pages/bangumi/widgets/bangumi_panel.dart b/lib/pages/bangumi/widgets/bangumi_panel.dart index 3df7ce25..3a589db6 100644 --- a/lib/pages/bangumi/widgets/bangumi_panel.dart +++ b/lib/pages/bangumi/widgets/bangumi_panel.dart @@ -86,9 +86,11 @@ class _BangumiPanelState extends State { item.aid, item.cover, ); - if (_bottomSheetController != null) { - _bottomSheetController?.close(); - } + try { + if (_bottomSheetController != null) { + _bottomSheetController?.close(); + } + } catch (_) {} currentIndex.value = i; scrollToIndex(); } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 50e94cb1..d3afdf1d 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -82,14 +82,16 @@ class _VideoDetailPageState extends State 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); From 2e206f20cc3e5ce77ccb1f31ed875de23bfc1b46 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 10 Oct 2024 23:02:16 +0800 Subject: [PATCH 140/152] Update yml --- .github/workflows/beta_ci.yml | 3 +-- .github/workflows/release_ci.yml | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/beta_ci.yml b/.github/workflows/beta_ci.yml index 14b51780..9c40de6b 100644 --- a/.github/workflows/beta_ci.yml +++ b/.github/workflows/beta_ci.yml @@ -12,7 +12,6 @@ on: - ".idea/**" - "!.github/workflows/**" - jobs: update_version: name: Read and update version @@ -96,7 +95,7 @@ jobs: if: steps.cache-flutter.outputs.cache-hit != 'true' uses: subosito/flutter-action@v2 with: - flutter-version: 3.16.5 + flutter-version: 3.19.6 channel: any - name: 下载项目依赖 diff --git a/.github/workflows/release_ci.yml b/.github/workflows/release_ci.yml index 78230645..f7c06d29 100644 --- a/.github/workflows/release_ci.yml +++ b/.github/workflows/release_ci.yml @@ -36,7 +36,7 @@ jobs: if: steps.cache-flutter.outputs.cache-hit != 'true' uses: subosito/flutter-action@v2 with: - flutter-version: 3.16.5 + flutter-version: 3.19.6 channel: any - name: 下载项目依赖 @@ -98,7 +98,7 @@ jobs: uses: subosito/flutter-action@v2.10.0 with: cache: true - flutter-version: 3.16.5 + flutter-version: 3.19.6 - name: flutter build ipa run: | From caa066237d87591652a6648a79a98faaa415b104 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 10 Oct 2024 23:04:59 +0800 Subject: [PATCH 141/152] =?UTF-8?q?v1.0.25=20=E6=9B=B4=E6=96=B0=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- change_log/1.0.25.1010.md | 39 +++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 change_log/1.0.25.1010.md diff --git a/change_log/1.0.25.1010.md b/change_log/1.0.25.1010.md new file mode 100644 index 00000000..951efcb1 --- /dev/null +++ b/change_log/1.0.25.1010.md @@ -0,0 +1,39 @@ +## 1.0.25 + +### 功能 ++ 直播弹幕 ++ 稍后再看、收藏夹播放全部 ++ 收藏夹新建、编辑 ++ 评论删除 ++ 评论保存为图片 ++ 动态页滑动切换up ++ up投稿筛选充电视频 ++ 直播tab展示关注up ++ up主页专栏展示 + +### 优化 ++ 视频详情页一键三连 ++ 动态页标识充电视频 ++ 播放器亮度、音量调整百分比展示 ++ 封面预览时视频标题可复制 ++ 竖屏直播布局 ++ 图片预览 ++ 专栏渲染优化 ++ 私信图片查看 + +### 修复 ++ 收藏夹点击异常 ++ 搜索up异常 ++ 系统通知已读异常 ++ [赞了我的]展示错误 ++ 部分up合集无法打开 ++ 切换合集视频投币个数未重置 ++ 搜索条件筛选面板无法滚动 ++ 部分机型导航条未沉浸 ++ 专栏图片渲染问题 ++ 专栏浏览历史记录 ++ 直播间历史记录 + + +更多更新日志可在Github上查看 +问题反馈、功能建议请查看「关于」页面。 diff --git a/pubspec.yaml b/pubspec.yaml index 617b0c7d..ac04a1b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.24+1024 +version: 1.0.25+1025 environment: sdk: ">=3.0.0 <4.0.0" From fb84ba04815edb98633e0515d464c5726c2a7dfd Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 11 Oct 2024 00:23:27 +0800 Subject: [PATCH 142/152] =?UTF-8?q?fix:=20=E7=9B=B4=E6=92=AD=E9=97=B4?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E5=BC=82=E5=B8=B8=E3=80=81=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=99=A8=E5=88=9D=E5=A7=8B=E5=8C=96=E5=BC=82=E5=B8=B8=E3=80=81?= =?UTF-8?q?imgQuality/cover=E5=8F=96=E5=80=BC=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/network_img_layer.dart | 6 +- lib/pages/live_room/controller.dart | 2 +- lib/pages/live_room/view.dart | 214 +++++++++++----------- lib/pages/main/view.dart | 18 +- 4 files changed, 123 insertions(+), 117 deletions(-) diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart index 0b715a89..fbedfbba 100644 --- a/lib/common/widgets/network_img_layer.dart +++ b/lib/common/widgets/network_img_layer.dart @@ -33,7 +33,11 @@ class NetworkImgLayer extends StatelessWidget { @override Widget build(BuildContext context) { - final int defaultImgQuality = GlobalDataCache().imgQuality; + int defaultImgQuality = 10; + try { + defaultImgQuality = GlobalDataCache().imgQuality; + } catch (_) {} + if (src == '' || src == null) { return placeholder(context); } diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 34379f64..0d347bf2 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -64,7 +64,7 @@ class LiveRoomController extends GetxController { ? liveItem.pic : (liveItem.cover != null && liveItem.cover != '') ? liveItem.cover - : null; + : ''; } Request.getBuvid().then((value) => buvid = value); } diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index cff0c410..1e0814e9 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -108,6 +108,12 @@ class _LiveRoomPageState extends State @override Widget build(BuildContext context) { + final mediaQuery = MediaQuery.of(context); + final isPortrait = mediaQuery.orientation == Orientation.portrait; + final isLandscape = mediaQuery.orientation == Orientation.landscape; + + final padding = mediaQuery.padding; + Widget videoPlayerPanel = FutureBuilder( future: _futureBuilderFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { @@ -187,10 +193,8 @@ class _LiveRoomPageState extends State children: [ Obx( () => SizedBox( - height: MediaQuery.of(context).padding.top + - (_liveRoomController.isPortrait.value || - MediaQuery.of(context).orientation == - Orientation.landscape + height: padding.top + + (_liveRoomController.isPortrait.value || isLandscape ? 0 : kToolbarHeight), ), @@ -201,21 +205,18 @@ class _LiveRoomPageState extends State if (plPlayerController.isFullScreen.value == true) { plPlayerController.triggerFullScreen(status: false); } - if (MediaQuery.of(context).orientation == - Orientation.landscape) { + if (isLandscape) { verticalScreen(); } }, child: Obx( () => Container( width: Get.size.width, - height: MediaQuery.of(context).orientation == - Orientation.landscape + height: isLandscape ? Get.size.height : !_liveRoomController.isPortrait.value ? Get.size.width * 9 / 16 - : Get.size.height - - MediaQuery.of(context).padding.top, + : Get.size.height - padding.top, clipBehavior: Clip.hardEdge, decoration: const BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(6)), @@ -229,7 +230,7 @@ class _LiveRoomPageState extends State // 定位 快速滑动到底部 Positioned( right: 20, - bottom: MediaQuery.of(context).padding.bottom + 80, + bottom: padding.bottom + 80, child: SlideTransition( position: Tween( begin: const Offset(0, 4), @@ -262,10 +263,7 @@ class _LiveRoomPageState extends State titleSpacing: 0, backgroundColor: Colors.transparent, foregroundColor: Colors.white, - toolbarHeight: - MediaQuery.of(context).orientation == Orientation.portrait - ? 56 - : 0, + toolbarHeight: isPortrait ? 56 : 0, title: FutureBuilder( future: _futureBuilder, builder: (context, snapshot) { @@ -317,35 +315,38 @@ class _LiveRoomPageState extends State ), // 消息列表 Obx( - () => Positioned( - top: MediaQuery.of(context).padding.top + - kToolbarHeight + - (_liveRoomController.isPortrait.value - ? Get.size.width - : Get.size.width * 9 / 16), - bottom: 90 + MediaQuery.of(context).padding.bottom, - left: 0, - right: 0, - child: buildMessageListUI( - context, - _liveRoomController, - _scrollController, + () => Align( + alignment: Alignment.bottomCenter, + child: Container( + margin: EdgeInsets.only( + bottom: 90 + padding.bottom, + ), + height: Get.size.height - + (padding.top + + kToolbarHeight + + (_liveRoomController.isPortrait.value + ? Get.size.width + : Get.size.width * 9 / 16) + + 100 + + padding.bottom), + child: buildMessageListUI( + context, + _liveRoomController, + _scrollController, + ), ), ), ), // 消息输入框 Visibility( - visible: MediaQuery.of(context).orientation == Orientation.portrait, + visible: isPortrait, child: Positioned( bottom: 0, left: 0, right: 0, child: Container( padding: EdgeInsets.only( - left: 14, - right: 14, - top: 4, - bottom: MediaQuery.of(context).padding.bottom + 20), + left: 14, right: 14, top: 4, bottom: padding.bottom + 20), decoration: BoxDecoration( color: Colors.grey.withOpacity(0.1), borderRadius: const BorderRadius.all(Radius.circular(20)), @@ -421,6 +422,7 @@ class _LiveRoomPageState extends State ], ), ); + if (Platform.isAndroid) { return PiPSwitcher( childWhenDisabled: childWhenDisabled, @@ -438,84 +440,82 @@ Widget buildMessageListUI( LiveRoomController liveRoomController, ScrollController scrollController, ) { - return Expanded( - child: Obx( - () => MediaQuery.removePadding( - context: context, - removeTop: true, - removeBottom: true, - child: ShaderMask( - shaderCallback: (Rect bounds) { - return LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.transparent, - Colors.black.withOpacity(0.5), - Colors.black, - ], - stops: const [0.01, 0.05, 0.2], - ).createShader(bounds); + return Obx( + () => MediaQuery.removePadding( + context: context, + removeTop: true, + removeBottom: true, + child: ShaderMask( + shaderCallback: (Rect bounds) { + return LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black.withOpacity(0.5), + Colors.black, + ], + stops: const [0.01, 0.05, 0.2], + ).createShader(bounds); + }, + blendMode: BlendMode.dstIn, + child: GestureDetector( + onTap: () { + // 键盘失去焦点 + FocusScope.of(context).requestFocus(FocusNode()); }, - blendMode: BlendMode.dstIn, - child: GestureDetector( - onTap: () { - // 键盘失去焦点 - FocusScope.of(context).requestFocus(FocusNode()); - }, - child: ListView.builder( - controller: scrollController, - itemCount: liveRoomController.messageList.length, - itemBuilder: (context, index) { - final LiveMessageModel liveMsgItem = - liveRoomController.messageList[index]; - return Align( - alignment: Alignment.centerLeft, - child: Container( - decoration: BoxDecoration( - color: liveRoomController.isPortrait.value - ? Colors.black.withOpacity(0.3) - : Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - margin: EdgeInsets.only( - top: index == 0 ? 20.0 : 0.0, - bottom: 6.0, - left: 14.0, - right: 14.0, - ), - padding: const EdgeInsets.symmetric( - vertical: 3.0, - horizontal: 10.0, - ), - child: Text.rich( - TextSpan( - style: const TextStyle(color: Colors.white), - children: [ - TextSpan( - text: '${liveMsgItem.userName}: ', - style: TextStyle( - color: Colors.white.withOpacity(0.6), - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - // 处理点击事件 - print('Text clicked'); - }, + child: ListView.builder( + controller: scrollController, + itemCount: liveRoomController.messageList.length, + itemBuilder: (context, index) { + final LiveMessageModel liveMsgItem = + liveRoomController.messageList[index]; + return Align( + alignment: Alignment.centerLeft, + child: Container( + decoration: BoxDecoration( + color: liveRoomController.isPortrait.value + ? Colors.black.withOpacity(0.3) + : Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + margin: EdgeInsets.only( + top: index == 0 ? 20.0 : 0.0, + bottom: 6.0, + left: 14.0, + right: 14.0, + ), + padding: const EdgeInsets.symmetric( + vertical: 3.0, + horizontal: 10.0, + ), + child: Text.rich( + TextSpan( + style: const TextStyle(color: Colors.white), + children: [ + TextSpan( + text: '${liveMsgItem.userName}: ', + style: TextStyle( + color: Colors.white.withOpacity(0.6), ), - TextSpan( - children: [ - ...buildMessageTextSpan(context, liveMsgItem) - ], - // text: liveMsgItem.message, - ), - ], - ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // 处理点击事件 + print('Text clicked'); + }, + ), + TextSpan( + children: [ + ...buildMessageTextSpan(context, liveMsgItem) + ], + // text: liveMsgItem.message, + ), + ], ), ), - ); - }, - ), + ), + ); + }, ), ), ), diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 4399bf30..f829b0c1 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -24,8 +24,8 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { final MainController _mainController = Get.put(MainController()); late HomeController _homeController; RankController? _rankController; - DynamicsController? _dynamicController; - MediaController? _mediaController; + late DynamicsController _dynamicController; + late MediaController _mediaController; int? _lastSelectTime; //上次点击时间 Box setting = GStrorage.setting; @@ -76,28 +76,30 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { } if (currentPage is DynamicsPage) { - if (_dynamicController!.flag) { + if (_dynamicController.flag) { // 单击返回顶部 双击并刷新 if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { - _dynamicController!.onRefresh(); + _dynamicController.onRefresh(); } else { - _dynamicController!.animateToTop(); + _dynamicController.animateToTop(); } _lastSelectTime = DateTime.now().millisecondsSinceEpoch; } - _dynamicController!.flag = true; + _dynamicController.flag = true; _mainController.clearUnread(); } else { - _dynamicController?.flag = false; + _dynamicController.flag = false; } if (currentPage is MediaPage) { - _mediaController!.queryFavFolder(); + _mediaController.queryFavFolder(); } } void controllerInit() { _homeController = Get.put(HomeController()); + _dynamicController = Get.put(DynamicsController()); + _mediaController = Get.put(MediaController()); if (_mainController.pagesIds.contains(1)) { _rankController = Get.put(RankController()); } From fffbd62f4fda91a53464225ec00012c4ba6af578 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 12 Oct 2024 11:35:49 +0800 Subject: [PATCH 143/152] opt: Request().get --- lib/http/init.dart | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/http/init.dart b/lib/http/init.dart index eae94ae4..abe8d019 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -217,12 +217,13 @@ class Request { /* * get请求 */ - get(url, {data, options, cancelToken, extra}) async { + get(url, {data, Options? options, cancelToken, extra}) async { Response response; - final Options options = Options(); + options ??= Options(); // 如果 options 为 null,则初始化一个新的 Options 对象 ResponseType resType = ResponseType.json; + if (extra != null) { - resType = extra!['resType'] ?? ResponseType.json; + resType = extra['resType'] ?? ResponseType.json; if (extra['ua'] != null) { options.headers = {'user-agent': headerUa(type: extra['ua'])}; } @@ -238,14 +239,11 @@ class Request { ); return response; } on DioException catch (e) { - Response errResponse = Response( - data: { - 'message': await ApiInterceptor.dioError(e) - }, // 将自定义 Map 数据赋值给 Response 的 data 属性 + return Response( + data: {'message': await ApiInterceptor.dioError(e)}, statusCode: 200, requestOptions: RequestOptions(), ); - return errResponse; } } From cf29a62a524cb3da1bb5457361b55a2ca4b474d6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 12 Oct 2024 14:16:53 +0800 Subject: [PATCH 144/152] =?UTF-8?q?fix:=20=E7=BC=93=E5=AD=98=E5=BC=B9?= =?UTF-8?q?=E5=B9=95=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/plugin/pl_player/controller.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 8dff954a..0bd7950c 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -1045,6 +1045,14 @@ class PlPlayerController { /// 缓存本次弹幕选项 cacheDanmakuOption() { + final cache = GlobalDataCache(); + cache.blockTypes = blockTypes; + cache.showArea = showArea; + cache.opacityVal = opacityVal; + cache.fontSizeVal = fontSizeVal; + cache.danmakuDurationVal = danmakuDurationVal; + cache.strokeWidth = strokeWidth; + localCache.put(LocalCacheKey.danmakuBlockType, blockTypes); localCache.put(LocalCacheKey.danmakuShowArea, showArea); localCache.put(LocalCacheKey.danmakuOpacity, opacityVal); From e7bbd3d32aa50fd321f02e2ac62f8dff290daae3 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 12 Oct 2024 14:27:16 +0800 Subject: [PATCH 145/152] =?UTF-8?q?mod:=20=E5=AE=98=E7=BD=91&=E7=BD=91?= =?UTF-8?q?=E7=9B=98=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/about/index.dart | 4 ++-- lib/utils/utils.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart index 81d3c3f4..9164d4e9 100644 --- a/lib/pages/about/index.dart +++ b/lib/pages/about/index.dart @@ -295,7 +295,7 @@ class AboutController extends GetxController { displayTime: const Duration(milliseconds: 500), ).then( (value) => launchUrl( - Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'), + Uri.parse('https://www.123684.com/s/9sVqVv-DEZ0A'), mode: LaunchMode.externalApplication, ), ); @@ -349,7 +349,7 @@ class AboutController extends GetxController { // 官网 webSiteUrl() { launchUrl( - Uri.parse('https://pilipalanet.mysxl.cn'), + Uri.parse('https://pilipala.life'), mode: LaunchMode.externalApplication, ); } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index e50c295c..7a254f7d 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -306,7 +306,7 @@ class Utils { onPressed: () async { await SmartDialog.dismiss(); launchUrl( - Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'), + Uri.parse('https://www.123684.com/s/9sVqVv-DEZ0A'), mode: LaunchMode.externalApplication, ); }, From 85f77ed93349e6f7f15cb45395a87d6cb45dd8e5 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 12 Oct 2024 22:05:04 +0800 Subject: [PATCH 146/152] =?UTF-8?q?mod:=20up=E4=B8=BB=E9=A1=B5wWebid?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/member.dart | 31 +++++++++++++++--------- lib/pages/member_article/controller.dart | 16 ------------ lib/utils/global_data_cache.dart | 1 + 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/lib/http/member.dart b/lib/http/member.dart index c7b22359..fc99c987 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -4,6 +4,7 @@ import 'package:hive/hive.dart'; import 'package:html/parser.dart'; import 'package:pilipala/models/member/article.dart'; import 'package:pilipala/models/member/like.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import '../common/constants.dart'; import '../models/dynamics/result.dart'; import '../models/follow/result.dart'; @@ -19,14 +20,20 @@ import 'index.dart'; class MemberHttp { static Future memberInfo({ - int? mid, + required int mid, String token = '', }) async { + String? wWebid; + if ((await getWWebid(mid: mid))['status']) { + wWebid = GlobalDataCache().wWebid; + } + Map params = await WbiSign().makSign({ 'mid': mid, 'token': token, 'platform': 'web', 'web_location': 1550101, + ...wWebid != null ? {'w_webid': wWebid} : {}, }); var res = await Request().get( Api.memberInfo, @@ -566,6 +573,10 @@ class MemberHttp { } static Future getWWebid({required int mid}) async { + String? wWebid = GlobalDataCache().wWebid; + if (wWebid != null) { + return {'status': true, 'data': wWebid}; + } var res = await Request().get('https://space.bilibili.com/$mid/article'); String? headContent = parse(res.data).head?.outerHtml; final regex = RegExp( @@ -576,6 +587,7 @@ class MemberHttp { final content = match.group(1); String decodedString = Uri.decodeComponent(content!); Map map = jsonDecode(decodedString); + GlobalDataCache().wWebid = map['access_id']; return {'status': true, 'data': map['access_id']}; } else { return {'status': false, 'data': '请检查登录状态'}; @@ -588,25 +600,20 @@ class MemberHttp { static Future getMemberArticle({ required int mid, required int pn, - required String wWebid, String? offset, }) async { + String? wWebid; + if ((await getWWebid(mid: mid))['status']) { + wWebid = GlobalDataCache().wWebid; + } Map params = await WbiSign().makSign({ 'host_mid': mid, 'page': pn, 'offset': offset, 'web_location': 333.999, - 'w_webid': wWebid, - }); - var res = await Request().get(Api.opusList, data: { - 'host_mid': mid, - 'page': pn, - 'offset': offset, - 'web_location': 333.999, - 'w_webid': wWebid, - 'w_rid': params['w_rid'], - 'wts': params['wts'], + ...wWebid != null ? {'w_webid': wWebid} : {}, }); + var res = await Request().get(Api.opusList, data: params); if (res.data['code'] == 0) { return { 'status': true, diff --git a/lib/pages/member_article/controller.dart b/lib/pages/member_article/controller.dart index cffce2fe..d79fb4a6 100644 --- a/lib/pages/member_article/controller.dart +++ b/lib/pages/member_article/controller.dart @@ -10,7 +10,6 @@ class MemberArticleController extends GetxController { int pn = 1; String? offset; bool hasMore = true; - String? wWebid; RxBool isLoading = false.obs; RxList articleList = [].obs; @@ -20,25 +19,11 @@ class MemberArticleController extends GetxController { mid = int.parse(Get.parameters['mid']!); } - // 获取wWebid - Future getWWebid() async { - var res = await MemberHttp.getWWebid(mid: mid); - if (res['status']) { - wWebid = res['data']; - } else { - wWebid = '-1'; - SmartDialog.showToast(res['msg']); - } - } - Future getMemberArticle(type) async { if (isLoading.value) { return; } isLoading.value = true; - if (wWebid == null) { - await getWWebid(); - } if (type == 'init') { pn = 1; articleList.clear(); @@ -47,7 +32,6 @@ class MemberArticleController extends GetxController { mid: mid, pn: pn, offset: offset, - wWebid: wWebid!, ); if (res['status']) { offset = res['data'].offset; diff --git a/lib/utils/global_data_cache.dart b/lib/utils/global_data_cache.dart index 9d925a0f..a2b454a9 100644 --- a/lib/utils/global_data_cache.dart +++ b/lib/utils/global_data_cache.dart @@ -15,6 +15,7 @@ class GlobalDataCache { late FullScreenGestureMode fullScreenGestureMode; late bool enablePlayerControlAnimation; late List actionTypeSort; + String? wWebid; /// 播放器相关 // 弹幕开关 From c1cd024db6ffcee4e8c3192990b9c4360328239b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 13 Oct 2024 01:11:48 +0800 Subject: [PATCH 147/152] =?UTF-8?q?opt:=20up=E4=B8=BB=E9=A1=B5=E5=B8=83?= =?UTF-8?q?=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member/controller.dart | 118 ++-- lib/pages/member/view.dart | 651 +++++++++----------- lib/pages/member/widgets/commen_widget.dart | 24 + lib/pages/member/widgets/profile.dart | 476 +++++++------- 4 files changed, 601 insertions(+), 668 deletions(-) create mode 100644 lib/pages/member/widgets/commen_widget.dart diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 3b7f24a4..8454cebe 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -49,6 +49,8 @@ class MemberController extends GetxController { if (res['status']) { memberInfo.value = res['data']; face.value = res['data'].face; + } else { + SmartDialog.showToast('用户信息请求异常:${res['msg']}'); } return res; } @@ -78,42 +80,10 @@ class MemberController extends GetxController { return; } if (attribute.value == 128) { - blockUser(); - return; + modifyRelation('block'); + } else { + modifyRelation('follow'); } - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('提示'), - content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'), - actions: [ - TextButton( - onPressed: () => SmartDialog.dismiss(), - child: Text( - '点错了', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - onPressed: () async { - await VideoHttp.relationMod( - mid: mid, - act: memberInfo.value.isFollowed! ? 2 : 1, - reSrc: 11, - ); - memberInfo.value.isFollowed = !memberInfo.value.isFollowed!; - relationSearch(); - SmartDialog.dismiss(); - memberInfo.update((val) {}); - }, - child: const Text('确认'), - ) - ], - ); - }, - ); } // 关系查询 @@ -123,24 +93,15 @@ class MemberController extends GetxController { var res = await UserHttp.hasFollow(mid); if (res['status']) { attribute.value = res['data']['attribute']; - switch (attribute.value) { - case 1: - attributeText.value = '悄悄关注'; - break; - case 2: - attributeText.value = '已关注'; - break; - case 6: - attributeText.value = '已互关'; - break; - case 128: - attributeText.value = '已拉黑'; - break; - default: - attributeText.value = '关注'; - } + final Map attributeTextMap = { + 1: '悄悄关注', + 2: '已关注', + 6: '已互关', + 128: '已拉黑', + }; + attributeText.value = attributeTextMap[attribute.value] ?? '关注'; if (res['data']['special'] == 1) { - attributeText.value += 'SP'; + attributeText.value = '特别关注'; } } } @@ -151,16 +112,37 @@ class MemberController extends GetxController { SmartDialog.showToast('账号未登录'); return; } - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, + modifyRelation('block'); + } + +// 合并关注/取关和拉黑逻辑 + Future modifyRelation(String actionType) async { + if (userInfo == null) { + SmartDialog.showToast('账号未登录'); + return; + } + + String contentText; + int act; + if (actionType == 'follow') { + contentText = memberInfo.value.isFollowed! ? '确定取消关注UP主?' : '确定关注UP主?'; + act = memberInfo.value.isFollowed! ? 2 : 1; + } else if (actionType == 'block') { + contentText = attribute.value != 128 ? '确定拉黑UP主?' : '确定从黑名单移除UP主?'; + act = attribute.value != 128 ? 5 : 6; + } else { + return; + } + + showDialog( + context: Get.context!, builder: (BuildContext context) { return AlertDialog( title: const Text('提示'), - content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'), + content: Text(contentText), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Navigator.of(context).pop(), child: Text( '点错了', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -170,19 +152,26 @@ class MemberController extends GetxController { onPressed: () async { var res = await VideoHttp.relationMod( mid: mid, - act: attribute.value != 128 ? 5 : 6, + act: act, reSrc: 11, ); SmartDialog.dismiss(); if (res['status']) { - attribute.value = attribute.value != 128 ? 128 : 0; - attributeText.value = attribute.value == 128 ? '已拉黑' : '关注'; - memberInfo.value.isFollowed = false; + if (actionType == 'follow') { + memberInfo.value.isFollowed = !memberInfo.value.isFollowed!; + } else if (actionType == 'block') { + attribute.value = attribute.value != 128 ? 128 : 0; + attributeText.value = attribute.value == 128 ? '已拉黑' : '关注'; + memberInfo.value.isFollowed = false; + } relationSearch(); + if (context.mounted) { + Navigator.of(context).pop(); + } memberInfo.update((val) {}); } }, - child: const Text('确认'), + child: const Text('确定'), ) ], ); @@ -228,17 +217,14 @@ class MemberController extends GetxController { // 跳转查看动态 void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid'); - // 跳转查看投稿 void pushArchivesPage() => Get.toNamed('/memberArchive?mid=$mid'); - - // 跳转查看专栏 - void pushSeasonsPage() {} // 跳转查看最近投币 void pushRecentCoinsPage() async { if (recentCoinsList.isNotEmpty) {} } + // 跳转查看收藏夹 void pushfavPage() => Get.toNamed('/fav?mid=$mid'); // 跳转图文专栏 void pushArticlePage() => Get.toNamed('/memberArticle?mid=$mid'); diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index fafba9dc..df501253 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -1,13 +1,13 @@ import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/models/member/info.dart'; import 'package:pilipala/pages/member/index.dart'; import 'package:pilipala/utils/utils.dart'; - +import 'widgets/commen_widget.dart'; import 'widgets/conis.dart'; import 'widgets/like.dart'; import 'widgets/profile.dart'; @@ -65,259 +65,233 @@ class _MemberPageState extends State @override Widget build(BuildContext context) { return Scaffold( - primary: true, - body: Column( - children: [ - AppBar( - title: StreamBuilder( - stream: appbarStream.stream.distinct(), - initialData: false, - builder: (BuildContext context, AsyncSnapshot snapshot) { - return AnimatedOpacity( - opacity: snapshot.data ? 1 : 0, - curve: Curves.easeOut, - duration: const Duration(milliseconds: 500), - child: Row( - children: [ - Row( - children: [ - Obx( - () => NetworkImgLayer( - width: 35, - height: 35, - type: 'avatar', - src: _memberController.face.value, - ), - ), - const SizedBox(width: 10), - Obx( - () => Text( - _memberController.memberInfo.value.name ?? '', - style: TextStyle( - color: - Theme.of(context).colorScheme.onSurface, - fontSize: 14), - ), - ), - ], - ) - ], + appBar: AppBar( + title: StreamBuilder( + stream: appbarStream.stream.distinct(), + initialData: false, + builder: (BuildContext context, AsyncSnapshot snapshot) { + return AnimatedOpacity( + opacity: snapshot.data ? 1 : 0, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 500), + child: Row( + children: [ + Obx( + () => NetworkImgLayer( + width: 35, + height: 35, + type: 'avatar', + src: _memberController.face.value, + ), ), - ); - }, - ), - actions: [ - IconButton( - onPressed: () => Get.toNamed( - '/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'), - icon: const Icon(Icons.search_outlined), - ), - PopupMenuButton( - icon: const Icon(Icons.more_vert), - itemBuilder: (BuildContext context) => [ - if (_memberController.ownerMid != _memberController.mid) ...[ - PopupMenuItem( - onTap: () => _memberController.blockUser(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.block, size: 19), - const SizedBox(width: 10), - Text(_memberController.attribute.value != 128 - ? '加入黑名单' - : '移除黑名单'), - ], - ), - ) - ], - PopupMenuItem( - onTap: () => _memberController.shareUser(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.share_outlined, size: 19), - const SizedBox(width: 10), - Text(_memberController.ownerMid != _memberController.mid - ? '分享UP主' - : '分享我的主页'), - ], + const SizedBox(width: 10), + Obx( + () => Text( + _memberController.memberInfo.value.name ?? '', + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontSize: 14), ), ), ], ), - const SizedBox(width: 4), - ], + ); + }, + ), + actions: [ + IconButton( + onPressed: () => Get.toNamed( + '/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'), + icon: const Icon(Icons.search_outlined), ), - Expanded( - child: SingleChildScrollView( - controller: _extendNestCtr, - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom + 20, - ), - child: Column( + PopupMenuButton( + icon: const Icon(Icons.more_vert), + itemBuilder: (BuildContext context) => [ + if (_memberController.ownerMid != _memberController.mid) ...[ + PopupMenuItem( + onTap: () => _memberController.blockUser(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.block, size: 19), + const SizedBox(width: 10), + Text(_memberController.attribute.value != 128 + ? '加入黑名单' + : '移除黑名单'), + ], + ), + ) + ], + PopupMenuItem( + onTap: () => _memberController.shareUser(), + child: Row( + mainAxisSize: MainAxisSize.min, children: [ - profileWidget(), - - /// 动态链接 - Obx( - () => ListTile( - onTap: _memberController.pushDynamicsPage, - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的动态'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), - ), - - /// 视频 - Obx( - () => ListTile( - onTap: _memberController.pushArchivesPage, - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的投稿'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), - ), - - /// 他的收藏夹 - Obx( - () => ListTile( - onTap: _memberController.pushfavPage, - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的收藏'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), - ), - - /// 专栏 - Obx( - () => ListTile( - onTap: _memberController.pushArticlePage, - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的专栏'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), - ), - - /// 合集 - Obx( - () => ListTile( - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的合集')), - ), - MediaQuery.removePadding( - removeTop: true, - removeBottom: true, - context: context, - child: FutureBuilder( - future: _memberSeasonsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data['status']) { - Map data = snapshot.data as Map; - if (data['data'].seasonsList.isEmpty) { - return commenWidget('用户没有设置合集'); - } else { - return MemberSeasonsPanel(data: data['data']); - } - } else { - // 请求错误 - return const SizedBox(); - } - } else { - return const SizedBox(); - } - }, - ), - ), - - /// 追番 - /// 最近投币 - Obx( - () => _memberController.recentCoinsList.isNotEmpty - ? const ListTile(title: Text('最近投币的视频')) - : const SizedBox(), - ), - MediaQuery.removePadding( - removeTop: true, - removeBottom: true, - context: context, - child: Padding( - padding: const EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - ), - child: FutureBuilder( - future: _memberCoinsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data['status']) { - Map data = snapshot.data as Map; - return MemberCoinsPanel(data: data['data']); - } else { - // 请求错误 - return const SizedBox(); - } - } else { - return const SizedBox(); - } - }, - ), - ), - ), - - /// 最近点赞 - Obx( - () => _memberController.recentLikeList.isNotEmpty - ? const ListTile(title: Text('最近点赞的视频')) - : const SizedBox(), - ), - MediaQuery.removePadding( - removeTop: true, - removeBottom: true, - context: context, - child: Padding( - padding: const EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - ), - child: FutureBuilder( - future: _memberLikeFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data['status']) { - Map data = snapshot.data as Map; - return MemberLikePanel(data: data['data']); - } else { - // 请求错误 - return const SizedBox(); - } - } else { - return const SizedBox(); - } - }, - ), - ), - ), + const Icon(Icons.share_outlined, size: 19), + const SizedBox(width: 10), + Text(_memberController.ownerMid != _memberController.mid + ? '分享UP主' + : '分享我的主页'), ], ), ), + ], + ), + const SizedBox(width: 4), + ], + ), + primary: true, + body: ListView( + controller: _extendNestCtr, + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom + 20, + ), + children: [ + profileWidget(), + + /// 动态链接 + Obx( + () => ListTile( + onTap: _memberController.pushDynamicsPage, + title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的动态'), + trailing: const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), + + /// 视频 + Obx( + () => ListTile( + onTap: _memberController.pushArchivesPage, + title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的投稿'), + trailing: const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), + + /// 他的收藏夹 + Obx( + () => ListTile( + onTap: _memberController.pushfavPage, + title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的收藏'), + trailing: const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), + + /// 专栏 + Obx( + () => ListTile( + onTap: _memberController.pushArticlePage, + title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的专栏'), + trailing: const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), + + /// 合集 + Obx( + () => ListTile( + title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的合集'), + ), + ), + MediaQuery.removePadding( + removeTop: true, + removeBottom: true, + context: context, + child: FutureBuilder( + future: _memberSeasonsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + Map data = snapshot.data as Map; + if (data['data'].seasonsList.isEmpty) { + return const CommenWidget(msg: '用户没有设置合集'); + } else { + return MemberSeasonsPanel(data: data['data']); + } + } else { + // 请求错误 + return const SizedBox(); + } + } else { + return const SizedBox(); + } + }, + ), + ), + + /// 追番 + /// 最近投币 + Obx( + () => _memberController.recentCoinsList.isNotEmpty + ? const ListTile(title: Text('最近投币的视频')) + : const SizedBox(), + ), + MediaQuery.removePadding( + removeTop: true, + removeBottom: true, + context: context, + child: Padding( + padding: const EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + ), + child: FutureBuilder( + future: _memberCoinsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + Map data = snapshot.data as Map; + return MemberCoinsPanel(data: data['data']); + } else { + // 请求错误 + return const SizedBox(); + } + } else { + return const SizedBox(); + } + }, + ), + ), + ), + + /// 最近点赞 + Obx( + () => _memberController.recentLikeList.isNotEmpty + ? const ListTile(title: Text('最近点赞的视频')) + : const SizedBox(), + ), + MediaQuery.removePadding( + removeTop: true, + removeBottom: true, + context: context, + child: Padding( + padding: const EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + ), + child: FutureBuilder( + future: _memberLikeFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + Map data = snapshot.data as Map; + return MemberLikePanel(data: data['data']); + } else { + // 请求错误 + return const SizedBox(); + } + } else { + return const SizedBox(); + } + }, + ), ), ), ], @@ -334,115 +308,90 @@ class _MemberPageState extends State if (snapshot.connectionState == ConnectionState.done) { Map? data = snapshot.data; if (data != null && data['status']) { + Rx memberInfo = _memberController.memberInfo; return Obx( - () => Stack( - alignment: AlignmentDirectional.center, + () => Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, + ProfilePanel(ctr: _memberController), + const SizedBox(height: 20), + Row( children: [ - ProfilePanel(ctr: _memberController), - const SizedBox(height: 20), - Row( - children: [ - Flexible( - child: Text( - _memberController.memberInfo.value.name!, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith( - fontWeight: FontWeight.bold, - color: _memberController.memberInfo.value - .vip!.nicknameColor != - null - ? Color(_memberController.memberInfo - .value.vip!.nicknameColor!) - : null), - )), - const SizedBox(width: 2), - if (_memberController.memberInfo.value.sex == '女') - const Icon( - FontAwesomeIcons.venus, - size: 14, - color: Colors.pink, - ), - if (_memberController.memberInfo.value.sex == '男') - const Icon( - FontAwesomeIcons.mars, - size: 14, - color: Colors.blue, - ), - const SizedBox(width: 4), - Image.asset( - 'assets/images/lv/lv${_memberController.memberInfo.value.level}.png', - height: 11, - ), - const SizedBox(width: 6), - if (_memberController - .memberInfo.value.vip!.status == - 1 && - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans'] != - '') ...[ - Image.network( - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans'], - height: 20, - ), - ] else if (_memberController - .memberInfo.value.vip!.status == - 1 && - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans_static'] != - '') ...[ - Image.network( - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans_static'], - height: 20, - ), - ] - ], + Flexible( + child: Text( + memberInfo.value.name!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + fontWeight: FontWeight.bold, + color: memberInfo.value.vip!.nicknameColor != + null + ? Color(_memberController + .memberInfo.value.vip!.nicknameColor!) + : null), + )), + const SizedBox(width: 2), + if (memberInfo.value.sex == '女') + const Icon( + FontAwesomeIcons.venus, + size: 14, + color: Colors.pink, + ), + if (memberInfo.value.sex == '男') + const Icon( + FontAwesomeIcons.mars, + size: 14, + color: Colors.blue, + ), + const SizedBox(width: 4), + Image.asset( + 'assets/images/lv/lv${memberInfo.value.level}.png', + height: 11, ), - if (_memberController - .memberInfo.value.official!['title'] != - '') ...[ - const SizedBox(height: 6), - Text.rich( - maxLines: 2, - TextSpan( - text: _memberController - .memberInfo.value.official!['role'] == - 1 - ? '个人认证:' - : '企业认证:', - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - ), - children: [ - TextSpan( - text: _memberController - .memberInfo.value.official!['title'], - ), - ], - ), - softWrap: true, + const SizedBox(width: 6), + if (memberInfo.value.vip!.status == 1 && + memberInfo + .value.vip!.label!['img_label_uri_hans'] != + '') ...[ + Image.network( + memberInfo.value.vip!.label!['img_label_uri_hans'], + height: 20, ), - ], - const SizedBox(height: 6), - if (_memberController.memberInfo.value.sign != '') - SelectableText( - _memberController.memberInfo.value.sign!, + ] else if (memberInfo.value.vip!.status == 1 && + memberInfo.value.vip! + .label!['img_label_uri_hans_static'] != + '') ...[ + Image.network( + memberInfo + .value.vip!.label!['img_label_uri_hans_static'], + height: 20, ), + ] ], ), + if (memberInfo.value.official!['title'] != '') ...[ + const SizedBox(height: 6), + Text( + memberInfo.value.official!['role'] == 1 + ? '个人认证:${memberInfo.value.official!['title']}' + : '企业认证:${memberInfo.value.official!['title']}', + maxLines: 2, + softWrap: true, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + const SizedBox(height: 6), + SelectableText(memberInfo.value.sign ?? ''), ], ), ); } else { - return const SizedBox(); + return ProfilePanel(ctr: _memberController, loadingStatus: true); } } else { // 骨架屏 @@ -452,22 +401,4 @@ class _MemberPageState extends State ), ); } - - Widget commenWidget(msg) { - return Padding( - padding: const EdgeInsets.only( - top: 20, - bottom: 30, - ), - child: Center( - child: Text( - msg, - style: Theme.of(context) - .textTheme - .labelMedium! - .copyWith(color: Theme.of(context).colorScheme.outline), - ), - ), - ); - } } diff --git a/lib/pages/member/widgets/commen_widget.dart b/lib/pages/member/widgets/commen_widget.dart new file mode 100644 index 00000000..0c92803e --- /dev/null +++ b/lib/pages/member/widgets/commen_widget.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +class CommenWidget extends StatelessWidget { + final String msg; + + const CommenWidget({required this.msg, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0), + child: Center( + child: Text( + msg, + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .labelMedium! + .copyWith(color: Theme.of(context).colorScheme.outline), + ), + ), + ); + } +} diff --git a/lib/pages/member/widgets/profile.dart b/lib/pages/member/widgets/profile.dart index 8c6385db..7001c886 100644 --- a/lib/pages/member/widgets/profile.dart +++ b/lib/pages/member/widgets/profile.dart @@ -18,253 +18,245 @@ class ProfilePanel extends StatelessWidget { @override Widget build(BuildContext context) { MemberInfoModel memberInfo = ctr.memberInfo.value; - return Builder( - builder: ((context) { - return Padding( - padding: - EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20), - child: Row( - children: [ - Hero( - tag: ctr.heroTag!, - child: Stack( - children: [ - NetworkImgLayer( - width: 90, - height: 90, - type: 'avatar', - src: !loadingStatus ? memberInfo.face : ctr.face.value, - ), - if (!loadingStatus && - memberInfo.liveRoom != null && - memberInfo.liveRoom!.liveStatus == 1) - Positioned( - bottom: 0, - left: 14, - child: GestureDetector( - onTap: () { - LiveItemModel liveItem = LiveItemModel.fromJson({ - 'title': memberInfo.liveRoom!.title, - 'uname': memberInfo.name, - 'face': memberInfo.face, - 'roomid': memberInfo.liveRoom!.roomId, - 'watched_show': memberInfo.liveRoom!.watchedShow, - }); - Get.toNamed( - '/liveRoom?roomid=${memberInfo.liveRoom!.roomId}', - arguments: {'liveItem': liveItem}, - ); - }, - child: Container( - padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: - const BorderRadius.all(Radius.circular(10)), - ), - child: Row(children: [ - Image.asset( - 'assets/images/live.gif', - height: 10, - ), - Text( - ' 直播中', - style: TextStyle( - color: Colors.white, - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize), - ) - ]), - ), - ), - ) - ], + final int? mid = memberInfo.mid; + final String? name = memberInfo.name; + + Map buildStatItem({ + required String label, + required String value, + required VoidCallback onTap, + }) { + return { + 'label': label, + 'value': value, + 'fn': onTap, + }; + } + + final List> statList = [ + buildStatItem( + label: '关注', + value: !loadingStatus ? "${ctr.userStat!['following']}" : '-', + onTap: () { + Get.toNamed('/follow?mid=$mid&name=$name'); + }, + ), + buildStatItem( + label: '粉丝', + value: !loadingStatus + ? ctr.userStat!['follower'] != null + ? Utils.numFormat(ctr.userStat!['follower']) + : '-' + : '-', + onTap: () { + Get.toNamed('/fan?mid=$mid&name=$name'); + }, + ), + buildStatItem( + label: '获赞', + value: !loadingStatus + ? ctr.userStat!['likes'] != null + ? Utils.numFormat(ctr.userStat!['likes']) + : '-' + : '-', + onTap: () {}, + ), + ]; + + return Padding( + padding: const EdgeInsets.only(top: 30, left: 4), + child: Row( + children: [ + Hero( + tag: ctr.heroTag!, + child: Stack( + children: [ + NetworkImgLayer( + width: 90, + height: 90, + type: 'avatar', + src: !loadingStatus ? memberInfo.face : ctr.face.value, ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: - const EdgeInsets.only(top: 10, left: 10, right: 10), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - InkWell( - onTap: () { - Get.toNamed( - '/follow?mid=${memberInfo.mid}&name=${memberInfo.name}'); - }, - child: Column( - children: [ - Text( - !loadingStatus - ? ctr.userStat!['following'].toString() - : '-', - style: const TextStyle( - fontWeight: FontWeight.bold), - ), - Text( - '关注', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize), - ) - ], - ), + if (!loadingStatus && + memberInfo.liveRoom != null && + memberInfo.liveRoom!.liveStatus == 1) + Positioned( + bottom: 0, + left: 14, + child: GestureDetector( + onTap: () { + LiveItemModel liveItem = LiveItemModel( + title: memberInfo.liveRoom!.title, + uname: memberInfo.name, + face: memberInfo.face, + roomId: memberInfo.liveRoom!.roomId, + watchedShow: memberInfo.liveRoom!.watchedShow, + ); + Get.toNamed( + '/liveRoom?roomid=${memberInfo.liveRoom!.roomId}', + arguments: {'liveItem': liveItem}, + ); + }, + child: Container( + padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: + const BorderRadius.all(Radius.circular(10)), + ), + child: Row(children: [ + Image.asset( + 'assets/images/live.gif', + height: 10, ), - InkWell( - onTap: () { - Get.toNamed( - '/fan?mid=${memberInfo.mid}&name=${memberInfo.name}'); - }, - child: Column( - children: [ - Text( - !loadingStatus - ? ctr.userStat!['follower'] != null - ? Utils.numFormat( - ctr.userStat!['follower'], - ) - : '-' - : '-', - style: const TextStyle( - fontWeight: FontWeight.bold)), - Text( - '粉丝', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize), - ) - ], - ), - ), - Column( - children: [ - Text( - !loadingStatus - ? ctr.userStat!['likes'] != null - ? Utils.numFormat( - ctr.userStat!['likes'], - ) - : '-' - : '-', - style: const TextStyle( - fontWeight: FontWeight.bold)), - Text( - '获赞', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize), - ) - ], - ), - ], + Text( + ' 直播中', + style: TextStyle( + color: Colors.white, + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize), + ) + ]), ), ), - const SizedBox(height: 10), - if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) ...[ - Row( - children: [ - Obx( - () => Expanded( - child: TextButton( - onPressed: () => loadingStatus - ? null - : ctr.actionRelationMod(), - style: TextButton.styleFrom( - foregroundColor: ctr.attribute.value == -1 - ? Colors.transparent - : ctr.attribute.value != 0 - ? Theme.of(context) - .colorScheme - .outline - : Theme.of(context) - .colorScheme - .onPrimary, - backgroundColor: ctr.attribute.value != 0 - ? Theme.of(context) - .colorScheme - .onInverseSurface - : Theme.of(context) - .colorScheme - .primary, // 设置按钮背景色 - ), - child: Obx(() => Text(ctr.attributeText.value)), - ), - ), - ), - const SizedBox(width: 8), - Expanded( - child: TextButton( - onPressed: () { - Get.toNamed( - '/whisperDetail', - parameters: { - 'name': memberInfo.name!, - 'face': memberInfo.face!, - 'mid': memberInfo.mid.toString(), - 'heroTag': ctr.heroTag!, - }, - ); - }, - style: TextButton.styleFrom( - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, - ), - child: const Text('发消息'), - ), - ) - ], - ) - ], - if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) ...[ - TextButton( - onPressed: () { - SmartDialog.showToast('功能开发中 💪'); - }, - style: TextButton.styleFrom( - padding: const EdgeInsets.only(left: 80, right: 80), - foregroundColor: - Theme.of(context).colorScheme.onPrimary, - backgroundColor: - Theme.of(context).colorScheme.primary, - ), - child: const Text('编辑资料'), - ) - ], - if (ctr.ownerMid == -1) ...[ - TextButton( - onPressed: () {}, - style: TextButton.styleFrom( - padding: const EdgeInsets.only(left: 80, right: 80), - foregroundColor: - Theme.of(context).colorScheme.outline, - backgroundColor: - Theme.of(context).colorScheme.onInverseSurface, - ), - child: const Text('未登录'), - ) - ] - ], - ), - ), - ], + ) + ], + ), ), - ); - }), + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: statList.map((item) { + return buildStatColumn( + context, + item['label'], + item['value'], + item['fn'], + ); + }).toList(), + ), + ), + const SizedBox(height: 16), + if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) + buildActionButtons(context, ctr, memberInfo), + if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) + buildEditProfileButton(context), + if (ctr.ownerMid == -1) buildNotLoggedInButton(context), + ], + ), + ), + ], + ), + ); + } + + Widget buildStatColumn( + BuildContext context, + String label, + String value, + VoidCallback? onTap, + ) { + return InkWell( + onTap: onTap, + child: Column( + children: [ + Text( + value, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + Text( + label, + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + ), + ), + ], + ), + ); + } + + Widget buildActionButtons( + BuildContext context, + dynamic ctr, + MemberInfoModel memberInfo, + ) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + return Row( + children: [ + const SizedBox(width: 20), + Obx( + () => Expanded( + child: TextButton( + onPressed: () => loadingStatus ? null : ctr.actionRelationMod(), + style: TextButton.styleFrom( + foregroundColor: ctr.attribute.value == -1 + ? Colors.transparent + : ctr.attribute.value != 0 + ? colorScheme.outline + : colorScheme.onPrimary, + backgroundColor: ctr.attribute.value != 0 + ? colorScheme.onInverseSurface + : colorScheme.primary, + ), + child: Obx(() => Text(ctr.attributeText.value)), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextButton( + onPressed: () { + Get.toNamed( + '/whisperDetail', + parameters: { + 'name': memberInfo.name!, + 'face': memberInfo.face!, + 'mid': memberInfo.mid.toString(), + 'heroTag': ctr.heroTag!, + }, + ); + }, + style: TextButton.styleFrom( + backgroundColor: colorScheme.onInverseSurface, + ), + child: const Text('发消息'), + ), + ), + ], + ); + } + + Widget buildEditProfileButton(BuildContext context) { + return TextButton( + onPressed: () { + SmartDialog.showToast('功能开发中 💪'); + }, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 80), + foregroundColor: Theme.of(context).colorScheme.onPrimary, + backgroundColor: Theme.of(context).colorScheme.primary, + ), + child: const Text('编辑资料'), + ); + } + + Widget buildNotLoggedInButton(BuildContext context) { + return TextButton( + onPressed: () {}, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 80), + foregroundColor: Theme.of(context).colorScheme.outline, + backgroundColor: Theme.of(context).colorScheme.onInverseSurface, + ), + child: const Text('未登录'), ); } } From b03fe0fce8c564879d660e82159adaa8cb2ccb23 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 13 Oct 2024 01:19:59 +0800 Subject: [PATCH 148/152] =?UTF-8?q?fix:=20=E6=B8=85=E9=99=A4=E7=BC=93?= =?UTF-8?q?=E5=AD=98dialog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/cache_manage.dart | 77 +++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/lib/utils/cache_manage.dart b/lib/utils/cache_manage.dart index d6bbb816..4603da9f 100644 --- a/lib/utils/cache_manage.dart +++ b/lib/utils/cache_manage.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -50,13 +51,17 @@ class CacheManage { Future getTotalSizeOfFilesInDir(final FileSystemEntity file) async { if (file is File) { int length = await file.length(); - return double.parse(length.toString()); + return length.toDouble(); } if (file is Directory) { - final List children = file.listSync(); double total = 0; - for (final FileSystemEntity child in children) { - total += await getTotalSizeOfFilesInDir(child); + try { + await for (final FileSystemEntity child in file.list()) { + total += await getTotalSizeOfFilesInDir(child); + } + } catch (e) { + // 处理错误,例如记录日志或显示错误消息 + print('读取目录时出错: $e'); } return total; } @@ -77,16 +82,17 @@ class CacheManage { // 清除缓存 Future clearCacheAll() async { - bool cleanStatus = await SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, + bool? cleanStatus = await showDialog( + context: Get.context!, builder: (BuildContext context) { return AlertDialog( title: const Text('提示'), content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'), actions: [ TextButton( - onPressed: (() => {SmartDialog.dismiss()}), + onPressed: () { + Navigator.of(context).pop(false); + }, child: Text( '取消', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -94,40 +100,45 @@ class CacheManage { ), TextButton( onPressed: () async { - SmartDialog.dismiss(); - SmartDialog.showLoading(msg: '正在清除...'); - try { - // 清除缓存 图片缓存 - await clearLibraryCache(); - SmartDialog.dismiss().then((res) { - SmartDialog.showToast('清除完成'); - }); - } catch (err) { - SmartDialog.dismiss(); - SmartDialog.showToast(err.toString()); - } + Navigator.of(context).pop(true); }, child: const Text('确认'), - ) + ), ], ); }, - ).then((res) { - return true; - }); - return cleanStatus; + ); + if (cleanStatus != null && cleanStatus) { + SmartDialog.showLoading(msg: '正在清除...'); + try { + // 清除缓存 图片缓存 + await clearLibraryCache(); + SmartDialog.dismiss().then((res) { + SmartDialog.showToast('清除完成'); + }); + } catch (err) { + SmartDialog.dismiss(); + SmartDialog.showToast(err.toString()); + } + } + return cleanStatus!; } /// 清除 Documents 目录下的 DioCache.db - Future clearApplicationCache() async { - Directory directory = await getApplicationDocumentsDirectory(); - if (directory.existsSync()) { - String dioCacheFileName = - '${directory.path}${Platform.pathSeparator}DioCache.db'; - var dioCacheFile = File(dioCacheFileName); - if (dioCacheFile.existsSync()) { - dioCacheFile.delete(); + Future clearApplicationCache() async { + try { + Directory directory = await getApplicationDocumentsDirectory(); + if (directory.existsSync()) { + String dioCacheFileName = + '${directory.path}${Platform.pathSeparator}DioCache.db'; + File dioCacheFile = File(dioCacheFileName); + if (await dioCacheFile.exists()) { + await dioCacheFile.delete(); + } } + } catch (e) { + // 处理错误,例如记录日志或显示错误消息 + print('清除缓存时出错: $e'); } } From 326827624c2c30da8942dc79db54ba13e471605f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 13 Oct 2024 01:27:07 +0800 Subject: [PATCH 149/152] =?UTF-8?q?fix:=20=E9=A6=96=E9=A1=B5=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E9=A1=B6=E9=83=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/home/controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/home/controller.dart b/lib/pages/home/controller.dart index f197fdfa..ea6e72ba 100644 --- a/lib/pages/home/controller.dart +++ b/lib/pages/home/controller.dart @@ -8,7 +8,7 @@ import 'package:pilipala/utils/storage.dart'; import '../../http/index.dart'; class HomeController extends GetxController with GetTickerProviderStateMixin { - bool flag = false; + bool flag = true; late RxList tabs = [].obs; RxInt initialIndex = 1.obs; late TabController tabController; From 430795017763f77444459b039c016bc0506fdfeb Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 13 Oct 2024 15:48:27 +0800 Subject: [PATCH 150/152] =?UTF-8?q?fix:=20=E8=BF=94=E5=9B=9E=E4=B8=8A?= =?UTF-8?q?=E4=B8=80=E9=A1=B5=E6=92=AD=E6=94=BE=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/controller.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index a57ec1de..3fead9c7 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -675,7 +675,6 @@ class VideoDetailController extends GetxController @override void onClose() { super.onClose(); - plPlayerController.dispose(); tabCtr.removeListener(() { onTabChanged(); }); From 3a2d9def650e54e0fa77e95220856a27313dcf84 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 13 Oct 2024 16:41:53 +0800 Subject: [PATCH 151/152] =?UTF-8?q?feat:=20=E5=85=A8=E5=B1=8F=E6=89=8B?= =?UTF-8?q?=E5=8A=BF=E5=8F=AF=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/common/gesture_mode.dart | 7 +++++-- lib/pages/setting/pages/play_gesture_set.dart | 4 +++- lib/plugin/pl_player/view.dart | 14 +++++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/models/common/gesture_mode.dart b/lib/models/common/gesture_mode.dart index 1149ae12..bf51fd96 100644 --- a/lib/models/common/gesture_mode.dart +++ b/lib/models/common/gesture_mode.dart @@ -4,9 +4,12 @@ enum FullScreenGestureMode { /// 从下滑到上 fromBottomtoTop, + + /// 关闭手势 + none, } extension FullScreenGestureModeExtension on FullScreenGestureMode { - String get values => ['fromToptoBottom', 'fromBottomtoTop'][index]; - String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏'][index]; + String get values => ['fromToptoBottom', 'fromBottomtoTop', 'none'][index]; + String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏', '关闭手势'][index]; } diff --git a/lib/pages/setting/pages/play_gesture_set.dart b/lib/pages/setting/pages/play_gesture_set.dart index e671bfb2..9659e58a 100644 --- a/lib/pages/setting/pages/play_gesture_set.dart +++ b/lib/pages/setting/pages/play_gesture_set.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/utils/global_data_cache.dart'; @@ -22,7 +23,7 @@ class _PlayGesturePageState extends State { void initState() { super.initState(); fullScreenGestureMode = setting.get(SettingBoxKey.fullScreenGestureMode, - defaultValue: FullScreenGestureMode.values.last.index); + defaultValue: FullScreenGestureMode.fromBottomtoTop.index); } @override @@ -71,6 +72,7 @@ class _PlayGesturePageState extends State { GlobalDataCache().fullScreenGestureMode.index; setting.put( SettingBoxKey.fullScreenGestureMode, fullScreenGestureMode); + SmartDialog.showToast('设置成功'); setState(() {}); } }, diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index dd5b617e..b69a3d16 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -204,6 +204,14 @@ class _PLVideoPlayerState extends State widget.controller.brightness.value = value; } + bool isUsingFullScreenGestures(double tapPosition, double sectionWidth) { + if (fullScreenGestureMode == FullScreenGestureMode.none) { + return false; + } else { + return tapPosition < sectionWidth * 2; + } + } + @override void dispose() { animationController.dispose(); @@ -660,12 +668,12 @@ class _PLVideoPlayerState extends State _brightnessValue.value - delta / level; final double result = brightness.clamp(0.0, 1.0); setBrightness(result); - } else if (tapPosition < sectionWidth * 2) { + } else if (isUsingFullScreenGestures(tapPosition, sectionWidth)) { // 全屏 final double dy = details.delta.dy; const double threshold = 7.0; // 滑动阈值 - final bool flag = - fullScreenGestureMode != FullScreenGestureMode.values.last; + final bool flag = fullScreenGestureMode != + FullScreenGestureMode.fromBottomtoTop; if (dy > _distance.value && dy > threshold && !_.controlsLock.value) { From 51ff684aafb7a08d297026c2143c5a7a6bfd3f63 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 13 Oct 2024 17:19:39 +0800 Subject: [PATCH 152/152] =?UTF-8?q?fix:=20=E5=AF=BC=E8=88=AA=E6=A0=8F?= =?UTF-8?q?=E6=B2=89=E6=B5=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 1 + lib/pages/main/view.dart | 9 +++++++++ lib/pages/video/detail/view.dart | 13 ++++++++----- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index fe93da22..3ac97b25 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -60,6 +60,7 @@ void main() async { systemNavigationBarColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent, statusBarColor: Colors.transparent, + systemNavigationBarContrastEnforced: false, )); } diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index f829b0c1..e978eaaa 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/models/common/dynamic_badge_mode.dart'; @@ -127,6 +128,14 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { MediaQuery.sizeOf(context).width * 9 / 16; localCache.put('sheetHeight', sheetHeight); localCache.put('statusBarHeight', statusBarHeight); + + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + systemNavigationBarColor: Colors.transparent, + systemNavigationBarIconBrightness: + Get.isDarkMode ? Brightness.light : Brightness.dark, + ), + ); return PopScope( canPop: false, onPopInvoked: (bool didPop) async { diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index d3afdf1d..7fe76745 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -511,6 +511,14 @@ class _VideoDetailPageState extends State 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)); } @@ -605,11 +613,6 @@ class _VideoDetailPageState extends State backgroundColor: Colors.black, elevation: 0, scrolledUnderElevation: 0, - systemOverlayStyle: Get.isDarkMode - ? SystemUiOverlayStyle.light - : snapshot.data!.toDouble() > kToolbarHeight - ? SystemUiOverlayStyle.dark - : SystemUiOverlayStyle.light, ); }), ),