accessibility: fixed system check to remove animations

This commit is contained in:
Thibault Deckers 2021-09-28 20:42:25 +09:00
parent 8560ebdd14
commit 3a0124a8e9
7 changed files with 71 additions and 36 deletions

View file

@ -3,21 +3,35 @@ package deckers.thibault.aves.channel.calls
import android.app.Activity
import android.content.Context
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.accessibility.AccessibilityManager
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.utils.LogUtils
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
class AccessibilityHandler(private val context: Activity) : MethodCallHandler {
class AccessibilityHandler(private val activity: Activity) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"areAnimationsRemoved" -> safe(call, result, ::areAnimationsRemoved)
"hasRecommendedTimeouts" -> safe(call, result, ::hasRecommendedTimeouts)
"getRecommendedTimeoutMillis" -> safe(call, result, ::getRecommendedTimeoutMillis)
else -> result.notImplemented()
}
}
private fun areAnimationsRemoved(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
var removed = false
try {
removed = Settings.Global.getFloat(activity.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE) == 0f
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get settings", e)
}
result.success(removed)
}
private fun hasRecommendedTimeouts(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
}
@ -48,7 +62,7 @@ class AccessibilityHandler(private val context: Activity) : MethodCallHandler {
}
}
val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
val am = activity.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
if (am == null) {
result.error("getRecommendedTimeoutMillis-service", "failed to get accessibility manager", null)
return
@ -59,6 +73,7 @@ class AccessibilityHandler(private val context: Activity) : MethodCallHandler {
}
companion object {
private val LOG_TAG = LogUtils.createTag<AccessibilityHandler>()
const val CHANNEL = "deckers.thibault/aves/accessibility"
}
}

View file

@ -20,6 +20,7 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
private val contentObserver = object : ContentObserver(null) {
private var accelerometerRotation: Int = 0
private var transitionAnimationScale: Float = 1f
init {
update()
@ -33,7 +34,8 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
if (update()) {
success(
hashMapOf(
Settings.System.ACCELEROMETER_ROTATION to accelerometerRotation
Settings.System.ACCELEROMETER_ROTATION to accelerometerRotation,
Settings.Global.TRANSITION_ANIMATION_SCALE to transitionAnimationScale,
)
)
}
@ -47,6 +49,12 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
accelerometerRotation = newAccelerometerRotation
changed = true
}
val newTransitionAnimationScale = Settings.Global.getFloat(context.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE)
if (transitionAnimationScale != newTransitionAnimationScale) {
transitionAnimationScale = newTransitionAnimationScale
changed = true
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get settings", e)
}

View file

@ -1,5 +1,4 @@
import 'dart:ui';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/widgets.dart';
@ -20,7 +19,9 @@ extension ExtraAccessibilityAnimations on AccessibilityAnimations {
bool get animate {
switch (this) {
case AccessibilityAnimations.system:
return !window.accessibilityFeatures.disableAnimations;
// as of Flutter v2.5.1, the check for `disableAnimations` is unreliable
// so we cannot use `window.accessibilityFeatures.disableAnimations` nor `MediaQuery.of(context).disableAnimations`
return !settings.areAnimationsRemoved;
case AccessibilityAnimations.disabled:
return false;
case AccessibilityAnimations.enabled:

View file

@ -116,11 +116,18 @@ class Settings extends ChangeNotifier {
// cf Android `Settings.System.ACCELEROMETER_ROTATION`
static const platformAccelerometerRotationKey = 'accelerometer_rotation';
// cf Android `Settings.Global.TRANSITION_ANIMATION_SCALE`
static const platformTransitionAnimationScaleKey = 'transition_animation_scale';
bool get initialized => _prefs != null;
Future<void> init({bool isRotationLocked = false}) async {
Future<void> init({
bool isRotationLocked = false,
bool areAnimationsRemoved = false,
}) async {
_prefs = await SharedPreferences.getInstance();
_isRotationLocked = isRotationLocked;
_areAnimationsRemoved = areAnimationsRemoved;
}
Future<void> reset({required bool includeInternalKeys}) async {
@ -447,10 +454,11 @@ class Settings extends ChangeNotifier {
// platform settings
void _onPlatformSettingsChange(Map? fields) {
var changed = false;
fields?.forEach((key, value) {
switch (key) {
case platformAccelerometerRotationKey:
if (value is int) {
if (value is num) {
final newValue = value == 0;
if (_isRotationLocked != newValue) {
_isRotationLocked = newValue;
@ -458,18 +466,34 @@ class Settings extends ChangeNotifier {
windowService.requestOrientation();
}
_updateStreamController.add(key);
notifyListeners();
changed = true;
}
}
break;
case platformTransitionAnimationScaleKey:
if (value is num) {
final newValue = value == 0;
if (_areAnimationsRemoved != newValue) {
_areAnimationsRemoved = newValue;
_updateStreamController.add(key);
changed = true;
}
}
}
});
if (changed) {
notifyListeners();
}
}
bool _isRotationLocked = false;
bool get isRotationLocked => _isRotationLocked;
bool _areAnimationsRemoved = false;
bool get areAnimationsRemoved => _areAnimationsRemoved;
// import/export
String toJson() => jsonEncode(Map.fromEntries(

View file

@ -4,6 +4,16 @@ import 'package:flutter/services.dart';
class AccessibilityService {
static const platform = MethodChannel('deckers.thibault/aves/accessibility');
static Future<bool> areAnimationsRemoved() async {
try {
final result = await platform.invokeMethod('areAnimationsRemoved');
if (result != null) return result as bool;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return false;
}
static Future<bool> hasRecommendedTimeouts() async {
try {
final result = await platform.invokeMethod('hasRecommendedTimeouts');

View file

@ -1,5 +1,4 @@
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
@ -72,7 +71,7 @@ class Durations {
static const lastVersionCheckInterval = Duration(days: 7);
}
class DurationsProvider extends StatefulWidget {
class DurationsProvider extends StatelessWidget {
final Widget child;
const DurationsProvider({
@ -80,30 +79,6 @@ class DurationsProvider extends StatefulWidget {
required this.child,
}) : super(key: key);
@override
_DurationsProviderState createState() => _DurationsProviderState();
}
class _DurationsProviderState extends State<DurationsProvider> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
@override
void didChangeAccessibilityFeatures() {
if (settings.accessibilityAnimations == AccessibilityAnimations.system) {
// TODO TLAD update provider
}
}
@override
Widget build(BuildContext context) {
return ProxyProvider<Settings, DurationsData>(
@ -111,7 +86,7 @@ class _DurationsProviderState extends State<DurationsProvider> with WidgetsBindi
final enabled = settings.accessibilityAnimations.animate;
return enabled ? DurationsData() : DurationsData.noAnimation();
},
child: widget.child,
child: child,
);
}
}

View file

@ -6,6 +6,7 @@ import 'package:aves/model/settings/screen_on.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/media_store_source.dart';
import 'package:aves/services/accessibility_service.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
@ -144,6 +145,7 @@ class _AvesAppState extends State<AvesApp> {
Future<void> _setup() async {
await settings.init(
isRotationLocked: await windowService.isRotationLocked(),
areAnimationsRemoved: await AccessibilityService.areAnimationsRemoved(),
);
// keep screen on