refactor(ui): redesign SearchBar with compact mode and add home screen tabs
Refactor SearchBar component to support compact mode with transparent styling, reduced padding, and smaller icons. Add home screen tab navigation for switching between home feed and market view with animated tab indicator. Also adjust PostDetailScreen action button sizing and CommentItem margins for better spacing.
This commit is contained in:
@@ -37,12 +37,22 @@ import { useDifferentialPosts } from '../../hooks/useDifferentialPosts';
|
||||
import { postSyncService } from '@/services/post';
|
||||
import { SearchScreen } from './SearchScreen';
|
||||
import { CreatePostScreen } from '../create/CreatePostScreen';
|
||||
import { MarketView } from './MarketView';
|
||||
import { CreateTradeScreen } from '../trade/CreateTradeScreen';
|
||||
import type { TradeCategory } from '../../types/trade';
|
||||
import { ALL_TRADE_CATEGORIES } from '../../types/trade';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
import { blurActiveElement } from '../../infrastructure/platform';
|
||||
|
||||
const SORT_OPTIONS = ['关注', '推荐', '最新'];
|
||||
const SORT_ICONS = ['account-heart', 'fire', 'clock-outline'];
|
||||
const SORT_TYPES: PostType[] = ['follow', 'hot', 'latest'];
|
||||
|
||||
const MARKET_FILTER_OPTIONS = [
|
||||
{ key: 'all' as const, label: '全部', icon: 'view-grid-outline' },
|
||||
{ key: 'sell' as const, label: '出售', icon: 'tag-outline' },
|
||||
{ key: 'buy' as const, label: '收购', icon: 'hand-coin-outline' },
|
||||
];
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
const MOBILE_TAB_BAR_HEIGHT = 64;
|
||||
const MOBILE_TAB_FLOATING_MARGIN = 12;
|
||||
@@ -56,7 +66,7 @@ type ViewMode = 'list' | 'grid';
|
||||
type PostType = 'follow' | 'hot' | 'latest';
|
||||
type LatestCapsule = { id: string; name: string };
|
||||
|
||||
function createHomeStyles(colors: AppColors) {
|
||||
function createHomeStyles(colors: AppColors, responsivePadding: number) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
@@ -66,13 +76,45 @@ function createHomeStyles(colors: AppColors) {
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
searchWrapper: {
|
||||
paddingTop: spacing.lg,
|
||||
paddingTop: spacing.sm,
|
||||
paddingBottom: spacing.xs,
|
||||
shadowColor: 'transparent',
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: 0,
|
||||
shadowRadius: 0,
|
||||
elevation: 0,
|
||||
},
|
||||
homeTabSwitcher: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: spacing.xs,
|
||||
marginBottom: spacing.xs,
|
||||
paddingHorizontal: responsivePadding + 4,
|
||||
},
|
||||
homeTabSwitcherLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 20,
|
||||
},
|
||||
homeTabSwitcherRight: {
|
||||
flex: 1,
|
||||
marginLeft: 16,
|
||||
},
|
||||
homeTabItem: {
|
||||
paddingVertical: spacing.sm,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderBottomWidth: 2,
|
||||
borderBottomColor: 'transparent',
|
||||
},
|
||||
homeTabItemActive: {
|
||||
borderBottomColor: colors.text.primary,
|
||||
},
|
||||
homeTabText: {
|
||||
fontSize: 20,
|
||||
fontWeight: '400',
|
||||
color: colors.text.hint,
|
||||
},
|
||||
homeTabTextActive: {
|
||||
fontSize: 20,
|
||||
fontWeight: '700',
|
||||
color: colors.text.primary,
|
||||
},
|
||||
homeTabBar: {
|
||||
marginTop: spacing.sm,
|
||||
@@ -89,7 +131,6 @@ function createHomeStyles(colors: AppColors) {
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.xs,
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
sortBarCapsuleScroll: {
|
||||
flex: 1,
|
||||
@@ -182,21 +223,22 @@ export const HomeScreen: React.FC = () => {
|
||||
const hasFetchedVerification = useVerificationStore((s) => s.hasFetchedStatus);
|
||||
const colors = useAppColors();
|
||||
const resolvedScheme = useResolvedColorScheme();
|
||||
const styles = useMemo(() => createHomeStyles(colors), [colors]);
|
||||
const statusBarStyle = resolvedScheme === 'dark' ? 'light-content' : 'dark-content';
|
||||
|
||||
// 使用响应式 hook
|
||||
const {
|
||||
width,
|
||||
isMobile,
|
||||
isTablet,
|
||||
isDesktop,
|
||||
const {
|
||||
width,
|
||||
isMobile,
|
||||
isTablet,
|
||||
isDesktop,
|
||||
isWideScreen,
|
||||
} = useResponsive();
|
||||
|
||||
|
||||
// 响应式间距
|
||||
const responsiveGap = useResponsiveSpacing({ xs: 4, sm: 6, md: 8, lg: 12, xl: 16 });
|
||||
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
|
||||
|
||||
const styles = useMemo(() => createHomeStyles(colors, responsivePadding), [colors, responsivePadding]);
|
||||
const statusBarStyle = resolvedScheme === 'dark' ? 'light-content' : 'dark-content';
|
||||
|
||||
const [sortIndex, setSortIndex] = useState(0);
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('list');
|
||||
@@ -214,6 +256,14 @@ export const HomeScreen: React.FC = () => {
|
||||
// 发帖弹窗状态
|
||||
const [showCreatePost, setShowCreatePost] = useState(false);
|
||||
|
||||
// 广场/市集 Tab
|
||||
const [homeTab, setHomeTab] = useState<'square' | 'market'>('square');
|
||||
const [showCreateTrade, setShowCreateTrade] = useState(false);
|
||||
|
||||
// 市集筛选状态
|
||||
const [marketFilterType, setMarketFilterType] = useState<'all' | 'sell' | 'buy'>('all');
|
||||
const [marketCategory, setMarketCategory] = useState<TradeCategory | 'all'>('all');
|
||||
|
||||
// 分享面板状态
|
||||
const [showShareSheet, setShowShareSheet] = useState(false);
|
||||
const [sharePost, setSharePost] = useState<Post | null>(null);
|
||||
@@ -530,6 +580,13 @@ export const HomeScreen: React.FC = () => {
|
||||
setSortIndex(nextIndex);
|
||||
}, [sortIndex]);
|
||||
|
||||
const cycleMarketFilter = useCallback(() => {
|
||||
setMarketFilterType(prev => {
|
||||
const idx = MARKET_FILTER_OPTIONS.findIndex(f => f.key === prev);
|
||||
return MARKET_FILTER_OPTIONS[(idx + 1) % MARKET_FILTER_OPTIONS.length].key;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadChannels = async () => {
|
||||
const list = await channelService.list();
|
||||
@@ -856,18 +913,42 @@ export const HomeScreen: React.FC = () => {
|
||||
|
||||
{/* 顶部Header */}
|
||||
<View style={styles.header}>
|
||||
{/* 搜索栏 */}
|
||||
<View style={[styles.searchWrapper, { paddingHorizontal: responsivePadding }]}>
|
||||
<SearchBar
|
||||
value=""
|
||||
onChangeText={() => {}}
|
||||
onSubmit={handleSearchPress}
|
||||
onFocus={handleSearchPress}
|
||||
placeholder="搜索帖子、用户"
|
||||
/>
|
||||
{/* 广场/市集切换 */}
|
||||
<View style={styles.homeTabSwitcher}>
|
||||
<View style={styles.homeTabSwitcherLeft}>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
style={[styles.homeTabItem, homeTab === 'square' && styles.homeTabItemActive]}
|
||||
onPress={() => setHomeTab('square')}
|
||||
>
|
||||
<Text style={homeTab === 'square' ? styles.homeTabTextActive : styles.homeTabText}>
|
||||
广场
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
style={[styles.homeTabItem, homeTab === 'market' && styles.homeTabItemActive]}
|
||||
onPress={() => setHomeTab('market')}
|
||||
>
|
||||
<Text style={homeTab === 'market' ? styles.homeTabTextActive : styles.homeTabText}>
|
||||
市集
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.homeTabSwitcherRight}>
|
||||
<SearchBar
|
||||
value=""
|
||||
onChangeText={() => {}}
|
||||
onSubmit={handleSearchPress}
|
||||
onFocus={handleSearchPress}
|
||||
placeholder="搜索"
|
||||
compact
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 排序筛选 + 频道 */}
|
||||
|
||||
{/* 排序筛选 + 频道(仅广场模式显示) */}
|
||||
{homeTab === 'square' && (
|
||||
<View style={styles.sortBar}>
|
||||
<TouchableOpacity onPress={toggleViewMode} style={styles.viewToggleBtn}>
|
||||
<MaterialCommunityIcons
|
||||
@@ -894,9 +975,56 @@ export const HomeScreen: React.FC = () => {
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 市集分类 + 筛选(仅市集模式显示) */}
|
||||
{homeTab === 'market' && (
|
||||
<View style={styles.sortBar}>
|
||||
<FlashList
|
||||
horizontal
|
||||
data={[{ key: 'all', label: '全部' }, ...ALL_TRADE_CATEGORIES]}
|
||||
renderItem={({ item }: { item: { key: string; label: string } }) => (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
style={[
|
||||
styles.capsuleItem,
|
||||
marketCategory === item.key && { backgroundColor: `${colors.primary.main}18` },
|
||||
]}
|
||||
onPress={() => setMarketCategory(item.key as TradeCategory | 'all')}
|
||||
>
|
||||
<Text style={[
|
||||
styles.capsuleText,
|
||||
marketCategory === item.key ? styles.capsuleTextActive : {},
|
||||
]}>
|
||||
{item.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
keyExtractor={(item: { key: string; label: string }) => item.key}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.sortBarCapsuleContent}
|
||||
style={styles.sortBarCapsuleScroll}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={cycleMarketFilter}
|
||||
style={styles.sortTrigger}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name={(MARKET_FILTER_OPTIONS.find(f => f.key === marketFilterType)?.icon || 'view-grid-outline') as any}
|
||||
size={18}
|
||||
color={colors.text.secondary}
|
||||
/>
|
||||
<Text style={styles.sortTriggerText}>
|
||||
{MARKET_FILTER_OPTIONS.find(f => f.key === marketFilterType)?.label || '全部'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 帖子列表 */}
|
||||
{/* 内容区域 */}
|
||||
{homeTab === 'square' ? (
|
||||
<View style={styles.contentContainer}>
|
||||
{isInitialLoading ? (
|
||||
<Loading fullScreen />
|
||||
@@ -906,8 +1034,17 @@ export const HomeScreen: React.FC = () => {
|
||||
renderResponsiveGrid()
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<MarketView
|
||||
onCreateTrade={() => setShowCreateTrade(true)}
|
||||
filterType={marketFilterType}
|
||||
onFilterTypeChange={setMarketFilterType}
|
||||
selectedCategory={marketCategory}
|
||||
onSelectedCategoryChange={setMarketCategory}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 漂浮发帖按钮 */}
|
||||
{/* 漂浮发帖/发布按钮 */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.floatingButton,
|
||||
@@ -915,7 +1052,11 @@ export const HomeScreen: React.FC = () => {
|
||||
isWideScreen && styles.floatingButtonWide,
|
||||
floatingButtonBottom !== undefined && { bottom: floatingButtonBottom },
|
||||
]}
|
||||
onPress={handleCreatePost}
|
||||
onPress={homeTab === 'market' ? () => {
|
||||
if (!isAuthenticated) { router.push('/login'); return; }
|
||||
if (!isVerified) { router.push(hrefs.hrefVerificationGuide()); return; }
|
||||
setShowCreateTrade(true);
|
||||
} : handleCreatePost}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<MaterialCommunityIcons name="plus" size={28} color={colors.text.inverse} />
|
||||
@@ -945,6 +1086,18 @@ export const HomeScreen: React.FC = () => {
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
{/* 发布交易弹窗 */}
|
||||
<Modal
|
||||
visible={showCreateTrade}
|
||||
animationType="slide"
|
||||
presentationStyle="pageSheet"
|
||||
onRequestClose={() => setShowCreateTrade(false)}
|
||||
>
|
||||
<CreateTradeScreen
|
||||
onClose={() => setShowCreateTrade(false)}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
{/* 分享面板 */}
|
||||
<ShareSheet
|
||||
visible={showShareSheet}
|
||||
|
||||
Reference in New Issue
Block a user