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
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.media.session.PlaybackState
import android.net.Uri
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
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.safeSuspend
import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.getParcelableExtraCompat
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@ -74,7 +79,9 @@ class MediaSessionHandler(private val context: Context) : MethodCallHandler {
var session = sessions[uri]
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
val metadata = MediaMetadataCompat.Builder()
@ -86,6 +93,12 @@ class MediaSessionHandler(private val context: Context) : MethodCallHandler {
session.setMetadata(metadata)
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() {
super.onPlay()
Log.d(LOG_TAG, "TLAD onPlay uri=$uri")

View file

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

View file

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

View file

@ -70,6 +70,7 @@ class Settings extends ChangeNotifier {
static const themeColorModeKey = 'theme_color_mode';
static const enableDynamicColorKey = 'dynamic_color';
static const enableBlurEffectKey = 'enable_overlay_blur_effect';
static const forceTvLayoutKey = 'force_tv_layout';
// navigation
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;
mustBackTwiceToExit = false;
// 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);
bool get forceTvLayout => getBool(forceTvLayoutKey) ?? SettingsDefaults.forceTvLayout;
set forceTvLayout(bool newValue) => setAndNotify(forceTvLayoutKey, newValue);
bool get useTvLayout => device.isTelevision || forceTvLayout;
// navigation
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/bug_report.dart';
import 'package:aves/widgets/about/credits.dart';
@ -28,7 +28,7 @@ class AboutPage extends StatelessWidget {
delegate: SliverChildListDelegate(
[
const AppReference(),
if (!device.isTelevision) ...[
if (!settings.useTvLayout) ...[
const Divider(),
const BugReport(),
],
@ -46,7 +46,8 @@ class AboutPage extends StatelessWidget {
],
);
if (device.isTelevision) {
if (settings.useTvLayout) {
final isRtl = context.isRtl;
return Scaffold(
body: AvesPopScope(
handlers: const [TvNavigationPopHandler.pop],
@ -55,7 +56,13 @@ class AboutPage extends StatelessWidget {
TvRail(
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();
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();
_tvMediaQueryModifierNotifier.value = (mq) => mq.copyWith(
textScaleFactor: 1.1,
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();
FijkLog.setLevel(FijkLogLevel.Warn);
@ -448,15 +451,28 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
void applyKeepScreenOn() => settings.keepScreenOn.apply();
void applyIsRotationLocked() {
if (!settings.isRotationLocked) {
if (!settings.isRotationLocked && !settings.useTvLayout) {
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.displayRefreshRateModeKey).listen((_) => applyDisplayRefreshRateMode());
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.forceTvLayoutKey).listen((_) => applyForceTvLayout());
applyDisplayRefreshRateMode();
applyKeepScreenOn();

View file

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

View file

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

View file

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

View file

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

View file

@ -82,20 +82,15 @@ class _FilterBarState extends State<FilterBar> {
// chip border clipping when the floating app bar is fading
color: Colors.transparent,
height: FilterBar.preferredHeight,
child: NotificationListener<ScrollNotification>(
// cancel notification bubbling so that the draggable scroll bar
// does not misinterpret filter bar scrolling for collection scrolling
onNotification: (notification) => true,
child: AnimatedList(
key: _animatedListKey,
initialItemCount: filters.length,
scrollDirection: Axis.horizontal,
padding: FilterBar.rowPadding,
itemBuilder: (context, index, animation) {
if (index >= filters.length) return const SizedBox();
return _buildChip(filters.toList()[index]);
},
),
child: AnimatedList(
key: _animatedListKey,
initialItemCount: filters.length,
scrollDirection: Axis.horizontal,
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/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/borders.dart';
@ -72,13 +72,13 @@ class _ColorPickerDialogState extends State<ColorPickerDialog> {
@override
Widget build(BuildContext context) {
final isTelevision = device.isTelevision;
final useTvLayout = settings.useTvLayout;
return AvesDialog(
scrollableContent: [
ColorPicker(
color: color,
onColorChanged: (v) => color = v,
pickersEnabled: isTelevision
pickersEnabled: useTvLayout
? const {
ColorPickerType.primary: true,
ColorPickerType.accent: false,
@ -90,7 +90,7 @@ class _ColorPickerDialogState extends State<ColorPickerDialog> {
},
hasBorder: true,
borderRadius: 20,
subheading: isTelevision ? const SizedBox(height: 16) : null,
subheading: useTvLayout ? const SizedBox(height: 16) : null,
)
],
actions: [

View file

@ -1,6 +1,6 @@
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/common/extensions/media_query.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
@ -127,7 +127,7 @@ class TvTileGridBottomPaddingSliver extends StatelessWidget {
Widget build(BuildContext context) {
return SliverToBoxAdapter(
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/home_page.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
class TvNavigationPopHandler {
static bool pop(BuildContext context) {
if (!device.isTelevision || _isHome(context)) {
if (!settings.useTvLayout || _isHome(context)) {
return true;
}

View file

@ -1,5 +1,5 @@
import 'package:aves/model/device.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/theme/durations.dart';
import 'package:aves/theme/icons.dart';
@ -33,7 +33,7 @@ class SectionHeader<T> extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget child = _buildContent(context);
if (device.isTelevision) {
if (settings.useTvLayout) {
final colors = Theme.of(context).colorScheme;
child = Material(
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/theme/colors.dart';
import 'package:aves/theme/durations.dart';
@ -34,7 +33,7 @@ class AvesAppBar extends StatelessWidget {
selector: (context, mq) => mq.padding.top,
builder: (context, mqPaddingTop, child) {
return SliverPersistentHeader(
floating: !device.isTelevision,
floating: !settings.useTvLayout,
pinned: false,
delegate: _SliverAppBarDelegate(
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/settings.dart';
import 'package:aves/services/common/services.dart';
@ -36,7 +35,7 @@ class MapButtonPanel extends StatelessWidget {
Widget? navigationButton;
switch (context.select<MapThemeData, MapNavigationButton>((v) => v.navigationButton)) {
case MapNavigationButton.back:
if (!device.isTelevision) {
if (!settings.useTvLayout) {
navigationButton = MapOverlayButton(
icon: const BackButtonIcon(),
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/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/search/route.dart';
@ -20,7 +20,7 @@ abstract class AvesSearchDelegate extends SearchDelegate {
@override
Widget? buildLeading(BuildContext context) {
if (device.isTelevision) {
if (settings.useTvLayout) {
return const Icon(AIcons.search);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,6 +8,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.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/tiles.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
@ -29,11 +30,12 @@ class DisplaySection extends SettingsSection {
@override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
if (!device.isTelevision) SettingsTileDisplayThemeBrightness(),
if (!settings.useTvLayout) SettingsTileDisplayThemeBrightness(),
SettingsTileDisplayThemeColorMode(),
if (device.isDynamicColorAvailable) SettingsTileDisplayEnableDynamicColor(),
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
String title(BuildContext context) => context.l10n.settingsDisplayRefreshRateModeTile;
@ -102,3 +104,41 @@ class SettingsTileDisplayDisplayRefreshRateMode extends SettingsTile {
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 'package:aves/model/device.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/enums/screen_on.dart';
@ -32,11 +31,11 @@ class NavigationSection extends SettingsSection {
@override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
SettingsTileNavigationHomePage(),
if (!device.isTelevision) SettingsTileNavigationKeepScreenOn(),
if (!device.isTelevision) SettingsTileShowBottomNavigationBar(),
if (!device.isTelevision) SettingsTileNavigationDoubleBackExit(),
if (!settings.useTvLayout) SettingsTileNavigationKeepScreenOn(),
if (!settings.useTvLayout) SettingsTileShowBottomNavigationBar(),
if (!settings.useTvLayout) SettingsTileNavigationDoubleBackExit(),
SettingsTileNavigationDrawer(),
if (!device.isTelevision) SettingsTileNavigationConfirmationDialog(),
if (!settings.useTvLayout) SettingsTileNavigationConfirmationDialog(),
];
}

View file

@ -34,11 +34,11 @@ class PrivacySection extends SettingsSection {
return [
SettingsTilePrivacyAllowInstalledAppAccess(),
if (canEnableErrorReporting) SettingsTilePrivacyAllowErrorReporting(),
if (!device.isTelevision && device.canRequestManageMedia) SettingsTilePrivacyManageMedia(),
if (!settings.useTvLayout && device.canRequestManageMedia) SettingsTilePrivacyManageMedia(),
SettingsTilePrivacySaveSearchHistory(),
if (!device.isTelevision) SettingsTilePrivacyEnableBin(),
if (!settings.useTvLayout) SettingsTilePrivacyEnableBin(),
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 '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/ref/mime_types.dart';
import 'package:aves/services/common/services.dart';
@ -73,7 +73,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
Widget build(BuildContext context) {
final appBarTitle = Text(context.l10n.settingsPageTitle);
if (device.isTelevision) {
if (settings.useTvLayout) {
return Scaffold(
body: AvesPopScope(
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/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -25,7 +25,7 @@ class ThumbnailsSection extends SettingsSection {
@override
List<SettingsTile> tiles(BuildContext context) => [
if (!device.isTelevision) SettingsTileCollectionQuickActions(),
if (!settings.useTvLayout) SettingsTileCollectionQuickActions(),
SettingsTileThumbnailOverlay(),
];
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,6 @@ import 'dart:ui';
import 'package:aves/app_mode.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/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
@ -181,12 +180,12 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
}
Widget _buildImagePage() {
final isTelevision = device.isTelevision;
final useTvLayout = settings.useTvLayout;
Widget? child;
Map<ShortcutActivator, Intent>? shortcuts = {
const SingleActivator(LogicalKeyboardKey.arrowUp): isTelevision ? const TvShowLessInfoIntent() : const _LeaveIntent(),
const SingleActivator(LogicalKeyboardKey.arrowDown): isTelevision ? const _TvShowMoreInfoIntent() : const _ShowInfoIntent(),
const SingleActivator(LogicalKeyboardKey.arrowUp): useTvLayout ? const TvShowLessInfoIntent() : const _LeaveIntent(),
const SingleActivator(LogicalKeyboardKey.arrowDown): useTvLayout ? const _TvShowMoreInfoIntent() : const _ShowInfoIntent(),
const SingleActivator(LogicalKeyboardKey.mediaPause): const _PlayPauseIntent.pause(),
const SingleActivator(LogicalKeyboardKey.mediaPlay): const _PlayPauseIntent.play(),
const SingleActivator(LogicalKeyboardKey.mediaPlayPause): const _PlayPauseIntent.toggle(),
@ -211,7 +210,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
);
}
if (child != null) {
if (device.isTelevision) {
if (settings.useTvLayout) {
child = ValueListenableBuilder<bool>(
valueListenable: _isImageFocusedNotifier,
builder: (context, isImageFocused, child) {
@ -238,7 +237,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
_TvShowMoreInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowMoreInfoNotification().dispatch(context)),
_PlayPauseIntent: CallbackAction<_PlayPauseIntent>(onInvoke: (intent) => _onPlayPauseIntent(intent, entry)),
ActivateIntent: CallbackAction<Intent>(onInvoke: (intent) {
if (isTelevision) {
if (useTvLayout) {
final _entry = entry;
if (_entry != null && _entry.isVideo) {
// 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();
AvesApp.setSystemUIStyle(context);
await windowService.requestOrientation();
if (!settings.useTvLayout) {
await windowService.requestOrientation();
}
}
// overlay

View file

@ -1,6 +1,6 @@
import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/device.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/theme/durations.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));
return SliverAppBar(
leading: device.isTelevision
leading: settings.useTvLayout
? null
: IconButton(
// key is expected by test driver

View file

@ -1,7 +1,6 @@
import 'dart:math';
import 'package:aves/app_mode.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.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 viewerButtonRow = FocusableActionDetector(
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))},
child: SafeArea(
top: false,

View file

@ -1,5 +1,5 @@
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/overlay_button.dart';
import 'package:aves/widgets/viewer/entry_vertical_pager.dart';
@ -70,9 +70,9 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
Widget build(BuildContext context) {
return FocusableActionDetector(
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))},
child: device.isTelevision
child: settings.useTvLayout
? Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,

View file

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

View file

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

View file

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

View file

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