diff --git a/assets/images/video/danmu_close.svg b/assets/images/video/danmu_close.svg
new file mode 100644
index 000000000..9f48027b0
--- /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 000000000..24e8d7a99
--- /dev/null
+++ b/assets/images/video/danmu_open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart
index 9626ef082..20bbcd149 100644
--- a/lib/pages/video/detail/controller.dart
+++ b/lib/pages/video/detail/controller.dart
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
+import 'package:PiliPalaX/http/danmaku.dart';
import 'package:floating/floating.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -14,6 +15,7 @@ import 'package:PiliPalaX/plugin/pl_player/index.dart';
import 'package:PiliPalaX/utils/storage.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:PiliPalaX/utils/video_utils.dart';
+import 'package:ns_danmaku/models/danmaku_item.dart';
import '../../../utils/id_utils.dart';
import 'widgets/header_control.dart';
@@ -147,6 +149,85 @@ class VideoDetailController extends GetxController
oid.value = IdUtils.bv2av(Get.parameters['bvid']!);
}
+ /// 发送弹幕
+ void showShootDanmakuSheet() {
+ final TextEditingController textController = TextEditingController();
+ bool isSending = false; // 追踪是否正在发送
+ showDialog(
+ context: Get.context!,
+ builder: (BuildContext context) {
+ // TODO: 支持更多类型和颜色的弹幕
+ return AlertDialog(
+ title: const Text('发送弹幕'),
+ content: StatefulBuilder(
+ builder: (BuildContext context, StateSetter setState) {
+ return TextField(
+ controller: textController,
+ autofocus: true,
+ );
+ }),
+ actions: [
+ TextButton(
+ onPressed: () => Get.back(),
+ child: Text(
+ '取消',
+ style: TextStyle(color: Theme.of(context).colorScheme.outline),
+ ),
+ ),
+ StatefulBuilder(
+ builder: (BuildContext context, StateSetter setState) {
+ return TextButton(
+ onPressed: isSending
+ ? null
+ : () async {
+ final String msg = textController.text;
+ if (msg.isEmpty) {
+ SmartDialog.showToast('弹幕内容不能为空');
+ return;
+ } else if (msg.length > 100) {
+ SmartDialog.showToast('弹幕内容不能超过100个字符');
+ return;
+ }
+ isSending = true; // 开始发送,更新状态
+ //修改按钮文字
+ // SmartDialog.showToast('弹幕发送中,\n$msg');
+ final dynamic res = await DanmakaHttp.shootDanmaku(
+ oid: cid.value,
+ msg: textController.text,
+ bvid: bvid,
+ progress:
+ plPlayerController.position.value.inMilliseconds,
+ type: 1,
+ );
+ isSending = false; // 发送结束,更新状态
+ if (res['status']) {
+ SmartDialog.showToast('发送成功');
+ // 发送成功,自动预览该弹幕,避免重新请求
+ // TODO: 暂停状态下预览弹幕仍会移动与计时,可考虑添加到dmSegList或其他方式实现
+ plPlayerController.danmakuController!.addItems([
+ DanmakuItem(
+ msg,
+ color: Colors.white,
+ time: plPlayerController
+ .position.value.inMilliseconds,
+ type: DanmakuItemType.scroll,
+ isSend: true,
+ )
+ ]);
+ Get.back();
+ } else {
+ SmartDialog.showToast('发送失败,错误信息为${res['msg']}');
+ }
+ },
+ child: Text(isSending ? '发送中...' : '发送'),
+ );
+ })
+ ],
+ );
+ },
+ );
+ }
+
/// 更新画质、音质
/// TODO 继续进度播放
updatePlayer() {
diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart
index 7721d41d5..43faf9d91 100644
--- a/lib/pages/video/detail/view.dart
+++ b/lib/pages/video/detail/view.dart
@@ -19,6 +19,7 @@ import 'package:auto_orientation/auto_orientation.dart';
import 'package:floating/floating.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
@@ -1162,6 +1163,52 @@ class _VideoDetailPageState extends State
),
),
),
+ Flexible(
+ flex: 1,
+ child: Center(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ SizedBox(
+ height: 32,
+ child: TextButton(
+ style: ButtonStyle(
+ padding: WidgetStateProperty.all(EdgeInsets.zero),
+ ),
+ onPressed: videoDetailController.showShootDanmakuSheet,
+ child:
+ const Text('发弹幕', style: TextStyle(fontSize: 12)),
+ ),
+ ),
+ SizedBox(
+ width: 38,
+ height: 38,
+ child: Obx(
+ () => IconButton(
+ onPressed: () {
+ if (plPlayerController != null) {
+ videoDetailController
+ .plPlayerController.isOpenDanmu.value =
+ !videoDetailController
+ .plPlayerController.isOpenDanmu.value;
+ }
+ },
+ icon: SvgPicture.asset(
+ videoDetailController
+ .plPlayerController.isOpenDanmu.value
+ ? 'assets/images/video/danmu_open.svg'
+ : 'assets/images/video/danmu_close.svg',
+ // ignore: deprecated_member_use
+ color: Theme.of(context).colorScheme.outline,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(width: 14),
+ ],
+ ),
+ ),
+ ),
],
),
),
diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart
index aecc8f431..c55dac199 100644
--- a/lib/pages/video/detail/widgets/header_control.dart
+++ b/lib/pages/video/detail/widgets/header_control.dart
@@ -20,7 +20,6 @@ import 'package:PiliPalaX/pages/video/detail/introduction/widgets/menu_row.dart'
import 'package:PiliPalaX/plugin/pl_player/index.dart';
import 'package:PiliPalaX/plugin/pl_player/models/play_repeat.dart';
import 'package:PiliPalaX/utils/storage.dart';
-import 'package:PiliPalaX/http/danmaku.dart';
import 'package:PiliPalaX/services/shutdown_timer_service.dart';
import '../../../../models/video/play/CDN.dart';
import '../../../../models/video_detail_res.dart';
@@ -117,7 +116,7 @@ class _HeaderControlState extends State {
height: 500,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.background,
+ color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
margin: const EdgeInsets.all(12),
@@ -154,7 +153,7 @@ class _HeaderControlState extends State {
// trailing: Transform.scale(
// scale: 0.75,
// child: Switch(
- // thumbIcon: MaterialStateProperty.resolveWith(
+ // thumbIcon: WidgetStateProperty.resolveWith(
// (Set states) {
// if (states.isNotEmpty &&
// states.first == MaterialState.selected) {
@@ -461,88 +460,6 @@ class _HeaderControlState extends State {
);
}
- /// 发送弹幕
- void showShootDanmakuSheet() {
- final TextEditingController textController = TextEditingController();
- bool isSending = false; // 追踪是否正在发送
- showDialog(
- context: Get.context!,
- builder: (BuildContext context) {
- // TODO: 支持更多类型和颜色的弹幕
- return AlertDialog(
- title: const Text('发送弹幕'),
- content: StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- return TextField(
- controller: textController,
- );
- }),
- actions: [
- TextButton(
- onPressed: () => Get.back(),
- child: Text(
- '取消',
- style: TextStyle(color: Theme.of(context).colorScheme.outline),
- ),
- ),
- StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- return TextButton(
- onPressed: isSending
- ? null
- : () async {
- final String msg = textController.text;
- if (msg.isEmpty) {
- SmartDialog.showToast('弹幕内容不能为空');
- return;
- } else if (msg.length > 100) {
- SmartDialog.showToast('弹幕内容不能超过100个字符');
- return;
- }
- setState(() {
- isSending = true; // 开始发送,更新状态
- });
- //修改按钮文字
- // SmartDialog.showToast('弹幕发送中,\n$msg');
- final dynamic res = await DanmakaHttp.shootDanmaku(
- oid: widget.videoDetailCtr!.cid.value,
- msg: textController.text,
- bvid: widget.videoDetailCtr!.bvid,
- progress:
- widget.controller!.position.value.inMilliseconds,
- type: 1,
- );
- setState(() {
- isSending = false; // 发送结束,更新状态
- });
- if (res['status']) {
- SmartDialog.showToast('发送成功');
- // 发送成功,自动预览该弹幕,避免重新请求
- // TODO: 暂停状态下预览弹幕仍会移动与计时,可考虑添加到dmSegList或其他方式实现
- widget.controller!.danmakuController!.addItems([
- DanmakuItem(
- msg,
- color: Colors.white,
- time: widget
- .controller!.position.value.inMilliseconds,
- type: DanmakuItemType.scroll,
- isSend: true,
- )
- ]);
- Get.back();
- } else {
- SmartDialog.showToast('发送失败,错误信息为${res['msg']}');
- }
- },
- child: Text(isSending ? '发送中...' : '发送'),
- );
- })
- ],
- );
- },
- );
- }
-
/// 定时关闭
void scheduleExit() async {
const List scheduleTimeChoices = [
@@ -564,7 +481,7 @@ class _HeaderControlState extends State {
height: 500,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.background,
+ color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
margin: const EdgeInsets.all(12),
@@ -624,7 +541,7 @@ class _HeaderControlState extends State {
inactiveThumbColor:
Theme.of(context).colorScheme.primaryContainer,
inactiveTrackColor:
- Theme.of(context).colorScheme.background,
+ Theme.of(context).colorScheme.surface,
splashRadius: 10.0,
// boolean variable value
value: shutdownTimerService.waitForPlayingCompleted,
@@ -704,7 +621,7 @@ class _HeaderControlState extends State {
height: 310,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.background,
+ color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
margin: const EdgeInsets.all(12),
@@ -803,7 +720,7 @@ class _HeaderControlState extends State {
height: 250,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.background,
+ color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
margin: const EdgeInsets.all(12),
@@ -889,7 +806,7 @@ class _HeaderControlState extends State {
height: 250,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.background,
+ color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
margin: const EdgeInsets.all(12),
@@ -985,7 +902,7 @@ class _HeaderControlState extends State {
height: 580,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.background,
+ color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
margin: const EdgeInsets.all(12),
@@ -1345,7 +1262,7 @@ class _HeaderControlState extends State {
height: 300,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.background,
+ color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
margin: const EdgeInsets.all(12),
@@ -1529,11 +1446,11 @@ class _HeaderControlState extends State {
child: IconButton(
tooltip: '发弹幕',
style: ButtonStyle(
- padding: MaterialStateProperty.all(EdgeInsets.zero),
+ padding: WidgetStateProperty.all(EdgeInsets.zero),
),
- onPressed: () => showShootDanmakuSheet(),
+ onPressed: widget.videoDetailCtr?.showShootDanmakuSheet,
icon: const Icon(
- Icons.add_comment_outlined,
+ Icons.comment_outlined,
size: 19,
color: Colors.white,
),
@@ -1558,8 +1475,8 @@ class _HeaderControlState extends State {
},
icon: Icon(
_.isOpenDanmu.value
- ? Icons.comment_outlined
- : Icons.comments_disabled_outlined,
+ ? Icons.subtitles_outlined
+ : Icons.subtitles_off_outlined,
size: 19,
color: Colors.white,
),
@@ -1612,7 +1529,7 @@ class _HeaderControlState extends State {
TextButton(
style: ButtonStyle(
foregroundColor:
- MaterialStateProperty.resolveWith(
+ WidgetStateProperty.resolveWith(
(states) {
return Theme.of(context)
.snackBarTheme
@@ -1629,7 +1546,7 @@ class _HeaderControlState extends State {
TextButton(
style: ButtonStyle(
foregroundColor:
- MaterialStateProperty.resolveWith(
+ WidgetStateProperty.resolveWith(
(states) {
return Theme.of(context)
.snackBarTheme
@@ -1669,7 +1586,7 @@ class _HeaderControlState extends State {
child: IconButton(
tooltip: "更多设置",
style: ButtonStyle(
- padding: MaterialStateProperty.all(EdgeInsets.zero),
+ padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => showSettingSheet(),
icon: const Icon(
diff --git a/pubspec.yaml b/pubspec.yaml
index 5c2579eb9..3bf6aa121 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -249,6 +249,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