mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-12 05:10:52 +02:00
polish
This commit is contained in:
@@ -1,14 +1,12 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Tabs } from 'expo-router';
|
||||
// RN's Easing (not reanimated): the bottom-tabs scene transition runs on legacy
|
||||
// Animated.timing and may use the native driver, so the easing must be serializable.
|
||||
import { Easing } from 'react-native';
|
||||
import { TabBar, type TabItem } from '@/components/TabBar';
|
||||
import {
|
||||
TAB_TRANSITION_SETTLE_MS,
|
||||
TAB_TRANSITION_SPEC,
|
||||
} from '@/navigation/tabTransition';
|
||||
import { useColors } from '@/theme/themed';
|
||||
|
||||
const TAB_TRANSITION_MS = 160;
|
||||
const TAB_EASING = Easing.out(Easing.cubic);
|
||||
|
||||
export default function TabsLayout() {
|
||||
const colors = useColors();
|
||||
const lastSwitchAt = useRef(0);
|
||||
@@ -22,10 +20,7 @@ export default function TabsLayout() {
|
||||
sceneStyle: { backgroundColor: colors.bgPrimary },
|
||||
// Directional slide + cross-fade between tabs, following tab order.
|
||||
animation: 'shift' as const,
|
||||
transitionSpec: {
|
||||
animation: 'timing' as const,
|
||||
config: { duration: TAB_TRANSITION_MS, easing: TAB_EASING },
|
||||
},
|
||||
transitionSpec: TAB_TRANSITION_SPEC,
|
||||
}),
|
||||
[colors.bgPrimary]
|
||||
);
|
||||
@@ -45,7 +40,7 @@ export default function TabsLayout() {
|
||||
// completion frame and leave the incoming scene invisible; swallow
|
||||
// taps until the current transition has finished.
|
||||
const now = Date.now();
|
||||
if (now - lastSwitchAt.current < TAB_TRANSITION_MS + 30) return;
|
||||
if (now - lastSwitchAt.current < TAB_TRANSITION_SETTLE_MS + 30) return;
|
||||
const event = navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: item.key,
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
import { useRipple } from '@/theme/ripple';
|
||||
import { motion } from '@/theme/motion';
|
||||
import { TAB_TRANSITION_SETTLE_MS } from '@/navigation/tabTransition';
|
||||
import { useDesktopRemoteStore } from '@/stores/desktopRemoteStore';
|
||||
import { usePlaybackTargetStore } from '@/stores/playbackTargetStore';
|
||||
import { usePlayerStore } from '@/stores/playerStore';
|
||||
@@ -30,8 +31,6 @@ import { playHaptic } from '@/lib/haptics';
|
||||
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' },
|
||||
library: { label: 'Library', icon: 'musical-notes' },
|
||||
@@ -85,7 +84,10 @@ export function TabBar({ items, onPress }: TabBarProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => setSettledHomeFocused(homeFocused), TAB_TRANSITION_MS);
|
||||
const timer = setTimeout(
|
||||
() => setSettledHomeFocused(homeFocused),
|
||||
TAB_TRANSITION_SETTLE_MS
|
||||
);
|
||||
return () => clearTimeout(timer);
|
||||
}, [homeFocused, settledHomeFocused]);
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Bottom tabs use React Native's legacy native Animated driver. On Android,
|
||||
* timing animations are pre-sampled at 60 fps, so their positions repeat on a
|
||||
* 120 Hz display. A critically damped native spring is evaluated from each
|
||||
* display frame instead while preserving the current ~160 ms ease-out feel.
|
||||
*/
|
||||
export const TAB_TRANSITION_SETTLE_MS = 160;
|
||||
|
||||
export const TAB_TRANSITION_SPEC = {
|
||||
animation: 'spring',
|
||||
config: {
|
||||
stiffness: 2500,
|
||||
damping: 100,
|
||||
mass: 1,
|
||||
overshootClamping: true,
|
||||
restDisplacementThreshold: 0.004,
|
||||
restSpeedThreshold: 0.15,
|
||||
},
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user