2026-05-05 18:17:09 +05:00
|
|
|
import { useEffect } from 'react';
|
2026-05-08 02:54:57 +05:00
|
|
|
import { View, StyleSheet, ActivityIndicator, Linking } from 'react-native';
|
2026-05-05 18:17:09 +05:00
|
|
|
import { Tabs, router } from 'expo-router';
|
|
|
|
|
import { BlurView } from 'expo-blur';
|
|
|
|
|
import Svg, { Path, Circle, Line, Polyline } from 'react-native-svg';
|
|
|
|
|
import { Colors, TAB_BAR_HEIGHT } from '../../constants/theme';
|
2026-05-08 02:54:57 +05:00
|
|
|
import { useAuth, usePlayer, useLikes, useHidden, usePlaylists } from '../../store';
|
2026-05-05 18:17:09 +05:00
|
|
|
import { useAudio } from '../../hooks/useAudio';
|
|
|
|
|
import MiniPlayer from '../../components/MiniPlayer';
|
|
|
|
|
import Player from '../../components/Player';
|
|
|
|
|
|
|
|
|
|
export default function TabsLayout() {
|
|
|
|
|
const { user, ready } = useAuth();
|
|
|
|
|
const { expanded } = usePlayer();
|
|
|
|
|
const { loadLikes } = useLikes();
|
2026-05-08 02:54:57 +05:00
|
|
|
const { loadHidden } = useHidden();
|
|
|
|
|
const { loadPlaylists } = usePlaylists();
|
2026-05-05 18:17:09 +05:00
|
|
|
useAudio(); // single audio controller for the whole app
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (ready && !user) router.replace('/auth');
|
|
|
|
|
}, [user, ready]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-05-08 02:54:57 +05:00
|
|
|
if (user) { loadLikes(); loadHidden(); loadPlaylists(); }
|
2026-05-05 18:17:09 +05:00
|
|
|
}, [user]);
|
|
|
|
|
|
2026-05-08 02:54:57 +05:00
|
|
|
// Handle deep link: burmalda://listen?tid=123&ts=45
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handleUrl = (event: { url: string }) => {
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(event.url);
|
|
|
|
|
if (url.pathname === '//listen' || url.host === 'listen') {
|
|
|
|
|
const tid = Number(url.searchParams.get('tid'));
|
|
|
|
|
const ts = Number(url.searchParams.get('ts') ?? '0');
|
|
|
|
|
if (!tid) return;
|
|
|
|
|
const { queue } = usePlayer.getState();
|
|
|
|
|
const trackIdx = queue.findIndex(t => t.id === tid);
|
|
|
|
|
if (trackIdx >= 0) {
|
|
|
|
|
usePlayer.getState().setIndex(trackIdx);
|
|
|
|
|
usePlayer.getState().setExpanded(true);
|
|
|
|
|
setTimeout(() => { import('../../hooks/useAudio').then(m => m.seekTo(ts)); }, 500);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {}
|
|
|
|
|
};
|
|
|
|
|
const sub = Linking.addEventListener('url', handleUrl);
|
|
|
|
|
Linking.getInitialURL().then(url => { if (url) handleUrl({ url }); });
|
|
|
|
|
return () => sub.remove();
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-05-05 18:17:09 +05:00
|
|
|
if (!ready) return (
|
|
|
|
|
<View style={{ flex: 1, backgroundColor: Colors.bg, alignItems: 'center', justifyContent: 'center' }}>
|
|
|
|
|
<ActivityIndicator color={Colors.accent} />
|
|
|
|
|
</View>
|
|
|
|
|
);
|
|
|
|
|
if (!user) return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<View style={{ flex: 1, backgroundColor: Colors.bg }}>
|
|
|
|
|
<Tabs
|
|
|
|
|
screenOptions={{
|
|
|
|
|
headerShown: false,
|
|
|
|
|
tabBarStyle: styles.tabBar,
|
|
|
|
|
tabBarBackground: () => (
|
|
|
|
|
<BlurView intensity={30} tint="dark" style={StyleSheet.absoluteFill} />
|
|
|
|
|
),
|
|
|
|
|
tabBarActiveTintColor: Colors.text,
|
|
|
|
|
tabBarInactiveTintColor: Colors.text3,
|
|
|
|
|
tabBarLabelStyle: { fontSize: 9, fontWeight: '500', letterSpacing: 0.3, marginBottom: 4 },
|
|
|
|
|
tabBarItemStyle: { paddingTop: 6 },
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Tabs.Screen name="index" options={{ title: 'Главная', tabBarIcon: ({ color }) => <HomeIcon color={color} /> }} />
|
|
|
|
|
<Tabs.Screen name="library" options={{ title: 'Библиотека', tabBarIcon: ({ color }) => <LibIcon color={color} /> }} />
|
|
|
|
|
<Tabs.Screen name="search" options={{ title: 'Поиск', tabBarIcon: ({ color }) => <SearchIcon color={color} /> }} />
|
|
|
|
|
<Tabs.Screen name="liked" options={{ title: 'Понравилось', tabBarIcon: ({ color }) => <HeartIcon color={color} /> }} />
|
|
|
|
|
<Tabs.Screen name="profile" options={{ title: 'Профиль', tabBarIcon: ({ color }) => <ProfileIcon color={color} /> }} />
|
|
|
|
|
</Tabs>
|
|
|
|
|
|
|
|
|
|
<MiniPlayer />
|
|
|
|
|
{expanded && <Player />}
|
|
|
|
|
</View>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function HomeIcon({ color }: { color: string }) {
|
|
|
|
|
return (
|
|
|
|
|
<Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
|
|
|
|
|
<Path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" stroke={color} strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" />
|
|
|
|
|
<Polyline points="9,22 9,12 15,12 15,22" stroke={color} strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" />
|
|
|
|
|
</Svg>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
function LibIcon({ color }: { color: string }) {
|
|
|
|
|
return (
|
|
|
|
|
<Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
|
|
|
|
|
<Path d="M9 18V5l12-2v13" stroke={color} strokeWidth={1.8} strokeLinecap="round" />
|
|
|
|
|
<Circle cx={6} cy={18} r={3} stroke={color} strokeWidth={1.8} />
|
|
|
|
|
<Circle cx={18} cy={16} r={3} stroke={color} strokeWidth={1.8} />
|
|
|
|
|
</Svg>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
function SearchIcon({ color }: { color: string }) {
|
|
|
|
|
return (
|
|
|
|
|
<Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
|
|
|
|
|
<Circle cx={11} cy={11} r={8} stroke={color} strokeWidth={1.8} />
|
|
|
|
|
<Line x1="21" y1="21" x2="16.65" y2="16.65" stroke={color} strokeWidth={1.8} strokeLinecap="round" />
|
|
|
|
|
</Svg>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
function HeartIcon({ color }: { color: string }) {
|
|
|
|
|
return (
|
|
|
|
|
<Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
|
|
|
|
|
<Path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" stroke={color} strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" />
|
|
|
|
|
</Svg>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
function ProfileIcon({ color }: { color: string }) {
|
|
|
|
|
return (
|
|
|
|
|
<Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
|
|
|
|
|
<Path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" stroke={color} strokeWidth={1.8} strokeLinecap="round" />
|
|
|
|
|
<Circle cx={12} cy={7} r={4} stroke={color} strokeWidth={1.8} />
|
|
|
|
|
</Svg>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
|
|
|
|
tabBar: {
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
|
|
|
borderTopColor: 'rgba(255,255,255,0.08)',
|
|
|
|
|
backgroundColor: 'transparent',
|
|
|
|
|
elevation: 0,
|
|
|
|
|
height: TAB_BAR_HEIGHT,
|
|
|
|
|
},
|
|
|
|
|
});
|