fix(client): profile polish + proper back stack + dev feed seed
Profile screen
- Missing safe-area top inset on the header — fixed by wrapping the
outer View in paddingTop: insets.top (matches the rest of the tab
screens).
- "Чат" button icon + text sat on two lines because the button used
column layout by default. Rewritten as a full-width CTA pill with
flexDirection: row and alignItems: center → chat-bubble icon and
label sit on one line.
- Dedicated "Копировать адрес" button removed. The address row in
the info card is now the tap target: pressing it copies to clipboard
and flips the row to "Скопировано" with a green check for 1.8s.
- Posts tab removed entirely. User's right — a "chat profile" has no
posts concept, just participant count + date + encryption. The
profile screen is now a single-view info card (address, encryption
status, added date, participants). Posts are discoverable via the
Feed tab; a dedicated /feed/author/{pub} screen is on the roadmap
for browsing a specific user's timeline.
Back-stack navigation
- app/(app)/profile/_layout.tsx + app/(app)/feed/_layout.tsx added,
each with a native <Stack>. The outer AnimatedSlot is stack-less
(intentional — it animates tab switches), so without these nested
stacks `router.back()` from a profile screen had no history to pop
and fell through to root. Now tapping an author in the feed → back
returns to feed; opening a profile from chat header → back returns
to the chat.
Dev feed seed
- lib/devSeedFeed.ts: 12 synthetic posts (text-only, mix of hashtags,
one with has_attachment, varying likes/views, timestamps from "1m
ago" to "36h ago"). Exposed via getDevSeedFeed() — stripped from
production via __DEV__ gate.
- Feed screen falls back to the dev seed only when the real API
returned zero posts. Gives something to scroll / tap / like-toggle
before the backend has real content; real data takes over as soon
as it arrives.
Note: tapping a mock post into detail will 404 against the real node
(the post_ids don't exist on-chain). Intentional — the seed is for
scroll + interaction feel, not deep linking.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
181
client-app/lib/devSeedFeed.ts
Normal file
181
client-app/lib/devSeedFeed.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* 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'],
|
||||
},
|
||||
];
|
||||
|
||||
/** True when the current build is a Metro dev bundle. __DEV__ is a
|
||||
* global injected by Metro at bundle time and typed via react-native's
|
||||
* ambient declarations, so no ts-ignore is needed. */
|
||||
function isDev(): boolean {
|
||||
return typeof __DEV__ !== 'undefined' && __DEV__ === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dev-seed post list (only in __DEV__). Called by the Feed
|
||||
* screen as a fallback when the real API returned an empty list.
|
||||
*/
|
||||
export function getDevSeedFeed(): FeedPostItem[] {
|
||||
if (!isDev()) return [];
|
||||
return SEED_POSTS;
|
||||
}
|
||||
Reference in New Issue
Block a user