mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-29 06:50:12 +08:00
File diff suppressed because it is too large
Load Diff
@@ -1,903 +0,0 @@
|
|||||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
|
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/text_selection.dart';
|
|
||||||
import 'package:flutter/cupertino.dart'
|
|
||||||
hide TextSelectionGestureDetectorBuilder;
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart'
|
|
||||||
hide SelectableText, TextSelectionGestureDetectorBuilder;
|
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
import 'package:flutter/scheduler.dart';
|
|
||||||
|
|
||||||
class _TextSpanEditingController extends TextEditingController {
|
|
||||||
_TextSpanEditingController({required TextSpan textSpan})
|
|
||||||
: _textSpan = textSpan,
|
|
||||||
super(text: textSpan.toPlainText(includeSemanticsLabels: false));
|
|
||||||
|
|
||||||
final TextSpan _textSpan;
|
|
||||||
|
|
||||||
@override
|
|
||||||
TextSpan buildTextSpan({
|
|
||||||
required BuildContext context,
|
|
||||||
TextStyle? style,
|
|
||||||
required bool withComposing,
|
|
||||||
}) {
|
|
||||||
// This does not care about composing.
|
|
||||||
return TextSpan(style: style, children: <TextSpan>[_textSpan]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
set text(String? newText) {
|
|
||||||
// This should never be reached.
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SelectableTextSelectionGestureDetectorBuilder
|
|
||||||
extends CustomTextSelectionGestureDetectorBuilder {
|
|
||||||
_SelectableTextSelectionGestureDetectorBuilder({
|
|
||||||
required _SelectableTextState state,
|
|
||||||
}) : _state = state,
|
|
||||||
super(delegate: state);
|
|
||||||
|
|
||||||
final _SelectableTextState _state;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onSingleTapUp(TapDragUpDetails details) {
|
|
||||||
if (!delegate.selectionEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
super.onSingleTapUp(details);
|
|
||||||
_state.widget.onTap?.call();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A run of selectable text with a single style.
|
|
||||||
///
|
|
||||||
/// Consider using [SelectionArea] or [SelectableRegion] instead, which enable
|
|
||||||
/// selection on a widget subtree, including but not limited to [Text] widgets.
|
|
||||||
///
|
|
||||||
/// The [SelectableText] widget displays a string of text with a single style.
|
|
||||||
/// The string might break across multiple lines or might all be displayed on
|
|
||||||
/// the same line depending on the layout constraints.
|
|
||||||
///
|
|
||||||
/// {@youtube 560 315 https://www.youtube.com/watch?v=ZSU3ZXOs6hc}
|
|
||||||
///
|
|
||||||
/// The [style] argument is optional. When omitted, the text will use the style
|
|
||||||
/// from the closest enclosing [DefaultTextStyle]. If the given style's
|
|
||||||
/// [TextStyle.inherit] property is true (the default), the given style will
|
|
||||||
/// be merged with the closest enclosing [DefaultTextStyle]. This merging
|
|
||||||
/// behavior is useful, for example, to make the text bold while using the
|
|
||||||
/// default font family and size.
|
|
||||||
///
|
|
||||||
/// {@macro flutter.material.textfield.wantKeepAlive}
|
|
||||||
///
|
|
||||||
/// {@tool snippet}
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// const SelectableText(
|
|
||||||
/// 'Hello! How are you?',
|
|
||||||
/// textAlign: TextAlign.center,
|
|
||||||
/// style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
/// {@end-tool}
|
|
||||||
///
|
|
||||||
/// Using the [SelectableText.rich] constructor, the [SelectableText] widget can
|
|
||||||
/// display a paragraph with differently styled [TextSpan]s. The sample
|
|
||||||
/// that follows displays "Hello beautiful world" with different styles
|
|
||||||
/// for each word.
|
|
||||||
///
|
|
||||||
/// {@tool snippet}
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// const SelectableText.rich(
|
|
||||||
/// TextSpan(
|
|
||||||
/// text: 'Hello', // default text style
|
|
||||||
/// children: <TextSpan>[
|
|
||||||
/// TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic)),
|
|
||||||
/// TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)),
|
|
||||||
/// ],
|
|
||||||
/// ),
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
/// {@end-tool}
|
|
||||||
///
|
|
||||||
/// ## Interactivity
|
|
||||||
///
|
|
||||||
/// To make [SelectableText] react to touch events, use callback [onTap] to achieve
|
|
||||||
/// the desired behavior.
|
|
||||||
///
|
|
||||||
/// ## Scrolling Considerations
|
|
||||||
///
|
|
||||||
/// If this [SelectableText] is not a descendant of [Scaffold] and is being used
|
|
||||||
/// within a [Scrollable] or nested [Scrollable]s, consider placing a
|
|
||||||
/// [ScrollNotificationObserver] above the root [Scrollable] that contains this
|
|
||||||
/// [SelectableText] to ensure proper scroll coordination for [SelectableText]
|
|
||||||
/// and its components like [TextSelectionOverlay].
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [Text], which is the non selectable version of this widget.
|
|
||||||
/// * [TextField], which is the editable version of this widget.
|
|
||||||
/// * [SelectionArea], which enables the selection of multiple [Text] widgets
|
|
||||||
/// and of other widgets.
|
|
||||||
class SelectableText extends StatefulWidget {
|
|
||||||
/// Creates a selectable text widget.
|
|
||||||
///
|
|
||||||
/// If the [style] argument is null, the text will use the style from the
|
|
||||||
/// closest enclosing [DefaultTextStyle].
|
|
||||||
///
|
|
||||||
|
|
||||||
/// If the [showCursor], [autofocus], [dragStartBehavior],
|
|
||||||
/// [selectionHeightStyle], [selectionWidthStyle] and [data] arguments are
|
|
||||||
/// specified, the [maxLines] argument must be greater than zero.
|
|
||||||
const SelectableText(
|
|
||||||
String this.data, {
|
|
||||||
super.key,
|
|
||||||
this.focusNode,
|
|
||||||
this.style,
|
|
||||||
this.strutStyle,
|
|
||||||
this.textAlign,
|
|
||||||
this.textDirection,
|
|
||||||
@Deprecated(
|
|
||||||
'Use textScaler instead. '
|
|
||||||
'Use of textScaleFactor was deprecated in preparation for the upcoming nonlinear text scaling support. '
|
|
||||||
'This feature was deprecated after v3.12.0-2.0.pre.',
|
|
||||||
)
|
|
||||||
this.textScaleFactor,
|
|
||||||
this.textScaler,
|
|
||||||
this.showCursor = false,
|
|
||||||
this.autofocus = false,
|
|
||||||
@Deprecated(
|
|
||||||
'Use `contextMenuBuilder` instead. '
|
|
||||||
'This feature was deprecated after v3.3.0-0.5.pre.',
|
|
||||||
)
|
|
||||||
this.toolbarOptions,
|
|
||||||
this.minLines,
|
|
||||||
this.maxLines,
|
|
||||||
this.cursorWidth = 2.0,
|
|
||||||
this.cursorHeight,
|
|
||||||
this.cursorRadius,
|
|
||||||
this.cursorColor,
|
|
||||||
this.selectionColor,
|
|
||||||
this.selectionHeightStyle,
|
|
||||||
this.selectionWidthStyle,
|
|
||||||
this.dragStartBehavior = DragStartBehavior.start,
|
|
||||||
this.enableInteractiveSelection = true,
|
|
||||||
this.selectionControls,
|
|
||||||
this.onTap,
|
|
||||||
this.scrollPhysics,
|
|
||||||
this.scrollBehavior,
|
|
||||||
this.semanticsLabel,
|
|
||||||
this.textHeightBehavior,
|
|
||||||
this.textWidthBasis,
|
|
||||||
this.onSelectionChanged,
|
|
||||||
this.contextMenuBuilder = _defaultContextMenuBuilder,
|
|
||||||
this.magnifierConfiguration,
|
|
||||||
}) : assert(maxLines == null || maxLines > 0),
|
|
||||||
assert(minLines == null || minLines > 0),
|
|
||||||
assert(
|
|
||||||
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
|
|
||||||
"minLines can't be greater than maxLines",
|
|
||||||
),
|
|
||||||
assert(
|
|
||||||
textScaler == null || textScaleFactor == null,
|
|
||||||
'textScaleFactor is deprecated and cannot be specified when textScaler is specified.',
|
|
||||||
),
|
|
||||||
textSpan = null;
|
|
||||||
|
|
||||||
/// Creates a selectable text widget with a [TextSpan].
|
|
||||||
///
|
|
||||||
/// The [TextSpan.children] attribute of the [textSpan] parameter must only
|
|
||||||
/// contain [TextSpan]s. Other types of [InlineSpan] are not allowed.
|
|
||||||
const SelectableText.rich(
|
|
||||||
TextSpan this.textSpan, {
|
|
||||||
super.key,
|
|
||||||
this.focusNode,
|
|
||||||
this.style,
|
|
||||||
this.strutStyle,
|
|
||||||
this.textAlign,
|
|
||||||
this.textDirection,
|
|
||||||
@Deprecated(
|
|
||||||
'Use textScaler instead. '
|
|
||||||
'Use of textScaleFactor was deprecated in preparation for the upcoming nonlinear text scaling support. '
|
|
||||||
'This feature was deprecated after v3.12.0-2.0.pre.',
|
|
||||||
)
|
|
||||||
this.textScaleFactor,
|
|
||||||
this.textScaler,
|
|
||||||
this.showCursor = false,
|
|
||||||
this.autofocus = false,
|
|
||||||
@Deprecated(
|
|
||||||
'Use `contextMenuBuilder` instead. '
|
|
||||||
'This feature was deprecated after v3.3.0-0.5.pre.',
|
|
||||||
)
|
|
||||||
this.toolbarOptions,
|
|
||||||
this.minLines,
|
|
||||||
this.maxLines,
|
|
||||||
this.cursorWidth = 2.0,
|
|
||||||
this.cursorHeight,
|
|
||||||
this.cursorRadius,
|
|
||||||
this.cursorColor,
|
|
||||||
this.selectionColor,
|
|
||||||
this.selectionHeightStyle,
|
|
||||||
this.selectionWidthStyle,
|
|
||||||
this.dragStartBehavior = DragStartBehavior.start,
|
|
||||||
this.enableInteractiveSelection = true,
|
|
||||||
this.selectionControls,
|
|
||||||
this.onTap,
|
|
||||||
this.scrollPhysics,
|
|
||||||
this.scrollBehavior,
|
|
||||||
this.semanticsLabel,
|
|
||||||
this.textHeightBehavior,
|
|
||||||
this.textWidthBasis,
|
|
||||||
this.onSelectionChanged,
|
|
||||||
this.contextMenuBuilder = _defaultContextMenuBuilder,
|
|
||||||
this.magnifierConfiguration,
|
|
||||||
}) : assert(maxLines == null || maxLines > 0),
|
|
||||||
assert(minLines == null || minLines > 0),
|
|
||||||
assert(
|
|
||||||
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
|
|
||||||
"minLines can't be greater than maxLines",
|
|
||||||
),
|
|
||||||
assert(
|
|
||||||
textScaler == null || textScaleFactor == null,
|
|
||||||
'textScaleFactor is deprecated and cannot be specified when textScaler is specified.',
|
|
||||||
),
|
|
||||||
data = null;
|
|
||||||
|
|
||||||
/// The text to display.
|
|
||||||
///
|
|
||||||
/// This will be null if a [textSpan] is provided instead.
|
|
||||||
final String? data;
|
|
||||||
|
|
||||||
/// The text to display as a [TextSpan].
|
|
||||||
///
|
|
||||||
/// This will be null if [data] is provided instead.
|
|
||||||
final TextSpan? textSpan;
|
|
||||||
|
|
||||||
/// Defines the focus for this widget.
|
|
||||||
///
|
|
||||||
/// Text is only selectable when widget is focused.
|
|
||||||
///
|
|
||||||
/// The [focusNode] is a long-lived object that's typically managed by a
|
|
||||||
/// [StatefulWidget] parent. See [FocusNode] for more information.
|
|
||||||
///
|
|
||||||
/// To give the focus to this widget, provide a [focusNode] and then
|
|
||||||
/// use the current [FocusScope] to request the focus:
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// FocusScope.of(context).requestFocus(myFocusNode);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// This happens automatically when the widget is tapped.
|
|
||||||
///
|
|
||||||
/// To be notified when the widget gains or loses the focus, add a listener
|
|
||||||
/// to the [focusNode]:
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// myFocusNode.addListener(() { print(myFocusNode.hasFocus); });
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// If null, this widget will create its own [FocusNode] with
|
|
||||||
/// [FocusNode.skipTraversal] parameter set to `true`, which causes the widget
|
|
||||||
/// to be skipped over during focus traversal.
|
|
||||||
final FocusNode? focusNode;
|
|
||||||
|
|
||||||
/// The style to use for the text.
|
|
||||||
///
|
|
||||||
/// If null, defaults [DefaultTextStyle] of context.
|
|
||||||
final TextStyle? style;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.strutStyle}
|
|
||||||
final StrutStyle? strutStyle;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.textAlign}
|
|
||||||
final TextAlign? textAlign;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.textDirection}
|
|
||||||
final TextDirection? textDirection;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.textScaleFactor}
|
|
||||||
@Deprecated(
|
|
||||||
'Use textScaler instead. '
|
|
||||||
'Use of textScaleFactor was deprecated in preparation for the upcoming nonlinear text scaling support. '
|
|
||||||
'This feature was deprecated after v3.12.0-2.0.pre.',
|
|
||||||
)
|
|
||||||
final double? textScaleFactor;
|
|
||||||
|
|
||||||
/// {@macro flutter.painting.textPainter.textScaler}
|
|
||||||
final TextScaler? textScaler;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.autofocus}
|
|
||||||
final bool autofocus;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.minLines}
|
|
||||||
final int? minLines;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.maxLines}
|
|
||||||
final int? maxLines;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.showCursor}
|
|
||||||
final bool showCursor;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.cursorWidth}
|
|
||||||
final double cursorWidth;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.cursorHeight}
|
|
||||||
final double? cursorHeight;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.cursorRadius}
|
|
||||||
final Radius? cursorRadius;
|
|
||||||
|
|
||||||
/// The color of the cursor.
|
|
||||||
///
|
|
||||||
/// The cursor indicates the current text insertion point.
|
|
||||||
///
|
|
||||||
/// If null then [DefaultSelectionStyle.cursorColor] is used. If that is also
|
|
||||||
/// null and [ThemeData.platform] is [TargetPlatform.iOS] or
|
|
||||||
/// [TargetPlatform.macOS], then [CupertinoThemeData.primaryColor] is used.
|
|
||||||
/// Otherwise [ColorScheme.primary] of [ThemeData.colorScheme] is used.
|
|
||||||
final Color? cursorColor;
|
|
||||||
|
|
||||||
/// The color to use when painting the selection.
|
|
||||||
///
|
|
||||||
/// If this property is null, this widget gets the selection color from the
|
|
||||||
/// inherited [DefaultSelectionStyle] (if any); if none, the selection
|
|
||||||
/// color is derived from the [CupertinoThemeData.primaryColor] on
|
|
||||||
/// Apple platforms and [ColorScheme.primary] of [ThemeData.colorScheme] on
|
|
||||||
/// other platforms.
|
|
||||||
final Color? selectionColor;
|
|
||||||
|
|
||||||
/// Controls how tall the selection highlight boxes are computed to be.
|
|
||||||
///
|
|
||||||
/// See [ui.BoxHeightStyle] for details on available styles.
|
|
||||||
final ui.BoxHeightStyle? selectionHeightStyle;
|
|
||||||
|
|
||||||
/// Controls how wide the selection highlight boxes are computed to be.
|
|
||||||
///
|
|
||||||
/// See [ui.BoxWidthStyle] for details on available styles.
|
|
||||||
final ui.BoxWidthStyle? selectionWidthStyle;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.enableInteractiveSelection}
|
|
||||||
final bool enableInteractiveSelection;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.selectionControls}
|
|
||||||
final TextSelectionControls? selectionControls;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
|
|
||||||
final DragStartBehavior dragStartBehavior;
|
|
||||||
|
|
||||||
/// Configuration of toolbar options.
|
|
||||||
///
|
|
||||||
/// Paste and cut will be disabled regardless.
|
|
||||||
///
|
|
||||||
/// If not set, select all and copy will be enabled by default.
|
|
||||||
@Deprecated(
|
|
||||||
'Use `contextMenuBuilder` instead. '
|
|
||||||
'This feature was deprecated after v3.3.0-0.5.pre.',
|
|
||||||
)
|
|
||||||
final ToolbarOptions? toolbarOptions;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.selectionEnabled}
|
|
||||||
bool get selectionEnabled => enableInteractiveSelection;
|
|
||||||
|
|
||||||
/// Called when the user taps on this selectable text.
|
|
||||||
///
|
|
||||||
/// The selectable text builds a [GestureDetector] to handle input events like tap,
|
|
||||||
/// to trigger focus requests, to move the caret, adjust the selection, etc.
|
|
||||||
/// Handling some of those events by wrapping the selectable text with a competing
|
|
||||||
/// GestureDetector is problematic.
|
|
||||||
///
|
|
||||||
/// To unconditionally handle taps, without interfering with the selectable text's
|
|
||||||
/// internal gesture detector, provide this callback.
|
|
||||||
///
|
|
||||||
/// To be notified when the text field gains or loses the focus, provide a
|
|
||||||
/// [focusNode] and add a listener to that.
|
|
||||||
///
|
|
||||||
/// To listen to arbitrary pointer events without competing with the
|
|
||||||
/// selectable text's internal gesture detector, use a [Listener].
|
|
||||||
final GestureTapCallback? onTap;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.scrollPhysics}
|
|
||||||
final ScrollPhysics? scrollPhysics;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.scrollBehavior}
|
|
||||||
final ScrollBehavior? scrollBehavior;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.Text.semanticsLabel}
|
|
||||||
final String? semanticsLabel;
|
|
||||||
|
|
||||||
/// {@macro dart.ui.textHeightBehavior}
|
|
||||||
final TextHeightBehavior? textHeightBehavior;
|
|
||||||
|
|
||||||
/// {@macro flutter.painting.textPainter.textWidthBasis}
|
|
||||||
final TextWidthBasis? textWidthBasis;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.onSelectionChanged}
|
|
||||||
final SelectionChangedCallback? onSelectionChanged;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.EditableText.contextMenuBuilder}
|
|
||||||
final EditableTextContextMenuBuilder? contextMenuBuilder;
|
|
||||||
|
|
||||||
static Widget _defaultContextMenuBuilder(
|
|
||||||
BuildContext context,
|
|
||||||
EditableTextState editableTextState,
|
|
||||||
) {
|
|
||||||
return AdaptiveTextSelectionToolbar.editableText(
|
|
||||||
editableTextState: editableTextState,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The configuration for the magnifier used when the text is selected.
|
|
||||||
///
|
|
||||||
/// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier]
|
|
||||||
/// on Android, and builds nothing on all other platforms. To suppress the
|
|
||||||
/// magnifier, consider passing [TextMagnifierConfiguration.disabled].
|
|
||||||
///
|
|
||||||
/// {@macro flutter.widgets.magnifier.intro}
|
|
||||||
final TextMagnifierConfiguration? magnifierConfiguration;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<SelectableText> createState() => _SelectableTextState();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
||||||
super.debugFillProperties(properties);
|
|
||||||
properties
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<String>('data', data, defaultValue: null),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<String>(
|
|
||||||
'semanticsLabel',
|
|
||||||
semanticsLabel,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<FocusNode>(
|
|
||||||
'focusNode',
|
|
||||||
focusNode,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<TextStyle>('style', style, defaultValue: null),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<bool>(
|
|
||||||
'showCursor',
|
|
||||||
showCursor,
|
|
||||||
defaultValue: false,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(IntProperty('minLines', minLines, defaultValue: null))
|
|
||||||
..add(IntProperty('maxLines', maxLines, defaultValue: null))
|
|
||||||
..add(
|
|
||||||
EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
EnumProperty<TextDirection>(
|
|
||||||
'textDirection',
|
|
||||||
textDirection,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<TextScaler>(
|
|
||||||
'textScaler',
|
|
||||||
textScaler,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DoubleProperty('cursorHeight', cursorHeight, defaultValue: null),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<Radius>(
|
|
||||||
'cursorRadius',
|
|
||||||
cursorRadius,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<Color>(
|
|
||||||
'cursorColor',
|
|
||||||
cursorColor,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<Color>(
|
|
||||||
'selectionColor',
|
|
||||||
selectionColor,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
FlagProperty(
|
|
||||||
'selectionEnabled',
|
|
||||||
value: selectionEnabled,
|
|
||||||
defaultValue: true,
|
|
||||||
ifFalse: 'selection disabled',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<TextSelectionControls>(
|
|
||||||
'selectionControls',
|
|
||||||
selectionControls,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<ScrollPhysics>(
|
|
||||||
'scrollPhysics',
|
|
||||||
scrollPhysics,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<ScrollBehavior>(
|
|
||||||
'scrollBehavior',
|
|
||||||
scrollBehavior,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..add(
|
|
||||||
DiagnosticsProperty<TextHeightBehavior>(
|
|
||||||
'textHeightBehavior',
|
|
||||||
textHeightBehavior,
|
|
||||||
defaultValue: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SelectableTextState extends State<SelectableText>
|
|
||||||
implements TextSelectionGestureDetectorBuilderDelegate {
|
|
||||||
EditableTextState? get _editableText => editableTextKey.currentState;
|
|
||||||
|
|
||||||
late _TextSpanEditingController _controller;
|
|
||||||
|
|
||||||
FocusNode? _focusNode;
|
|
||||||
FocusNode get _effectiveFocusNode =>
|
|
||||||
widget.focusNode ?? (_focusNode ??= FocusNode(skipTraversal: true));
|
|
||||||
|
|
||||||
bool _showSelectionHandles = false;
|
|
||||||
|
|
||||||
late _SelectableTextSelectionGestureDetectorBuilder
|
|
||||||
_selectionGestureDetectorBuilder;
|
|
||||||
|
|
||||||
// API for TextSelectionGestureDetectorBuilderDelegate.
|
|
||||||
@override
|
|
||||||
late bool forcePressEnabled;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final GlobalKey<EditableTextState> editableTextKey =
|
|
||||||
GlobalKey<EditableTextState>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get selectionEnabled => widget.selectionEnabled;
|
|
||||||
// End of API for TextSelectionGestureDetectorBuilderDelegate.
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_selectionGestureDetectorBuilder =
|
|
||||||
_SelectableTextSelectionGestureDetectorBuilder(state: this);
|
|
||||||
_controller = _TextSpanEditingController(
|
|
||||||
textSpan: widget.textSpan ?? TextSpan(text: widget.data),
|
|
||||||
);
|
|
||||||
_controller.addListener(_onControllerChanged);
|
|
||||||
_effectiveFocusNode.addListener(_handleFocusChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(SelectableText oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (widget.data != oldWidget.data ||
|
|
||||||
widget.textSpan != oldWidget.textSpan) {
|
|
||||||
_controller
|
|
||||||
..removeListener(_onControllerChanged)
|
|
||||||
..dispose();
|
|
||||||
_controller = _TextSpanEditingController(
|
|
||||||
textSpan: widget.textSpan ?? TextSpan(text: widget.data),
|
|
||||||
);
|
|
||||||
_controller.addListener(_onControllerChanged);
|
|
||||||
}
|
|
||||||
if (widget.focusNode != oldWidget.focusNode) {
|
|
||||||
(oldWidget.focusNode ?? _focusNode)?.removeListener(_handleFocusChanged);
|
|
||||||
(widget.focusNode ?? _focusNode)?.addListener(_handleFocusChanged);
|
|
||||||
}
|
|
||||||
if (_effectiveFocusNode.hasFocus && _controller.selection.isCollapsed) {
|
|
||||||
_showSelectionHandles = false;
|
|
||||||
} else {
|
|
||||||
_showSelectionHandles = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_effectiveFocusNode.removeListener(_handleFocusChanged);
|
|
||||||
_focusNode?.dispose();
|
|
||||||
_controller.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onControllerChanged() {
|
|
||||||
final bool showSelectionHandles =
|
|
||||||
!_effectiveFocusNode.hasFocus || !_controller.selection.isCollapsed;
|
|
||||||
if (showSelectionHandles == _showSelectionHandles) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_showSelectionHandles = showSelectionHandles;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleFocusChanged() {
|
|
||||||
if (!_effectiveFocusNode.hasFocus &&
|
|
||||||
SchedulerBinding.instance.lifecycleState == AppLifecycleState.resumed) {
|
|
||||||
// We should only clear the selection when this SelectableText loses
|
|
||||||
// focus while the application is currently running. It is possible
|
|
||||||
// that the application is not currently running, for example on desktop
|
|
||||||
// platforms, clicking on a different window switches the focus to
|
|
||||||
// the new window causing the Flutter application to go inactive. In this
|
|
||||||
// case we want to retain the selection so it remains when we return to
|
|
||||||
// the Flutter application.
|
|
||||||
_controller.value = TextEditingValue(text: _controller.value.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleSelectionChanged(
|
|
||||||
TextSelection selection,
|
|
||||||
SelectionChangedCause? cause,
|
|
||||||
) {
|
|
||||||
final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause);
|
|
||||||
if (willShowSelectionHandles != _showSelectionHandles) {
|
|
||||||
setState(() {
|
|
||||||
_showSelectionHandles = willShowSelectionHandles;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
widget.onSelectionChanged?.call(selection, cause);
|
|
||||||
|
|
||||||
switch (Theme.of(context).platform) {
|
|
||||||
case TargetPlatform.iOS:
|
|
||||||
case TargetPlatform.macOS:
|
|
||||||
if (cause == SelectionChangedCause.longPress) {
|
|
||||||
_editableText?.bringIntoView(selection.base);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case TargetPlatform.android:
|
|
||||||
case TargetPlatform.fuchsia:
|
|
||||||
case TargetPlatform.linux:
|
|
||||||
case TargetPlatform.windows:
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle the toolbar when a selection handle is tapped.
|
|
||||||
void _handleSelectionHandleTapped() {
|
|
||||||
if (_controller.selection.isCollapsed) {
|
|
||||||
_editableText!.toggleToolbar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _shouldShowSelectionHandles(SelectionChangedCause? cause) {
|
|
||||||
// When the text field is activated by something that doesn't trigger the
|
|
||||||
// selection overlay, we shouldn't show the handles either.
|
|
||||||
if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_controller.selection.isCollapsed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cause == SelectionChangedCause.keyboard) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cause == SelectionChangedCause.longPress) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_controller.text.isNotEmpty) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// TODO(garyq): Assert to block WidgetSpans from being used here are removed,
|
|
||||||
// but we still do not yet have nice handling of things like carets, clipboard,
|
|
||||||
// and other features. We should add proper support. Currently, caret handling
|
|
||||||
// is blocked on SkParagraph switch and https://github.com/flutter/engine/pull/27010
|
|
||||||
// should be landed in SkParagraph after the switch is complete.
|
|
||||||
assert(debugCheckHasMediaQuery(context));
|
|
||||||
assert(debugCheckHasDirectionality(context));
|
|
||||||
assert(
|
|
||||||
!(widget.style != null &&
|
|
||||||
!widget.style!.inherit &&
|
|
||||||
(widget.style!.fontSize == null ||
|
|
||||||
widget.style!.textBaseline == null)),
|
|
||||||
'inherit false style must supply fontSize and textBaseline',
|
|
||||||
);
|
|
||||||
|
|
||||||
final ThemeData theme = Theme.of(context);
|
|
||||||
final DefaultSelectionStyle selectionStyle = DefaultSelectionStyle.of(
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
final FocusNode focusNode = _effectiveFocusNode;
|
|
||||||
|
|
||||||
TextSelectionControls? textSelectionControls = widget.selectionControls;
|
|
||||||
final bool paintCursorAboveText;
|
|
||||||
final bool cursorOpacityAnimates;
|
|
||||||
Offset? cursorOffset;
|
|
||||||
final Color cursorColor;
|
|
||||||
final Color selectionColor;
|
|
||||||
Radius? cursorRadius = widget.cursorRadius;
|
|
||||||
|
|
||||||
switch (theme.platform) {
|
|
||||||
case TargetPlatform.iOS:
|
|
||||||
final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context);
|
|
||||||
forcePressEnabled = true;
|
|
||||||
textSelectionControls ??= cupertinoTextSelectionHandleControls;
|
|
||||||
paintCursorAboveText = true;
|
|
||||||
cursorOpacityAnimates = true;
|
|
||||||
cursorColor =
|
|
||||||
widget.cursorColor ??
|
|
||||||
selectionStyle.cursorColor ??
|
|
||||||
cupertinoTheme.primaryColor;
|
|
||||||
selectionColor =
|
|
||||||
selectionStyle.selectionColor ??
|
|
||||||
cupertinoTheme.primaryColor.withValues(alpha: 0.40);
|
|
||||||
cursorRadius ??= const Radius.circular(2.0);
|
|
||||||
cursorOffset = Offset(
|
|
||||||
iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
case TargetPlatform.macOS:
|
|
||||||
final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context);
|
|
||||||
forcePressEnabled = false;
|
|
||||||
textSelectionControls ??= cupertinoDesktopTextSelectionHandleControls;
|
|
||||||
paintCursorAboveText = true;
|
|
||||||
cursorOpacityAnimates = true;
|
|
||||||
cursorColor =
|
|
||||||
widget.cursorColor ??
|
|
||||||
selectionStyle.cursorColor ??
|
|
||||||
cupertinoTheme.primaryColor;
|
|
||||||
selectionColor =
|
|
||||||
selectionStyle.selectionColor ??
|
|
||||||
cupertinoTheme.primaryColor.withValues(alpha: 0.40);
|
|
||||||
cursorRadius ??= const Radius.circular(2.0);
|
|
||||||
cursorOffset = Offset(
|
|
||||||
iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
case TargetPlatform.android:
|
|
||||||
case TargetPlatform.fuchsia:
|
|
||||||
forcePressEnabled = false;
|
|
||||||
textSelectionControls ??= materialTextSelectionHandleControls;
|
|
||||||
paintCursorAboveText = false;
|
|
||||||
cursorOpacityAnimates = false;
|
|
||||||
cursorColor =
|
|
||||||
widget.cursorColor ??
|
|
||||||
selectionStyle.cursorColor ??
|
|
||||||
theme.colorScheme.primary;
|
|
||||||
selectionColor =
|
|
||||||
selectionStyle.selectionColor ??
|
|
||||||
theme.colorScheme.primary.withValues(alpha: 0.40);
|
|
||||||
|
|
||||||
case TargetPlatform.linux:
|
|
||||||
case TargetPlatform.windows:
|
|
||||||
forcePressEnabled = false;
|
|
||||||
textSelectionControls ??= desktopTextSelectionHandleControls;
|
|
||||||
paintCursorAboveText = false;
|
|
||||||
cursorOpacityAnimates = false;
|
|
||||||
cursorColor =
|
|
||||||
widget.cursorColor ??
|
|
||||||
selectionStyle.cursorColor ??
|
|
||||||
theme.colorScheme.primary;
|
|
||||||
selectionColor =
|
|
||||||
selectionStyle.selectionColor ??
|
|
||||||
theme.colorScheme.primary.withValues(alpha: 0.40);
|
|
||||||
}
|
|
||||||
|
|
||||||
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
|
|
||||||
TextStyle? effectiveTextStyle = widget.style;
|
|
||||||
if (effectiveTextStyle == null || effectiveTextStyle.inherit) {
|
|
||||||
effectiveTextStyle = defaultTextStyle.style.merge(
|
|
||||||
widget.style ?? _controller._textSpan.style,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final TextScaler? effectiveScaler =
|
|
||||||
widget.textScaler ??
|
|
||||||
switch (widget.textScaleFactor) {
|
|
||||||
null => null,
|
|
||||||
final double textScaleFactor => TextScaler.linear(textScaleFactor),
|
|
||||||
};
|
|
||||||
final Widget child = RepaintBoundary(
|
|
||||||
child: EditableText(
|
|
||||||
key: editableTextKey,
|
|
||||||
style: effectiveTextStyle,
|
|
||||||
readOnly: true,
|
|
||||||
toolbarOptions: widget.toolbarOptions,
|
|
||||||
textWidthBasis:
|
|
||||||
widget.textWidthBasis ?? defaultTextStyle.textWidthBasis,
|
|
||||||
textHeightBehavior:
|
|
||||||
widget.textHeightBehavior ?? defaultTextStyle.textHeightBehavior,
|
|
||||||
showSelectionHandles: _showSelectionHandles,
|
|
||||||
showCursor: widget.showCursor,
|
|
||||||
controller: _controller,
|
|
||||||
focusNode: focusNode,
|
|
||||||
strutStyle: widget.strutStyle ?? const StrutStyle(),
|
|
||||||
textAlign:
|
|
||||||
widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
|
|
||||||
textDirection: widget.textDirection,
|
|
||||||
textScaler: effectiveScaler,
|
|
||||||
autofocus: widget.autofocus,
|
|
||||||
forceLine: false,
|
|
||||||
minLines: widget.minLines,
|
|
||||||
maxLines: widget.maxLines ?? defaultTextStyle.maxLines,
|
|
||||||
selectionColor: widget.selectionColor ?? selectionColor,
|
|
||||||
selectionControls: widget.selectionEnabled
|
|
||||||
? textSelectionControls
|
|
||||||
: null,
|
|
||||||
onSelectionChanged: _handleSelectionChanged,
|
|
||||||
onSelectionHandleTapped: _handleSelectionHandleTapped,
|
|
||||||
rendererIgnoresPointer: true,
|
|
||||||
cursorWidth: widget.cursorWidth,
|
|
||||||
cursorHeight: widget.cursorHeight,
|
|
||||||
cursorRadius: cursorRadius,
|
|
||||||
cursorColor: cursorColor,
|
|
||||||
selectionHeightStyle: widget.selectionHeightStyle,
|
|
||||||
selectionWidthStyle: widget.selectionWidthStyle,
|
|
||||||
cursorOpacityAnimates: cursorOpacityAnimates,
|
|
||||||
cursorOffset: cursorOffset,
|
|
||||||
paintCursorAboveText: paintCursorAboveText,
|
|
||||||
backgroundCursorColor: CupertinoColors.inactiveGray,
|
|
||||||
enableInteractiveSelection: widget.enableInteractiveSelection,
|
|
||||||
magnifierConfiguration:
|
|
||||||
widget.magnifierConfiguration ??
|
|
||||||
TextMagnifier.adaptiveMagnifierConfiguration,
|
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
|
||||||
scrollPhysics: widget.scrollPhysics,
|
|
||||||
scrollBehavior: widget.scrollBehavior,
|
|
||||||
autofillHints: null,
|
|
||||||
contextMenuBuilder: widget.contextMenuBuilder,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Semantics(
|
|
||||||
label: widget.semanticsLabel,
|
|
||||||
excludeSemantics: widget.semanticsLabel != null,
|
|
||||||
onLongPress: () {
|
|
||||||
_effectiveFocusNode.requestFocus();
|
|
||||||
},
|
|
||||||
child: _selectionGestureDetectorBuilder.buildGestureDetector(
|
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/selectable_region.dart';
|
|
||||||
import 'package:flutter/cupertino.dart'
|
|
||||||
hide
|
|
||||||
SelectableRegion,
|
|
||||||
SelectableRegionState,
|
|
||||||
SelectableRegionContextMenuBuilder;
|
|
||||||
import 'package:flutter/material.dart'
|
|
||||||
hide
|
|
||||||
SelectionArea,
|
|
||||||
SelectableRegion,
|
|
||||||
SelectableRegionState,
|
|
||||||
SelectableRegionContextMenuBuilder;
|
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
|
|
||||||
/// A widget that introduces an area for user selections with adaptive selection
|
|
||||||
/// controls.
|
|
||||||
///
|
|
||||||
/// This widget creates a [SelectableRegion] with platform-adaptive selection
|
|
||||||
/// controls.
|
|
||||||
///
|
|
||||||
/// Flutter widgets are not selectable by default. To enable selection for
|
|
||||||
/// a specific screen, consider wrapping the body of the [Route] with a
|
|
||||||
/// [SelectionArea].
|
|
||||||
///
|
|
||||||
/// The [SelectionArea] widget must have a [Localizations] ancestor that
|
|
||||||
/// contains a [MaterialLocalizations] delegate; using the [MaterialApp] widget
|
|
||||||
/// ensures that such an ancestor is present.
|
|
||||||
///
|
|
||||||
/// {@tool dartpad}
|
|
||||||
/// This example shows how to make a screen selectable.
|
|
||||||
///
|
|
||||||
/// ** See code in examples/api/lib/material/selection_area/selection_area.0.dart **
|
|
||||||
/// {@end-tool}
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [SelectableRegion], which provides an overview of the selection system.
|
|
||||||
/// * [SelectableText], which enables selection on a single run of text.
|
|
||||||
/// * [SelectionListener], which enables accessing the [SelectionDetails] of
|
|
||||||
/// the selectable subtree it wraps.
|
|
||||||
class SelectionArea extends StatefulWidget {
|
|
||||||
/// Creates a [SelectionArea].
|
|
||||||
///
|
|
||||||
/// If [selectionControls] is null, a platform specific one is used.
|
|
||||||
const SelectionArea({
|
|
||||||
super.key,
|
|
||||||
this.focusNode,
|
|
||||||
this.selectionControls,
|
|
||||||
this.contextMenuBuilder = _defaultContextMenuBuilder,
|
|
||||||
this.magnifierConfiguration,
|
|
||||||
this.onSelectionChanged,
|
|
||||||
required this.child,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The configuration for the magnifier in the selection region.
|
|
||||||
///
|
|
||||||
/// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier]
|
|
||||||
/// on Android, and builds nothing on all other platforms. To suppress the
|
|
||||||
/// magnifier, consider passing [TextMagnifierConfiguration.disabled].
|
|
||||||
///
|
|
||||||
/// {@macro flutter.widgets.magnifier.intro}
|
|
||||||
final TextMagnifierConfiguration? magnifierConfiguration;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.Focus.focusNode}
|
|
||||||
final FocusNode? focusNode;
|
|
||||||
|
|
||||||
/// The delegate to build the selection handles and toolbar.
|
|
||||||
///
|
|
||||||
/// If it is null, the platform specific selection control is used.
|
|
||||||
final TextSelectionControls? selectionControls;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.EditableText.contextMenuBuilder}
|
|
||||||
///
|
|
||||||
/// If not provided, will build a default menu based on the ambient
|
|
||||||
/// [ThemeData.platform].
|
|
||||||
///
|
|
||||||
/// {@tool dartpad}
|
|
||||||
/// This example shows how to build a custom context menu for any selected
|
|
||||||
/// content in a SelectionArea.
|
|
||||||
///
|
|
||||||
/// ** See code in examples/api/lib/material/context_menu/selectable_region_toolbar_builder.0.dart **
|
|
||||||
/// {@end-tool}
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [AdaptiveTextSelectionToolbar], which is built by default.
|
|
||||||
final SelectableRegionContextMenuBuilder? contextMenuBuilder;
|
|
||||||
|
|
||||||
/// Called when the selected content changes.
|
|
||||||
final ValueChanged<SelectedContent?>? onSelectionChanged;
|
|
||||||
|
|
||||||
/// The child widget this selection area applies to.
|
|
||||||
///
|
|
||||||
/// {@macro flutter.widgets.ProxyWidget.child}
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
static Widget _defaultContextMenuBuilder(
|
|
||||||
BuildContext context,
|
|
||||||
SelectableRegionState selectableRegionState,
|
|
||||||
) => AdaptiveTextSelectionToolbar.buttonItems(
|
|
||||||
buttonItems: selectableRegionState.contextMenuButtonItems,
|
|
||||||
anchors: selectableRegionState.contextMenuAnchors,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => SelectionAreaState();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State for a [SelectionArea].
|
|
||||||
class SelectionAreaState extends State<SelectionArea> {
|
|
||||||
final GlobalKey<SelectableRegionState> _selectableRegionKey =
|
|
||||||
GlobalKey<SelectableRegionState>();
|
|
||||||
|
|
||||||
/// The [State] of the [SelectableRegion] for which this [SelectionArea] wraps.
|
|
||||||
SelectableRegionState get selectableRegion =>
|
|
||||||
_selectableRegionKey.currentState!;
|
|
||||||
|
|
||||||
@protected
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
assert(debugCheckHasMaterialLocalizations(context));
|
|
||||||
final TextSelectionControls controls =
|
|
||||||
widget.selectionControls ??
|
|
||||||
switch (Theme.of(context).platform) {
|
|
||||||
TargetPlatform.android ||
|
|
||||||
TargetPlatform.fuchsia => materialTextSelectionHandleControls,
|
|
||||||
TargetPlatform.linux ||
|
|
||||||
TargetPlatform.windows => desktopTextSelectionHandleControls,
|
|
||||||
TargetPlatform.iOS => cupertinoTextSelectionHandleControls,
|
|
||||||
TargetPlatform.macOS => cupertinoDesktopTextSelectionHandleControls,
|
|
||||||
};
|
|
||||||
return SelectableRegion(
|
|
||||||
key: _selectableRegionKey,
|
|
||||||
selectionControls: controls,
|
|
||||||
focusNode: widget.focusNode,
|
|
||||||
contextMenuBuilder: widget.contextMenuBuilder,
|
|
||||||
magnifierConfiguration:
|
|
||||||
widget.magnifierConfiguration ??
|
|
||||||
TextMagnifier.adaptiveMagnifierConfiguration,
|
|
||||||
onSelectionChanged: widget.onSelectionChanged,
|
|
||||||
child: widget.child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,421 +0,0 @@
|
|||||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/tap_and_drag.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/gestures.dart'
|
|
||||||
hide
|
|
||||||
BaseTapAndDragGestureRecognizer,
|
|
||||||
TapAndHorizontalDragGestureRecognizer,
|
|
||||||
TapAndPanGestureRecognizer;
|
|
||||||
import 'package:flutter/material.dart' hide TextSelectionGestureDetector;
|
|
||||||
|
|
||||||
class CustomTextSelectionGestureDetectorBuilder
|
|
||||||
extends TextSelectionGestureDetectorBuilder {
|
|
||||||
CustomTextSelectionGestureDetectorBuilder({required super.delegate});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildGestureDetector({
|
|
||||||
Key? key,
|
|
||||||
HitTestBehavior? behavior,
|
|
||||||
required Widget child,
|
|
||||||
}) {
|
|
||||||
return TextSelectionGestureDetector(
|
|
||||||
key: key,
|
|
||||||
onTapTrackStart: onTapTrackStart,
|
|
||||||
onTapTrackReset: onTapTrackReset,
|
|
||||||
onTapDown: onTapDown,
|
|
||||||
onForcePressStart: delegate.forcePressEnabled ? onForcePressStart : null,
|
|
||||||
onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null,
|
|
||||||
onSecondaryTap: onSecondaryTap,
|
|
||||||
onSecondaryTapDown: onSecondaryTapDown,
|
|
||||||
onSingleTapUp: onSingleTapUp,
|
|
||||||
onSingleTapCancel: onSingleTapCancel,
|
|
||||||
onUserTap: onUserTap,
|
|
||||||
onSingleLongTapStart: onSingleLongTapStart,
|
|
||||||
onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate,
|
|
||||||
onSingleLongTapEnd: onSingleLongTapEnd,
|
|
||||||
onSingleLongTapCancel: onSingleLongTapCancel,
|
|
||||||
onDoubleTapDown: onDoubleTapDown,
|
|
||||||
onTripleTapDown: onTripleTapDown,
|
|
||||||
onDragSelectionStart: onDragSelectionStart,
|
|
||||||
onDragSelectionUpdate: onDragSelectionUpdate,
|
|
||||||
onDragSelectionEnd: onDragSelectionEnd,
|
|
||||||
onUserTapAlwaysCalled: onUserTapAlwaysCalled,
|
|
||||||
behavior: behavior,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A gesture detector to respond to non-exclusive event chains for a text field.
|
|
||||||
///
|
|
||||||
/// An ordinary [GestureDetector] configured to handle events like tap and
|
|
||||||
/// double tap will only recognize one or the other. This widget detects both:
|
|
||||||
/// the first tap and then any subsequent taps that occurs within a time limit
|
|
||||||
/// after the first.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [TextField], a Material text field which uses this gesture detector.
|
|
||||||
/// * [CupertinoTextField], a Cupertino text field which uses this gesture
|
|
||||||
/// detector.
|
|
||||||
class TextSelectionGestureDetector extends StatefulWidget {
|
|
||||||
/// Create a [TextSelectionGestureDetector].
|
|
||||||
///
|
|
||||||
/// Multiple callbacks can be called for one sequence of input gesture.
|
|
||||||
const TextSelectionGestureDetector({
|
|
||||||
super.key,
|
|
||||||
this.onTapTrackStart,
|
|
||||||
this.onTapTrackReset,
|
|
||||||
this.onTapDown,
|
|
||||||
this.onForcePressStart,
|
|
||||||
this.onForcePressEnd,
|
|
||||||
this.onSecondaryTap,
|
|
||||||
this.onSecondaryTapDown,
|
|
||||||
this.onSingleTapUp,
|
|
||||||
this.onSingleTapCancel,
|
|
||||||
this.onUserTap,
|
|
||||||
this.onSingleLongTapStart,
|
|
||||||
this.onSingleLongTapMoveUpdate,
|
|
||||||
this.onSingleLongTapEnd,
|
|
||||||
this.onSingleLongTapCancel,
|
|
||||||
this.onDoubleTapDown,
|
|
||||||
this.onTripleTapDown,
|
|
||||||
this.onDragSelectionStart,
|
|
||||||
this.onDragSelectionUpdate,
|
|
||||||
this.onDragSelectionEnd,
|
|
||||||
this.onUserTapAlwaysCalled = false,
|
|
||||||
this.behavior,
|
|
||||||
required this.child,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// {@template flutter.gestures.selectionrecognizers.TextSelectionGestureDetector.onTapTrackStart}
|
|
||||||
/// Callback used to indicate that a tap tracking has started upon
|
|
||||||
/// a [PointerDownEvent].
|
|
||||||
/// {@endtemplate}
|
|
||||||
final VoidCallback? onTapTrackStart;
|
|
||||||
|
|
||||||
/// {@template flutter.gestures.selectionrecognizers.TextSelectionGestureDetector.onTapTrackReset}
|
|
||||||
/// Callback used to indicate that a tap tracking has been reset which
|
|
||||||
/// happens on the next [PointerDownEvent] after the timer between two taps
|
|
||||||
/// elapses, the recognizer loses the arena, the gesture is cancelled or
|
|
||||||
/// the recognizer is disposed of.
|
|
||||||
/// {@endtemplate}
|
|
||||||
final VoidCallback? onTapTrackReset;
|
|
||||||
|
|
||||||
/// Called for every tap down including every tap down that's part of a
|
|
||||||
/// double click or a long press, except touches that include enough movement
|
|
||||||
/// to not qualify as taps (e.g. pans and flings).
|
|
||||||
final GestureTapDragDownCallback? onTapDown;
|
|
||||||
|
|
||||||
/// Called when a pointer has tapped down and the force of the pointer has
|
|
||||||
/// just become greater than [ForcePressGestureRecognizer.startPressure].
|
|
||||||
final GestureForcePressStartCallback? onForcePressStart;
|
|
||||||
|
|
||||||
/// Called when a pointer that had previously triggered [onForcePressStart] is
|
|
||||||
/// lifted off the screen.
|
|
||||||
final GestureForcePressEndCallback? onForcePressEnd;
|
|
||||||
|
|
||||||
/// Called for a tap event with the secondary mouse button.
|
|
||||||
final GestureTapCallback? onSecondaryTap;
|
|
||||||
|
|
||||||
/// Called for a tap down event with the secondary mouse button.
|
|
||||||
final GestureTapDownCallback? onSecondaryTapDown;
|
|
||||||
|
|
||||||
/// Called for the first tap in a series of taps, consecutive taps do not call
|
|
||||||
/// this method.
|
|
||||||
///
|
|
||||||
/// For example, if the detector was configured with [onTapDown] and
|
|
||||||
/// [onDoubleTapDown], three quick taps would be recognized as a single tap
|
|
||||||
/// down, followed by a tap up, then a double tap down, followed by a single tap down.
|
|
||||||
final GestureTapDragUpCallback? onSingleTapUp;
|
|
||||||
|
|
||||||
/// Called for each touch that becomes recognized as a gesture that is not a
|
|
||||||
/// short tap, such as a long tap or drag. It is called at the moment when
|
|
||||||
/// another gesture from the touch is recognized.
|
|
||||||
final GestureCancelCallback? onSingleTapCancel;
|
|
||||||
|
|
||||||
/// Called for the first tap in a series of taps when [onUserTapAlwaysCalled] is
|
|
||||||
/// disabled, which is the default behavior.
|
|
||||||
///
|
|
||||||
/// When [onUserTapAlwaysCalled] is enabled, this is called for every tap,
|
|
||||||
/// including consecutive taps.
|
|
||||||
final GestureTapCallback? onUserTap;
|
|
||||||
|
|
||||||
/// Called for a single long tap that's sustained for longer than
|
|
||||||
/// [kLongPressTimeout] but not necessarily lifted. Not called for a
|
|
||||||
/// double-tap-hold, which calls [onDoubleTapDown] instead.
|
|
||||||
final GestureLongPressStartCallback? onSingleLongTapStart;
|
|
||||||
|
|
||||||
/// Called after [onSingleLongTapStart] when the pointer is dragged.
|
|
||||||
final GestureLongPressMoveUpdateCallback? onSingleLongTapMoveUpdate;
|
|
||||||
|
|
||||||
/// Called after [onSingleLongTapStart] when the pointer is lifted.
|
|
||||||
final GestureLongPressEndCallback? onSingleLongTapEnd;
|
|
||||||
|
|
||||||
/// Called after [onSingleLongTapStart] when the pointer is canceled.
|
|
||||||
final GestureLongPressCancelCallback? onSingleLongTapCancel;
|
|
||||||
|
|
||||||
/// Called after a momentary hold or a short tap that is close in space and
|
|
||||||
/// time (within [kDoubleTapTimeout]) to a previous short tap.
|
|
||||||
final GestureTapDragDownCallback? onDoubleTapDown;
|
|
||||||
|
|
||||||
/// Called after a momentary hold or a short tap that is close in space and
|
|
||||||
/// time (within [kDoubleTapTimeout]) to a previous double-tap.
|
|
||||||
final GestureTapDragDownCallback? onTripleTapDown;
|
|
||||||
|
|
||||||
/// Called when a mouse starts dragging to select text.
|
|
||||||
final GestureTapDragStartCallback? onDragSelectionStart;
|
|
||||||
|
|
||||||
/// Called repeatedly as a mouse moves while dragging.
|
|
||||||
final GestureTapDragUpdateCallback? onDragSelectionUpdate;
|
|
||||||
|
|
||||||
/// Called when a mouse that was previously dragging is released.
|
|
||||||
final GestureTapDragEndCallback? onDragSelectionEnd;
|
|
||||||
|
|
||||||
/// Whether [onUserTap] will be called for all taps including consecutive taps.
|
|
||||||
///
|
|
||||||
/// Defaults to false, so [onUserTap] is only called for each distinct tap.
|
|
||||||
final bool onUserTapAlwaysCalled;
|
|
||||||
|
|
||||||
/// How this gesture detector should behave during hit testing.
|
|
||||||
///
|
|
||||||
/// This defaults to [HitTestBehavior.deferToChild].
|
|
||||||
final HitTestBehavior? behavior;
|
|
||||||
|
|
||||||
/// Child below this widget.
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => _TextSelectionGestureDetectorState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TextSelectionGestureDetectorState
|
|
||||||
extends State<TextSelectionGestureDetector> {
|
|
||||||
// Converts the details.consecutiveTapCount from a TapAndDrag*Details object,
|
|
||||||
// which can grow to be infinitely large, to a value between 1 and 3. The value
|
|
||||||
// that the raw count is converted to is based on the default observed behavior
|
|
||||||
// on the native platforms.
|
|
||||||
//
|
|
||||||
// This method should be used in all instances when details.consecutiveTapCount
|
|
||||||
// would be used.
|
|
||||||
static int _getEffectiveConsecutiveTapCount(int rawCount) {
|
|
||||||
switch (defaultTargetPlatform) {
|
|
||||||
case TargetPlatform.android:
|
|
||||||
case TargetPlatform.fuchsia:
|
|
||||||
case TargetPlatform.linux:
|
|
||||||
// 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 math.min(rawCount, 3);
|
|
||||||
case TargetPlatform.windows:
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleTapTrackStart() {
|
|
||||||
widget.onTapTrackStart?.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleTapTrackReset() {
|
|
||||||
widget.onTapTrackReset?.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
// The down handler is force-run on success of a single tap and optimistically
|
|
||||||
// run before a long press success.
|
|
||||||
void _handleTapDown(TapDragDownDetails details) {
|
|
||||||
widget.onTapDown?.call(details);
|
|
||||||
// This isn't detected as a double tap gesture in the gesture recognizer
|
|
||||||
// because it's 2 single taps, each of which may do different things depending
|
|
||||||
// on whether it's a single tap, the first tap of a double tap, the second
|
|
||||||
// tap held down, a clean double tap etc.
|
|
||||||
if (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount) == 2) {
|
|
||||||
return widget.onDoubleTapDown?.call(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount) == 3) {
|
|
||||||
return widget.onTripleTapDown?.call(details);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleTapUp(TapDragUpDetails details) {
|
|
||||||
if (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount) == 1) {
|
|
||||||
widget.onSingleTapUp?.call(details);
|
|
||||||
widget.onUserTap?.call();
|
|
||||||
} else if (widget.onUserTapAlwaysCalled) {
|
|
||||||
widget.onUserTap?.call();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleTapCancel() {
|
|
||||||
widget.onSingleTapCancel?.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragStart(TapDragStartDetails details) {
|
|
||||||
widget.onDragSelectionStart?.call(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragUpdate(TapDragUpdateDetails details) {
|
|
||||||
widget.onDragSelectionUpdate?.call(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragEnd(TapDragEndDetails details) {
|
|
||||||
widget.onDragSelectionEnd?.call(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _forcePressStarted(ForcePressDetails details) {
|
|
||||||
widget.onForcePressStart?.call(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _forcePressEnded(ForcePressDetails details) {
|
|
||||||
widget.onForcePressEnd?.call(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleLongPressStart(LongPressStartDetails details) {
|
|
||||||
widget.onSingleLongTapStart?.call(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) {
|
|
||||||
widget.onSingleLongTapMoveUpdate?.call(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleLongPressEnd(LongPressEndDetails details) {
|
|
||||||
widget.onSingleLongTapEnd?.call(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleLongPressCancel() {
|
|
||||||
widget.onSingleLongTapCancel?.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final gestures = <Type, GestureRecognizerFactory>{};
|
|
||||||
|
|
||||||
gestures[TapGestureRecognizer] =
|
|
||||||
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
|
||||||
() => TapGestureRecognizer(debugOwner: this),
|
|
||||||
(TapGestureRecognizer instance) {
|
|
||||||
instance
|
|
||||||
..onSecondaryTap = widget.onSecondaryTap
|
|
||||||
..onSecondaryTapDown = widget.onSecondaryTapDown;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (widget.onSingleLongTapStart != null ||
|
|
||||||
widget.onSingleLongTapMoveUpdate != null ||
|
|
||||||
widget.onSingleLongTapEnd != null ||
|
|
||||||
widget.onSingleLongTapCancel != null) {
|
|
||||||
gestures[LongPressGestureRecognizer] =
|
|
||||||
GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
|
|
||||||
() => LongPressGestureRecognizer(
|
|
||||||
debugOwner: this,
|
|
||||||
supportedDevices: <PointerDeviceKind>{PointerDeviceKind.touch},
|
|
||||||
),
|
|
||||||
(LongPressGestureRecognizer instance) {
|
|
||||||
instance
|
|
||||||
..onLongPressStart = _handleLongPressStart
|
|
||||||
..onLongPressMoveUpdate = _handleLongPressMoveUpdate
|
|
||||||
..onLongPressEnd = _handleLongPressEnd
|
|
||||||
..onLongPressCancel = _handleLongPressCancel;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.onDragSelectionStart != null ||
|
|
||||||
widget.onDragSelectionUpdate != null ||
|
|
||||||
widget.onDragSelectionEnd != null) {
|
|
||||||
switch (defaultTargetPlatform) {
|
|
||||||
case TargetPlatform.android:
|
|
||||||
case TargetPlatform.fuchsia:
|
|
||||||
case TargetPlatform.iOS:
|
|
||||||
gestures[TapAndHorizontalDragGestureRecognizer] =
|
|
||||||
GestureRecognizerFactoryWithHandlers<
|
|
||||||
TapAndHorizontalDragGestureRecognizer
|
|
||||||
>(
|
|
||||||
() => TapAndHorizontalDragGestureRecognizer(debugOwner: this),
|
|
||||||
(TapAndHorizontalDragGestureRecognizer instance) {
|
|
||||||
instance
|
|
||||||
// Text selection should start from the position of the first pointer
|
|
||||||
// down event.
|
|
||||||
..dragStartBehavior = DragStartBehavior.down
|
|
||||||
..eagerVictoryOnDrag =
|
|
||||||
defaultTargetPlatform != TargetPlatform.iOS
|
|
||||||
..onTapTrackStart = _handleTapTrackStart
|
|
||||||
..onTapTrackReset = _handleTapTrackReset
|
|
||||||
..onTapDown = _handleTapDown
|
|
||||||
..onDragStart = _handleDragStart
|
|
||||||
..onDragUpdate = _handleDragUpdate
|
|
||||||
..onDragEnd = _handleDragEnd
|
|
||||||
..onTapUp = _handleTapUp
|
|
||||||
..onCancel = _handleTapCancel;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
case TargetPlatform.linux:
|
|
||||||
case TargetPlatform.macOS:
|
|
||||||
case TargetPlatform.windows:
|
|
||||||
gestures[TapAndPanGestureRecognizer] =
|
|
||||||
GestureRecognizerFactoryWithHandlers<TapAndPanGestureRecognizer>(
|
|
||||||
() => TapAndPanGestureRecognizer(debugOwner: this),
|
|
||||||
(TapAndPanGestureRecognizer instance) {
|
|
||||||
instance
|
|
||||||
// Text selection should start from the position of the first pointer
|
|
||||||
// down event.
|
|
||||||
..dragStartBehavior = DragStartBehavior.down
|
|
||||||
..onTapTrackStart = _handleTapTrackStart
|
|
||||||
..onTapTrackReset = _handleTapTrackReset
|
|
||||||
..onTapDown = _handleTapDown
|
|
||||||
..onDragStart = _handleDragStart
|
|
||||||
..onDragUpdate = _handleDragUpdate
|
|
||||||
..onDragEnd = _handleDragEnd
|
|
||||||
..onTapUp = _handleTapUp
|
|
||||||
..onCancel = _handleTapCancel;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.onForcePressStart != null || widget.onForcePressEnd != null) {
|
|
||||||
gestures[ForcePressGestureRecognizer] =
|
|
||||||
GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
|
|
||||||
() => ForcePressGestureRecognizer(debugOwner: this),
|
|
||||||
(ForcePressGestureRecognizer instance) {
|
|
||||||
instance
|
|
||||||
..onStart = widget.onForcePressStart != null
|
|
||||||
? _forcePressStarted
|
|
||||||
: null
|
|
||||||
..onEnd = widget.onForcePressEnd != null
|
|
||||||
? _forcePressEnded
|
|
||||||
: null;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return RawGestureDetector(
|
|
||||||
gestures: gestures,
|
|
||||||
excludeFromSemantics: true,
|
|
||||||
behavior: widget.behavior,
|
|
||||||
child: widget.child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/selectable_text.dart';
|
|
||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/selection_area.dart';
|
|
||||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||||
import 'package:flutter/material.dart' hide SelectableText, SelectionArea;
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
Widget selectableText(
|
Widget selectableText(
|
||||||
String text, {
|
String text, {
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/selection_area.dart';
|
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/models/common/image_type.dart';
|
import 'package:PiliPlus/models/common/image_type.dart';
|
||||||
import 'package:PiliPlus/models_new/live/live_superchat/item.dart';
|
import 'package:PiliPlus/models_new/live/live_superchat/item.dart';
|
||||||
@@ -11,7 +10,7 @@ import 'package:PiliPlus/utils/platform_utils.dart';
|
|||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/material.dart' hide SelectionArea;
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class SuperChatCard extends StatefulWidget {
|
class SuperChatCard extends StatefulWidget {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:PiliPlus/common/style.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
||||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
|
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/selectable_text.dart';
|
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.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/loading_widget/http_error.dart';
|
||||||
import 'package:PiliPlus/common/widgets/sliver/sliver_floating_header.dart';
|
import 'package:PiliPlus/common/widgets/sliver/sliver_floating_header.dart';
|
||||||
@@ -19,7 +18,7 @@ import 'package:PiliPlus/utils/extension/theme_ext.dart';
|
|||||||
import 'package:PiliPlus/utils/num_utils.dart';
|
import 'package:PiliPlus/utils/num_utils.dart';
|
||||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart' hide SelectableText;
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/text.dart';
|
|
||||||
import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart';
|
import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/selectable_text.dart';
|
||||||
import 'package:PiliPlus/models_new/video/video_ai_conclusion/model_result.dart';
|
import 'package:PiliPlus/models_new/video/video_ai_conclusion/model_result.dart';
|
||||||
import 'package:PiliPlus/pages/common/slide/common_slide_page.dart';
|
import 'package:PiliPlus/pages/common/slide/common_slide_page.dart';
|
||||||
import 'package:PiliPlus/pages/video/controller.dart';
|
import 'package:PiliPlus/pages/video/controller.dart';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart';
|
import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/text.dart';
|
|
||||||
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
||||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/selectable_text.dart';
|
||||||
import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
||||||
import 'package:PiliPlus/models/common/stat_type.dart';
|
import 'package:PiliPlus/models/common/stat_type.dart';
|
||||||
import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart';
|
import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart';
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ import 'package:PiliPlus/common/assets.dart';
|
|||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
import 'package:PiliPlus/common/style.dart';
|
import 'package:PiliPlus/common/style.dart';
|
||||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/selection_area.dart';
|
|
||||||
import 'package:PiliPlus/common/widgets/flutter/selectable_text/text.dart';
|
|
||||||
import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.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/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
||||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/selectable_text.dart';
|
||||||
import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
||||||
import 'package:PiliPlus/http/sponsor_block.dart';
|
import 'package:PiliPlus/http/sponsor_block.dart';
|
||||||
import 'package:PiliPlus/models/common/image_type.dart';
|
import 'package:PiliPlus/models/common/image_type.dart';
|
||||||
@@ -39,7 +38,7 @@ import 'package:PiliPlus/utils/platform_utils.dart';
|
|||||||
import 'package:PiliPlus/utils/request_utils.dart';
|
import 'package:PiliPlus/utils/request_utils.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:expandable/expandable.dart';
|
import 'package:expandable/expandable.dart';
|
||||||
import 'package:flutter/material.dart' hide SelectionArea;
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ $BottomSheetPatch = "lib/scripts/bottom_sheet.patch"
|
|||||||
|
|
||||||
$ScrollViewPatch = "lib/scripts/scroll_view.patch"
|
$ScrollViewPatch = "lib/scripts/scroll_view.patch"
|
||||||
|
|
||||||
|
$TextSelectionPatch = "lib/scripts/text_selection.patch"
|
||||||
|
|
||||||
# TODO: remove
|
# TODO: remove
|
||||||
# https://github.com/flutter/flutter/issues/90223
|
# https://github.com/flutter/flutter/issues/90223
|
||||||
$ModalBarrierPatch = "lib/scripts/modal_barrier.patch"
|
$ModalBarrierPatch = "lib/scripts/modal_barrier.patch"
|
||||||
@@ -26,7 +28,7 @@ Set-Location $env:FLUTTER_ROOT
|
|||||||
|
|
||||||
$picks = @()
|
$picks = @()
|
||||||
$reverts = @()
|
$reverts = @()
|
||||||
$patches = @($ModalBarrierPatch, $MouseCursorPatch)
|
$patches = @($ModalBarrierPatch, $TextSelectionPatch, $MouseCursorPatch)
|
||||||
|
|
||||||
switch ($platform.ToLower()) {
|
switch ($platform.ToLower()) {
|
||||||
"android" {
|
"android" {
|
||||||
|
|||||||
20
lib/scripts/text_selection.patch
Normal file
20
lib/scripts/text_selection.patch
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
diff --git a/packages/flutter/lib/src/gestures/tap_and_drag.dart b/packages/flutter/lib/src/gestures/tap_and_drag.dart
|
||||||
|
index 2409d19eabd..9460b61c00a 100644
|
||||||
|
--- a/packages/flutter/lib/src/gestures/tap_and_drag.dart
|
||||||
|
+++ b/packages/flutter/lib/src/gestures/tap_and_drag.dart
|
||||||
|
@@ -1407,6 +1407,7 @@ class TapAndHorizontalDragGestureRecognizer extends BaseTapAndDragGestureRecogni
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind) {
|
||||||
|
+ return false;
|
||||||
|
return _globalDistanceMoved.abs() > computeHitSlop(pointerDeviceKind, gestureSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -1442,6 +1443,7 @@ class TapAndPanGestureRecognizer extends BaseTapAndDragGestureRecognizer {
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind) {
|
||||||
|
+ return true;
|
||||||
|
return _globalDistanceMoved.abs() > computePanSlop(pointerDeviceKind, gestureSettings);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user