Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/features/settings/data/settings_search_catalog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,11 @@ List<SettingsSearchPage> buildSettingsSearchCatalog() {
('selectedSize', l10n.selectedSize),
('depth', l10n.depth),
]),
_section('cardBackground', l10n.cardBackground, [
('blurGradient', l10n.blurGradient),
('blackGradient', l10n.blackGradient),
('albumArtColorGradient', l10n.albumArtColorGradient),
]),
_section('artResolution', l10n.artResolution, [
('low', l10n.low),
('medium', l10n.medium),
Expand Down
59 changes: 56 additions & 3 deletions lib/features/settings/screens/orbit_settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flick/providers/app_preferences_provider.dart';
import 'package:flick/features/settings/widgets/orbit_preview.dart';
import 'package:flick/features/settings/widgets/settings_widgets.dart';
import 'package:flick/l10n/settings_l10n.dart';
import 'package:flick/models/orbit_card_background_style.dart';

enum _OrbitLayoutProfile { portrait, landscape }

Expand All @@ -32,9 +33,11 @@ class _OrbitSettingsScreenState extends ConsumerState<OrbitSettingsScreen> {
super.didChangeDependencies();
if (_profileInitialized) return;
_profileInitialized = true;
_profile = AdaptiveLayout.isWide(MediaQuery.sizeOf(context))
? _OrbitLayoutProfile.landscape
: _OrbitLayoutProfile.portrait;
_profile =
widget.initialSection == 'cardBackground' ||
!AdaptiveLayout.isWide(MediaQuery.sizeOf(context))
? _OrbitLayoutProfile.portrait
: _OrbitLayoutProfile.landscape;
}

@override
Expand All @@ -61,6 +64,9 @@ class _OrbitSettingsScreenState extends ConsumerState<OrbitSettingsScreen> {
final cardWidthRatio = _landscape
? prefs.orbitLandscapeCardWidthRatio
: prefs.orbitCardWidthRatio;
final cardBackgroundStyle = OrbitCardBackgroundStyleX.fromStorageValue(
prefs.orbitCardBackgroundStyle,
);

return SettingsScaffold(
title: l10n.englishForFilipino.customizeOrbital,
Expand Down Expand Up @@ -250,6 +256,53 @@ class _OrbitSettingsScreenState extends ConsumerState<OrbitSettingsScreen> {
],
),
const SizedBox(height: AppConstants.spacingLg),
if (!_landscape) ...[
SettingsSectionHeader(
l10n.englishForFilipino.cardBackground,
tag: l10n.englishForFilipino.portrait,
sectionId: 'cardBackground',
),
SettingsCard(
children: [
SelectionSetting(
icon: LucideIcons.aperture,
title: l10n.englishForFilipino.blurGradient,
subtitle: l10n.englishForFilipino.blurGradientHint,
selected:
cardBackgroundStyle ==
OrbitCardBackgroundStyle.blurGradient,
onTap: () => notifier.setOrbitCardBackgroundStyle(
OrbitCardBackgroundStyle.blurGradient.storageValue,
),
),
const SettingsDivider(),
SelectionSetting(
icon: LucideIcons.circle,
title: l10n.englishForFilipino.blackGradient,
subtitle: l10n.englishForFilipino.blackGradientHint,
selected:
cardBackgroundStyle ==
OrbitCardBackgroundStyle.blackGradient,
onTap: () => notifier.setOrbitCardBackgroundStyle(
OrbitCardBackgroundStyle.blackGradient.storageValue,
),
),
const SettingsDivider(),
SelectionSetting(
icon: LucideIcons.palette,
title: l10n.englishForFilipino.albumArtColorGradient,
subtitle: l10n.englishForFilipino.albumArtColorGradientHint,
selected:
cardBackgroundStyle ==
OrbitCardBackgroundStyle.albumColorGradient,
onTap: () => notifier.setOrbitCardBackgroundStyle(
OrbitCardBackgroundStyle.albumColorGradient.storageValue,
),
),
],
),
],
const SizedBox(height: AppConstants.spacingLg),
SettingsSectionHeader(
l10n.englishForFilipino.artResolution,
tag: l10n.englishForFilipino.orbitSharedVisuals,
Expand Down
118 changes: 87 additions & 31 deletions lib/features/settings/widgets/orbit_preview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ import 'package:flick/core/theme/app_colors.dart';
import 'package:flick/features/settings/widgets/settings_widgets.dart';
import 'package:flick/features/songs/widgets/orbit_scroll.dart';
import 'package:flick/l10n/settings_l10n.dart';
import 'package:flick/models/orbit_card_background_style.dart';
import 'package:flick/models/song.dart';
import 'package:flick/providers/app_preferences_provider.dart';
import 'package:flick/providers/songs_provider.dart';
import 'package:flick/services/app_preferences_service.dart';

/// Live, browse-only preview of the orbit settings.
///
/// The preview paints the library header above the orbit so the top boundary
/// is visible while the controls below change. Tapping cards only moves the
/// preview's focus; it never starts playback or changes the queue.
/// The preview paints the library header above the orbit so the overlap and
/// screen-edge clipping are visible while the controls below change. Tapping
/// cards only moves the preview's focus; it never starts playback or changes
/// the queue.
class OrbitPreview extends ConsumerStatefulWidget {
const OrbitPreview({super.key, required this.landscape});

Expand Down Expand Up @@ -353,22 +355,19 @@ class _VirtualLibraryPreview extends StatelessWidget {
// around it still honors the user's text scale.
textScaler: TextScaler.noScaling,
),
child: Column(
children: [
_PreviewLibraryHeader(
songCount: songs.length,
compact: landscape,
),
Expanded(
child: _PreviewOrbit(
landscape: landscape,
prefs: prefs,
songs: songs,
selectedIndex: selectedIndex,
onSelectedIndexChanged: onSelectedIndexChanged,
),
),
],
child: _PreviewHeaderOverlay(
header: _PreviewLibraryHeader(
songCount: songs.length,
compact: landscape,
),
content: _PreviewOrbit(
landscape: landscape,
prefs: prefs,
songs: songs,
selectedIndex: selectedIndex,
onSelectedIndexChanged: onSelectedIndexChanged,
paintOverflowTop: size.width * 2,
),
),
),
),
Expand All @@ -384,17 +383,20 @@ class _PreviewOrbit extends StatelessWidget {
required this.songs,
required this.selectedIndex,
required this.onSelectedIndexChanged,
required this.paintOverflowTop,
});

final bool landscape;
final AppPreferences prefs;
final List<Song> songs;
final int selectedIndex;
final ValueChanged<int> onSelectedIndexChanged;
final double paintOverflowTop;

@override
Widget build(BuildContext context) {
return OrbitScroll(
paintOverflowTop: paintOverflowTop,
songs: songs,
selectedIndex: selectedIndex,
scrollDirection: landscape ? Axis.horizontal : Axis.vertical,
Expand Down Expand Up @@ -422,6 +424,9 @@ class _PreviewOrbit extends StatelessWidget {
artResolutionMultiplier: prefs.orbitArtResolutionMultiplier,
showPath: prefs.orbitShowPath,
showGlow: prefs.orbitShowGlow,
cardBackgroundStyle: OrbitCardBackgroundStyleX.fromStorageValue(
prefs.orbitCardBackgroundStyle,
),
swipeActionsEnabled: false,
isSelectionMode: false,
onSelectedIndexChanged: onSelectedIndexChanged,
Expand Down Expand Up @@ -469,19 +474,26 @@ class _FullScreenOrbitPreviewState extends State<_FullScreenOrbitPreview> {
child: Column(
children: [
_FullScreenPreviewHeader(landscape: widget.landscape),
_PreviewLibraryHeader(
songCount: widget.songs.length,
compact: MediaQuery.sizeOf(context).height < 500,
),
Expanded(
child: SizedBox.expand(
key: const Key('orbit_preview_fullscreen'),
child: _PreviewOrbit(
landscape: widget.landscape,
prefs: widget.prefs,
songs: widget.songs,
selectedIndex: _selectedIndex,
onSelectedIndexChanged: _select,
// Keep the orbit's upward paint inside the preview stage so it
// never crosses the preview chrome or escapes the route.
child: ClipRect(
child: _PreviewHeaderOverlay(
header: _PreviewLibraryHeader(
songCount: widget.songs.length,
compact: MediaQuery.sizeOf(context).height < 500,
),
content: SizedBox.expand(
key: const Key('orbit_preview_fullscreen'),
child: _PreviewOrbit(
landscape: widget.landscape,
prefs: widget.prefs,
songs: widget.songs,
selectedIndex: _selectedIndex,
onSelectedIndexChanged: _select,
paintOverflowTop: MediaQuery.sizeOf(context).width * 2,
),
),
),
),
),
Expand Down Expand Up @@ -715,3 +727,47 @@ class _PreviewHeaderButton extends StatelessWidget {
);
}
}

enum _PreviewLayoutSlot { content, header }

/// Lays out the miniature library below its header but paints the orbit first,
/// so the header stays readable while the orbit passes behind it.
class _PreviewHeaderOverlay extends StatelessWidget {
const _PreviewHeaderOverlay({required this.header, required this.content});

final Widget header;
final Widget content;

@override
Widget build(BuildContext context) {
return CustomMultiChildLayout(
delegate: _PreviewHeaderOverlayDelegate(),
children: [
LayoutId(id: _PreviewLayoutSlot.content, child: content),
LayoutId(id: _PreviewLayoutSlot.header, child: header),
],
);
}
}

class _PreviewHeaderOverlayDelegate extends MultiChildLayoutDelegate {
@override
void performLayout(Size size) {
final headerSize = layoutChild(
_PreviewLayoutSlot.header,
BoxConstraints(minWidth: size.width, maxWidth: size.width),
);
positionChild(_PreviewLayoutSlot.header, Offset.zero);

final contentHeight = math.max(0.0, size.height - headerSize.height);
layoutChild(
_PreviewLayoutSlot.content,
BoxConstraints.tightFor(width: size.width, height: contentHeight),
);
positionChild(_PreviewLayoutSlot.content, Offset(0, headerSize.height));
}

@override
bool shouldRelayout(covariant _PreviewHeaderOverlayDelegate oldDelegate) =>
false;
}
Loading
Loading