rewrite entire queue system to native

This commit is contained in:
Boof2015
2026-07-29 19:25:54 -04:00
parent d4c88042e0
commit 771d7a034c
29 changed files with 4376 additions and 220 deletions
@@ -5,7 +5,7 @@ import { QueueTray } from '@/components/queue/QueueTray';
import { RemoteQueueSheet } from '@/components/queue/RemoteQueueSheet';
import { seekTo } from '@/audio/playbackController';
import { spacing } from '@/theme';
import { createThemedStyles } from '@/theme/themed';
import { createThemedStyles, useColors } from '@/theme/themed';
import {
getNowPlayingTrackTransitionKey,
NowPlayingTrackFadeThrough,
@@ -14,6 +14,10 @@ import { usePlayerStore } from '@/stores/playerStore';
import { useSettingsStore } from '@/stores/settingsStore';
import type { NowPlayingCompanion } from './nowPlayingPreferences';
import type { Track } from '@/types/audio';
import {
AstraQueueView,
toNativeQueuePalette,
} from '../../../modules/astra-library-scanner';
const COMPANION_SEGMENTS = [
{ key: 'queue', label: 'Queue' },
@@ -35,7 +39,9 @@ export function NowPlayingCompanionPane({
track,
}: NowPlayingCompanionPaneProps) {
const styles = useStyles();
const colors = useColors();
const companion = useSettingsStore((s) => s.nowPlayingCompanion);
const nativeQueueEnabled = useSettingsStore((s) => s.nativeQueueEnabled);
const setCompanion = useSettingsStore((s) => s.setNowPlayingCompanion);
const currentTime = usePlayerStore((s) => (active && !desktopTarget ? s.currentTime : 0));
const duration = usePlayerStore((s) => (desktopTarget ? 0 : s.duration));
@@ -68,7 +74,15 @@ export function NowPlayingCompanionPane({
</View>
<View style={styles.content}>
{companion === 'queue' ? (
<QueueTray embedded onClose={noop} />
nativeQueueEnabled ? (
<AstraQueueView
active={active}
palette={toNativeQueuePalette(colors)}
style={styles.nativeQueue}
/>
) : (
<QueueTray embedded onClose={noop} />
)
) : track ? (
<NowPlayingTrackFadeThrough
transitionKey={transitionTrackKey}
@@ -113,4 +127,8 @@ const useStyles = createThemedStyles((colors) => ({
minHeight: 0,
position: 'relative',
},
nativeQueue: {
flex: 1,
minHeight: 0,
},
}));
+55 -3
View File
@@ -108,12 +108,18 @@ import { useThemeStore } from '@/stores/themeStore';
import type { DbTrack } from '@/types/library';
import {
cycleRepeat,
jumpToQueueIndex,
seekTo,
skipToNext,
skipToPrevious,
synchronizeVirtualQueueRevision,
togglePlay,
toggleShuffle
} from '@/audio/playbackController';
import {
AstraQueue,
toNativeQueuePalette,
} from '../../../modules/astra-library-scanner';
import {
desktopConnectionLabel,
getDesktopPlaybackPresentation,
@@ -174,6 +180,7 @@ export function NowPlayingOverlay() {
const nowPlayingAccentSource = useThemeStore((s) => s.nowPlayingAccentSource);
const coverArtAccentMethod = useThemeStore((s) => s.coverArtAccentMethod);
const scopeMode = useSettingsStore((s) => s.scopeMode);
const nativeQueueEnabled = useSettingsStore((s) => s.nativeQueueEnabled);
const scopeStageVisible = useSettingsStore((s) => s.scopeStageVisible);
const setScopeStageVisible = useSettingsStore((s) => s.setScopeStageVisible);
const scopeStyle = useSettingsStore((s) => s.nowPlayingScopeStyle);
@@ -378,6 +385,12 @@ export function NowPlayingOverlay() {
}
suspendPanForChildTransition();
setQueueOpen(true);
if (nativeQueueEnabled && !isDesktopTarget) {
void AstraQueue.present({ palette: toNativeQueuePalette(colors) }).catch((error) => {
console.warn('[queue] native presentation failed', error);
setQueueOpen(false);
});
}
};
const swapScopeMode = () =>
@@ -742,10 +755,49 @@ export function NowPlayingOverlay() {
// Stable identity: QueueTray is memo'd, so a fresh arrow here would defeat it.
const closeQueue = useCallback(() => {
suspendPanForChildTransition();
if (nativeQueueEnabled && !isDesktopTarget) AstraQueue.dismiss();
setQueueOpen(false);
// Shared values and the state setter remain stable for this overlay mount.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [isDesktopTarget, nativeQueueEnabled]);
useEffect(() => {
if (!nativeQueueEnabled) return undefined;
const dismissed = AstraQueue.addListener('onDismissed', () => {
setQueueOpen(false);
});
const playbackRequest = AstraQueue.addListener('onPlaybackRequest', (request) => {
void (async () => {
try {
const position = await AstraQueue.resolveEntryPosition(
request.entryId,
request.queueRevision,
);
if (position == null) {
throw new Error('The queue changed. Try that song again.');
}
await jumpToQueueIndex(position, { virtualPosition: true });
AstraQueue.resolvePlaybackRequest(request.requestId, true);
} catch (error) {
const message = error instanceof Error ? error.message : 'Could not play that song';
AstraQueue.resolvePlaybackRequest(request.requestId, false, message);
}
})();
});
const revision = AstraQueue.addListener('onQueueRevision', (event) => {
void synchronizeVirtualQueueRevision(
event.queueRevision,
event.activePosition,
).catch((error) => {
console.warn('[queue] transport revision sync failed', error);
});
});
return () => {
dismissed.remove();
playbackRequest.remove();
revision.remove();
};
}, [nativeQueueEnabled]);
// Hardware back, innermost layer first: menu → queue tray → player. Registered
// only while open, so it sits above the focused screen's own handlers (LIFO)
@@ -1692,9 +1744,9 @@ export function NowPlayingOverlay() {
{queueOpen && !hasTabletCompanion && (
isDesktopTarget ? (
<RemoteQueueSheet onClose={closeQueue} />
) : (
) : !nativeQueueEnabled ? (
<QueueTray onClose={closeQueue} />
)
) : null
)}
<PlaybackTargetPicker
visible={targetPickerOpen}