/** * Main app layout — кастомный `` + ``. * * AnimatedSlot — обёртка над Slot'ом, анимирующая переход при смене * pathname'а. Направление анимации вычисляется по TAB_ORDER: если * целевой tab "справа" — слайд из правой стороны, "слева" — из левой. * * Intra-tab навигация (chats/index → chats/[id]) обслуживается вложенным * Stack'ом в chats/_layout.tsx — там остаётся нативная slide-from-right * анимация, чтобы chat detail "выезжал" поверх списка. * * Side-effects (balance, contacts, WS auth, dev seed) — монтируются здесь * один раз; переходы между tab'ами их не перезапускают. */ import React, { useEffect } from 'react'; import { View } from 'react-native'; import { router, usePathname } from 'expo-router'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useStore } from '@/lib/store'; import { useBalance } from '@/hooks/useBalance'; import { useContacts } from '@/hooks/useContacts'; import { useWellKnownContracts } from '@/hooks/useWellKnownContracts'; import { useNotifications } from '@/hooks/useNotifications'; import { useGlobalInbox } from '@/hooks/useGlobalInbox'; import { getWSClient } from '@/lib/ws'; import { useDevSeed } from '@/lib/devSeed'; import { NavBar } from '@/components/NavBar'; import { AnimatedSlot } from '@/components/AnimatedSlot'; export default function AppLayout() { const keyFile = useStore(s => s.keyFile); const requests = useStore(s => s.requests); const insets = useSafeAreaInsets(); const pathname = usePathname(); // NavBar прячется на full-screen экранах: // - chat detail // - compose (new post modal) // - feed sub-routes (post detail, hashtag search) const hideNav = /^\/chats\/[^/]+/.test(pathname) || pathname === '/compose' || /^\/feed\/.+/.test(pathname); useBalance(); useContacts(); useWellKnownContracts(); useDevSeed(); useNotifications(); // permission + tap-handler useGlobalInbox(); // global inbox listener → notifications on new peer msg useEffect(() => { const ws = getWSClient(); if (keyFile) ws.setAuthCreds({ pubKey: keyFile.pub_key, privKey: keyFile.priv_key }); else ws.setAuthCreds(null); }, [keyFile]); useEffect(() => { if (keyFile === null) { const t = setTimeout(() => { if (!useStore.getState().keyFile) router.replace('/'); }, 300); return () => clearTimeout(t); } }, [keyFile]); return ( {!hideNav && ( )} ); }