show livetime

tweak

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-08-07 15:09:04 +08:00
parent edb5ea7a7a
commit ef1ccabc8a
7 changed files with 203 additions and 144 deletions

View File

@@ -10,7 +10,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/dynamic/dyn_mention/group.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/controller.dart';
import 'package:PiliPlus/pages/dynamics_mention/widgets/item.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/context_ext.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -80,8 +80,7 @@ class _DynMentionPanelState extends State<DynMentionPanel>
} }
@override @override
ValueChanged<String> get onKeywordChanged => void onKeywordChanged(String value) => _controller
(value) => _controller
..enableClear.value = value.isNotEmpty ..enableClear.value = value.isNotEmpty
..onRefresh().whenComplete( ..onRefresh().whenComplete(
() => WidgetsBinding.instance.addPostFrameCallback( () => WidgetsBinding.instance.addPostFrameCallback(

View File

@@ -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/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/controller.dart';
import 'package:PiliPlus/pages/dynamics_select_topic/widgets/item.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/context_ext.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -78,8 +78,7 @@ class _SelectTopicPanelState extends State<SelectTopicPanel>
} }
@override @override
ValueChanged<String> get onKeywordChanged => void onKeywordChanged(String value) => _controller
(value) => _controller
..enableClear.value = value.isNotEmpty ..enableClear.value = value.isNotEmpty
..onRefresh().whenComplete( ..onRefresh().whenComplete(
() => WidgetsBinding.instance.addPostFrameCallback( () => WidgetsBinding.instance.addPostFrameCallback(

View File

@@ -38,6 +38,24 @@ class LiveRoomController extends GetxController {
RxBool isLoaded = false.obs; RxBool isLoaded = false.obs;
Rx<RoomInfoH5Data?> roomInfoH5 = Rx<RoomInfoH5Data?>(null); Rx<RoomInfoH5Data?> roomInfoH5 = Rx<RoomInfoH5Data?>(null);
Rx<int?> liveTime = Rx<int?>(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 // dm
LiveDmInfoData? dmInfo; LiveDmInfoData? dmInfo;
List<RichTextItem>? savedDanmaku; List<RichTextItem>? savedDanmaku;
@@ -104,6 +122,8 @@ class LiveRoomController extends GetxController {
if (data.roomId != null) { if (data.roomId != null) {
roomId = data.roomId!; roomId = data.roomId!;
} }
liveTime.value = data.liveTime;
startLiveTimer();
isPortrait.value = data.isPortrait ?? false; isPortrait.value = data.isPortrait ?? false;
List<CodecItem> codec = List<CodecItem> codec =
data.playurlInfo!.playurl!.stream!.first.format!.first.codec!; data.playurlInfo!.playurl!.stream!.first.format!.first.codec!;
@@ -237,6 +257,7 @@ class LiveRoomController extends GetxController {
@override @override
void onClose() { void onClose() {
cancelLiveTimer();
savedDanmaku?.clear(); savedDanmaku?.clear();
savedDanmaku = null; savedDanmaku = null;
scrollController scrollController

View File

@@ -15,6 +15,7 @@ import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart';
import 'package:PiliPlus/plugin/pl_player/view.dart'; import 'package:PiliPlus/plugin/pl_player/view.dart';
import 'package:PiliPlus/services/service_locator.dart'; import 'package:PiliPlus/services/service_locator.dart';
import 'package:PiliPlus/utils/context_ext.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/extension.dart';
import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
@@ -67,11 +68,14 @@ class _LiveRoomPageState extends State<LiveRoomPage>
if (status != PlayerStatus.playing) { if (status != PlayerStatus.playing) {
plPlayerController.danmakuController?.pause(); plPlayerController.danmakuController?.pause();
_liveRoomController _liveRoomController
..cancelLiveTimer()
..msgStream?.close() ..msgStream?.close()
..msgStream = null; ..msgStream = null;
} else { } else {
plPlayerController.danmakuController?.resume(); plPlayerController.danmakuController?.resume();
_liveRoomController.liveMsg(); _liveRoomController
..startLiveTimer()
..liveMsg();
} }
} }
@@ -94,6 +98,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) { if (state == AppLifecycleState.resumed) {
if (!plPlayerController.showDanmaku) { if (!plPlayerController.showDanmaku) {
_liveRoomController.startLiveTimer();
plPlayerController.showDanmaku = true; plPlayerController.showDanmaku = true;
if (isFullScreen && Platform.isIOS) { if (isFullScreen && Platform.isIOS) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -104,6 +109,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
} }
} }
} else if (state == AppLifecycleState.paused) { } else if (state == AppLifecycleState.paused) {
_liveRoomController.cancelLiveTimer();
plPlayerController plPlayerController
..showDanmaku = false ..showDanmaku = false
..danmakuController?.clear(); ..danmakuController?.clear();
@@ -313,12 +319,12 @@ class _LiveRoomPageState extends State<LiveRoomPage>
title: Obx( title: Obx(
() { () {
RoomInfoH5Data? roomInfoH5 = _liveRoomController.roomInfoH5.value; RoomInfoH5Data? roomInfoH5 = _liveRoomController.roomInfoH5.value;
return roomInfoH5 == null if (roomInfoH5 == null) {
? const SizedBox.shrink() return const SizedBox.shrink();
: GestureDetector( }
return GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () => onTap: () => Get.toNamed('/member?mid=${roomInfoH5.roomInfo?.uid}'),
Get.toNamed('/member?mid=${roomInfoH5.roomInfo?.uid}'),
child: Row( child: Row(
spacing: 10, spacing: 10,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -337,12 +343,28 @@ class _LiveRoomPageState extends State<LiveRoomPage>
roomInfoH5.anchorInfo!.baseInfo!.uname!, roomInfoH5.anchorInfo!.baseInfo!.uname!,
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
if (roomInfoH5.watchedShow?.textLarge?.isNotEmpty == Obx(() {
true) final liveTime = _liveRoomController.liveTime.value;
Text( final textLarge = roomInfoH5.watchedShow?.textLarge;
roomInfoH5.watchedShow!.textLarge!, String text = '';
style: const TextStyle(fontSize: 12), 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));
}),
], ],
), ),
], ],
@@ -351,18 +373,19 @@ class _LiveRoomPageState extends State<LiveRoomPage>
}, },
), ),
actions: [ actions: [
IconButton( // IconButton(
tooltip: '刷新', // tooltip: '刷新',
onPressed: _liveRoomController.queryLiveUrl, // onPressed: _liveRoomController.queryLiveUrl,
icon: const Icon(Icons.refresh, size: 20), // icon: const Icon(Icons.refresh, size: 20),
), // ),
PopupMenuButton( PopupMenuButton(
icon: const Icon(Icons.more_vert, size: 20), icon: const Icon(Icons.more_vert, size: 20),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[ itemBuilder: (BuildContext context) {
final liveUrl =
'https://live.bilibili.com/${_liveRoomController.roomId}';
return <PopupMenuEntry>[
PopupMenuItem( PopupMenuItem(
onTap: () => Utils.copyText( onTap: () => Utils.copyText(liveUrl),
'https://live.bilibili.com/${_liveRoomController.roomId}',
),
child: Row( child: Row(
spacing: 10, spacing: 10,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -377,9 +400,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
), ),
), ),
PopupMenuItem( PopupMenuItem(
onTap: () => Utils.shareText( onTap: () => Utils.shareText(liveUrl),
'https://live.bilibili.com/${_liveRoomController.roomId}',
),
child: Row( child: Row(
spacing: 10, spacing: 10,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -394,10 +415,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
), ),
), ),
PopupMenuItem( PopupMenuItem(
onTap: () => PageUtils.inAppWebview( onTap: () => PageUtils.inAppWebview(liveUrl, off: true),
'https://live.bilibili.com/h5/${_liveRoomController.roomId}',
off: true,
),
child: Row( child: Row(
spacing: 10, spacing: 10,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -423,8 +441,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
"cover": roomInfo.roomInfo!.cover!, "cover": roomInfo.roomInfo!.cover!,
"sourceID": _liveRoomController.roomId.toString(), "sourceID": _liveRoomController.roomId.toString(),
"title": roomInfo.roomInfo!.title!, "title": roomInfo.roomInfo!.title!,
"url": "url": liveUrl,
"https://live.bilibili.com/${_liveRoomController.roomId}",
"authorID": roomInfo.roomInfo!.uid.toString(), "authorID": roomInfo.roomInfo!.uid.toString(),
"source": "直播", "source": "直播",
"desc": roomInfo.roomInfo!.title!, "desc": roomInfo.roomInfo!.title!,
@@ -448,7 +465,8 @@ class _LiveRoomPageState extends State<LiveRoomPage>
], ],
), ),
), ),
], ];
},
), ),
], ],
); );

View File

@@ -18,7 +18,7 @@ mixin SearchKeywordMixin {
Duration duration = const Duration(milliseconds: 200); Duration duration = const Duration(milliseconds: 200);
StreamController<String>? ctr; StreamController<String>? ctr;
StreamSubscription<String>? sub; StreamSubscription<String>? sub;
ValueChanged<String> get onKeywordChanged; void onKeywordChanged(String value);
void subInit() { void subInit() {
ctr = StreamController<String>(); ctr = StreamController<String>();
@@ -170,7 +170,7 @@ class SSearchController extends GetxController with SearchKeywordMixin {
} }
@override @override
ValueChanged<String> get onKeywordChanged => (String value) async { Future<void> onKeywordChanged(String value) async {
var res = await SearchHttp.searchSuggest(term: value); var res = await SearchHttp.searchSuggest(term: value);
if (res['status']) { if (res['status']) {
SearchSuggestModel data = res['data']; SearchSuggestModel data = res['data'];
@@ -178,7 +178,7 @@ class SSearchController extends GetxController with SearchKeywordMixin {
searchSuggestList.value = data.tag!; searchSuggestList.value = data.tag!;
} }
} }
}; }
void onLongSelect(String word) { void onLongSelect(String word) {
historyList.remove(word); historyList.remove(word);

View File

@@ -1,5 +1,5 @@
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; 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/extra_settings.dart';
import 'package:PiliPlus/pages/setting/models/model.dart'; import 'package:PiliPlus/pages/setting/models/model.dart';
import 'package:PiliPlus/pages/setting/models/play_settings.dart'; import 'package:PiliPlus/pages/setting/models/play_settings.dart';
@@ -39,7 +39,7 @@ class _SettingsSearchPageState extends State<SettingsSearchPage>
} }
@override @override
ValueChanged<String> get onKeywordChanged => (value) { void onKeywordChanged(String value) {
if (value.isEmpty) { if (value.isEmpty) {
_list.clear(); _list.clear();
} else { } else {
@@ -54,7 +54,7 @@ class _SettingsSearchPageState extends State<SettingsSearchPage>
) )
.toList(); .toList();
} }
}; }
@override @override
void dispose() { void dispose() {

View File

@@ -32,4 +32,26 @@ class DurationUtil {
} }
return duration; 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;
}
} }