feat(search): add keyword highlighting to PostCard and improve QRCodeScanner
Add HighlightText component for search result highlighting in PostCard. Refactor QRCodeScanner to use async permission methods with better error handling. Improve SearchScreen with stable function refs and pass highlightKeyword to PostCard. Modernize MessageListScreen search UI with SearchBar and TabBar components. Add emoji virtualization to EmojiPanel and auto-focus input after inserting emoji. BREAKING CHANGE: EmojiPanel onFocusInput prop added for focus management
This commit is contained in:
@@ -20,7 +20,6 @@ import {
|
||||
RefreshControl,
|
||||
ActivityIndicator,
|
||||
Animated,
|
||||
TextInput,
|
||||
Keyboard,
|
||||
BackHandler,
|
||||
Platform,
|
||||
@@ -60,6 +59,7 @@ import { ConversationListRow } from './components/ConversationListRow';
|
||||
// 导入 NotificationsScreen 用于在内部显示
|
||||
import { NotificationsScreen } from './NotificationsScreen';
|
||||
// 导入扫码组件
|
||||
import { SearchBar, TabBar } from '../../components/business';
|
||||
import { QRCodeScanner } from '../../components/business/QRCodeScanner';
|
||||
|
||||
// 系统通知会话特殊ID
|
||||
@@ -673,49 +673,40 @@ export const MessageListScreen: React.FC = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
// 渲染搜索模式UI
|
||||
// 渲染搜索模式UI(扁平化现代化设计)
|
||||
const renderSearchMode = () => (
|
||||
<View style={styles.searchModeContainer}>
|
||||
<View style={styles.searchInputContainer}>
|
||||
<AppBackButton onPress={handleCloseSearch} iconColor={colors.text.secondary} />
|
||||
<TextInput
|
||||
style={styles.searchInput}
|
||||
placeholder={activeSearchTab === 'chat' ? '搜索聊天记录' : '搜索用户'}
|
||||
placeholderTextColor={colors.text.hint}
|
||||
value={searchText}
|
||||
onChangeText={setSearchText}
|
||||
autoFocus
|
||||
returnKeyType="search"
|
||||
// 确保光标可见
|
||||
cursorColor={colors.primary.main}
|
||||
selectionColor={`${colors.primary.main}40`}
|
||||
{/* 搜索头部 */}
|
||||
<View style={[styles.searchHeader, { paddingTop: Math.max(spacing.sm, insets.top + spacing.xs) }]}>
|
||||
<View style={styles.searchShell}>
|
||||
<SearchBar
|
||||
value={searchText}
|
||||
onChangeText={setSearchText}
|
||||
onSubmit={() => performSearch(searchText)}
|
||||
placeholder={activeSearchTab === 'chat' ? '搜索聊天记录' : '搜索用户'}
|
||||
autoFocus
|
||||
/>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={handleCloseSearch}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelText}>取消</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Tab切换 - 现代化风格 */}
|
||||
<View style={styles.searchTabContainer}>
|
||||
<TabBar
|
||||
tabs={['聊天记录', '用户']}
|
||||
activeIndex={activeSearchTab === 'chat' ? 0 : 1}
|
||||
onTabChange={(index) => setActiveSearchTab(index === 0 ? 'chat' : 'user')}
|
||||
variant="modern"
|
||||
/>
|
||||
{searchText.length > 0 && (
|
||||
<TouchableOpacity onPress={() => setSearchText('')}>
|
||||
<MaterialCommunityIcons name="close-circle" size={18} color={colors.text.hint} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.searchTabs}>
|
||||
<TouchableOpacity
|
||||
style={[styles.searchTab, activeSearchTab === 'chat' && styles.searchTabActive]}
|
||||
onPress={() => setActiveSearchTab('chat')}
|
||||
>
|
||||
<Text style={activeSearchTab === 'chat' ? styles.searchTabTextActive : styles.searchTabText}>
|
||||
聊天记录
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.searchTab, activeSearchTab === 'user' && styles.searchTabActive]}
|
||||
onPress={() => setActiveSearchTab('user')}
|
||||
>
|
||||
<Text style={activeSearchTab === 'user' ? styles.searchTabTextActive : styles.searchTabText}>
|
||||
用户
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
|
||||
{/* 搜索结果 */}
|
||||
<FlatList
|
||||
data={searchResults}
|
||||
renderItem={renderSearchResult}
|
||||
@@ -725,7 +716,10 @@ export const MessageListScreen: React.FC = () => {
|
||||
}
|
||||
return `user-${item.user?.id || index}`;
|
||||
}}
|
||||
contentContainerStyle={[styles.searchResultsContent, { paddingBottom: listBottomInset }]}
|
||||
contentContainerStyle={[
|
||||
styles.searchResultsContent,
|
||||
{ paddingBottom: listBottomInset },
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
scrollEnabled={true}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
@@ -757,14 +751,18 @@ export const MessageListScreen: React.FC = () => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.searchContainer, ...(isWideScreen ? [styles.searchContainerWide] : [])]}
|
||||
onPress={handleSearch}
|
||||
activeOpacity={0.7}
|
||||
<TouchableOpacity
|
||||
style={[styles.searchContainer, ...(isWideScreen ? [styles.searchContainerWide] : [])]}
|
||||
onPress={handleSearch}
|
||||
activeOpacity={0.85}
|
||||
>
|
||||
<View style={styles.searchBox}>
|
||||
<MaterialCommunityIcons name="magnify" size={18} color={colors.text.hint} />
|
||||
<Text style={styles.searchPlaceholder}>搜索</Text>
|
||||
<View pointerEvents="none">
|
||||
<SearchBar
|
||||
value=""
|
||||
onChangeText={() => {}}
|
||||
onSubmit={() => {}}
|
||||
placeholder="搜索"
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -1026,19 +1024,7 @@ function createMessageListStyles(colors: AppColors) {
|
||||
paddingBottom: spacing.sm,
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
searchBox: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.chat.surfaceInput,
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: spacing.sm,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
searchPlaceholder: {
|
||||
fontSize: 14,
|
||||
color: colors.text.hint,
|
||||
marginLeft: spacing.xs,
|
||||
},
|
||||
|
||||
listContent: {
|
||||
flexGrow: 1,
|
||||
backgroundColor: colors.background.default,
|
||||
@@ -1062,60 +1048,49 @@ function createMessageListStyles(colors: AppColors) {
|
||||
},
|
||||
searchModeContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background.default,
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
searchInputContainer: {
|
||||
searchHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.chat.surfaceInput,
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.background.paper,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: `${colors.divider}70`,
|
||||
paddingHorizontal: spacing.md,
|
||||
marginHorizontal: spacing.md,
|
||||
marginTop: spacing.md,
|
||||
height: 40,
|
||||
paddingBottom: spacing.sm,
|
||||
},
|
||||
searchInput: {
|
||||
searchShell: {
|
||||
flex: 1,
|
||||
fontSize: 15,
|
||||
color: colors.text.primary,
|
||||
marginLeft: spacing.md,
|
||||
},
|
||||
searchTabs: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
gap: spacing.sm,
|
||||
cancelButton: {
|
||||
paddingHorizontal: spacing.sm,
|
||||
paddingVertical: spacing.xs,
|
||||
marginLeft: spacing.sm,
|
||||
},
|
||||
searchTab: {
|
||||
flex: 1,
|
||||
paddingVertical: spacing.sm,
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.chat.surfaceInput,
|
||||
borderRadius: 8,
|
||||
cancelText: {
|
||||
fontSize: fontSizes.md,
|
||||
fontWeight: '600',
|
||||
color: colors.primary.main,
|
||||
},
|
||||
searchTabActive: {
|
||||
backgroundColor: colors.primary.main,
|
||||
},
|
||||
searchTabText: {
|
||||
fontSize: 14,
|
||||
color: colors.text.secondary,
|
||||
fontWeight: '500',
|
||||
},
|
||||
searchTabTextActive: {
|
||||
color: colors.primary.contrast,
|
||||
searchTabContainer: {
|
||||
backgroundColor: colors.background.paper,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: `${colors.divider}50`,
|
||||
},
|
||||
searchResultsContent: {
|
||||
flexGrow: 1,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingTop: spacing.sm,
|
||||
},
|
||||
searchResultItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: spacing.md,
|
||||
padding: spacing.md,
|
||||
backgroundColor: colors.background.paper,
|
||||
borderRadius: 10,
|
||||
borderRadius: borderRadius.lg,
|
||||
marginBottom: spacing.sm,
|
||||
paddingHorizontal: spacing.md,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: `${colors.divider}50`,
|
||||
},
|
||||
searchResultContent: {
|
||||
flex: 1,
|
||||
|
||||
Reference in New Issue
Block a user