Three related UX fixes on the client. 1. Participants count on profile DMs always have exactly two participants (you and the contact) so a "Участников: 1" row was confusing — either it's obviously the other person or it's wrong depending on how you count. Removed for direct conversations; the row still appears for group chats (and shows an em-dash until v2.1.0 gives groups a real member list). 2. Dev feed seed now activates on network / 404 errors The seed was only surfaced when the real API returned an EMPTY array. If the node was down (Network request failed) or the endpoint replied 404, the catch block quietly set posts to [] and the list stayed blank — defeating the point of the seed. Now both the empty- response path AND the network-error path fall back to getDevSeedFeed(), so scrolling / like-toggling works even without a running node. Also made the __DEV__ lookup more defensive: use `globalThis.__DEV__` at runtime instead of the typed global. Some bundler configurations have the TS type but not the runtime binding, or vice-versa — the runtime lookup always agrees with Metro. 3. Back from profile → previous screen instead of tab root Root cause: AnimatedSlot rendered <Slot>, which is stack-less. When /chats/xyz pushed /profile/abc (cross-group), the chats group unmounted. Hitting Back then re-entered chats at its root (/chats list) rather than /chats/xyz. Replaced <Slot> with <Stack> in AnimatedSlot. Tab switching still stays flat because NavBar uses router.replace (which maps to navigation.replace on the Stack — no history accumulation). Cross-group pushes (post author tap from feed, avatar tap from chat header, compose modal) now live in the outer Stack's history, so Back pops correctly to the caller. The nested Stacks (chats/_layout.tsx, feed/_layout.tsx, profile/_layout.tsx) still handle intra-group navigation as before. The PanResponder-based swipe-right-to-back was removed since the native Stack now provides iOS edge-swipe natively; Android uses the system back button. animation: 'none' keeps the visual swap instant — matches the prior Slot look so nothing flashes slide-animations that weren't there before. Sub-group layouts can opt into slide_from_right themselves (profile/_layout.tsx and feed/_layout.tsx already do). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
181 lines
6.9 KiB
TypeScript
181 lines
6.9 KiB
TypeScript
/**
|
||
* Dev-only mock posts for the feed.
|
||
*
|
||
* Why: in __DEV__ before any real posts exist on the node, the timeline/
|
||
* for-you/trending tabs come back empty. Empty state is fine visually but
|
||
* doesn't let you test scrolling, like animations, view-counter bumps,
|
||
* navigation to post detail, etc. This module injects a small set of
|
||
* synthetic posts so the UI has something to chew on.
|
||
*
|
||
* Gating:
|
||
* - Only active when __DEV__ === true (stripped from production builds).
|
||
* - Only surfaces when the REAL API returns an empty array. If the node
|
||
* is returning actual posts, we trust those and skip the mocks.
|
||
*
|
||
* These posts have made-up post_ids — tapping on them to open detail
|
||
* WILL 404 against the real backend. That's intentional — the mock is
|
||
* purely for scroll / tap-feedback testing.
|
||
*/
|
||
import type { FeedPostItem } from './feed';
|
||
|
||
// Fake hex-like pubkeys so Avatar's colour hash still looks varied.
|
||
function fakeAddr(seed: number): string {
|
||
const h = (seed * 2654435761).toString(16).padStart(8, '0');
|
||
return (h + h + h + h).slice(0, 64);
|
||
}
|
||
|
||
function fakePostID(n: number): string {
|
||
return `dev${String(n).padStart(29, '0')}`;
|
||
}
|
||
|
||
const NOW = Math.floor(Date.now() / 1000);
|
||
|
||
// Small curated pool of posts covering the render surface we care about:
|
||
// plain text, hashtag variety, different lengths, likes / views spread,
|
||
// reply/quote references, one with an attachment marker.
|
||
const SEED_POSTS: FeedPostItem[] = [
|
||
{
|
||
post_id: fakePostID(1),
|
||
author: fakeAddr(1),
|
||
content: 'Добро пожаловать в ленту DChain. Это #DEV-посты — они видны только пока реальная лента пустая.',
|
||
created_at: NOW - 60,
|
||
size: 200,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 127, likes: 42,
|
||
has_attachment: false,
|
||
hashtags: ['dev'],
|
||
},
|
||
{
|
||
post_id: fakePostID(2),
|
||
author: fakeAddr(2),
|
||
content: 'Пробую новую ленту #twitter-style. Лайки, просмотры, подписки — всё on-chain, тела постов — off-chain в mailbox релея.',
|
||
created_at: NOW - 540,
|
||
size: 310,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 89, likes: 23,
|
||
has_attachment: false,
|
||
hashtags: ['twitter'],
|
||
},
|
||
{
|
||
post_id: fakePostID(3),
|
||
author: fakeAddr(3),
|
||
content: 'Сжатие изображений — максимальное на клиенте (WebP Q=50 @1080p), плюс серверный EXIF-скраб через stdlib re-encode. GPS-координаты из EXIF больше никогда не утекают. #privacy',
|
||
created_at: NOW - 1200,
|
||
size: 420,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 312, likes: 78,
|
||
has_attachment: true,
|
||
hashtags: ['privacy'],
|
||
},
|
||
{
|
||
post_id: fakePostID(4),
|
||
author: fakeAddr(4),
|
||
content: 'Короткий пост.',
|
||
created_at: NOW - 3600,
|
||
size: 128,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 12, likes: 3,
|
||
has_attachment: false,
|
||
},
|
||
{
|
||
post_id: fakePostID(5),
|
||
author: fakeAddr(1),
|
||
content: 'Отвечаю сам себе — фича threads пока через reply_to только, без UI thread-виджета.',
|
||
created_at: NOW - 7200,
|
||
size: 220,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 45, likes: 11,
|
||
has_attachment: false,
|
||
reply_to: fakePostID(1),
|
||
},
|
||
{
|
||
post_id: fakePostID(6),
|
||
author: fakeAddr(5),
|
||
content: '#golang + #badgerdb + #libp2p = DChain бэкенд. Пять package в test suite, все зелёные.',
|
||
created_at: NOW - 10800,
|
||
size: 180,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 201, likes: 66,
|
||
has_attachment: false,
|
||
hashtags: ['golang', 'badgerdb', 'libp2p'],
|
||
},
|
||
{
|
||
post_id: fakePostID(7),
|
||
author: fakeAddr(6),
|
||
content: 'Feed-mailbox хранит тела постов до 30 дней (настраиваемо через DCHAIN_FEED_TTL_DAYS). Потом BadgerDB выселяет автоматически — chain-метаданные остаются навсегда.',
|
||
created_at: NOW - 14400,
|
||
size: 380,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 156, likes: 48,
|
||
has_attachment: false,
|
||
},
|
||
{
|
||
post_id: fakePostID(8),
|
||
author: fakeAddr(7),
|
||
content: 'Pricing: BasePostFee = 1000 µT (0.001 T) + 1 µT за каждый байт. Уходит владельцу релея, принявшего пост.',
|
||
created_at: NOW - 21600,
|
||
size: 250,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 78, likes: 22,
|
||
has_attachment: false,
|
||
},
|
||
{
|
||
post_id: fakePostID(9),
|
||
author: fakeAddr(8),
|
||
content: 'Twitter-like, но без миллиардов долларов на инфраструктуру — каждый оператор ноды платит за свой кусок хостинга и зарабатывает на публикациях. #decentralised #messaging',
|
||
created_at: NOW - 43200,
|
||
size: 340,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 412, likes: 103,
|
||
has_attachment: false,
|
||
hashtags: ['decentralised', 'messaging'],
|
||
},
|
||
{
|
||
post_id: fakePostID(10),
|
||
author: fakeAddr(9),
|
||
content: 'Короче. Лайк = on-chain tx с fee 1000 µT. Дорого для спама, дёшево для реального лайка. Пока без батчинга, но в плане. #design',
|
||
created_at: NOW - 64800,
|
||
size: 200,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 92, likes: 29,
|
||
has_attachment: false,
|
||
hashtags: ['design'],
|
||
},
|
||
{
|
||
post_id: fakePostID(11),
|
||
author: fakeAddr(2),
|
||
content: 'Follow граф на chain: двусторонний индекс (forward + inbound), так что Followers() и Following() — оба O(M).',
|
||
created_at: NOW - 86400 - 1000,
|
||
size: 230,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 61, likes: 14,
|
||
has_attachment: false,
|
||
},
|
||
{
|
||
post_id: fakePostID(12),
|
||
author: fakeAddr(10),
|
||
content: 'Рекомендации (For You): берём последние 48ч постов, фильтруем подписки + уже лайкнутые + свои, ранжируем по likes × 3 + views. Версия 1 — будет умнее. #recsys',
|
||
created_at: NOW - 129600,
|
||
size: 290,
|
||
hosting_relay: fakeAddr(100),
|
||
views: 189, likes: 58,
|
||
has_attachment: false,
|
||
hashtags: ['recsys'],
|
||
},
|
||
];
|
||
|
||
/**
|
||
* Returns the dev-seed post list. Only returns actual items in dev
|
||
* builds; release bundles return an empty array so fake posts never
|
||
* appear in production.
|
||
*
|
||
* We use the runtime `globalThis.__DEV__` lookup rather than the typed
|
||
* `__DEV__` global because some builds can have the TS typing
|
||
* out-of-sync with the actual injected value.
|
||
*/
|
||
export function getDevSeedFeed(): FeedPostItem[] {
|
||
const g = globalThis as unknown as { __DEV__?: boolean };
|
||
if (g.__DEV__ !== true) return [];
|
||
return SEED_POSTS;
|
||
}
|