Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ module.exports = {
splash: {
image: './assets/images/splash-icon.png',
resizeMode: 'contain',
backgroundColor: '#ffffff',
backgroundColor: '#000000',
},
androidStatusBar: {
barStyle: 'light-content',
backgroundColor: '#000000',
translucent: false,
},
ios: {
buildNumber: versionData.iosBuildNumber, // Using iOS build number from version.json
Expand Down
4 changes: 2 additions & 2 deletions app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export default function LocationComponent() {
const textColor = '#ffffff';

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

Expand Down
2 changes: 1 addition & 1 deletion app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import CustomHeader from "@/components/CustomHeader";

export default function SimplePage() {
return (
<View style={{ flex: 1 }}>
<View style={{ flex: 1, backgroundColor: '#000' }}>
<CustomHeader title="Settings" />
<View style={styles.container}>
<Text style={styles.header}>My Header</Text>
Expand Down
55 changes: 42 additions & 13 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'react-native-reanimated';
import { useFonts } from 'expo-font';
import FontAwesome from '@expo/vector-icons/FontAwesome';
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { View } from 'react-native';
import { StatusBar } from 'expo-status-bar';

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

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

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

// Enable push notifications and observer
usePushNotifications();
useNotificationObserver();

// Register background fetch task
useEffect(() => {
registerBackgroundFetch();
Expand All @@ -91,14 +90,30 @@ export default function RootLayout() {
if (!loaded) return null;

return (
<AuthProvider>
<RootLayoutNav />
</AuthProvider>
<SessionProvider>
<AuthProvider>
<NotificationBootstrap />
<RootLayoutNav />
</AuthProvider>
</SessionProvider>
);
}

// Forced dark theme (can be extended later for dynamic theming)
const ForcedDarkTheme = {
...DarkTheme,
colors: {
...DarkTheme.colors,
background: '#000000',
card: '#000000',
text: '#ffffff',
border: '#222222',
primary: '#ffffff',
},
};

function RootLayoutNav() {
const colorScheme = useColorScheme();
// const colorScheme = useColorScheme(); // Not used since we force dark
const { user } = useAuth();
// Avoid logging during render to prevent side-effects in LogViewer
useEffect(() => {
Expand All @@ -112,13 +127,27 @@ function RootLayoutNav() {
return (
<ErrorBoundary>
<NotificationProvider>
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
{user ? <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> : <Stack.Screen name="login" options={{ headerShown: false }} />}
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack>
<ThemeProvider value={ForcedDarkTheme}>
<View style={{ flex: 1, backgroundColor: '#000' }}>
<StatusBar style="light" backgroundColor="#000" translucent={false} />
<Stack screenOptions={{
contentStyle: { backgroundColor: '#000' },
headerStyle: { backgroundColor: '#000' },
}}>
{user ? <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> : <Stack.Screen name="login" options={{ headerShown: false }} />}
<Stack.Screen name="debug" options={{ title: 'Debug' }} />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack>
</View>
</ThemeProvider>
</NotificationProvider>
</ErrorBoundary>
);
}

// Separate component so hooks mount within providers
function NotificationBootstrap() {
usePushNotifications();
useNotificationObserver();
return null;
}
129 changes: 129 additions & 0 deletions app/debug.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { useCallback, useEffect, useState } from 'react';
import { View, Text, StyleSheet, ScrollView, Pressable } from 'react-native';
import { useSessionUser, useSession } from '@/providers/SessionProvider';
import { BACKGROUND_TASK_IDENTIFIER, getBackgroundTaskRegistrationState, registerBackgroundFetch, unregisterBackgroundTask, triggerBackgroundTaskForTesting } from '@/utils/tasks.utils';

interface BgState {
status: number | null;
isRegistered: boolean;
loading: boolean;
lastAction?: string;
}

export default function DebugScreen() {
const { fcmToken, authReady, fcmReady, user, accessToken } = useSessionUser();
const { session } = useSession();
const [bg, setBg] = useState<BgState>({ status: null, isRegistered: false, loading: false });

const refresh = useCallback(async () => {
setBg((s) => ({ ...s, loading: true }));
try {
const r = await getBackgroundTaskRegistrationState();
setBg((s) => ({ ...s, ...r, loading: false }));
} catch (e) {
setBg((s) => ({ ...s, loading: false, lastAction: 'refresh failed' }));
}
}, []);

useEffect(() => {
refresh();
}, [refresh]);

const register = async () => {
setBg((s) => ({ ...s, loading: true }));
await registerBackgroundFetch();
await refresh();
setBg((s) => ({ ...s, lastAction: 'registered' }));
};
const unregister = async () => {
setBg((s) => ({ ...s, loading: true }));
await unregisterBackgroundTask();
await refresh();
setBg((s) => ({ ...s, lastAction: 'unregistered' }));
};
const trigger = async () => {
setBg((s) => ({ ...s, lastAction: 'triggering' }));
await triggerBackgroundTaskForTesting();
};

const tokenTail = fcmToken ? fcmToken.slice(-10) : 'none';

return (
<ScrollView style={styles.root} contentContainerStyle={styles.content}>
<Text style={styles.h1}>Debug</Text>
<Section title="Session">
<Mono label="FCM Ready" value={String(fcmReady)} />
<Mono label="Auth Ready" value={String(authReady)} />
<Mono label="FCM Token Tail" value={tokenTail} />
<Mono label="User Email" value={user?.email || '—'} />
<Mono label="Access Token" value={accessToken ? accessToken.slice(0, 15) + '…' : '—'} />
</Section>
<Section title="Raw Session Snapshot">
<Text style={styles.code}>{JSON.stringify(session, null, 2)}</Text>
</Section>
<Section title="Background Task">
<Mono label="Identifier" value={BACKGROUND_TASK_IDENTIFIER} />
<Mono label="Status" value={bg.status === null ? '—' : String(bg.status)} />
<Mono label="Registered" value={String(bg.isRegistered)} />
<Mono label="Last Action" value={bg.lastAction || '—'} />
<View style={styles.rowWrap}>
<Btn label="Refresh" onPress={refresh} disabled={bg.loading} />
<Btn label="Register" onPress={register} disabled={bg.loading || bg.isRegistered} />
<Btn label="Unregister" onPress={unregister} disabled={bg.loading || !bg.isRegistered} />
<Btn label="Trigger(dev)" onPress={trigger} />
</View>
</Section>
<Text style={styles.footer}>Build Debug Utilities v1</Text>
</ScrollView>
);
}

function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>{title}</Text>
{children}
</View>
);
}

function Mono({ label, value }: { label: string; value: string }) {
return (
<View style={styles.kvRow}>
<Text style={styles.kvLabel}>{label}</Text>
<Text style={styles.kvValue}>{value}</Text>
</View>
);
}

function Btn({ label, onPress, disabled }: { label: string; onPress: () => void; disabled?: boolean }) {
return (
<Pressable
onPress={disabled ? undefined : onPress}
style={[styles.btn, disabled && styles.btnDisabled]}
android_ripple={{ color: '#222' }}
>
<Text style={styles.btnText}>{label}</Text>
</Pressable>
);
}

const styles = StyleSheet.create({
root: { flex: 1, backgroundColor: '#000' },
content: { padding: 16, paddingBottom: 48 },
h1: { fontSize: 26, fontWeight: '600', color: '#fff', marginBottom: 12 },
section: { marginBottom: 24, backgroundColor: '#111', borderRadius: 8, padding: 12 },
sectionTitle: { color: '#9acd32', fontWeight: '600', fontSize: 16, marginBottom: 8 },
kvRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 4 },
kvLabel: { color: '#bbb', fontSize: 13 },
kvValue: { color: '#fff', fontFamily: 'monospace', fontSize: 13, flexShrink: 1, textAlign: 'right' },
code: { color: '#8f8', fontFamily: 'monospace', fontSize: 12 },
rowWrap: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 8 },
btn: { backgroundColor: '#222', paddingVertical: 8, paddingHorizontal: 12, borderRadius: 6, marginRight: 8, marginBottom: 8 },
btnDisabled: { opacity: 0.4 },
btnText: { color: '#fff', fontSize: 12, fontWeight: '600' },
footer: { textAlign: 'center', color: '#444', fontSize: 12 },
row: { flexDirection: 'row', alignItems: 'center', gap: 12 },
debugButton: { marginLeft: 12, backgroundColor: '#333', paddingHorizontal: 14, paddingVertical: 10, borderRadius: 6, justifyContent: 'center' },
debugButtonText: { color: '#fff', fontWeight: '600' },
});
Loading