Two dev-only seed modules removed now that the app talks to a real
backend:
- lib/devSeed.ts — fake 15+ contacts with mock chat histories,
mounted via useDevSeed() in (app)/_layout.tsx on empty store.
Was useful during client-first development; now it fights real
contact sync and confuses operators bringing up fresh nodes
("why do I see NBA scores and a dchain_updates channel in my
chat list?").
- lib/devSeedFeed.ts — 12 synthetic feed posts surfaced when the
real API returned empty. Same reasoning: operator imports genesis
key on a fresh node, opens Feed, sees 12 mock posts that aren't on
their chain. "Test data" that looks real is worse than an honest
empty state.
Feed screen now shows its proper empty state ("Пока нет
рекомендаций", etc.) when the API returns zero items OR on network
error. Chat screen starts empty until real contacts + messages
arrive via WS / storage cache.
Also cleaned a stale comment in chats/[id].tsx that referenced
devSeed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
/**
|
||
* Main app layout — кастомный `<AnimatedSlot>` + `<NavBar>`.
|
||
*
|
||
* 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 { 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();
|
||
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 (
|
||
<View style={{ flex: 1, backgroundColor: '#000000' }}>
|
||
<View style={{ flex: 1 }}>
|
||
<AnimatedSlot />
|
||
</View>
|
||
{!hideNav && (
|
||
<NavBar
|
||
bottomInset={insets.bottom}
|
||
requestCount={requests.length}
|
||
notifCount={0}
|
||
/>
|
||
)}
|
||
</View>
|
||
);
|
||
}
|