From ee368d348d00148edf7c5557f6171b044bc3d353 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 27 Feb 2024 22:50:02 +0800 Subject: [PATCH 01/19] =?UTF-8?q?feat:=20=E5=AD=97=E5=B9=95=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 3 ++ lib/http/video.dart | 16 ++++++ lib/models/video/subTitile/content.dart | 20 +++++++ lib/models/video/subTitile/result.dart | 70 +++++++++++++++++++++++++ lib/pages/video/detail/controller.dart | 36 +++++++++++++ lib/pages/video/detail/view.dart | 5 +- lib/plugin/pl_player/controller.dart | 21 +++++++- lib/plugin/pl_player/view.dart | 37 ++++++++++++- 8 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 lib/models/video/subTitile/content.dart create mode 100644 lib/models/video/subTitile/result.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 2e758439..559c6fdb 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -483,4 +483,7 @@ class Api { /// 激活buvid3 static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi'; + + /// 获取字幕配置 + static const getSubtitleConfig = '/x/player/v2'; } diff --git a/lib/http/video.dart b/lib/http/video.dart index 30df62c3..a852e74b 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -8,6 +8,7 @@ import '../models/model_rec_video_item.dart'; import '../models/user/fav_folder.dart'; import '../models/video/ai.dart'; import '../models/video/play/url.dart'; +import '../models/video/subTitile/result.dart'; import '../models/video_detail_res.dart'; import '../utils/recommend_filter.dart'; import '../utils/storage.dart'; @@ -475,4 +476,19 @@ class VideoHttp { return {'status': false, 'data': []}; } } + + static Future getSubtitle({int? cid, String? bvid}) async { + var res = await Request().get(Api.getSubtitleConfig, data: { + 'cid': cid, + 'bvid': bvid, + }); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': SubTitlteModel.fromJson(res.data['data']), + }; + } else { + return {'status': false, 'data': [], 'msg': res.data['msg']}; + } + } } diff --git a/lib/models/video/subTitile/content.dart b/lib/models/video/subTitile/content.dart new file mode 100644 index 00000000..b18098a4 --- /dev/null +++ b/lib/models/video/subTitile/content.dart @@ -0,0 +1,20 @@ +class SubTitileContentModel { + double? from; + double? to; + int? location; + String? content; + + SubTitileContentModel({ + this.from, + this.to, + this.location, + this.content, + }); + + SubTitileContentModel.fromJson(Map json) { + from = json['from']; + to = json['to']; + location = json['location']; + content = json['content']; + } +} diff --git a/lib/models/video/subTitile/result.dart b/lib/models/video/subTitile/result.dart new file mode 100644 index 00000000..389378fa --- /dev/null +++ b/lib/models/video/subTitile/result.dart @@ -0,0 +1,70 @@ +class SubTitlteModel { + SubTitlteModel({ + this.aid, + this.bvid, + this.cid, + this.loginMid, + this.loginMidHash, + this.isOwner, + this.name, + this.subtitles, + }); + + int? aid; + String? bvid; + int? cid; + int? loginMid; + String? loginMidHash; + bool? isOwner; + String? name; + List? subtitles; + + factory SubTitlteModel.fromJson(Map json) => SubTitlteModel( + aid: json["aid"], + bvid: json["bvid"], + cid: json["cid"], + loginMid: json["login_mid"], + loginMidHash: json["login_mid_hash"], + isOwner: json["is_owner"], + name: json["name"], + subtitles: json["subtitle"] != null + ? json["subtitle"]["subtitles"] + .map((x) => SubTitlteItemModel.fromJson(x)) + .toList() + : [], + ); +} + +class SubTitlteItemModel { + SubTitlteItemModel({ + this.id, + this.lan, + this.lanDoc, + this.isLock, + this.subtitleUrl, + this.type, + this.aiType, + this.aiStatus, + }); + + int? id; + String? lan; + String? lanDoc; + bool? isLock; + String? subtitleUrl; + int? type; + int? aiType; + int? aiStatus; + + factory SubTitlteItemModel.fromJson(Map json) => + SubTitlteItemModel( + id: json["id"], + lan: json["lan"], + lanDoc: json["lan_doc"], + isLock: json["is_lock"], + subtitleUrl: json["subtitle_url"], + type: json["type"], + aiType: json["ai_type"], + aiStatus: json["ai_status"], + ); +} diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 7465c6f2..198da362 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -19,6 +19,8 @@ import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/video_utils.dart'; import 'package:screen_brightness/screen_brightness.dart'; +import '../../../http/index.dart'; +import '../../../models/video/subTitile/content.dart'; import '../../../utils/id_utils.dart'; import 'widgets/header_control.dart'; @@ -91,6 +93,8 @@ class VideoDetailController extends GetxController late int cacheAudioQa; PersistentBottomSheetController? replyReplyBottomSheetCtr; + RxList subtitleContents = + [].obs; @override void onInit() { @@ -139,6 +143,7 @@ class VideoDetailController extends GetxController cacheAudioQa = setting.get(SettingBoxKey.defaultAudioQa, defaultValue: AudioQuality.hiRes.code); oid.value = IdUtils.bv2av(Get.parameters['bvid']!); + getSubtitle(); } showReplyReplyPanel() { @@ -381,4 +386,35 @@ class VideoDetailController extends GetxController ? replyReplyBottomSheetCtr!.close() : print('replyReplyBottomSheetCtr is null'); } + + // 获取字幕配置 + Future getSubtitle() async { + var result = await VideoHttp.getSubtitle(bvid: bvid, cid: cid.value); + if (result['status']) { + if (result['data'].subtitles.isNotEmpty) { + SmartDialog.showToast('字幕加载中...'); + var subtitle = result['data'].subtitles.first; + getSubtitleContent(subtitle.subtitleUrl); + } + return result['data']; + } else { + SmartDialog.showToast(result['msg'].toString()); + } + } + + // 获取字幕内容 + 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 = ''; + if (subtitleContents.isNotEmpty) { + plPlayerController.subtitleContents = subtitleContents; + } + } } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index febca105..449c377e 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -236,7 +236,10 @@ class _VideoDetailPageState extends State void didPopNext() async { if (plPlayerController != null && plPlayerController!.videoPlayerController != null) { - setState(() => isShowing = true); + setState(() { + videoDetailController.setSubtitleContent(); + isShowing = true; + }); } videoDetailController.isFirstTime = false; final bool autoplay = autoPlayEnable; diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index dfa580ab..081b9ad9 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -21,6 +21,7 @@ import 'package:pilipala/utils/storage.dart'; import 'package:screen_brightness/screen_brightness.dart'; import 'package:status_bar_control/status_bar_control.dart'; import 'package:universal_platform/universal_platform.dart'; +import '../../models/video/subTitile/content.dart'; // import 'package:wakelock_plus/wakelock_plus.dart'; Box videoStorage = GStrorage.video; @@ -231,6 +232,10 @@ class PlPlayerController { // 播放顺序相关 PlayRepeat playRepeat = PlayRepeat.pause; + RxList subtitleContents = + [].obs; + RxString subtitleContent = ''.obs; + void updateSliderPositionSecond() { int newSecond = _sliderPosition.value.inSeconds; if (sliderPositionSeconds.value != newSecond) { @@ -277,8 +282,7 @@ class PlPlayerController { danmakuDurationVal = localCache.get(LocalCacheKey.danmakuDuration, defaultValue: 4.0); // 描边粗细 - strokeWidth = - localCache.get(LocalCacheKey.strokeWidth, defaultValue: 1.5); + strokeWidth = localCache.get(LocalCacheKey.strokeWidth, defaultValue: 1.5); playRepeat = PlayRepeat.values.toList().firstWhere( (e) => e.value == @@ -566,6 +570,8 @@ class PlPlayerController { _sliderPosition.value = event; updateSliderPositionSecond(); } + querySubtitleContent( + videoPlayerController!.state.position.inSeconds.toDouble()); /// 触发回调事件 for (var element in _positionListeners) { @@ -1050,6 +1056,17 @@ class PlPlayerController { } } + void querySubtitleContent(double progress) { + if (subtitleContents.isNotEmpty) { + for (var content in subtitleContents) { + if (progress >= content.from! && progress <= content.to!) { + subtitleContent.value = content.content!; + return; + } + } + } + } + setPlayRepeat(PlayRepeat type) { playRepeat = type; videoStorage.put(VideoBoxKey.playRepeat, type.value); diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index d073945b..e7a8ce73 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -16,7 +16,6 @@ import 'package:pilipala/plugin/pl_player/utils.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:screen_brightness/screen_brightness.dart'; - import 'models/bottom_progress_behavior.dart'; import 'widgets/app_bar_ani.dart'; import 'widgets/backward_seek.dart'; @@ -428,6 +427,42 @@ class _PLVideoPlayerState extends State if (widget.danmuWidget != null) Positioned.fill(top: 4, child: widget.danmuWidget!), + widget.controller.subscriptions.isNotEmpty + ? Stack( + children: [ + Positioned( + left: 0, + right: 0, + bottom: 30, + child: Align( + alignment: Alignment.center, + child: Obx( + () => Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: widget.controller.subtitleContent.value != '' + ? Colors.black.withOpacity(0.4) + : Colors.transparent, + ), + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + child: Text( + widget.controller.subtitleContent.value, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + ), + ), + ), + ], + ) + : nil, + /// 手势 Positioned.fill( left: 16, From 64292d523f26bd679447d1d34f5c3c51087c8760 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 18 Mar 2024 22:13:15 +0800 Subject: [PATCH 02/19] fix: player fit rebuild --- lib/plugin/pl_player/view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 0b2e652e..02f442d4 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -346,6 +346,7 @@ class _PLVideoPlayerState extends State children: [ Obx( () => Video( + key: ValueKey(_.videoFit.value), controller: videoController, controls: NoVideoControls, pauseUponEnteringBackgroundMode: !enableBackgroundPlay, From 79fcd017ab16e182cee8a52ebdf05f68b6370958 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 20 Mar 2024 00:07:00 +0800 Subject: [PATCH 03/19] =?UTF-8?q?mod:=20tabbar=E6=A0=B7=E5=BC=8F&=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=AF=84=E8=AE=BA=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/detail/introduction/controller.dart | 7 +- lib/pages/video/detail/view.dart | 124 ++++++++++-------- 2 files changed, 73 insertions(+), 58 deletions(-) diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index c7f22b13..babc18aa 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -112,10 +112,9 @@ class VideoIntroController extends GetxController { if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) { lastPlayCid.value = videoDetail.value.pages!.first.cid!; } - // Get.find(tag: heroTag).tabs.value = [ - // '简介', - // '评论 ${result['data']!.stat!.reply}' - // ]; + final VideoDetailController videoDetailCtr = + Get.find(tag: heroTag); + videoDetailCtr.tabs.value = ['简介', '评论 ${result['data']?.stat?.reply}']; // 获取到粉丝数再返回 await queryUserStat(); } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 1dff9060..af901cd2 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -319,62 +319,78 @@ class _VideoDetailPageState extends State ), ), ), - child: Row( - children: [ - const SizedBox(width: 20), - Expanded( - child: TabBar( - controller: vdCtr.tabCtr, - dividerColor: Colors.transparent, - tabs: vdCtr.tabs.map((String name) => Tab(text: name)).toList(), - ), - ), - SizedBox( - width: 220, - child: Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SizedBox( - height: 32, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => vdCtr.showShootDanmakuSheet(), - child: const Text('发弹幕', style: TextStyle(fontSize: 12)), - ), - ), - const SizedBox(width: 4), - SizedBox( - width: 34, - height: 32, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () { - plPlayerController?.isOpenDanmu.value = - !(plPlayerController?.isOpenDanmu.value ?? false); - }, - child: Obx(() => Text( - '弹', - style: TextStyle( - fontSize: 12, - color: (plPlayerController?.isOpenDanmu.value ?? - false) - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.outline, - ), - )), - ), - ), - const SizedBox(width: 14), - ], + child: Material( + child: Row( + children: [ + Flexible( + flex: 1, + child: Obx( + () => TabBar( + padding: EdgeInsets.zero, + controller: vdCtr.tabCtr, + labelStyle: const TextStyle(fontSize: 13), + labelPadding: + const EdgeInsets.symmetric(horizontal: 10.0), // 设置每个标签的宽度 + dividerColor: Colors.transparent, + tabs: vdCtr.tabs + .map( + (String name) => Flexible( + child: Tab(text: name), + ), + ) + .toList(), + ), ), ), - ), - ], + Flexible( + flex: 1, + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 32, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => vdCtr.showShootDanmakuSheet(), + child: + const Text('发弹幕', style: TextStyle(fontSize: 12)), + ), + ), + const SizedBox(width: 4), + SizedBox( + width: 34, + height: 32, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () { + plPlayerController?.isOpenDanmu.value = + !(plPlayerController?.isOpenDanmu.value ?? + false); + }, + child: Obx(() => Text( + '弹', + style: TextStyle( + fontSize: 12, + color: (plPlayerController + ?.isOpenDanmu.value ?? + false) + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + ), + )), + ), + ), + const SizedBox(width: 14), + ], + ), + )), + ], + ), ), ); From 710361caeac2f3aa155fad8d8a2633890d01be10 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 20 Mar 2024 23:21:41 +0800 Subject: [PATCH 04/19] =?UTF-8?q?fix:=20=E5=8E=86=E5=8F=B2=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E7=9B=B8=E5=85=B3=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/interceptor.dart | 12 ++++++++---- lib/pages/history/controller.dart | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart index 362ff17a..fbc60ddf 100644 --- a/lib/http/interceptor.dart +++ b/lib/http/interceptor.dart @@ -45,10 +45,14 @@ class ApiInterceptor extends Interceptor { void onError(DioException err, ErrorInterceptorHandler handler) async { // 处理网络请求错误 // handler.next(err); - SmartDialog.showToast( - await dioError(err), - displayType: SmartToastType.onlyRefresh, - ); + String url = err.requestOptions.uri.toString(); + print('🌹🌹ApiInterceptor: $url'); + if (!url.contains('heartBeat')) { + SmartDialog.showToast( + await dioError(err), + displayType: SmartToastType.onlyRefresh, + ); + } super.onError(err, handler); } diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index e7822cd9..a1f18113 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -88,8 +88,10 @@ class HistoryController extends GetxController { // 观看历史暂停状态 Future historyStatus() async { var res = await UserHttp.historyStatus(); - pauseStatus.value = res.data['data']; - localCache.put(LocalCacheKey.historyPause, res.data['data']); + if (res.data['code'] == 0) { + pauseStatus.value = res.data['data']; + localCache.put(LocalCacheKey.historyPause, res.data['data']); + } } // 清空观看历史 From 6d276fce4c01e6a24d7abcd8313a0b653916c777 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 21 Mar 2024 00:03:20 +0800 Subject: [PATCH 05/19] =?UTF-8?q?fix:=20=E5=AA=92=E4=BD=93=E5=BA=93?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E6=BB=91=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/media/view.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index c3bad4b9..460c5648 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; +import 'package:media_kit/media_kit.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/user/fav_folder.dart'; import 'package:pilipala/pages/main/index.dart'; @@ -102,7 +103,11 @@ class _MediaPageState extends State ], Obx(() => mediaController.userLogin.value ? favFolder(mediaController, context) - : const SizedBox()) + : const SizedBox()), + SizedBox( + height: MediaQuery.of(context).padding.bottom + + kBottomNavigationBarHeight, + ) ], ), ), From 4dbcd2e0ec1bfd99d305651b295f8390778ae845 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 21 Mar 2024 23:39:56 +0800 Subject: [PATCH 06/19] =?UTF-8?q?fix:=20=E5=B0=9D=E8=AF=95=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=9F=B3=E9=87=8F=E8=B0=83=E8=8A=82=E6=8A=96=E5=8A=A8?= =?UTF-8?q?=20issues=20#647=20#498=20#104=20#198?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/plugin/pl_player/view.dart | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 4b6627b8..b0933af2 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; +import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -674,13 +675,16 @@ class _PLVideoPlayerState extends State _distance.value = dy; } else { // 右边区域 👈 - final double level = (_.isFullScreen.value - ? Get.size.height - : screenWidth * 9 / 16) * - 3; - final double volume = _volumeValue.value - delta / level; - final double result = volume.clamp(0.0, 1.0); - setVolume(result); + EasyThrottle.throttle( + 'setVolume', const Duration(milliseconds: 20), () { + final double level = (_.isFullScreen.value + ? Get.size.height + : screenWidth * 9 / 16); + final double volume = _volumeValue.value - + double.parse(delta.toStringAsFixed(1)) / level; + final double result = volume.clamp(0.0, 1.0); + setVolume(result); + }); } }, onVerticalDragEnd: (DragEndDetails details) {}, From c216c9bd653524bcd6a9cc613b4fd0bc96b35630 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 20 Mar 2024 00:07:00 +0800 Subject: [PATCH 07/19] =?UTF-8?q?mod:=20tabbar=E6=A0=B7=E5=BC=8F&=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=AF=84=E8=AE=BA=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/detail/introduction/controller.dart | 7 +- lib/pages/video/detail/view.dart | 124 ++++++++++-------- 2 files changed, 73 insertions(+), 58 deletions(-) diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 6714b887..8114bdaf 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -85,10 +85,9 @@ class VideoIntroController extends GetxController { if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) { lastPlayCid.value = videoDetail.value.pages!.first.cid!; } - // Get.find(tag: heroTag).tabs.value = [ - // '简介', - // '评论 ${result['data']!.stat!.reply}' - // ]; + final VideoDetailController videoDetailCtr = + Get.find(tag: heroTag); + videoDetailCtr.tabs.value = ['简介', '评论 ${result['data']?.stat?.reply}']; // 获取到粉丝数再返回 await queryUserStat(); } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 1dff9060..af901cd2 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -319,62 +319,78 @@ class _VideoDetailPageState extends State ), ), ), - child: Row( - children: [ - const SizedBox(width: 20), - Expanded( - child: TabBar( - controller: vdCtr.tabCtr, - dividerColor: Colors.transparent, - tabs: vdCtr.tabs.map((String name) => Tab(text: name)).toList(), - ), - ), - SizedBox( - width: 220, - child: Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SizedBox( - height: 32, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => vdCtr.showShootDanmakuSheet(), - child: const Text('发弹幕', style: TextStyle(fontSize: 12)), - ), - ), - const SizedBox(width: 4), - SizedBox( - width: 34, - height: 32, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () { - plPlayerController?.isOpenDanmu.value = - !(plPlayerController?.isOpenDanmu.value ?? false); - }, - child: Obx(() => Text( - '弹', - style: TextStyle( - fontSize: 12, - color: (plPlayerController?.isOpenDanmu.value ?? - false) - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.outline, - ), - )), - ), - ), - const SizedBox(width: 14), - ], + child: Material( + child: Row( + children: [ + Flexible( + flex: 1, + child: Obx( + () => TabBar( + padding: EdgeInsets.zero, + controller: vdCtr.tabCtr, + labelStyle: const TextStyle(fontSize: 13), + labelPadding: + const EdgeInsets.symmetric(horizontal: 10.0), // 设置每个标签的宽度 + dividerColor: Colors.transparent, + tabs: vdCtr.tabs + .map( + (String name) => Flexible( + child: Tab(text: name), + ), + ) + .toList(), + ), ), ), - ), - ], + Flexible( + flex: 1, + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 32, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => vdCtr.showShootDanmakuSheet(), + child: + const Text('发弹幕', style: TextStyle(fontSize: 12)), + ), + ), + const SizedBox(width: 4), + SizedBox( + width: 34, + height: 32, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () { + plPlayerController?.isOpenDanmu.value = + !(plPlayerController?.isOpenDanmu.value ?? + false); + }, + child: Obx(() => Text( + '弹', + style: TextStyle( + fontSize: 12, + color: (plPlayerController + ?.isOpenDanmu.value ?? + false) + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + ), + )), + ), + ), + const SizedBox(width: 14), + ], + ), + )), + ], + ), ), ); From 14338dc33d508dbd06d8766310fbf197493c6332 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 23 Mar 2024 17:23:51 +0800 Subject: [PATCH 08/19] =?UTF-8?q?fix:=20=E8=AF=A6=E6=83=85=E9=A1=B5TabBar?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/video/danmu_close.svg | 1 + assets/images/video/danmu_open.svg | 1 + lib/pages/video/detail/view.dart | 47 ++++++++++++++--------------- pubspec.yaml | 1 + 4 files changed, 25 insertions(+), 25 deletions(-) create mode 100644 assets/images/video/danmu_close.svg create mode 100644 assets/images/video/danmu_open.svg diff --git a/assets/images/video/danmu_close.svg b/assets/images/video/danmu_close.svg new file mode 100644 index 00000000..9f48027b --- /dev/null +++ b/assets/images/video/danmu_close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/video/danmu_open.svg b/assets/images/video/danmu_open.svg new file mode 100644 index 00000000..24e8d7a9 --- /dev/null +++ b/assets/images/video/danmu_open.svg @@ -0,0 +1 @@ +Layer 1 \ No newline at end of file diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index af901cd2..a403e298 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -5,6 +5,7 @@ import 'dart:ui'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:floating/floating.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'; @@ -334,9 +335,7 @@ class _VideoDetailPageState extends State dividerColor: Colors.transparent, tabs: vdCtr.tabs .map( - (String name) => Flexible( - child: Tab(text: name), - ), + (String name) => Tab(text: name), ) .toList(), ), @@ -359,30 +358,28 @@ class _VideoDetailPageState extends State const Text('发弹幕', style: TextStyle(fontSize: 12)), ), ), - const SizedBox(width: 4), SizedBox( - width: 34, - height: 32, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), + width: 38, + height: 38, + child: Obx( + () => IconButton( + onPressed: () { + plPlayerController?.isOpenDanmu.value = + !(plPlayerController?.isOpenDanmu.value ?? + false); + }, + icon: (plPlayerController?.isOpenDanmu.value ?? + false) + ? SvgPicture.asset( + 'assets/images/video/danmu_close.svg', + ) + : SvgPicture.asset( + 'assets/images/video/danmu_open.svg', + // ignore: deprecated_member_use + color: + Theme.of(context).colorScheme.primary, + ), ), - onPressed: () { - plPlayerController?.isOpenDanmu.value = - !(plPlayerController?.isOpenDanmu.value ?? - false); - }, - child: Obx(() => Text( - '弹', - style: TextStyle( - fontSize: 12, - color: (plPlayerController - ?.isOpenDanmu.value ?? - false) - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.outline, - ), - )), ), ), const SizedBox(width: 14), diff --git a/pubspec.yaml b/pubspec.yaml index 5c25f044..82166403 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -191,6 +191,7 @@ flutter: - assets/images/lv/ - assets/images/logo/ - assets/images/live/ + - assets/images/video/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware From b5a46d1be02ab94811d8c37bf7a14ab49ffb31d1 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 11 Mar 2024 23:03:50 +0800 Subject: [PATCH 09/19] =?UTF-8?q?feat:=20=E6=8E=A5=E6=94=B6=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E7=BB=84=E4=BB=B6=E4=BC=A0=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pl_player/models/bottom_control_type.dart | 1 + lib/plugin/pl_player/view.dart | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/plugin/pl_player/models/bottom_control_type.dart b/lib/plugin/pl_player/models/bottom_control_type.dart index 599f6e4f..739e1d38 100644 --- a/lib/plugin/pl_player/models/bottom_control_type.dart +++ b/lib/plugin/pl_player/models/bottom_control_type.dart @@ -7,4 +7,5 @@ enum BottomControlType { fit, speed, fullscreen, + custom, } diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 5f47ca4a..90861204 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -35,6 +35,8 @@ class PLVideoPlayer extends StatefulWidget { this.bottomControl, this.danmuWidget, this.bottomList, + this.customWidget, + this.customWidgets, super.key, }); @@ -43,6 +45,10 @@ class PLVideoPlayer extends StatefulWidget { final PreferredSizeWidget? bottomControl; final Widget? danmuWidget; final List? bottomList; + // List or Widget + + final Widget? customWidget; + final List? customWidgets; @override State createState() => _PLVideoPlayerState(); @@ -311,7 +317,7 @@ class _PLVideoPlayerState extends State ), }; final List list = []; - var userSpecifyItem = widget.bottomList ?? + List userSpecifyItem = widget.bottomList ?? [ BottomControlType.playOrPause, BottomControlType.time, @@ -320,7 +326,16 @@ class _PLVideoPlayerState extends State BottomControlType.fullscreen, ]; for (var i = 0; i < userSpecifyItem.length; i++) { - list.add(videoProgressWidgets[userSpecifyItem[i]]!); + if (userSpecifyItem[i] == BottomControlType.custom) { + if (widget.customWidget != null && widget.customWidget is Widget) { + list.add(widget.customWidget!); + } + if (widget.customWidgets != null && widget.customWidgets!.isNotEmpty) { + list.addAll(widget.customWidgets!); + } + } else { + list.add(videoProgressWidgets[userSpecifyItem[i]]!); + } } return list; } From 72f1a82650b30053f807da017c6f44fa36d4ff48 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 23 Mar 2024 19:44:37 +0800 Subject: [PATCH 10/19] bump: sdk&dependencies --- lib/http/interceptor.dart | 34 ++++---- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- pubspec.lock | 84 +++++++++---------- pubspec.yaml | 26 +++--- 4 files changed, 73 insertions(+), 73 deletions(-) diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart index fbc60ddf..a5359283 100644 --- a/lib/http/interceptor.dart +++ b/lib/http/interceptor.dart @@ -46,7 +46,6 @@ class ApiInterceptor extends Interceptor { // 处理网络请求错误 // handler.next(err); String url = err.requestOptions.uri.toString(); - print('🌹🌹ApiInterceptor: $url'); if (!url.contains('heartBeat')) { SmartDialog.showToast( await dioError(err), @@ -79,23 +78,24 @@ class ApiInterceptor extends Interceptor { } static Future checkConnect() async { - final ConnectivityResult connectivityResult = + final List connectivityResult = await Connectivity().checkConnectivity(); - switch (connectivityResult) { - case ConnectivityResult.mobile: - return '正在使用移动流量'; - case ConnectivityResult.wifi: - return '正在使用wifi'; - case ConnectivityResult.ethernet: - return '正在使用局域网'; - case ConnectivityResult.vpn: - return '正在使用代理网络'; - case ConnectivityResult.other: - return '正在使用其他网络'; - case ConnectivityResult.none: - return '未连接到任何网络'; - default: - return ''; + if (connectivityResult.contains(ConnectivityResult.mobile)) { + return '正在使用移动流量'; + } else if (connectivityResult.contains(ConnectivityResult.wifi)) { + return '正在使用wifi'; + } else if (connectivityResult.contains(ConnectivityResult.ethernet)) { + return '正在使用局域网'; + } else if (connectivityResult.contains(ConnectivityResult.vpn)) { + return '正在使用代理网络'; + } else if (connectivityResult.contains(ConnectivityResult.bluetooth)) { + return '正在使用蓝牙网络'; + } else if (connectivityResult.contains(ConnectivityResult.other)) { + return '正在使用其他网络'; + } else if (connectivityResult.contains(ConnectivityResult.none)) { + return '未连接到任何网络'; + } else { + return ''; } } } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 3e5f82f7..8af2f922 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -24,7 +24,7 @@ import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) - ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 8972ef45..695505d7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: "direct main" description: name: animations - sha256: "708e4b68c23228c264b038fe7003a2f5d01ce85fc64d8cae090e86b27fcea6c5" + sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.10" + version: "2.0.11" appscheme: dependency: "direct main" description: @@ -101,10 +101,10 @@ packages: dependency: "direct main" description: name: audio_video_progress_bar - sha256: "3384875247cdbea748bd9ae8330631cd06a6cabfcda4945d45c9b406da92bc66" + sha256: ccc7d7b83d2a16c52d4a7fb332faabd1baa053fb0e4c16815aefd3945ab33b81 url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.1" + version: "2.0.2" auto_orientation: dependency: "direct main" description: @@ -157,10 +157,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b" + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.7" + version: "2.4.8" build_runner_core: dependency: transitive description: @@ -213,10 +213,10 @@ packages: dependency: "direct main" description: name: catcher_2 - sha256: ca94d45ffb52bf4b16a425cdff6734ae8443d36d5f06c276f1c2a593120b11ed + sha256: "9cf33d2befd10058374e5fc6177577fdd938d73d9c06810de81cf91311a7ce98" url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.0" + version: "1.2.3" characters: dependency: transitive description: @@ -269,18 +269,18 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "77a180d6938f78ca7d2382d2240eb626c0f6a735d0bfdce227d8ffb80f95c48b" + sha256: e9feae83b1849f61bad9f6f33ee00646e3410d54ce0821e02f262f9901dad3c9 url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.2" + version: "6.0.1" connectivity_plus_platform_interface: dependency: transitive description: name: connectivity_plus_platform_interface - sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a + sha256: b6a56efe1e6675be240de39107281d4034b64ac23438026355b4234042a35adb url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.4" + version: "2.0.0" convert: dependency: transitive description: @@ -373,10 +373,10 @@ packages: dependency: "direct main" description: name: dio - sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" + sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" url: "https://pub.flutter-io.cn" source: hosted - version: "5.4.0" + version: "5.4.1" dio_cookie_manager: dependency: "direct main" description: @@ -413,10 +413,10 @@ packages: dependency: "direct main" description: name: dynamic_color - sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f" + sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d url: "https://pub.flutter-io.cn" source: hosted - version: "1.6.8" + version: "1.7.0" easy_debounce: dependency: "direct main" description: @@ -469,10 +469,10 @@ packages: dependency: "direct main" description: name: extended_nested_scroll_view - sha256: "444a6f883e6e07effc7639e69a309e1fb491b6c19b095e9281714a51ace2b384" + sha256: "835580d40c2c62b448bd14adecd316acba469ba61f1510ef559d17668a85e777" url: "https://pub.flutter-io.cn" source: hosted - version: "6.1.2" + version: "6.2.1" fake_async: dependency: transitive description: @@ -592,10 +592,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.9" + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -1067,50 +1067,50 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "860c6b871c94c78e202dc69546d4d8fd84bd59faeb36f8fb9888668a53ff4f78" + sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44" url: "https://pub.flutter-io.cn" source: hosted - version: "11.1.0" + version: "11.3.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "2f1bec180ee2f5665c22faada971a8f024761f632e93ddc23310487df52dcfa6" + sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474" url: "https://pub.flutter-io.cn" source: hosted - version: "12.0.1" + version: "12.0.5" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "1a816084338ada8d574b1cb48390e6e8b19305d5120fe3a37c98825bacc78306" + sha256: "92861b0f0c2443dd8898398c2baa4f1ae925109b5909ae4a17d0108a6a788932" url: "https://pub.flutter-io.cn" source: hosted - version: "9.2.0" + version: "9.4.2" permission_handler_html: dependency: transitive description: name: permission_handler_html - sha256: "11b762a8c123dced6461933a88ea1edbbe036078c3f9f41b08886e678e7864df" + sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d" url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.0+2" + version: "0.1.1" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: d87349312f7eaf6ce0adaf668daf700ac5b06af84338bd8b8574dfbd93ffe1a1 + sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78" url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.2" + version: "4.2.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: "1e8640c1e39121128da6b816d236e714d2cf17fac5a105dd6acdd3403a628004" + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" url: "https://pub.flutter-io.cn" source: hosted - version: "0.2.0" + version: "0.2.1" petitparser: dependency: transitive description: @@ -1536,26 +1536,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1640,10 +1640,10 @@ packages: dependency: "direct main" description: name: webview_flutter - sha256: "42393b4492e629aa3a88618530a4a00de8bb46e50e7b3993fedbfdc5352f0dbf" + sha256: d81b68e88cc353e546afb93fb38958e3717282c5ac6e5d3be4a4aef9fc3c1413 url: "https://pub.flutter-io.cn" source: hosted - version: "4.4.2" + version: "4.5.0" webview_flutter_android: dependency: transitive description: @@ -1664,10 +1664,10 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: accdaaa49a2aca2dc3c3230907988954cdd23fed0a19525d6c9789d380f4dc76 + sha256: "4d062ad505390ecef1c4bfb6001cd857a51e00912cc9dfb66edb1886a9ebd80c" url: "https://pub.flutter-io.cn" source: hosted - version: "3.9.4" + version: "3.10.2" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 82166403..5e19b56b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.21+1021 environment: - sdk: ">=2.19.6 <3.0.0" + sdk: ">=3.0.0 <4.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -36,15 +36,15 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.5 # 动态取色 - dynamic_color: ^1.6.8 + dynamic_color: ^1.7.0 get: ^4.6.5 # 网络 - dio: ^5.3.3 + dio: ^5.4.1 cookie_jar: ^4.0.8 dio_cookie_manager: ^3.1.1 - connectivity_plus: ^4.0.1 + connectivity_plus: ^6.0.1 dio_http2_adapter: ^2.3.1+1 # 图片 @@ -58,17 +58,17 @@ dependencies: hive_flutter: ^1.1.0 # 设备信息 - device_info_plus: ^9.0.2 + device_info_plus: ^9.0.0 # 权限 - permission_handler: ^11.0.1 + permission_handler: ^11.3.0 # 分享 share_plus: ^7.0.2 # cookie 管理 webview_cookie_manager: ^2.0.6 # 浏览器 - webview_flutter: ^4.2.2 + webview_flutter: ^4.5.0 # 解决sliver滑动不同步 - extended_nested_scroll_view: ^6.1.2 + extended_nested_scroll_view: ^6.2.1 # 上拉加载 loading_more_list: ^6.0.0 # 下拉刷新 @@ -99,15 +99,15 @@ dependencies: wakelock_plus: ^1.1.1 universal_platform: ^1.0.0+1 # 进度条 - audio_video_progress_bar: ^2.0.1 + audio_video_progress_bar: ^2.0.2 auto_orientation: ^2.3.1 protobuf: ^3.0.0 - animations: ^2.0.8 + animations: ^2.0.11 # 获取appx信息 package_info_plus: ^4.1.0 url_launcher: ^6.1.14 - flutter_svg: ^2.0.7 + flutter_svg: ^2.0.10+1 # 防抖节流 easy_debounce: ^2.0.3 # 高帧率 @@ -137,7 +137,7 @@ dependencies: uuid: ^3.0.7 scrollable_positioned_list: ^0.3.8 nil: ^1.1.1 - catcher_2: ^1.1.0 + catcher_2: ^1.2.3 logger: ^2.0.2+1 path: 1.8.3 # 电池优化 @@ -159,7 +159,7 @@ dev_dependencies: # url: https://github.com/nvi9/flutter_launcher_icons.git # ref: e045d40 hive_generator: ^2.0.0 - build_runner: ^2.3.3 + build_runner: ^2.4.8 flutter_launcher_icons: android: true From 8ff387d54a2b7e31fb1150a7030b61c9f97a3ecd Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Mar 2024 11:30:50 +0800 Subject: [PATCH 11/19] =?UTF-8?q?mod:=20=E8=AF=84=E8=AE=BA=E5=A4=B4?= =?UTF-8?q?=E9=83=A8=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/reply/view.dart | 45 ++++++++------------------ 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index b07a6168..38203f7e 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -148,35 +148,14 @@ class _VideoReplyPanelState extends State floating: true, delegate: _MySliverPersistentHeaderDelegate( child: Container( - height: 45, - padding: const EdgeInsets.fromLTRB(12, 0, 6, 0), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - border: Border( - bottom: BorderSide( - color: Theme.of(context) - .colorScheme - .outline - .withOpacity(0.1)), - ), - ), + height: 40, + padding: const EdgeInsets.fromLTRB(12, 6, 6, 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Obx( - () => AnimatedSwitcher( - duration: const Duration(milliseconds: 400), - transitionBuilder: - (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, child: child); - }, - child: Text( - '共${_videoReplyController.count.value}条回复', - key: ValueKey( - _videoReplyController.count.value), - ), - ), + Text( + '${_videoReplyController.sortTypeLabel.value}评论', + style: const TextStyle(fontSize: 13), ), SizedBox( height: 35, @@ -184,10 +163,12 @@ class _VideoReplyPanelState extends State onPressed: () => _videoReplyController.queryBySort(), icon: const Icon(Icons.sort, size: 16), - label: Obx(() => Text( - _videoReplyController.sortTypeLabel.value, - style: const TextStyle(fontSize: 13), - )), + label: Obx( + () => Text( + _videoReplyController.sortTypeLabel.value, + style: const TextStyle(fontSize: 13), + ), + ), ), ) ], @@ -329,8 +310,8 @@ class _VideoReplyPanelState extends State class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { _MySliverPersistentHeaderDelegate({required this.child}); - final double _minExtent = 45; - final double _maxExtent = 45; + final double _minExtent = 40; + final double _maxExtent = 40; final Widget child; @override From c9327c97e5ae7f044426bbb3847c988f71046558 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Mar 2024 10:59:00 +0800 Subject: [PATCH 12/19] =?UTF-8?q?fix:=20=E8=AF=84=E8=AE=BA=E6=8A=95?= =?UTF-8?q?=E7=A5=A8message=E9=87=8D=E5=A4=8D?= 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 f9f695d4..e79b6159 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -498,7 +498,7 @@ InlineSpan buildContent( return str; }); } - // content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' '); + content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' '); content.message = content.message .replaceAll('&', '&') .replaceAll('<', '<') From 4db5a950f344af1aa5aee4b309cd626a2de2fbfc Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Mar 2024 10:48:45 +0800 Subject: [PATCH 13/19] =?UTF-8?q?mod:=20=E5=BC=B9=E5=B9=95=E5=BC=80?= =?UTF-8?q?=E5=85=B3=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index a403e298..69801beb 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -368,7 +368,7 @@ class _VideoDetailPageState extends State !(plPlayerController?.isOpenDanmu.value ?? false); }, - icon: (plPlayerController?.isOpenDanmu.value ?? + icon: !(plPlayerController?.isOpenDanmu.value ?? false) ? SvgPicture.asset( 'assets/images/video/danmu_close.svg', From 031d57e1fdf34e64a6d2f989a500d17ab63e43e2 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Mar 2024 13:42:40 +0800 Subject: [PATCH 14/19] =?UTF-8?q?opt:=20=E6=8E=92=E8=A1=8C=E6=A6=9C?= =?UTF-8?q?=E5=88=87=E6=8D=A2tab=E6=95=B0=E6=8D=AE=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/rank/controller.dart | 2 +- lib/pages/rank/zone/view.dart | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/pages/rank/controller.dart b/lib/pages/rank/controller.dart index 61475d97..da73ea02 100644 --- a/lib/pages/rank/controller.dart +++ b/lib/pages/rank/controller.dart @@ -9,7 +9,7 @@ import 'package:pilipala/utils/storage.dart'; class RankController extends GetxController with GetTickerProviderStateMixin { bool flag = false; late RxList tabs = [].obs; - RxInt initialIndex = 1.obs; + RxInt initialIndex = 0.obs; late TabController tabController; late List tabsCtrList; late List tabsPageList; diff --git a/lib/pages/rank/zone/view.dart b/lib/pages/rank/zone/view.dart index 58ca187f..fbf8a524 100644 --- a/lib/pages/rank/zone/view.dart +++ b/lib/pages/rank/zone/view.dart @@ -22,15 +22,20 @@ class ZonePage extends StatefulWidget { State createState() => _ZonePageState(); } -class _ZonePageState extends State { - final ZoneController _zoneController = Get.put(ZoneController()); +class _ZonePageState extends State + with AutomaticKeepAliveClientMixin { + late ZoneController _zoneController; List videoList = []; Future? _futureBuilderFuture; late ScrollController scrollController; + @override + bool get wantKeepAlive => true; + @override void initState() { super.initState(); + _zoneController = Get.put(ZoneController(), tag: widget.rid.toString()); _futureBuilderFuture = _zoneController.queryRankFeed('init', widget.rid); scrollController = _zoneController.scrollController; StreamController mainStream = @@ -68,6 +73,7 @@ class _ZonePageState extends State { @override Widget build(BuildContext context) { + super.build(context); return RefreshIndicator( onRefresh: () async { return await _zoneController.onRefresh(); From a925ef63eb09ebaae30f5558f37999b00d443df3 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Mar 2024 16:03:18 +0800 Subject: [PATCH 15/19] =?UTF-8?q?mod:=20=E8=A7=86=E9=A2=91=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=A1=B5=E7=AE=80=E4=BB=8B=E6=9F=A5=E7=9C=8B&?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/introduction/view.dart | 33 ++--- .../introduction/widgets/intro_detail.dart | 130 ++++-------------- .../detail/widgets/expandable_section.dart | 20 +-- 3 files changed, 50 insertions(+), 133 deletions(-) diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 9c1b7db0..831491f6 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -15,6 +15,7 @@ import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; +import '../widgets/expandable_section.dart'; import 'widgets/action_item.dart'; import 'widgets/fav_panel.dart'; import 'widgets/intro_detail.dart'; @@ -137,6 +138,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { late String memberHeroTag; late bool enableAi; bool isProcessing = false; + RxBool isExpand = false.obs; void Function()? handleState(Future Function() action) { return isProcessing ? null @@ -212,13 +214,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { // 视频介绍 showIntroDetail() { feedBack(); - showBottomSheet( - context: context, - enableDrag: true, - builder: (BuildContext context) { - return IntroDetail(videoDetail: widget.videoDetail!); - }, - ); + isExpand.value = !(isExpand.value); } // 用户主页 @@ -330,6 +326,16 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ], ), + /// 视频简介 + Obx( + () => ExpandedSection( + expand: isExpand.value, + begin: 0, + end: 1, + child: IntroDetail(videoDetail: widget.videoDetail!), + ), + ), + /// 点赞收藏转发 actionGrid(context, videoIntroController), // 合集 @@ -438,6 +444,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { margin: const EdgeInsets.only(top: 6, bottom: 4), height: constraints.maxWidth / 5 * 0.8, child: GridView.count( + physics: const NeverScrollableScrollPhysics(), primary: false, padding: EdgeInsets.zero, crossAxisCount: 5, @@ -451,12 +458,6 @@ class _VideoInfoState extends State with TickerProviderStateMixin { selectStatus: videoIntroController.hasLike.value, text: widget.videoDetail!.stat!.like!.toString()), ), - // ActionItem( - // icon: const Icon(FontAwesomeIcons.clock), - // onTap: () => videoIntroController.actionShareVideo(), - // selectStatus: false, - // loadingStatus: loadingStatus, - // text: '稍后再看'), Obx( () => ActionItem( icon: const Icon(FontAwesomeIcons.b), @@ -477,10 +478,10 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ), ), ActionItem( - icon: const Icon(FontAwesomeIcons.comment), - onTap: () => videoDetailCtr.tabCtr.animateTo(1), + icon: const Icon(FontAwesomeIcons.clock), + onTap: () => videoIntroController.actionShareVideo(), selectStatus: false, - text: widget.videoDetail!.stat!.reply!.toString(), + text: '稍后看', ), ActionItem( icon: const Icon(FontAwesomeIcons.shareFromSquare), diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index c74e27ee..1e9bb842 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -1,16 +1,10 @@ import 'package:flutter/gestures.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:hive/hive.dart'; -import 'package:pilipala/common/widgets/stat/danmu.dart'; -import 'package:pilipala/common/widgets/stat/view.dart'; -import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; -Box localCache = GStrorage.localCache; -late double sheetHeight; - class IntroDetail extends StatelessWidget { const IntroDetail({ super.key, @@ -20,105 +14,39 @@ class IntroDetail extends StatelessWidget { @override Widget build(BuildContext context) { - sheetHeight = localCache.get('sheetHeight'); - return Container( - color: Theme.of(context).colorScheme.background, - padding: EdgeInsets.only( - left: 14, - right: 14, - bottom: MediaQuery.of(context).padding.bottom + 20), - height: sheetHeight, + return SizedBox( + width: double.infinity, + child: SelectableRegion( + focusNode: FocusNode(), + selectionControls: MaterialTextSelectionControls(), child: Column( - 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.primary, - borderRadius: - const BorderRadius.all(Radius.circular(3))), - ), - ), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: videoDetail!.bvid!)); + SmartDialog.showToast('已复制'); + }, + child: Text( + videoDetail!.bvid!, + style: TextStyle( + fontSize: 13, color: Theme.of(context).colorScheme.primary), ), ), - Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - videoDetail!.title, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 6), - Row( - children: [ - StatView( - theme: 'gray', - view: videoDetail!.stat!.view, - size: 'medium', - ), - const SizedBox(width: 10), - StatDanMu( - theme: 'gray', - danmu: videoDetail!.stat!.danmaku, - size: 'medium', - ), - const SizedBox(width: 10), - Text( - Utils.dateFormat(videoDetail!.pubdate, - formatType: 'detail'), - style: TextStyle( - fontSize: 12, - color: Theme.of(context).colorScheme.outline, - ), - ), - ], - ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - child: SelectableRegion( - focusNode: FocusNode(), - selectionControls: MaterialTextSelectionControls(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - videoDetail!.bvid!, - style: const TextStyle(fontSize: 13), - ), - const SizedBox(height: 4), - Text.rich( - style: const TextStyle( - height: 1.4, - // fontSize: 13, - ), - TextSpan( - children: [ - buildContent(context, videoDetail!), - ], - ), - ), - ], - ), - ), - ), - ], - ), + const SizedBox(height: 4), + Text.rich( + style: const TextStyle(height: 1.4), + TextSpan( + children: [ + buildContent(context, videoDetail!), + ], ), - ) + ), ], - )); + ), + ), + ); } InlineSpan buildContent(BuildContext context, content) { diff --git a/lib/pages/video/detail/widgets/expandable_section.dart b/lib/pages/video/detail/widgets/expandable_section.dart index afa68cc9..69e73e20 100644 --- a/lib/pages/video/detail/widgets/expandable_section.dart +++ b/lib/pages/video/detail/widgets/expandable_section.dart @@ -32,28 +32,14 @@ class _ExpandedSectionState extends State _runExpandCheck(); } - ///Setting up the animation - // void prepareAnimations() { - // expandController = AnimationController( - // vsync: this, duration: const Duration(milliseconds: 500)); - // animation = CurvedAnimation( - // parent: expandController, - // curve: Curves.fastOutSlowIn, - // ); - // } - void prepareAnimations() { expandController = AnimationController( vsync: this, duration: const Duration(milliseconds: 400)); Animation curve = CurvedAnimation( parent: expandController, - curve: Curves.fastOutSlowIn, + curve: Curves.linear, ); animation = Tween(begin: widget.begin, end: widget.end).animate(curve); - // animation = CurvedAnimation( - // parent: expandController, - // curve: Curves.fastOutSlowIn, - // ); } void _runExpandCheck() { @@ -67,7 +53,9 @@ class _ExpandedSectionState extends State @override void didUpdateWidget(ExpandedSection oldWidget) { super.didUpdateWidget(oldWidget); - _runExpandCheck(); + if (widget.expand != oldWidget.expand) { + _runExpandCheck(); + } } @override From 7e7bb1f43a4c23f0b3473ca58506c93da51912ec Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Mar 2024 16:17:02 +0800 Subject: [PATCH 16/19] =?UTF-8?q?fix:=20appbar=E6=BB=91=E5=8A=A8=E8=B7=9D?= =?UTF-8?q?=E7=A6=BB=E7=A7=BB=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/widgets/app_bar.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/pages/video/detail/widgets/app_bar.dart b/lib/pages/video/detail/widgets/app_bar.dart index 17f4bec7..efc0b593 100644 --- a/lib/pages/video/detail/widgets/app_bar.dart +++ b/lib/pages/video/detail/widgets/app_bar.dart @@ -17,12 +17,16 @@ class ScrollAppBar extends StatelessWidget { Widget build(BuildContext context) { final double statusBarHeight = MediaQuery.of(context).padding.top; final videoHeight = MediaQuery.sizeOf(context).width * 9 / 16; + double scrollDistance = scrollVal; + if (scrollVal > videoHeight - kToolbarHeight) { + scrollDistance = videoHeight - kToolbarHeight; + } return Positioned( - top: -videoHeight + scrollVal + kToolbarHeight + 0.5, + top: -videoHeight + scrollDistance + kToolbarHeight + 0.5, left: 0, right: 0, child: Opacity( - opacity: scrollVal / (videoHeight - kToolbarHeight), + opacity: scrollDistance / (videoHeight - kToolbarHeight), child: Container( height: statusBarHeight + kToolbarHeight, color: Theme.of(context).colorScheme.background, From 2cd8ab7d27fcf9c6a686725827242d76091b3207 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Mar 2024 16:03:18 +0800 Subject: [PATCH 17/19] =?UTF-8?q?mod:=20=E8=A7=86=E9=A2=91=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=A1=B5=E7=AE=80=E4=BB=8B=E6=9F=A5=E7=9C=8B&?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/introduction/view.dart | 33 ++--- .../introduction/widgets/intro_detail.dart | 130 ++++-------------- .../detail/widgets/expandable_section.dart | 20 +-- 3 files changed, 50 insertions(+), 133 deletions(-) diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 9c1b7db0..831491f6 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -15,6 +15,7 @@ import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; +import '../widgets/expandable_section.dart'; import 'widgets/action_item.dart'; import 'widgets/fav_panel.dart'; import 'widgets/intro_detail.dart'; @@ -137,6 +138,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { late String memberHeroTag; late bool enableAi; bool isProcessing = false; + RxBool isExpand = false.obs; void Function()? handleState(Future Function() action) { return isProcessing ? null @@ -212,13 +214,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { // 视频介绍 showIntroDetail() { feedBack(); - showBottomSheet( - context: context, - enableDrag: true, - builder: (BuildContext context) { - return IntroDetail(videoDetail: widget.videoDetail!); - }, - ); + isExpand.value = !(isExpand.value); } // 用户主页 @@ -330,6 +326,16 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ], ), + /// 视频简介 + Obx( + () => ExpandedSection( + expand: isExpand.value, + begin: 0, + end: 1, + child: IntroDetail(videoDetail: widget.videoDetail!), + ), + ), + /// 点赞收藏转发 actionGrid(context, videoIntroController), // 合集 @@ -438,6 +444,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { margin: const EdgeInsets.only(top: 6, bottom: 4), height: constraints.maxWidth / 5 * 0.8, child: GridView.count( + physics: const NeverScrollableScrollPhysics(), primary: false, padding: EdgeInsets.zero, crossAxisCount: 5, @@ -451,12 +458,6 @@ class _VideoInfoState extends State with TickerProviderStateMixin { selectStatus: videoIntroController.hasLike.value, text: widget.videoDetail!.stat!.like!.toString()), ), - // ActionItem( - // icon: const Icon(FontAwesomeIcons.clock), - // onTap: () => videoIntroController.actionShareVideo(), - // selectStatus: false, - // loadingStatus: loadingStatus, - // text: '稍后再看'), Obx( () => ActionItem( icon: const Icon(FontAwesomeIcons.b), @@ -477,10 +478,10 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ), ), ActionItem( - icon: const Icon(FontAwesomeIcons.comment), - onTap: () => videoDetailCtr.tabCtr.animateTo(1), + icon: const Icon(FontAwesomeIcons.clock), + onTap: () => videoIntroController.actionShareVideo(), selectStatus: false, - text: widget.videoDetail!.stat!.reply!.toString(), + text: '稍后看', ), ActionItem( icon: const Icon(FontAwesomeIcons.shareFromSquare), diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index c74e27ee..1e9bb842 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -1,16 +1,10 @@ import 'package:flutter/gestures.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:hive/hive.dart'; -import 'package:pilipala/common/widgets/stat/danmu.dart'; -import 'package:pilipala/common/widgets/stat/view.dart'; -import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; -Box localCache = GStrorage.localCache; -late double sheetHeight; - class IntroDetail extends StatelessWidget { const IntroDetail({ super.key, @@ -20,105 +14,39 @@ class IntroDetail extends StatelessWidget { @override Widget build(BuildContext context) { - sheetHeight = localCache.get('sheetHeight'); - return Container( - color: Theme.of(context).colorScheme.background, - padding: EdgeInsets.only( - left: 14, - right: 14, - bottom: MediaQuery.of(context).padding.bottom + 20), - height: sheetHeight, + return SizedBox( + width: double.infinity, + child: SelectableRegion( + focusNode: FocusNode(), + selectionControls: MaterialTextSelectionControls(), child: Column( - 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.primary, - borderRadius: - const BorderRadius.all(Radius.circular(3))), - ), - ), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: videoDetail!.bvid!)); + SmartDialog.showToast('已复制'); + }, + child: Text( + videoDetail!.bvid!, + style: TextStyle( + fontSize: 13, color: Theme.of(context).colorScheme.primary), ), ), - Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - videoDetail!.title, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 6), - Row( - children: [ - StatView( - theme: 'gray', - view: videoDetail!.stat!.view, - size: 'medium', - ), - const SizedBox(width: 10), - StatDanMu( - theme: 'gray', - danmu: videoDetail!.stat!.danmaku, - size: 'medium', - ), - const SizedBox(width: 10), - Text( - Utils.dateFormat(videoDetail!.pubdate, - formatType: 'detail'), - style: TextStyle( - fontSize: 12, - color: Theme.of(context).colorScheme.outline, - ), - ), - ], - ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - child: SelectableRegion( - focusNode: FocusNode(), - selectionControls: MaterialTextSelectionControls(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - videoDetail!.bvid!, - style: const TextStyle(fontSize: 13), - ), - const SizedBox(height: 4), - Text.rich( - style: const TextStyle( - height: 1.4, - // fontSize: 13, - ), - TextSpan( - children: [ - buildContent(context, videoDetail!), - ], - ), - ), - ], - ), - ), - ), - ], - ), + const SizedBox(height: 4), + Text.rich( + style: const TextStyle(height: 1.4), + TextSpan( + children: [ + buildContent(context, videoDetail!), + ], ), - ) + ), ], - )); + ), + ), + ); } InlineSpan buildContent(BuildContext context, content) { diff --git a/lib/pages/video/detail/widgets/expandable_section.dart b/lib/pages/video/detail/widgets/expandable_section.dart index afa68cc9..69e73e20 100644 --- a/lib/pages/video/detail/widgets/expandable_section.dart +++ b/lib/pages/video/detail/widgets/expandable_section.dart @@ -32,28 +32,14 @@ class _ExpandedSectionState extends State _runExpandCheck(); } - ///Setting up the animation - // void prepareAnimations() { - // expandController = AnimationController( - // vsync: this, duration: const Duration(milliseconds: 500)); - // animation = CurvedAnimation( - // parent: expandController, - // curve: Curves.fastOutSlowIn, - // ); - // } - void prepareAnimations() { expandController = AnimationController( vsync: this, duration: const Duration(milliseconds: 400)); Animation curve = CurvedAnimation( parent: expandController, - curve: Curves.fastOutSlowIn, + curve: Curves.linear, ); animation = Tween(begin: widget.begin, end: widget.end).animate(curve); - // animation = CurvedAnimation( - // parent: expandController, - // curve: Curves.fastOutSlowIn, - // ); } void _runExpandCheck() { @@ -67,7 +53,9 @@ class _ExpandedSectionState extends State @override void didUpdateWidget(ExpandedSection oldWidget) { super.didUpdateWidget(oldWidget); - _runExpandCheck(); + if (widget.expand != oldWidget.expand) { + _runExpandCheck(); + } } @override From 1f75a7e781dd452eae763bd2fc38fb409e97e973 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Mar 2024 16:17:02 +0800 Subject: [PATCH 18/19] =?UTF-8?q?fix:=20appbar=E6=BB=91=E5=8A=A8=E8=B7=9D?= =?UTF-8?q?=E7=A6=BB=E7=A7=BB=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/widgets/app_bar.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/pages/video/detail/widgets/app_bar.dart b/lib/pages/video/detail/widgets/app_bar.dart index 17f4bec7..efc0b593 100644 --- a/lib/pages/video/detail/widgets/app_bar.dart +++ b/lib/pages/video/detail/widgets/app_bar.dart @@ -17,12 +17,16 @@ class ScrollAppBar extends StatelessWidget { Widget build(BuildContext context) { final double statusBarHeight = MediaQuery.of(context).padding.top; final videoHeight = MediaQuery.sizeOf(context).width * 9 / 16; + double scrollDistance = scrollVal; + if (scrollVal > videoHeight - kToolbarHeight) { + scrollDistance = videoHeight - kToolbarHeight; + } return Positioned( - top: -videoHeight + scrollVal + kToolbarHeight + 0.5, + top: -videoHeight + scrollDistance + kToolbarHeight + 0.5, left: 0, right: 0, child: Opacity( - opacity: scrollVal / (videoHeight - kToolbarHeight), + opacity: scrollDistance / (videoHeight - kToolbarHeight), child: Container( height: statusBarHeight + kToolbarHeight, color: Theme.of(context).colorScheme.background, From 955d8f54019ee6f0d97672fcc4f06a03776baddc Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Mar 2024 23:25:45 +0800 Subject: [PATCH 19/19] =?UTF-8?q?feat:=20=E7=AE=80=E5=8D=95=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=AD=97=E5=B9=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/video.dart | 27 +++++-- lib/models/common/subtitle_type.dart | 47 ++++++++++++ lib/models/video/subTitile/result.dart | 21 +++++- lib/pages/video/detail/controller.dart | 42 +++++++---- lib/pages/video/detail/view.dart | 1 + .../video/detail/widgets/header_control.dart | 75 +++++++++++++++++++ lib/plugin/pl_player/controller.dart | 66 ++++++++++++++-- lib/plugin/pl_player/view.dart | 69 +++++++++-------- lib/utils/subtitle.dart | 32 ++++++++ 9 files changed, 318 insertions(+), 62 deletions(-) create mode 100644 lib/models/common/subtitle_type.dart create mode 100644 lib/utils/subtitle.dart diff --git a/lib/http/video.dart b/lib/http/video.dart index 4ac886c7..d43656b2 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -12,6 +12,7 @@ import '../models/video/subTitile/result.dart'; import '../models/video_detail_res.dart'; import '../utils/recommend_filter.dart'; import '../utils/storage.dart'; +import '../utils/subtitle.dart'; import '../utils/wbi_sign.dart'; import 'api.dart'; import 'init.dart'; @@ -482,13 +483,17 @@ class VideoHttp { 'cid': cid, 'bvid': bvid, }); - if (res.data['code'] == 0) { - return { - 'status': true, - 'data': SubTitlteModel.fromJson(res.data['data']), - }; - } else { - return {'status': false, 'data': [], 'msg': res.data['msg']}; + try { + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': SubTitlteModel.fromJson(res.data['data']), + }; + } else { + return {'status': false, 'data': [], 'msg': res.data['msg']}; + } + } catch (err) { + print(err); } } @@ -514,4 +519,12 @@ class VideoHttp { return {'status': false, 'data': [], 'msg': err}; } } + + // 获取字幕内容 + static Future> getSubtitleContent(url) async { + var res = await Request().get('https:$url'); + final String content = SubTitleUtils.convertToWebVTT(res.data['body']); + final List body = res.data['body']; + return {'content': content, 'body': body}; + } } diff --git a/lib/models/common/subtitle_type.dart b/lib/models/common/subtitle_type.dart new file mode 100644 index 00000000..11716351 --- /dev/null +++ b/lib/models/common/subtitle_type.dart @@ -0,0 +1,47 @@ +enum SubtitleType { + // 中文(中国) + zhCN, + // 中文(自动翻译) + aizh, + // 英语(自动生成) + aien, +} + +extension SubtitleTypeExtension on SubtitleType { + String get description { + switch (this) { + case SubtitleType.zhCN: + return '中文(中国)'; + case SubtitleType.aizh: + return '中文(自动翻译)'; + case SubtitleType.aien: + 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'; + } + } +} + +extension SubtitleCodeExtension on SubtitleType { + int get code { + switch (this) { + case SubtitleType.zhCN: + return 1; + case SubtitleType.aizh: + return 2; + case SubtitleType.aien: + return 3; + } + } +} diff --git a/lib/models/video/subTitile/result.dart b/lib/models/video/subTitile/result.dart index 389378fa..d3e32e55 100644 --- a/lib/models/video/subTitile/result.dart +++ b/lib/models/video/subTitile/result.dart @@ -1,3 +1,6 @@ +import 'package:get/get.dart'; +import '../../common/subtitle_type.dart'; + class SubTitlteModel { SubTitlteModel({ this.aid, @@ -45,6 +48,10 @@ class SubTitlteItemModel { this.type, this.aiType, this.aiStatus, + this.title, + this.code, + this.content, + this.body, }); int? id; @@ -55,16 +62,28 @@ class SubTitlteItemModel { int? type; int? aiType; int? aiStatus; + String? title; + int? code; + String? content; + List? body; factory SubTitlteItemModel.fromJson(Map json) => SubTitlteItemModel( id: json["id"], - lan: json["lan"], + lan: json["lan"].replaceAll('-', ''), lanDoc: json["lan_doc"], isLock: json["is_lock"], subtitleUrl: json["subtitle_url"], type: json["type"], 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 5eda1e77..5c4ac14b 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -20,7 +20,6 @@ import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/video_utils.dart'; import 'package:screen_brightness/screen_brightness.dart'; -import '../../../http/index.dart'; import '../../../models/video/subTitile/content.dart'; import '../../../http/danmaku.dart'; import '../../../utils/id_utils.dart'; @@ -98,6 +97,7 @@ class VideoDetailController extends GetxController RxList subtitleContents = [].obs; late bool enableRelatedVideo; + List subtitles = []; @override void onInit() { @@ -256,6 +256,8 @@ class VideoDetailController extends GetxController /// 开启自动全屏时,在player初始化完成后立即传入headerControl plPlayerController.headerControl = headerControl; + + plPlayerController.subtitles.value = subtitles; } // 视频链接 @@ -398,30 +400,38 @@ class VideoDetailController extends GetxController var result = await VideoHttp.getSubtitle(bvid: bvid, cid: cid.value); if (result['status']) { if (result['data'].subtitles.isNotEmpty) { - SmartDialog.showToast('字幕加载中...'); - var subtitle = result['data'].subtitles.first; - getSubtitleContent(subtitle.subtitleUrl); + 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']; + } + } } return result['data']; - } else { - SmartDialog.showToast(result['msg'].toString()); } } // 获取字幕内容 - 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(); - } + // 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 = ''; - if (subtitleContents.isNotEmpty) { - plPlayerController.subtitleContents = subtitleContents; - } + plPlayerController.subtitles.value = subtitles; + } + + clearSubtitleContent() { + plPlayerController.subtitleContent.value = ''; + plPlayerController.subtitles.value = []; } /// 发送弹幕 diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 08826763..6958a62d 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -212,6 +212,7 @@ class _VideoDetailPageState extends State videoIntroController.isPaused = true; plPlayerController!.removeStatusLister(playerListener); plPlayerController!.pause(); + vdCtr.clearSubtitleContent(); } setState(() => isShowing = false); super.didPushNext(); diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 858ca2df..b0b7db17 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -344,6 +344,56 @@ class _HeaderControlState extends State { ); } + /// 选择字幕 + void showSubtitleDialog() async { + int tempThemeValue = widget.controller!.subTitleCode.value; + int len = widget.videoDetailCtr!.subtitles.length; + showDialog( + context: context, + builder: (BuildContext context) { + 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(); + }, + ), + ...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(), + ], + ); + }), + ); + }); + } + /// 选择倍速 void showSetSpeedSheet() { final double currentSpeed = widget.controller!.playbackSpeed; @@ -1115,6 +1165,31 @@ class _HeaderControlState extends State { ), SizedBox(width: buttonSpace), ], + + /// 字幕 + // SizedBox( + // width: 34, + // height: 34, + // child: IconButton( + // style: ButtonStyle( + // padding: MaterialStateProperty.all(EdgeInsets.zero), + // ), + // onPressed: () => showSubtitleDialog(), + // icon: const Icon( + // Icons.closed_caption_off, + // size: 22, + // ), + // ), + // ), + ComBtn( + icon: const Icon( + Icons.closed_caption_off, + size: 22, + color: Colors.white, + ), + fuc: () => showSubtitleDialog(), + ), + SizedBox(width: buttonSpace), Obx( () => SizedBox( width: 45, diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 6f73f6aa..b385fca8 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -22,6 +22,7 @@ import 'package:screen_brightness/screen_brightness.dart'; import 'package:status_bar_control/status_bar_control.dart'; import 'package:universal_platform/universal_platform.dart'; import '../../models/video/subTitile/content.dart'; +import '../../models/video/subTitile/result.dart'; // import 'package:wakelock_plus/wakelock_plus.dart'; Box videoStorage = GStrorage.video; @@ -74,6 +75,8 @@ class PlPlayerController { final Rx _doubleSpeedStatus = false.obs; final Rx _controlsLock = false.obs; final Rx _isFullScreen = false.obs; + final Rx _subTitleOpen = false.obs; + final Rx _subTitleCode = (-1).obs; // 默认投稿视频格式 static Rx _videoType = 'archive'.obs; @@ -119,6 +122,7 @@ class PlPlayerController { PreferredSizeWidget? headerControl; PreferredSizeWidget? bottomControl; Widget? danmuWidget; + late RxList subtitles; /// 数据加载监听 Stream get onDataStatusChanged => dataStatus.status.stream; @@ -148,6 +152,11 @@ class PlPlayerController { Rx get mute => _mute; Stream get onMuteChanged => _mute.stream; + /// 字幕开启状态 + Rx get subTitleOpen => _subTitleOpen; + Rx get subTitleCode => _subTitleCode; + // Stream get onSubTitleOpenChanged => _subTitleOpen.stream; + /// [videoPlayerController] instace of Player Player? get videoPlayerController => _videoPlayerController; @@ -355,6 +364,8 @@ class PlPlayerController { bool enableHeart = true, // 是否首次加载 bool isFirstTime = true, + // 是否开启字幕 + bool enableSubTitle = false, }) async { try { _autoPlay = autoplay; @@ -369,7 +380,9 @@ class PlPlayerController { _cid = cid; _enableHeart = enableHeart; _isFirstTime = isFirstTime; - + _subTitleOpen.value = enableSubTitle; + subtitles = [].obs; + subtitleContent.value = ''; if (_videoPlayerController != null && _videoPlayerController!.state.playing) { await pause(notify: false); @@ -616,6 +629,10 @@ class PlPlayerController { const Duration(seconds: 1), () => videoPlayerServiceHandler.onPositionChange(event)); }), + + // onSubTitleOpenChanged.listen((bool event) { + // toggleSubtitle(event ? subTitleCode.value : -1); + // }) ], ); } @@ -1054,11 +1071,46 @@ 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) { - if (subtitleContents.isNotEmpty) { - for (var content in subtitleContents) { - if (progress >= content.from! && progress <= content.to!) { - subtitleContent.value = content.content!; + if (subTitleCode.value == -1) { + subtitleContent.value = ''; + return; + } + if (subtitles.isEmpty) { + return; + } + final SubTitlteItemModel? subtitle = subtitles.firstWhereOrNull( + (element) => element.code == subTitleCode.value, + ); + if (subtitle != null && subtitle.body!.isNotEmpty) { + for (var content in subtitle.body!) { + if (progress >= content['from']! && progress <= content['to']!) { + subtitleContent.value = content['content']!; return; } } @@ -1071,6 +1123,9 @@ class PlPlayerController { } Future dispose({String type = 'single'}) async { + print('dispose'); + print('dispose: ${playerCount.value}'); + // 每次减1,最后销毁 if (type == 'single' && playerCount.value > 1) { _playerCount.value -= 1; @@ -1080,6 +1135,7 @@ class PlPlayerController { } _playerCount.value = 0; try { + print('dispose dispose ---------'); _timer?.cancel(); _timerForVolume?.cancel(); _timerForGettingVolume?.cancel(); diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index aad686e3..be24b105 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -580,41 +580,44 @@ class _PLVideoPlayerState extends State if (widget.danmuWidget != null) Positioned.fill(top: 4, child: widget.danmuWidget!), - widget.controller.subscriptions.isNotEmpty - ? Stack( - children: [ - Positioned( - left: 0, - right: 0, - bottom: 30, - child: Align( - alignment: Alignment.center, - child: Obx( - () => Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: widget.controller.subtitleContent.value != '' - ? Colors.black.withOpacity(0.4) - : Colors.transparent, - ), - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 4, - ), - child: Text( - widget.controller.subtitleContent.value, - style: const TextStyle( - color: Colors.white, - fontSize: 12, - ), + /// 开启且有字幕时展示 + Stack( + children: [ + Positioned( + left: 0, + right: 0, + bottom: 30, + child: Align( + alignment: Alignment.center, + child: Obx( + () => Visibility( + visible: widget.controller.subTitleCode.value != -1, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: widget.controller.subtitleContent.value != '' + ? Colors.black.withOpacity(0.6) + : Colors.transparent, + ), + padding: widget.controller.subTitleCode.value != -1 + ? const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ) + : EdgeInsets.zero, + child: Text( + widget.controller.subtitleContent.value, + style: const TextStyle( + color: Colors.white, + fontSize: 12, ), ), - ), - ), - ), - ], - ) - : const SizedBox(), + )), + ), + ), + ), + ], + ), /// 手势 Positioned.fill( diff --git a/lib/utils/subtitle.dart b/lib/utils/subtitle.dart new file mode 100644 index 00000000..452be542 --- /dev/null +++ b/lib/utils/subtitle.dart @@ -0,0 +1,32 @@ +class SubTitleUtils { + // 格式整理 + static String convertToWebVTT(List jsonData) { + String webVTTContent = 'WEBVTT FILE\n\n'; + + for (int i = 0; i < jsonData.length; i++) { + final item = jsonData[i]; + double from = item['from'] as double; + double to = item['to'] as double; + 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'; + } + + return webVTTContent; + } + + static String formatTime(num seconds) { + final String h = (seconds / 3600).floor().toString().padLeft(2, '0'); + final String m = (seconds % 3600 / 60).floor().toString().padLeft(2, '0'); + final String s = (seconds % 60).floor().toString().padLeft(2, '0'); + final String ms = + (seconds * 1000 % 1000).floor().toString().padLeft(3, '0'); + if (h == '00') { + return "$m:$s.$ms"; + } + return "$h:$m:$s.$ms"; + } +}