feat(search): add keyword highlighting to PostCard and improve QRCodeScanner
Some checks failed
Frontend CI / build-and-push-web (push) Successful in 4m39s
Frontend CI / ota-android (push) Successful in 11m22s
Frontend CI / build-android-apk (push) Failing after 14m58s

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:
lafay
2026-04-21 12:31:51 +08:00
parent b16759a147
commit 30c493c88f
9 changed files with 243 additions and 134 deletions

View File

@@ -0,0 +1,54 @@
import React, { useMemo } from 'react';
import { Text, TextStyle, StyleProp } from 'react-native';
interface HighlightTextProps {
text: string;
keyword: string;
style?: StyleProp<TextStyle>;
}
const HighlightText: React.FC<HighlightTextProps> = ({ text, keyword, style }) => {
const parts = useMemo(() => {
if (!text || !keyword) return [{ text, highlight: false }];
const result: Array<{ text: string; highlight: boolean }> = [];
const lowerText = text.toLowerCase();
const lowerKeyword = keyword.toLowerCase();
let lastIndex = 0;
let searchFrom = 0;
while (searchFrom < lowerText.length) {
const index = lowerText.indexOf(lowerKeyword, searchFrom);
if (index === -1) break;
if (index > lastIndex) {
result.push({ text: text.substring(lastIndex, index), highlight: false });
}
result.push({ text: text.substring(index, index + keyword.length), highlight: true });
lastIndex = index + keyword.length;
searchFrom = lastIndex;
}
if (lastIndex < text.length) {
result.push({ text: text.substring(lastIndex), highlight: false });
}
return result.length > 0 ? result : [{ text, highlight: false }];
}, [text, keyword]);
return (
<Text>
{parts.map((part, i) =>
part.highlight ? (
<Text key={i} style={style}>
{part.text}
</Text>
) : (
<Text key={i}>{part.text}</Text>
)
)}
</Text>
);
};
export default HighlightText;

View File

@@ -7,6 +7,7 @@ export { default as Button } from './Button';
export { default as Card } from './Card';
export { default as Input } from './Input';
export { default as Loading } from './Loading';
export { default as HighlightText } from './HighlightText';
export { default as Text } from './Text';
export { default as EmptyState } from './EmptyState';
export { default as Divider } from './Divider';