mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
show livetime
tweak Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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('分享至消息'),
|
||||
],
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user