Refine iOS navigation, media controls, and background sync
Build APK / build (push) Successful in 5m50s
Build APK / build (push) Successful in 5m50s
This commit is contained in:
@@ -4,8 +4,10 @@ import '../../models/selection_action.dart';
|
||||
import '../../providers/settings_controller.dart';
|
||||
import '../../theme/design_tokens.dart';
|
||||
import '../noo/lists/noo_tab_order_row.dart';
|
||||
import '../noo/lists/noo_settings_row.dart';
|
||||
import '../noo/lists/noo_grouped_list.dart';
|
||||
import '../noo/core/noo_segmented_control.dart';
|
||||
import '../noo/noo_layout.dart';
|
||||
import 'settings_section.dart';
|
||||
|
||||
/// Settings section: the priority order bulk actions (favorite, share,
|
||||
/// download, ...) show in on the Files/Photos/Favorites multi-select
|
||||
@@ -13,7 +15,7 @@ import 'settings_section.dart';
|
||||
/// which ones land in `NooSelectionBar`'s fixed inline slots (3 mobile / 4
|
||||
/// desktop, see its own doc comment) vs. behind its trailing "More" -
|
||||
/// there's no pin/hide concept here the way Tabs has, so this is just a
|
||||
/// reorder list, not a whole section of its own option rows.
|
||||
/// position choice followed by a reorder list.
|
||||
class SettingsActionBarSection extends StatelessWidget {
|
||||
const SettingsActionBarSection({super.key});
|
||||
|
||||
@@ -33,18 +35,30 @@ class SettingsActionBarSection extends StatelessWidget {
|
||||
final desktop = NooLayout.isDesktop(context);
|
||||
|
||||
final orderList = _ActionOrderList(
|
||||
rounded: desktop,
|
||||
order: settings.selectionActionOrder,
|
||||
onReorder: (oldIndex, newIndex) =>
|
||||
_handleReorder(settings, oldIndex, newIndex),
|
||||
);
|
||||
|
||||
final positionRow = NooSettingsRow(
|
||||
label: const Text('Position'),
|
||||
trailing: NooSegmentedControl<SelectionBarPosition>(
|
||||
onSurface: true,
|
||||
value: settings.selectionBarPosition,
|
||||
onChanged: settings.setSelectionBarPosition,
|
||||
options: const [
|
||||
NooSegmentOption(value: SelectionBarPosition.top, label: 'Top'),
|
||||
NooSegmentOption(value: SelectionBarPosition.bottom, label: 'Bottom'),
|
||||
],
|
||||
),
|
||||
);
|
||||
final colors = context.nooColors;
|
||||
|
||||
if (!desktop) {
|
||||
return SettingsSection(
|
||||
title: 'Action bar',
|
||||
subtitle: _subtitle,
|
||||
children: [orderList],
|
||||
return NooGroupedList(
|
||||
footer: const Text(_subtitle),
|
||||
children: [positionRow, orderList],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,6 +79,8 @@ class SettingsActionBarSection extends StatelessWidget {
|
||||
const SizedBox(height: 4),
|
||||
Text(_subtitle, style: NooText.meta.copyWith(color: colors.fg3)),
|
||||
const SizedBox(height: NooSpace.md),
|
||||
positionRow,
|
||||
const SizedBox(height: NooSpace.md),
|
||||
orderList,
|
||||
],
|
||||
),
|
||||
@@ -79,8 +95,13 @@ class SettingsActionBarSection extends StatelessWidget {
|
||||
class _ActionOrderList extends StatelessWidget {
|
||||
final List<SelectionActionKind> order;
|
||||
final void Function(int oldIndex, int newIndex) onReorder;
|
||||
final bool rounded;
|
||||
|
||||
const _ActionOrderList({required this.order, required this.onReorder});
|
||||
const _ActionOrderList({
|
||||
required this.order,
|
||||
required this.onReorder,
|
||||
required this.rounded,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -105,12 +126,14 @@ class _ActionOrderList extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
);
|
||||
final rows = DecoratedBox(
|
||||
decoration: BoxDecoration(color: colors.line),
|
||||
child: list,
|
||||
);
|
||||
if (!rounded) return rows;
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(NooRadii.card),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(color: colors.line),
|
||||
child: list,
|
||||
),
|
||||
child: rows,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ class SettingsAppearanceSection extends StatelessWidget {
|
||||
_BottomBarStyleRow(settings: settings),
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.glassWater,
|
||||
label: const Text('Frosted glass bottom bar'),
|
||||
label: const Text('Frosted glass'),
|
||||
subtitle: const Text(
|
||||
'A translucent, blurred bottom bar - content scrolls behind it',
|
||||
'Blurred bottom navigation, avatar menus and media viewer panels',
|
||||
),
|
||||
trailing: NooToggle(
|
||||
checked: settings.bottomBarFrosted,
|
||||
@@ -67,6 +67,23 @@ class SettingsAppearanceSection extends StatelessWidget {
|
||||
),
|
||||
if (settings.bottomBarFrosted) _FrostedGlassRow(settings: settings),
|
||||
_NavMenuStyleRow(settings: settings),
|
||||
if (settings.navMenuStyle == NooNavMenuStyle.avatarMenu)
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.userRound,
|
||||
label: const Text('Avatar position'),
|
||||
trailing: NooSegmentedControl<AvatarPosition>(
|
||||
onSurface: true,
|
||||
value: settings.avatarPosition,
|
||||
onChanged: settings.setAvatarPosition,
|
||||
options: const [
|
||||
NooSegmentOption(value: AvatarPosition.top, label: 'Top'),
|
||||
NooSegmentOption(
|
||||
value: AvatarPosition.bottom,
|
||||
label: 'Bottom',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.search,
|
||||
label: const Text('Search in bottom bar'),
|
||||
@@ -96,14 +113,19 @@ class SettingsAppearanceSection extends StatelessWidget {
|
||||
icon: LucideIcons.palette,
|
||||
label: const Text('Accent color'),
|
||||
subtitle: Text(
|
||||
settings.useDynamicColor
|
||||
settings.useDynamicColor &&
|
||||
Theme.of(context).platform != TargetPlatform.iOS
|
||||
? 'Matching your wallpaper'
|
||||
: settings.seedColor == AppTheme.defaultAccent
|
||||
? 'Default'
|
||||
: 'Custom color',
|
||||
),
|
||||
trailing: _AccentSwatchDot(
|
||||
color: settings.useDynamicColor ? null : settings.seedColor,
|
||||
color:
|
||||
settings.useDynamicColor &&
|
||||
Theme.of(context).platform != TargetPlatform.iOS
|
||||
? null
|
||||
: settings.seedColor,
|
||||
),
|
||||
onTap: () => _openAccentPicker(context, settings),
|
||||
),
|
||||
@@ -116,15 +138,16 @@ class SettingsAppearanceSection extends StatelessWidget {
|
||||
onChanged: settings.setAmoledDark,
|
||||
),
|
||||
),
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.waves,
|
||||
label: const Text('Seek bar style'),
|
||||
subtitle: const Text(
|
||||
'The progress bar style used when playing videos',
|
||||
if (Theme.of(context).platform != TargetPlatform.iOS)
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.waves,
|
||||
label: const Text('Seek bar style'),
|
||||
subtitle: const Text(
|
||||
'The progress bar style used when playing videos',
|
||||
),
|
||||
value: _seekBarStyleLabel(settings.mediaProgressBarStyle),
|
||||
onTap: () => _openSeekBarPicker(context, settings),
|
||||
),
|
||||
value: _seekBarStyleLabel(settings.mediaProgressBarStyle),
|
||||
onTap: () => _openSeekBarPicker(context, settings),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -168,6 +191,7 @@ class _ThemeRow extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
NooSegmentedControl<ThemeMode>(
|
||||
fill: true,
|
||||
onSurface: true,
|
||||
value: settings.themeMode,
|
||||
onChanged: settings.setThemeMode,
|
||||
options: const [
|
||||
@@ -231,6 +255,7 @@ class _BottomBarStyleRow extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
NooSegmentedControl<NooBottomBarStyle>(
|
||||
fill: true,
|
||||
onSurface: true,
|
||||
value: settings.bottomBarStyle,
|
||||
onChanged: settings.setBottomBarStyle,
|
||||
options: const [
|
||||
@@ -287,6 +312,7 @@ class _FrostedGlassRow extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
NooSegmentedControl<FrostedGlassPreset?>(
|
||||
fill: true,
|
||||
onSurface: true,
|
||||
// No segment selected once the sliders no longer match a preset.
|
||||
value: settings.frostedPreset,
|
||||
onChanged: (preset) {
|
||||
@@ -402,6 +428,7 @@ class _NavMenuStyleRow extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
NooSegmentedControl<NooNavMenuStyle>(
|
||||
fill: true,
|
||||
onSurface: true,
|
||||
value: settings.navMenuStyle,
|
||||
onChanged: settings.setNavMenuStyle,
|
||||
options: const [
|
||||
@@ -455,6 +482,7 @@ class _FabStyleRow extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
NooSegmentedControl<FabStyle>(
|
||||
fill: true,
|
||||
onSurface: true,
|
||||
value: settings.fabStyle,
|
||||
onChanged: settings.setFabStyle,
|
||||
options: const [
|
||||
@@ -508,22 +536,26 @@ class _AccentSwatchDot extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _openAccentPicker(BuildContext context, SettingsController settings) {
|
||||
final useDynamicColor =
|
||||
settings.useDynamicColor &&
|
||||
Theme.of(context).platform != TargetPlatform.iOS;
|
||||
Widget grid(NooColors colors) => Wrap(
|
||||
spacing: 14,
|
||||
runSpacing: 14,
|
||||
children: [
|
||||
_AccentSwatch(
|
||||
isSelected: settings.useDynamicColor,
|
||||
onTap: () {
|
||||
settings.setUseDynamicColor(true);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
background: colors.surface3,
|
||||
child: Icon(LucideIcons.wallpaper, color: colors.fg2, size: 20),
|
||||
),
|
||||
if (Theme.of(context).platform != TargetPlatform.iOS)
|
||||
_AccentSwatch(
|
||||
isSelected: settings.useDynamicColor,
|
||||
onTap: () {
|
||||
settings.setUseDynamicColor(true);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
background: colors.surface3,
|
||||
child: Icon(LucideIcons.wallpaper, color: colors.fg2, size: 20),
|
||||
),
|
||||
for (final color in AppTheme.seedColors)
|
||||
_AccentSwatch(
|
||||
isSelected: !settings.useDynamicColor && settings.seedColor == color,
|
||||
isSelected: !useDynamicColor && settings.seedColor == color,
|
||||
onTap: () {
|
||||
settings.setSeedColor(color);
|
||||
Navigator.pop(context);
|
||||
|
||||
@@ -125,12 +125,16 @@ class _CacheIntervalRow extends StatelessWidget {
|
||||
onChanged: (value) => onChanged(value.round()),
|
||||
),
|
||||
),
|
||||
if (minutes < 15)
|
||||
if (Theme.of(context).platform == TargetPlatform.iOS ||
|
||||
(Theme.of(context).platform == TargetPlatform.android && minutes < 15))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
'While the app is closed, synced files refresh at most every '
|
||||
'15 min (an Android limit).',
|
||||
Theme.of(context).platform == TargetPlatform.iOS
|
||||
? 'iOS decides when background sync runs; the refresh '
|
||||
'interval is a request, not a guaranteed schedule.'
|
||||
: 'While the app is closed, synced files refresh at most every '
|
||||
'15 min (an Android limit).',
|
||||
style: NooText.meta.copyWith(color: colors.fg3),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user