import { unwrap } from '@pipu/core'; import { useQuery } from '@tanstack/react-query'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Pressable, ScrollView, Text, View } from 'react-native'; import { useSession, useUser } from '../../lib/session'; import { player } from '../../player/controller'; import { usePlayerState } from '../../player/hooks'; import { useColors } from '../../theme'; import { ErrorBox, Icon, Screen, useErrorText, type IconName } from '../../ui/kit'; import { OnboardingGate } from '../../ui/Onboarding'; import { WaveOrb } from '../../ui/wave/WaveOrb'; /** Имена из /wave/options (Material Symbols) → MaterialIcons. */ function iconFor(name: string): IconName { const map: Record = { rainy: 'umbrella', sentiment_satisfied: 'sentiment-satisfied' }; return (map[name] ?? name.replace(/_/g, '-')) as IconName; } /** * Главная: кнопка «Моя волна» (Skia, вид orb / liquid из профиля) и выбор занятия / настроения. * На Android спектр недоступен, поэтому пульс берётся из BPM и energy трека (docs/mobile.md). */ export default function HomeScreen() { return ; } function WaveHome() { const colors = useColors(); const { t } = useTranslation(); const tr = t as unknown as (k: string) => string; const session = useSession(); const user = useUser(); const st = usePlayerState(); const errorText = useErrorText(); const [error, setError] = useState(null); const [activity, setActivity] = useState(st.wave?.activity ?? null); const [mood, setMood] = useState(st.wave?.mood ?? null); const options = useQuery({ queryKey: ['wave-options'], queryFn: async () => unwrap(await session.client.GET('/api/v1/wave/options')), staleTime: Infinity, }); const waveOn = st.wave !== null; const playing = waveOn && st.playing; const run = (fn: () => Promise) => { setError(null); fn().catch(setError); }; const onWave = () => (waveOn ? player.toggle() : run(() => player.startWave(activity, mood))); const choose = (kind: 'activity' | 'mood', key: string) => { const a = kind === 'activity' ? (activity === key ? null : key) : activity; const m = kind === 'mood' ? (mood === key ? null : key) : mood; setActivity(a); setMood(m); if (waveOn) run(() => player.setWaveContext(a, m)); }; const ctx = [activity && tr(`wave.activities.${activity}`), mood && tr(`wave.moods.${mood}`)].filter(Boolean).join(' · ') || t('wave.for_you'); return ( {t('wave.title')} {ctx} {st.wave?.fallback && {t('wave.fallback')}} {error ? errorText(error) : null} {options.data && ( <> tr(`wave.activities.${k}`)} onPick={(k) => choose('activity', k)} /> tr(`wave.moods.${k}`)} onPick={(k) => choose('mood', k)} /> )} ); } function Chips({ title, options, value, label, onPick, }: { title: string; options: { key: string; icon: string }[]; value: string | null; label: (k: string) => string; onPick: (k: string) => void; }) { const colors = useColors(); return ( {title} {options.map((o) => { const on = value === o.key; return ( onPick(o.key)} accessibilityRole="button" accessibilityState={{ selected: on }} style={{ flexDirection: 'row', alignItems: 'center', gap: 6, height: 38, paddingHorizontal: 14, borderRadius: 999, backgroundColor: on ? colors['accent-fill'] : colors.surface2, borderWidth: 1, borderColor: on ? colors.accent : colors.line, }} > {label(o.key)} ); })} ); }