onboarding + eq fixes

This commit is contained in:
Boof2015
2026-07-07 20:21:09 -04:00
parent c072a1df16
commit 6aa4b544fc
20 changed files with 1349 additions and 131 deletions
+3 -1
View File
@@ -51,7 +51,8 @@ import {
EQ_MIN_FREQUENCY,
EQ_MIN_PREAMP_DB,
EQ_MIN_Q,
isPassEQBandType
isPassEQBandType,
isShelfEQBandType
} from '@/audio/eq';
import { parseAutoEQ } from '@/audio/autoEQParser';
import { buildGraphicBands } from '@/audio/graphicEq';
@@ -516,6 +517,7 @@ function getValueEditConfig(kind: EQEditableValue, band: EQBand) {
parseValue: parseDb,
};
case 'Q':
if (isShelfEQBandType(band.type)) return null;
return {
title: 'Edit Q',
initialValue: band.Q.toFixed(2),
+42 -12
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { AppState } from 'react-native';
import { AppState, StyleSheet, View } from 'react-native';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
@@ -41,6 +41,8 @@ import { fetchDesktopRemoteIdentity } from '@/services/desktopRemoteClient';
import { useDesktopSyncStore } from '@/stores/desktopSyncStore';
import { SyncConflictPrompt } from '@/components/sync/SyncConflictPrompt';
import { useThemeStore } from '@/stores/themeStore';
import { useOnboardingStore } from '@/stores/onboardingStore';
import { OnboardingFlow } from '@/components/onboarding/OnboardingFlow';
import { useTheme } from '@/theme/themed';
// Anchor the root stack at the tabs so a deep link straight to a top-level route (the
@@ -280,8 +282,10 @@ export default function RootLayout() {
// persisted theme (no flash). The failsafe path paints the default theme
// and snaps once the SQLite read lands — accepted degradation.
const themeLoaded = useThemeStore((s) => s.loaded);
const onboardingLoaded = useOnboardingStore((s) => s.loaded);
const onboardingComplete = useOnboardingStore((s) => s.onboardingComplete);
const theme = useTheme();
const ready = (fontsLoaded && themeLoaded) || splashTimedOut;
const ready = (fontsLoaded && themeLoaded && onboardingLoaded) || splashTimedOut;
useEffect(() => {
if (ready) {
@@ -297,6 +301,10 @@ export default function RootLayout() {
.getState()
.load()
.catch((err) => console.error('[theme] load failed', err));
useOnboardingStore
.getState()
.load()
.catch((err) => console.error('[onboarding] load failed', err));
useLibraryStore
.getState()
.initialize()
@@ -326,14 +334,22 @@ export default function RootLayout() {
<GestureHandlerRootView style={{ flex: 1, backgroundColor: theme.colors.bgPrimary }}>
<SafeAreaProvider>
<StatusBar style={theme.statusBarStyle} />
<ThemeSystemSync />
<PlaybackSync />
<ScopeLifecycle />
<NormalizationSync />
<LastFmScrobbler />
<PlaybackTargetSync />
<DesktopRemoteMediaSessionSync />
<DesktopSyncAutoTrigger />
{/* The navigator stays mounted whatever the onboarding state (expo-router
needs a root navigator), but the playback/sync/desktop side-effects and
overlays are gated off during the wizard — no LAN-discovery bursts or
scrobbler running mid-onboarding. */}
{onboardingComplete ? (
<>
<ThemeSystemSync />
<PlaybackSync />
<ScopeLifecycle />
<NormalizationSync />
<LastFmScrobbler />
<PlaybackTargetSync />
<DesktopRemoteMediaSessionSync />
<DesktopSyncAutoTrigger />
</>
) : null}
<Stack
screenOptions={{
headerShown: false,
@@ -351,8 +367,22 @@ export default function RootLayout() {
}}
/>
</Stack>
<QuickSearchOverlay />
<SyncConflictPrompt />
{onboardingComplete ? (
<>
<QuickSearchOverlay />
<SyncConflictPrompt />
</>
) : (
// First-run gate: opaque full-screen wizard over the (hidden) navigator.
// markComplete flips the flag → this unmounts, revealing the app.
<View style={StyleSheet.absoluteFill}>
<OnboardingFlow
onDone={() => {
void useOnboardingStore.getState().markComplete();
}}
/>
</View>
)}
</SafeAreaProvider>
</GestureHandlerRootView>
);
+21 -1
View File
@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { InteractionManager } from 'react-native';
import { Alert, InteractionManager } from 'react-native';
import { useRouter } from 'expo-router';
import {
SettingsNavRow,
@@ -10,6 +10,7 @@ import { formatRelativeTime } from '@/lib/format';
import { useColors } from '@/theme/themed';
import { useDesktopRemoteStore } from '@/stores/desktopRemoteStore';
import { useDesktopSyncStore } from '@/stores/desktopSyncStore';
import { useOnboardingStore } from '@/stores/onboardingStore';
export default function ExperimentalSettingsScreen() {
const colors = useColors();
@@ -31,6 +32,17 @@ export default function ExperimentalSettingsScreen() {
const desktopRemoteSubtitle = desktopRemoteConnection
? `${desktopRemoteConnection.desktopName ?? 'Astra Desktop'}: ${desktopRemoteState === 'connected' ? 'connected' : desktopRemoteState}`
: 'Pair with Astra Desktop to control playback from this phone.';
const replayOnboarding = () => {
Alert.alert(
'Replay onboarding?',
'The first-run setup wizard will show again the next time you return to the home screen. Your library and settings are kept.',
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Replay', onPress: () => void useOnboardingStore.getState().reset() },
]
);
};
const desktopSyncSubtitle = !desktopRemoteConnection
? 'Sync favorites and playlists with Astra Desktop.'
: desktopSyncConflictCount > 0
@@ -57,6 +69,14 @@ export default function ExperimentalSettingsScreen() {
subtitleColor={desktopSyncConflictCount > 0 ? colors.warning : undefined}
onPress={() => router.push('/desktop-sync' as never)}
/>
<SettingsSectionLabel>DEVELOPER</SettingsSectionLabel>
<SettingsNavRow
icon="refresh-outline"
title="Replay onboarding"
subtitle="Show the first-run setup wizard again."
onPress={replayOnboarding}
/>
</SettingsSectionScreen>
);
}