import React, {useState, useEffect, useCallback, useRef} from 'react';
import {
CommonActions,
NavigationContainer,
useNavigationContainerRef,
} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {
ActivityIndicator,
View,
StyleSheet,
Alert,
Text,
Dimensions,
} from 'react-native';
import Toast from 'react-native-toast-message';
import {LogBox} from 'react-native';
// Contexts and Utils
import AsyncStorageManager from '../utils/AsyncStorageManager'; // Adjust path if needed
import {UserProvider, useUser} from '../context/UserContext'; // Adjust path if needed
import {
MqttProvider,
useMqtt,
MqttConnectionState,
} from '../context/MqttContext'; // Adjust path if needed
// Screens and Navigators
import LoginScreen from '../screens/LoginScreen'; // Adjust path if needed
import MainTabNavigator from './MainTabNavigator'; // Adjust path if needed
import CommunityItemDetailScreen from '../screens/CommunityItemDetailScreen'; // <<< Import the detail screen
import GalleryScreen from '../screens/GalleryScreen';
import FrontCameraScreen from '../screens/FrontCameraScreen';
import DeviceScreen from '../screens/DeviceScreen';
import ZoneScreen from '../screens/ZoneScreen';
import AlarmsScreen from '../screens/AlarmsScreen';
import DeviceItemScreen from '../screens/DeviceItemScreen';
import GiveAccessScreen from '../screens/GiveAccessScreen';
import ViewAccessScreen from '../screens/AccessTabs/ViewAccessScreen';
// Notification Service
import NotificationService, {
ACTION_ACCEPT,
ACTION_DECLINE,
ACTION_SILENCE,
} from '../services/NotificationService'; // Adjust path if needed
import CCTVScreen from '../screens/CameraTabs/CCTVScreen';
import ZoneItemScreen from '../screens/ZoneItemScreen';
import AmenityScreen from '../screens/AmenityScreen';
import EventScreen from '../screens/EventScreen';
import CommunityScreen from '../screens/CommunityScreen';
import AmenityDetailsScreen from '../screens/AmenityDetailsScreen';
import EventDetailsScreen from '../screens/EventDetailsScreen';
import BookingScreen from '../screens/BookingTabs/BookingScreen';
import InviteGuestScreen from '../screens/InviteGuestScreen';
import messaging from '@react-native-firebase/messaging';
const Stack = createNativeStackNavigator();
const {height: screenHeight} = Dimensions.get('window');
// --- Notification Handling Logic (Keep as before) ---
const handleNotificationOpen = (navigationRef, data, action) => {
console.log('Handling Notification Open/Action:', {data, action});
if (!data) {
console.warn('No data found in notification to handle.');
return;
}
const targetScreen = data.screen;
const notificationId = data.localNotificationId; // Assuming you pass this in userInfo
// Example action handling (customize as needed)
if (action) {
console.log(
`Action '${action}' taken on notification ID: ${notificationId}.`,
);
// Handle specific actions like ACCEPT, DECLINE, SILENCE
if (action === ACTION_ACCEPT) {
console.log(`Handling ACCEPT action.`);
// Navigate or perform accept logic
if (targetScreen && navigationRef.isReady()) {
navigationRef.navigate(targetScreen, {
notificationData: data,
actionTaken: action,
});
}
} else if (action === ACTION_DECLINE) {
console.log(`Handling DECLINE action.`);
// Perform decline logic
} else if (action === ACTION_SILENCE) {
console.log(`Handling SILENCE action.`);
// Perform silence logic
}
// Optionally cancel the notification after handling the action
// notificationService.current?.cancelNotificationById(notificationId); // Need access to service instance
} else {
// --- Notification Tapped ---
console.log(`Handling TAP action for notification ID: ${notificationId}`);
if (targetScreen && navigationRef.isReady()) {
console.log(`Navigating to screen: ${targetScreen}`);
// Ensure navigation happens correctly, potentially checking if already on the screen
// or using reset/push depending on desired behavior.
// Check if the target screen is the detail screen
if (targetScreen === 'CommunityItemDetail') {
// Ensure data contains the necessary 'item' for the detail screen
if (data.item) {
navigationRef.navigate(targetScreen, {item: data.item});
} else {
console.warn(
'Notification tapped for CommunityItemDetail but missing item data.',
);
// Optionally navigate to a default state or show an error
}
} else {
// Handle navigation for other screens
navigationRef.navigate(targetScreen, {notificationData: data});
}
} else if (!targetScreen) {
console.log('No target screen specified in notification data.');
} else {
console.warn('Navigation not ready when notification was opened.');
// Optionally queue the navigation action until ready
}
}
};
// --- Main Content Component ---
const AppNavigationContent = () => {
const [isLoading, setIsLoading] = useState(true);
const [isLoggedIn, setIsLoggedIn] = useState(false);
let fcmToken = '';
const {setUserDetails, clearUserDetails, updateUserDetails} = useUser(); // Added updateUserDetails
const {
connect: connectMqtt,
disconnect: disconnectMqtt,
connectionState: mqttState,
publish,
} = useMqtt();
const shownConnectedToastRef = useRef(false);
const navigationRef = useNavigationContainerRef();
const notificationService = useRef(null);
const [notificationInitialized, setNotificationInitialized] = useState(false);
// Initialize Notification Service Ref (runs only once)
useEffect(() => {
LogBox.ignoreAllLogs(true);
const onRegisterToken = token => {
console.log('AppNavigator received FCM Token:', token);
fcmToken = token;
};
const onNotificationOpened = (data, action) => {
// Ensure navigationRef is available and ready if needed
handleNotificationOpen(navigationRef, data, action);
};
console.log('AppNavigator: Creating NotificationService instance...');
// Pass the callbacks when creating the instance
notificationService.current = new NotificationService(
onNotificationOpened,
onRegisterToken,
);
// Don't initialize immediately here, wait for login status
}, []); // Empty dependency array ensures this runs only once
// Effect to initialize notifications AFTER login status is confirmed
useEffect(() => {
// Only initialize if logged in AND not already initialized
if (isLoggedIn && notificationService.current && !notificationInitialized) {
console.log(
'User logged in, initializing Notification Service (will request permissions)...',
);
// Call initialize() which now includes requesting permissions
notificationService.current
.initialize()
.then(() => {
console.log('Notification Service initialization attempt complete.');
setNotificationInitialized(true); // Mark as initialized (or attempted)
})
.catch(error => {
console.error(
'Error during Notification Service initialization:',
error,
);
setNotificationInitialized(true); // Mark as initialized even on error to prevent retries
});
} else if (!isLoggedIn && notificationInitialized) {
// Optional: Reset initialization status if user logs out
console.log(
'User logged out, resetting notification initialization status.',
);
setNotificationInitialized(false);
}
}, [isLoggedIn, notificationInitialized]); // Depend on login status and initialization status
// MQTT Connection Toast (Keep as before)
useEffect(() => {
if (mqttState === MqttConnectionState.CONNECTED) {
if (!shownConnectedToastRef.current) {
Toast.show({
type: 'success',
text1: 'MQTT Status',
text2: 'Successfully connected! 👋',
position: 'bottom',
visibilityTime: 3000,
});
shownConnectedToastRef.current = true;
}
} else if (
mqttState !== MqttConnectionState.CONNECTING &&
mqttState !== MqttConnectionState.RECONNECTING
) {
if (shownConnectedToastRef.current) {
shownConnectedToastRef.current = false;
}
}
}, [mqttState]);
useEffect(() => {
let isMounted = true;
const checkLoginStatus = async () => {
setIsLoading(true); // Start loading indicator
try {
const storedUserData = await AsyncStorageManager.getItem('userData');
console.log('isMounted', isMounted);
console.log('storedUserData', storedUserData);
if (isMounted) {
if (storedUserData && storedUserData.token) {
// Check only for token existence
console.log('storedUserData after ismount', storedUserData);
console.log('User session found.');
setUserDetails(storedUserData);
setIsLoggedIn(true);
} else {
console.log('No user session found.');
setIsLoggedIn(false);
clearUserDetails();
}
}
} catch (error) {
console.error('AppNavigator: Failed to load user data:', error);
if (isMounted) {
Alert.alert('Error', 'Failed to load session data.');
setIsLoggedIn(false);
clearUserDetails();
}
} finally {
if (isMounted) setIsLoading(false); // Session check complete
}
};
checkLoginStatus();
return () => {
isMounted = false;
};
}, [setUserDetails, clearUserDetails]); // Removed isLoggedIn from dependency array
// Connect/Disconnect MQTT based on Login Status (Keep as before)
useEffect(() => {
if (isLoggedIn) {
if (
mqttState === MqttConnectionState.DISCONNECTED ||
mqttState === MqttConnectionState.CLOSED ||
mqttState === MqttConnectionState.ERROR
) {
console.log('User logged in, connecting MQTT...');
connectMqtt();
}
} else {
if (
mqttState === MqttConnectionState.CONNECTED ||
mqttState === MqttConnectionState.CONNECTING ||
mqttState === MqttConnectionState.RECONNECTING
) {
console.log('User logged out, disconnecting MQTT...');
disconnectMqtt();
}
}
}, [isLoggedIn, connectMqtt, disconnectMqtt, mqttState]);
// Login Handler (Keep as before)
const handleLoginSuccess = useCallback(
loggedInUserData => {
setIsLoading(true); // Show loading briefly while setting state
console.log('loggedInUserData', loggedInUserData);
if (loggedInUserData && loggedInUserData.profileDetails) {
setUserDetails(loggedInUserData);
setIsLoggedIn(true);
console.log('fcmToken', loggedInUserData);
messaging()
.subscribeToTopic('advaint_global')
.then(() => {
console.log('Successfully subscribed to advaint_global topic');
})
.catch(err => {
console.log('Error subscribing to advaint_global topic:', err);
});
messaging()
.subscribeToTopic(
loggedInUserData.profileDetails.society_id.toString(),
)
.then(() => {
console.log('Successfully subscribed to society topic');
})
.catch(err => {
console.log('Error subscribing to society topic:', err);
});
const mqtt_firebase_token_flat =
'advaint/addfirebasetoken/cus_' +
loggedInUserData.profileDetails.customer_id;
const mqtt_firebase_token_society =
'advaint/addfirebasetoken/soc_' +
loggedInUserData.profileDetails.society_id;
const firebasebTokenMessage = {
contact_id: loggedInUserData.profileDetails.flat_contact_id,
email: loggedInUserData.profileDetails.email,
firebase_token: fcmToken,
};
const firebasebTokenMessageForSociety = {
contact_id: loggedInUserData.profileDetails.flat_contact_id,
email: loggedInUserData.profileDetails.email,
firebase_token: fcmToken,
};
console.log(
'firebasebTokenMessageForSociety',
mqtt_firebase_token_society,
firebasebTokenMessageForSociety,
);
console.log(
'firebasebTokenMessage',
mqtt_firebase_token_flat,
firebasebTokenMessage,
);
publish(mqtt_firebase_token_society, firebasebTokenMessageForSociety);
publish(mqtt_firebase_token_flat, firebasebTokenMessage);
} else {
Alert.alert('Login Error', 'Failed to process login data.');
setIsLoggedIn(false);
clearUserDetails();
}
setIsLoading(false);
},
[setUserDetails, clearUserDetails],
);
const handleLogout = useCallback(async () => {
try {
// Clear AsyncStorage first
await AsyncStorageManager.clearAll();
console.log('AsyncStorage cleared during logout.');
// Clear user details from context
clearUserDetails();
console.log('User context cleared.');
// Update isLoggedIn state
setIsLoggedIn(false);
console.log('isLoggedIn state set to false.');
// Reset notification initialization state
setNotificationInitialized(false);
console.log('Notification initialization state reset.');
if (navigationRef.isReady()) {
console.log('Resetting navigation stack to Login.');
navigationRef.dispatch(
CommonActions.reset({
index: 0,
routes: [{name: 'Login'}],
}),
);
} else {
console.warn(
'Navigation not ready when attempting to reset after logout delay.',
);
// Fallback: try resetting without delay if navigation wasn't ready initially
navigationRef.dispatch(
CommonActions.reset({
index: 0,
routes: [{name: 'Login'}],
}),
);
}
// 500ms delay
} catch (error) {
setTimeout(() => {
console.error('Logout failed:', error);
Alert.alert('Logout Error', 'An error occurred during logout.');
// Even if an error occurs during logout (e.g., AsyncStorage),
// we still want to attempt to reset the UI state and navigation.
clearUserDetails();
setIsLoggedIn(false);
setNotificationInitialized(false);
if (navigationRef.isReady()) {
console.log('Resetting navigation stack to Login after error.');
navigationRef.dispatch(
CommonActions.reset({
index: 0,
routes: [{name: 'Login'}],
}),
);
} else {
console.warn(
'Navigation not ready when attempting to reset after logout error delay.',
);
navigationRef.dispatch(
CommonActions.reset({
index: 0,
routes: [{name: 'Login'}],
}),
);
}
}, 500); // 500ms delay
}
}, [clearUserDetails, navigationRef]);
// Loading State UI
if (isLoading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#0000ff" />
<Text style={styles.loadingText}>Loading session...</Text>
</View>
);
}
// Navigation Rendering
return (
<NavigationContainer ref={navigationRef}>
{/* The main stack navigator now includes Login, MainApp (Tabs), and the Detail screen */}
<Stack.Navigator
// Default screen options to apply to all screens
screenOptions={{
headerStyle: {height: screenHeight * 0.07}, // <- Global header height is set here
}}>
{isLoggedIn ? (
<>
<Stack.Screen
name="MainApp" // This holds the Tab Navigator
options={{headerShown: false}} // Hide header for the Tab container itself
>
{props => (
<MainTabNavigator {...props} handleLogout={handleLogout} />
)}
</Stack.Screen>
{/* Add CommunityItemDetailScreen HERE, outside MainApp but within the logged-in group */}
<Stack.Screen
name="CommunityItemDetail"
component={CommunityItemDetailScreen}
options={({route}) => ({
title: route.params?.item?.title || 'Item Details',
headerShown: true, // <<< Make sure header is shown for the detail screen
headerBackTitleVisible: false, // Optional: hide back button text on iOS
})}
/>
<Stack.Screen
name="GalleryScreen"
component={GalleryScreen}
options={({route}) => ({
title: route.params?.item?.title || 'Gallery',
headerShown: true, // <<< Make sure header is shown for the detail screen
headerBackTitleVisible: false, // Optional: hide back button text on iOS
})}
/>
<Stack.Screen
name="FrontCameraScreen"
component={FrontCameraScreen}
options={({route}) => ({
title: 'FRONT CAMERA',
headerTitleAlign: 'center', // Add this line to center the title
headerShown: true,
headerBackTitleVisible: false, // Optional: hide back button text on iOS
})}
/>
<Stack.Screen
name="DeviceScreen"
component={DeviceScreen}
options={({route}) => ({
title: 'DEVICES',
headerTitleAlign: 'center', // Add this line to center the title
headerShown: true,
headerBackTitleVisible: false, // Optional: hide back button text on iOS
})}
/>
<Stack.Screen
name="ZoneScreen"
component={ZoneScreen}
options={({route}) => ({
title: 'ZONES',
headerTitleAlign: 'center', // Add this line to center the title
headerShown: true,
headerBackTitleVisible: false, // Optional: hide back button text on iOS
})}
/>
<Stack.Screen
name="AlarmsScreen"
component={AlarmsScreen}
options={({route}) => ({
title: 'ALARMS',
headerTitleAlign: 'center', // Add this line to center the title
headerShown: true,
headerBackTitleVisible: false, // Optional: hide back button text on iOS
})}
/>
<Stack.Screen
name="CCTVScreen"
component={CCTVScreen}
options={({route}) => ({
title: 'CAMERAS',
headerTitleAlign: 'center', // Add this line to center the title
headerShown: true,
headerBackTitleVisible: false, // Optional: hide back button text on iOS
})}
/>
<Stack.Screen
name="DeviceItemScreen"
component={DeviceItemScreen}
options={({route}) => ({
title: route.params?.device_name || 'DEVICES',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
<Stack.Screen
name="ZoneItemScreen"
component={ZoneItemScreen}
options={({route}) => ({
title: route.params?.zone_name || 'DEVICES',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
<Stack.Screen
name="GiveAccessScreen"
component={GiveAccessScreen}
options={({route}) => ({
title: 'GIVE ACCESS',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
<Stack.Screen
name="InviteGuestScreen"
component={InviteGuestScreen}
options={({route}) => ({
title: 'GUEST',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
<Stack.Screen
name="ViewAccessScreen"
component={ViewAccessScreen}
options={({route}) => ({
title: 'ACCESS',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
<Stack.Screen
name="AmenityScreen"
component={AmenityScreen}
options={({route}) => ({
title: 'AMENITIES',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
<Stack.Screen
name="EventScreen"
component={EventScreen}
options={({route}) => ({
title: 'EVENTS',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
<Stack.Screen
name="CommunityScreen"
component={CommunityScreen}
options={({route}) => ({
title: 'COMMUNITY',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
<Stack.Screen
name="AmenityDetailsScreen"
component={AmenityDetailsScreen}
options={({route}) => ({
title: 'BOOKING',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
<Stack.Screen
name="EventDetailsScreen"
component={EventDetailsScreen}
options={({route}) => ({
title: 'BOOKING',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
<Stack.Screen
name="BookingScreen"
component={BookingScreen}
options={({route}) => ({
title: 'BOOKINGS',
headerTitleAlign: 'center',
headerShown: true,
headerBackTitleVisible: false,
})}
/>
{/* Add other globally accessible logged-in screens here (e.g., Settings, Profile) */}
</>
) : (
// Logged-out screen(s)
<Stack.Screen
name="Login"
options={{headerShown: false}} // Hide header for Login screen
>
{/* Pass fcmToken down to LoginScreen if needed for display/debug */}
{props => (
<LoginScreen
{...props}
onLoginSuccess={handleLoginSuccess}
fcmToken={fcmToken}
/>
)}
</Stack.Screen>
)}
</Stack.Navigator>
<Toast />
</NavigationContainer>
);
};
// --- Main AppNavigator Component (Wraps Providers) ---
const AppNavigator = () => {
return (
<UserProvider>
<MqttProvider>
<AppNavigationContent />
</MqttProvider>
</UserProvider>
);
};
const styles = StyleSheet.create({
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
loadingText: {marginTop: 10, fontSize: 14, color: '#666'},
});
export default AppNavigator;
got log in console.log('AppNavigator received FCM Token:', token);
but in const firebasebTokenMessageForSociety = {
contact_id: loggedInUserData.profileDetails.flat_contact_id,
email: loggedInUserData.profileDetails.email,
firebase_token: fcmToken,
};
console.log(
'firebasebTokenMessageForSociety',
mqtt_firebase_token_society,
firebasebTokenMessageForSociety,
); fcmToken is empty