refactor(ui): redesign SearchBar with compact mode and add home screen tabs
Some checks failed
Frontend CI / build-and-push-web (push) Failing after 3m13s
Frontend CI / build-android-apk (push) Failing after 9m39s
Frontend CI / ota-android (push) Successful in 10m31s

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:
lafay
2026-04-26 17:13:43 +08:00
parent 5815f1a5f7
commit 6cbd2092ac
17 changed files with 2341 additions and 64 deletions

View File

@@ -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}