refactor(platform): extract blurActiveElement to infrastructure module
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 4m47s
Frontend CI / ota-android (push) Successful in 10m48s
Frontend CI / build-android-apk (push) Successful in 1h36m37s

- Centralize duplicated Platform.OS === 'web' blur logic across 16 components
- Add blurActiveElement utility to src/infrastructure/platform module
- Optimize MessageBubble and ImageSegment with React.memo custom comparators
- Add useMemo for computed values in MessageSegmentsRenderer
- Update DTO types with is_system_notice and notice_content fields
- Fix keyboard dismissal order in useChatScreen handleDismiss
- Simplify userStore follow/unfollow state updates
- Remove unnecessary TypeScript type assertions throughout codebase
This commit is contained in:
lafay
2026-04-11 22:35:11 +08:00
parent 6f84e17772
commit 4b5ce1ba21
29 changed files with 679 additions and 222 deletions

View File

@@ -13,6 +13,7 @@ import { useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import * as hrefs from '../../navigation/hrefs';
import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme';
import { blurActiveElement } from '../../infrastructure/platform';
const { width, height } = Dimensions.get('window');
@@ -156,9 +157,8 @@ const QRCodeScannerWeb: React.FC<QRCodeScannerProps> = ({ visible, onClose }) =>
const styles = useMemo(() => createQrScannerStyles(themeColors), [themeColors]);
useEffect(() => {
if (visible && Platform.OS === 'web') {
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
active?.blur?.();
if (visible) {
blurActiveElement();
}
}, [visible]);

View File

@@ -21,6 +21,7 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useAppColors } from '../../../theme';
import { spacing, borderRadius, fontSizes, shadows } from '../../../theme';
import reportService, { ReportReason, ReportTargetType, REPORT_REASONS } from '../../../services/reportService';
import { blurActiveElement } from '../../../infrastructure/platform';
import Text from '../../common/Text';
export interface ReportDialogProps {
@@ -63,9 +64,8 @@ const ReportDialog: React.FC<ReportDialogProps> = ({
const targetConfig = TARGET_CONFIG[targetType];
useEffect(() => {
if (visible && Platform.OS === 'web') {
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
active?.blur?.();
if (visible) {
blurActiveElement();
}
}, [visible]);

View File

@@ -13,6 +13,7 @@ import {
} from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { callStore } from '../../stores/callStore';
import { blurActiveElement } from '../../infrastructure/platform';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
@@ -26,10 +27,7 @@ const IncomingCallModal: React.FC = () => {
useEffect(() => {
if (incomingCall) {
if (Platform.OS === 'web') {
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
active?.blur?.();
}
blurActiveElement();
const pulse = Animated.loop(
Animated.sequence([
Animated.timing(pulseAnim, {

View File

@@ -22,6 +22,7 @@ import {
useBreakpointGTE,
FineBreakpointKey,
} from '../../hooks/useResponsive';
import { blurActiveElement } from '../../infrastructure/platform';
export interface AdaptiveLayoutProps {
/** 主内容区 */
@@ -160,9 +161,8 @@ export function AdaptiveLayout({
// 抽屉动画
useEffect(() => {
if (isDrawerOpen) {
if (Platform.OS === 'web') {
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
active?.blur?.();
if (isDrawerOpen) {
blurActiveElement();
}
Animated.parallel([
Animated.timing(slideAnim, {

View File

@@ -6,6 +6,7 @@ import { LinearGradient } from 'expo-linear-gradient';
import { bindDialogListener, DialogPayload } from '../../services/dialogService';
import { borderRadius, shadows, spacing, useAppColors, type AppColors } from '../../theme';
import { blurActiveElement } from '../../infrastructure/platform';
function createDialogHostStyles(colors: AppColors) {
return StyleSheet.create({
@@ -93,12 +94,6 @@ function createDialogHostStyles(colors: AppColors) {
});
}
const blurActiveElementOnWeb = () => {
if (Platform.OS !== 'web') return;
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
active?.blur?.();
};
const AppDialogHost: React.FC = () => {
const [dialog, setDialog] = useState<DialogPayload | null>(null);
const colors = useAppColors();
@@ -113,7 +108,7 @@ const AppDialogHost: React.FC = () => {
useEffect(() => {
if (dialog) {
blurActiveElementOnWeb();
blurActiveElement();
}
}, [dialog]);

View File

@@ -33,6 +33,7 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
import * as MediaLibrary from 'expo-media-library';
import { File, Paths } from 'expo-file-system';
import { spacing, borderRadius, fontSizes } from '../../theme';
import { blurActiveElement } from '../../infrastructure/platform';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
@@ -127,10 +128,7 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
// 打开/关闭时重置状态
useEffect(() => {
if (visible) {
if (Platform.OS === 'web') {
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
active?.blur?.();
}
blurActiveElement();
setCurrentIndex(initialIndex);
setShowControls(true);
setError(false);

View File

@@ -18,6 +18,7 @@ import { VideoView, useVideoPlayer } from 'expo-video';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { spacing, fontSizes, borderRadius } from '../../theme';
import { blurActiveElement } from '../../infrastructure/platform';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
@@ -81,9 +82,8 @@ export const VideoPlayerModal: React.FC<VideoPlayerModalProps> = ({
onClose,
}) => {
useEffect(() => {
if (visible && Platform.OS === 'web') {
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
active?.blur?.();
if (visible) {
blurActiveElement();
}
}, [visible]);