1+ import 'dart:io' ;
12import 'dart:async' ;
23import 'dart:ui' as ui;
34import 'package:get/get.dart' ;
5+ import 'package:flutter/services.dart' ;
46import 'package:flutter/rendering.dart' ;
57import 'package:flutter/scheduler.dart' ;
8+ import 'package:flutter/foundation.dart' ;
69import 'package:pure_live/common/index.dart' ;
710import 'package:pure_live/plugins/emoji_manager.dart' ;
11+ import 'package:device_info_plus/device_info_plus.dart' ;
812import 'package:pure_live/modules/live_play/player_state.dart' ;
913import 'package:pure_live/modules/live_play/live_play_controller.dart' ;
1014
@@ -32,7 +36,7 @@ class DanmakuListViewState extends State<DanmakuListView> {
3236 Worker ? windowFullscreenWorker;
3337
3438 StreamSubscription ? messagesSub;
35-
39+ bool _autoScrollEnabled = true ;
3640 LivePlayController get controller => Get .find <LivePlayController >();
3741
3842 @override
@@ -106,6 +110,7 @@ class DanmakuListViewState extends State<DanmakuListView> {
106110
107111 void scheduleAutoScroll () {
108112 if (! mounted) return ;
113+ if (! _autoScrollEnabled) return ;
109114 if (! _scrollController.hasClients) return ;
110115 final position = _scrollController.position;
111116 bool shouldAutoScroll = true ;
@@ -144,6 +149,7 @@ class DanmakuListViewState extends State<DanmakuListView> {
144149 if (! userScrolling) {
145150 setState (() {
146151 userScrolling = true ;
152+ _autoScrollEnabled = false ;
147153 });
148154 }
149155 }
@@ -213,6 +219,7 @@ class DanmakuListViewState extends State<DanmakuListView> {
213219 onPressed: () async {
214220 setState (() {
215221 userScrolling = false ;
222+ _autoScrollEnabled = true ;
216223 });
217224
218225 await forceScrollToBottom ();
@@ -269,21 +276,16 @@ class DanmakuItem extends StatelessWidget {
269276 ),
270277
271278 Expanded (
272- child: Text .rich (
279+ child: SelectableText .rich (
273280 TextSpan (
274281 children: [
275282 TextSpan (
276283 text: "${danmaku .userName }: " ,
277- style: TextStyle (
278- fontSize: 14 ,
279- fontWeight: FontWeight .w700,
280- color: Colors .black87,
281- letterSpacing: 0.2 ,
282- ),
284+ style: const TextStyle (fontSize: 14 , fontWeight: FontWeight .w700, color: Colors .black87),
283285 ),
284286 TextSpan (
285287 children: parseEmojis (danmaku.message, 14 , vibrantColor),
286- style: TextStyle (
288+ style: const TextStyle (
287289 fontSize: 14 ,
288290 height: 1.45 ,
289291 fontWeight: FontWeight .w500,
@@ -292,6 +294,43 @@ class DanmakuItem extends StatelessWidget {
292294 ),
293295 ],
294296 ),
297+ contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
298+ return AdaptiveTextSelectionToolbar .buttonItems (
299+ anchors: editableTextState.contextMenuAnchors,
300+ buttonItems: [
301+ ContextMenuButtonItem (
302+ label: i18n ('copy_danmaku' ),
303+ type: ContextMenuButtonType .copy,
304+ onPressed: () async {
305+ final String textToCopy = "${danmaku .userName }: ${danmaku .message }" ;
306+ await Clipboard .setData (ClipboardData (text: textToCopy));
307+ ContextMenuController .removeAny ();
308+ bool shouldShowToast = false ;
309+ if (kIsWeb) {
310+ shouldShowToast = true ;
311+ } else if (Platform .isWindows) {
312+ shouldShowToast = true ;
313+ } else if (Platform .isAndroid) {
314+ final androidInfo = await DeviceInfoPlugin ().androidInfo;
315+ if (androidInfo.version.sdkInt < 33 ) {
316+ shouldShowToast = true ;
317+ }
318+ }
319+ if (shouldShowToast && context.mounted) {
320+ ScaffoldMessenger .of (context).clearSnackBars (); // 清除之前的气泡
321+ ScaffoldMessenger .of (context).showSnackBar (
322+ SnackBar (
323+ content: Text (i18n ('copied_to_clipboard' )),
324+ duration: const Duration (seconds: 2 ),
325+ behavior: SnackBarBehavior .floating, // 让气泡悬浮,更好看
326+ ),
327+ );
328+ }
329+ },
330+ ),
331+ ],
332+ );
333+ },
295334 ),
296335 ),
297336 ],
0 commit comments