Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 69 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
ed104a9
Add new text OCR impl
luckysmg Dec 15, 2022
b5cf438
Merge branch 'flutter:master' into ios_ocr
luckysmg Dec 15, 2022
374efed
Fix test error
luckysmg Dec 15, 2022
8691dce
++
luckysmg Dec 15, 2022
8b46a64
Add support for tool bar
luckysmg Dec 15, 2022
995bdab
Merge branch 'flutter:master' into ios_text_ocr
luckysmg Dec 16, 2022
5a90f00
Delete localization for the moment
luckysmg Dec 16, 2022
7acbb16
++
luckysmg Dec 20, 2022
5bc7bff
++
luckysmg Dec 20, 2022
485d282
Merge branch 'master' into ios_text_ocr
luckysmg Dec 23, 2022
3911481
++
luckysmg Dec 23, 2022
dcbf74e
Merge branch 'flutter:master' into ios_text_ocr
luckysmg Dec 25, 2022
73510c3
++
luckysmg Dec 25, 2022
c0a5e27
++
luckysmg Dec 25, 2022
b93d2d1
Add localizations
luckysmg Dec 26, 2022
7490b13
Merge branch 'flutter:master' into ios_text_ocr
luckysmg Dec 26, 2022
1c99ca2
++
luckysmg Dec 26, 2022
8111811
Merge branch 'flutter:master' into ios_text_ocr
luckysmg Dec 28, 2022
dcf051d
Add tests
luckysmg Dec 28, 2022
feac6bd
Merge branch 'flutter:master' into ios_text_ocr
luckysmg Dec 28, 2022
e31b97e
Merge branch 'flutter:master' into ios_text_ocr
luckysmg Jan 4, 2023
da1bec1
++
luckysmg Jan 4, 2023
402a7bb
modify comments
luckysmg Jan 4, 2023
5a82003
++
luckysmg Jan 4, 2023
6068f48
++
luckysmg Jan 4, 2023
25bcb5b
++
luckysmg Jan 4, 2023
8f4ee70
++
luckysmg Jan 4, 2023
e1fdb08
++
luckysmg Jan 4, 2023
4e9252c
++
luckysmg Jan 4, 2023
41ac0d5
++
luckysmg Jan 4, 2023
433a70d
Add tests
luckysmg Jan 4, 2023
87f9d69
++
luckysmg Jan 4, 2023
2577715
++
luckysmg Jan 4, 2023
8eb5e0c
++
luckysmg Jan 4, 2023
3c97d69
++
luckysmg Jan 4, 2023
c5b5c93
++
luckysmg Jan 10, 2023
6d58a9c
++
luckysmg Jan 10, 2023
2b037a1
++
luckysmg Jan 10, 2023
eeb1cf5
Merge branch 'master' into ios_text_ocr
luckysmg Apr 19, 2023
5e24664
++
luckysmg Apr 19, 2023
8f6021e
++
luckysmg Apr 19, 2023
1ae04b5
++
luckysmg Apr 19, 2023
f9c577c
++
luckysmg Apr 19, 2023
47ce4f3
++
luckysmg Apr 19, 2023
e629941
++
luckysmg Apr 21, 2023
8a16823
Merge branch 'master' into ios_text_ocr
luckysmg Apr 21, 2023
b02dc4f
++
luckysmg Jun 16, 2023
e196c28
Merge branch 'master' into ios_text_ocr
luckysmg Jun 16, 2023
53d332b
++
luckysmg Jun 16, 2023
f07038c
++
luckysmg Jun 16, 2023
f312e4a
++
luckysmg Jun 16, 2023
e8b3e2f
++
luckysmg Jun 17, 2023
d444b4d
++
luckysmg Jun 21, 2023
850929e
Merge branch 'master' into ios_text_ocr
luckysmg Jun 21, 2023
07e7aa9
++
luckysmg Jun 27, 2023
c3e16c2
Merge branch 'master' into ios_text_ocr
luckysmg Jun 27, 2023
79ddee7
++
luckysmg Jun 27, 2023
78a2964
++
luckysmg Jun 28, 2023
d34a1f1
Merge branch 'master' into ios_text_ocr
luckysmg Jun 28, 2023
b5fef9c
Merge branch 'master' into ios_text_ocr
luckysmg Jun 28, 2023
2571aaa
++
luckysmg Jun 29, 2023
6ea56f7
++
luckysmg Jun 29, 2023
8416dcd
Merge branch 'master' into ios_text_ocr
luckysmg Jun 29, 2023
f845288
++
luckysmg Jun 29, 2023
0a8baee
Merge branch 'master' into ios_text_ocr
luckysmg Jul 3, 2023
779680b
++
luckysmg Jul 4, 2023
8e0874a
Merge branch 'master' into ios_text_ocr
luckysmg Jul 4, 2023
aef841d
++
luckysmg Jul 4, 2023
857c799
++
luckysmg Jul 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/flutter/lib/services.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -102,6 +103,7 @@ class CupertinoAdaptiveTextSelectionToolbar extends StatelessWidget {
onCut: onCut,
onPaste: onPaste,
onSelectAll: onSelectAll,
onLiveTextInput: onLiveTextInput
);

/// Create an instance of [CupertinoAdaptiveTextSelectionToolbar] with the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 '';
Expand Down Expand Up @@ -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)
Expand All @@ -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) {
Expand All @@ -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;

luckysmg marked this conversation as resolved.
Show resolved Hide resolved
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);
luckysmg marked this conversation as resolved.
Show resolved Hide resolved
}

// Draw three lines.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a common practice in the framework code to include the decimal point for doubles except maybe for "divide by two" like cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there are still some doubles without decimal point in this method and possibly elsewhere e.g.:
..lineTo(origin.dx, origin.dy + 1)
canvas.drawLine(const Offset(-3, 0), const Offset(3, 0), _painter);

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -111,6 +112,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget {
onCut: onCut,
onPaste: onPaste,
onSelectAll: onSelectAll,
onLiveTextInput: onLiveTextInput
);

/// Create an instance of [AdaptiveTextSelectionToolbar] with the default
Expand Down Expand Up @@ -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 '';
}
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions packages/flutter/lib/src/material/material_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1159,6 +1162,9 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
@override
String get cutButtonLabel => 'Cut';

@override
String get scanTextButtonLabel => 'Scan text';

@override
String get okButtonLabel => 'OK';

Expand Down
35 changes: 35 additions & 0 deletions packages/flutter/lib/src/services/live_text.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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');
}
}
luckysmg marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions packages/flutter/lib/src/services/text_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
Loading