From 26e5b0ae6f577f0c697d75b706628d867757d4d0 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 16 Oct 2025 16:41:01 +0200 Subject: [PATCH] Completely reworked the app by starting fresh - Now you can just swipe to access categories - Redone side menu - Inital API input screen is more legible - A goose as an icon - Proper display and internal name - Correct handling of incoming data --- .gitignore | 2 + app.json | 49 +- app/HomeScreen.tsx | 144 - app/MainContent.tsx | 83 - app/SideMenu.tsx | 52 - app/index.tsx | 210 +- assets/images/android-icon-background.png | Bin 17549 -> 0 bytes assets/images/android-icon-foreground.png | Bin 78796 -> 0 bytes assets/images/android-icon-monochrome.png | Bin 4140 -> 0 bytes assets/images/favicon.png | Bin 1129 -> 1290566 bytes assets/images/icon.png | Bin 393493 -> 1290566 bytes assets/images/partial-react-logo.png | Bin 5075 -> 0 bytes assets/images/react-logo.png | Bin 6341 -> 0 bytes assets/images/react-logo@2x.png | Bin 14225 -> 0 bytes assets/images/react-logo@3x.png | Bin 21252 -> 0 bytes assets/images/splash-icon.png | Bin 17547 -> 1293721 bytes backup_package.json | 49 - eas.json | 2 +- hooks/useNotificationListener.ts | 30 +- hooks/usePushNotifications.ts | 89 +- package-lock.json | 7025 +++++++++++++++++---- package.json | 41 +- styles/HomeScreen.styles.ts | 79 - styles/styles.ts | 35 + tsconfig.json | 3 - types/Category.ts | 6 +- 26 files changed, 6089 insertions(+), 1810 deletions(-) delete mode 100644 app/HomeScreen.tsx delete mode 100644 app/MainContent.tsx delete mode 100644 app/SideMenu.tsx delete mode 100644 assets/images/android-icon-background.png delete mode 100644 assets/images/android-icon-foreground.png delete mode 100644 assets/images/android-icon-monochrome.png delete mode 100644 assets/images/partial-react-logo.png delete mode 100644 assets/images/react-logo.png delete mode 100644 assets/images/react-logo@2x.png delete mode 100644 assets/images/react-logo@3x.png delete mode 100644 backup_package.json delete mode 100644 styles/HomeScreen.styles.ts create mode 100644 styles/styles.ts diff --git a/.gitignore b/.gitignore index e352b46..96b1821 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,5 @@ app-example google-services.json +build-* +*.apk diff --git a/app.json b/app.json index ed73105..fd2095a 100644 --- a/app.json +++ b/app.json @@ -1,44 +1,30 @@ { "expo": { - "name": "my-drawer-app", - "slug": "my-drawer-app", + "name": "Notifier", + "slug": "notifier", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/images/icon.png", - "scheme": "mydrawerapp", + "scheme": "notifier", "userInterfaceStyle": "automatic", "newArchEnabled": true, "ios": { - "supportsTablet": true, - "bundleIdentifier": "com.anonymous.mydrawerapp", - "buildNumber": "1.0.0", - "infoPlist": { - "UIBackgroundModes": [ - "remote-notification" - ], - "NSUserTrackingUsageDescription": "This identifier will be used to deliver personalized notifications.", - "NSUserNotificationUsageDescription": "This app uses notifications to keep you informed." - } + "supportsTablet": true }, "android": { - "adaptiveIcon": { - "backgroundColor": "#E6F4FE", - "foregroundImage": "./assets/images/android-icon-foreground.png", - "backgroundImage": "./assets/images/android-icon-background.png", - "monochromeImage": "./assets/images/android-icon-monochrome.png" - }, + "useNextNotificationsApi": true, + "googleServicesFile": "./google-services.json", + "adaptiveIcon": { + "backgroundColor": "#E6F4FE", + "foregroundImage": "./assets/images/icon.png" + }, "edgeToEdgeEnabled": true, "predictiveBackGestureEnabled": false, - "package": "com.anonymous.mydrawerapp", - "versionCode": 1, - "permissions": [ - "NOTIFICATIONS" - ], - "googleServicesFile": "./google-services.json" + "package": "com.gansejunge.notifier" }, "web": { "output": "static", - "favicon": "./assets/images/favicon.png" + "favicon": "./assets/images/icon.png" }, "plugins": [ "expo-router", @@ -53,15 +39,6 @@ "backgroundColor": "#000000" } } - ], - [ - "expo-notifications", - { - "icon": "./assets/images/notification-icon.png", - "color": "#000000", - "androidMode": "default", - "androidCollapsedTitle": "New notification" - } ] ], "experiments": { @@ -71,7 +48,7 @@ "extra": { "router": {}, "eas": { - "projectId": "630e211b-f7de-4a82-a863-5962a593f5aa" + "projectId": "361036ad-f0cf-41d1-ba27-d3f39f6019dc" } } } diff --git a/app/HomeScreen.tsx b/app/HomeScreen.tsx deleted file mode 100644 index b2936ae..0000000 --- a/app/HomeScreen.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import AsyncStorage from "@react-native-async-storage/async-storage"; -import * as Notifications from "expo-notifications"; -import React, { useEffect, useState } from "react"; -import { Linking, ScrollView, Text, TextInput, TouchableOpacity, View } from "react-native"; -import { SafeAreaView } from "react-native-safe-area-context"; -import { Category, categories, categoryKeys, categoryTitles } from "types/Category"; -import { Item } from "types/Item"; -import { usePushNotifications } from "../hooks/usePushNotifications"; -import { styles } from "../styles/HomeScreen.styles"; - -const API_URL = "https://notifier.gansejunge.com"; -const STORAGE_KEY = "notifications"; -const API_KEY_STORAGE = "api_key"; - -export default function HomeScreen() { - const [data, setData] = useState([]); - const [selected, setSelected] = useState("home"); - const [menuOpen, setMenuOpen] = useState(false); - const [apiKey, setApiKey] = useState(null); - const [tempKey, setTempKey] = useState(""); - - const pushToken = usePushNotifications({ - apiKey, - backendUrl: API_URL, - appVersion: "1.0.0", - locale: "en-uk", - }); - - // Load API key on startup - useEffect(() => { - (async () => { - const storedKey = await AsyncStorage.getItem(API_KEY_STORAGE); - if (storedKey) setApiKey(storedKey); - })(); - }, []); - - // Load saved notifications - useEffect(() => { - (async () => { - try { - const stored = await AsyncStorage.getItem(STORAGE_KEY); - if (stored) setData(JSON.parse(stored)); - } catch (err) { - console.error("Failed to load stored notifications:", err); - } - })(); - }, []); - - - // Listen for incoming notifications - useEffect(() => { - const subscription = Notifications.addNotificationReceivedListener(notification => { - const rawCategory = notification.request.content.data?.category as Category | undefined; - const category: Category = rawCategory && categoryKeys.includes(rawCategory) ? rawCategory : "home"; - - const item: Item = { - timestamp: Date.now(), - category, - title: notification.request.content.title || "No title", - info: notification.request.content.body || "No description", - link: (notification.request.content.data?.link as string) || "#", - }; - - setData(prev => { - const updated = [...prev, item].sort((a, b) => b.timestamp - a.timestamp); - AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); - return updated; - }); - }); - return () => subscription.remove(); - }, []); - - const filteredData = selected === "home" ? data : data.filter(item => item.category === selected); - const menuItems = categories; - - if (!apiKey) { - return ( - - - Enter API Key: - - { - await AsyncStorage.setItem(API_KEY_STORAGE, tempKey); - setApiKey(tempKey); - }} - style={{ backgroundColor: "blue", padding: 10 }} - > - Save - - - - ); - } - - return ( - - {/* Side Menu */} - - {menuItems.map(item => ( - { - setSelected(item.key); - setMenuOpen(false); - }} - > - {item.label} - - ))} - - - {/* Main Content */} - - setMenuOpen(!menuOpen)}> - - - {categoryTitles[selected]} - - - {filteredData.length === 0 ? ( - No items yet - ) : ( - filteredData.map(item => ( - - {item.title} - {item.info} - Linking.openURL(item.link)}> - Read more - - - )) - )} - - - - ); -} - diff --git a/app/MainContent.tsx b/app/MainContent.tsx deleted file mode 100644 index 8e759f2..0000000 --- a/app/MainContent.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import { Linking, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import { Category, categoryTitles } from 'types/Category'; -import { Item } from 'types/Item'; - -type MainContentProps = { - selected: Category; - onToggleMenu: () => void; - data: Item[]; -}; - -export default function MainContent({ selected, onToggleMenu, data }: MainContentProps) { - return ( - - - - - - {categoryTitles[selected]} - - - {data.map(item => ( - Linking.openURL(item.link)}> - - {item.title} - {item.info} - - - ))} - - - ); -} - -const styles = StyleSheet.create({ - mainContent: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - position: 'relative', - backgroundColor: '#f2f2f2', - }, - menuButton: { - position: 'absolute', - top: 40, - left: 16, - zIndex: 1, - backgroundColor: '#333', - borderRadius: 20, - padding: 8, - }, - menuButtonText: { - color: '#fff', - fontSize: 24, - }, - title: { - fontSize: 24, - fontWeight: 'bold', - color: '#333', - marginTop: 60, - marginBottom: 16, - }, - dataContainer: { - width: '90%', - }, - dataItem: { - backgroundColor: '#fff', - borderRadius: 8, - padding: 16, - marginBottom: 12, - boxShadow: '0 2px 4px rgba(0,0,0,0.05)', - }, - dataTitle: { - fontSize: 18, - fontWeight: 'bold', - color: '#222', - marginBottom: 4, - }, - dataDescription: { - fontSize: 15, - color: '#555', - }, -}); diff --git a/app/SideMenu.tsx b/app/SideMenu.tsx deleted file mode 100644 index d34cdcf..0000000 --- a/app/SideMenu.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; - -export type MenuItem = { - label: string; - key: string; -}; - -type SideMenuProps = { - menuItems: MenuItem[]; - selected: string; - onSelect: (key: string) => void; -}; - -export default function SideMenu({ menuItems, selected, onSelect }: SideMenuProps) { - return ( - - {menuItems.map(item => ( - onSelect(item.key)}> - {item.label} - - ))} - - ); -} - -const styles = StyleSheet.create({ - sideMenu: { - width: 180, - backgroundColor: '#333', - paddingTop: 40, - paddingHorizontal: 16, - }, - menuItem: { - paddingVertical: 16, - borderRadius: 4, - marginBottom: 8, - }, - menuItemSelected: { - backgroundColor: '#555', - }, - menuText: { - color: '#fff', - fontSize: 18, - }, -}); diff --git a/app/index.tsx b/app/index.tsx index 4df6faf..34f7d02 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,11 +1,207 @@ -import React from 'react'; -import { SafeAreaProvider } from "react-native-safe-area-context"; -import HomeScreen from './HomeScreen'; +import React, { useEffect, useState } from 'react'; +import { View, Text, TextInput, Button, Linking, TouchableOpacity, ScrollView } from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { createDrawerNavigator, DrawerContentScrollView, DrawerItemList } from '@react-navigation/drawer'; +import { categories, categoryTitles, Category } from '../types/Category'; +import { Item } from "../types/Item"; +import { styles } from '../styles/styles'; +import { usePushNotifications } from '../hooks/usePushNotifications'; +import { useNotificationListener, STORAGE_KEY } from '../hooks/useNotificationListener'; +import { version as appVersion } from '../package.json'; + +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: + +