Skip to content

Commit 1b10a94

Browse files
Merge pull request #6 from codebuilderinc/chore/session-backgroundtask-migration
2 parents 192b62e + 4d932f0 commit 1b10a94

15 files changed

Lines changed: 617 additions & 617 deletions

app.config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ module.exports = {
5959
splash: {
6060
image: './assets/images/splash-icon.png',
6161
resizeMode: 'contain',
62-
backgroundColor: '#ffffff',
62+
backgroundColor: '#000000',
63+
},
64+
androidStatusBar: {
65+
barStyle: 'light-content',
66+
backgroundColor: '#000000',
67+
translucent: false,
6368
},
6469
ios: {
6570
buildNumber: versionData.iosBuildNumber, // Using iOS build number from version.json

app/(tabs)/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ export default function LocationComponent() {
1515
const textColor = '#ffffff';
1616

1717
return (
18-
<View style={{ flex: 1 }}>
18+
<View style={{ flex: 1, backgroundColor: '#000' }}>
1919
<CustomHeader title="Home" showModalButton={true} />
20-
<ScrollView contentContainerStyle={styles.scrollContent}>
20+
<ScrollView contentContainerStyle={styles.scrollContent} style={{ flex: 1 }}>
2121
<View style={styles.container}>
2222
<Image source={require('../../assets/images/icon.png')} style={imgStyles.image} />
2323

app/(tabs)/settings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import CustomHeader from "@/components/CustomHeader";
44

55
export default function SimplePage() {
66
return (
7-
<View style={{ flex: 1 }}>
7+
<View style={{ flex: 1, backgroundColor: '#000' }}>
88
<CustomHeader title="Settings" />
99
<View style={styles.container}>
1010
<Text style={styles.header}>My Header</Text>

app/_layout.tsx

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import 'react-native-reanimated';
88
import { useFonts } from 'expo-font';
99
import FontAwesome from '@expo/vector-icons/FontAwesome';
1010
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
11+
import { View } from 'react-native';
12+
import { StatusBar } from 'expo-status-bar';
1113

1214
import { usePushNotifications, useNotificationObserver } from '@/hooks/usePushNotifications';
1315
import { useColorScheme } from '@/hooks/useColorScheme';
@@ -17,6 +19,7 @@ import { NotificationProvider } from '@/providers/NotificationProvider';
1719

1820
// ✅ import from providers + hooks (don’t import AuthProvider from hooks)
1921
import { AuthProvider } from '@/providers/AuthProvider';
22+
import { SessionProvider } from '@/providers/SessionProvider';
2023
import { useAuth } from '@/hooks/useAuth';
2124

2225
import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler';
@@ -63,10 +66,6 @@ setNativeExceptionHandler(nativeExceptionHandler);
6366
export default function RootLayout() {
6467
const navigationRef = useNavigationContainerRef();
6568

66-
// Enable push notifications and observer
67-
usePushNotifications();
68-
useNotificationObserver();
69-
7069
// Register background fetch task
7170
useEffect(() => {
7271
registerBackgroundFetch();
@@ -91,14 +90,30 @@ export default function RootLayout() {
9190
if (!loaded) return null;
9291

9392
return (
94-
<AuthProvider>
95-
<RootLayoutNav />
96-
</AuthProvider>
93+
<SessionProvider>
94+
<AuthProvider>
95+
<NotificationBootstrap />
96+
<RootLayoutNav />
97+
</AuthProvider>
98+
</SessionProvider>
9799
);
98100
}
99101

102+
// Forced dark theme (can be extended later for dynamic theming)
103+
const ForcedDarkTheme = {
104+
...DarkTheme,
105+
colors: {
106+
...DarkTheme.colors,
107+
background: '#000000',
108+
card: '#000000',
109+
text: '#ffffff',
110+
border: '#222222',
111+
primary: '#ffffff',
112+
},
113+
};
114+
100115
function RootLayoutNav() {
101-
const colorScheme = useColorScheme();
116+
// const colorScheme = useColorScheme(); // Not used since we force dark
102117
const { user } = useAuth();
103118
// Avoid logging during render to prevent side-effects in LogViewer
104119
useEffect(() => {
@@ -112,13 +127,27 @@ function RootLayoutNav() {
112127
return (
113128
<ErrorBoundary>
114129
<NotificationProvider>
115-
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
116-
<Stack>
117-
{user ? <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> : <Stack.Screen name="login" options={{ headerShown: false }} />}
118-
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
119-
</Stack>
130+
<ThemeProvider value={ForcedDarkTheme}>
131+
<View style={{ flex: 1, backgroundColor: '#000' }}>
132+
<StatusBar style="light" backgroundColor="#000" translucent={false} />
133+
<Stack screenOptions={{
134+
contentStyle: { backgroundColor: '#000' },
135+
headerStyle: { backgroundColor: '#000' },
136+
}}>
137+
{user ? <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> : <Stack.Screen name="login" options={{ headerShown: false }} />}
138+
<Stack.Screen name="debug" options={{ title: 'Debug' }} />
139+
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
140+
</Stack>
141+
</View>
120142
</ThemeProvider>
121143
</NotificationProvider>
122144
</ErrorBoundary>
123145
);
124146
}
147+
148+
// Separate component so hooks mount within providers
149+
function NotificationBootstrap() {
150+
usePushNotifications();
151+
useNotificationObserver();
152+
return null;
153+
}

app/debug.tsx

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import React, { useCallback, useEffect, useState } from 'react';
2+
import { View, Text, StyleSheet, ScrollView, Pressable } from 'react-native';
3+
import { useSessionUser, useSession } from '@/providers/SessionProvider';
4+
import { BACKGROUND_TASK_IDENTIFIER, getBackgroundTaskRegistrationState, registerBackgroundFetch, unregisterBackgroundTask, triggerBackgroundTaskForTesting } from '@/utils/tasks.utils';
5+
6+
interface BgState {
7+
status: number | null;
8+
isRegistered: boolean;
9+
loading: boolean;
10+
lastAction?: string;
11+
}
12+
13+
export default function DebugScreen() {
14+
const { fcmToken, authReady, fcmReady, user, accessToken } = useSessionUser();
15+
const { session } = useSession();
16+
const [bg, setBg] = useState<BgState>({ status: null, isRegistered: false, loading: false });
17+
18+
const refresh = useCallback(async () => {
19+
setBg((s) => ({ ...s, loading: true }));
20+
try {
21+
const r = await getBackgroundTaskRegistrationState();
22+
setBg((s) => ({ ...s, ...r, loading: false }));
23+
} catch (e) {
24+
setBg((s) => ({ ...s, loading: false, lastAction: 'refresh failed' }));
25+
}
26+
}, []);
27+
28+
useEffect(() => {
29+
refresh();
30+
}, [refresh]);
31+
32+
const register = async () => {
33+
setBg((s) => ({ ...s, loading: true }));
34+
await registerBackgroundFetch();
35+
await refresh();
36+
setBg((s) => ({ ...s, lastAction: 'registered' }));
37+
};
38+
const unregister = async () => {
39+
setBg((s) => ({ ...s, loading: true }));
40+
await unregisterBackgroundTask();
41+
await refresh();
42+
setBg((s) => ({ ...s, lastAction: 'unregistered' }));
43+
};
44+
const trigger = async () => {
45+
setBg((s) => ({ ...s, lastAction: 'triggering' }));
46+
await triggerBackgroundTaskForTesting();
47+
};
48+
49+
const tokenTail = fcmToken ? fcmToken.slice(-10) : 'none';
50+
51+
return (
52+
<ScrollView style={styles.root} contentContainerStyle={styles.content}>
53+
<Text style={styles.h1}>Debug</Text>
54+
<Section title="Session">
55+
<Mono label="FCM Ready" value={String(fcmReady)} />
56+
<Mono label="Auth Ready" value={String(authReady)} />
57+
<Mono label="FCM Token Tail" value={tokenTail} />
58+
<Mono label="User Email" value={user?.email || '—'} />
59+
<Mono label="Access Token" value={accessToken ? accessToken.slice(0, 15) + '…' : '—'} />
60+
</Section>
61+
<Section title="Raw Session Snapshot">
62+
<Text style={styles.code}>{JSON.stringify(session, null, 2)}</Text>
63+
</Section>
64+
<Section title="Background Task">
65+
<Mono label="Identifier" value={BACKGROUND_TASK_IDENTIFIER} />
66+
<Mono label="Status" value={bg.status === null ? '—' : String(bg.status)} />
67+
<Mono label="Registered" value={String(bg.isRegistered)} />
68+
<Mono label="Last Action" value={bg.lastAction || '—'} />
69+
<View style={styles.rowWrap}>
70+
<Btn label="Refresh" onPress={refresh} disabled={bg.loading} />
71+
<Btn label="Register" onPress={register} disabled={bg.loading || bg.isRegistered} />
72+
<Btn label="Unregister" onPress={unregister} disabled={bg.loading || !bg.isRegistered} />
73+
<Btn label="Trigger(dev)" onPress={trigger} />
74+
</View>
75+
</Section>
76+
<Text style={styles.footer}>Build Debug Utilities v1</Text>
77+
</ScrollView>
78+
);
79+
}
80+
81+
function Section({ title, children }: { title: string; children: React.ReactNode }) {
82+
return (
83+
<View style={styles.section}>
84+
<Text style={styles.sectionTitle}>{title}</Text>
85+
{children}
86+
</View>
87+
);
88+
}
89+
90+
function Mono({ label, value }: { label: string; value: string }) {
91+
return (
92+
<View style={styles.kvRow}>
93+
<Text style={styles.kvLabel}>{label}</Text>
94+
<Text style={styles.kvValue}>{value}</Text>
95+
</View>
96+
);
97+
}
98+
99+
function Btn({ label, onPress, disabled }: { label: string; onPress: () => void; disabled?: boolean }) {
100+
return (
101+
<Pressable
102+
onPress={disabled ? undefined : onPress}
103+
style={[styles.btn, disabled && styles.btnDisabled]}
104+
android_ripple={{ color: '#222' }}
105+
>
106+
<Text style={styles.btnText}>{label}</Text>
107+
</Pressable>
108+
);
109+
}
110+
111+
const styles = StyleSheet.create({
112+
root: { flex: 1, backgroundColor: '#000' },
113+
content: { padding: 16, paddingBottom: 48 },
114+
h1: { fontSize: 26, fontWeight: '600', color: '#fff', marginBottom: 12 },
115+
section: { marginBottom: 24, backgroundColor: '#111', borderRadius: 8, padding: 12 },
116+
sectionTitle: { color: '#9acd32', fontWeight: '600', fontSize: 16, marginBottom: 8 },
117+
kvRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 4 },
118+
kvLabel: { color: '#bbb', fontSize: 13 },
119+
kvValue: { color: '#fff', fontFamily: 'monospace', fontSize: 13, flexShrink: 1, textAlign: 'right' },
120+
code: { color: '#8f8', fontFamily: 'monospace', fontSize: 12 },
121+
rowWrap: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 8 },
122+
btn: { backgroundColor: '#222', paddingVertical: 8, paddingHorizontal: 12, borderRadius: 6, marginRight: 8, marginBottom: 8 },
123+
btnDisabled: { opacity: 0.4 },
124+
btnText: { color: '#fff', fontSize: 12, fontWeight: '600' },
125+
footer: { textAlign: 'center', color: '#444', fontSize: 12 },
126+
row: { flexDirection: 'row', alignItems: 'center', gap: 12 },
127+
debugButton: { marginLeft: 12, backgroundColor: '#333', paddingHorizontal: 14, paddingVertical: 10, borderRadius: 6, justifyContent: 'center' },
128+
debugButtonText: { color: '#fff', fontWeight: '600' },
129+
});

0 commit comments

Comments
 (0)