Files
mobile-darmasaba/app/(application)/_layout.tsx
2026-02-24 17:44:49 +08:00

335 lines
14 KiB
TypeScript

import HeaderRightAnnouncementList from "@/components/announcement/headerAnnouncementList";
import AppHeader from "@/components/AppHeader";
import Styles from "@/constants/Styles";
import HeaderDiscussionGeneral from "@/components/discussion_general/headerDiscussionGeneral";
import HeaderRightDivisionList from "@/components/division/headerDivisionList";
import HeaderRightGroupList from "@/components/group/headerGroupList";
import HeaderMemberList from "@/components/member/headerMemberList";
import HeaderRightPositionList from "@/components/position/headerRightPositionList";
import HeaderRightProjectList from "@/components/project/headerProjectList";
import Text from "@/components/Text";
import ToastCustom from "@/components/toastCustom";
import ModalUpdateMaintenance from "@/components/ModalUpdateMaintenance";
import { apiGetVersion, apiReadOneNotification } from "@/lib/api";
import { pushToPage } from "@/lib/pushToPage";
import store from "@/lib/store";
import { useAuthSession } from "@/providers/AuthProvider";
import AsyncStorage from "@react-native-async-storage/async-storage";
import Constants from "expo-constants";
import { getApp } from "@react-native-firebase/app";
import { getMessaging, onMessage } from "@react-native-firebase/messaging";
import { Redirect, router, Stack, usePathname } from "expo-router";
import { StatusBar } from 'expo-status-bar';
import { useEffect, useState } from "react";
import { Easing, Notifier, NotifierComponents } from 'react-native-notifier';
import { Provider } from "react-redux";
import { useTheme } from "@/providers/ThemeProvider";
export default function RootLayout() {
const { token, decryptToken, isLoading } = useAuthSession()
const pathname = usePathname()
const { colors } = useTheme()
const [modalUpdateMaintenance, setModalUpdateMaintenance] = useState(false)
const [modalType, setModalType] = useState<'update' | 'maintenance'>('update')
const [isForceUpdate, setIsForceUpdate] = useState(false)
const [updateMessage, setUpdateMessage] = useState('')
const currentVersion = Constants.expoConfig?.version ?? '0.0.0'
const compareVersions = (v1: string, v2: string) => {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const p1 = parts1[i] || 0;
const p2 = parts2[i] || 0;
if (p1 < p2) return -1;
if (p1 > p2) return 1;
}
return 0;
};
useEffect(() => {
const checkVersion = async () => {
try {
const response = await apiGetVersion();
if (response.success && response.data) {
const maintenance = response.data.find((item: any) => item.id === 'mobile_maintenance')?.value === 'true';
const latestVersion = response.data.find((item: any) => item.id === 'mobile_latest_version')?.value || '0.0.0';
const minVersion = response.data.find((item: any) => item.id === 'mobile_minimum_version')?.value || '0.0.0';
const message = response.data.find((item: any) => item.id === 'mobile_message_update')?.value || '';
if (maintenance) {
setModalType('maintenance');
setModalUpdateMaintenance(true);
setIsForceUpdate(true);
return;
}
if (compareVersions(currentVersion, minVersion) === -1) {
setModalType('update');
setIsForceUpdate(true);
setUpdateMessage(message);
setModalUpdateMaintenance(true);
} else if (compareVersions(currentVersion, latestVersion) === -1) {
// Check if this soft update version was already dismissed
const dismissedVersion = await AsyncStorage.getItem('dismissed_update_version');
if (dismissedVersion !== latestVersion) {
setModalType('update');
setIsForceUpdate(false);
setUpdateMessage(message);
setModalUpdateMaintenance(true);
}
}
}
} catch (error) {
console.error('Failed to check version:', error);
}
};
checkVersion();
}, [currentVersion]);
const handleDismissUpdate = async () => {
if (!isForceUpdate) {
try {
const response = await apiGetVersion();
const latestVersion = response.data.find((item: any) => item.id === 'mobile_latest_version')?.value;
if (latestVersion) {
await AsyncStorage.setItem('dismissed_update_version', latestVersion);
}
} catch (e) {
console.error(e);
}
setModalUpdateMaintenance(false);
}
}
async function handleReadNotification(id: string, category: string, idContent: string, title: string) {
try {
if (title != "Komentar Baru") {
const hasil = await decryptToken(String(token?.current))
const response = await apiReadOneNotification({ user: hasil, id: id })
}
pushToPage(category, idContent)
} catch (error) {
console.error(error)
}
}
useEffect(() => {
const checkNavigation = async () => {
const navData = await AsyncStorage.getItem('navigateOnOpen');
if (navData) {
const { screen, content } = JSON.parse(navData);
await AsyncStorage.removeItem('navigateOnOpen'); // reset
pushToPage(screen, content)
}
};
checkNavigation();
}, []);
useEffect(() => {
const mess = getMessaging(getApp());
const unsubscribe = onMessage(mess, async remoteMessage => {
const id = remoteMessage?.data?.id;
const category = remoteMessage?.data?.category;
const content = remoteMessage?.data?.content;
const title = remoteMessage?.notification?.title;
if (remoteMessage.notification?.title && remoteMessage.notification?.body) {
if (category === 'discussion-general' && pathname === '/discussion/' + content) {
return null;
} else if (pathname !== `/${category}/${content}`) {
Notifier.showNotification({
title: title,
description: String(remoteMessage.notification?.body),
duration: 3000,
animationDuration: 300,
showEasing: Easing.ease,
onPress: () => handleReadNotification(String(id), String(category), String(content), String(title)),
hideOnPress: true,
Component: NotifierComponents.Notification,
componentProps: {
containerStyle: [
Styles.shadowBox,
{
backgroundColor: colors.modalBackground,
borderRadius: 5,
marginHorizontal: 15,
marginTop: 10,
padding: 15,
}
],
titleStyle: {
color: colors.text,
fontWeight: 'bold',
fontSize: 16,
},
descriptionStyle: {
color: colors.dimmed,
fontSize: 14,
},
}
});
}
}
});
return unsubscribe;
}, [pathname]);
if (isLoading) {
return <Text>Loading...</Text>;
}
if (!token?.current) {
return <Redirect href="/" />;
}
return (
<Provider store={store}>
<Stack screenOptions={{
headerShown: true,
animation: "slide_from_right",
animationTypeForReplace: "pop",
fullScreenGestureEnabled: true,
gestureEnabled: true,
contentStyle: { backgroundColor: colors.header },
}} >
<Stack.Screen name="home" options={{ title: 'Home' }} />
<Stack.Screen name="feature" options={{ title: 'Fitur' }} />
<Stack.Screen name="search" options={{ title: 'Pencarian' }} />
<Stack.Screen name="notification" options={{
title: 'Notifikasi',
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Notifikasi',
headerTitleAlign: 'center',
header: () => (
<AppHeader title="Notifikasi" showBack={true} onPressLeft={() => router.back()} />
)
}} />
<Stack.Screen name="profile" options={{ title: 'Profile' }} />
<Stack.Screen name="setting/index" options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
title: 'Pengaturan',
headerTitleAlign: 'center',
// headerRight: () => <HeaderRightProjectList />
header: () => (
<AppHeader title="Pengaturan"
showBack={true}
onPressLeft={() => router.back()}
/>
)
}} />
<Stack.Screen name="member/index" options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
title: 'Anggota',
headerTitleAlign: 'center',
// headerRight: () => <HeaderMemberList />
header: () => (
<AppHeader title="Anggota"
showBack={true}
onPressLeft={() => router.back()}
right={<HeaderMemberList />}
/>
)
}} />
<Stack.Screen name="discussion/index" options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
title: 'Diskusi Umum',
headerTitleAlign: 'center',
// headerRight: () => <HeaderDiscussionGeneral />
header: () => (
<AppHeader
title="Diskusi Umum"
showBack={true}
onPressLeft={() => router.back()}
right={<HeaderDiscussionGeneral />}
/>
)
}} />
<Stack.Screen name="project/index" options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
title: 'Kegiatan',
headerTitleAlign: 'center',
// headerRight: () => <HeaderRightProjectList />
header: () => (
<AppHeader title="Kegiatan"
showBack={true}
onPressLeft={() => router.back()}
right={<HeaderRightProjectList />}
/>
)
}} />
<Stack.Screen name="division/index" options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
title: 'Divisi',
headerTitleAlign: 'center',
// headerRight: () => <HeaderRightDivisionList />
header: () => (
<AppHeader title="Divisi"
showBack={true}
onPressLeft={() => router.back()}
right={<HeaderRightDivisionList />}
/>
)
}} />
<Stack.Screen name="division/[id]/(fitur-division)" options={{ headerShown: false }} />
<Stack.Screen name="group/index" options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Lembaga Desa',
headerTitleAlign: 'center',
// headerRight: () => <HeaderRightGroupList />
header: () => (
<AppHeader title="Lembaga Desa"
showBack={true}
onPressLeft={() => router.back()}
right={<HeaderRightGroupList />}
/>
)
}} />
<Stack.Screen name="position/index" options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Jabatan',
headerTitleAlign: 'center',
// headerRight: () => <HeaderRightPositionList />
header: () => (
<AppHeader title="Jabatan"
showBack={true}
onPressLeft={() => router.back()}
right={<HeaderRightPositionList />}
/>
)
}} />
<Stack.Screen name="announcement/index"
options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Pengumuman',
headerTitleAlign: 'center',
// headerRight: () => <HeaderRightAnnouncementList />
header: () => (
<AppHeader title="Pengumuman"
showBack={true}
onPressLeft={() => router.back()}
right={<HeaderRightAnnouncementList />}
/>
)
}}
/>
</Stack>
<StatusBar style={'light'} translucent={false} backgroundColor="black" />
<ToastCustom />
<ModalUpdateMaintenance
visible={modalUpdateMaintenance}
type={modalType}
isForceUpdate={isForceUpdate}
customDescription={updateMessage}
onDismiss={handleDismissUpdate}
/>
</Provider>
)
}