mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-22 19:40:10 +08:00
replace SelectableText with SelectionArea
Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
49
lib/common/widgets/selection_text.dart
Normal file
49
lib/common/widgets/selection_text.dart
Normal file
@@ -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 ? <InlineSpan>[textSpan!] : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user