// 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/foundation.dart'; import 'package:flutter/painting.dart'; export 'dart:ui' show TextDirection; /// Determines the assertiveness level of the accessibility announcement. /// /// It is used by [AnnounceSemanticsEvent] to determine the priority with which /// assistive technology should treat announcements. enum Assertiveness { /// The assistive technology will speak changes whenever the user is idle. polite, /// The assistive technology will interrupt any announcement that it is /// currently making to notify the user about the change. /// /// It should only be used for time-sensitive/critical notifications. assertive, } /// An event sent by the application to notify interested listeners that /// something happened to the user interface (e.g. a view scrolled). /// /// These events are usually interpreted by assistive technologies to give the /// user additional clues about the current state of the UI. abstract class SemanticsEvent { /// Initializes internal fields. /// /// [type] is a string that identifies this class of [SemanticsEvent]s. const SemanticsEvent(this.type); /// The type of this event. /// /// The type is used by the engine to translate this event into the /// appropriate native event (`UIAccessibility*Notification` on iOS and /// `AccessibilityEvent` on Android). final String type; /// Converts this event to a Map that can be encoded with /// [StandardMessageCodec]. /// /// [nodeId] is the unique identifier of the semantics node associated with /// the event, or null if the event is not associated with a semantics node. Map toMap({ int? nodeId }) { final Map event = { 'type': type, 'data': getDataMap(), }; if (nodeId != null) { event['nodeId'] = nodeId; } return event; } /// Returns the event's data object. Map getDataMap(); @override String toString() { final List pairs = []; final Map dataMap = getDataMap(); final List sortedKeys = dataMap.keys.toList()..sort(); for (final String key in sortedKeys) { pairs.add('$key: ${dataMap[key]}'); } return '${objectRuntimeType(this, 'SemanticsEvent')}(${pairs.join(', ')})'; } } /// An event for a semantic announcement. /// /// This should be used for announcement that are not seamlessly announced by /// the system as a result of a UI state change. /// /// For example a camera application can use this method to make accessibility /// announcements regarding objects in the viewfinder. /// /// When possible, prefer using mechanisms like [Semantics] to implicitly /// trigger announcements over using this event. class AnnounceSemanticsEvent extends SemanticsEvent { /// Constructs an event that triggers an announcement by the platform. const AnnounceSemanticsEvent(this.message, this.textDirection, {this.assertiveness = Assertiveness.polite}) : super('announce'); /// The message to announce. final String message; /// Text direction for [message]. final TextDirection textDirection; /// Determines whether the announcement should interrupt any existing announcement, /// or queue after it. /// /// On the web this option uses the aria-live level to set the assertiveness /// of the announcement. On iOS, Android, Windows, Linux, macOS, and Fuchsia /// this option currently has no effect. final Assertiveness assertiveness; @override Map getDataMap() { return { 'message': message, 'textDirection': textDirection.index, if (assertiveness != Assertiveness.polite) 'assertiveness': assertiveness.index, }; } } /// An event for a semantic announcement of a tooltip. /// /// This is only used by Android to announce tooltip values. class TooltipSemanticsEvent extends SemanticsEvent { /// Constructs an event that triggers a tooltip announcement by the platform. const TooltipSemanticsEvent(this.message) : super('tooltip'); /// The text content of the tooltip. final String message; @override Map getDataMap() { return { 'message': message, }; } } /// An event which triggers long press semantic feedback. /// /// Currently only honored on Android. Triggers a long-press specific sound /// when TalkBack is enabled. class LongPressSemanticsEvent extends SemanticsEvent { /// Constructs an event that triggers a long-press semantic feedback by the platform. const LongPressSemanticsEvent() : super('longPress'); @override Map getDataMap() => const {}; } /// An event which triggers tap semantic feedback. /// /// Currently only honored on Android. Triggers a tap specific sound when /// TalkBack is enabled. class TapSemanticEvent extends SemanticsEvent { /// Constructs an event that triggers a long-press semantic feedback by the platform. const TapSemanticEvent() : super('tap'); @override Map getDataMap() => const {}; } /// An event to move the accessibility focus. /// /// Using this API is generally not recommended, as it may break a users' expectation of /// how a11y focus works and therefore should be just very carefully. /// /// One possibile use case: /// For example, the currently focused rendering object is replaced by another rendering /// object. In general, such design should be avoided if possible. If not, one may want /// to refocus the newly added rendering object. /// /// One example that is not recommended: /// When a new popup or dropdown opens, moving the focus in these cases may confuse users /// and make it less accessible. /// /// {@tool snippet} /// /// The following code snippet shows how one can request focus on a /// certain widget. /// /// ```dart /// class MyWidget extends StatefulWidget { /// const MyWidget({super.key}); /// /// @override /// State createState() => _MyWidgetState(); /// } /// /// class _MyWidgetState extends State { /// final GlobalKey mykey = GlobalKey(); /// /// @override /// void initState() { /// super.initState(); /// // Using addPostFrameCallback because changing focus need to wait for the widget to finish rendering. /// WidgetsBinding.instance.addPostFrameCallback((_) { /// mykey.currentContext?.findRenderObject()?.sendSemanticsEvent(const FocusSemanticEvent()); /// }); /// } /// /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// appBar: AppBar( /// title: const Text('example'), /// ), /// body: Column( /// children: [ /// const Text('Hello World'), /// const SizedBox(height: 50), /// Text('set focus here', key: mykey), /// ], /// ), /// ); /// } /// } /// ``` /// {@end-tool} /// /// This currently only supports Android and iOS. class FocusSemanticEvent extends SemanticsEvent { /// Constructs an event that triggers a focus change by the platform. const FocusSemanticEvent() : super('focus'); @override Map getDataMap() => const {}; }