From 0c73e01013ffd0bc9798248e07c2a992e41f5a06 Mon Sep 17 00:00:00 2001 From: dom Date: Mon, 20 Jul 2026 13:38:56 +0800 Subject: [PATCH] replace SelectableText with SelectionArea Signed-off-by: dom --- .../widgets/context_menu/dyn_menu_helper.dart | 66 +++--- .../context_menu/reply_menu_helper.dart | 188 +++++++++--------- lib/common/widgets/image/image_save.dart | 6 +- .../widgets/loading_widget/http_error.dart | 16 +- lib/common/widgets/selection_text.dart | 49 +++++ lib/pages/audio/view.dart | 11 +- .../dynamics/widgets/additional_panel.dart | 8 +- lib/pages/dynamics/widgets/content_panel.dart | 8 +- lib/pages/dynamics_topic/view.dart | 6 +- .../live_room/superchat/superchat_card.dart | 8 +- lib/pages/member/widget/user_info_card.dart | 4 +- lib/pages/music/view.dart | 6 +- lib/pages/pgc_review/child/view.dart | 6 +- .../setting/models/privacy_settings.dart | 18 +- lib/pages/setting/pages/logs.dart | 9 +- .../pgc/widgets/intro_detail.dart | 7 +- lib/pages/video/introduction/ugc/view.dart | 9 +- .../video/reply/widgets/reply_item_grpc.dart | 5 +- lib/pages/webview/view.dart | 8 +- .../whisper_detail/widget/chat_item.dart | 13 +- lib/scripts/editable_text.patch | 30 --- lib/scripts/selectable_region.patch | 138 ++++++++++++- lib/utils/extension/editable_text_ext.dart | 38 ---- .../extension/selectable_region_ext.dart | 32 +++ lib/utils/reply_utils.dart | 8 +- lib/utils/request_utils.dart | 9 +- 26 files changed, 406 insertions(+), 300 deletions(-) create mode 100644 lib/common/widgets/selection_text.dart delete mode 100644 lib/utils/extension/editable_text_ext.dart create mode 100644 lib/utils/extension/selectable_region_ext.dart diff --git a/lib/common/widgets/context_menu/dyn_menu_helper.dart b/lib/common/widgets/context_menu/dyn_menu_helper.dart index 16db73bbc..b7c744f1d 100644 --- a/lib/common/widgets/context_menu/dyn_menu_helper.dart +++ b/lib/common/widgets/context_menu/dyn_menu_helper.dart @@ -1,7 +1,7 @@ part of 'package:PiliPlus/pages/dynamics/widgets/content_panel.dart'; Widget dynTextMenuBuilder( - EditableTextState state, + SelectableRegionState state, String text, ModuleDynamicModel? moduleDynamic, ) { @@ -23,7 +23,7 @@ Widget dynTextMenuBuilder( onPressed: () { state ..hideToolbar() - ..clearSelectionIfNeeded(); + ..clearSelection(); _showEmoteDialog(moduleDynamic); }, ), @@ -55,32 +55,33 @@ void _showEmoteDialog(ModuleDynamicModel? moduleDynamic) { builder: (context) => Dialog( child: Padding( padding: const .symmetric(horizontal: 20, vertical: 16), - child: SelectableText.rich( - TextSpan( - children: emotes!.entries.mapIndexed( - (i, e) { - final emoji = e.value; - final size = emoji.size * 25.0; - return TextSpan( - children: [ - if (i != 0) const TextSpan(text: '\n\n'), - WidgetSpan( - child: NetworkImgLayer( - src: emoji.url, - type: .emote, - width: size, - height: size, + child: SingleChildScrollView( + child: SelectionText.rich( + TextSpan( + children: emotes!.entries.mapIndexed( + (i, e) { + final emoji = e.value; + final size = emoji.size * 25.0; + return TextSpan( + children: [ + if (i != 0) const TextSpan(text: '\n\n'), + WidgetSpan( + child: NetworkImgLayer( + src: emoji.url, + type: .emote, + width: size, + height: size, + ), ), - ), - TextSpan(text: '\n${e.key}\n${emoji.url}'), - ], - ); - }, - ).toList(), + TextSpan(text: '\n${e.key}\n${emoji.url}'), + ], + ); + }, + ).toList(), + ), + contextMenuBuilder: openUrlMenuBuilder, + style: const TextStyle(fontSize: 15, height: 1.7), ), - contextMenuBuilder: openUrlMenuBuilder, - style: const TextStyle(fontSize: 15, height: 1.7), - scrollPhysics: const ClampingScrollPhysicsExt(), ), ), ), @@ -93,18 +94,19 @@ void _showTextDialog(String text) { builder: (context) => Dialog( child: Padding( padding: const .symmetric(horizontal: 20, vertical: 16), - child: SelectableText( - text, - contextMenuBuilder: openUrlMenuBuilder, - style: const TextStyle(fontSize: 15, height: 1.7), - scrollPhysics: const ClampingScrollPhysicsExt(), + child: SingleChildScrollView( + child: SelectionText( + text, + contextMenuBuilder: openUrlMenuBuilder, + style: const TextStyle(fontSize: 15, height: 1.7), + ), ), ), ), ); } -Widget openUrlMenuBuilder(_, EditableTextState state) { +Widget openUrlMenuBuilder(_, SelectableRegionState state) { final buttonItems = state.contextMenuButtonItems; state.addLaunchMenuIfNeeded(buttonItems, index: 3); return AdaptiveTextSelectionToolbar.buttonItems( diff --git a/lib/common/widgets/context_menu/reply_menu_helper.dart b/lib/common/widgets/context_menu/reply_menu_helper.dart index bf0b89e64..f8820cb24 100644 --- a/lib/common/widgets/context_menu/reply_menu_helper.dart +++ b/lib/common/widgets/context_menu/reply_menu_helper.dart @@ -12,106 +12,108 @@ void showReplyCopyDialog( constraints: const BoxConstraints.tightFor(width: 380), child: Padding( padding: const .symmetric(horizontal: 20, vertical: 16), - child: SelectableText.rich( - scrollPhysics: const ClampingScrollPhysicsExt(), - showEmote - ? TextSpan( - children: emotes.entries.mapIndexed( - (i, e) { - final emote = e.value; - final size = emote.size.toInt() * 25.0; - return TextSpan( - children: [ - if (i != 0) const TextSpan(text: '\n\n'), - WidgetSpan( - child: NetworkImgLayer( - src: emote.url, - type: .emote, - width: size, - height: size, - ), - ), - TextSpan(text: '\n${e.key}\n${emote.url}'), - ], - ); - }, - ).toList(), - ) - : TextSpan(text: message), - contextMenuBuilder: (_, state) { - final buttonItems = state.contextMenuButtonItems; - final selection = state.textEditingValue.selection; - if (emotes.isNotEmpty) { - buttonItems.insertOrAdd( - 3, - ContextMenuButtonItem( - label: showEmote ? '文本' : '表情', - onPressed: () { - state - ..widget.focusNode.unfocus() - ..hideToolbar(); - showEmote = !showEmote; - (context as Element).markNeedsBuild(); - }, - ), - ); - if (showEmote) { - state.addLaunchMenuIfNeeded(buttonItems, index: 4); - } - } - if (!selection.isCollapsed) { - buttonItems.add( - ContextMenuButtonItem( - onPressed: () { - Navigator.of(context).pop(); - final value = state.textEditingValue; - String text = RegExp.escape( - selection.textInside(value.text), - ); - if (ReplyGrpc.enableFilter) text = '|$text'; - - showConfirmDialog( - context: context, - title: const Text('是否确认评论过滤的变更:'), - content: Text.rich( - TextSpan( - text: ReplyGrpc.replyRegExp.pattern, + child: SingleChildScrollView( + child: SelectionText.rich( + showEmote + ? TextSpan( + children: emotes.entries.mapIndexed( + (i, e) { + final emote = e.value; + final size = emote.size.toInt() * 25.0; + return TextSpan( children: [ - TextSpan( - text: text, - style: const TextStyle( - color: Colors.green, - fontWeight: .bold, + if (i != 0) const TextSpan(text: '\n\n'), + WidgetSpan( + child: NetworkImgLayer( + src: emote.url, + type: .emote, + width: size, + height: size, ), ), + TextSpan(text: '\n${e.key}\n${emote.url}'), ], - ), - ), - onConfirm: () { - final filter = ReplyGrpc.replyRegExp.pattern + text; - ReplyGrpc.replyRegExp = RegExp( - filter, - caseSensitive: true, ); - ReplyGrpc.enableFilter = true; - GStorage.setting.put( - SettingBoxKey.banWordForReply, - filter, - ); - SmartDialog.showToast('已保存'); }, - ); - }, - label: '加入过滤', - ), + ).toList(), + ) + : TextSpan(text: message), + contextMenuBuilder: (_, state) { + final buttonItems = state.contextMenuButtonItems; + if (emotes.isNotEmpty) { + buttonItems.insertOrAdd( + 3, + ContextMenuButtonItem( + label: showEmote ? '文本' : '表情', + onPressed: () { + state + ..hideToolbar() + ..clearSelection(); + showEmote = !showEmote; + (context as Element).markNeedsBuild(); + }, + ), + ); + if (showEmote) { + state.addLaunchMenuIfNeeded(buttonItems, index: 4); + } + } + try { + if (state.selectionEndpoints.first != + state.selectionEndpoints[1]) { + buttonItems.add( + ContextMenuButtonItem( + onPressed: () { + final selectedText = (state as dynamic).selectable + ?.getSelectedContent() + ?.plainText; + String text = RegExp.escape(selectedText); + if (ReplyGrpc.enableFilter) text = '|$text'; + + showConfirmDialog( + context: context, + title: const Text('是否确认评论过滤的变更:'), + content: Text.rich( + TextSpan( + text: ReplyGrpc.replyRegExp.pattern, + children: [ + TextSpan( + text: text, + style: const TextStyle( + color: Colors.green, + fontWeight: .bold, + ), + ), + ], + ), + ), + onConfirm: () { + final filter = ReplyGrpc.replyRegExp.pattern + text; + ReplyGrpc.replyRegExp = RegExp( + filter, + caseSensitive: true, + ); + ReplyGrpc.enableFilter = true; + GStorage.setting.put( + SettingBoxKey.banWordForReply, + filter, + ); + SmartDialog.showToast('已保存'); + }, + ); + }, + label: '加入过滤', + ), + ); + } + } catch (_) {} + return AdaptiveTextSelectionToolbar.buttonItems( + buttonItems: buttonItems, + anchors: state.contextMenuAnchors, ); - } - return AdaptiveTextSelectionToolbar.buttonItems( - buttonItems: buttonItems, - anchors: state.contextMenuAnchors, - ); - }, - style: const TextStyle(fontSize: 15, height: 1.7), + }, + style: const TextStyle(fontSize: 15, height: 1.7), + ), ), ), ), diff --git a/lib/common/widgets/image/image_save.dart b/lib/common/widgets/image/image_save.dart index 106e72163..6f86947dd 100644 --- a/lib/common/widgets/image/image_save.dart +++ b/lib/common/widgets/image/image_save.dart @@ -1,8 +1,7 @@ import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; @@ -72,10 +71,9 @@ void imageSaveDialog({ children: [ if (title != null) Expanded( - child: SelectableText( + child: SelectionText( title, style: theme.textTheme.titleSmall, - scrollPhysics: const NeverSelectableScrollPhysics(), ), ) else diff --git a/lib/common/widgets/loading_widget/http_error.dart b/lib/common/widgets/loading_widget/http_error.dart index dfcf62497..94282f6db 100644 --- a/lib/common/widgets/loading_widget/http_error.dart +++ b/lib/common/widgets/loading_widget/http_error.dart @@ -1,6 +1,5 @@ import 'package:PiliPlus/common/assets.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -24,20 +23,19 @@ class HttpError extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final child = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: .min, + mainAxisAlignment: .center, + crossAxisAlignment: .center, children: [ const SizedBox(height: 40), SvgPicture.asset(Assets.error, height: 200), const SizedBox(height: 30), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 5), - child: SelectableText( + padding: const .symmetric(horizontal: 16, vertical: 5), + child: SelectionText( errMsg ?? '没有数据', - textAlign: TextAlign.center, + textAlign: .center, style: theme.textTheme.titleSmall, - scrollPhysics: const NeverSelectableScrollPhysics(), ), ), if (onReload != null) diff --git a/lib/common/widgets/selection_text.dart b/lib/common/widgets/selection_text.dart new file mode 100644 index 000000000..8617ff5b1 --- /dev/null +++ b/lib/common/widgets/selection_text.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +class SelectionText extends StatelessWidget { + const SelectionText( + String this.data, { + super.key, + this.style, + this.textAlign, + this.contextMenuBuilder = _defaultContextMenuBuilder, + }) : textSpan = null; + + const SelectionText.rich( + InlineSpan this.textSpan, { + super.key, + this.style, + this.textAlign, + this.contextMenuBuilder = _defaultContextMenuBuilder, + }) : data = null; + + final String? data; + final InlineSpan? textSpan; + final TextStyle? style; + final TextAlign? textAlign; + final SelectableRegionContextMenuBuilder? contextMenuBuilder; + + static Widget _defaultContextMenuBuilder( + BuildContext context, + SelectableRegionState selectableRegionState, + ) { + return AdaptiveTextSelectionToolbar.selectableRegion( + selectableRegionState: selectableRegionState, + ); + } + + @override + Widget build(BuildContext context) { + return SelectionArea( + contextMenuBuilder: contextMenuBuilder, + child: Text.rich( + style: style, + textAlign: textAlign, + TextSpan( + text: data, + children: textSpan != null ? [textSpan!] : null, + ), + ), + ); + } +} diff --git a/lib/pages/audio/view.dart b/lib/pages/audio/view.dart index 296444267..31be044f5 100644 --- a/lib/pages/audio/view.dart +++ b/lib/pages/audio/view.dart @@ -9,8 +9,7 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_viewer/hero.dart'; import 'package:PiliPlus/common/widgets/progress_bar/audio_video_progress_bar.dart'; import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pb.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart'; import 'package:PiliPlus/models/common/image_type.dart'; @@ -938,10 +937,9 @@ class _AudioPageState extends State { ), ), const SizedBox(height: 12), - SelectableText( + SelectionText( audioItem.arc.title, style: const TextStyle(height: 1.7, fontSize: 16), - scrollPhysics: const NeverSelectableScrollPhysics(), ), const SizedBox(height: 12), if (audioItem.owner.hasName()) ...[ @@ -1004,10 +1002,7 @@ class _AudioPageState extends State { ), if (audioItem.arc.hasDesc()) ...[ const SizedBox(height: 10), - SelectableText( - audioItem.arc.desc, - scrollPhysics: const NeverSelectableScrollPhysics(), - ), + SelectionText(audioItem.arc.desc), ], ], ), diff --git a/lib/pages/dynamics/widgets/additional_panel.dart b/lib/pages/dynamics/widgets/additional_panel.dart index 6f6f7cd26..964d0c894 100644 --- a/lib/pages/dynamics/widgets/additional_panel.dart +++ b/lib/pages/dynamics/widgets/additional_panel.dart @@ -1,8 +1,7 @@ import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; @@ -693,14 +692,13 @@ Widget? addWidget( } } catch (e) { return Padding( - padding: const EdgeInsets.all(12), - child: SelectableText( + padding: const .all(12), + child: SelectionText( ''' additional panel error id: $idStr type: $type err: $e''', - scrollPhysics: const NeverSelectableScrollPhysics(), ), ); } diff --git a/lib/pages/dynamics/widgets/content_panel.dart b/lib/pages/dynamics/widgets/content_panel.dart index 7de980ff7..dc6dd1ea5 100644 --- a/lib/pages/dynamics/widgets/content_panel.dart +++ b/lib/pages/dynamics/widgets/content_panel.dart @@ -3,12 +3,11 @@ import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/flutter/text/text.dart' as custom_text; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_grid/image_grid_view.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show ClampingScrollPhysicsExt, NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/dynamics/widgets/rich_node_panel.dart'; -import 'package:PiliPlus/utils/extension/editable_text_ext.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; +import 'package:PiliPlus/utils/extension/selectable_region_ext.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; @@ -78,7 +77,7 @@ Widget content( ), if (richNodes != null) isDetail && floor == 1 - ? SelectableText.rich( + ? SelectionText.rich( richNodes, style: isSave ? const TextStyle(fontSize: 15) @@ -90,7 +89,6 @@ Widget content( text, item.modules.moduleDynamic, ), - scrollPhysics: const NeverSelectableScrollPhysics(), ) : custom_text.Text.rich( style: floor == 1 diff --git a/lib/pages/dynamics_topic/view.dart b/lib/pages/dynamics_topic/view.dart index 11feff1bc..863dea601 100644 --- a/lib/pages/dynamics_topic/view.dart +++ b/lib/pages/dynamics_topic/view.dart @@ -5,8 +5,7 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -260,10 +259,9 @@ class _DynTopicPageState extends State ), ), const SizedBox(height: 6), - SelectableText( + SelectionText( response.topicItem!.description!, style: TextStyle(color: colorScheme.onSurfaceVariant), - scrollPhysics: const NeverSelectableScrollPhysics(), ), const SizedBox(height: 10), Row( diff --git a/lib/pages/live_room/superchat/superchat_card.dart b/lib/pages/live_room/superchat/superchat_card.dart index 8e0b8c30d..614f731c6 100644 --- a/lib/pages/live_room/superchat/superchat_card.dart +++ b/lib/pages/live_room/superchat/superchat_card.dart @@ -1,8 +1,7 @@ import 'dart:async' show Timer; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; import 'package:PiliPlus/pages/member/widget/medal_widget.dart'; import 'package:PiliPlus/utils/color_utils.dart'; @@ -246,12 +245,11 @@ class _SuperChatCardState extends State { padding: const .all(8), child: TextSelectionTheme( data: TextSelectionThemeData( + selectionColor: Color.lerp(bottomColor, Colors.black, .26), selectionHandleColor: Color.lerp(bottomColor, Colors.white, .26), ), - child: SelectableText( + child: SelectionText( item.message, - scrollPhysics: const NeverSelectableScrollPhysics(), - selectionColor: Color.lerp(bottomColor, Colors.black, .26), style: TextStyle( color: ColourUtils.parseColor(item.messageFontColor), // decoration: widget.persistentSC && item.deleted diff --git a/lib/pages/member/widget/user_info_card.dart b/lib/pages/member/widget/user_info_card.dart index 710b668f0..45736a180 100644 --- a/lib/pages/member/widget/user_info_card.dart +++ b/lib/pages/member/widget/user_info_card.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/avatars.dart'; import 'package:PiliPlus/common/widgets/image_viewer/hero.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart'; import 'package:PiliPlus/models/common/member/user_info_type.dart'; @@ -311,10 +312,9 @@ class UserInfoCard extends StatelessWidget { Widget _buildSign() { return Padding( padding: const .only(left: 20, top: 6, right: 20), - child: SelectableText( + child: SelectionText( card.sign!.trim().replaceAll(RegExp(r'\n{2,}'), '\n'), style: const TextStyle(fontSize: 14), - scrollPhysics: const NeverSelectableScrollPhysics(), ), ); } diff --git a/lib/pages/music/view.dart b/lib/pages/music/view.dart index e42abf707..452c471b7 100644 --- a/lib/pages/music/view.dart +++ b/lib/pages/music/view.dart @@ -7,8 +7,7 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_viewer/hero.dart'; import 'package:PiliPlus/common/widgets/marquee.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_to_box_adapter.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/music.dart'; @@ -555,7 +554,7 @@ class _MusicDetailPageState extends CommonDynPageState { ], ), const SizedBox(height: 10), - SelectableText( + SelectionText( [ if (!(item.originArtist ?? item.originArtistList) .isNullOrEmpty) @@ -563,7 +562,6 @@ class _MusicDetailPageState extends CommonDynPageState { if (!item.album.isNullOrEmpty) '专辑:${item.album}', if (!item.musicSource.isNullOrEmpty) '出处:${item.musicSource}', ].join('\n'), - scrollPhysics: const NeverSelectableScrollPhysics(), ), const Divider(), Row( diff --git a/lib/pages/pgc_review/child/view.dart b/lib/pages/pgc_review/child/view.dart index 6fad23ef9..a4a5cc0e1 100644 --- a/lib/pages/pgc_review/child/view.dart +++ b/lib/pages/pgc_review/child/view.dart @@ -6,8 +6,7 @@ import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_floating_header.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/image_type.dart'; @@ -289,10 +288,9 @@ class _PgcReviewChildPageState extends State style: const TextStyle(height: 1.75), ) else - SelectableText( + SelectionText( item.content!, style: const TextStyle(height: 1.75), - scrollPhysics: const NeverSelectableScrollPhysics(), ), Builder( builder: (context) { diff --git a/lib/pages/setting/models/privacy_settings.dart b/lib/pages/setting/models/privacy_settings.dart index 3734b72bf..0c505d3e2 100644 --- a/lib/pages/setting/models/privacy_settings.dart +++ b/lib/pages/setting/models/privacy_settings.dart @@ -42,20 +42,22 @@ List get privacySettings => [ ]; Widget _getAccountDetail(BuildContext context) { - final slivers = []; + final children = []; final theme = TextTheme.of(context); for (final i in AccountType.values) { final url = ApiType.apiTypeSet[i]; if (url == null) continue; - slivers + children ..add(Center(child: Text(i.title, style: theme.titleMedium))) - ..add(SelectableText(url.join('\n'))); + ..add(Text(url.join('\n'))); } - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 8, - children: slivers, + return SelectionArea( + child: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + spacing: 8, + children: children, + ), ); } diff --git a/lib/pages/setting/pages/logs.dart b/lib/pages/setting/pages/logs.dart index b27744716..1ee3a591e 100644 --- a/lib/pages/setting/pages/logs.dart +++ b/lib/pages/setting/pages/logs.dart @@ -4,8 +4,7 @@ import 'dart:convert' show jsonDecode; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/services/logger.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; @@ -364,13 +363,12 @@ class _ReportCard extends StatelessWidget { borderRadius: const .all(.circular(8)), border: .all(color: colorScheme.outline.withValues(alpha: 0.5)), ), - child: SelectableText( + child: SelectionText( report.item.error.toString(), style: TextStyle( fontFamily: 'Monospace', color: colorScheme.onSurfaceVariant, ), - scrollPhysics: const NeverSelectableScrollPhysics(), ), ), // stackTrace may be null or String("null") or blank @@ -392,7 +390,7 @@ class _ReportCard extends StatelessWidget { borderRadius: const .all(.circular(8)), border: .all(color: colorScheme.outline.withValues(alpha: 0.5)), ), - child: SelectableText.rich( + child: SelectionText.rich( TextSpan( children: stackTrace .map( @@ -409,7 +407,6 @@ class _ReportCard extends StatelessWidget { .toList(), ), style: const TextStyle(fontFamily: 'Monospace', fontSize: 13), - scrollPhysics: const NeverSelectableScrollPhysics(), ), ), ], diff --git a/lib/pages/video/introduction/pgc/widgets/intro_detail.dart b/lib/pages/video/introduction/pgc/widgets/intro_detail.dart index 37bb04d29..45acf0ad3 100644 --- a/lib/pages/video/introduction/pgc/widgets/intro_detail.dart +++ b/lib/pages/video/introduction/pgc/widgets/intro_detail.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/models/common/stat_type.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart'; @@ -126,10 +127,9 @@ class _IntroDetailState extends State bottom: MediaQuery.viewPaddingOf(context).bottom + 100, ), children: [ - SelectableText( + SelectionText( widget.item.title!, style: const TextStyle(fontSize: 16), - scrollPhysics: const NeverSelectableScrollPhysics(), ), const SizedBox(height: 4), Row( @@ -171,10 +171,9 @@ class _IntroDetailState extends State style: theme.textTheme.titleMedium, ), const SizedBox(height: 4), - SelectableText( + SelectionText( widget.item.evaluate!, style: textStyle, - scrollPhysics: const NeverSelectableScrollPhysics(), ), ], if (widget.item.actors?.isNotEmpty == true) ...[ diff --git a/lib/pages/video/introduction/ugc/view.dart b/lib/pages/video/introduction/ugc/view.dart index d3ed3f4ef..e666a7e0c 100644 --- a/lib/pages/video/introduction/ugc/view.dart +++ b/lib/pages/video/introduction/ugc/view.dart @@ -8,7 +8,8 @@ import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show ReloadScrollPhysics, NeverSelectableScrollPhysics; + show ReloadScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/common/widgets/translucent_column.dart'; import 'package:PiliPlus/http/sponsor_block.dart'; @@ -284,10 +285,9 @@ class _UgcIntroPanelState extends State { ), if (videoDetail.descV2 case final descV2? when descV2.isNotEmpty) ...[ const SizedBox(height: 8), - SelectableText.rich( + SelectionText.rich( buildDesc(descV2), style: const TextStyle(height: 1.4), - scrollPhysics: const NeverSelectableScrollPhysics(), ), ], NoTranslucentArea( @@ -409,10 +409,9 @@ class _UgcIntroPanelState extends State { ], ); if (isSelectable) { - return SelectableText.rich( + return SelectionText.rich( textSpan, style: const TextStyle(fontSize: 16), - scrollPhysics: const NeverSelectableScrollPhysics(), ); } return Text.rich( diff --git a/lib/pages/video/reply/widgets/reply_item_grpc.dart b/lib/pages/video/reply/widgets/reply_item_grpc.dart index 8db5fa1a0..d0ed4ac10 100644 --- a/lib/pages/video/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/reply/widgets/reply_item_grpc.dart @@ -13,8 +13,7 @@ import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_grid/image_grid_view.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show ClampingScrollPhysicsExt; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo, ReplyControl, Content, Url, ReplyControl_VoteOption, Emote; import 'package:PiliPlus/grpc/reply.dart'; @@ -36,9 +35,9 @@ import 'package:PiliPlus/utils/danmaku_utils.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/extension/context_ext.dart'; -import 'package:PiliPlus/utils/extension/editable_text_ext.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; +import 'package:PiliPlus/utils/extension/selectable_region_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/global_data.dart'; diff --git a/lib/pages/webview/view.dart b/lib/pages/webview/view.dart index 12b0ddc56..dd8d18d50 100644 --- a/lib/pages/webview/view.dart +++ b/lib/pages/webview/view.dart @@ -1,7 +1,6 @@ import 'dart:io' show Platform; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/http/browser_ua.dart'; import 'package:PiliPlus/main.dart'; import 'package:PiliPlus/models/common/webview_menu_type.dart'; @@ -270,10 +269,7 @@ class _WebviewPageState extends State { '下载文件: $suggestedFilename ?', style: const TextStyle(fontSize: 18), ), - content: SelectableText( - request.url.toString(), - scrollPhysics: const NeverSelectableScrollPhysics(), - ), + content: SelectionText(request.url.toString()), actions: [ TextButton( onPressed: Get.back, diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index 432bd32b4..e502d2f58 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -7,8 +7,7 @@ import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_viewer/hero.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/grpc/bilibili/im/interfaces/v1.pb.dart' show EmotionInfo; import 'package:PiliPlus/grpc/bilibili/im/type.pb.dart' show Msg, MsgType; @@ -719,10 +718,7 @@ class ChatItem extends StatelessWidget { return ''; }, ); - return SelectableText.rich( - TextSpan(children: children), - scrollPhysics: const NeverSelectableScrollPhysics(), - ); + return SelectionText.rich(TextSpan(children: children)); } Widget msgTypeNotifyMsg_10(ThemeData theme, content) { @@ -764,10 +760,7 @@ class ChatItem extends StatelessWidget { ), Divider(color: theme.colorScheme.primary.withValues(alpha: 0.05)), if ((content['text'] as String?)?.isNotEmpty == true) - SelectableText( - content['text'], - scrollPhysics: const NeverSelectableScrollPhysics(), - ), + SelectionText(content['text']), if (modules != null && modules.isNotEmpty) ...[ const SizedBox(height: 4), ...modules.map( diff --git a/lib/scripts/editable_text.patch b/lib/scripts/editable_text.patch index 179925de4..b76817f0d 100644 --- a/lib/scripts/editable_text.patch +++ b/lib/scripts/editable_text.patch @@ -13,33 +13,3 @@ index b1ae878f2cc..8a7540350ef 100644 if (shouldShowCaret) { _scheduleShowCaretOnScreen(withAnimation: true); } -@@ -5709,7 +5711,7 @@ class EditableTextState extends State - PasteTextIntent: _makeOverridable(_PasteSelectionAction(this)), - - TransposeCharactersIntent: _makeOverridable(_transposeCharactersAction), -- EditableTextTapOutsideIntent: _makeOverridable(_EditableTextTapOutsideAction()), -+ EditableTextTapOutsideIntent: _makeOverridable(_EditableTextTapOutsideAction(this)), - EditableTextTapUpOutsideIntent: _makeOverridable(_EditableTextTapUpOutsideAction()), - }; - -@@ -6775,10 +6777,19 @@ class _WebClipboardStatusNotifier extends ClipboardStatusNotifier { - } - - class _EditableTextTapOutsideAction extends ContextAction { -- _EditableTextTapOutsideAction(); -+ _EditableTextTapOutsideAction(this.state); -+ -+ final EditableTextState state; - - @override - void invoke(EditableTextTapOutsideIntent intent, [BuildContext? context]) { -+ state.hideToolbar(); -+ final controller = state.widget.controller; -+ final selection = controller.value.selection; -+ if (!selection.isCollapsed) { -+ controller.selection = TextSelection.collapsed(offset: selection.end); -+ } -+ return; - // The focus dropping behavior is only present on desktop platforms. - switch (defaultTargetPlatform) { - case TargetPlatform.android: diff --git a/lib/scripts/selectable_region.patch b/lib/scripts/selectable_region.patch index 6653f48bf..f2649777f 100644 --- a/lib/scripts/selectable_region.patch +++ b/lib/scripts/selectable_region.patch @@ -1,8 +1,140 @@ +diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart +index 97a41dafb8d..70c55832625 100644 +--- a/packages/flutter/lib/src/rendering/paragraph.dart ++++ b/packages/flutter/lib/src/rendering/paragraph.dart +@@ -3568,7 +3568,10 @@ class _SelectableFragment + final selectionPaint = Paint() + ..style = PaintingStyle.fill + ..color = paragraph.selectionColor!; +- for (final TextBox textBox in paragraph.getBoxesForSelection(selection)) { ++ for (final TextBox textBox in paragraph.getBoxesForSelection( ++ selection, ++ boxHeightStyle: ui.BoxHeightStyle.max, ++ )) { + context.canvas.drawRect(textBox.toRect().shift(offset), selectionPaint); + } + } diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart -index 59de8bae20b..a1b6ea5651e 100644 +index 59de8bae20b..525bb76c064 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart -@@ -1957,6 +1957,8 @@ class SelectableRegionState extends State +@@ -404,6 +404,7 @@ class SelectableRegionState extends State + final StaticSelectionContainerDelegate _selectionDelegate = StaticSelectionContainerDelegate(); + // there should only ever be one selectable, which is the SelectionContainer. + Selectable? _selectable; ++ Selectable? get selectable => _selectable; + + bool get _hasSelectionOverlayGeometry => + _selectionDelegate.value.startSelectionPoint != null || +@@ -599,48 +600,32 @@ class SelectableRegionState extends State + // + // This method should be used in all instances when details.consecutiveTapCount + // would be used. +- int _getEffectiveConsecutiveTapCount(int rawCount) { +- var maxConsecutiveTap = 3; ++ static int _getEffectiveConsecutiveTapCount(int rawCount) { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: +- if (_lastPointerDeviceKind != null && _lastPointerDeviceKind != PointerDeviceKind.mouse) { +- // When the pointer device kind is not precise like a mouse, native +- // Android resets the tap count at 2. For example, this is so the +- // selection can collapse on the third tap. +- maxConsecutiveTap = 2; +- } +- // From observation, these platforms reset their tap count to 0 when +- // the number of consecutive taps exceeds the max consecutive tap supported. +- // For example on native Android, when going past a triple click, +- // on the fourth click the selection is moved to the precise click +- // position, on the fifth click the word at the position is selected, and +- // on the sixth click the paragraph at the position is selected. +- return rawCount <= maxConsecutiveTap +- ? rawCount +- : (rawCount % maxConsecutiveTap == 0 +- ? maxConsecutiveTap +- : rawCount % maxConsecutiveTap); + case TargetPlatform.linux: +- // From observation, these platforms reset their tap count to 0 when +- // the number of consecutive taps exceeds the max consecutive tap supported. +- // For example on Debian Linux with GTK, when going past a triple click, +- // on the fourth click the selection is moved to the precise click +- // position, on the fifth click the word at the position is selected, and +- // on the sixth click the paragraph at the position is selected. +- return rawCount <= maxConsecutiveTap +- ? rawCount +- : (rawCount % maxConsecutiveTap == 0 +- ? maxConsecutiveTap +- : rawCount % maxConsecutiveTap); ++ // From observation, these platform's reset their tap count to 0 when ++ // the number of consecutive taps exceeds 3. For example on Debian Linux ++ // with GTK, when going past a triple click, on the fourth click the ++ // selection is moved to the precise click position, on the fifth click ++ // the word at the position is selected, and on the sixth click the ++ // paragraph at the position is selected. ++ return rawCount <= 3 ? rawCount : (rawCount % 3 == 0 ? 3 : rawCount % 3); + case TargetPlatform.iOS: + case TargetPlatform.macOS: ++ // From observation, these platform's either hold their tap count at 3. ++ // For example on macOS, when going past a triple click, the selection ++ // should be retained at the paragraph that was first selected on triple ++ // click. ++ return min(rawCount, 3); + case TargetPlatform.windows: +- // From observation, these platforms hold their tap count at the max +- // consecutive tap supported. For example on macOS, when going past a triple +- // click, the selection should be retained at the paragraph that was first +- // selected on triple click. +- return min(rawCount, maxConsecutiveTap); ++ // From observation, this platform's consecutive tap actions alternate ++ // between double click and triple click actions. For example, after a ++ // triple click has selected a paragraph, on the next click the word at ++ // the clicked position will be selected, and on the next click the ++ // paragraph at the position is selected. ++ return rawCount < 2 ? rawCount : 2 + rawCount % 2; + } + } + +@@ -782,22 +767,8 @@ class SelectableRegionState extends State + _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; + } + case 3: +- switch (defaultTargetPlatform) { +- case TargetPlatform.android: +- case TargetPlatform.fuchsia: +- case TargetPlatform.iOS: +- if (details.kind != null && _isPrecisePointerDevice(details.kind!)) { +- // Triple tap on static text is only supported on mobile +- // platforms using a precise pointer device. +- _selectParagraphAt(offset: details.globalPosition); +- _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; +- } +- case TargetPlatform.macOS: +- case TargetPlatform.linux: +- case TargetPlatform.windows: +- _selectParagraphAt(offset: details.globalPosition); +- _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; +- } ++ _selectParagraphAt(offset: details.globalPosition); ++ _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; + } + _updateSelectedContentIfNeeded(); + } +@@ -1049,10 +1020,10 @@ class SelectableRegionState extends State + case TargetPlatform.windows: + // If _lastSecondaryTapDownPosition is within the current selection then + // keep the current selection, if not then collapse it. +- final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection( +- globalPosition: details.globalPosition, +- ); +- if (lastSecondaryTapDownPositionWasOnActiveSelection) { ++ // final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection( ++ // globalPosition: details.globalPosition, ++ // ); ++ if (_selectionDelegate.value.selectionRects.isNotEmpty) { + // Restore _lastSecondaryTapDownPosition since it may be cleared if a user + // accesses contextMenuAnchors. + _lastSecondaryTapDownPosition = details.globalPosition; +@@ -1957,6 +1928,8 @@ class SelectableRegionState extends State // the region on non-web platforms. if (kIsWeb) { _focusNode.unfocus(); @@ -11,7 +143,7 @@ index 59de8bae20b..a1b6ea5651e 100644 } }, child: CompositedTransformTarget( -@@ -2822,7 +2824,10 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai +@@ -2822,7 +2795,10 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai return null; } final buffer = StringBuffer(); diff --git a/lib/utils/extension/editable_text_ext.dart b/lib/utils/extension/editable_text_ext.dart deleted file mode 100644 index 69b44c953..000000000 --- a/lib/utils/extension/editable_text_ext.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:PiliPlus/utils/extension/iterable_ext.dart'; -import 'package:PiliPlus/utils/page_utils.dart'; -import 'package:flutter/material.dart'; - -extension EditableTextExt on EditableTextState { - void addLaunchMenuIfNeeded( - List buttonItems, { - required int index, - }) { - final selection = textEditingValue.selection; - if (!selection.isCollapsed) { - buttonItems.insertOrAdd( - index, - ContextMenuButtonItem( - label: '打开', - onPressed: () { - hideToolbar(); - clearSelection(selection); - PageUtils.launchURL( - selection.textInside(textEditingValue.text).trim(), - ); - }, - ), - ); - } - } - - void clearSelection(TextSelection currSelection) { - widget.controller.selection = .collapsed(offset: currSelection.end); - } - - void clearSelectionIfNeeded() { - final selection = textEditingValue.selection; - if (!selection.isCollapsed) { - clearSelection(selection); - } - } -} diff --git a/lib/utils/extension/selectable_region_ext.dart b/lib/utils/extension/selectable_region_ext.dart new file mode 100644 index 000000000..5c0dc2227 --- /dev/null +++ b/lib/utils/extension/selectable_region_ext.dart @@ -0,0 +1,32 @@ +import 'package:PiliPlus/utils/extension/iterable_ext.dart'; +import 'package:PiliPlus/utils/page_utils.dart'; +import 'package:flutter/material.dart'; + +extension SelectableRegionStateExt on SelectableRegionState { + void addLaunchMenuIfNeeded( + List buttonItems, { + required int index, + }) { + try { + if (selectionEndpoints.first != selectionEndpoints[1]) { + buttonItems.insertOrAdd( + index, + ContextMenuButtonItem( + label: '打开', + onPressed: () { + final text = (this as dynamic).selectable + ?.getSelectedContent() + ?.plainText + .trim(); + hideToolbar(); + clearSelection(); + if (text != null && text.isNotEmpty) { + PageUtils.launchURL(text); + } + }, + ), + ); + } + } catch (_) {} + } +} diff --git a/lib/utils/reply_utils.dart b/lib/utils/reply_utils.dart index 94448656c..926307f63 100644 --- a/lib/utils/reply_utils.dart +++ b/lib/utils/reply_utils.dart @@ -1,7 +1,6 @@ import 'dart:io' show Platform; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo; import 'package:PiliPlus/http/loading_state.dart'; @@ -139,10 +138,7 @@ abstract final class ReplyUtils { barrierDismissible: isManual, builder: (context) => AlertDialog( title: const Text('评论检查结果'), - content: SelectableText( - message, - scrollPhysics: const NeverSelectableScrollPhysics(), - ), + content: SelectionText(message), actions: actions.isEmpty ? null : actions, ), ); diff --git a/lib/utils/request_utils.dart b/lib/utils/request_utils.dart index 18d643d47..0c9df35a6 100644 --- a/lib/utils/request_utils.dart +++ b/lib/utils/request_utils.dart @@ -3,8 +3,7 @@ import 'dart:math'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; -import 'package:PiliPlus/common/widgets/scroll_physics.dart' - show NeverSelectableScrollPhysics; +import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/grpc/bilibili/im/type.pbenum.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo; @@ -362,9 +361,8 @@ abstract final class RequestUtils { barrierDismissible: isManual, builder: (context) => AlertDialog( title: const Text('动态检查结果'), - content: SelectableText( + content: SelectionText( '${isSuccess ? '无账号状态下找到了你的动态,动态正常!' : '你的动态被shadow ban(仅自己可见)!'}${dynText != null ? ' \n\n动态内容: $dynText' : ''}', - scrollPhysics: const NeverSelectableScrollPhysics(), ), actions: actions.isEmpty ? null : actions, ), @@ -580,9 +578,8 @@ abstract final class RequestUtils { showDialog( context: Get.context!, builder: (context) => AlertDialog( - title: SelectableText( + title: SelectionText( show ? response.name! : response.rejectPage?.title ?? '', - scrollPhysics: const NeverSelectableScrollPhysics(), ), content: show ? null : Text(response.rejectPage?.text ?? ''), actions: [