fix(feed): visible post divider + reliable FAB positioning
- Post divider was on each PostCard's outer Pressable as borderBottom (#222), which was barely visible on OLED black and disappeared entirely in pressed state (the pressed bg ate the line). Moved the seam to a dedicated PostSeparator component (1px, #2a2a2a) wired as FlatList's ItemSeparatorComponent on both /feed (timeline / for-you / trending) and /feed/tag/[tag]. Also bumped inter-card vertical padding (14-16 top / 16-20 bottom) so cards have real breathing room even before the divider. - FAB position was flaky: with <Stack> at the (app) level the overlay could end up positioned against the Stack's card view instead of the tab container, which made the button drift around and stick against unexpected edges. Wrapped it in an absoluteFill container with pointerEvents="box-none" — the wrapper owns positioning against the tab screen, the button inside just declares right: 14 / bottom: N. Bumped bottom offset to `max(insets.bottom, 8) + 70` so the FAB always clears the 5-icon NavBar with ~14px visual gap on every device. Shadow switched from blue-cast to standard dark for better depth perception on dark backgrounds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { router } from 'expo-router';
|
||||
|
||||
import { TabHeader } from '@/components/TabHeader';
|
||||
import { PostCard } from '@/components/feed/PostCard';
|
||||
import { PostCard, PostSeparator } from '@/components/feed/PostCard';
|
||||
import { useStore } from '@/lib/store';
|
||||
import {
|
||||
fetchTimeline, fetchForYou, fetchTrending, fetchStats, bumpView,
|
||||
@@ -218,6 +218,7 @@ export default function FeedScreen() {
|
||||
onDeleted={onDeleted}
|
||||
/>
|
||||
)}
|
||||
ItemSeparatorComponent={PostSeparator}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
@@ -250,29 +251,43 @@ export default function FeedScreen() {
|
||||
contentContainerStyle={posts.length === 0 ? { flexGrow: 1 } : undefined}
|
||||
/>
|
||||
|
||||
{/* Floating compose button — pinned to the bottom-right corner
|
||||
with 14px side inset. Vertical offset clears the 5-icon NavBar
|
||||
(which lives below this view in the same layer) by sitting
|
||||
~14px above its top edge. */}
|
||||
<Pressable
|
||||
onPress={() => router.push('/(app)/compose' as never)}
|
||||
style={({ pressed }) => ({
|
||||
{/* Floating compose button.
|
||||
*
|
||||
* Wrapped in a StyleSheet.absoluteFill container with pointerEvents
|
||||
* "box-none" so only the FAB captures touches — taps anywhere else
|
||||
* pass through to the FlatList below.
|
||||
*
|
||||
* Inside the wrapper, alignSelf: 'flex-end' pins to the right;
|
||||
* bottom inset leaves ~14px clearance above the NavBar (≈56px tall
|
||||
* + safe-area-bottom). Explicit `right: 14` is belt-and-braces
|
||||
* for RTL / platform quirks where alignSelf alone might not pin. */}
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 14,
|
||||
bottom: Math.max(insets.bottom, 0) + 62,
|
||||
width: 56, height: 56,
|
||||
borderRadius: 28,
|
||||
backgroundColor: pressed ? '#1a8cd8' : '#1d9bf0',
|
||||
alignItems: 'center', justifyContent: 'center',
|
||||
shadowColor: '#1d9bf0',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.4,
|
||||
shadowRadius: 8,
|
||||
elevation: 6,
|
||||
})}
|
||||
left: 0, right: 0, top: 0, bottom: 0,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="create-outline" size={24} color="#ffffff" />
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={() => router.push('/(app)/compose' as never)}
|
||||
style={({ pressed }) => ({
|
||||
position: 'absolute',
|
||||
right: 14,
|
||||
bottom: Math.max(insets.bottom, 8) + 70,
|
||||
width: 56, height: 56,
|
||||
borderRadius: 28,
|
||||
backgroundColor: pressed ? '#1a8cd8' : '#1d9bf0',
|
||||
alignItems: 'center', justifyContent: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.5,
|
||||
shadowRadius: 6,
|
||||
elevation: 8,
|
||||
})}
|
||||
>
|
||||
<Ionicons name="create-outline" size={24} color="#ffffff" />
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import { Header } from '@/components/Header';
|
||||
import { IconButton } from '@/components/IconButton';
|
||||
import { PostCard } from '@/components/feed/PostCard';
|
||||
import { PostCard, PostSeparator } from '@/components/feed/PostCard';
|
||||
import { useStore } from '@/lib/store';
|
||||
import { fetchHashtag, fetchStats, type FeedPostItem } from '@/lib/feed';
|
||||
|
||||
@@ -94,6 +94,7 @@ export default function HashtagScreen() {
|
||||
onStatsChanged={onStatsChanged}
|
||||
/>
|
||||
)}
|
||||
ItemSeparatorComponent={PostSeparator}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
|
||||
Reference in New Issue
Block a user