tv layout on non-tv devices

This commit is contained in:
Thibault Deckers 2023-01-05 17:15:00 +01:00
parent 4c6a4e3568
commit b343e32db0
42 changed files with 379 additions and 237 deletions

View file

@ -1,16 +1,21 @@
package deckers.thibault.aves.channel.calls package deckers.thibault.aves.channel.calls
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent
import android.media.session.PlaybackState import android.media.session.PlaybackState
import android.net.Uri import android.net.Uri
import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log import android.util.Log
import android.view.KeyEvent
import androidx.media.session.MediaButtonReceiver
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
import deckers.thibault.aves.utils.FlutterUtils import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.getParcelableExtraCompat
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
@ -74,7 +79,9 @@ class MediaSessionHandler(private val context: Context) : MethodCallHandler {
var session = sessions[uri] var session = sessions[uri]
if (session == null) { if (session == null) {
session = MediaSessionCompat(context, "aves-$uri") val mbrIntent = MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY_PAUSE)
val mbrName = ComponentName(context, MediaButtonReceiver::class.java)
session = MediaSessionCompat(context, "aves-$uri", mbrName, mbrIntent)
sessions[uri] = session sessions[uri] = session
val metadata = MediaMetadataCompat.Builder() val metadata = MediaMetadataCompat.Builder()
@ -86,6 +93,12 @@ class MediaSessionHandler(private val context: Context) : MethodCallHandler {
session.setMetadata(metadata) session.setMetadata(metadata)
val callback: MediaSessionCompat.Callback = object : MediaSessionCompat.Callback() { val callback: MediaSessionCompat.Callback = object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
val keyEvent = mediaButtonEvent.getParcelableExtraCompat<KeyEvent?>(Intent.EXTRA_KEY_EVENT) ?: return false
Log.d(LOG_TAG, "TLAD onMediaButtonEvent keyEvent=$keyEvent")
return super.onMediaButtonEvent(mediaButtonEvent)
}
override fun onPlay() { override fun onPlay() {
super.onPlay() super.onPlay()
Log.d(LOG_TAG, "TLAD onPlay uri=$uri") Log.d(LOG_TAG, "TLAD onPlay uri=$uri")

View file

@ -656,6 +656,7 @@
"settingsSystemDefault": "System default", "settingsSystemDefault": "System default",
"settingsDefault": "Default", "settingsDefault": "Default",
"settingsDisabled": "Disabled", "settingsDisabled": "Disabled",
"settingsModificationWarningDialogMessage": "Other settings will be modified.",
"settingsSearchFieldLabel": "Search settings", "settingsSearchFieldLabel": "Search settings",
"settingsSearchEmpty": "No matching setting", "settingsSearchEmpty": "No matching setting",
@ -816,6 +817,7 @@
"settingsThemeEnableDynamicColor": "Dynamic color", "settingsThemeEnableDynamicColor": "Dynamic color",
"settingsDisplayRefreshRateModeTile": "Display refresh rate", "settingsDisplayRefreshRateModeTile": "Display refresh rate",
"settingsDisplayRefreshRateModeDialogTitle": "Refresh Rate", "settingsDisplayRefreshRateModeDialogTitle": "Refresh Rate",
"settingsDisplayUseTvInterface": "Android TV interface",
"settingsLanguageSectionTitle": "Language & Formats", "settingsLanguageSectionTitle": "Language & Formats",
"settingsLanguageTile": "Language", "settingsLanguageTile": "Language",

View file

@ -24,6 +24,7 @@ class SettingsDefaults {
static const themeColorMode = AvesThemeColorMode.polychrome; static const themeColorMode = AvesThemeColorMode.polychrome;
static const enableDynamicColor = false; static const enableDynamicColor = false;
static const enableBlurEffect = true; // `enableBlurEffect` has a contextual default value static const enableBlurEffect = true; // `enableBlurEffect` has a contextual default value
static const forceTvLayout = false;
// navigation // navigation
static const mustBackTwiceToExit = true; static const mustBackTwiceToExit = true;

View file

@ -70,6 +70,7 @@ class Settings extends ChangeNotifier {
static const themeColorModeKey = 'theme_color_mode'; static const themeColorModeKey = 'theme_color_mode';
static const enableDynamicColorKey = 'dynamic_color'; static const enableDynamicColorKey = 'dynamic_color';
static const enableBlurEffectKey = 'enable_overlay_blur_effect'; static const enableBlurEffectKey = 'enable_overlay_blur_effect';
static const forceTvLayoutKey = 'force_tv_layout';
// navigation // navigation
static const mustBackTwiceToExitKey = 'must_back_twice_to_exit'; static const mustBackTwiceToExitKey = 'must_back_twice_to_exit';
@ -240,7 +241,11 @@ class Settings extends ChangeNotifier {
} }
} }
if (device.isTelevision) { applyTvSettings();
}
void applyTvSettings() {
if (settings.useTvLayout) {
themeBrightness = AvesThemeBrightness.dark; themeBrightness = AvesThemeBrightness.dark;
mustBackTwiceToExit = false; mustBackTwiceToExit = false;
// address `TV-BU` / `TV-BY` requirements from https://developer.android.com/docs/quality-guidelines/tv-app-quality // address `TV-BU` / `TV-BY` requirements from https://developer.android.com/docs/quality-guidelines/tv-app-quality
@ -392,6 +397,12 @@ class Settings extends ChangeNotifier {
set enableBlurEffect(bool newValue) => setAndNotify(enableBlurEffectKey, newValue); set enableBlurEffect(bool newValue) => setAndNotify(enableBlurEffectKey, newValue);
bool get forceTvLayout => getBool(forceTvLayoutKey) ?? SettingsDefaults.forceTvLayout;
set forceTvLayout(bool newValue) => setAndNotify(forceTvLayoutKey, newValue);
bool get useTvLayout => device.isTelevision || forceTvLayout;
// navigation // navigation
bool get mustBackTwiceToExit => getBool(mustBackTwiceToExitKey) ?? SettingsDefaults.mustBackTwiceToExit; bool get mustBackTwiceToExit => getBool(mustBackTwiceToExitKey) ?? SettingsDefaults.mustBackTwiceToExit;

View file

@ -1,4 +1,4 @@
import 'package:aves/model/device.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/about/app_ref.dart'; import 'package:aves/widgets/about/app_ref.dart';
import 'package:aves/widgets/about/bug_report.dart'; import 'package:aves/widgets/about/bug_report.dart';
import 'package:aves/widgets/about/credits.dart'; import 'package:aves/widgets/about/credits.dart';
@ -28,7 +28,7 @@ class AboutPage extends StatelessWidget {
delegate: SliverChildListDelegate( delegate: SliverChildListDelegate(
[ [
const AppReference(), const AppReference(),
if (!device.isTelevision) ...[ if (!settings.useTvLayout) ...[
const Divider(), const Divider(),
const BugReport(), const BugReport(),
], ],
@ -46,7 +46,8 @@ class AboutPage extends StatelessWidget {
], ],
); );
if (device.isTelevision) { if (settings.useTvLayout) {
final isRtl = context.isRtl;
return Scaffold( return Scaffold(
body: AvesPopScope( body: AvesPopScope(
handlers: const [TvNavigationPopHandler.pop], handlers: const [TvNavigationPopHandler.pop],
@ -55,7 +56,13 @@ class AboutPage extends StatelessWidget {
TvRail( TvRail(
controller: context.read<TvRailController>(), controller: context.read<TvRailController>(),
), ),
Expanded(child: body), Expanded(
child: SafeArea(
left: isRtl,
right: !isRtl,
child: body,
),
),
], ],
), ),
), ),

View file

@ -416,17 +416,20 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
await device.init(); await device.init();
if (device.isTelevision) { await mobileServices.init();
await settings.init(monitorPlatformSettings: true);
settings.isRotationLocked = await windowService.isRotationLocked();
settings.areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved();
if (settings.useTvLayout) {
_pageTransitionsBuilderNotifier.value = const TvPageTransitionsBuilder(); _pageTransitionsBuilderNotifier.value = const TvPageTransitionsBuilder();
_tvMediaQueryModifierNotifier.value = (mq) => mq.copyWith( _tvMediaQueryModifierNotifier.value = (mq) => mq.copyWith(
textScaleFactor: 1.1, textScaleFactor: 1.1,
navigationMode: NavigationMode.directional, navigationMode: NavigationMode.directional,
); );
if (settings.forceTvLayout) {
await windowService.requestOrientation(Orientation.landscape);
}
} }
await mobileServices.init();
await settings.init(monitorPlatformSettings: true);
settings.isRotationLocked = await windowService.isRotationLocked();
settings.areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved();
_monitorSettings(); _monitorSettings();
FijkLog.setLevel(FijkLogLevel.Warn); FijkLog.setLevel(FijkLogLevel.Warn);
@ -448,15 +451,28 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
void applyKeepScreenOn() => settings.keepScreenOn.apply(); void applyKeepScreenOn() => settings.keepScreenOn.apply();
void applyIsRotationLocked() { void applyIsRotationLocked() {
if (!settings.isRotationLocked) { if (!settings.isRotationLocked && !settings.useTvLayout) {
windowService.requestOrientation(); windowService.requestOrientation();
} }
} }
void applyForceTvLayout() {
settings.applyTvSettings();
windowService.requestOrientation(settings.forceTvLayout ? Orientation.landscape : null);
AvesApp.navigatorKey.currentState!.pushAndRemoveUntil(
MaterialPageRoute(
settings: const RouteSettings(name: HomePage.routeName),
builder: (_) => _getFirstPage(),
),
(route) => false,
);
}
settings.updateStream.where((event) => event.key == Settings.isInstalledAppAccessAllowedKey).listen((_) => applyIsInstalledAppAccessAllowed()); settings.updateStream.where((event) => event.key == Settings.isInstalledAppAccessAllowedKey).listen((_) => applyIsInstalledAppAccessAllowed());
settings.updateStream.where((event) => event.key == Settings.displayRefreshRateModeKey).listen((_) => applyDisplayRefreshRateMode()); settings.updateStream.where((event) => event.key == Settings.displayRefreshRateModeKey).listen((_) => applyDisplayRefreshRateMode());
settings.updateStream.where((event) => event.key == Settings.keepScreenOnKey).listen((_) => applyKeepScreenOn()); settings.updateStream.where((event) => event.key == Settings.keepScreenOnKey).listen((_) => applyKeepScreenOn());
settings.updateStream.where((event) => event.key == Settings.platformAccelerometerRotationKey).listen((_) => applyIsRotationLocked()); settings.updateStream.where((event) => event.key == Settings.platformAccelerometerRotationKey).listen((_) => applyIsRotationLocked());
settings.updateStream.where((event) => event.key == Settings.forceTvLayoutKey).listen((_) => applyForceTvLayout());
applyDisplayRefreshRateMode(); applyDisplayRefreshRateMode();
applyKeepScreenOn(); applyKeepScreenOn();

View file

@ -151,70 +151,75 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
final selection = context.watch<Selection<AvesEntry>>(); final selection = context.watch<Selection<AvesEntry>>();
final isSelecting = selection.isSelecting; final isSelecting = selection.isSelecting;
_isSelectingNotifier.value = isSelecting; _isSelectingNotifier.value = isSelecting;
return AnimatedBuilder( return NotificationListener<ScrollNotification>(
animation: collection.filterChangeNotifier, // cancel notification bubbling so that the draggable scroll bar
builder: (context, child) { // does not misinterpret filter bar scrolling for collection scrolling
final removableFilters = appMode != AppMode.pickMediaInternal; onNotification: (notification) => true,
return Selector<Query, bool>( child: AnimatedBuilder(
selector: (context, query) => query.enabled, animation: collection.filterChangeNotifier,
builder: (context, queryEnabled, child) { builder: (context, child) {
return Selector<Settings, List<EntrySetAction>>( final removableFilters = appMode != AppMode.pickMediaInternal;
selector: (context, s) => s.collectionBrowsingQuickActions, return Selector<Query, bool>(
builder: (context, _, child) { selector: (context, query) => query.enabled,
final isTelevision = device.isTelevision; builder: (context, queryEnabled, child) {
final actions = _buildActions(context, selection); return Selector<Settings, List<EntrySetAction>>(
final onFilterTap = removableFilters ? collection.removeFilter : null; selector: (context, s) => s.collectionBrowsingQuickActions,
return AvesAppBar( builder: (context, _, child) {
contentHeight: appBarContentHeight, final useTvLayout = settings.useTvLayout;
leading: _buildAppBarLeading( final actions = _buildActions(context, selection);
hasDrawer: appMode.canNavigate, final onFilterTap = removableFilters ? collection.removeFilter : null;
isSelecting: isSelecting, return AvesAppBar(
), contentHeight: appBarContentHeight,
title: _buildAppBarTitle(isSelecting), leading: _buildAppBarLeading(
actions: isTelevision ? [] : actions, hasDrawer: appMode.canNavigate,
bottom: Column( isSelecting: isSelecting,
children: [ ),
if (isTelevision) title: _buildAppBarTitle(isSelecting),
SizedBox( actions: useTvLayout ? [] : actions,
height: CaptionedButton.getTelevisionButtonHeight(context), bottom: Column(
child: ListView( children: [
padding: const EdgeInsets.symmetric(horizontal: 8), if (useTvLayout)
scrollDirection: Axis.horizontal, SizedBox(
children: actions, height: CaptionedButton.getTelevisionButtonHeight(context),
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 8),
scrollDirection: Axis.horizontal,
children: actions,
),
), ),
), if (showFilterBar)
if (showFilterBar) NotificationListener<ReverseFilterNotification>(
NotificationListener<ReverseFilterNotification>( onNotification: (notification) {
onNotification: (notification) { collection.addFilter(notification.reversedFilter);
collection.addFilter(notification.reversedFilter); return true;
return true; },
}, child: FilterBar(
child: FilterBar( filters: visibleFilters,
filters: visibleFilters, onTap: onFilterTap,
onTap: onFilterTap, onRemove: onFilterTap,
onRemove: onFilterTap, ),
), ),
), if (queryEnabled)
if (queryEnabled) EntryQueryBar(
EntryQueryBar( queryNotifier: context.select<Query, ValueNotifier<String>>((query) => query.queryNotifier),
queryNotifier: context.select<Query, ValueNotifier<String>>((query) => query.queryNotifier), focusNode: _queryBarFocusNode,
focusNode: _queryBarFocusNode, ),
), ],
], ),
), transitionKey: isSelecting,
transitionKey: isSelecting, );
); },
}, );
); },
}, );
); },
}, ),
); );
} }
double get appBarContentHeight { double get appBarContentHeight {
double height = kToolbarHeight; double height = kToolbarHeight;
if (device.isTelevision) { if (settings.useTvLayout) {
height += CaptionedButton.getTelevisionButtonHeight(context); height += CaptionedButton.getTelevisionButtonHeight(context);
} }
if (showFilterBar) { if (showFilterBar) {
@ -227,7 +232,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
} }
Widget? _buildAppBarLeading({required bool hasDrawer, required bool isSelecting}) { Widget? _buildAppBarLeading({required bool hasDrawer, required bool isSelecting}) {
if (device.isTelevision) return null; if (settings.useTvLayout) return null;
if (!hasDrawer) { if (!hasDrawer) {
return const CloseButton(); return const CloseButton();
@ -309,7 +314,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
selectedItemCount: selectedItemCount, selectedItemCount: selectedItemCount,
); );
return device.isTelevision return settings.useTvLayout
? _buildTelevisionActions( ? _buildTelevisionActions(
context: context, context: context,
appMode: appMode, appMode: appMode,

View file

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/favourites.dart'; import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/favourite.dart';
@ -57,7 +56,7 @@ class CollectionGrid extends StatefulWidget {
static const double fixedExtentLayoutSpacing = 2; static const double fixedExtentLayoutSpacing = 2;
static const double mosaicLayoutSpacing = 4; static const double mosaicLayoutSpacing = 4;
static int get columnCountDefault => device.isTelevision ? 6 : 4; static int get columnCountDefault => settings.useTvLayout ? 6 : 4;
const CollectionGrid({ const CollectionGrid({
super.key, super.key,
@ -176,7 +175,7 @@ class _CollectionGridContentState extends State<_CollectionGridContent> {
tileLayout: tileLayout, tileLayout: tileLayout,
isScrollingNotifier: _isScrollingNotifier, isScrollingNotifier: _isScrollingNotifier,
); );
if (!device.isTelevision) return tile; if (!settings.useTvLayout) return tile;
return Focus( return Focus(
onFocusChange: (focused) { onFocusChange: (focused) {

View file

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/query.dart';
@ -122,7 +121,7 @@ class _CollectionPageState extends State<CollectionPage> {
); );
Widget page; Widget page;
if (device.isTelevision) { if (settings.useTvLayout) {
page = Scaffold( page = Scaffold(
body: Row( body: Row(
children: [ children: [

View file

@ -69,7 +69,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
return isSelecting && selectedItemCount == itemCount; return isSelecting && selectedItemCount == itemCount;
// browsing // browsing
case EntrySetAction.searchCollection: case EntrySetAction.searchCollection:
return !device.isTelevision && appMode.canNavigate && !isSelecting; return !settings.useTvLayout && appMode.canNavigate && !isSelecting;
case EntrySetAction.toggleTitleSearch: case EntrySetAction.toggleTitleSearch:
return !isSelecting; return !isSelecting;
case EntrySetAction.addShortcut: case EntrySetAction.addShortcut:
@ -82,7 +82,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
case EntrySetAction.stats: case EntrySetAction.stats:
return isMain; return isMain;
case EntrySetAction.rescan: case EntrySetAction.rescan:
return !device.isTelevision && isMain && !isTrash; return !settings.useTvLayout && isMain && !isTrash;
// selecting // selecting
case EntrySetAction.share: case EntrySetAction.share:
case EntrySetAction.toggleFavourite: case EntrySetAction.toggleFavourite:

View file

@ -82,20 +82,15 @@ class _FilterBarState extends State<FilterBar> {
// chip border clipping when the floating app bar is fading // chip border clipping when the floating app bar is fading
color: Colors.transparent, color: Colors.transparent,
height: FilterBar.preferredHeight, height: FilterBar.preferredHeight,
child: NotificationListener<ScrollNotification>( child: AnimatedList(
// cancel notification bubbling so that the draggable scroll bar key: _animatedListKey,
// does not misinterpret filter bar scrolling for collection scrolling initialItemCount: filters.length,
onNotification: (notification) => true, scrollDirection: Axis.horizontal,
child: AnimatedList( padding: FilterBar.rowPadding,
key: _animatedListKey, itemBuilder: (context, index, animation) {
initialItemCount: filters.length, if (index >= filters.length) return const SizedBox();
scrollDirection: Axis.horizontal, return _buildChip(filters.toList()[index]);
padding: FilterBar.rowPadding, },
itemBuilder: (context, index, animation) {
if (index >= filters.length) return const SizedBox();
return _buildChip(filters.toList()[index]);
},
),
), ),
); );
} }

View file

@ -1,4 +1,4 @@
import 'package:aves/model/device.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/borders.dart'; import 'package:aves/widgets/common/fx/borders.dart';
@ -72,13 +72,13 @@ class _ColorPickerDialogState extends State<ColorPickerDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isTelevision = device.isTelevision; final useTvLayout = settings.useTvLayout;
return AvesDialog( return AvesDialog(
scrollableContent: [ scrollableContent: [
ColorPicker( ColorPicker(
color: color, color: color,
onColorChanged: (v) => color = v, onColorChanged: (v) => color = v,
pickersEnabled: isTelevision pickersEnabled: useTvLayout
? const { ? const {
ColorPickerType.primary: true, ColorPickerType.primary: true,
ColorPickerType.accent: false, ColorPickerType.accent: false,
@ -90,7 +90,7 @@ class _ColorPickerDialogState extends State<ColorPickerDialog> {
}, },
hasBorder: true, hasBorder: true,
borderRadius: 20, borderRadius: 20,
subheading: isTelevision ? const SizedBox(height: 16) : null, subheading: useTvLayout ? const SizedBox(height: 16) : null,
) )
], ],
actions: [ actions: [

View file

@ -1,6 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/model/device.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/common/extensions/media_query.dart'; import 'package:aves/widgets/common/extensions/media_query.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
@ -127,7 +127,7 @@ class TvTileGridBottomPaddingSliver extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: SizedBox( child: SizedBox(
height: device.isTelevision ? context.select<TileExtentController, double>((controller) => controller.spacing) : 0, height: settings.useTvLayout ? context.select<TileExtentController, double>((controller) => controller.spacing) : 0,
), ),
); );
} }

View file

@ -1,4 +1,3 @@
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/home_page.dart'; import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
@ -13,7 +12,7 @@ import 'package:provider/provider.dart';
// address `TV-DB` requirement from https://developer.android.com/docs/quality-guidelines/tv-app-quality // address `TV-DB` requirement from https://developer.android.com/docs/quality-guidelines/tv-app-quality
class TvNavigationPopHandler { class TvNavigationPopHandler {
static bool pop(BuildContext context) { static bool pop(BuildContext context) {
if (!device.isTelevision || _isHome(context)) { if (!settings.useTvLayout || _isHome(context)) {
return true; return true;
} }

View file

@ -1,5 +1,5 @@
import 'package:aves/model/device.dart';
import 'package:aves/model/selection.dart'; import 'package:aves/model/selection.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/section_keys.dart'; import 'package:aves/model/source/section_keys.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';
@ -33,7 +33,7 @@ class SectionHeader<T> extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget child = _buildContent(context); Widget child = _buildContent(context);
if (device.isTelevision) { if (settings.useTvLayout) {
final colors = Theme.of(context).colorScheme; final colors = Theme.of(context).colorScheme;
child = Material( child = Material(
type: MaterialType.transparency, type: MaterialType.transparency,

View file

@ -1,4 +1,3 @@
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
@ -34,7 +33,7 @@ class AvesAppBar extends StatelessWidget {
selector: (context, mq) => mq.padding.top, selector: (context, mq) => mq.padding.top,
builder: (context, mqPaddingTop, child) { builder: (context, mqPaddingTop, child) {
return SliverPersistentHeader( return SliverPersistentHeader(
floating: !device.isTelevision, floating: !settings.useTvLayout,
pinned: false, pinned: false,
delegate: _SliverAppBarDelegate( delegate: _SliverAppBarDelegate(
height: mqPaddingTop + appBarHeightForContentHeight(contentHeight), height: mqPaddingTop + appBarHeightForContentHeight(contentHeight),

View file

@ -1,4 +1,3 @@
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/enums/map_style.dart'; import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
@ -36,7 +35,7 @@ class MapButtonPanel extends StatelessWidget {
Widget? navigationButton; Widget? navigationButton;
switch (context.select<MapThemeData, MapNavigationButton>((v) => v.navigationButton)) { switch (context.select<MapThemeData, MapNavigationButton>((v) => v.navigationButton)) {
case MapNavigationButton.back: case MapNavigationButton.back:
if (!device.isTelevision) { if (!settings.useTvLayout) {
navigationButton = MapOverlayButton( navigationButton = MapOverlayButton(
icon: const BackButtonIcon(), icon: const BackButtonIcon(),
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),

View file

@ -1,4 +1,4 @@
import 'package:aves/model/device.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/search/route.dart'; import 'package:aves/widgets/common/search/route.dart';
@ -20,7 +20,7 @@ abstract class AvesSearchDelegate extends SearchDelegate {
@override @override
Widget? buildLeading(BuildContext context) { Widget? buildLeading(BuildContext context) {
if (device.isTelevision) { if (settings.useTvLayout) {
return const Icon(AIcons.search); return const Icon(AIcons.search);
} }

View file

@ -1,7 +1,6 @@
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/actions/chip_set_actions.dart'; import 'package:aves/model/actions/chip_set_actions.dart';
import 'package:aves/model/covers.dart'; import 'package:aves/model/covers.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
@ -83,7 +82,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
return isSelecting && selectedItemCount == itemCount; return isSelecting && selectedItemCount == itemCount;
// browsing // browsing
case ChipSetAction.search: case ChipSetAction.search:
return !device.isTelevision && appMode.canNavigate && !isSelecting; return !settings.useTvLayout && appMode.canNavigate && !isSelecting;
case ChipSetAction.toggleTitleSearch: case ChipSetAction.toggleTitleSearch:
return !isSelecting; return !isSelecting;
case ChipSetAction.createAlbum: case ChipSetAction.createAlbum:

View file

@ -2,10 +2,10 @@ import 'dart:async';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/actions/chip_set_actions.dart'; import 'package:aves/model/actions/chip_set_actions.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/query.dart'; import 'package:aves/model/query.dart';
import 'package:aves/model/selection.dart'; import 'package:aves/model/selection.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/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/action_controls/togglers/title_search.dart'; import 'package:aves/widgets/common/action_controls/togglers/title_search.dart';
@ -125,47 +125,52 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
final selection = context.watch<Selection<FilterGridItem<T>>>(); final selection = context.watch<Selection<FilterGridItem<T>>>();
final isSelecting = selection.isSelecting; final isSelecting = selection.isSelecting;
_isSelectingNotifier.value = isSelecting; _isSelectingNotifier.value = isSelecting;
return Selector<Query, bool>( return NotificationListener<ScrollNotification>(
selector: (context, query) => query.enabled, // cancel notification bubbling so that the draggable scroll bar
builder: (context, queryEnabled, child) { // does not misinterpret filter bar scrolling for collection scrolling
ActionsBuilder<T, CSAD> actionsBuilder = widget.actionsBuilder ?? _buildActions; onNotification: (notification) => true,
final isTelevision = device.isTelevision; child: Selector<Query, bool>(
final actions = actionsBuilder(context, appMode, selection, widget.actionDelegate); selector: (context, query) => query.enabled,
return AvesAppBar( builder: (context, queryEnabled, child) {
contentHeight: appBarContentHeight, ActionsBuilder<T, CSAD> actionsBuilder = widget.actionsBuilder ?? _buildActions;
leading: _buildAppBarLeading( final useTvLayout = settings.useTvLayout;
hasDrawer: appMode.canNavigate, final actions = actionsBuilder(context, appMode, selection, widget.actionDelegate);
isSelecting: isSelecting, return AvesAppBar(
), contentHeight: appBarContentHeight,
title: _buildAppBarTitle(isSelecting), leading: _buildAppBarLeading(
actions: isTelevision ? [] : actions, hasDrawer: appMode.canNavigate,
bottom: Column( isSelecting: isSelecting,
children: [ ),
if (isTelevision) title: _buildAppBarTitle(isSelecting),
SizedBox( actions: useTvLayout ? [] : actions,
height: CaptionedButton.getTelevisionButtonHeight(context), bottom: Column(
child: ListView( children: [
padding: const EdgeInsets.symmetric(horizontal: 8), if (useTvLayout)
scrollDirection: Axis.horizontal, SizedBox(
children: actions, height: CaptionedButton.getTelevisionButtonHeight(context),
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 8),
scrollDirection: Axis.horizontal,
children: actions,
),
), ),
), if (queryEnabled)
if (queryEnabled) FilterQueryBar<T>(
FilterQueryBar<T>( queryNotifier: context.select<Query, ValueNotifier<String>>((query) => query.queryNotifier),
queryNotifier: context.select<Query, ValueNotifier<String>>((query) => query.queryNotifier), focusNode: _queryBarFocusNode,
focusNode: _queryBarFocusNode, ),
), ],
], ),
), transitionKey: isSelecting,
transitionKey: isSelecting, );
); },
}, ),
); );
} }
double get appBarContentHeight { double get appBarContentHeight {
double height = kToolbarHeight; double height = kToolbarHeight;
if (device.isTelevision) { if (settings.useTvLayout) {
height += CaptionedButton.getTelevisionButtonHeight(context); height += CaptionedButton.getTelevisionButtonHeight(context);
} }
if (context.read<Query>().enabled) { if (context.read<Query>().enabled) {
@ -175,7 +180,7 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
} }
Widget? _buildAppBarLeading({required bool hasDrawer, required bool isSelecting}) { Widget? _buildAppBarLeading({required bool hasDrawer, required bool isSelecting}) {
if (device.isTelevision) return null; if (settings.useTvLayout) return null;
if (!hasDrawer) { if (!hasDrawer) {
return const CloseButton(); return const CloseButton();
@ -251,7 +256,7 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
selectedFilters: selectedFilters, selectedFilters: selectedFilters,
); );
return device.isTelevision return settings.useTvLayout
? _buildTelevisionActions( ? _buildTelevisionActions(
context: context, context: context,
selection: selection, selection: selection,

View file

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/highlight.dart'; import 'package:aves/model/highlight.dart';
import 'package:aves/model/query.dart'; import 'package:aves/model/query.dart';
@ -113,7 +112,7 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
), ),
); );
if (device.isTelevision) { if (settings.useTvLayout) {
return Scaffold( return Scaffold(
body: Row( body: Row(
children: [ children: [
@ -202,7 +201,7 @@ class _FilterGridState<T extends CollectionFilter> extends State<_FilterGrid<T>>
Widget build(BuildContext context) { Widget build(BuildContext context) {
_tileExtentController ??= TileExtentController( _tileExtentController ??= TileExtentController(
settingsRouteKey: widget.settingsRouteKey ?? context.currentRouteName!, settingsRouteKey: widget.settingsRouteKey ?? context.currentRouteName!,
columnCountDefault: device.isTelevision ? 4 : 3, columnCountDefault: settings.useTvLayout ? 4 : 3,
extentMin: 60, extentMin: 60,
extentMax: 300, extentMax: 300,
spacing: 8, spacing: 8,
@ -356,7 +355,7 @@ class _FilterGridContentState<T extends CollectionFilter> extends State<_FilterG
banner: _getFilterBanner(context, gridItem.filter), banner: _getFilterBanner(context, gridItem.filter),
heroType: widget.heroType, heroType: widget.heroType,
); );
if (!device.isTelevision) return tile; if (!settings.useTvLayout) return tile;
return Focus( return Focus(
onFocusChange: (focused) { onFocusChange: (focused) {

View file

@ -109,25 +109,28 @@ class _TvRailState extends State<TvRail> {
), ),
); );
return Column( return SafeArea(
children: [ child: Column(
const SizedBox(height: 8), crossAxisAlignment: CrossAxisAlignment.center,
header, children: [
const SizedBox(height: 4), const SizedBox(height: 8),
Expanded( header,
child: LayoutBuilder( const SizedBox(height: 4),
builder: (context, constraints) { Expanded(
return SingleChildScrollView( child: LayoutBuilder(
controller: _scrollController, builder: (context, constraints) {
child: ConstrainedBox( return SingleChildScrollView(
constraints: BoxConstraints(minHeight: constraints.maxHeight), controller: _scrollController,
child: IntrinsicHeight(child: rail), child: ConstrainedBox(
), constraints: BoxConstraints(minHeight: constraints.maxHeight),
); child: IntrinsicHeight(child: rail),
}, ),
);
},
),
), ),
), ],
], ),
); );
} }

View file

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
@ -29,7 +28,7 @@ class AccessibilitySection extends SettingsSection {
@override @override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [ FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
if (!device.isTelevision) SettingsTileAccessibilityShowPinchGestureAlternatives(), if (!settings.useTvLayout) SettingsTileAccessibilityShowPinchGestureAlternatives(),
SettingsTileAccessibilityAnimations(), SettingsTileAccessibilityAnimations(),
SettingsTileAccessibilityTimeToTakeAction(), SettingsTileAccessibilityTimeToTakeAction(),
]; ];

View file

@ -8,6 +8,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart'; import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart'; import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/settings_definition.dart'; import 'package:aves/widgets/settings/settings_definition.dart';
@ -29,11 +30,12 @@ class DisplaySection extends SettingsSection {
@override @override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [ FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
if (!device.isTelevision) SettingsTileDisplayThemeBrightness(), if (!settings.useTvLayout) SettingsTileDisplayThemeBrightness(),
SettingsTileDisplayThemeColorMode(), SettingsTileDisplayThemeColorMode(),
if (device.isDynamicColorAvailable) SettingsTileDisplayEnableDynamicColor(), if (device.isDynamicColorAvailable) SettingsTileDisplayEnableDynamicColor(),
SettingsTileDisplayEnableBlurEffect(), SettingsTileDisplayEnableBlurEffect(),
if (!device.isTelevision) SettingsTileDisplayDisplayRefreshRateMode(), if (!settings.useTvLayout) SettingsTileDisplayRefreshRateMode(),
if (!device.isTelevision) SettingsTileDisplayForceTvLayout(),
]; ];
} }
@ -88,7 +90,7 @@ class SettingsTileDisplayEnableBlurEffect extends SettingsTile {
); );
} }
class SettingsTileDisplayDisplayRefreshRateMode extends SettingsTile { class SettingsTileDisplayRefreshRateMode extends SettingsTile {
@override @override
String title(BuildContext context) => context.l10n.settingsDisplayRefreshRateModeTile; String title(BuildContext context) => context.l10n.settingsDisplayRefreshRateModeTile;
@ -102,3 +104,41 @@ class SettingsTileDisplayDisplayRefreshRateMode extends SettingsTile {
dialogTitle: context.l10n.settingsDisplayRefreshRateModeDialogTitle, dialogTitle: context.l10n.settingsDisplayRefreshRateModeDialogTitle,
); );
} }
class SettingsTileDisplayForceTvLayout extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsDisplayUseTvInterface;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.forceTvLayout,
onChanged: (v) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) {
final l10n = context.l10n;
return AvesDialog(
content: Text([
l10n.settingsModificationWarningDialogMessage,
l10n.genericDangerWarningDialogMessage,
].join('\n\n')),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text(l10n.applyButtonLabel),
),
],
);
},
);
if (confirmed == null || !confirmed) return;
settings.forceTvLayout = v;
},
title: title(context),
);
}

View file

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/home_page.dart'; import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/enums/screen_on.dart'; import 'package:aves/model/settings/enums/screen_on.dart';
@ -32,11 +31,11 @@ class NavigationSection extends SettingsSection {
@override @override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [ FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
SettingsTileNavigationHomePage(), SettingsTileNavigationHomePage(),
if (!device.isTelevision) SettingsTileNavigationKeepScreenOn(), if (!settings.useTvLayout) SettingsTileNavigationKeepScreenOn(),
if (!device.isTelevision) SettingsTileShowBottomNavigationBar(), if (!settings.useTvLayout) SettingsTileShowBottomNavigationBar(),
if (!device.isTelevision) SettingsTileNavigationDoubleBackExit(), if (!settings.useTvLayout) SettingsTileNavigationDoubleBackExit(),
SettingsTileNavigationDrawer(), SettingsTileNavigationDrawer(),
if (!device.isTelevision) SettingsTileNavigationConfirmationDialog(), if (!settings.useTvLayout) SettingsTileNavigationConfirmationDialog(),
]; ];
} }

View file

@ -34,11 +34,11 @@ class PrivacySection extends SettingsSection {
return [ return [
SettingsTilePrivacyAllowInstalledAppAccess(), SettingsTilePrivacyAllowInstalledAppAccess(),
if (canEnableErrorReporting) SettingsTilePrivacyAllowErrorReporting(), if (canEnableErrorReporting) SettingsTilePrivacyAllowErrorReporting(),
if (!device.isTelevision && device.canRequestManageMedia) SettingsTilePrivacyManageMedia(), if (!settings.useTvLayout && device.canRequestManageMedia) SettingsTilePrivacyManageMedia(),
SettingsTilePrivacySaveSearchHistory(), SettingsTilePrivacySaveSearchHistory(),
if (!device.isTelevision) SettingsTilePrivacyEnableBin(), if (!settings.useTvLayout) SettingsTilePrivacyEnableBin(),
SettingsTilePrivacyHiddenItems(), SettingsTilePrivacyHiddenItems(),
if (!device.isTelevision && device.canGrantDirectoryAccess) SettingsTilePrivacyStorageAccess(), if (!settings.useTvLayout && device.canGrantDirectoryAccess) SettingsTilePrivacyStorageAccess(),
]; ];
} }
} }

View file

@ -3,7 +3,7 @@ import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:aves/model/actions/settings_actions.dart'; import 'package:aves/model/actions/settings_actions.dart';
import 'package:aves/model/device.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/ref/mime_types.dart'; import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
@ -73,7 +73,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appBarTitle = Text(context.l10n.settingsPageTitle); final appBarTitle = Text(context.l10n.settingsPageTitle);
if (device.isTelevision) { if (settings.useTvLayout) {
return Scaffold( return Scaffold(
body: AvesPopScope( body: AvesPopScope(
handlers: const [TvNavigationPopHandler.pop], handlers: const [TvNavigationPopHandler.pop],

View file

@ -1,4 +1,4 @@
import 'package:aves/model/device.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
@ -25,7 +25,7 @@ class ThumbnailsSection extends SettingsSection {
@override @override
List<SettingsTile> tiles(BuildContext context) => [ List<SettingsTile> tiles(BuildContext context) => [
if (!device.isTelevision) SettingsTileCollectionQuickActions(), if (!settings.useTvLayout) SettingsTileCollectionQuickActions(),
SettingsTileThumbnailOverlay(), SettingsTileThumbnailOverlay(),
]; ];
} }

View file

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/model/device.dart';
import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/video_auto_play_mode.dart'; import 'package:aves/model/settings/enums/video_auto_play_mode.dart';
@ -43,7 +42,7 @@ class VideoSection extends SettingsSection {
SettingsTileVideoEnableHardwareAcceleration(), SettingsTileVideoEnableHardwareAcceleration(),
SettingsTileVideoEnableAutoPlay(), SettingsTileVideoEnableAutoPlay(),
SettingsTileVideoLoopMode(), SettingsTileVideoLoopMode(),
if (!device.isTelevision) SettingsTileVideoControls(), if (!settings.useTvLayout) SettingsTileVideoControls(),
SettingsTileVideoSubtitleTheme(), SettingsTileVideoSubtitleTheme(),
]; ];
} }

View file

@ -1,4 +1,3 @@
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/settings.dart'; 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:aves/widgets/settings/common/tiles.dart'; import 'package:aves/widgets/settings/common/tiles.dart';
@ -20,7 +19,7 @@ class ViewerOverlayPage extends StatelessWidget {
body: SafeArea( body: SafeArea(
child: ListView( child: ListView(
children: [ children: [
if (!device.isTelevision) if (!settings.useTvLayout)
SettingsSwitchListTile( SettingsSwitchListTile(
selector: (context, s) => s.showOverlayOnOpening, selector: (context, s) => s.showOverlayOnOpening,
onChanged: (v) => settings.showOverlayOnOpening = v, onChanged: (v) => settings.showOverlayOnOpening = v,
@ -68,13 +67,13 @@ class ViewerOverlayPage extends StatelessWidget {
); );
}, },
), ),
if (!device.isTelevision) if (!settings.useTvLayout)
SettingsSwitchListTile( SettingsSwitchListTile(
selector: (context, s) => s.showOverlayMinimap, selector: (context, s) => s.showOverlayMinimap,
onChanged: (v) => settings.showOverlayMinimap = v, onChanged: (v) => settings.showOverlayMinimap = v,
title: context.l10n.settingsViewerShowMinimap, title: context.l10n.settingsViewerShowMinimap,
), ),
if (!device.isTelevision) if (!settings.useTvLayout)
SettingsSwitchListTile( SettingsSwitchListTile(
selector: (context, s) => s.showOverlayThumbnailPreview, selector: (context, s) => s.showOverlayThumbnailPreview,
onChanged: (v) => settings.showOverlayThumbnailPreview = v, onChanged: (v) => settings.showOverlayThumbnailPreview = v,

View file

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
@ -34,12 +33,12 @@ class ViewerSection extends SettingsSection {
FutureOr<List<SettingsTile>> tiles(BuildContext context) async { FutureOr<List<SettingsTile>> tiles(BuildContext context) async {
final isCutoutAware = await windowService.isCutoutAware(); final isCutoutAware = await windowService.isCutoutAware();
return [ return [
if (!device.isTelevision) SettingsTileViewerQuickActions(), if (!settings.useTvLayout) SettingsTileViewerQuickActions(),
SettingsTileViewerOverlay(), SettingsTileViewerOverlay(),
SettingsTileViewerSlideshow(), SettingsTileViewerSlideshow(),
if (!device.isTelevision) SettingsTileViewerGestureSideTapNext(), if (!settings.useTvLayout) SettingsTileViewerGestureSideTapNext(),
if (!device.isTelevision && isCutoutAware) SettingsTileViewerUseCutout(), if (!settings.useTvLayout && isCutoutAware) SettingsTileViewerUseCutout(),
if (!device.isTelevision) SettingsTileViewerMaxBrightness(), if (!settings.useTvLayout) SettingsTileViewerMaxBrightness(),
SettingsTileViewerMotionPhotoAutoPlay(), SettingsTileViewerMotionPhotoAutoPlay(),
SettingsTileViewerImageBackground(), SettingsTileViewerImageBackground(),
]; ];

View file

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
@ -226,7 +225,7 @@ class _StatsPageState extends State<StatsPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
automaticallyImplyLeading: !device.isTelevision, automaticallyImplyLeading: !settings.useTvLayout,
title: Text(l10n.statsPageTitle), title: Text(l10n.statsPageTitle),
), ),
body: GestureAreaProtectorStack( body: GestureAreaProtectorStack(
@ -356,7 +355,7 @@ class StatsTopPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
automaticallyImplyLeading: !device.isTelevision, automaticallyImplyLeading: !settings.useTvLayout,
title: Text(title), title: Text(title),
), ),
body: GestureAreaProtectorStack( body: GestureAreaProtectorStack(

View file

@ -93,7 +93,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
case EntryAction.videoCaptureFrame: case EntryAction.videoCaptureFrame:
return canWrite && targetEntry.isVideo; return canWrite && targetEntry.isVideo;
case EntryAction.videoToggleMute: case EntryAction.videoToggleMute:
return !device.isTelevision && targetEntry.isVideo; return !settings.useTvLayout && targetEntry.isVideo;
case EntryAction.videoSelectStreams: case EntryAction.videoSelectStreams:
case EntryAction.videoSetSpeed: case EntryAction.videoSetSpeed:
case EntryAction.videoSettings: case EntryAction.videoSettings:
@ -103,13 +103,13 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
case EntryAction.openVideo: case EntryAction.openVideo:
return targetEntry.isVideo; return targetEntry.isVideo;
case EntryAction.rotateScreen: case EntryAction.rotateScreen:
return !device.isTelevision && settings.isRotationLocked; return !settings.useTvLayout && settings.isRotationLocked;
case EntryAction.addShortcut: case EntryAction.addShortcut:
return device.canPinShortcut; return device.canPinShortcut;
case EntryAction.edit: case EntryAction.edit:
return canWrite; return canWrite;
case EntryAction.copyToClipboard: case EntryAction.copyToClipboard:
return !device.isTelevision; return !settings.useTvLayout;
case EntryAction.info: case EntryAction.info:
case EntryAction.open: case EntryAction.open:
case EntryAction.setAs: case EntryAction.setAs:

View file

@ -4,7 +4,6 @@ import 'dart:ui';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
@ -181,12 +180,12 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
} }
Widget _buildImagePage() { Widget _buildImagePage() {
final isTelevision = device.isTelevision; final useTvLayout = settings.useTvLayout;
Widget? child; Widget? child;
Map<ShortcutActivator, Intent>? shortcuts = { Map<ShortcutActivator, Intent>? shortcuts = {
const SingleActivator(LogicalKeyboardKey.arrowUp): isTelevision ? const TvShowLessInfoIntent() : const _LeaveIntent(), const SingleActivator(LogicalKeyboardKey.arrowUp): useTvLayout ? const TvShowLessInfoIntent() : const _LeaveIntent(),
const SingleActivator(LogicalKeyboardKey.arrowDown): isTelevision ? const _TvShowMoreInfoIntent() : const _ShowInfoIntent(), const SingleActivator(LogicalKeyboardKey.arrowDown): useTvLayout ? const _TvShowMoreInfoIntent() : const _ShowInfoIntent(),
const SingleActivator(LogicalKeyboardKey.mediaPause): const _PlayPauseIntent.pause(), const SingleActivator(LogicalKeyboardKey.mediaPause): const _PlayPauseIntent.pause(),
const SingleActivator(LogicalKeyboardKey.mediaPlay): const _PlayPauseIntent.play(), const SingleActivator(LogicalKeyboardKey.mediaPlay): const _PlayPauseIntent.play(),
const SingleActivator(LogicalKeyboardKey.mediaPlayPause): const _PlayPauseIntent.toggle(), const SingleActivator(LogicalKeyboardKey.mediaPlayPause): const _PlayPauseIntent.toggle(),
@ -211,7 +210,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
); );
} }
if (child != null) { if (child != null) {
if (device.isTelevision) { if (settings.useTvLayout) {
child = ValueListenableBuilder<bool>( child = ValueListenableBuilder<bool>(
valueListenable: _isImageFocusedNotifier, valueListenable: _isImageFocusedNotifier,
builder: (context, isImageFocused, child) { builder: (context, isImageFocused, child) {
@ -238,7 +237,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
_TvShowMoreInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowMoreInfoNotification().dispatch(context)), _TvShowMoreInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowMoreInfoNotification().dispatch(context)),
_PlayPauseIntent: CallbackAction<_PlayPauseIntent>(onInvoke: (intent) => _onPlayPauseIntent(intent, entry)), _PlayPauseIntent: CallbackAction<_PlayPauseIntent>(onInvoke: (intent) => _onPlayPauseIntent(intent, entry)),
ActivateIntent: CallbackAction<Intent>(onInvoke: (intent) { ActivateIntent: CallbackAction<Intent>(onInvoke: (intent) {
if (isTelevision) { if (useTvLayout) {
final _entry = entry; final _entry = entry;
if (_entry != null && _entry.isVideo) { if (_entry != null && _entry.isVideo) {
// address `TV-PC` requirement from https://developer.android.com/docs/quality-guidelines/tv-app-quality // address `TV-PC` requirement from https://developer.android.com/docs/quality-guidelines/tv-app-quality

View file

@ -689,7 +689,9 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
await AvesApp.showSystemUI(); await AvesApp.showSystemUI();
AvesApp.setSystemUIStyle(context); AvesApp.setSystemUIStyle(context);
await windowService.requestOrientation(); if (!settings.useTvLayout) {
await windowService.requestOrientation();
}
} }
// overlay // overlay

View file

@ -1,6 +1,6 @@
import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.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';
@ -37,7 +37,7 @@ class InfoAppBar extends StatelessWidget {
final formatSpecificActions = EntryActions.formatSpecificMetadataActions.where((v) => actionDelegate.isVisible(entry, v)); final formatSpecificActions = EntryActions.formatSpecificMetadataActions.where((v) => actionDelegate.isVisible(entry, v));
return SliverAppBar( return SliverAppBar(
leading: device.isTelevision leading: settings.useTvLayout
? null ? null
: IconButton( : IconButton(
// key is expected by test driver // key is expected by test driver

View file

@ -1,7 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
@ -186,7 +185,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
final viewInsetsPadding = (widget.viewInsets ?? EdgeInsets.zero) + (widget.viewPadding ?? EdgeInsets.zero); final viewInsetsPadding = (widget.viewInsets ?? EdgeInsets.zero) + (widget.viewPadding ?? EdgeInsets.zero);
final viewerButtonRow = FocusableActionDetector( final viewerButtonRow = FocusableActionDetector(
focusNode: _buttonRowFocusScopeNode, focusNode: _buttonRowFocusScopeNode,
shortcuts: device.isTelevision ? const {SingleActivator(LogicalKeyboardKey.arrowUp): TvShowLessInfoIntent()} : null, shortcuts: settings.useTvLayout ? const {SingleActivator(LogicalKeyboardKey.arrowUp): TvShowLessInfoIntent()} : null,
actions: {TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context))}, actions: {TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context))},
child: SafeArea( child: SafeArea(
top: false, top: false,

View file

@ -1,5 +1,5 @@
import 'package:aves/model/actions/slideshow_actions.dart'; import 'package:aves/model/actions/slideshow_actions.dart';
import 'package:aves/model/device.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/entry_vertical_pager.dart'; import 'package:aves/widgets/viewer/entry_vertical_pager.dart';
@ -70,9 +70,9 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FocusableActionDetector( return FocusableActionDetector(
focusNode: _buttonRowFocusScopeNode, focusNode: _buttonRowFocusScopeNode,
shortcuts: device.isTelevision ? const {SingleActivator(LogicalKeyboardKey.arrowUp): TvShowLessInfoIntent()} : null, shortcuts: settings.useTvLayout ? const {SingleActivator(LogicalKeyboardKey.arrowUp): TvShowLessInfoIntent()} : null,
actions: {TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context))}, actions: {TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context))},
child: device.isTelevision child: settings.useTvLayout
? Row( ? Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View file

@ -1,5 +1,4 @@
import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
@ -51,7 +50,7 @@ class ViewerButtons extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection); final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection);
if (device.isTelevision) { if (settings.useTvLayout) {
return _TvButtonRowContent( return _TvButtonRowContent(
actionDelegate: actionDelegate, actionDelegate: actionDelegate,
scale: scale, scale: scale,

View file

@ -1,9 +1,9 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.dart'; import 'package:aves/model/entry_images.dart';
import 'package:aves/model/panorama.dart'; import 'package:aves/model/panorama.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/basic/insets.dart';
@ -110,7 +110,7 @@ class _PanoramaPageState extends State<PanoramaPage> {
} }
Widget _buildOverlay(BuildContext context) { Widget _buildOverlay(BuildContext context) {
if (device.isTelevision) return const SizedBox(); if (settings.useTvLayout) return const SizedBox();
return TooltipTheme( return TooltipTheme(
data: TooltipTheme.of(context).copyWith( data: TooltipTheme.of(context).copyWith(

View file

@ -1,5 +1,4 @@
import 'package:aves/app_flavor.dart'; import 'package:aves/app_flavor.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
@ -71,7 +70,7 @@ class _WelcomePageState extends State<WelcomePage> {
child: child, child: child,
), ),
), ),
children: device.isTelevision children: settings.useTvLayout
? [ ? [
..._buildHeader(context, isPortrait: isPortrait), ..._buildHeader(context, isPortrait: isPortrait),
Padding( Padding(

View file

@ -387,6 +387,7 @@
"settingsSystemDefault", "settingsSystemDefault",
"settingsDefault", "settingsDefault",
"settingsDisabled", "settingsDisabled",
"settingsModificationWarningDialogMessage",
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty", "settingsSearchEmpty",
"settingsActionExport", "settingsActionExport",
@ -526,6 +527,7 @@
"settingsThemeEnableDynamicColor", "settingsThemeEnableDynamicColor",
"settingsDisplayRefreshRateModeTile", "settingsDisplayRefreshRateModeTile",
"settingsDisplayRefreshRateModeDialogTitle", "settingsDisplayRefreshRateModeDialogTitle",
"settingsDisplayUseTvInterface",
"settingsLanguageSectionTitle", "settingsLanguageSectionTitle",
"settingsLanguageTile", "settingsLanguageTile",
"settingsLanguagePageTitle", "settingsLanguagePageTitle",
@ -592,21 +594,29 @@
], ],
"cs": [ "cs": [
"settingsViewerShowDescription" "settingsModificationWarningDialogMessage",
"settingsViewerShowDescription",
"settingsDisplayUseTvInterface"
], ],
"de": [ "de": [
"columnCount", "columnCount",
"settingsModificationWarningDialogMessage",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsAccessibilityShowPinchGestureAlternatives" "settingsAccessibilityShowPinchGestureAlternatives",
"settingsDisplayUseTvInterface"
], ],
"el": [ "el": [
"settingsViewerShowDescription" "settingsModificationWarningDialogMessage",
"settingsViewerShowDescription",
"settingsDisplayUseTvInterface"
], ],
"es": [ "es": [
"settingsViewerShowDescription" "settingsModificationWarningDialogMessage",
"settingsViewerShowDescription",
"settingsDisplayUseTvInterface"
], ],
"fa": [ "fa": [
@ -867,6 +877,7 @@
"settingsSystemDefault", "settingsSystemDefault",
"settingsDefault", "settingsDefault",
"settingsDisabled", "settingsDisabled",
"settingsModificationWarningDialogMessage",
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty", "settingsSearchEmpty",
"settingsActionImport", "settingsActionImport",
@ -1002,6 +1013,7 @@
"settingsThemeEnableDynamicColor", "settingsThemeEnableDynamicColor",
"settingsDisplayRefreshRateModeTile", "settingsDisplayRefreshRateModeTile",
"settingsDisplayRefreshRateModeDialogTitle", "settingsDisplayRefreshRateModeDialogTitle",
"settingsDisplayUseTvInterface",
"settingsLanguageSectionTitle", "settingsLanguageSectionTitle",
"settingsLanguageTile", "settingsLanguageTile",
"settingsLanguagePageTitle", "settingsLanguagePageTitle",
@ -1074,6 +1086,11 @@
"filePickerUseThisFolder" "filePickerUseThisFolder"
], ],
"fr": [
"settingsModificationWarningDialogMessage",
"settingsDisplayUseTvInterface"
],
"gl": [ "gl": [
"columnCount", "columnCount",
"entryActionShareImageOnly", "entryActionShareImageOnly",
@ -1329,6 +1346,7 @@
"settingsSystemDefault", "settingsSystemDefault",
"settingsDefault", "settingsDefault",
"settingsDisabled", "settingsDisabled",
"settingsModificationWarningDialogMessage",
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty", "settingsSearchEmpty",
"settingsActionExport", "settingsActionExport",
@ -1468,6 +1486,7 @@
"settingsThemeEnableDynamicColor", "settingsThemeEnableDynamicColor",
"settingsDisplayRefreshRateModeTile", "settingsDisplayRefreshRateModeTile",
"settingsDisplayRefreshRateModeDialogTitle", "settingsDisplayRefreshRateModeDialogTitle",
"settingsDisplayUseTvInterface",
"settingsLanguageSectionTitle", "settingsLanguageSectionTitle",
"settingsLanguageTile", "settingsLanguageTile",
"settingsLanguagePageTitle", "settingsLanguagePageTitle",
@ -1543,11 +1562,15 @@
], ],
"id": [ "id": [
"settingsViewerShowDescription" "settingsModificationWarningDialogMessage",
"settingsViewerShowDescription",
"settingsDisplayUseTvInterface"
], ],
"it": [ "it": [
"settingsViewerShowDescription" "settingsModificationWarningDialogMessage",
"settingsViewerShowDescription",
"settingsDisplayUseTvInterface"
], ],
"ja": [ "ja": [
@ -1559,16 +1582,25 @@
"keepScreenOnVideoPlayback", "keepScreenOnVideoPlayback",
"subtitlePositionTop", "subtitlePositionTop",
"subtitlePositionBottom", "subtitlePositionBottom",
"settingsModificationWarningDialogMessage",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsAccessibilityShowPinchGestureAlternatives", "settingsAccessibilityShowPinchGestureAlternatives",
"settingsDisplayUseTvInterface",
"settingsWidgetDisplayedItem" "settingsWidgetDisplayedItem"
], ],
"ko": [
"settingsModificationWarningDialogMessage",
"settingsDisplayUseTvInterface"
],
"lt": [ "lt": [
"columnCount", "columnCount",
"keepScreenOnVideoPlayback", "keepScreenOnVideoPlayback",
"settingsModificationWarningDialogMessage",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsAccessibilityShowPinchGestureAlternatives" "settingsAccessibilityShowPinchGestureAlternatives",
"settingsDisplayUseTvInterface"
], ],
"nb": [ "nb": [
@ -1577,8 +1609,10 @@
"entryActionShareVideoOnly", "entryActionShareVideoOnly",
"entryInfoActionRemoveLocation", "entryInfoActionRemoveLocation",
"keepScreenOnVideoPlayback", "keepScreenOnVideoPlayback",
"settingsModificationWarningDialogMessage",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsAccessibilityShowPinchGestureAlternatives" "settingsAccessibilityShowPinchGestureAlternatives",
"settingsDisplayUseTvInterface"
], ],
"nl": [ "nl": [
@ -1595,11 +1629,13 @@
"subtitlePositionBottom", "subtitlePositionBottom",
"widgetDisplayedItemRandom", "widgetDisplayedItemRandom",
"widgetDisplayedItemMostRecent", "widgetDisplayedItemMostRecent",
"settingsModificationWarningDialogMessage",
"settingsViewerShowRatingTags", "settingsViewerShowRatingTags",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsSubtitleThemeTextPositionTile", "settingsSubtitleThemeTextPositionTile",
"settingsSubtitleThemeTextPositionDialogTitle", "settingsSubtitleThemeTextPositionDialogTitle",
"settingsAccessibilityShowPinchGestureAlternatives", "settingsAccessibilityShowPinchGestureAlternatives",
"settingsDisplayUseTvInterface",
"settingsWidgetDisplayedItem" "settingsWidgetDisplayedItem"
], ],
@ -1841,6 +1877,7 @@
"settingsSystemDefault", "settingsSystemDefault",
"settingsDefault", "settingsDefault",
"settingsDisabled", "settingsDisabled",
"settingsModificationWarningDialogMessage",
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty", "settingsSearchEmpty",
"settingsActionExport", "settingsActionExport",
@ -1980,6 +2017,7 @@
"settingsThemeEnableDynamicColor", "settingsThemeEnableDynamicColor",
"settingsDisplayRefreshRateModeTile", "settingsDisplayRefreshRateModeTile",
"settingsDisplayRefreshRateModeDialogTitle", "settingsDisplayRefreshRateModeDialogTitle",
"settingsDisplayUseTvInterface",
"settingsLanguageSectionTitle", "settingsLanguageSectionTitle",
"settingsLanguageTile", "settingsLanguageTile",
"settingsLanguagePageTitle", "settingsLanguagePageTitle",
@ -2349,6 +2387,7 @@
"settingsSystemDefault", "settingsSystemDefault",
"settingsDefault", "settingsDefault",
"settingsDisabled", "settingsDisabled",
"settingsModificationWarningDialogMessage",
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty", "settingsSearchEmpty",
"settingsActionExport", "settingsActionExport",
@ -2488,6 +2527,7 @@
"settingsThemeEnableDynamicColor", "settingsThemeEnableDynamicColor",
"settingsDisplayRefreshRateModeTile", "settingsDisplayRefreshRateModeTile",
"settingsDisplayRefreshRateModeDialogTitle", "settingsDisplayRefreshRateModeDialogTitle",
"settingsDisplayUseTvInterface",
"settingsLanguageSectionTitle", "settingsLanguageSectionTitle",
"settingsLanguageTile", "settingsLanguageTile",
"settingsLanguagePageTitle", "settingsLanguagePageTitle",
@ -2576,16 +2616,25 @@
"subtitlePositionBottom", "subtitlePositionBottom",
"widgetDisplayedItemRandom", "widgetDisplayedItemRandom",
"widgetDisplayedItemMostRecent", "widgetDisplayedItemMostRecent",
"settingsModificationWarningDialogMessage",
"settingsViewerShowRatingTags", "settingsViewerShowRatingTags",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsSubtitleThemeTextPositionTile", "settingsSubtitleThemeTextPositionTile",
"settingsSubtitleThemeTextPositionDialogTitle", "settingsSubtitleThemeTextPositionDialogTitle",
"settingsAccessibilityShowPinchGestureAlternatives", "settingsAccessibilityShowPinchGestureAlternatives",
"settingsDisplayUseTvInterface",
"settingsWidgetDisplayedItem" "settingsWidgetDisplayedItem"
], ],
"ro": [ "ro": [
"settingsViewerShowDescription" "settingsModificationWarningDialogMessage",
"settingsViewerShowDescription",
"settingsDisplayUseTvInterface"
],
"ru": [
"settingsModificationWarningDialogMessage",
"settingsDisplayUseTvInterface"
], ],
"th": [ "th": [
@ -2718,6 +2767,7 @@
"settingsSystemDefault", "settingsSystemDefault",
"settingsDefault", "settingsDefault",
"settingsDisabled", "settingsDisabled",
"settingsModificationWarningDialogMessage",
"settingsSearchFieldLabel", "settingsSearchFieldLabel",
"settingsSearchEmpty", "settingsSearchEmpty",
"settingsActionExport", "settingsActionExport",
@ -2857,6 +2907,7 @@
"settingsThemeEnableDynamicColor", "settingsThemeEnableDynamicColor",
"settingsDisplayRefreshRateModeTile", "settingsDisplayRefreshRateModeTile",
"settingsDisplayRefreshRateModeDialogTitle", "settingsDisplayRefreshRateModeDialogTitle",
"settingsDisplayUseTvInterface",
"settingsLanguageSectionTitle", "settingsLanguageSectionTitle",
"settingsLanguageTile", "settingsLanguageTile",
"settingsLanguagePageTitle", "settingsLanguagePageTitle",
@ -2932,21 +2983,29 @@
], ],
"tr": [ "tr": [
"settingsViewerShowDescription" "settingsModificationWarningDialogMessage",
"settingsViewerShowDescription",
"settingsDisplayUseTvInterface"
], ],
"uk": [ "uk": [
"settingsViewerShowDescription" "settingsModificationWarningDialogMessage",
"settingsViewerShowDescription",
"settingsDisplayUseTvInterface"
], ],
"zh": [ "zh": [
"settingsModificationWarningDialogMessage",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsAccessibilityShowPinchGestureAlternatives" "settingsAccessibilityShowPinchGestureAlternatives",
"settingsDisplayUseTvInterface"
], ],
"zh_Hant": [ "zh_Hant": [
"columnCount", "columnCount",
"settingsModificationWarningDialogMessage",
"settingsViewerShowDescription", "settingsViewerShowDescription",
"settingsAccessibilityShowPinchGestureAlternatives" "settingsAccessibilityShowPinchGestureAlternatives",
"settingsDisplayUseTvInterface"
] ]
} }