// 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}'; }