Unverified Commit e0ad1296 authored by luckysmg's avatar luckysmg Committed by GitHub

[framework] Add textField OCR support for framework side (#96637)

iOS OCR keyboard input support.
parent 2f7614a8
......@@ -25,6 +25,7 @@ export 'src/services/hardware_keyboard.dart';
export 'src/services/keyboard_inserted_content.dart';
export 'src/services/keyboard_key.g.dart';
export 'src/services/keyboard_maps.g.dart';
export 'src/services/live_text.dart';
export 'src/services/message_codec.dart';
export 'src/services/message_codecs.dart';
export 'src/services/mouse_cursor.dart';
......
......@@ -94,6 +94,7 @@ class CupertinoAdaptiveTextSelectionToolbar extends StatelessWidget {
required VoidCallback? onCut,
required VoidCallback? onPaste,
required VoidCallback? onSelectAll,
required VoidCallback? onLiveTextInput,
required this.anchors,
}) : children = null,
buttonItems = EditableText.getEditableButtonItems(
......@@ -102,6 +103,7 @@ class CupertinoAdaptiveTextSelectionToolbar extends StatelessWidget {
onCut: onCut,
onPaste: onPaste,
onSelectAll: onSelectAll,
onLiveTextInput: onLiveTextInput
);
/// Create an instance of [CupertinoAdaptiveTextSelectionToolbar] with the
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';
import 'package:flutter/widgets.dart';
import 'button.dart';
......@@ -103,6 +105,7 @@ class CupertinoTextSelectionToolbarButton extends StatefulWidget {
return localizations.pasteButtonLabel;
case ContextMenuButtonType.selectAll:
return localizations.selectAllButtonLabel;
case ContextMenuButtonType.liveTextInput:
case ContextMenuButtonType.delete:
case ContextMenuButtonType.custom:
return '';
......@@ -131,6 +134,7 @@ class _CupertinoTextSelectionToolbarButtonState extends State<CupertinoTextSelec
@override
Widget build(BuildContext context) {
final Widget content = _getContentWidget(context);
final Widget child = CupertinoButton(
color: isPressed
? _kToolbarPressedColor.resolveFrom(context)
......@@ -145,15 +149,7 @@ class _CupertinoTextSelectionToolbarButtonState extends State<CupertinoTextSelec
// There's no foreground fade on iOS toolbar anymore, just the background
// is darkened.
pressedOpacity: 1.0,
child: widget.child ?? Text(
widget.text ?? CupertinoTextSelectionToolbarButton.getButtonLabel(context, widget.buttonItem!),
overflow: TextOverflow.ellipsis,
style: _kToolbarButtonFontStyle.copyWith(
color: widget.onPressed != null
? _kToolbarTextColor.resolveFrom(context)
: CupertinoColors.inactiveGray,
),
),
child: content,
);
if (widget.onPressed != null) {
......@@ -170,4 +166,85 @@ class _CupertinoTextSelectionToolbarButtonState extends State<CupertinoTextSelec
return child;
}
}
Widget _getContentWidget(BuildContext context) {
if (widget.child != null) {
return widget.child!;
}
final Widget textWidget = Text(
widget.text ?? CupertinoTextSelectionToolbarButton.getButtonLabel(context, widget.buttonItem!),
overflow: TextOverflow.ellipsis,
style: _kToolbarButtonFontStyle.copyWith(
color: widget.onPressed != null
? _kToolbarTextColor.resolveFrom(context)
: CupertinoColors.inactiveGray,
),
);
if (widget.buttonItem == null) {
return textWidget;
}
switch (widget.buttonItem!.type) {
case ContextMenuButtonType.cut:
case ContextMenuButtonType.copy:
case ContextMenuButtonType.paste:
case ContextMenuButtonType.selectAll:
case ContextMenuButtonType.delete:
case ContextMenuButtonType.custom:
return textWidget;
case ContextMenuButtonType.liveTextInput:
return SizedBox(
width: 13.0,
height: 13.0,
child: CustomPaint(
painter: _LiveTextIconPainter(color: _kToolbarTextColor.resolveFrom(context)),
),
);
}
}
}
class _LiveTextIconPainter extends CustomPainter {
_LiveTextIconPainter({required this.color});
final Color color;
final Paint _painter = Paint()
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
..strokeWidth = 1.0
..style = PaintingStyle.stroke;
@override
void paint(Canvas canvas, Size size) {
_painter.color = color;
canvas.save();
canvas.translate(size.width / 2.0, size.height / 2.0);
final Offset origin = Offset(-size.width / 2.0, -size.height / 2.0);
// Path for the one corner.
final Path path = Path()
..moveTo(origin.dx, origin.dy + 3.5)
..lineTo(origin.dx, origin.dy + 1.0)
..arcToPoint(Offset(origin.dx + 1.0, origin.dy), radius: const Radius.circular(1))
..lineTo(origin.dx + 3.5, origin.dy);
// Rotate to draw corner four times.
final Matrix4 rotationMatrix = Matrix4.identity()..rotateZ(pi / 2.0);
for (int i = 0; i < 4; i += 1) {
canvas.drawPath(path, _painter);
canvas.transform(rotationMatrix.storage);
}
// Draw three lines.
canvas.drawLine(const Offset(-3.0, -3.0), const Offset(3.0, -3.0), _painter);
canvas.drawLine(const Offset(-3.0, 0.0), const Offset(3.0, 0.0), _painter);
canvas.drawLine(const Offset(-3.0, 3.0), const Offset(1.0, 3.0), _painter);
canvas.restore();
}
@override
bool shouldRepaint(covariant _LiveTextIconPainter oldDelegate) {
return oldDelegate.color != color;
}
}
......@@ -103,6 +103,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget {
required VoidCallback? onCut,
required VoidCallback? onPaste,
required VoidCallback? onSelectAll,
required VoidCallback? onLiveTextInput,
required this.anchors,
}) : children = null,
buttonItems = EditableText.getEditableButtonItems(
......@@ -111,6 +112,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget {
onCut: onCut,
onPaste: onPaste,
onSelectAll: onSelectAll,
onLiveTextInput: onLiveTextInput
);
/// Create an instance of [AdaptiveTextSelectionToolbar] with the default
......@@ -213,6 +215,8 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget {
return localizations.selectAllButtonLabel;
case ContextMenuButtonType.delete:
return localizations.deleteButtonTooltip.toUpperCase();
case ContextMenuButtonType.liveTextInput:
return localizations.scanTextButtonLabel;
case ContextMenuButtonType.custom:
return '';
}
......@@ -242,9 +246,8 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget {
switch (Theme.of(context).platform) {
case TargetPlatform.iOS:
return buttonItems.map((ContextMenuButtonItem buttonItem) {
return CupertinoTextSelectionToolbarButton.text(
onPressed: buttonItem.onPressed,
text: getButtonLabel(context, buttonItem),
return CupertinoTextSelectionToolbarButton.buttonItem(
buttonItem: buttonItem,
);
});
case TargetPlatform.fuchsia:
......
......@@ -103,6 +103,9 @@ abstract class MaterialLocalizations {
/// Label for "cut" edit buttons and menu items.
String get cutButtonLabel;
/// Label for "scan text" OCR edit buttons and menu items.
String get scanTextButtonLabel;
/// Label for OK buttons and menu items.
String get okButtonLabel;
......@@ -1159,6 +1162,9 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
@override
String get cutButtonLabel => 'Cut';
@override
String get scanTextButtonLabel => 'Scan text';
@override
String get okButtonLabel => 'OK';
......
// 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 'system_channels.dart';
/// Utility methods for interacting with the system's Live Text.
///
/// For example, the Live Text input feature of iOS turns the keyboard into a camera view for
/// directly inserting text obtained through OCR into the active field.
///
/// See also:
/// * <https://developer.apple.com/documentation/uikit/uiresponder/3778577-capturetextfromcamera>
/// * <https://support.apple.com/guide/iphone/use-live-text-iphcf0b71b0e/ios>
class LiveText {
// This class is not meant to be instantiated or extended; this constructor
// prevents instantiation and extension.
LiveText._();
/// Returns true if the Live Text input feature is available on the current device.
static Future<bool> isLiveTextInputAvailable() async {
final bool supportLiveTextInput =
await SystemChannels.platform.invokeMethod('LiveText.isLiveTextInputAvailable') ?? false;
return supportLiveTextInput;
}
/// Start Live Text input.
///
/// If any [TextInputConnection] is currently active, calling this method will tell the text field
/// to start Live Text input. If the current device doesn't support Live Text input,
/// nothing will happen.
static void startLiveTextInput() {
SystemChannels.textInput.invokeMethod('TextInput.startLiveTextInput');
}
}
......@@ -1050,6 +1050,13 @@ mixin TextSelectionDelegate {
/// Whether select all is enabled, must not be null.
bool get selectAllEnabled => true;
/// Whether Live Text input is enabled.
///
/// See also:
/// * [LiveText], where the availability of Live Text input can be obtained.
/// * [LiveTextInputStatusNotifier], where the status of Live Text can be listened to.
bool get liveTextInputEnabled => false;
/// Cut current selection to [Clipboard].
///
/// If and only if [cause] is [SelectionChangedCause.toolbar], the toolbar
......
......@@ -26,6 +26,13 @@ enum ContextMenuButtonType {
/// A button that deletes the current text selection.
delete,
/// A button for starting Live Text input.
///
/// See also:
/// * [LiveText], where the availability of Live Text input can be obtained.
/// * [LiveTextInputStatusNotifier], where the status of Live Text can be listened to.
liveTextInput,
/// Anything other than the default button types.
custom,
}
......
......@@ -1835,36 +1835,48 @@ class EditableText extends StatefulWidget {
required final VoidCallback? onCut,
required final VoidCallback? onPaste,
required final VoidCallback? onSelectAll,
required final VoidCallback? onLiveTextInput,
}) {
// If the paste button is enabled, don't render anything until the state
// of the clipboard is known, since it's used to determine if paste is
// shown.
if (onPaste != null && clipboardStatus == ClipboardStatus.unknown) {
return <ContextMenuButtonItem>[];
final List<ContextMenuButtonItem> resultButtonItem = <ContextMenuButtonItem>[];
// Configure button items with clipboard.
if (onPaste == null || clipboardStatus != ClipboardStatus.unknown) {
// If the paste button is enabled, don't render anything until the state
// of the clipboard is known, since it's used to determine if paste is
// shown.
resultButtonItem.addAll(<ContextMenuButtonItem>[
if (onCut != null)
ContextMenuButtonItem(
onPressed: onCut,
type: ContextMenuButtonType.cut,
),
if (onCopy != null)
ContextMenuButtonItem(
onPressed: onCopy,
type: ContextMenuButtonType.copy,
),
if (onPaste != null)
ContextMenuButtonItem(
onPressed: onPaste,
type: ContextMenuButtonType.paste,
),
if (onSelectAll != null)
ContextMenuButtonItem(
onPressed: onSelectAll,
type: ContextMenuButtonType.selectAll,
),
]);
}
return <ContextMenuButtonItem>[
if (onCut != null)
ContextMenuButtonItem(
onPressed: onCut,
type: ContextMenuButtonType.cut,
),
if (onCopy != null)
ContextMenuButtonItem(
onPressed: onCopy,
type: ContextMenuButtonType.copy,
),
if (onPaste != null)
ContextMenuButtonItem(
onPressed: onPaste,
type: ContextMenuButtonType.paste,
),
if (onSelectAll != null)
ContextMenuButtonItem(
onPressed: onSelectAll,
type: ContextMenuButtonType.selectAll,
),
];
// Config button items with Live Text.
if (onLiveTextInput != null) {
resultButtonItem.add(ContextMenuButtonItem(
onPressed: onLiveTextInput,
type: ContextMenuButtonType.liveTextInput,
));
}
return resultButtonItem;
}
// Infer the keyboard type of an `EditableText` if it's not specified.
......@@ -2063,6 +2075,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
/// Detects whether the clipboard can paste.
final ClipboardStatusNotifier clipboardStatus = ClipboardStatusNotifier();
/// Detects whether the Live Text input is enabled.
///
/// See also:
/// * [LiveText], where the availability of Live Text input can be obtained.
final LiveTextInputStatusNotifier? _liveTextInputStatus =
kIsWeb ? null : LiveTextInputStatusNotifier();
TextInputConnection? _textInputConnection;
bool get _hasInputConnection => _textInputConnection?.attached ?? false;
......@@ -2196,12 +2215,26 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
@override
bool get liveTextInputEnabled {
return _liveTextInputStatus?.value == LiveTextInputStatus.enabled &&
!widget.obscureText &&
!widget.readOnly &&
textEditingValue.selection.isCollapsed;
}
void _onChangedClipboardStatus() {
setState(() {
// Inform the widget that the value of clipboardStatus has changed.
});
}
void _onChangedLiveTextInputStatus() {
setState(() {
// Inform the widget that the value of liveTextInputStatus has changed.
});
}
TextEditingValue get _textEditingValueforTextLayoutMetrics {
final Widget? editableWidget =_editableKey.currentContext?.widget;
if (editableWidget is! _Editable) {
......@@ -2347,6 +2380,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
void _startLiveTextInput(SelectionChangedCause cause) {
if (!liveTextInputEnabled) {
return;
}
if (_hasInputConnection) {
LiveText.startLiveTextInput();
}
if (cause == SelectionChangedCause.toolbar) {
hideToolbar();
}
}
/// Finds specified [SuggestionSpan] that matches the provided index using
/// binary search.
///
......@@ -2561,6 +2606,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
onSelectAll: selectAllEnabled
? () => selectAll(SelectionChangedCause.toolbar)
: null,
onLiveTextInput: liveTextInputEnabled
? () => _startLiveTextInput(SelectionChangedCause.toolbar)
: null,
);
}
......@@ -2569,6 +2617,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
void initState() {
super.initState();
_liveTextInputStatus?.addListener(_onChangedLiveTextInputStatus);
clipboardStatus.addListener(_onChangedClipboardStatus);
widget.controller.addListener(_didChangeTextEditingValue);
widget.focusNode.addListener(_handleFocusChanged);
......@@ -2734,6 +2783,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_selectionOverlay = null;
widget.focusNode.removeListener(_handleFocusChanged);
WidgetsBinding.instance.removeObserver(this);
_liveTextInputStatus?.removeListener(_onChangedLiveTextInputStatus);
_liveTextInputStatus?.dispose();
clipboardStatus.removeListener(_onChangedClipboardStatus);
clipboardStatus.dispose();
_cursorVisibilityNotifier.dispose();
......@@ -3986,6 +4037,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if (_selectionOverlay == null) {
return false;
}
_liveTextInputStatus?.update();
clipboardStatus.update();
_selectionOverlay!.showToolbar();
return true;
......
......@@ -3383,6 +3383,115 @@ enum ClipboardStatus {
notPasteable,
}
/// A [ValueNotifier] whose [value] indicates whether the current device supports the Live Text
/// (OCR) function.
///
/// See also:
/// * [LiveText], where the availability of Live Text input can be obtained.
/// * [LiveTextInputStatus], an enumeration that indicates whether the current device is available
/// for Live Text input.
///
/// Call [update] to asynchronously update [value] if needed.
class LiveTextInputStatusNotifier extends ValueNotifier<LiveTextInputStatus> with WidgetsBindingObserver {
/// Create a new LiveTextStatusNotifier.
LiveTextInputStatusNotifier({
LiveTextInputStatus value = LiveTextInputStatus.unknown,
}) : super(value);
bool _disposed = false;
/// Check the [LiveTextInputStatus] and update [value] if needed.
Future<void> update() async {
if (_disposed) {
return;
}
final bool isLiveTextInputEnabled;
try {
isLiveTextInputEnabled = await LiveText.isLiveTextInputAvailable();
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widget library',
context: ErrorDescription('while checking the availability of Live Text input'),
));
// In the case of an error from the Live Text API, set the value to
// unknown so that it will try to update again later.
if (_disposed || value == LiveTextInputStatus.unknown) {
return;
}
value = LiveTextInputStatus.unknown;
return;
}
final LiveTextInputStatus nextStatus = isLiveTextInputEnabled
? LiveTextInputStatus.enabled
: LiveTextInputStatus.disabled;
if (_disposed || nextStatus == value) {
return;
}
value = nextStatus;
}
@override
void addListener(VoidCallback listener) {
if (!hasListeners) {
WidgetsBinding.instance.addObserver(this);
}
if (value == LiveTextInputStatus.unknown) {
update();
}
super.addListener(listener);
}
@override
void removeListener(VoidCallback listener) {
super.removeListener(listener);
if (!_disposed && !hasListeners) {
WidgetsBinding.instance.removeObserver(this);
}
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
update();
case AppLifecycleState.detached:
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.hidden:
// Nothing to do.
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_disposed = true;
super.dispose();
}
}
/// An enumeration that indicates whether the current device is available for Live Text input.
///
/// See also:
/// * [LiveText], where the availability of Live Text input can be obtained.
enum LiveTextInputStatus {
/// This device supports Live Text input currently.
enabled,
/// The status of the Live Text input is unknown. Since getting the Live Text input availability
/// is asynchronous (see [LiveText.isLiveTextInputAvailable]), this status often exists while
/// waiting to receive the status value for the first time.
unknown,
/// The current device doesn't support Live Text input.
disabled,
}
// TODO(justinmc): Deprecate this after TextSelectionControls.buildToolbar is
// deleted, when users should migrate back to TextSelectionControls.buildHandle.
// See https://github.com/flutter/flutter/pull/124262
......
......@@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/clipboard_utils.dart';
import '../widgets/live_text_utils.dart';
void main() {
final MockClipboard mockClipboard = MockClipboard();
......@@ -166,6 +167,7 @@ void main() {
onCut: () {},
onPaste: () {},
onSelectAll: () {},
onLiveTextInput: () {},
),
),
));
......@@ -178,13 +180,16 @@ void main() {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(5));
case TargetPlatform.fuchsia:
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(5));
case TargetPlatform.iOS:
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(4));
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(5));
expect(findLiveTextButton(), findsOneWidget);
case TargetPlatform.macOS:
case TargetPlatform.linux:
case TargetPlatform.windows:
expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(4));
expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(5));
}
},
skip: kIsWeb, // [intended] on web the browser handles the context menu.
......
......@@ -21,6 +21,7 @@ import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart' show OverflowWidgetTextEditingController;
import '../widgets/live_text_utils.dart';
import '../widgets/semantics_tester.dart';
// On web, the context menu (aka toolbar) is provided by the browser.
......@@ -199,12 +200,56 @@ void main() {
Offset textOffsetToPosition(WidgetTester tester, int offset) => textOffsetToBottomLeftPosition(tester, offset) + const Offset(kIsWeb ? 1 : 0, -2);
setUp(() async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall);
EditableText.debugDeterministicCursor = false;
// Fill the clipboard so that the Paste option is available in the text
// selection menu.
await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
});
testWidgets(
'Live Text button shows and hides correctly when LiveTextStatus changes',
(WidgetTester tester) async {
final LiveTextInputTester liveTextInputTester = LiveTextInputTester();
addTearDown(liveTextInputTester.dispose);
final TextEditingController controller = TextEditingController(text: '');
const Key key = ValueKey<String>('TextField');
final FocusNode focusNode = FocusNode();
final Widget app = MaterialApp(
theme: ThemeData(platform: TargetPlatform.iOS),
home: Scaffold(
body: Center(
child: CupertinoTextField(
key: key,
controller: controller,
focusNode: focusNode,
),
),
),
);
liveTextInputTester.mockLiveTextInputEnabled = true;
await tester.pumpWidget(app);
focusNode.requestFocus();
await tester.pumpAndSettle();
final Finder textFinder = find.byType(EditableText);
await tester.longPress(textFinder);
await tester.pumpAndSettle();
expect(
findLiveTextButton(),
kIsWeb ? findsNothing : findsOneWidget,
);
liveTextInputTester.mockLiveTextInputEnabled = false;
await tester.longPress(textFinder);
await tester.pumpAndSettle();
expect(findLiveTextButton(), findsNothing);
},
);
testWidgets('can use the desktop cut/copy/paste buttons on Mac', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(
text: 'blah1 blah2',
......
......@@ -11,6 +11,7 @@ import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart';
import '../widgets/live_text_utils.dart';
void main() {
final MockClipboard mockClipboard = MockClipboard();
......@@ -184,6 +185,7 @@ void main() {
onCut: () {},
onPaste: () {},
onSelectAll: () {},
onLiveTextInput: () {},
),
),
),
......@@ -199,17 +201,18 @@ void main() {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
expect(find.text('Select all'), findsOneWidget);
expect(find.byType(TextSelectionToolbarTextButton), findsNWidgets(4));
expect(find.byType(TextSelectionToolbarTextButton), findsNWidgets(5));
case TargetPlatform.iOS:
expect(find.text('Select All'), findsOneWidget);
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(4));
expect(findLiveTextButton(), findsOneWidget);
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(5));
case TargetPlatform.linux:
case TargetPlatform.windows:
expect(find.text('Select all'), findsOneWidget);
expect(find.byType(DesktopTextSelectionToolbarButton), findsNWidgets(4));
expect(find.byType(DesktopTextSelectionToolbarButton), findsNWidgets(5));
case TargetPlatform.macOS:
expect(find.text('Select All'), findsOneWidget);
expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(4));
expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(5));
}
},
skip: kIsWeb, // [intended] on web the browser handles the context menu.
......
......@@ -28,6 +28,7 @@ void main() {
expect(localizations.continueButtonLabel, isNotNull);
expect(localizations.copyButtonLabel, isNotNull);
expect(localizations.cutButtonLabel, isNotNull);
expect(localizations.scanTextButtonLabel, isNotNull);
expect(localizations.okButtonLabel, isNotNull);
expect(localizations.pasteButtonLabel, isNotNull);
expect(localizations.selectAllButtonLabel, isNotNull);
......
......@@ -26,6 +26,7 @@ import 'package:flutter_test/flutter_test.dart';
import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart';
import '../widgets/live_text_utils.dart';
import '../widgets/semantics_tester.dart';
import 'feedback_tester.dart';
......@@ -203,6 +204,47 @@ void main() {
);
}
testWidgets(
'Live Text button shows and hides correctly when LiveTextStatus changes',
(WidgetTester tester) async {
final LiveTextInputTester liveTextInputTester = LiveTextInputTester();
addTearDown(liveTextInputTester.dispose);
final TextEditingController controller = TextEditingController(text: '');
const Key key = ValueKey<String>('TextField');
final FocusNode focusNode = FocusNode();
final Widget app = MaterialApp(
theme: ThemeData(platform: TargetPlatform.iOS),
home: Scaffold(
body: Center(
child: TextField(
key: key,
controller: controller,
focusNode: focusNode,
),
),
),
);
liveTextInputTester.mockLiveTextInputEnabled = true;
await tester.pumpWidget(app);
focusNode.requestFocus();
await tester.pumpAndSettle();
final Finder textFinder = find.byType(EditableText);
await tester.longPress(textFinder);
await tester.pumpAndSettle();
expect(
findLiveTextButton(),
kIsWeb ? findsNothing : findsOneWidget,
);
liveTextInputTester.mockLiveTextInputEnabled = false;
await tester.longPress(textFinder);
await tester.pumpAndSettle();
expect(findLiveTextButton(), findsNothing);
},
);
testWidgets('text field selection toolbar should hide when the user starts typing', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
......
......@@ -15,6 +15,7 @@ import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/clipboard_utils.dart';
import 'editable_text_utils.dart';
import 'live_text_utils.dart';
import 'semantics_tester.dart';
Matcher matchesMethodCall(String method, { dynamic args }) => _MatchesMethodCall(method, arguments: args == null ? null : wrapMatcher(args));
......@@ -118,6 +119,72 @@ void main() {
expect(tester.testTextInput.setClientArgs!['inputAction'], equals(serializedActionName));
}
testWidgets(
'Tapping the Live Text button calls onLiveTextInput',
(WidgetTester tester) async {
bool invokedLiveTextInputSuccessfully = false;
final GlobalKey key = GlobalKey();
final TextEditingController controller = TextEditingController(text: '');
await tester.pumpWidget(
MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 400,
child: EditableText(
maxLines: 10,
controller: controller,
showSelectionHandles: true,
autofocus: true,
focusNode: FocusNode(),
style: Typography.material2018().black.subtitle1!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
keyboardType: TextInputType.text,
textAlign: TextAlign.right,
selectionControls: materialTextSelectionHandleControls,
contextMenuBuilder: (
BuildContext context,
EditableTextState editableTextState,
) {
return CupertinoAdaptiveTextSelectionToolbar.editable(
key: key,
clipboardStatus: ClipboardStatus.pasteable,
onCopy: null,
onCut: null,
onPaste: null,
onSelectAll: null,
onLiveTextInput: () {
invokedLiveTextInputSuccessfully = true;
},
anchors: const TextSelectionToolbarAnchors(primaryAnchor: Offset.zero),
);
},
),
),
),
),
);
await tester.pump(); // Wait for autofocus to take effect.
expect(find.byKey(key), findsNothing);
// Long-press to bring up the context menu.
final Finder textFinder = find.byType(EditableText);
await tester.longPress(textFinder);
tester.state<EditableTextState>(textFinder).showToolbar();
await tester.pumpAndSettle();
expect(find.byKey(key), findsOneWidget);
expect(findLiveTextButton(), findsOneWidget);
await tester.tap(findLiveTextButton());
await tester.pump();
expect(invokedLiveTextInputSuccessfully, isTrue);
},
skip: kIsWeb, // [intended]
);
// Regression test for https://github.com/flutter/flutter/issues/126312.
testWidgets('when open input connection in didUpdateWidget, should not throw', (WidgetTester tester) async {
final Key key = GlobalKey();
......
// 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:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
/// A mock class to control the return result of Live Text input functions.
class LiveTextInputTester {
LiveTextInputTester() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, _handler);
}
bool mockLiveTextInputEnabled = false;
Future<Object?> _handler(MethodCall methodCall) async {
// Need to set Clipboard.hasStrings method handler because when showing the tool bar,
// the Clipboard.hasStrings will also be invoked. If this isn't handled,
// an exception will be thrown.
if (methodCall.method == 'Clipboard.hasStrings') {
return <String, bool>{'value': true};
}
if (methodCall.method == 'LiveText.isLiveTextInputAvailable') {
return mockLiveTextInputEnabled;
}
return false;
}
void dispose() {
assert(TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.checkMockMessageHandler(SystemChannels.platform.name, _handler));
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null);
}
}
/// A function to find the live text button.
Finder findLiveTextButton() => find.byWidgetPredicate((Widget widget) =>
widget is CustomPaint &&
'${widget.painter?.runtimeType}' == '_LiveTextIconPainter',
);
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Gaan voort",
"copyButtonLabel": "Kopieer",
"cutButtonLabel": "Knip",
"scanTextButtonLabel": "Skena umbhalo",
"okButtonLabel": "OK",
"pasteButtonLabel": "Plak",
"selectAllButtonLabel": "Kies alles",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "ቀጥል",
"copyButtonLabel": "ቅዳ",
"cutButtonLabel": "ቁረጥ",
"scanTextButtonLabel": "ጽሑፍ ይቃኙ",
"okButtonLabel": "እሺ",
"pasteButtonLabel": "ለጥፍ",
"selectAllButtonLabel": "ሁሉንም ምረጥ",
......
......@@ -35,6 +35,7 @@
"continueButtonLabel": "المتابعة",
"copyButtonLabel": "نسخ",
"cutButtonLabel": "قص",
"scanTextButtonLabel": "مسح النص",
"okButtonLabel": "حسنًا",
"pasteButtonLabel": "لصق",
"selectAllButtonLabel": "اختيار الكل",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "অব্যাহত ৰাখক",
"copyButtonLabel": "প্ৰতিলিপি কৰক",
"cutButtonLabel": "কাট কৰক",
"scanTextButtonLabel": "স্কেন টেক্সট",
"okButtonLabel": "ঠিক আছে",
"pasteButtonLabel": "পে'ষ্ট কৰক",
"selectAllButtonLabel": "সকলো বাছনি কৰক",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Davam edin",
"copyButtonLabel": "Kopyalayın",
"cutButtonLabel": "Kəsin",
"scanTextButtonLabel": "Mətni skan edin",
"okButtonLabel": "OK",
"pasteButtonLabel": "Yerləşdirin",
"selectAllButtonLabel": "Hamısını seçin",
......
......@@ -31,6 +31,7 @@
"continueButtonLabel": "Працягнуць",
"copyButtonLabel": "Капіраваць",
"cutButtonLabel": "Выразаць",
"scanTextButtonLabel": "Сканаваць тэкст",
"okButtonLabel": "ОК",
"pasteButtonLabel": "Уставіць",
"selectAllButtonLabel": "Выбраць усе",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Напред",
"copyButtonLabel": "Копиране",
"cutButtonLabel": "Изрязване",
"scanTextButtonLabel": "Сканиране на текст",
"okButtonLabel": "OK",
"pasteButtonLabel": "Поставяне",
"selectAllButtonLabel": "Избиране на всички",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "চালিয়ে যান",
"copyButtonLabel": "কপি করুন",
"cutButtonLabel": "কাট করুন",
"scanTextButtonLabel": "পাঠ্য স্ক্যান করুন",
"okButtonLabel": "ঠিক আছে",
"pasteButtonLabel": "পেস্ট করুন",
"selectAllButtonLabel": "সব বেছে নিন",
......
......@@ -28,6 +28,7 @@
"continueButtonLabel": "Nastavi",
"copyButtonLabel": "Kopiraj",
"cutButtonLabel": "Izreži",
"scanTextButtonLabel": "Skeniraj tekst",
"okButtonLabel": "Uredu",
"pasteButtonLabel": "Zalijepi",
"selectAllButtonLabel": "Odaberi sve",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Continua",
"copyButtonLabel": "Copia",
"cutButtonLabel": "Retalla",
"scanTextButtonLabel": "Escaneja el text",
"okButtonLabel": "D'ACORD",
"pasteButtonLabel": "Enganxa",
"selectAllButtonLabel": "Selecciona-ho tot",
......
......@@ -31,6 +31,7 @@
"continueButtonLabel": "Pokračovat",
"copyButtonLabel": "Kopírovat",
"cutButtonLabel": "Vyjmout",
"scanTextButtonLabel": "Naskenujte text",
"okButtonLabel": "OK",
"pasteButtonLabel": "Vložit",
"selectAllButtonLabel": "Vybrat vše",
......
......@@ -150,5 +150,6 @@
"expansionTileExpandedTapHint": "Collapse",
"expansionTileCollapsedTapHint": "Expand for more details",
"expandedHint": "Collapsed",
"collapsedHint": "Expanded"
"collapsedHint": "Expanded",
"scanTextButtonLabel": "Scan text"
}
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Fortsæt",
"copyButtonLabel": "Kopiér",
"cutButtonLabel": "Klip",
"scanTextButtonLabel": "Scan tekst",
"okButtonLabel": "OK",
"pasteButtonLabel": "Indsæt",
"selectAllButtonLabel": "Markér alt",
......
......@@ -26,6 +26,7 @@
"continueButtonLabel": "Weiter",
"copyButtonLabel": "Kopieren",
"cutButtonLabel": "Ausschneiden",
"scanTextButtonLabel": "Text scannen",
"okButtonLabel": "OK",
"pasteButtonLabel": "Einsetzen",
"selectAllButtonLabel": "Alle auswählen",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Συνέχεια",
"copyButtonLabel": "Αντιγραφή",
"cutButtonLabel": "Αποκοπή",
"scanTextButtonLabel": "Σάρωση κειμένου",
"okButtonLabel": "ΟΚ",
"pasteButtonLabel": "Επικόλληση",
"selectAllButtonLabel": "Επιλογή όλων",
......
......@@ -192,6 +192,11 @@
"description": "The label for cut buttons and menu items."
},
"scanTextButtonLabel": "Scan text",
"@scanTextButtonLabel": {
"description": "The label for scan text buttons and menu items for starting the insertion of text via OCR."
},
"okButtonLabel": "OK",
"@okButtonLabel": {
"description": "The label for OK buttons and menu items."
......
......@@ -26,6 +26,7 @@
"continueButtonLabel": "Continuar",
"copyButtonLabel": "Copiar",
"cutButtonLabel": "Cortar",
"scanTextButtonLabel": "Escanear texto",
"okButtonLabel": "ACEPTAR",
"pasteButtonLabel": "Pegar",
"selectAllButtonLabel": "Seleccionar todo",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Jätka",
"copyButtonLabel": "Kopeeri",
"cutButtonLabel": "Lõika",
"scanTextButtonLabel": "Skanni teksti",
"okButtonLabel": "OK",
"pasteButtonLabel": "Kleebi",
"selectAllButtonLabel": "Vali kõik",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Egin aurrera",
"copyButtonLabel": "Kopiatu",
"cutButtonLabel": "Ebaki",
"scanTextButtonLabel": "Eskaneatu testua",
"okButtonLabel": "Ados",
"pasteButtonLabel": "Itsatsi",
"selectAllButtonLabel": "Hautatu guztiak",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "ادامه",
"copyButtonLabel": "کپی",
"cutButtonLabel": "برش",
"scanTextButtonLabel": "اسکن متن",
"okButtonLabel": "تأیید",
"pasteButtonLabel": "جای‌گذاری",
"selectAllButtonLabel": "انتخاب همه",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Jatka",
"copyButtonLabel": "Kopioi",
"cutButtonLabel": "Leikkaa",
"scanTextButtonLabel": "Skannaa tekstiä",
"okButtonLabel": "OK",
"pasteButtonLabel": "Liitä",
"selectAllButtonLabel": "Valitse kaikki",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Magpatuloy",
"copyButtonLabel": "Kopyahin",
"cutButtonLabel": "I-cut",
"scanTextButtonLabel": "I-scan ang text",
"okButtonLabel": "OK",
"pasteButtonLabel": "I-paste",
"selectAllButtonLabel": "Piliin lahat",
......
......@@ -26,6 +26,7 @@
"continueButtonLabel": "Continuer",
"copyButtonLabel": "Copier",
"cutButtonLabel": "Couper",
"scanTextButtonLabel": "Numériser du texte",
"okButtonLabel": "OK",
"pasteButtonLabel": "Coller",
"selectAllButtonLabel": "Tout sélectionner",
......
......@@ -26,6 +26,7 @@
"continueButtonLabel": "Continuar",
"copyButtonLabel": "Copiar",
"cutButtonLabel": "Cortar",
"scanTextButtonLabel": "Escanear texto",
"okButtonLabel": "Aceptar",
"pasteButtonLabel": "Pegar",
"selectAllButtonLabel": "Seleccionar todo",
......
......@@ -30,6 +30,7 @@
"continueButtonLabel": "Weiter",
"copyButtonLabel": "Kopieren",
"cutButtonLabel": "Ausschneiden",
"scanTextButtonLabel": "Text scannen",
"okButtonLabel": "OK",
"pasteButtonLabel": "Einsetzen",
"selectAllButtonLabel": "Alle auswählen",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "ચાલુ રાખો",
"copyButtonLabel": "કૉપિ કરો",
"cutButtonLabel": "કાપો",
"scanTextButtonLabel": "ટેક્સ્ટ સ્કેન કરો",
"okButtonLabel": "ઓકે",
"pasteButtonLabel": "પેસ્ટ કરો",
"selectAllButtonLabel": "બધા પસંદ કરો",
......
......@@ -31,6 +31,7 @@
"continueButtonLabel": "המשך",
"copyButtonLabel": "העתקה",
"cutButtonLabel": "גזירה",
"scanTextButtonLabel": "סרוק טקסט",
"okButtonLabel": "אישור",
"pasteButtonLabel": "הדבקה",
"selectAllButtonLabel": "בחירת הכול",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "जारी रखें",
"copyButtonLabel": "कॉपी करें",
"cutButtonLabel": "काटें",
"scanTextButtonLabel": "पाठ स्कैन करें",
"okButtonLabel": "ठीक है",
"pasteButtonLabel": "चिपकाएं",
"selectAllButtonLabel": "सभी को चुनें",
......
......@@ -28,6 +28,7 @@
"continueButtonLabel": "Nastavi",
"copyButtonLabel": "Kopiraj",
"cutButtonLabel": "Izreži",
"scanTextButtonLabel": "Skeniraj tekst",
"okButtonLabel": "U REDU",
"pasteButtonLabel": "Zalijepi",
"selectAllButtonLabel": "Odaberi sve",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Folytatás",
"copyButtonLabel": "Másolás",
"cutButtonLabel": "Kivágás",
"scanTextButtonLabel": "Szöveg beolvasása",
"okButtonLabel": "OK",
"pasteButtonLabel": "Beillesztés",
"selectAllButtonLabel": "Összes kijelölése",
......
......@@ -30,6 +30,7 @@
"continueButtonLabel": "Շարունակել",
"copyButtonLabel": "Պատճենել",
"cutButtonLabel": "Կտրել",
"scanTextButtonLabel": "Սկանավորեք տեքստը",
"okButtonLabel": "Եղավ",
"pasteButtonLabel": "Տեղադրել",
"selectAllButtonLabel": "Նշել բոլորը",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Lanjutkan",
"copyButtonLabel": "Salin",
"cutButtonLabel": "Potong",
"scanTextButtonLabel": "Pindai teks",
"okButtonLabel": "OKE",
"pasteButtonLabel": "Tempel",
"selectAllButtonLabel": "Pilih semua",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Áfram",
"copyButtonLabel": "Afrita",
"cutButtonLabel": "Klippa",
"scanTextButtonLabel": "Skannaðu texta",
"okButtonLabel": "Í lagi",
"pasteButtonLabel": "Líma",
"selectAllButtonLabel": "Velja allt",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Continua",
"copyButtonLabel": "Copia",
"cutButtonLabel": "Taglia",
"scanTextButtonLabel": "Scansiona il testo",
"okButtonLabel": "OK",
"pasteButtonLabel": "Incolla",
"selectAllButtonLabel": "Seleziona tutto",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "続行",
"copyButtonLabel": "コピー",
"cutButtonLabel": "切り取り",
"scanTextButtonLabel": "テキストをスキャン",
"okButtonLabel": "OK",
"pasteButtonLabel": "貼り付け",
"selectAllButtonLabel": "すべて選択",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "გაგრძელება",
"copyButtonLabel": "კოპირება",
"cutButtonLabel": "ამოჭრა",
"scanTextButtonLabel": "ტექსტის სკანირება",
"okButtonLabel": "კარგი",
"pasteButtonLabel": "ჩასმა",
"selectAllButtonLabel": "ყველას არჩევა",
......
......@@ -27,6 +27,7 @@
"continueButtonLabel": "Жалғастыру",
"copyButtonLabel": "Көшіру",
"cutButtonLabel": "Қию",
"scanTextButtonLabel": "Мәтінді сканерлеу",
"okButtonLabel": "Иә",
"pasteButtonLabel": "Қою",
"selectAllButtonLabel": "Барлығын таңдау",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "បន្ត",
"copyButtonLabel": "ចម្លង",
"cutButtonLabel": "កាត់",
"scanTextButtonLabel": "ស្កេនអត្ថបទ",
"okButtonLabel": "យល់ព្រម",
"pasteButtonLabel": "ដាក់​ចូល",
"selectAllButtonLabel": "ជ្រើសរើស​ទាំងអស់",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "\u0cae\u0cc1\u0c82\u0ca6\u0cc1\u0cb5\u0cb0\u0cbf\u0cb8\u0cbf",
"copyButtonLabel": "\u0ca8\u0c95\u0cb2\u0cbf\u0cb8\u0cbf",
"cutButtonLabel": "\u0c95\u0ca4\u0ccd\u0ca4\u0cb0\u0cbf\u0cb8\u0cbf",
"scanTextButtonLabel": "\u0caa\u0ca0\u0ccd\u0caf\u0cb5\u0ca8\u0ccd\u0ca8\u0cc1\u0020\u0cb8\u0ccd\u0c95\u0ccd\u0caf\u0cbe\u0ca8\u0ccd\u0020\u0cae\u0cbe\u0ca1\u0cbf",
"okButtonLabel": "\u0cb8\u0cb0\u0cbf",
"pasteButtonLabel": "\u0c85\u0c82\u0c9f\u0cbf\u0cb8\u0cbf",
"selectAllButtonLabel": "\u0c8e\u0cb2\u0ccd\u0cb2\u0cb5\u0ca8\u0ccd\u0ca8\u0cc2\u0020\u0c86\u0caf\u0ccd\u0c95\u0cc6\u0020\u0cae\u0cbe\u0ca1\u0cbf",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "계속",
"copyButtonLabel": "복사",
"cutButtonLabel": "잘라냄",
"scanTextButtonLabel": "스캔 텍스트",
"okButtonLabel": "확인",
"pasteButtonLabel": "붙여넣기",
"selectAllButtonLabel": "전체 선택",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Улантуу",
"copyButtonLabel": "Көчүрүү",
"cutButtonLabel": "Кесүү",
"scanTextButtonLabel": "Текстти скандоо",
"okButtonLabel": "Макул",
"pasteButtonLabel": "Чаптоо",
"selectAllButtonLabel": "Баарын тандоо",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "ສືບຕໍ່",
"copyButtonLabel": "ສຳເນົາ",
"cutButtonLabel": "ຕັດ",
"scanTextButtonLabel": "ສະແກນຂໍ້ຄວາມ",
"okButtonLabel": "ຕົກລົງ",
"pasteButtonLabel": "ວາງ",
"selectAllButtonLabel": "ເລືອກທັງໝົດ",
......
......@@ -31,6 +31,7 @@
"continueButtonLabel": "Tęsti",
"copyButtonLabel": "Kopijuoti",
"cutButtonLabel": "Iškirpti",
"scanTextButtonLabel": "Nuskaityti tekstą",
"okButtonLabel": "GERAI",
"pasteButtonLabel": "Įklijuoti",
"selectAllButtonLabel": "Pasirinkti viską",
......
......@@ -26,6 +26,7 @@
"continueButtonLabel": "Turpināt",
"copyButtonLabel": "Kopēt",
"cutButtonLabel": "Izgriezt",
"scanTextButtonLabel": "Skenēt tekstu",
"okButtonLabel": "LABI",
"pasteButtonLabel": "Ielīmēt",
"selectAllButtonLabel": "Atlasīt visu",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Продолжи",
"copyButtonLabel": "Копирај",
"cutButtonLabel": "Исечи",
"scanTextButtonLabel": "Скенирајте текст",
"okButtonLabel": "Во ред",
"pasteButtonLabel": "Залепи",
"selectAllButtonLabel": "Избери ги сите",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "തുടരുക",
"copyButtonLabel": "പകർത്തുക",
"cutButtonLabel": "മുറിക്കുക",
"scanTextButtonLabel": "ടെക്സ്റ്റ് സ്കാൻ ചെയ്യുക",
"okButtonLabel": "ശരി",
"pasteButtonLabel": "ഒട്ടിക്കുക",
"selectAllButtonLabel": "എല്ലാം തിരഞ്ഞെടുക്കുക",
......
......@@ -26,6 +26,7 @@
"continueButtonLabel": "Үргэлжлүүлэх",
"copyButtonLabel": "Хуулах",
"cutButtonLabel": "Таслах",
"scanTextButtonLabel": "Текст сканнердах",
"okButtonLabel": "OK",
"pasteButtonLabel": "Буулгах",
"selectAllButtonLabel": "Бүгдийг сонгох",
......
......@@ -26,6 +26,7 @@
"continueButtonLabel": "पुढे सुरू ठेवा",
"copyButtonLabel": "कॉपी करा",
"cutButtonLabel": "कट करा",
"scanTextButtonLabel": "मजकूर स्कॅन करा",
"okButtonLabel": "ओके",
"pasteButtonLabel": "पेस्ट करा",
"selectAllButtonLabel": "सर्व निवडा",
......
......@@ -26,6 +26,7 @@
"continueButtonLabel": "Teruskan",
"copyButtonLabel": "Salin",
"cutButtonLabel": "Potong",
"scanTextButtonLabel": "Pindai teks",
"okButtonLabel": "OK",
"pasteButtonLabel": "Tampal",
"selectAllButtonLabel": "Pilih semua",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "ရှေ့ဆက်ရန်",
"copyButtonLabel": "မိတ္တူကူးရန်",
"cutButtonLabel": "ဖြတ်ယူရန်",
"scanTextButtonLabel": "စာသားကို စကင်ဖတ်ပါ။",
"okButtonLabel": "OK",
"pasteButtonLabel": "ကူးထည့်ရန်",
"selectAllButtonLabel": "အားလုံး ရွေးရန်",
......
......@@ -28,6 +28,7 @@
"continueButtonLabel": "Fortsett",
"copyButtonLabel": "Kopiér",
"cutButtonLabel": "Klipp ut",
"scanTextButtonLabel": "Scan tekst",
"okButtonLabel": "OK",
"pasteButtonLabel": "Lim inn",
"selectAllButtonLabel": "Velg alle",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "जारी राख्नुहोस्",
"copyButtonLabel": "प्रतिलिपि गर्नुहोस्",
"cutButtonLabel": "काट्नुहोस्",
"scanTextButtonLabel": "पाठ स्क्यान गर्नुहोस्",
"okButtonLabel": "ठिक छ",
"pasteButtonLabel": "टाँस्नुहोस्",
"selectAllButtonLabel": "सबै बटनहरू चयन गर्नुहोस्",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Doorgaan",
"copyButtonLabel": "Kopiëren",
"cutButtonLabel": "Knippen",
"scanTextButtonLabel": "Tekst scannen",
"okButtonLabel": "OK",
"pasteButtonLabel": "Plakken",
"selectAllButtonLabel": "Alles selecteren",
......
......@@ -28,6 +28,7 @@
"continueButtonLabel": "Fortsett",
"copyButtonLabel": "Kopiér",
"cutButtonLabel": "Klipp ut",
"scanTextButtonLabel": "Skann tekst",
"okButtonLabel": "OK",
"pasteButtonLabel": "Lim inn",
"selectAllButtonLabel": "Velg alle",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "ଜାରି ରଖନ୍ତୁ",
"copyButtonLabel": "କପି କରନ୍ତୁ",
"cutButtonLabel": "କଟ୍ କରନ୍ତୁ",
"scanTextButtonLabel": "ପାଠ୍ୟ ସ୍କାନ୍ କରନ୍ତୁ",
"okButtonLabel": "ଠିକ୍ ଅଛି",
"pasteButtonLabel": "ପେଷ୍ଟ କରନ୍ତୁ",
"selectAllButtonLabel": "ସବୁ ଚୟନ କରନ୍ତୁ",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "ਜਾਰੀ ਰੱਖੋ",
"copyButtonLabel": "ਕਾਪੀ ਕਰੋ",
"cutButtonLabel": "ਕੱਟ ਕਰੋ",
"scanTextButtonLabel": "ਟੈਕਸਟ ਸਕੈਨ ਕਰੋ",
"okButtonLabel": "ਠੀਕ ਹੈ",
"pasteButtonLabel": "ਪੇਸਟ ਕਰੋ",
"selectAllButtonLabel": "ਸਭ ਚੁਣੋ",
......
......@@ -31,6 +31,7 @@
"continueButtonLabel": "Dalej",
"copyButtonLabel": "Kopiuj",
"cutButtonLabel": "Wytnij",
"scanTextButtonLabel": "Zeskanuj tekst",
"okButtonLabel": "OK",
"pasteButtonLabel": "Wklej",
"selectAllButtonLabel": "Zaznacz wszystko",
......
......@@ -28,6 +28,7 @@
"continueButtonLabel": "منځپانګې",
"copyButtonLabel": "کاپی",
"cutButtonLabel": "کم کړئ",
"scanTextButtonLabel": "متن سکین کړئ",
"okButtonLabel": "سمه ده",
"pasteButtonLabel": "پیټ کړئ",
"selectAllButtonLabel": "غوره کړئ",
......
......@@ -29,6 +29,7 @@
"continueButtonLabel": "Continuar",
"copyButtonLabel": "Copiar",
"cutButtonLabel": "Cortar",
"scanTextButtonLabel": "Digitalizar texto",
"okButtonLabel": "OK",
"pasteButtonLabel": "Colar",
"selectAllButtonLabel": "Selecionar tudo",
......
......@@ -29,6 +29,7 @@
"continueButtonLabel": "Continuați",
"copyButtonLabel": "Copiați",
"cutButtonLabel": "Decupați",
"scanTextButtonLabel": "Scanați textul",
"okButtonLabel": "OK",
"pasteButtonLabel": "Inserați",
"selectAllButtonLabel": "Selectați tot",
......
......@@ -32,6 +32,7 @@
"continueButtonLabel": "Продолжить",
"copyButtonLabel": "Копировать",
"cutButtonLabel": "Вырезать",
"scanTextButtonLabel": "Сканировать текст",
"okButtonLabel": "ОК",
"pasteButtonLabel": "Вставить",
"selectAllButtonLabel": "Выбрать все",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "ඉදිරියට යන්න",
"copyButtonLabel": "පිටපත් කරන්න",
"cutButtonLabel": "කපන්න",
"scanTextButtonLabel": "පෙළ පරිලෝකනය කරන්න",
"okButtonLabel": "හරි",
"pasteButtonLabel": "අලවන්න",
"selectAllButtonLabel": "සියල්ල තෝරන්න",
......
......@@ -31,6 +31,7 @@
"continueButtonLabel": "Pokračovať",
"copyButtonLabel": "Kopírovať",
"cutButtonLabel": "Vystrihnúť",
"scanTextButtonLabel": "Naskenujte text",
"okButtonLabel": "OK",
"pasteButtonLabel": "Prilepiť",
"selectAllButtonLabel": "Vybrať všetko",
......
......@@ -31,6 +31,7 @@
"continueButtonLabel": "Naprej",
"copyButtonLabel": "Kopiraj",
"cutButtonLabel": "Izreži",
"scanTextButtonLabel": "Skeniraj besedilo",
"okButtonLabel": "V REDU",
"pasteButtonLabel": "Prilepi",
"selectAllButtonLabel": "Izberi vse",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Vazhdo",
"copyButtonLabel": "Kopjo",
"cutButtonLabel": "Prit",
"scanTextButtonLabel": "Skanoni tekstin",
"okButtonLabel": "Në rregull",
"pasteButtonLabel": "Ngjit",
"selectAllButtonLabel": "Zgjidh të gjitha",
......
......@@ -28,6 +28,7 @@
"continueButtonLabel": "Настави",
"copyButtonLabel": "Копирај",
"cutButtonLabel": "Исеци",
"scanTextButtonLabel": "Скенирајте текст",
"okButtonLabel": "Потврди",
"pasteButtonLabel": "Налепи",
"selectAllButtonLabel": "Изабери све",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Fortsätt",
"copyButtonLabel": "Kopiera",
"cutButtonLabel": "Klipp ut",
"scanTextButtonLabel": "Skanna text",
"okButtonLabel": "OK",
"pasteButtonLabel": "Klistra in",
"selectAllButtonLabel": "Markera allt",
......
......@@ -26,6 +26,7 @@
"continueButtonLabel": "Endelea",
"copyButtonLabel": "Nakili",
"cutButtonLabel": "Kata",
"scanTextButtonLabel": "Changanua maandishi",
"okButtonLabel": "Sawa",
"pasteButtonLabel": "Bandika",
"selectAllButtonLabel": "Chagua vyote",
......
......@@ -17,6 +17,7 @@
"reorderItemLeft": "இடப்புறம் நகர்த்தவும்",
"reorderItemRight": "வலப்புறம் நகர்த்தவும்",
"cutButtonLabel": "வெட்டு",
"scanTextButtonLabel": "உரையை ஸ்கேன் செய்யவும்",
"pasteButtonLabel": "ஒட்டு",
"previousMonthTooltip": "முந்தைய மாதம்",
"nextMonthTooltip": "அடுத்த மாதம்",
......
......@@ -24,6 +24,7 @@
"closeButtonLabel": "మూసివేయండి",
"continueButtonLabel": "కొనసాగించండి",
"copyButtonLabel": "కాపీ చేయి",
"scanTextButtonLabel": "వచనాన్ని స్కాన్ చేయండి",
"cutButtonLabel": "కత్తిరించండి",
"okButtonLabel": "సరే",
"pasteButtonLabel": "పేస్ట్ చేయండి",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "ต่อไป",
"copyButtonLabel": "คัดลอก",
"cutButtonLabel": "ตัด",
"scanTextButtonLabel": "สแกนข้อความ",
"okButtonLabel": "ตกลง",
"pasteButtonLabel": "วาง",
"selectAllButtonLabel": "เลือกทั้งหมด",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Magpatuloy",
"copyButtonLabel": "Kopyahin",
"cutButtonLabel": "I-cut",
"scanTextButtonLabel": "I-scan ang text",
"okButtonLabel": "OK",
"pasteButtonLabel": "I-paste",
"selectAllButtonLabel": "Piliin lahat",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Devam",
"copyButtonLabel": "Kopyala",
"cutButtonLabel": "Kes",
"scanTextButtonLabel": "Metni tara",
"okButtonLabel": "Tamam",
"pasteButtonLabel": "Yapıştır",
"selectAllButtonLabel": "Tümünü seç",
......
......@@ -31,6 +31,7 @@
"continueButtonLabel": "Продовжити",
"copyButtonLabel": "Копіювати",
"cutButtonLabel": "Вирізати",
"scanTextButtonLabel": "Сканувати текст",
"okButtonLabel": "OK",
"pasteButtonLabel": "Вставити",
"selectAllButtonLabel": "Вибрати всі",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "جاری رکھیں",
"copyButtonLabel": "کاپی کریں",
"cutButtonLabel": "کٹ کریں",
"scanTextButtonLabel": "متن کو اسکین کریں",
"okButtonLabel": "ٹھیک ہے",
"pasteButtonLabel": "پیسٹ کریں",
"selectAllButtonLabel": "سبھی کو منتخب کریں",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Davom etish",
"copyButtonLabel": "Nusxa olish",
"cutButtonLabel": "Kesib olish",
"scanTextButtonLabel": "Matnni skanerlash",
"okButtonLabel": "OK",
"pasteButtonLabel": "Joylash",
"selectAllButtonLabel": "Hammasi",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Tiếp tục",
"copyButtonLabel": "Sao chép",
"cutButtonLabel": "Cắt",
"scanTextButtonLabel": "Quét văn bản",
"okButtonLabel": "OK",
"pasteButtonLabel": "Dán",
"selectAllButtonLabel": "Chọn tất cả",
......
......@@ -21,6 +21,7 @@
"closeButtonLabel": "关闭",
"copyButtonLabel": "复制",
"cutButtonLabel": "剪切",
"scanTextButtonLabel": "扫描文本",
"okButtonLabel": "确定",
"pasteButtonLabel": "粘贴",
"selectAllButtonLabel": "全选",
......
......@@ -25,6 +25,7 @@
"continueButtonLabel": "Qhubeka",
"copyButtonLabel": "Kopisha",
"cutButtonLabel": "Sika",
"scanTextButtonLabel": "Skena umbhalo",
"okButtonLabel": "KULUNGILE",
"pasteButtonLabel": "Namathisela",
"selectAllButtonLabel": "Khetha konke",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment