Kostia Sokolovskyi 3892c57dd6
Add new motion accessibility features to iOS. (#178102)
Closes https://github.com/flutter/flutter/issues/177330
Contributes to https://github.com/flutter/flutter/issues/175878

### Description

This PR implements support for three new iOS accessibility motion
features.
- `Auto-Play Animated Images`: Informs the app when the user has chosen
to pause automatically playing GIFs and other animated content.
- `Auto-Play Video Previews`: Informs the app when the user has disabled
the automatic playback of video previews.
- `Prefer Non-Blinking Cursor`: Informs the app that the user prefers a
non-blinking text insertion indicator in editable text fields.

The original issue requested seven features. I was able to find and
implement features with clear, public, and general-purpose APIs.

The remaining features were not included for the following reasons:

- `Vehicle Motion Cues`: No public API found.
- `Auto-Play Message Effects`: No public API found. This is likely a
feature exclusive to the Messages framework.
- `Limit Frame Rate`: No public API found.
- `Dim Flashing Lights`: There is a public API available
[MADimFlashingLightsEnabled](https://developer.apple.com/documentation/mediaaccessibility/madimflashinglightsenabled()).
It is intended for media playback frameworks. It's unclear if exposing
this feature is necessary or actionable for a general-purpose
application UI.

| Feature Name | API | Flutter AccessibilityFeatures |
| :-: | :-: | :-: |
| Vehicle Motion Cues | - | - |
| Dim Flashing Lights |
[MADimFlashingLightsEnabled](https://developer.apple.com/documentation/mediaaccessibility/madimflashinglightsenabled())
| - |
| Auto-Play Animated Images |
[animatedImagesEnabled](https://developer.apple.com/documentation/accessibility/accessibilitysettings/animatedimagesenabled)
| autoPlayAnimatedImages |
| Auto-Play Video Previews |
[isVideoAutoplayEnabled](https://developer.apple.com/documentation/uikit/uiaccessibility/isvideoautoplayenabled)
| autoPlayVideos |
| Auto-Play Message Effects | - | - |
| Prefer Non-Blinking Cursor |
[prefersNonBlinkingTextInsertionIndicator](https://developer.apple.com/documentation/accessibility/accessibilitysettings/prefersnonblinkingtextinsertionindicator)
| deterministicCursor |
| Limit Frame Rate | - | - |

The `AccessibilityFeatures.swift` was introduced to refactor all native
iOS accessibility logic into a single, dedicated class. This change
moves the responsibility of querying settings and observing
notifications out of `FlutterViewController.mm`, which cleans up the
controller, simplifies future maintenance, and makes logic
unit-testable.

### Demo


https://github.com/user-attachments/assets/1b3e7a2f-03b4-4716-959e-dbeea938e4d2

<details closed><summary>Code sample</summary>

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Screen(),
    ),
  );
}

class Screen extends StatefulWidget {
  @override
  State<Screen> createState() => _ScreenState();
}

class _ScreenState extends State<Screen> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeAccessibilityFeatures() {
    super.didChangeAccessibilityFeatures();
    if (mounted) {
      setState(() {});
    }
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final accessibilityFeatures = View.of(
      context,
    ).platformDispatcher.accessibilityFeatures;

    return Scaffold(
      body: Center(
        child: Column(
          spacing: 10,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            for (final feature in [
              'accessibleNavigation: ${accessibilityFeatures.accessibleNavigation}',
              'invertColors: ${accessibilityFeatures.invertColors}',
              'disableAnimations: ${accessibilityFeatures.disableAnimations}',
              'boldText: ${accessibilityFeatures.boldText}',
              'reduceMotion: ${accessibilityFeatures.reduceMotion}',
              'highContrast: ${accessibilityFeatures.highContrast}',
              'onOffSwitchLabels: ${accessibilityFeatures.onOffSwitchLabels}',
              'supportsAnnounce: ${accessibilityFeatures.supportsAnnounce}',
              'autoPlayAnimatedImages: ${accessibilityFeatures.autoPlayAnimatedImages}',
              'autoPlayVideos: ${accessibilityFeatures.autoPlayVideos}',
              'deterministicCursor: ${accessibilityFeatures.deterministicCursor}',
            ])
              Text(feature),
          ],
        ),
      ),
    );
  }
}
```

</details>

## Pre-launch Checklist

- [X] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [X] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [X] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [X] I signed the [CLA].
- [X] I listed at least one issue that this PR fixes in the description
above.
- [X] I updated/added relevant documentation (doc comments with `///`).
- [X] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [X] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [X] All existing and new tests are passing.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com>
2026-01-08 21:06:41 +00:00
..
2025-10-09 19:44:25 +00:00

Flutter Engine

Setting up the Engine development environment

See here