import AsyncStorage from '@react-native-async-storage/async-storage';
import { createDrawerNavigator, DrawerContentScrollView, DrawerItemList } from '@react-navigation/drawer';
import React, { useEffect, useState } from 'react';
import { Button, Linking, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { STORAGE_KEY, useNotificationListener } from '../hooks/useNotificationListener';
import { usePushNotifications } from '../hooks/usePushNotifications';
import { version as appVersion } from '../package.json';
import { styles } from '../styles/styles';
import { categories, Category, categoryTitles } from '../types/Category';
import { Item } from "../types/Item";
const Drawer = createDrawerNavigator();
const API_KEY_STORAGE = 'API_KEY';
const API_URL = 'https://notifier.gansejunge.com';
type ApiKeyScreenProps = {
onApiKeySaved: (key: string) => void;
};
function ApiKeyScreen({ onApiKeySaved }: ApiKeyScreenProps) {
const [apiKey, setApiKey] = useState('');
const saveApiKey = async () => {
if (apiKey.trim()) {
await AsyncStorage.setItem(API_KEY_STORAGE, apiKey);
onApiKeySaved(apiKey);
}
};
return (
Enter your API Key:
);
}
function CategoryScreen({
category,
notifications,
registered,
registrationError,
}: {
category: Category;
notifications: Item[];
registered: boolean;
registrationError: string | null;
}) {
const [showBanner, setShowBanner] = useState(true);
// Auto-hide banner after 4 seconds whenever it changes
useEffect(() => {
if (!registered && !registrationError) return; // nothing to show
setShowBanner(true); // reset visibility
const timer = setTimeout(() => setShowBanner(false), 4000);
return () => clearTimeout(timer);
}, [registered, registrationError]);
const filtered = category === "home"
? notifications
: notifications.filter(n => n.category === category);
const sorted = filtered.sort((a, b) => b.timestamp - a.timestamp);
const openLink = (url: string) => {
if (url && url !== "#") Linking.openURL(url).catch(console.error);
};
return (
{/* Banner */}
{showBanner && registrationError && (
{registrationError}
)}
{categoryTitles[category]}
{sorted.length === 0 ? (
No notifications yet.
) : (
sorted.map((item, index) => (
{item.title}
{new Date(item.timestamp).toLocaleString()}
{item.info}
{item.link && item.link !== "#" && (
openLink(item.link)}>
Click here
)}
))
)}
);
}
// Custom Drawer with Logout at bottom
function CustomDrawerContent({ onLogout, ...props }: any) {
return (
{/* Logout Button */}
Logout
);
}
export default function App() {
const [apiKeySaved, setApiKeySaved] = useState(null);
const [apiKey, setApiKey] = useState(null);
const [notifications, setNotifications] = useState- ([]);
useEffect(() => {
const checkApiKey = async () => {
const key = await AsyncStorage.getItem(API_KEY_STORAGE);
setApiKey(key);
setApiKeySaved(!!key);
};
checkApiKey();
}, []);
const { expoPushToken, registered, registrationError, unregisterToken } = usePushNotifications({
apiKey: apiKey ?? undefined,
backendUrl: API_URL,
appVersion,
locale: 'en-uk',
});
const handleLogout = async () => {
if (unregisterToken) await unregisterToken();
await AsyncStorage.removeItem(API_KEY_STORAGE);
await AsyncStorage.removeItem(STORAGE_KEY);
setApiKey(null);
setApiKeySaved(false);
setNotifications([]);
};
useNotificationListener(setNotifications);
if (apiKeySaved === null) return null;
if (!apiKeySaved) {
return (
{
setApiKey(key);
setApiKeySaved(true);
}}
/>
);
}
return (
}
screenOptions={{
headerStyle: { backgroundColor: '#222' },
headerTintColor: '#fff',
drawerStyle: { backgroundColor: '#333', width: 240 },
drawerActiveTintColor: '#fff',
drawerInactiveTintColor: '#ccc',
drawerLabelStyle: { textAlign: 'center', fontSize: 16 },
drawerItemStyle: { paddingVertical: 5, marginVertical: 2 },
}}
>
{categories.map(c => (
{() => (
< CategoryScreen
category={c.key}
notifications={notifications}
registered={registered}
registrationError={registrationError}
/>
)}
))}
);
}