chore: update styles, add splash config, and improve search functionality
All checks were successful
Frontend CI / ota-android (push) Successful in 1m50s
Frontend CI / ota-ios (push) Successful in 2m38s
Frontend CI / build-and-push-web (push) Successful in 10m12s
Frontend CI / build-android-apk (push) Successful in 17m46s

- Replace deprecated StyleSheet.absoluteFillObject with StyleSheet.absoluteFill (React Native 0.76+)
- Add splash screen configuration in app.json
- Fix duplicate UIBackgroundModes and add audio mode for iOS
- Disable Android ripple effect on tab bar buttons
- Improve SmartImage loading state to prevent unnecessary re-renders
- Refactor ImageGrid to use mainUri with separate previewUrl for better preview handling
- Add market search support with trade items tab in SearchScreen
- Pass homeTab prop to SearchScreen for context-aware search behavior
- Simplify fetchUnreadCount return type to void in MessageSyncService
- Fix StatusBar import from expo to react-native in ChatScreen
This commit is contained in:
lafay
2026-06-02 08:14:04 +08:00
parent 52d2581dda
commit 8ee6e77cb4
24 changed files with 156 additions and 59 deletions

View File

@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(python -c \"import sys,json; d=json.load\\(sys.stdin\\); deps={**d.get\\('dependencies',{}\\), **d.get\\('devDependencies',{}\\)}; [print\\(f'{k}: {v}'\\) for k,v in deps.items\\(\\) if 'navigation' in k or 'tab' in k.lower\\(\\)]\")",
"Bash(npx tsc *)",
"Bash(echo \"EXIT: $?\")"
]
}
}

View File

@@ -7,14 +7,17 @@
"icon": "./assets/icon.png", "icon": "./assets/icon.png",
"userInterfaceStyle": "automatic", "userInterfaceStyle": "automatic",
"scheme": "withyou", "scheme": "withyou",
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": { "ios": {
"supportsTablet": true, "supportsTablet": true,
"infoPlist": { "infoPlist": {
"NSMicrophoneUsageDescription": "允许威友访问您的麦克风以进行语音通话", "NSMicrophoneUsageDescription": "允许威友访问您的麦克风以进行语音通话",
"NSCameraUsageDescription": "允许威友访问您的相机以进行视频通话", "NSCameraUsageDescription": "允许威友访问您的相机以进行视频通话",
"UIBackgroundModes": [ "UIBackgroundModes": [
"fetch",
"remote-notification",
"fetch", "fetch",
"remote-notification", "remote-notification",
"audio" "audio"

View File

@@ -1,8 +1,9 @@
import { useMemo, useCallback } from 'react'; import { useMemo, useCallback } from 'react';
import { Platform, useWindowDimensions } from 'react-native'; import { Platform, Pressable, useWindowDimensions } from 'react-native';
import { Tabs, usePathname } from 'expo-router'; import { Tabs, usePathname } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import type { PressableProps } from 'react-native';
import { useAppColors, shadows } from '../../../src/theme'; import { useAppColors, shadows } from '../../../src/theme';
import { BREAKPOINTS } from '../../../src/hooks'; import { BREAKPOINTS } from '../../../src/hooks';
@@ -12,6 +13,16 @@ const TAB_BAR_HEIGHT = 56;
const TAB_BAR_FLOATING_MARGIN = 12; const TAB_BAR_FLOATING_MARGIN = 12;
const TAB_BAR_MARGIN = 20; const TAB_BAR_MARGIN = 20;
const TabBarButton = ({ children, style, ...rest }: PressableProps) => (
<Pressable
{...rest}
style={style}
android_ripple={null}
>
{children}
</Pressable>
);
export default function TabsLayout() { export default function TabsLayout() {
const colors = useAppColors(); const colors = useAppColors();
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
@@ -72,6 +83,7 @@ export default function TabsLayout() {
marginHorizontal: 2, marginHorizontal: 2,
paddingVertical: 1, paddingVertical: 1,
}, },
tabBarButton: (props) => <TabBarButton {...props} />,
tabBarShowLabel: true, tabBarShowLabel: true,
tabBarLabelStyle: { fontSize: 11, fontWeight: '600', marginTop: -1 }, tabBarLabelStyle: { fontSize: 11, fontWeight: '600', marginTop: -1 },
}} }}

View File

@@ -67,7 +67,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
}, },
uploadingOverlay: { uploadingOverlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0, 0, 0, 0.3)', backgroundColor: 'rgba(0, 0, 0, 0.3)',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',

View File

@@ -159,7 +159,7 @@ function createVoteCardStyles(colors: AppColors) {
fontSize: fontSizes.sm, fontSize: fontSizes.sm,
}, },
loadingOverlay: { loadingOverlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: colors.background.paper + 'CC', backgroundColor: colors.background.paper + 'CC',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',

View File

@@ -267,12 +267,12 @@ const CallScreen: React.FC = () => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: '#1A1A2E', backgroundColor: '#1A1A2E',
zIndex: 9999, zIndex: 9999,
}, },
background: { background: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: '#1A1A2E', backgroundColor: '#1A1A2E',
}, },
topBar: { topBar: {
@@ -334,7 +334,7 @@ const styles = StyleSheet.create({
letterSpacing: 0.3, letterSpacing: 0.3,
}, },
fullScreenVideo: { fullScreenVideo: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: '#1A1A2E', backgroundColor: '#1A1A2E',
}, },
localVideoContainer: { localVideoContainer: {

View File

@@ -252,12 +252,12 @@ const CallScreen: React.FC = () => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: '#1A1A2E', backgroundColor: '#1A1A2E',
zIndex: 9999, zIndex: 9999,
}, },
background: { background: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: '#1A1A2E', backgroundColor: '#1A1A2E',
}, },
topBar: { topBar: {
@@ -319,7 +319,7 @@ const styles = StyleSheet.create({
letterSpacing: 0.3, letterSpacing: 0.3,
}, },
fullScreenVideo: { fullScreenVideo: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: '#1A1A2E', backgroundColor: '#1A1A2E',
}, },
localVideoContainer: { localVideoContainer: {

View File

@@ -346,7 +346,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
}, },
overlayTouchable: { overlayTouchable: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
}, },
overlay: { overlay: {
flex: 1, flex: 1,

View File

@@ -548,7 +548,7 @@ const styles = StyleSheet.create({
height: SCREEN_HEIGHT * 0.8, height: SCREEN_HEIGHT * 0.8,
}, },
errorContainer: { errorContainer: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
backgroundColor: '#000', backgroundColor: '#000',

View File

@@ -148,7 +148,7 @@ function createImageGridStyles(colors: AppColors) {
backgroundColor: colors.background.disabled, backgroundColor: colors.background.disabled,
}, },
moreOverlay: { moreOverlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0, 0, 0, 0.4)', backgroundColor: 'rgba(0, 0, 0, 0.4)',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
@@ -390,9 +390,11 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
return ( return (
<View style={[gridStyles.horizontalContainer, { gap }]}> <View style={[gridStyles.horizontalContainer, { gap }]}>
{displayImages.map((image, index) => { {displayImages.map((image, index) => {
const previewUrl = usePreview && displayMode const mainUri = image.uri || image.url || '';
const previewForSmart = usePreview && displayMode
? getPreviewImageUrl(image as any, displayMode) ? getPreviewImageUrl(image as any, displayMode)
: (image.uri || image.url || ''); : '';
const useSmartPreview = !!(usePreview && previewForSmart && mainUri && previewForSmart !== mainUri);
return ( return (
<Pressable <Pressable
@@ -408,7 +410,9 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
]} ]}
> >
<SmartImage <SmartImage
source={{ uri: previewUrl }} source={{ uri: mainUri }}
previewUrl={useSmartPreview ? previewForSmart : undefined}
usePreview={useSmartPreview}
style={gridStyles.fullSize} style={gridStyles.fullSize}
resizeMode="cover" resizeMode="cover"
borderRadius={borderRadiusValue} borderRadius={borderRadiusValue}
@@ -428,9 +432,11 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
const isLastVisible = index === displayImages.length - 1; const isLastVisible = index === displayImages.length - 1;
const showOverlay = isLastVisible && remainingCount > 0 && showMoreOverlay; const showOverlay = isLastVisible && remainingCount > 0 && showMoreOverlay;
const previewUrl = usePreview && displayMode const mainUri = image.uri || image.url || '';
const previewForSmart = usePreview && displayMode
? getPreviewImageUrl(image as any, displayMode) ? getPreviewImageUrl(image as any, displayMode)
: (image.uri || image.url || ''); : '';
const useSmartPreview = !!(usePreview && previewForSmart && mainUri && previewForSmart !== mainUri);
return ( return (
<Pressable <Pressable
@@ -445,7 +451,9 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
]} ]}
> >
<SmartImage <SmartImage
source={{ uri: previewUrl }} source={{ uri: mainUri }}
previewUrl={useSmartPreview ? previewForSmart : undefined}
usePreview={useSmartPreview}
style={gridStyles.fullSize} style={gridStyles.fullSize}
resizeMode="cover" resizeMode="cover"
borderRadius={borderRadiusValue} borderRadius={borderRadiusValue}
@@ -490,9 +498,11 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
: 1; : 1;
const height = itemWidth / aspectRatio; const height = itemWidth / aspectRatio;
const previewUrl = usePreview && displayMode const mainUri = image.uri || image.url || '';
const previewForSmart = usePreview && displayMode
? getPreviewImageUrl(image as any, displayMode) ? getPreviewImageUrl(image as any, displayMode)
: (image.uri || image.url || ''); : '';
const useSmartPreview = !!(usePreview && previewForSmart && mainUri && previewForSmart !== mainUri);
return ( return (
<Pressable <Pressable
@@ -508,7 +518,9 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
]} ]}
> >
<SmartImage <SmartImage
source={{ uri: previewUrl }} source={{ uri: mainUri }}
previewUrl={useSmartPreview ? previewForSmart : undefined}
usePreview={useSmartPreview}
style={gridStyles.fullSize} style={gridStyles.fullSize}
resizeMode="cover" resizeMode="cover"
borderRadius={borderRadiusValue} borderRadius={borderRadiusValue}

View File

@@ -30,7 +30,7 @@ function createSmartImageStyles(colors: AppColors) {
flex: 1, flex: 1,
}, },
overlay: { overlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.background.disabled, backgroundColor: colors.background.disabled,
@@ -186,7 +186,8 @@ export const SmartImage: React.FC<SmartImageProps> = ({
if (isUpgradingFromPreview.current) { if (isUpgradingFromPreview.current) {
return; return;
} }
setLoadState('loading'); // 已成功加载的图片不再回到loading状态列表回收/re-render时onLoadStart可能重新触发
setLoadState(prev => (prev === 'success' ? prev : 'loading'));
}, []); }, []);
// 处理加载完成 // 处理加载完成

View File

@@ -1045,7 +1045,7 @@ function createCreatePostStyles(colors: AppColors) {
height: '100%', height: '100%',
}, },
uploadingOverlay: { uploadingOverlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0, 0, 0, 0.5)', backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',

View File

@@ -902,6 +902,7 @@ export const HomeScreen: React.FC = () => {
<StatusBar barStyle={statusBarStyle} backgroundColor={colors.background.paper} /> <StatusBar barStyle={statusBarStyle} backgroundColor={colors.background.paper} />
<SearchScreen <SearchScreen
onBack={() => setShowSearch(false)} onBack={() => setShowSearch(false)}
homeTab={homeTab}
/> />
</SafeAreaView> </SafeAreaView>
); );

View File

@@ -27,7 +27,7 @@ function createMarketStyles(colors: AppColors, gap: number, padding: number) {
}, },
listContent: { listContent: {
paddingHorizontal: padding, paddingHorizontal: padding,
paddingTop: gap / 2, paddingTop: padding,
paddingBottom: 80, paddingBottom: 80,
}, },
}); });

View File

@@ -18,28 +18,33 @@ import { useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme'; import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme';
import { Post, User } from '../../types'; import { Post, User } from '../../types';
import type { TradeItemDTO } from '../../types/trade';
import { useUserStore } from '../../stores'; import { useUserStore } from '../../stores';
import { postService, authService } from '../../services'; import { postService, authService } from '../../services';
import { tradeService } from '../../services/trade/tradeService';
import { postSyncService } from '@/services/post'; import { postSyncService } from '@/services/post';
import { PostCard, TabBar, SearchBar } from '../../components/business'; import { PostCard, TabBar, SearchBar } from '../../components/business';
import { TradeCard } from '../../components/business/TradeCard/TradeCard';
import type { PostCardAction } from '../../components/business/PostCard'; import type { PostCardAction } from '../../components/business/PostCard';
import { Avatar, EmptyState, Text, ResponsiveGrid, Loading } from '../../components/common'; import { Avatar, EmptyState, Text, ResponsiveGrid, Loading } from '../../components/common';
import * as hrefs from '../../navigation/hrefs'; import * as hrefs from '../../navigation/hrefs';
import { useResponsive, useResponsiveSpacing, useResponsiveValue } from '../../hooks'; import { useResponsive, useResponsiveSpacing, useResponsiveValue } from '../../hooks';
import { useCursorPagination } from '../../hooks/useCursorPagination'; import { useCursorPagination } from '../../hooks/useCursorPagination';
const TABS = ['帖子', '用户']; const SQUARE_TABS = ['帖子', '用户'];
const MARKET_TABS = ['商品', '用户'];
const DEFAULT_PAGE_SIZE = 20; const DEFAULT_PAGE_SIZE = 20;
// 与 TabsLayout 中的常量保持一致tabBarHeight 56 + 上下浮动间距 12*2 // 与 TabsLayout 中的常量保持一致tabBarHeight 56 + 上下浮动间距 12*2
const FLOATING_TAB_BAR_HEIGHT = 56 + 12 * 2; const FLOATING_TAB_BAR_HEIGHT = 56 + 12 * 2;
type SearchType = 'posts' | 'users'; type SearchType = 'posts' | 'users' | 'trades';
interface SearchScreenProps { interface SearchScreenProps {
onBack?: () => void; onBack?: () => void;
homeTab?: 'square' | 'market';
} }
export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => { export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack, homeTab = 'square' }) => {
const colors = useAppColors(); const colors = useAppColors();
const styles = useMemo(() => createSearchScreenStyles(colors), [colors]); const styles = useMemo(() => createSearchScreenStyles(colors), [colors]);
const router = useRouter(); const router = useRouter();
@@ -47,6 +52,9 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
const history = useUserStore((state) => state.searchHistory); const history = useUserStore((state) => state.searchHistory);
const { addSearchHistory, clearSearchHistory } = useUserStore.getState(); const { addSearchHistory, clearSearchHistory } = useUserStore.getState();
const isMarket = homeTab === 'market';
const TABS = isMarket ? MARKET_TABS : SQUARE_TABS;
// 使用响应式 hook // 使用响应式 hook
const { const {
width, width,
@@ -115,6 +123,10 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
const [userResults, setUserResults] = useState<User[]>([]); const [userResults, setUserResults] = useState<User[]>([]);
const [userLoading, setUserLoading] = useState(false); const [userLoading, setUserLoading] = useState(false);
// 市集搜索结果
const [tradeResults, setTradeResults] = useState<TradeItemDTO[]>([]);
const [tradeLoading, setTradeLoading] = useState(false);
const refreshRef = useRef(refresh); const refreshRef = useRef(refresh);
refreshRef.current = refresh; refreshRef.current = refresh;
const resetRef = useRef(reset); const resetRef = useRef(reset);
@@ -148,9 +160,16 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
setUserLoading(true); setUserLoading(true);
const usersResponse = await authService.searchUsers(trimmedKeyword, 1, 20); const usersResponse = await authService.searchUsers(trimmedKeyword, 1, 20);
setUserResults(usersResponse.list || []); setUserResults(usersResponse.list || []);
} else if (searchType === 'trades') {
setTradeLoading(true);
const tradeResponse = await tradeService.getTradeItems({ keyword: trimmedKeyword, page_size: DEFAULT_PAGE_SIZE });
setTradeResults(tradeResponse.list || []);
} }
} catch (error) { } catch (error) {
console.error('搜索失败:', error); console.error('搜索失败:', error);
} finally {
setUserLoading(false);
setTradeLoading(false);
} }
setHasSearched(true); setHasSearched(true);
@@ -219,6 +238,13 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
// 获取当前搜索类型 // 获取当前搜索类型
const getSearchType = (): SearchType => { const getSearchType = (): SearchType => {
if (isMarket) {
switch (activeIndex) {
case 0: return 'trades';
case 1: return 'users';
default: return 'trades';
}
}
switch (activeIndex) { switch (activeIndex) {
case 0: return 'posts'; case 0: return 'posts';
case 1: return 'users'; case 1: return 'users';
@@ -426,6 +452,38 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
); );
}; };
// 渲染市集搜索结果
const renderTradeResults = () => {
if (tradeResults.length === 0 && !tradeLoading) {
return (
<EmptyState
title="未找到相关商品"
description="试试其他关键词吧"
icon="shopping-search-outline"
/>
);
}
return (
<FlatList
data={tradeResults}
renderItem={({ item }) => (
<View style={{ paddingHorizontal: responsivePadding, marginBottom: responsiveGap }}>
<TradeCard
item={item}
variant="list"
onPress={(id) => router.push(`/trade/${id}` as any)}
/>
</View>
)}
keyExtractor={item => item.id}
contentContainerStyle={{ paddingVertical: responsiveGap, paddingBottom: listBottomInset }}
showsVerticalScrollIndicator={false}
ListFooterComponent={tradeLoading ? <Loading size="sm" /> : null}
/>
);
};
// 渲染搜索结果 // 渲染搜索结果
const renderSearchResults = () => { const renderSearchResults = () => {
const searchType = getSearchType(); const searchType = getSearchType();
@@ -438,6 +496,10 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
return renderUserResults(); return renderUserResults();
} }
if (searchType === 'trades') {
return renderTradeResults();
}
return null; return null;
}; };
@@ -517,7 +579,7 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
value={searchText} value={searchText}
onChangeText={setSearchText} onChangeText={setSearchText}
onSubmit={handleSearch} onSubmit={handleSearch}
placeholder="搜索帖子、用户" placeholder={isMarket ? "搜索商品、用户" : "搜索帖子、用户"}
autoFocus autoFocus
/> />
</View> </View>
@@ -548,7 +610,7 @@ export const SearchScreen: React.FC<SearchScreenProps> = ({ onBack }) => {
} }
}} }}
variant="modern" variant="modern"
icons={['file-document-outline', 'account-outline']} icons={isMarket ? ['shopping-outline', 'account-outline'] : ['file-document-outline', 'account-outline']}
/> />
</View> </View>

View File

@@ -27,11 +27,11 @@ import {
Platform, Platform,
TouchableOpacity, TouchableOpacity,
BackHandler, BackHandler,
StatusBar,
} from 'react-native'; } from 'react-native';
import { FlashList, ListRenderItem } from '@shopify/flash-list'; import { FlashList, ListRenderItem } from '@shopify/flash-list';
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useNavigation, useRouter } from 'expo-router'; import { useNavigation, useRouter } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { Text, ImageGallery, ImageGridItem } from '../../components/common'; import { Text, ImageGallery, ImageGridItem } from '../../components/common';
import { useAppColors, useResolvedColorScheme } from '../../theme'; import { useAppColors, useResolvedColorScheme } from '../../theme';
@@ -478,7 +478,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
behavior={Platform.OS === 'ios' ? 'padding' : undefined} behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0} keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
> >
{!props.isEmbedded && <StatusBar style={resolved === 'dark' ? 'light' : 'dark'} backgroundColor={colors.background.paper} />} {!props.isEmbedded && <StatusBar barStyle={resolved === 'dark' ? 'light-content' : 'dark-content'} backgroundColor={colors.background.paper} />}
{/* 顶部栏 */} {/* 顶部栏 */}
<ChatHeader <ChatHeader

View File

@@ -374,7 +374,7 @@ export const GroupInfoPanel: React.FC<GroupInfoPanelProps> = ({
function createGroupInfoPanelStyles(colors: AppColors) { function createGroupInfoPanelStyles(colors: AppColors) {
return StyleSheet.create({ return StyleSheet.create({
overlay: { overlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0, 0, 0, 0.3)', backgroundColor: 'rgba(0, 0, 0, 0.3)',
zIndex: 100, zIndex: 100,
}, },

View File

@@ -379,7 +379,7 @@ export const GroupInfoPanel: React.FC<GroupInfoPanelProps> = ({
function createGroupInfoPanelStyles(colors: AppColors) { function createGroupInfoPanelStyles(colors: AppColors) {
return StyleSheet.create({ return StyleSheet.create({
overlay: { overlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0, 0, 0, 0.35)', backgroundColor: 'rgba(0, 0, 0, 0.35)',
zIndex: 100, zIndex: 100,
}, },

View File

@@ -551,7 +551,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
// 输入框@高亮 // 输入框@高亮
inputHighlightOverlay: { inputHighlightOverlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
flexDirection: 'row', flexDirection: 'row',
flexWrap: 'wrap', flexWrap: 'wrap',
paddingTop: 8, paddingTop: 8,
@@ -1072,7 +1072,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
// 遮罩层 // 遮罩层
overlay: { overlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0, 0, 0, 0.5)', backgroundColor: 'rgba(0, 0, 0, 0.5)',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@@ -1142,7 +1142,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
justifyContent: 'flex-end', justifyContent: 'flex-end',
}, },
manageModalBackdrop: { manageModalBackdrop: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0, 0, 0, 0.4)', backgroundColor: 'rgba(0, 0, 0, 0.4)',
}, },
manageModalHandle: { manageModalHandle: {
@@ -1165,7 +1165,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
borderColor: colors.chat.replyBorder, borderColor: colors.chat.replyBorder,
}, },
stickerCheckOverlay: { stickerCheckOverlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: 'rgba(127, 182, 230, 0.14)', backgroundColor: 'rgba(127, 182, 230, 0.14)',
alignItems: 'flex-end', alignItems: 'flex-end',
justifyContent: 'flex-start', justifyContent: 'flex-start',

View File

@@ -68,7 +68,7 @@ function createEditProfileStyles(colors: AppColors) {
alignItems: 'center', alignItems: 'center',
}, },
coverOverlay: { coverOverlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0, 0, 0, 0.4)', backgroundColor: 'rgba(0, 0, 0, 0.4)',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',

View File

@@ -198,7 +198,7 @@ const CourseDetailScreen: React.FC = () => {
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
<View style={styles.mask}> <View style={styles.mask}>
<Pressable style={StyleSheet.absoluteFillObject} onPress={() => router.back()} /> <Pressable style={StyleSheet.absoluteFill} onPress={() => router.back()} />
</View> </View>
</SafeAreaView> </SafeAreaView>
); );
@@ -207,7 +207,7 @@ const CourseDetailScreen: React.FC = () => {
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
<View style={styles.mask}> <View style={styles.mask}>
<Pressable style={StyleSheet.absoluteFillObject} onPress={() => router.back()} /> <Pressable style={StyleSheet.absoluteFill} onPress={() => router.back()} />
<View style={styles.card}> <View style={styles.card}>
<View style={styles.header}> <View style={styles.header}>
<View style={styles.headerLeft}> <View style={styles.headerLeft}>

View File

@@ -259,7 +259,7 @@ function createStyles(colors: AppColors) {
backgroundColor: colors.background.default, backgroundColor: colors.background.default,
}, },
uploadingOverlay: { uploadingOverlay: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0,0,0,0.3)', backgroundColor: 'rgba(0,0,0,0.3)',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',

View File

@@ -208,7 +208,7 @@ class MessageManager {
return this.syncService.loadMoreMessages(conversationId, beforeSeq, limit); return this.syncService.loadMoreMessages(conversationId, beforeSeq, limit);
} }
async fetchUnreadCount(): Promise<{ totalUnread: number; systemUnread: number }> { async fetchUnreadCount(): Promise<void> {
return this.syncService.fetchUnreadCount(); return this.syncService.fetchUnreadCount();
} }

View File

@@ -30,7 +30,7 @@ export class MessageSyncService implements IMessageSyncService {
/** 正在加载会话列表下一页 */ /** 正在加载会话列表下一页 */
private loadingMoreConversations = false; private loadingMoreConversations = false;
/** fetchUnreadCount 去重:复用 in-flight promise */ /** fetchUnreadCount 去重:复用 in-flight promise */
private fetchUnreadCountPromise: Promise<{ totalUnread: number; systemUnread: number }> | null = null; private fetchUnreadCountPromise: Promise<void> | null = null;
constructor( constructor(
getCurrentUserId: () => string | null, getCurrentUserId: () => string | null,
@@ -352,20 +352,19 @@ export class MessageSyncService implements IMessageSyncService {
/** /**
* 获取未读数(去重:复用 in-flight promise * 获取未读数(去重:复用 in-flight promise
*/ */
async fetchUnreadCount(): Promise<{ totalUnread: number; systemUnread: number }> { async fetchUnreadCount(): Promise<void> {
if (this.fetchUnreadCountPromise) { if (this.fetchUnreadCountPromise) {
return this.fetchUnreadCountPromise; return this.fetchUnreadCountPromise;
} }
this.fetchUnreadCountPromise = this.doFetchUnreadCount(); this.fetchUnreadCountPromise = this.doFetchUnreadCount();
try { try {
const result = await this.fetchUnreadCountPromise; await this.fetchUnreadCountPromise;
return result;
} finally { } finally {
this.fetchUnreadCountPromise = null; this.fetchUnreadCountPromise = null;
} }
} }
private async doFetchUnreadCount(): Promise<{ totalUnread: number; systemUnread: number }> { private async doFetchUnreadCount(): Promise<void> {
const store = useMessageStore.getState(); const store = useMessageStore.getState();
try { try {
@@ -393,10 +392,8 @@ export class MessageSyncService implements IMessageSyncService {
// 服务端汇总未读为 0不主动清零本地未读等 fetchConversations 提供权威数据 // 服务端汇总未读为 0不主动清零本地未读等 fetchConversations 提供权威数据
// 避免后端缓存竞态导致的未读数抖动 // 避免后端缓存竞态导致的未读数抖动
return { totalUnread, systemUnread };
} catch (error) { } catch (error) {
console.error('[MessageSyncService] 获取未读数失败:', error); console.error('[MessageSyncService] 获取未读数失败:', error);
return { totalUnread: 0, systemUnread: 0 };
} }
} }