| // 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. |
| |
| // ignore_for_file: avoid_dynamic_calls |
| |
| import 'dart:convert'; |
| |
| import 'package:meta/meta.dart'; |
| |
| import 'constants.dart'; |
| |
| /// A semantics node created from Android accessibility information. |
| /// |
| /// This object represents Android accessibility information derived from an |
| /// [AccessibilityNodeInfo](https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo) |
| /// object. The purpose is to verify in integration |
| /// tests that our semantics framework produces the correct accessibility info |
| /// on Android. |
| /// |
| /// See also: |
| /// |
| /// * [AccessibilityNodeInfo](https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo) |
| class AndroidSemanticsNode { |
| AndroidSemanticsNode._(this._values); |
| |
| /// Deserializes a new [AndroidSemanticsNode] from a json map. |
| /// |
| /// The structure of the JSON: |
| /// |
| /// { |
| /// "flags": { |
| /// "isChecked": bool, |
| /// "isCheckable": bool, |
| /// "isEditable": bool, |
| /// "isEnabled": bool, |
| /// "isFocusable": bool, |
| /// "isFocused": bool, |
| /// "isHeading": bool, |
| /// "isPassword": bool, |
| /// "isLongClickable": bool, |
| /// }, |
| /// "text": String, |
| /// "contentDescription": String, |
| /// "className": String, |
| /// "id": int, |
| /// "rect": { |
| /// left: int, |
| /// top: int, |
| /// right: int, |
| /// bottom: int, |
| /// }, |
| /// actions: [ |
| /// int, |
| /// ] |
| /// } |
| factory AndroidSemanticsNode.deserialize(String value) { |
| return AndroidSemanticsNode._(json.decode(value)); |
| } |
| |
| final dynamic _values; |
| final List<AndroidSemanticsNode> _children = <AndroidSemanticsNode>[]; |
| |
| dynamic get _flags => _values['flags']; |
| |
| /// The text value of the semantics node. |
| /// |
| /// This is produced by combining the value, label, and hint fields from |
| /// the Flutter [SemanticsNode]. |
| String? get text => _values['text'] as String?; |
| |
| /// The contentDescription of the semantics node. |
| /// |
| /// This field is used for the Switch, Radio, and Checkbox widgets |
| /// instead of [text]. If the text property is used for these, TalkBack |
| /// will not read out the "checked" or "not checked" label by default. |
| /// |
| /// This is produced by combining the value, label, and hint fields from |
| /// the Flutter [SemanticsNode]. |
| String? get contentDescription => _values['contentDescription'] as String?; |
| |
| /// The className of the semantics node. |
| /// |
| /// Certain kinds of Flutter semantics are mapped to Android classes to |
| /// use their default semantic behavior, such as checkboxes and images. |
| /// |
| /// If a more specific value isn't provided, it defaults to |
| /// "android.view.View". |
| String? get className => _values['className'] as String?; |
| |
| /// The identifier for this semantics node. |
| int? get id => _values['id'] as int?; |
| |
| /// The children of this semantics node. |
| List<AndroidSemanticsNode> get children => _children; |
| |
| /// Whether the node is currently in a checked state. |
| /// |
| /// Equivalent to [SemanticsFlag.isChecked]. |
| bool? get isChecked => _flags['isChecked'] as bool?; |
| |
| /// Whether the node can be in a checked state. |
| /// |
| /// Equivalent to [SemanticsFlag.hasCheckedState] |
| bool? get isCheckable => _flags['isCheckable'] as bool?; |
| |
| /// Whether the node is editable. |
| /// |
| /// This is usually only applied to text fields, which map |
| /// to "android.widget.EditText". |
| bool? get isEditable => _flags['isEditable'] as bool?; |
| |
| /// Whether the node is enabled. |
| bool? get isEnabled => _flags['isEnabled'] as bool?; |
| |
| /// Whether the node is focusable. |
| bool? get isFocusable => _flags['isFocusable'] as bool?; |
| |
| /// Whether the node is focused. |
| bool? get isFocused => _flags['isFocused'] as bool?; |
| |
| /// Whether the node is considered a heading. |
| bool? get isHeading => _flags['isHeading'] as bool?; |
| |
| /// Whether the node represents a password field. |
| /// |
| /// Equivalent to [SemanticsFlag.isObscured]. |
| bool? get isPassword => _flags['isPassword'] as bool?; |
| |
| /// Whether the node is long clickable. |
| /// |
| /// Equivalent to having [SemanticsAction.longPress]. |
| bool? get isLongClickable => _flags['isLongClickable'] as bool?; |
| |
| /// Gets a [Rect] which defines the position and size of the semantics node. |
| Rect getRect() { |
| final dynamic rawRect = _values['rect']; |
| if (rawRect == null) { |
| return const Rect.fromLTRB(0.0, 0.0, 0.0, 0.0); |
| } |
| return Rect.fromLTRB( |
| (rawRect['left']! as int).toDouble(), |
| (rawRect['top']! as int).toDouble(), |
| (rawRect['right']! as int).toDouble(), |
| (rawRect['bottom']! as int).toDouble(), |
| ); |
| } |
| |
| /// Gets a [Size] which defines the size of the semantics node. |
| Size getSize() { |
| final Rect rect = getRect(); |
| return Size(rect.bottom - rect.top, rect.right - rect.left); |
| } |
| |
| /// Gets a list of [AndroidSemanticsActions] which are defined for the node. |
| List<AndroidSemanticsAction> getActions() { |
| final List<int>? actions = (_values['actions'] as List<dynamic>?)?.cast<int>(); |
| if (actions == null) { |
| return const <AndroidSemanticsAction>[]; |
| } |
| final List<AndroidSemanticsAction> convertedActions = <AndroidSemanticsAction>[]; |
| for (final int id in actions) { |
| final AndroidSemanticsAction? action = AndroidSemanticsAction.deserialize(id); |
| if (action != null) { |
| convertedActions.add(action); |
| } |
| } |
| return convertedActions; |
| } |
| |
| @override |
| String toString() { |
| return _values.toString(); |
| } |
| } |
| |
| |
| /// A Dart VM implementation of a rectangle. |
| /// |
| /// Created to mirror the implementation of [ui.Rect]. |
| @immutable |
| class Rect { |
| /// Creates a new rectangle. |
| /// |
| /// All values are required. |
| const Rect.fromLTRB(this.left, this.top, this.right, this.bottom); |
| |
| /// The top side of the rectangle. |
| final double top; |
| |
| /// The left side of the rectangle. |
| final double left; |
| |
| /// The right side of the rectangle. |
| final double right; |
| |
| /// The bottom side of the rectangle. |
| final double bottom; |
| |
| @override |
| int get hashCode => Object.hash(top, left, right, bottom); |
| |
| @override |
| bool operator ==(Object other) { |
| if (other.runtimeType != runtimeType) { |
| return false; |
| } |
| return other is Rect |
| && other.top == top |
| && other.left == left |
| && other.right == right |
| && other.bottom == bottom; |
| } |
| |
| @override |
| String toString() => 'Rect.fromLTRB($left, $top, $right, $bottom)'; |
| } |
| |
| /// A Dart VM implementation of a Size. |
| /// |
| /// Created to mirror the implementation [ui.Size]. |
| @immutable |
| class Size { |
| /// Creates a new [Size] object. |
| const Size(this.width, this.height); |
| |
| /// The width of some object. |
| final double width; |
| |
| /// The height of some object. |
| final double height; |
| |
| @override |
| int get hashCode => Object.hash(width, height); |
| |
| @override |
| bool operator ==(Object other) { |
| if (other.runtimeType != runtimeType) { |
| return false; |
| } |
| return other is Size |
| && other.width == width |
| && other.height == height; |
| } |
| |
| @override |
| String toString() => 'Size{$width, $height}'; |
| } |