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/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';
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);
const [fcmToken, setFcmToken] = useState(null);
const {setUserDetails, clearUserDetails, updateUserDetails} = useUser(); // Added updateUserDetails
const {
connect: connectMqtt,
disconnect: disconnectMqtt,
connectionState: mqttState,
} = 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);
setFcmToken(token); // Store the token
// TODO: Send the token to your backend server here if needed
// if (token) { sendTokenToServer(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
if (loggedInUserData && loggedInUserData.profileDetails) {
setUserDetails(loggedInUserData); // Store the entire loggedInUserData
setIsLoggedIn(true); // This will trigger the useEffect to initialize notifications
// No need to call initialize here directly, the effect handles it
} else {
Alert.alert('Login Error', 'Failed to process login data.');
setIsLoggedIn(false);
clearUserDetails();
}
setIsLoading(false);
},
[setUserDetails, clearUserDetails],
);
// Logout Handler (Keep as before)
const handleLogout = useCallback(async () => {
try {
await AsyncStorageManager.removeItem('userData');
} catch (error) {
console.error('Logout Error clearing storage:', error);
} finally {
clearUserDetails();
// 👇 Update state first
setIsLoggedIn(false);
// 👇 Delay reset until after state change
setTimeout(() => {
navigationRef.current?.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'Login' }],
})
);
}, 0);
}
}, [clearUserDetails]);
// 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
>
{/* Pass onLogout to the component containing the logout button */}
{props => <MainTabNavigator {...props} onLogout={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: 'ACCESS',
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;
import React from 'react';
import {Image, Dimensions} from 'react-native';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import HomeScreen from '../screens/tabs/HomeScreen';
import SceneScreen from '../screens/tabs/SceneScreen';
import ScheduleScreen from '../screens/tabs/ScheduleScreen';
import SettingsScreen from '../screens/tabs/SettingsScreen';
import {COLORS} from '../styles/colors';
import {HOME, SCENES, SCHEDULE, SETTINGS} from '../assets';
const Tab = createBottomTabNavigator();
const {height: screenHeight} = Dimensions.get('window');
const commonHeaderOptions = {
headerTitleAlign: 'center',
headerStyle: {
backgroundColor: COLORS.status_bar_color,
height: screenHeight * 0.07,
},
headerTintColor: '#333', // Color of title and back button
headerTitleStyle: {
fontWeight: 'bold',
fontSize: 18,
},
};
const TabNavigator = ({onLogout}) => {
return (
<Tab.Navigator
screenOptions={({route}) => ({
...commonHeaderOptions, // Apply common header styles to all tab screens
tabBarIcon: ({focused, color, size}) => {
let iconName;
let iconColor = color; // Default icon color
if (route.name === 'Home') {
iconName = focused ? HOME : HOME;
if (focused) {
iconColor = 'red'; // Change color to red when Home is focused
}
} else if (route.name === 'Scene') {
iconName = focused ? SCENES : SCENES; // Changed to layers icon
} else if (route.name === 'Schedules') {
iconName = focused ? SCHEDULE : SCHEDULE; // Changed to calendar icon
} else if (route.name === 'Settings') {
iconName = focused ? SETTINGS : SETTINGS;
}
// You can return any component that you like here!
return (
<Image
style={{
height: 25,
width: 25,
tintColor: focused && COLORS.red_color,
}}
source={iconName}
/>
);
},
tabBarActiveTintColor: COLORS.red_color, // Color for active tab (except Home when focused)
tabBarInactiveTintColor: 'gray', // Color for inactive tabs
tabBarStyle: {
backgroundColor: '#ffffff', // Background color of the tab bar
height: screenHeight * 0.08, // Increased height of the tab bar
// borderTopWidth: 0, // Optional: remove top border
// elevation: 5, // Optional: add shadow for Android
},
tabBarLabelStyle: {
fontSize: 12,
marginBottom: 10, // Space below tab labels
marginTop: 0,
paddingBottom: 5,
},
tabBarIconStyle: {
marginTop: 3, // Adjust icon position
},
})}>
<Tab.Screen
name="Home"
component={HomeScreen}
options={{title: 'Home', height: 20, backgroundColor: 'red'}}
/>
<Tab.Screen
name="Scene"
component={SceneScreen}
options={{title: 'Scenes'}}
/>
<Tab.Screen
name="Schedules"
component={ScheduleScreen}
options={{title: 'Schedules'}}
/>
{/* <Tab.Screen
name="Settings"
component={SettingsScreen}
options={{title: 'Settings'}}
/> */}
<Tab.Screen name="Settings">
{props => <SettingsScreen {...props} onLogout={onLogout} />}
</Tab.Screen>
</Tab.Navigator>
);
};
export default TabNavigator;
import React from 'react';
import {
View,
Text,
FlatList,
StyleSheet,
TouchableOpacity,
Alert,
} from 'react-native';
import {CommonActions} from '@react-navigation/native';
import {useUser, clearUserDetails} from '../../context/UserContext';
import ApiManager from '../../services/ApiManager';
import AsyncStorageManager from '../../utils/AsyncStorageManager';
const SettingsScreen = ({navigation}) => {
const data = [
{
id: '1',
title: 'Notifications',
description: 'Manage push notification preferences.',
},
{id: '2', title: 'Account', description: 'Update your email or password.'},
{id: '3', title: 'Privacy', description: 'Read our Privacy Policy.'},
{
id: '4',
title: 'Terms of Service',
description: 'Review the terms and conditions.',
},
{id: '5', title: 'About', description: 'App version 1.0.0'},
{id: '6', title: 'Logout', description: 'Log out from app'},
];
const {userDetails} = useUser();
console.log('userDetails', userDetails);
const handleLogout = async () => {
const email = userDetails.profileDetails?.email;
const contactId = userDetails?.profileDetails?.contact_id;
const societyId = userDetails?.profileDetails?.society_id;
try {
if (email && contactId && societyId) {
await ApiManager.logout(email, contactId, societyId);
console.log('Logout API call finished.');
} else {
console.warn('Missing user details, skipping logout API call.');
}
await AsyncStorageManager.removeItem('userData');
clearUserDetails();
setTimeout(() => {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{name: 'Login'}],
}),
);
}, 50);
} catch (error) {
console.error('Logout failed:', error);
Alert.alert('Logout Error', 'Something went wrong while logging out.');
}
};
const handleLogoutPress = () => {
Alert.alert(
'Logout',
'Are you sure you want to logout?',
[
{text: 'Cancel', style: 'cancel'},
{text: 'OK', onPress: handleLogout},
],
{cancelable: true},
);
};
const renderItem = ({item}) => (
<TouchableOpacity
onPress={() => {
if (item.id == '6') {
handleLogoutPress();
}
}}
style={styles.itemContainer}>
<Text style={styles.itemText}>{item.title}</Text>
{item.description && (
<Text style={styles.itemDescription}>{item.description}</Text>
)}
</TouchableOpacity>
);
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
ListEmptyComponent={<Text style={styles.emptyText}>No items yet.</Text>}
contentContainerStyle={styles.listContentContainer}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
listContentContainer: {
padding: 15,
},
itemContainer: {
backgroundColor: '#f9f9f9',
padding: 18,
marginVertical: 8,
borderRadius: 8,
borderWidth: 1,
borderColor: '#eee',
shadowColor: '#000',
shadowOffset: {width: 0, height: 1},
shadowOpacity: 0.1,
shadowRadius: 1.41,
elevation: 2,
},
itemText: {
fontSize: 17,
fontWeight: '500',
color: '#333',
},
itemDescription: {
fontSize: 14,
color: '#666',
marginTop: 4,
},
emptyText: {
textAlign: 'center',
marginTop: 50,
fontSize: 16,
color: '#888',
},
});
export default SettingsScreen;
on logout i want to go to login screen