smoothing

This commit is contained in:
Boof2015
2026-07-01 19:08:32 -04:00
parent 2016a0037d
commit cab67fbc05
7 changed files with 85 additions and 26 deletions
+18 -5
View File
@@ -15,12 +15,16 @@ const PILL_HEIGHT = 56;
const ART = 42;
const CURVE_POINTS = 64;
interface MiniPlayerProps {
visible?: boolean;
}
/**
* Persistent floating mini-player (M3 redesign): a rounded pill above the tab
* bar with the live filled-line spectrum drifting behind the metadata. Tapping
* opens the full now-playing screen.
*/
export function MiniPlayer() {
export function MiniPlayer({ visible = true }: MiniPlayerProps) {
const router = useRouter();
const track = usePlayerStore((s) => s.currentTrack);
const playbackState = usePlayerStore((s) => s.playbackState);
@@ -35,15 +39,21 @@ export function MiniPlayer() {
const isPlaying = playbackState === 'playing';
const isLoading = playbackState === 'loading';
const progress = duration > 0 ? Math.min(1, currentTime / duration) : 0;
const liveScopeActive = visible && scopeActive;
const onLayout = (e: LayoutChangeEvent) => setPillWidth(e.nativeEvent.layout.width);
return (
<Pressable style={styles.pill} onPress={() => router.push('/now-playing')} onLayout={onLayout}>
{scopeActive && pillWidth > 0 && (
<Pressable
pointerEvents={visible ? 'auto' : 'none'}
style={[styles.pill, !visible && styles.hidden]}
onPress={() => router.push('/now-playing')}
onLayout={onLayout}
>
{liveScopeActive && pillWidth > 0 && (
<View pointerEvents="none" style={styles.spectrum}>
<SpectrumCurve
active={scopeActive}
active={liveScopeActive}
pointCount={CURVE_POINTS}
analysisFrameMs={0}
dbMin={-84}
@@ -58,7 +68,7 @@ export function MiniPlayer() {
/>
</View>
)}
{scopeActive && pillWidth > 0 && <View pointerEvents="none" style={styles.spectrumVeil} />}
{liveScopeActive && pillWidth > 0 && <View pointerEvents="none" style={styles.spectrumVeil} />}
<View style={styles.row}>
<View style={styles.art}>
@@ -110,6 +120,9 @@ const styles = StyleSheet.create({
overflow: 'hidden',
justifyContent: 'center',
},
hidden: {
opacity: 0,
},
spectrum: {
position: 'absolute',
top: 0,
+24 -2
View File
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { View, Pressable, StyleSheet, type LayoutChangeEvent } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons';
@@ -13,6 +13,9 @@ import { colors, fonts, layout, spacing } from '@/theme';
import { motion } from '@/theme/motion';
type IconName = keyof typeof Ionicons.glyphMap;
type MiniPlayerPhase = 'hidden' | 'reserved' | 'visible';
const TAB_TRANSITION_MS = 160;
export const TAB_META: Record<string, { label: string; icon: IconName }> = {
index: { label: 'Home', icon: 'home' },
@@ -41,6 +44,7 @@ export function TabBar({ items, onPress }: TabBarProps) {
const insets = useSafeAreaInsets();
const tabs = items.filter((item) => TAB_META[item.name]);
const homeFocused = items.some((item) => item.name === 'index' && item.focused);
const [settledHomeFocused, setSettledHomeFocused] = useState(homeFocused);
const count = tabs.length;
const activeIndex = Math.max(
0,
@@ -56,6 +60,23 @@ export function TabBar({ items, onPress }: TabBarProps) {
position.value = withTiming(activeIndex, motion.snap);
}, [activeIndex, position]);
useEffect(() => {
if (settledHomeFocused === homeFocused) {
return;
}
const timer = setTimeout(() => setSettledHomeFocused(homeFocused), TAB_TRANSITION_MS);
return () => clearTimeout(timer);
}, [homeFocused, settledHomeFocused]);
const miniPlayerPhase: MiniPlayerPhase = homeFocused
? settledHomeFocused
? 'hidden'
: 'reserved'
: settledHomeFocused
? 'hidden'
: 'visible';
const indicatorStyle = useAnimatedStyle(() => {
const segment = count > 0 ? barWidth.value / count : 0;
return {
@@ -70,7 +91,8 @@ export function TabBar({ items, onPress }: TabBarProps) {
return (
<View style={styles.wrap}>
{!homeFocused ? <MiniPlayer /> : null}
{miniPlayerPhase === 'visible' ? <MiniPlayer /> : null}
{miniPlayerPhase === 'reserved' ? <MiniPlayer visible={false} /> : null}
<View
style={[
styles.bar,