accessibility: time to take action
This commit is contained in:
parent
ff1af89ce1
commit
b720f65754
23 changed files with 317 additions and 30 deletions
|
@ -5,11 +5,13 @@ All notable changes to this project will be documented in this file.
|
|||
### Added
|
||||
- Map: show items for bounds, open items in viewer, tap gesture to toggle fullscreen
|
||||
- Info: remove metadata (Exif, XMP, etc.)
|
||||
- Accessibility: support "time to take action" settings
|
||||
|
||||
### Changed
|
||||
- upgraded Flutter to stable v2.5.1
|
||||
- faster collection loading when launching the app
|
||||
- Collection: changed color & scale of thumbnail icons to match text
|
||||
- Albums / Countries / Tags: changed layout, with label below cover
|
||||
|
||||
### Fixed
|
||||
- album bookmarks & pins were reset when rescanning items
|
||||
|
|
|
@ -52,6 +52,7 @@ class MainActivity : FlutterActivity() {
|
|||
val messenger = flutterEngine!!.dartExecutor.binaryMessenger
|
||||
|
||||
// dart -> platform -> dart
|
||||
MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this))
|
||||
MethodChannel(messenger, AppAdapterHandler.CHANNEL).setMethodCallHandler(AppAdapterHandler(this))
|
||||
MethodChannel(messenger, DebugHandler.CHANNEL).setMethodCallHandler(DebugHandler(this))
|
||||
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler())
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package deckers.thibault.aves.channel.calls
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||
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 {
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"hasRecommendedTimeouts" -> safe(call, result, ::hasRecommendedTimeouts)
|
||||
"getRecommendedTimeoutMillis" -> safe(call, result, ::getRecommendedTimeoutMillis)
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasRecommendedTimeouts(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
|
||||
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
}
|
||||
|
||||
private fun getRecommendedTimeoutMillis(call: MethodCall, result: MethodChannel.Result) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
result.error("getRecommendedTimeoutMillis-sdk", "unsupported SDK version=${Build.VERSION.SDK_INT}", null)
|
||||
return
|
||||
}
|
||||
|
||||
val originalTimeoutMillis = call.argument<Int>("originalTimeoutMillis")
|
||||
val content = call.argument<List<String>>("content")
|
||||
if (originalTimeoutMillis == null || content == null) {
|
||||
result.error("getRecommendedTimeoutMillis-args", "failed because of missing arguments", null)
|
||||
return
|
||||
}
|
||||
|
||||
var uiContentFlags = 0
|
||||
content.forEach {
|
||||
uiContentFlags = when (it) {
|
||||
"controls" -> uiContentFlags or AccessibilityManager.FLAG_CONTENT_CONTROLS
|
||||
"icons" -> uiContentFlags or AccessibilityManager.FLAG_CONTENT_ICONS
|
||||
"text" -> uiContentFlags or AccessibilityManager.FLAG_CONTENT_TEXT
|
||||
else -> {
|
||||
result.error("getRecommendedTimeoutMillis-flag", "unsupported UI content flag=$it", null)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
|
||||
if (am == null) {
|
||||
result.error("getRecommendedTimeoutMillis-service", "failed to get accessibility manager", null)
|
||||
return
|
||||
}
|
||||
|
||||
val millis = am.getRecommendedTimeoutMillis(originalTimeoutMillis, uiContentFlags)
|
||||
result.success(millis)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL = "deckers.thibault/aves/a11y"
|
||||
}
|
||||
}
|
|
@ -102,7 +102,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
|
||||
private suspend fun getAppIcon(call: MethodCall, result: MethodChannel.Result) {
|
||||
val packageName = call.argument<String>("packageName")
|
||||
val sizeDip = call.argument<Double>("sizeDip")
|
||||
val sizeDip = call.argument<Number>("sizeDip")?.toDouble()
|
||||
if (packageName == null || sizeDip == null) {
|
||||
result.error("getAppIcon-args", "failed because of missing arguments", null)
|
||||
return
|
||||
|
|
|
@ -66,10 +66,10 @@ class MediaFileHandler(private val activity: Activity) : MethodCallHandler {
|
|||
val dateModifiedSecs = call.argument<Number>("dateModifiedSecs")?.toLong()
|
||||
val rotationDegrees = call.argument<Int>("rotationDegrees")
|
||||
val isFlipped = call.argument<Boolean>("isFlipped")
|
||||
val widthDip = call.argument<Double>("widthDip")
|
||||
val heightDip = call.argument<Double>("heightDip")
|
||||
val widthDip = call.argument<Number>("widthDip")?.toDouble()
|
||||
val heightDip = call.argument<Number>("heightDip")?.toDouble()
|
||||
val pageId = call.argument<Int>("pageId")
|
||||
val defaultSizeDip = call.argument<Double>("defaultSizeDip")
|
||||
val defaultSizeDip = call.argument<Number>("defaultSizeDip")?.toDouble()
|
||||
|
||||
if (uri == null || mimeType == null || dateModifiedSecs == null || rotationDegrees == null || isFlipped == null || widthDip == null || heightDip == null || defaultSizeDip == null) {
|
||||
result.error("getThumbnail-args", "failed because of missing arguments", null)
|
||||
|
|
|
@ -14,6 +14,19 @@
|
|||
}
|
||||
},
|
||||
|
||||
"timeSeconds": "{seconds, plural, =1{1 second} other{{seconds} seconds}}",
|
||||
"@timeSeconds": {
|
||||
"placeholders": {
|
||||
"seconds": {}
|
||||
}
|
||||
},
|
||||
"timeMinutes": "{minutes, plural, =1{1 minute} other{{minutes} minutes}}",
|
||||
"@timeMinutes": {
|
||||
"placeholders": {
|
||||
"minutes": {}
|
||||
}
|
||||
},
|
||||
|
||||
"applyButtonLabel": "APPLY",
|
||||
"@applyButtonLabel": {},
|
||||
"deleteButtonLabel": "DELETE",
|
||||
|
@ -616,6 +629,8 @@
|
|||
"@settingsPageTitle": {},
|
||||
"settingsSystemDefault": "System",
|
||||
"@settingsSystemDefault": {},
|
||||
"settingsDefault": "Default",
|
||||
"@settingsDefault": {},
|
||||
|
||||
"settingsActionExport": "Export",
|
||||
"@settingsActionExport": {},
|
||||
|
@ -782,6 +797,13 @@
|
|||
"settingsStorageAccessRevokeTooltip": "Revoke",
|
||||
"@settingsStorageAccessRevokeTooltip": {},
|
||||
|
||||
"settingsSectionAccessibility": "Accessibility",
|
||||
"@settingsSectionAccessibility": {},
|
||||
"settingsTimeToTakeActionTile": "Time to take action",
|
||||
"@settingsTimeToTakeActionTile": {},
|
||||
"settingsTimeToTakeActionTitle": "Time to Take Action",
|
||||
"@settingsTimeToTakeActionTitle": {},
|
||||
|
||||
"settingsSectionLanguage": "Language & Formats",
|
||||
"@settingsSectionLanguage": {},
|
||||
"settingsLanguage": "Language",
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
"welcomeTermsToggle": "이용약관에 동의합니다",
|
||||
"itemCount": "{count, plural, other{{count}개}}",
|
||||
|
||||
"timeSeconds": "{seconds, plural, other{{seconds}초}}",
|
||||
"timeMinutes": "{minutes, plural, other{{minutes}분}}",
|
||||
|
||||
"applyButtonLabel": "확인",
|
||||
"deleteButtonLabel": "삭제",
|
||||
"nextButtonLabel": "다음",
|
||||
|
@ -294,6 +297,7 @@
|
|||
|
||||
"settingsPageTitle": "설정",
|
||||
"settingsSystemDefault": "시스템",
|
||||
"settingsDefault": "기본",
|
||||
|
||||
"settingsActionExport": "내보내기",
|
||||
"settingsActionImport": "가져오기",
|
||||
|
@ -384,6 +388,10 @@
|
|||
"settingsStorageAccessEmpty": "접근 허용이 없습니다",
|
||||
"settingsStorageAccessRevokeTooltip": "취소",
|
||||
|
||||
"settingsSectionAccessibility": "접근성",
|
||||
"settingsTimeToTakeActionTile": "액션 취하기 전 대기 시간",
|
||||
"settingsTimeToTakeActionTitle": "액션 취하기 전 대기 시간",
|
||||
|
||||
"settingsSectionLanguage": "언어 및 표시 형식",
|
||||
"settingsLanguage": "언어",
|
||||
"settingsCoordinateFormatTile": "좌표 표현",
|
||||
|
|
23
lib/model/settings/a11y_timeout.dart
Normal file
23
lib/model/settings/a11y_timeout.dart
Normal file
|
@ -0,0 +1,23 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraAccessibilityTimeout on AccessibilityTimeout {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case AccessibilityTimeout.system:
|
||||
return context.l10n.settingsSystemDefault;
|
||||
case AccessibilityTimeout.appDefault:
|
||||
return context.l10n.settingsDefault;
|
||||
case AccessibilityTimeout.s10:
|
||||
return context.l10n.timeSeconds(10);
|
||||
case AccessibilityTimeout.s30:
|
||||
return context.l10n.timeSeconds(30);
|
||||
case AccessibilityTimeout.s60:
|
||||
return context.l10n.timeMinutes(1);
|
||||
case AccessibilityTimeout.s120:
|
||||
return context.l10n.timeMinutes(2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,8 +12,6 @@ extension ExtraCoordinateFormat on CoordinateFormat {
|
|||
return context.l10n.coordinateFormatDms;
|
||||
case CoordinateFormat.decimal:
|
||||
return context.l10n.coordinateFormatDecimal;
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,8 +21,6 @@ extension ExtraCoordinateFormat on CoordinateFormat {
|
|||
return toDMS(latLng).join(', ');
|
||||
case CoordinateFormat.decimal:
|
||||
return [latLng.latitude, latLng.longitude].map((n) => n.toStringAsFixed(6)).join(', ');
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,8 @@ class SettingsDefaults {
|
|||
static const showOverlayMinimap = false;
|
||||
static const showOverlayInfo = true;
|
||||
static const showOverlayShootingDetails = false;
|
||||
|
||||
// `enableOverlayBlurEffect` has a contextual default value
|
||||
static const enableOverlayBlurEffect = true;
|
||||
static const viewerUseCutout = true;
|
||||
|
||||
|
@ -78,6 +80,7 @@ class SettingsDefaults {
|
|||
static const subtitleBackgroundColor = Colors.transparent;
|
||||
|
||||
// info
|
||||
// `infoMapStyle` has a contextual default value
|
||||
static const infoMapStyle = EntryMapStyle.stamenWatercolor;
|
||||
static const infoMapZoom = 12.0;
|
||||
static const coordinateFormat = CoordinateFormat.dms;
|
||||
|
@ -87,4 +90,8 @@ class SettingsDefaults {
|
|||
|
||||
// search
|
||||
static const saveSearchHistory = true;
|
||||
|
||||
// accessibility
|
||||
// `timeToTakeAction` has a contextual default value
|
||||
static const timeToTakeAction = AccessibilityTimeout.appDefault;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
enum CoordinateFormat { dms, decimal }
|
||||
|
||||
enum AccessibilityTimeout { system, appDefault, s10, s30, s60, s120 }
|
||||
|
||||
enum EntryBackground { black, white, checkered }
|
||||
|
||||
enum HomePageSetting { collection, albums }
|
||||
|
|
|
@ -12,8 +12,6 @@ extension ExtraHomePageSetting on HomePageSetting {
|
|||
return context.l10n.collectionPageTitle;
|
||||
case HomePageSetting.albums:
|
||||
return context.l10n.albumPageTitle;
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,8 +21,6 @@ extension ExtraHomePageSetting on HomePageSetting {
|
|||
return CollectionPage.routeName;
|
||||
case HomePageSetting.albums:
|
||||
return AlbumListPage.routeName;
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,6 @@ extension ExtraEntryMapStyle on EntryMapStyle {
|
|||
return context.l10n.mapStyleStamenToner;
|
||||
case EntryMapStyle.stamenWatercolor:
|
||||
return context.l10n.mapStyleStamenWatercolor;
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ extension ExtraKeepScreenOn on KeepScreenOn {
|
|||
return context.l10n.keepScreenOnViewerOnly;
|
||||
case KeepScreenOn.always:
|
||||
return context.l10n.keepScreenOnAlways;
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:aves/model/settings/defaults.dart';
|
|||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/model/settings/map_style.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/services/a11y_service.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -104,6 +105,9 @@ class Settings extends ChangeNotifier {
|
|||
static const saveSearchHistoryKey = 'save_search_history';
|
||||
static const searchHistoryKey = 'search_history';
|
||||
|
||||
// accessibility
|
||||
static const timeToTakeActionKey = 'time_to_take_action';
|
||||
|
||||
// version
|
||||
static const lastVersionCheckDateKey = 'last_version_check_date';
|
||||
|
||||
|
@ -137,6 +141,10 @@ class Settings extends ChangeNotifier {
|
|||
final styles = EntryMapStyle.values.whereNot((v) => v.isGoogleMaps).toList();
|
||||
infoMapStyle = styles[Random().nextInt(styles.length)];
|
||||
}
|
||||
|
||||
// accessibility
|
||||
final hasRecommendedTimeouts = await AccessibilityService.hasRecommendedTimeouts();
|
||||
timeToTakeAction = hasRecommendedTimeouts ? AccessibilityTimeout.system : AccessibilityTimeout.appDefault;
|
||||
}
|
||||
|
||||
// app
|
||||
|
@ -372,6 +380,12 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
set searchHistory(List<CollectionFilter> newValue) => setAndNotify(searchHistoryKey, newValue.map((filter) => filter.toJson()).toList());
|
||||
|
||||
// accessibility
|
||||
|
||||
AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values);
|
||||
|
||||
set timeToTakeAction(AccessibilityTimeout newValue) => setAndNotify(timeToTakeActionKey, newValue.toString());
|
||||
|
||||
// version
|
||||
|
||||
DateTime get lastVersionCheckDate => DateTime.fromMillisecondsSinceEpoch(_prefs!.getInt(lastVersionCheckDateKey) ?? 0);
|
||||
|
@ -524,6 +538,7 @@ class Settings extends ChangeNotifier {
|
|||
case infoMapStyleKey:
|
||||
case coordinateFormatKey:
|
||||
case imageBackgroundKey:
|
||||
case timeToTakeActionKey:
|
||||
if (value is String) {
|
||||
_prefs!.setString(key, value);
|
||||
} else {
|
||||
|
|
|
@ -13,8 +13,6 @@ extension ExtraVideoLoopMode on VideoLoopMode {
|
|||
return context.l10n.videoLoopModeShortOnly;
|
||||
case VideoLoopMode.always:
|
||||
return context.l10n.videoLoopModeAlways;
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
42
lib/services/a11y_service.dart
Normal file
42
lib/services/a11y_service.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class AccessibilityService {
|
||||
static const platform = MethodChannel('deckers.thibault/aves/a11y');
|
||||
|
||||
static Future<bool> hasRecommendedTimeouts() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('hasRecommendedTimeouts');
|
||||
if (result != null) return result as bool;
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Future<int> getRecommendedTimeToRead(int originalTimeoutMillis) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getRecommendedTimeoutMillis', <String, dynamic>{
|
||||
'originalTimeoutMillis': originalTimeoutMillis,
|
||||
'content': ['icons', 'text']
|
||||
});
|
||||
if (result != null) return result as int;
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return originalTimeoutMillis;
|
||||
}
|
||||
|
||||
static Future<int> getRecommendedTimeToTakeAction(int originalTimeoutMillis) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getRecommendedTimeoutMillis', <String, dynamic>{
|
||||
'originalTimeoutMillis': originalTimeoutMillis,
|
||||
'content': ['controls', 'icons', 'text']
|
||||
});
|
||||
if (result != null) return result as int;
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return originalTimeoutMillis;
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ class Durations {
|
|||
static const quickActionHighlightAnimation = Duration(milliseconds: 200);
|
||||
|
||||
// delays & refresh intervals
|
||||
static const opToastDisplay = Duration(seconds: 3);
|
||||
static const opToastTextDisplay = Duration(seconds: 3);
|
||||
static const opToastActionDisplay = Duration(seconds: 5);
|
||||
static const infoScrollMonitoringTimerDelay = Duration(milliseconds: 100);
|
||||
static const collectionScrollMonitoringTimerDelay = Duration(milliseconds: 100);
|
||||
|
|
|
@ -7,6 +7,7 @@ class AIcons {
|
|||
static const IconData video = Icons.movie_outlined;
|
||||
static const IconData vector = Icons.code_outlined;
|
||||
|
||||
static const IconData a11y = Icons.accessibility_new_outlined;
|
||||
static const IconData android = Icons.android;
|
||||
static const IconData broken = Icons.broken_image_outlined;
|
||||
static const IconData checked = Icons.done_outlined;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/services/a11y_service.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
@ -15,7 +18,7 @@ mixin FeedbackMixin {
|
|||
|
||||
// provide the messenger if feedback happens as the widget is disposed
|
||||
void showFeedbackWithMessenger(BuildContext context, ScaffoldMessengerState messenger, String message, [SnackBarAction? action]) {
|
||||
final duration = action != null ? Durations.opToastActionDisplay : Durations.opToastDisplay;
|
||||
_getSnackBarDuration(action != null).then((duration) {
|
||||
final progressColor = Theme.of(context).colorScheme.secondary;
|
||||
messenger.showSnackBar(SnackBar(
|
||||
content: _FeedbackMessage(
|
||||
|
@ -26,6 +29,27 @@ mixin FeedbackMixin {
|
|||
action: action,
|
||||
duration: duration,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
Future<Duration> _getSnackBarDuration(bool hasAction) async {
|
||||
final appDefaultDuration = hasAction ? Durations.opToastActionDisplay : Durations.opToastTextDisplay;
|
||||
switch (settings.timeToTakeAction) {
|
||||
case AccessibilityTimeout.system:
|
||||
final original = appDefaultDuration.inMilliseconds;
|
||||
final millis = await (hasAction ? AccessibilityService.getRecommendedTimeToTakeAction(original) : AccessibilityService.getRecommendedTimeToRead(original));
|
||||
return Duration(milliseconds: millis);
|
||||
case AccessibilityTimeout.appDefault:
|
||||
return appDefaultDuration;
|
||||
case AccessibilityTimeout.s10:
|
||||
return const Duration(seconds: 10);
|
||||
case AccessibilityTimeout.s30:
|
||||
return const Duration(seconds: 30);
|
||||
case AccessibilityTimeout.s60:
|
||||
return const Duration(minutes: 1);
|
||||
case AccessibilityTimeout.s120:
|
||||
return const Duration(minutes: 2);
|
||||
}
|
||||
}
|
||||
|
||||
// report overlay for multiple operations
|
||||
|
|
32
lib/widgets/settings/a11y/a11y.dart
Normal file
32
lib/widgets/settings/a11y/a11y.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/color_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||
import 'package:aves/widgets/settings/a11y/time_to_take_action.dart';
|
||||
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AccessibilitySection extends StatelessWidget {
|
||||
final ValueNotifier<String?> expandedNotifier;
|
||||
|
||||
const AccessibilitySection({
|
||||
Key? key,
|
||||
required this.expandedNotifier,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AvesExpansionTile(
|
||||
leading: SettingsTileLeading(
|
||||
icon: AIcons.a11y,
|
||||
color: stringToColor('Accessibility'),
|
||||
),
|
||||
title: context.l10n.settingsSectionAccessibility,
|
||||
expandedNotifier: expandedNotifier,
|
||||
showHighlight: false,
|
||||
children: const [
|
||||
TimeToTakeActionTile(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
56
lib/widgets/settings/a11y/time_to_take_action.dart
Normal file
56
lib/widgets/settings/a11y/time_to_take_action.dart
Normal file
|
@ -0,0 +1,56 @@
|
|||
import 'package:aves/model/settings/a11y_timeout.dart';
|
||||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/services/a11y_service.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class TimeToTakeActionTile extends StatefulWidget {
|
||||
const TimeToTakeActionTile({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_TimeToTakeActionTileState createState() => _TimeToTakeActionTileState();
|
||||
}
|
||||
|
||||
class _TimeToTakeActionTileState extends State<TimeToTakeActionTile> {
|
||||
late Future<bool> _hasSystemOptionLoader;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_hasSystemOptionLoader = AccessibilityService.hasRecommendedTimeouts();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentTimeToTakeAction = context.select<Settings, AccessibilityTimeout>((s) => s.timeToTakeAction);
|
||||
|
||||
return FutureBuilder<bool>(
|
||||
future: _hasSystemOptionLoader,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError || !snapshot.hasData) return const SizedBox.shrink();
|
||||
final hasSystemOption = snapshot.data!;
|
||||
final optionValues = hasSystemOption ? AccessibilityTimeout.values : AccessibilityTimeout.values.where((v) => v != AccessibilityTimeout.system).toList();
|
||||
return ListTile(
|
||||
title: Text(context.l10n.settingsTimeToTakeActionTile),
|
||||
subtitle: Text(currentTimeToTakeAction.getName(context)),
|
||||
onTap: () async {
|
||||
final value = await showDialog<AccessibilityTimeout>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<AccessibilityTimeout>(
|
||||
initialValue: currentTimeToTakeAction,
|
||||
options: Map.fromEntries(optionValues.map((v) => MapEntry(v, v.getName(context)))),
|
||||
title: context.l10n.settingsTimeToTakeActionTitle,
|
||||
),
|
||||
);
|
||||
if (value != null) {
|
||||
settings.timeToTakeAction = value;
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
|||
import 'package:aves/widgets/common/basic/menu.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/settings/a11y/a11y.dart';
|
||||
import 'package:aves/widgets/settings/language/language.dart';
|
||||
import 'package:aves/widgets/settings/navigation/navigation.dart';
|
||||
import 'package:aves/widgets/settings/privacy/privacy.dart';
|
||||
|
@ -91,6 +92,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
|
|||
ViewerSection(expandedNotifier: _expandedNotifier),
|
||||
VideoSection(expandedNotifier: _expandedNotifier),
|
||||
PrivacySection(expandedNotifier: _expandedNotifier),
|
||||
AccessibilitySection(expandedNotifier: _expandedNotifier),
|
||||
LanguageSection(expandedNotifier: _expandedNotifier),
|
||||
],
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue