From ef1ccabc8a400c9d6142fbbff350680ee36dba5c Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Thu, 7 Aug 2025 15:09:04 +0800 Subject: [PATCH] show livetime tweak Signed-off-by: bggRGjQaUbCoE --- lib/pages/dynamics_mention/view.dart | 17 +- lib/pages/dynamics_select_topic/view.dart | 17 +- lib/pages/live_room/controller.dart | 21 ++ lib/pages/live_room/view.dart | 258 ++++++++++++---------- lib/pages/search/controller.dart | 6 +- lib/pages/settings_search/view.dart | 6 +- lib/utils/duration_util.dart | 22 ++ 7 files changed, 203 insertions(+), 144 deletions(-) diff --git a/lib/pages/dynamics_mention/view.dart b/lib/pages/dynamics_mention/view.dart index ec1c100d..5d5f7bcc 100644 --- a/lib/pages/dynamics_mention/view.dart +++ b/lib/pages/dynamics_mention/view.dart @@ -10,7 +10,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_mention/group.dart'; import 'package:PiliPlus/pages/dynamics_mention/controller.dart'; import 'package:PiliPlus/pages/dynamics_mention/widgets/item.dart'; -import 'package:PiliPlus/pages/search/controller.dart'; +import 'package:PiliPlus/pages/search/controller.dart' show SearchKeywordMixin; import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:flutter/material.dart'; @@ -80,14 +80,13 @@ class _DynMentionPanelState extends State } @override - ValueChanged get onKeywordChanged => - (value) => _controller - ..enableClear.value = value.isNotEmpty - ..onRefresh().whenComplete( - () => WidgetsBinding.instance.addPostFrameCallback( - (_) => widget.scrollController?.jumpToTop(), - ), - ); + void onKeywordChanged(String value) => _controller + ..enableClear.value = value.isNotEmpty + ..onRefresh().whenComplete( + () => WidgetsBinding.instance.addPostFrameCallback( + (_) => widget.scrollController?.jumpToTop(), + ), + ); @override Widget build(BuildContext context) { diff --git a/lib/pages/dynamics_select_topic/view.dart b/lib/pages/dynamics_select_topic/view.dart index c5d567ef..7e54b795 100644 --- a/lib/pages/dynamics_select_topic/view.dart +++ b/lib/pages/dynamics_select_topic/view.dart @@ -8,7 +8,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; import 'package:PiliPlus/pages/dynamics_select_topic/controller.dart'; import 'package:PiliPlus/pages/dynamics_select_topic/widgets/item.dart'; -import 'package:PiliPlus/pages/search/controller.dart'; +import 'package:PiliPlus/pages/search/controller.dart' show SearchKeywordMixin; import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:flutter/material.dart'; @@ -78,14 +78,13 @@ class _SelectTopicPanelState extends State } @override - ValueChanged get onKeywordChanged => - (value) => _controller - ..enableClear.value = value.isNotEmpty - ..onRefresh().whenComplete( - () => WidgetsBinding.instance.addPostFrameCallback( - (_) => widget.scrollController?.jumpToTop(), - ), - ); + void onKeywordChanged(String value) => _controller + ..enableClear.value = value.isNotEmpty + ..onRefresh().whenComplete( + () => WidgetsBinding.instance.addPostFrameCallback( + (_) => widget.scrollController?.jumpToTop(), + ), + ); @override Widget build(BuildContext context) { diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 90f27eb3..92268547 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -38,6 +38,24 @@ class LiveRoomController extends GetxController { RxBool isLoaded = false.obs; Rx roomInfoH5 = Rx(null); + Rx liveTime = Rx(null); + static const periodMins = 5; + Timer? liveTimeTimer; + + void startLiveTimer() { + if (liveTime.value != null) { + liveTimeTimer ??= Timer.periodic( + const Duration(minutes: periodMins), + (_) => liveTime.refresh(), + ); + } + } + + void cancelLiveTimer() { + liveTimeTimer?.cancel(); + liveTimeTimer = null; + } + // dm LiveDmInfoData? dmInfo; List? savedDanmaku; @@ -104,6 +122,8 @@ class LiveRoomController extends GetxController { if (data.roomId != null) { roomId = data.roomId!; } + liveTime.value = data.liveTime; + startLiveTimer(); isPortrait.value = data.isPortrait ?? false; List codec = data.playurlInfo!.playurl!.stream!.first.format!.first.codec!; @@ -237,6 +257,7 @@ class LiveRoomController extends GetxController { @override void onClose() { + cancelLiveTimer(); savedDanmaku?.clear(); savedDanmaku = null; scrollController diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 634e5ade..ae1d84b4 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -15,6 +15,7 @@ import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart'; import 'package:PiliPlus/plugin/pl_player/view.dart'; import 'package:PiliPlus/services/service_locator.dart'; import 'package:PiliPlus/utils/context_ext.dart'; +import 'package:PiliPlus/utils/duration_util.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; @@ -67,11 +68,14 @@ class _LiveRoomPageState extends State if (status != PlayerStatus.playing) { plPlayerController.danmakuController?.pause(); _liveRoomController + ..cancelLiveTimer() ..msgStream?.close() ..msgStream = null; } else { plPlayerController.danmakuController?.resume(); - _liveRoomController.liveMsg(); + _liveRoomController + ..startLiveTimer() + ..liveMsg(); } } @@ -94,6 +98,7 @@ class _LiveRoomPageState extends State void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { if (!plPlayerController.showDanmaku) { + _liveRoomController.startLiveTimer(); plPlayerController.showDanmaku = true; if (isFullScreen && Platform.isIOS) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -104,6 +109,7 @@ class _LiveRoomPageState extends State } } } else if (state == AppLifecycleState.paused) { + _liveRoomController.cancelLiveTimer(); plPlayerController ..showDanmaku = false ..danmakuController?.clear(); @@ -313,142 +319,154 @@ class _LiveRoomPageState extends State title: Obx( () { RoomInfoH5Data? roomInfoH5 = _liveRoomController.roomInfoH5.value; - return roomInfoH5 == null - ? const SizedBox.shrink() - : GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => - Get.toNamed('/member?mid=${roomInfoH5.roomInfo?.uid}'), - child: Row( - spacing: 10, - mainAxisSize: MainAxisSize.min, - children: [ - NetworkImgLayer( - width: 34, - height: 34, - type: ImageType.avatar, - src: roomInfoH5.anchorInfo!.baseInfo!.face, - ), - Column( - spacing: 1, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - roomInfoH5.anchorInfo!.baseInfo!.uname!, - style: const TextStyle(fontSize: 14), - ), - if (roomInfoH5.watchedShow?.textLarge?.isNotEmpty == - true) - Text( - roomInfoH5.watchedShow!.textLarge!, - style: const TextStyle(fontSize: 12), - ), - ], - ), - ], - ), - ); + if (roomInfoH5 == null) { + return const SizedBox.shrink(); + } + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => Get.toNamed('/member?mid=${roomInfoH5.roomInfo?.uid}'), + child: Row( + spacing: 10, + mainAxisSize: MainAxisSize.min, + children: [ + NetworkImgLayer( + width: 34, + height: 34, + type: ImageType.avatar, + src: roomInfoH5.anchorInfo!.baseInfo!.face, + ), + Column( + spacing: 1, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + roomInfoH5.anchorInfo!.baseInfo!.uname!, + style: const TextStyle(fontSize: 14), + ), + Obx(() { + final liveTime = _liveRoomController.liveTime.value; + final textLarge = roomInfoH5.watchedShow?.textLarge; + String text = ''; + if (textLarge != null) { + text += textLarge; + } + if (liveTime != null) { + if (text.isNotEmpty) { + text += ' '; + } + text += + '开播${DurationUtil.formatDurationBetween( + liveTime * 1000, + DateTime.now().millisecondsSinceEpoch, + )}'; + } + if (text.isEmpty) { + return const SizedBox.shrink(); + } + return Text(text, style: const TextStyle(fontSize: 12)); + }), + ], + ), + ], + ), + ); }, ), actions: [ - IconButton( - tooltip: '刷新', - onPressed: _liveRoomController.queryLiveUrl, - icon: const Icon(Icons.refresh, size: 20), - ), + // IconButton( + // tooltip: '刷新', + // onPressed: _liveRoomController.queryLiveUrl, + // icon: const Icon(Icons.refresh, size: 20), + // ), PopupMenuButton( icon: const Icon(Icons.more_vert, size: 20), - itemBuilder: (BuildContext context) => [ - PopupMenuItem( - onTap: () => Utils.copyText( - 'https://live.bilibili.com/${_liveRoomController.roomId}', - ), - child: Row( - spacing: 10, - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.copy, - size: 19, - color: color, - ), - const Text('复制链接'), - ], - ), - ), - PopupMenuItem( - onTap: () => Utils.shareText( - 'https://live.bilibili.com/${_liveRoomController.roomId}', - ), - child: Row( - spacing: 10, - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.share, - size: 19, - color: color, - ), - const Text('分享直播间'), - ], - ), - ), - PopupMenuItem( - onTap: () => PageUtils.inAppWebview( - 'https://live.bilibili.com/h5/${_liveRoomController.roomId}', - off: true, - ), - child: Row( - spacing: 10, - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.open_in_browser, - size: 19, - color: color, - ), - const Text('浏览器打开'), - ], - ), - ), - if (_liveRoomController.roomInfoH5.value != null) + itemBuilder: (BuildContext context) { + final liveUrl = + 'https://live.bilibili.com/${_liveRoomController.roomId}'; + return [ PopupMenuItem( - onTap: () { - try { - RoomInfoH5Data roomInfo = - _liveRoomController.roomInfoH5.value!; - PageUtils.pmShare( - this.context, - content: { - "cover": roomInfo.roomInfo!.cover!, - "sourceID": _liveRoomController.roomId.toString(), - "title": roomInfo.roomInfo!.title!, - "url": - "https://live.bilibili.com/${_liveRoomController.roomId}", - "authorID": roomInfo.roomInfo!.uid.toString(), - "source": "直播", - "desc": roomInfo.roomInfo!.title!, - "author": roomInfo.anchorInfo!.baseInfo!.uname, - }, - ); - } catch (e) { - SmartDialog.showToast(e.toString()); - } - }, + onTap: () => Utils.copyText(liveUrl), child: Row( spacing: 10, mainAxisSize: MainAxisSize.min, children: [ Icon( - Icons.forward_to_inbox, + Icons.copy, size: 19, color: color, ), - const Text('分享至消息'), + const Text('复制链接'), ], ), ), - ], + PopupMenuItem( + onTap: () => Utils.shareText(liveUrl), + child: Row( + spacing: 10, + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.share, + size: 19, + color: color, + ), + const Text('分享直播间'), + ], + ), + ), + PopupMenuItem( + onTap: () => PageUtils.inAppWebview(liveUrl, off: true), + child: Row( + spacing: 10, + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.open_in_browser, + size: 19, + color: color, + ), + const Text('浏览器打开'), + ], + ), + ), + if (_liveRoomController.roomInfoH5.value != null) + PopupMenuItem( + onTap: () { + try { + RoomInfoH5Data roomInfo = + _liveRoomController.roomInfoH5.value!; + PageUtils.pmShare( + this.context, + content: { + "cover": roomInfo.roomInfo!.cover!, + "sourceID": _liveRoomController.roomId.toString(), + "title": roomInfo.roomInfo!.title!, + "url": liveUrl, + "authorID": roomInfo.roomInfo!.uid.toString(), + "source": "直播", + "desc": roomInfo.roomInfo!.title!, + "author": roomInfo.anchorInfo!.baseInfo!.uname, + }, + ); + } catch (e) { + SmartDialog.showToast(e.toString()); + } + }, + child: Row( + spacing: 10, + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.forward_to_inbox, + size: 19, + color: color, + ), + const Text('分享至消息'), + ], + ), + ), + ]; + }, ), ], ); diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index 0318d467..6ed02928 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -18,7 +18,7 @@ mixin SearchKeywordMixin { Duration duration = const Duration(milliseconds: 200); StreamController? ctr; StreamSubscription? sub; - ValueChanged get onKeywordChanged; + void onKeywordChanged(String value); void subInit() { ctr = StreamController(); @@ -170,7 +170,7 @@ class SSearchController extends GetxController with SearchKeywordMixin { } @override - ValueChanged get onKeywordChanged => (String value) async { + Future onKeywordChanged(String value) async { var res = await SearchHttp.searchSuggest(term: value); if (res['status']) { SearchSuggestModel data = res['data']; @@ -178,7 +178,7 @@ class SSearchController extends GetxController with SearchKeywordMixin { searchSuggestList.value = data.tag!; } } - }; + } void onLongSelect(String word) { historyList.remove(word); diff --git a/lib/pages/settings_search/view.dart b/lib/pages/settings_search/view.dart index 69fef3dd..8bf5d766 100644 --- a/lib/pages/settings_search/view.dart +++ b/lib/pages/settings_search/view.dart @@ -1,5 +1,5 @@ import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; -import 'package:PiliPlus/pages/search/controller.dart'; +import 'package:PiliPlus/pages/search/controller.dart' show SearchKeywordMixin; import 'package:PiliPlus/pages/setting/models/extra_settings.dart'; import 'package:PiliPlus/pages/setting/models/model.dart'; import 'package:PiliPlus/pages/setting/models/play_settings.dart'; @@ -39,7 +39,7 @@ class _SettingsSearchPageState extends State } @override - ValueChanged get onKeywordChanged => (value) { + void onKeywordChanged(String value) { if (value.isEmpty) { _list.clear(); } else { @@ -54,7 +54,7 @@ class _SettingsSearchPageState extends State ) .toList(); } - }; + } @override void dispose() { diff --git a/lib/utils/duration_util.dart b/lib/utils/duration_util.dart index 41d8ef62..db1ba196 100644 --- a/lib/utils/duration_util.dart +++ b/lib/utils/duration_util.dart @@ -32,4 +32,26 @@ class DurationUtil { } return duration; } + + static String formatDurationBetween(int startMillis, int endMillis) { + int diffMillis = endMillis - startMillis; + final duration = Duration(milliseconds: diffMillis); + + final years = duration.inDays ~/ 365; + final months = (duration.inDays % 365) ~/ 30; + final days = (duration.inDays % 365) % 30; + final hours = duration.inHours % 24; + final minutes = duration.inMinutes % 60; + + var format = ''; + + if (years > 0) format += '$years年'; + if (months > 0) format += '$months月'; + if (days > 0) format += '$days天'; + if (hours > 0) format += '$hours小时'; + + format += '$minutes分钟'; + + return format; + } }