diff --git a/lib/screens/settings/services_settings_screen.dart b/lib/screens/settings/services_settings_screen.dart index abc7dbda..81ea61a1 100644 --- a/lib/screens/settings/services_settings_screen.dart +++ b/lib/screens/settings/services_settings_screen.dart @@ -147,10 +147,12 @@ class _ServiceHubRow extends StatelessWidget { Widget build(BuildContext context) { return FocusableListTile( leading: leading, - title: Text(title), + title: Text(title, style: settingsOptionTitleStyle(context)), subtitle: Text(username != null ? t.services.connectedAs(username: username!) : t.services.notConnected), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: onTap, + dense: settingsRowDense(), + visualDensity: settingsRowVisualDensity(), ); } } diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index 7e8d9563..1b50293e 100644 --- a/lib/screens/settings/settings_screen.dart +++ b/lib/screens/settings/settings_screen.dart @@ -333,12 +333,15 @@ class _SettingsScreenState extends State with FocusableTab, Moun return FocusableListTile( focusNode: _focusTracker.get(_kDownloadLocation), leading: const AppIcon(Symbols.folder_rounded, fill: 1), - title: Text(isCustom ? t.settings.downloadLocationCustom : t.settings.downloadLocationDefault), + title: Text( + isCustom ? t.settings.downloadLocationCustom : t.settings.downloadLocationDefault, + style: settingsOptionTitleStyle(context), + ), subtitle: Text(currentPath, maxLines: 2, overflow: .ellipsis), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showDownloadLocationDialog(), - dense: false, - visualDensity: VisualDensity.standard, + dense: settingsRowDense(), + visualDensity: settingsRowVisualDensity(), ); }, ), @@ -517,7 +520,10 @@ class _SettingsScreenState extends State with FocusableTab, Moun fill: 1, color: hasUpdate ? Colors.orange : null, ), - title: Text(hasUpdate ? t.settings.updateAvailable : t.settings.checkForUpdates), + title: Text( + hasUpdate ? t.settings.updateAvailable : t.settings.checkForUpdates, + style: settingsOptionTitleStyle(context), + ), subtitle: hasUpdate ? Text(t.update.versionAvailable(version: _updateInfo!['latestVersion'])) : null, trailing: _isCheckingForUpdate ? const LoadingIndicatorBox(size: 24) @@ -531,8 +537,8 @@ class _SettingsScreenState extends State with FocusableTab, Moun _checkForUpdates(); } }, - dense: false, - visualDensity: VisualDensity.standard, + dense: settingsRowDense(), + visualDensity: settingsRowVisualDensity(), ), _buildAutoCheckUpdatesOnStartupTile(), ], diff --git a/lib/widgets/setting_tile.dart b/lib/widgets/setting_tile.dart index 57210bbe..56ccea0a 100644 --- a/lib/widgets/setting_tile.dart +++ b/lib/widgets/setting_tile.dart @@ -5,7 +5,6 @@ import 'package:material_symbols_icons/symbols.dart'; import '../screens/settings/settings_utils.dart'; import '../services/settings_service.dart'; -import '../utils/platform_detector.dart'; import 'app_icon.dart'; import 'focusable_list_tile.dart'; import 'settings_section.dart'; @@ -41,7 +40,6 @@ class SettingSwitchTile extends StatelessWidget { @override Widget build(BuildContext context) { - final isMobile = PlatformDetector.isMobile(context); final svc = _TileBase._svc; return ValueListenableBuilder( valueListenable: svc.listenable(pref), @@ -51,8 +49,8 @@ class SettingSwitchTile extends StatelessWidget { title: Text(title, style: settingsOptionTitleStyle(context)), subtitle: subtitle != null ? Text(subtitle!) : null, value: value, - dense: isMobile, - visualDensity: isMobile ? const VisualDensity(vertical: -3) : VisualDensity.standard, + dense: settingsRowDense(), + visualDensity: settingsRowVisualDensity(), onChanged: enabled ? (v) async { await svc.write(pref, v); @@ -88,7 +86,6 @@ class SettingNavigationTile extends StatelessWidget { @override Widget build(BuildContext context) { - final isMobile = PlatformDetector.isMobile(context); return FocusableListTile( focusNode: focusNode, leading: AppIcon(icon, fill: 1), @@ -96,8 +93,8 @@ class SettingNavigationTile extends StatelessWidget { subtitle: subtitle != null ? Text(subtitle!) : null, trailing: AppIcon(trailingIcon, fill: 1), onTap: onTap ?? () => Navigator.push(context, MaterialPageRoute(builder: destinationBuilder!)), - dense: isMobile, - visualDensity: isMobile ? const VisualDensity(vertical: -3) : VisualDensity.standard, + dense: settingsRowDense(), + visualDensity: settingsRowVisualDensity(), ); } } @@ -129,7 +126,6 @@ class SettingNumberTile extends StatelessWidget { @override Widget build(BuildContext context) { - final isMobile = PlatformDetector.isMobile(context); final svc = _TileBase._svc; return ValueListenableBuilder( valueListenable: svc.listenable(pref), @@ -138,8 +134,8 @@ class SettingNumberTile extends StatelessWidget { title: Text(title, style: settingsOptionTitleStyle(context)), subtitle: Text(subtitleBuilder(value)), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), - dense: isMobile, - visualDensity: isMobile ? const VisualDensity(vertical: -3) : VisualDensity.standard, + dense: settingsRowDense(), + visualDensity: settingsRowVisualDensity(), onTap: () => showNumericInputDialog( context: context, title: title, @@ -186,7 +182,6 @@ class SettingSelectionTile extends StatelessWidget { @override Widget build(BuildContext context) { - final isMobile = PlatformDetector.isMobile(context); final svc = _TileBase._svc; return ValueListenableBuilder( valueListenable: svc.listenable(pref), @@ -197,8 +192,8 @@ class SettingSelectionTile extends StatelessWidget { title: Text(title, style: settingsOptionTitleStyle(context)), subtitle: Text(subtitleBuilder(value)), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), - dense: isMobile, - visualDensity: isMobile ? const VisualDensity(vertical: -3) : VisualDensity.standard, + dense: settingsRowDense(), + visualDensity: settingsRowVisualDensity(), onTap: () async { final picked = await showSelectionDialog( context: context, @@ -238,7 +233,6 @@ class SettingRegexTile extends StatelessWidget { @override Widget build(BuildContext context) { - final isMobile = PlatformDetector.isMobile(context); final svc = _TileBase._svc; return ValueListenableBuilder( valueListenable: svc.listenable(pref), @@ -247,8 +241,8 @@ class SettingRegexTile extends StatelessWidget { title: Text(title, style: settingsOptionTitleStyle(context)), subtitle: Text(subtitle), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), - dense: isMobile, - visualDensity: isMobile ? const VisualDensity(vertical: -3) : VisualDensity.standard, + dense: settingsRowDense(), + visualDensity: settingsRowVisualDensity(), onTap: () => showRegexInputDialog( context: context, title: title, @@ -331,7 +325,6 @@ class SettingColorTile extends StatelessWidget { @override Widget build(BuildContext context) { - final isMobile = PlatformDetector.isMobile(context); final svc = _TileBase._svc; return ValueListenableBuilder( valueListenable: svc.listenable(pref), @@ -348,8 +341,8 @@ class SettingColorTile extends StatelessWidget { border: Border.all(color: Theme.of(context).colorScheme.outlineVariant), ), ), - dense: isMobile, - visualDensity: isMobile ? const VisualDensity(vertical: -3) : VisualDensity.standard, + dense: settingsRowDense(), + visualDensity: settingsRowVisualDensity(), onTap: () => showColorInputDialog( context: context, title: title, diff --git a/lib/widgets/settings_section.dart b/lib/widgets/settings_section.dart index eea166cd..6fdc40c9 100644 --- a/lib/widgets/settings_section.dart +++ b/lib/widgets/settings_section.dart @@ -6,14 +6,22 @@ import 'expressive_button_group.dart'; /// Standard settings-option title style. /// -/// Mobile matches Flutter's dense ListTile title size so custom controls and -/// ordinary settings rows keep the same hierarchy. Desktop and TV retain the -/// larger body style used for D-pad readability. +/// Matches Flutter's dense ListTile title size so custom controls and +/// ordinary settings rows keep the same hierarchy. TV retains the larger +/// body style used for D-pad readability at distance. TextStyle? settingsOptionTitleStyle(BuildContext context) { final style = Theme.of(context).textTheme.bodyLarge; - return PlatformDetector.isMobile(context) ? style?.copyWith(fontSize: 13) : style; + return PlatformDetector.isTV() ? style : style?.copyWith(fontSize: 13); } +/// Standard settings-row density, paired with [settingsOptionTitleStyle]: +/// compact rows everywhere except TV, where full-height rows keep D-pad +/// readability. +bool settingsRowDense() => !PlatformDetector.isTV(); + +VisualDensity settingsRowVisualDensity() => + settingsRowDense() ? const VisualDensity(vertical: -3) : VisualDensity.standard; + class SettingsSectionHeader extends StatelessWidget { final String title;