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/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<DynMentionPanel>
}
@override
ValueChanged<String> 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) {

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/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<SelectTopicPanel>
}
@override
ValueChanged<String> 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) {

View File

@@ -38,6 +38,24 @@ class LiveRoomController extends GetxController {
RxBool isLoaded = false.obs;
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
LiveDmInfoData? dmInfo;
List<RichTextItem>? 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<CodecItem> 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

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/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<LiveRoomPage>
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<LiveRoomPage>
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<LiveRoomPage>
}
}
} else if (state == AppLifecycleState.paused) {
_liveRoomController.cancelLiveTimer();
plPlayerController
..showDanmaku = false
..danmakuController?.clear();
@@ -313,142 +319,154 @@ class _LiveRoomPageState extends State<LiveRoomPage>
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) => <PopupMenuEntry>[
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 <PopupMenuEntry>[
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('分享至消息'),
],
),
),
];
},
),
],
);

View File

@@ -18,7 +18,7 @@ mixin SearchKeywordMixin {
Duration duration = const Duration(milliseconds: 200);
StreamController<String>? ctr;
StreamSubscription<String>? sub;
ValueChanged<String> get onKeywordChanged;
void onKeywordChanged(String value);
void subInit() {
ctr = StreamController<String>();
@@ -170,7 +170,7 @@ class SSearchController extends GetxController with SearchKeywordMixin {
}
@override
ValueChanged<String> get onKeywordChanged => (String value) async {
Future<void> 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);

View File

@@ -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<SettingsSearchPage>
}
@override
ValueChanged<String> get onKeywordChanged => (value) {
void onKeywordChanged(String value) {
if (value.isEmpty) {
_list.clear();
} else {
@@ -54,7 +54,7 @@ class _SettingsSearchPageState extends State<SettingsSearchPage>
)
.toList();
}
};
}
@override
void dispose() {

View File

@@ -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;
}
}