feat(service): add foreground service for background message sync
Implement Android foreground service to keep app alive in background for real-time message sync: - Add withForegroundService expo config plugin for notification channel setup - Add BackgroundSyncManager with REALTIME, BATTERY_SAVER, and DISABLED modes - Add ForegroundServiceModule for native Android foreground service binding - Refactor backgroundService to integrate foreground service and expo-background-task Also refactor auth and profile screens to use dynamic theme colors: - Replace hardcoded THEME_COLORS with colors.primary.main, colors.background.paper, etc. - Add useResolvedColorScheme for dynamic StatusBar styling - Update NotificationSettingsScreen with sync mode selection UI BREAKING CHANGE: Switched from expo-background-fetch to expo-background-task, minimum background sync interval changed from 900s to 15min in config
This commit is contained in:
@@ -33,7 +33,7 @@ import { useNavigation, useRouter } from 'expo-router';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Text, ImageGallery, ImageGridItem } from '../../components/common';
|
||||
import { useAppColors } from '../../theme';
|
||||
import { useAppColors, useResolvedColorScheme } from '../../theme';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
import { messageManager, callStore } from '../../stores';
|
||||
import { useBreakpointGTE } from '../../hooks/useResponsive';
|
||||
@@ -56,6 +56,7 @@ export const ChatScreen: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const insets = useSafeAreaInsets();
|
||||
const colors = useAppColors();
|
||||
const resolved = useResolvedColorScheme();
|
||||
const styles = useChatScreenStyles();
|
||||
|
||||
// 响应式布局
|
||||
@@ -406,7 +407,7 @@ export const ChatScreen: React.FC = () => {
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
|
||||
>
|
||||
<StatusBar style="dark" backgroundColor="#FFFFFF" />
|
||||
<StatusBar style={resolved === 'dark' ? 'light' : 'dark'} backgroundColor={colors.background.paper} />
|
||||
|
||||
{/* 顶部栏 */}
|
||||
<ChatHeader
|
||||
@@ -499,7 +500,7 @@ export const ChatScreen: React.FC = () => {
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: 'rgba(255,255,255,0.9)',
|
||||
backgroundColor: colors.background.paper + 'E6',
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
|
||||
@@ -28,7 +28,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { spacing, borderRadius, useAppColors, type AppColors } from '../../theme';
|
||||
import { spacing, borderRadius, useAppColors, useResolvedColorScheme, type AppColors } from '../../theme';
|
||||
import { SystemMessageResponse } from '../../types/dto';
|
||||
import { messageService } from '../../services/messageService';
|
||||
import { commentService } from '../../services/commentService';
|
||||
@@ -49,18 +49,12 @@ const MESSAGE_TYPES = [
|
||||
{ key: 'announcement', title: '公告' },
|
||||
];
|
||||
|
||||
// 胡萝卜橙主题色
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
const LIKE_SYSTEM_TYPES = new Set(['like_post', 'like_comment', 'like_reply', 'favorite_post']);
|
||||
const GROUP_SYSTEM_TYPES = new Set(['group_invite', 'group_join_apply', 'group_join_approved', 'group_join_rejected']);
|
||||
|
||||
export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack }) => {
|
||||
const colors = useAppColors();
|
||||
const resolved = useResolvedColorScheme();
|
||||
const styles = useMemo(() => createNotificationsStyles(colors), [colors]);
|
||||
const isFocused = useIsFocused();
|
||||
const { setSystemUnreadCount, decrementSystemUnreadCount } = useMessageManagerSystemUnreadCount();
|
||||
@@ -424,9 +418,9 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
if (!isHydrated) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
|
||||
<StatusBar barStyle={resolved === 'dark' ? 'light-content' : 'dark-content'} backgroundColor={colors.background.paper} />
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={THEME_COLORS.primary} />
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -434,7 +428,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
|
||||
<StatusBar barStyle={resolved === 'dark' ? 'light-content' : 'dark-content'} backgroundColor={colors.background.paper} />
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.content,
|
||||
@@ -499,7 +493,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
{/* 消息列表 */}
|
||||
{isLoading && messages.length === 0 ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={THEME_COLORS.primary} />
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
@@ -519,8 +513,8 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={[THEME_COLORS.primary]}
|
||||
tintColor={THEME_COLORS.primary}
|
||||
colors={[colors.primary.main]}
|
||||
tintColor={colors.primary.main}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -580,7 +574,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
{/* 消息列表 */}
|
||||
{isLoading && messages.length === 0 ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={THEME_COLORS.primary} />
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
@@ -600,8 +594,8 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={[THEME_COLORS.primary]}
|
||||
tintColor={THEME_COLORS.primary}
|
||||
colors={[colors.primary.main]}
|
||||
tintColor={colors.primary.main}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -617,7 +611,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
@@ -629,7 +623,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
headerWide: {
|
||||
paddingHorizontal: 32,
|
||||
@@ -650,7 +644,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
fontSize: 20,
|
||||
},
|
||||
unreadBadge: {
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
@@ -660,7 +654,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
unreadBadgeText: {
|
||||
color: '#fff',
|
||||
color: colors.text.inverse,
|
||||
fontSize: 11,
|
||||
fontWeight: '600',
|
||||
},
|
||||
@@ -674,7 +668,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
@@ -686,7 +680,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.divider || '#E5E5EA',
|
||||
},
|
||||
@@ -702,7 +696,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
paddingVertical: 8,
|
||||
marginRight: 8,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderWidth: 1,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
@@ -712,15 +706,15 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
marginRight: 10,
|
||||
},
|
||||
filterTagActive: {
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
borderColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
borderColor: colors.primary.main,
|
||||
},
|
||||
filterTagTextActive: {
|
||||
fontWeight: '600',
|
||||
},
|
||||
filterCount: {
|
||||
marginLeft: 6,
|
||||
backgroundColor: THEME_COLORS.primaryLight + '40',
|
||||
backgroundColor: colors.primary.light + '40',
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 1,
|
||||
borderRadius: 8,
|
||||
@@ -770,7 +764,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 24,
|
||||
|
||||
Reference in New Issue
Block a user