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:
lafay
2026-04-11 04:27:22 +08:00
parent 9bbed8cf5e
commit 6f84e17772
22 changed files with 1271 additions and 394 deletions

View File

@@ -26,12 +26,6 @@ import {
import { showPrompt } from '../../services/promptService';
import * as hrefs from '../../navigation/hrefs';
const THEME_COLORS = {
primary: '#FF6B35',
primaryLight: '#FF8C5A',
primaryDark: '#E55A2B',
};
const STATUS_CONFIG = {
not_submitted: {
title: '未认证',
@@ -99,7 +93,7 @@ export const VerificationSettingsScreen: React.FC = () => {
return (
<SafeAreaView style={styles.container}>
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={THEME_COLORS.primary} />
<ActivityIndicator size="large" color={colors.primary.main} />
</View>
</SafeAreaView>
);
@@ -110,10 +104,11 @@ export const VerificationSettingsScreen: React.FC = () => {
const identityText = IDENTITY_TEXT[status?.identity || 'general'];
return (
<ScrollView
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
<SafeAreaView style={styles.container}>
<ScrollView
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
{/* 状态卡片 */}
<View style={styles.statusCard}>
<View style={[styles.statusIconContainer, { backgroundColor: statusConfig.color + '20' }]}>
@@ -128,7 +123,7 @@ export const VerificationSettingsScreen: React.FC = () => {
{status?.verification_status === 'approved' && (
<View style={styles.identityBadge}>
<MaterialCommunityIcons name="check-circle" size={16} color={THEME_COLORS.primary} />
<MaterialCommunityIcons name="check-circle" size={16} color={colors.primary.main} />
<Text style={styles.identityText}>{identityText}</Text>
</View>
)}
@@ -153,7 +148,7 @@ export const VerificationSettingsScreen: React.FC = () => {
<MaterialCommunityIcons
name="check-decagram"
size={24}
color={THEME_COLORS.primary}
color={colors.primary.main}
/>
</View>
<View style={styles.benefitContent}>
@@ -169,7 +164,7 @@ export const VerificationSettingsScreen: React.FC = () => {
<MaterialCommunityIcons
name="lock-open-outline"
size={24}
color={THEME_COLORS.primary}
color={colors.primary.main}
/>
</View>
<View style={styles.benefitContent}>
@@ -185,7 +180,7 @@ export const VerificationSettingsScreen: React.FC = () => {
<MaterialCommunityIcons
name="account-group-outline"
size={24}
color={THEME_COLORS.primary}
color={colors.primary.main}
/>
</View>
<View style={styles.benefitContent}>
@@ -208,7 +203,7 @@ export const VerificationSettingsScreen: React.FC = () => {
onPress={handleStartVerification}
activeOpacity={0.9}
>
<MaterialCommunityIcons name="shield-check-outline" size={20} color="#FFF" />
<MaterialCommunityIcons name="shield-check-outline" size={20} color={colors.primary.contrast} />
<Text style={styles.primaryButtonText}>
{status?.verification_status === 'rejected' ? '重新认证' : '开始认证'}
</Text>
@@ -221,7 +216,7 @@ export const VerificationSettingsScreen: React.FC = () => {
onPress={handleViewRecords}
activeOpacity={0.8}
>
<MaterialCommunityIcons name="file-document-outline" size={20} color={THEME_COLORS.primary} />
<MaterialCommunityIcons name="file-document-outline" size={20} color={colors.primary.main} />
<Text style={styles.secondaryButtonText}></Text>
</TouchableOpacity>
)}
@@ -262,6 +257,7 @@ export const VerificationSettingsScreen: React.FC = () => {
</Text>
</View>
</ScrollView>
</SafeAreaView>
);
};
@@ -269,7 +265,7 @@ function createStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
backgroundColor: colors.background.paper,
},
loadingContainer: {
flex: 1,
@@ -307,7 +303,7 @@ function createStyles(colors: AppColors) {
alignItems: 'center',
paddingVertical: 32,
paddingHorizontal: 24,
backgroundColor: '#F9F9F9',
backgroundColor: colors.background.default,
borderRadius: 16,
marginBottom: 24,
},
@@ -334,7 +330,7 @@ function createStyles(colors: AppColors) {
identityBadge: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FFF5F0',
backgroundColor: colors.primary.main + '15',
borderRadius: 20,
paddingVertical: 8,
paddingHorizontal: 16,
@@ -343,25 +339,25 @@ function createStyles(colors: AppColors) {
identityText: {
fontSize: 14,
fontWeight: '600',
color: THEME_COLORS.primary,
color: colors.primary.main,
marginLeft: 6,
},
rejectReasonContainer: {
marginTop: 16,
padding: 12,
backgroundColor: '#FFF3F3',
backgroundColor: colors.error.light + '20',
borderRadius: 8,
width: '100%',
},
rejectReasonLabel: {
fontSize: 13,
fontWeight: '600',
color: '#F44336',
color: colors.error.main,
marginBottom: 4,
},
rejectReasonText: {
fontSize: 13,
color: '#D32F2F',
color: colors.error.dark,
lineHeight: 18,
},
benefitsSection: {
@@ -379,7 +375,7 @@ function createStyles(colors: AppColors) {
benefitItem: {
flexDirection: 'row',
alignItems: 'flex-start',
backgroundColor: '#F9F9F9',
backgroundColor: colors.background.default,
borderRadius: 12,
padding: 16,
},
@@ -387,7 +383,7 @@ function createStyles(colors: AppColors) {
width: 48,
height: 48,
borderRadius: 12,
backgroundColor: '#FFF5F0',
backgroundColor: colors.primary.main + '15',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
@@ -415,12 +411,12 @@ function createStyles(colors: AppColors) {
justifyContent: 'center',
height: 56,
borderRadius: 14,
backgroundColor: THEME_COLORS.primary,
backgroundColor: colors.primary.main,
},
primaryButtonText: {
fontSize: 17,
fontWeight: '600',
color: '#FFF',
color: colors.text.inverse,
marginLeft: 8,
},
secondaryButton: {
@@ -429,18 +425,18 @@ function createStyles(colors: AppColors) {
justifyContent: 'center',
height: 56,
borderRadius: 14,
backgroundColor: '#FFF5F0',
backgroundColor: colors.primary.main + '15',
borderWidth: 1,
borderColor: THEME_COLORS.primary,
borderColor: colors.primary.main,
},
secondaryButtonText: {
fontSize: 17,
fontWeight: '600',
color: THEME_COLORS.primary,
color: colors.primary.main,
marginLeft: 8,
},
verifiedInfo: {
backgroundColor: '#F9F9F9',
backgroundColor: colors.background.default,
borderRadius: 12,
padding: 16,
},