mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-21 03:15:14 +08:00
feat: at user
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
// 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/text_field/editable_text.dart';
|
||||
import 'package:flutter/cupertino.dart' hide EditableText, EditableTextState;
|
||||
import 'package:flutter/material.dart' hide EditableText, EditableTextState;
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart'
|
||||
show SelectionChangedCause, SuggestionSpan;
|
||||
|
||||
// The default height of the SpellCheckSuggestionsToolbar, which
|
||||
// assumes there are the maximum number of spell check suggestions available, 3.
|
||||
// Size eyeballed on Pixel 4 emulator running Android API 31.
|
||||
const double _kDefaultToolbarHeight = 193.0;
|
||||
|
||||
/// The maximum number of suggestions in the toolbar is 3, plus a delete button.
|
||||
const int _kMaxSuggestions = 3;
|
||||
|
||||
/// The default spell check suggestions toolbar for Android.
|
||||
///
|
||||
/// Tries to position itself below the [anchor], but if it doesn't fit, then it
|
||||
/// readjusts to fit above bottom view insets.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [CupertinoSpellCheckSuggestionsToolbar], which is similar but builds an
|
||||
/// iOS-style spell check toolbar.
|
||||
class SpellCheckSuggestionsToolbar extends StatelessWidget {
|
||||
/// Constructs a [SpellCheckSuggestionsToolbar].
|
||||
///
|
||||
/// [buttonItems] must not contain more than four items, generally three
|
||||
/// suggestions and one delete button.
|
||||
const SpellCheckSuggestionsToolbar(
|
||||
{super.key, required this.anchor, required this.buttonItems})
|
||||
: assert(buttonItems.length <= _kMaxSuggestions + 1);
|
||||
|
||||
/// Constructs a [SpellCheckSuggestionsToolbar] with the default children for
|
||||
/// an [EditableText].
|
||||
///
|
||||
/// See also:
|
||||
/// * [CupertinoSpellCheckSuggestionsToolbar.editableText], which is similar
|
||||
/// but builds an iOS-style toolbar.
|
||||
SpellCheckSuggestionsToolbar.editableText({
|
||||
super.key,
|
||||
required EditableTextState editableTextState,
|
||||
}) : buttonItems =
|
||||
buildButtonItems(editableTextState) ?? <ContextMenuButtonItem>[],
|
||||
anchor = getToolbarAnchor(editableTextState.contextMenuAnchors);
|
||||
|
||||
/// {@template flutter.material.SpellCheckSuggestionsToolbar.anchor}
|
||||
/// The focal point below which the toolbar attempts to position itself.
|
||||
/// {@endtemplate}
|
||||
final Offset anchor;
|
||||
|
||||
/// The [ContextMenuButtonItem]s that will be turned into the correct button
|
||||
/// widgets and displayed in the spell check suggestions toolbar.
|
||||
///
|
||||
/// Must not contain more than four items, typically three suggestions and a
|
||||
/// delete button.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AdaptiveTextSelectionToolbar.buttonItems], the list of
|
||||
/// [ContextMenuButtonItem]s that are used to build the buttons of the
|
||||
/// text selection toolbar.
|
||||
/// * [CupertinoSpellCheckSuggestionsToolbar.buttonItems], the list of
|
||||
/// [ContextMenuButtonItem]s used to build the Cupertino style spell check
|
||||
/// suggestions toolbar.
|
||||
final List<ContextMenuButtonItem> buttonItems;
|
||||
|
||||
/// Builds the button items for the toolbar based on the available
|
||||
/// spell check suggestions.
|
||||
static List<ContextMenuButtonItem>? buildButtonItems(
|
||||
EditableTextState editableTextState) {
|
||||
// Determine if composing region is misspelled.
|
||||
final SuggestionSpan? spanAtCursorIndex =
|
||||
editableTextState.findSuggestionSpanAtCursorIndex(
|
||||
editableTextState.currentTextEditingValue.selection.baseOffset,
|
||||
);
|
||||
|
||||
if (spanAtCursorIndex == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];
|
||||
|
||||
// Build suggestion buttons.
|
||||
for (final String suggestion
|
||||
in spanAtCursorIndex.suggestions.take(_kMaxSuggestions)) {
|
||||
buttonItems.add(
|
||||
ContextMenuButtonItem(
|
||||
onPressed: () {
|
||||
if (!editableTextState.mounted) {
|
||||
return;
|
||||
}
|
||||
_replaceText(
|
||||
editableTextState, suggestion, spanAtCursorIndex.range);
|
||||
},
|
||||
label: suggestion,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Build delete button.
|
||||
final ContextMenuButtonItem deleteButton = ContextMenuButtonItem(
|
||||
onPressed: () {
|
||||
if (!editableTextState.mounted) {
|
||||
return;
|
||||
}
|
||||
_replaceText(editableTextState, '',
|
||||
editableTextState.currentTextEditingValue.composing);
|
||||
},
|
||||
type: ContextMenuButtonType.delete,
|
||||
);
|
||||
buttonItems.add(deleteButton);
|
||||
|
||||
return buttonItems;
|
||||
}
|
||||
|
||||
static void _replaceText(
|
||||
EditableTextState editableTextState,
|
||||
String text,
|
||||
TextRange replacementRange,
|
||||
) {
|
||||
// Replacement cannot be performed if the text is read only or obscured.
|
||||
assert(!editableTextState.widget.readOnly &&
|
||||
!editableTextState.widget.obscureText);
|
||||
|
||||
final TextEditingValue newValue =
|
||||
editableTextState.textEditingValue.replaced(
|
||||
replacementRange,
|
||||
text,
|
||||
);
|
||||
editableTextState.userUpdateTextEditingValue(
|
||||
newValue, SelectionChangedCause.toolbar);
|
||||
|
||||
// Schedule a call to bringIntoView() after renderEditable updates.
|
||||
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
|
||||
if (editableTextState.mounted) {
|
||||
editableTextState
|
||||
.bringIntoView(editableTextState.textEditingValue.selection.extent);
|
||||
}
|
||||
}, debugLabel: 'SpellCheckerSuggestionsToolbar.bringIntoView');
|
||||
editableTextState.hideToolbar();
|
||||
}
|
||||
|
||||
/// Determines the Offset that the toolbar will be anchored to.
|
||||
static Offset getToolbarAnchor(TextSelectionToolbarAnchors anchors) {
|
||||
// Since this will be positioned below the anchor point, use the secondary
|
||||
// anchor by default.
|
||||
return anchors.secondaryAnchor == null
|
||||
? anchors.primaryAnchor
|
||||
: anchors.secondaryAnchor!;
|
||||
}
|
||||
|
||||
/// Builds the toolbar buttons based on the [buttonItems].
|
||||
List<Widget> _buildToolbarButtons(BuildContext context) {
|
||||
return buttonItems.map((ContextMenuButtonItem buttonItem) {
|
||||
final TextSelectionToolbarTextButton button =
|
||||
TextSelectionToolbarTextButton(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
|
||||
onPressed: buttonItem.onPressed,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
AdaptiveTextSelectionToolbar.getButtonLabel(context, buttonItem),
|
||||
style: buttonItem.type == ContextMenuButtonType.delete
|
||||
? const TextStyle(color: Colors.blue)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
|
||||
if (buttonItem.type != ContextMenuButtonType.delete) {
|
||||
return button;
|
||||
}
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(top: BorderSide(color: Colors.grey))),
|
||||
child: button,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (buttonItems.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
// Adjust toolbar height if needed.
|
||||
final double spellCheckSuggestionsToolbarHeight =
|
||||
_kDefaultToolbarHeight - (48.0 * (4 - buttonItems.length));
|
||||
// Incorporate the padding distance between the content and toolbar.
|
||||
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
||||
final double softKeyboardViewInsetsBottom =
|
||||
mediaQueryData.viewInsets.bottom;
|
||||
final double paddingAbove = mediaQueryData.padding.top +
|
||||
CupertinoTextSelectionToolbar.kToolbarScreenPadding;
|
||||
// Makes up for the Padding.
|
||||
final Offset localAdjustment = Offset(
|
||||
CupertinoTextSelectionToolbar.kToolbarScreenPadding,
|
||||
paddingAbove,
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
CupertinoTextSelectionToolbar.kToolbarScreenPadding,
|
||||
paddingAbove,
|
||||
CupertinoTextSelectionToolbar.kToolbarScreenPadding,
|
||||
CupertinoTextSelectionToolbar.kToolbarScreenPadding +
|
||||
softKeyboardViewInsetsBottom,
|
||||
),
|
||||
child: CustomSingleChildLayout(
|
||||
delegate: SpellCheckSuggestionsToolbarLayoutDelegate(
|
||||
anchor: anchor - localAdjustment),
|
||||
child: AnimatedSize(
|
||||
// This duration was eyeballed on a Pixel 2 emulator running Android
|
||||
// API 28 for the Material TextSelectionToolbar.
|
||||
duration: const Duration(milliseconds: 140),
|
||||
child: _SpellCheckSuggestionsToolbarContainer(
|
||||
height: spellCheckSuggestionsToolbarHeight,
|
||||
children: <Widget>[..._buildToolbarButtons(context)],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The Material-styled toolbar outline for the spell check suggestions
|
||||
/// toolbar.
|
||||
class _SpellCheckSuggestionsToolbarContainer extends StatelessWidget {
|
||||
const _SpellCheckSuggestionsToolbarContainer(
|
||||
{required this.height, required this.children});
|
||||
|
||||
final double height;
|
||||
final List<Widget> children;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
// This elevation was eyeballed on a Pixel 4 emulator running Android
|
||||
// API 31 for the SpellCheckSuggestionsToolbar.
|
||||
elevation: 2.0,
|
||||
type: MaterialType.card,
|
||||
child: SizedBox(
|
||||
// This width was eyeballed on a Pixel 4 emulator running Android
|
||||
// API 31 for the SpellCheckSuggestionsToolbar.
|
||||
width: 165.0,
|
||||
height: height,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user