accessibility: fixed system check to remove animations
This commit is contained in:
parent
8560ebdd14
commit
3a0124a8e9
7 changed files with 71 additions and 36 deletions
|
@ -3,21 +3,35 @@ package deckers.thibault.aves.channel.calls
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
import android.view.accessibility.AccessibilityManager
|
import android.view.accessibility.AccessibilityManager
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
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.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
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) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
|
"areAnimationsRemoved" -> safe(call, result, ::areAnimationsRemoved)
|
||||||
"hasRecommendedTimeouts" -> safe(call, result, ::hasRecommendedTimeouts)
|
"hasRecommendedTimeouts" -> safe(call, result, ::hasRecommendedTimeouts)
|
||||||
"getRecommendedTimeoutMillis" -> safe(call, result, ::getRecommendedTimeoutMillis)
|
"getRecommendedTimeoutMillis" -> safe(call, result, ::getRecommendedTimeoutMillis)
|
||||||
else -> result.notImplemented()
|
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) {
|
private fun hasRecommendedTimeouts(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
|
||||||
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
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) {
|
if (am == null) {
|
||||||
result.error("getRecommendedTimeoutMillis-service", "failed to get accessibility manager", null)
|
result.error("getRecommendedTimeoutMillis-service", "failed to get accessibility manager", null)
|
||||||
return
|
return
|
||||||
|
@ -59,6 +73,7 @@ class AccessibilityHandler(private val context: Activity) : MethodCallHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val LOG_TAG = LogUtils.createTag<AccessibilityHandler>()
|
||||||
const val CHANNEL = "deckers.thibault/aves/accessibility"
|
const val CHANNEL = "deckers.thibault/aves/accessibility"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,6 +20,7 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
|
||||||
|
|
||||||
private val contentObserver = object : ContentObserver(null) {
|
private val contentObserver = object : ContentObserver(null) {
|
||||||
private var accelerometerRotation: Int = 0
|
private var accelerometerRotation: Int = 0
|
||||||
|
private var transitionAnimationScale: Float = 1f
|
||||||
|
|
||||||
init {
|
init {
|
||||||
update()
|
update()
|
||||||
|
@ -33,7 +34,8 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
|
||||||
if (update()) {
|
if (update()) {
|
||||||
success(
|
success(
|
||||||
hashMapOf(
|
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
|
accelerometerRotation = newAccelerometerRotation
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
val newTransitionAnimationScale = Settings.Global.getFloat(context.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE)
|
||||||
|
if (transitionAnimationScale != newTransitionAnimationScale) {
|
||||||
|
transitionAnimationScale = newTransitionAnimationScale
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(LOG_TAG, "failed to get settings", e)
|
Log.w(LOG_TAG, "failed to get settings", e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
@ -20,7 +19,9 @@ extension ExtraAccessibilityAnimations on AccessibilityAnimations {
|
||||||
bool get animate {
|
bool get animate {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case AccessibilityAnimations.system:
|
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:
|
case AccessibilityAnimations.disabled:
|
||||||
return false;
|
return false;
|
||||||
case AccessibilityAnimations.enabled:
|
case AccessibilityAnimations.enabled:
|
||||||
|
|
|
@ -116,11 +116,18 @@ class Settings extends ChangeNotifier {
|
||||||
// cf Android `Settings.System.ACCELEROMETER_ROTATION`
|
// cf Android `Settings.System.ACCELEROMETER_ROTATION`
|
||||||
static const platformAccelerometerRotationKey = '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;
|
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();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
_isRotationLocked = isRotationLocked;
|
_isRotationLocked = isRotationLocked;
|
||||||
|
_areAnimationsRemoved = areAnimationsRemoved;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reset({required bool includeInternalKeys}) async {
|
Future<void> reset({required bool includeInternalKeys}) async {
|
||||||
|
@ -447,10 +454,11 @@ class Settings extends ChangeNotifier {
|
||||||
// platform settings
|
// platform settings
|
||||||
|
|
||||||
void _onPlatformSettingsChange(Map? fields) {
|
void _onPlatformSettingsChange(Map? fields) {
|
||||||
|
var changed = false;
|
||||||
fields?.forEach((key, value) {
|
fields?.forEach((key, value) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case platformAccelerometerRotationKey:
|
case platformAccelerometerRotationKey:
|
||||||
if (value is int) {
|
if (value is num) {
|
||||||
final newValue = value == 0;
|
final newValue = value == 0;
|
||||||
if (_isRotationLocked != newValue) {
|
if (_isRotationLocked != newValue) {
|
||||||
_isRotationLocked = newValue;
|
_isRotationLocked = newValue;
|
||||||
|
@ -458,18 +466,34 @@ class Settings extends ChangeNotifier {
|
||||||
windowService.requestOrientation();
|
windowService.requestOrientation();
|
||||||
}
|
}
|
||||||
_updateStreamController.add(key);
|
_updateStreamController.add(key);
|
||||||
notifyListeners();
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
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 _isRotationLocked = false;
|
||||||
|
|
||||||
bool get isRotationLocked => _isRotationLocked;
|
bool get isRotationLocked => _isRotationLocked;
|
||||||
|
|
||||||
|
bool _areAnimationsRemoved = false;
|
||||||
|
|
||||||
|
bool get areAnimationsRemoved => _areAnimationsRemoved;
|
||||||
|
|
||||||
// import/export
|
// import/export
|
||||||
|
|
||||||
String toJson() => jsonEncode(Map.fromEntries(
|
String toJson() => jsonEncode(Map.fromEntries(
|
||||||
|
|
|
@ -4,6 +4,16 @@ import 'package:flutter/services.dart';
|
||||||
class AccessibilityService {
|
class AccessibilityService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/accessibility');
|
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 {
|
static Future<bool> hasRecommendedTimeouts() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('hasRecommendedTimeouts');
|
final result = await platform.invokeMethod('hasRecommendedTimeouts');
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:aves/model/settings/accessibility_animations.dart';
|
import 'package:aves/model/settings/accessibility_animations.dart';
|
||||||
import 'package:aves/model/settings/enums.dart';
|
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -72,7 +71,7 @@ class Durations {
|
||||||
static const lastVersionCheckInterval = Duration(days: 7);
|
static const lastVersionCheckInterval = Duration(days: 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DurationsProvider extends StatefulWidget {
|
class DurationsProvider extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const DurationsProvider({
|
const DurationsProvider({
|
||||||
|
@ -80,30 +79,6 @@ class DurationsProvider extends StatefulWidget {
|
||||||
required this.child,
|
required this.child,
|
||||||
}) : super(key: key);
|
}) : 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ProxyProvider<Settings, DurationsData>(
|
return ProxyProvider<Settings, DurationsData>(
|
||||||
|
@ -111,7 +86,7 @@ class _DurationsProviderState extends State<DurationsProvider> with WidgetsBindi
|
||||||
final enabled = settings.accessibilityAnimations.animate;
|
final enabled = settings.accessibilityAnimations.animate;
|
||||||
return enabled ? DurationsData() : DurationsData.noAnimation();
|
return enabled ? DurationsData() : DurationsData.noAnimation();
|
||||||
},
|
},
|
||||||
child: widget.child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/model/settings/screen_on.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/media_store_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/services/common/services.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
|
@ -144,6 +145,7 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
Future<void> _setup() async {
|
Future<void> _setup() async {
|
||||||
await settings.init(
|
await settings.init(
|
||||||
isRotationLocked: await windowService.isRotationLocked(),
|
isRotationLocked: await windowService.isRotationLocked(),
|
||||||
|
areAnimationsRemoved: await AccessibilityService.areAnimationsRemoved(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// keep screen on
|
// keep screen on
|
||||||
|
|
Loading…
Reference in a new issue