From 5dfbc980352b4dac990800a4b1a191d7e32485ca Mon Sep 17 00:00:00 2001 From: gibbyb Date: Tue, 10 Sep 2024 00:59:55 -0500 Subject: [PATCH] Made some good progress. --- app/(tabs)/countdown.tsx | 63 ++++++++++++++-- app/_layout.tsx | 75 +++++++++++++++++-- components/HelloWave.tsx | 37 ---------- components/ParallaxScrollView.tsx | 76 -------------------- components/UserSelection.tsx | 116 ++++++++++++++++++++++++++++++ package-lock.json | 76 ++++++++++++++++++++ package.json | 2 + 7 files changed, 320 insertions(+), 125 deletions(-) delete mode 100644 components/HelloWave.tsx delete mode 100644 components/ParallaxScrollView.tsx create mode 100644 components/UserSelection.tsx diff --git a/app/(tabs)/countdown.tsx b/app/(tabs)/countdown.tsx index 34c2528..b957208 100644 --- a/app/(tabs)/countdown.tsx +++ b/app/(tabs)/countdown.tsx @@ -1,8 +1,33 @@ import React, { useState, useEffect } from 'react'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, ActivityIndicator } from 'react-native'; import { ThemedText } from '@/components/ThemedText'; import { ThemedView } from '@/components/ThemedView'; +import axios from 'axios'; +//const API_KEY = 'I_Love_Madeline'; +const BASE_URL = 'http://192.168.0.39:3000/api'; +//const BASE_URL = 'https://ismadelinethecutest.gibbyb.com/api'; + +// Separate API call function +const fetchCountdownDate = async () => { + try { + //const response = await axios.get(`${BASE_URL}/getCountdown`, { + //params: { apiKey: API_KEY } + //}); + const response = await axios.get(`${BASE_URL}/getCountdown?apiKey=I_Love_Madeline`); + console.log('API response:', response.data); + if (response.data && response.data[0] && response.data[0].countdown) { + console.log('Countdown date:', response.data[0].countdown); + return new Date(response.data[0].countdown); + } else { + console.error('Unexpected API response format:', response.data); + return null; + } + } catch (error) { + console.error('API call error:', error); + return null; + } +}; export default function TabTwoScreen() { const [countdown, setCountdown] = useState({ @@ -11,13 +36,31 @@ export default function TabTwoScreen() { minutes: 0, seconds: 0, }); - - const targetDate = new Date('2024-09-20T10:10:00').getTime(); + const [targetDate, setTargetDate] = useState(null); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { + const loadCountdownDate = async () => { + setIsLoading(true); + const date = await fetchCountdownDate(); + if (date) { + setTargetDate(date); + } else { + // Fallback to a default date if API call fails + setTargetDate(new Date()); + } + setIsLoading(false); + }; + + loadCountdownDate(); + }, []); + + useEffect(() => { + if (targetDate === null) return; + const interval = setInterval(() => { - const now = new Date().getTime(); - const distance = targetDate - now; + const now = new Date(); + const distance = targetDate.getTime() - now.getTime(); const days = Math.floor(distance / (1000 * 60 * 60 * 24)); const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); @@ -35,6 +78,14 @@ export default function TabTwoScreen() { return () => clearInterval(interval); }, [targetDate]); + if (isLoading) { + return ( + + + + ); + } + return ( Countdown to Next Visit @@ -48,7 +99,7 @@ export default function TabTwoScreen() { ); } -function CountdownItem({ value, label }) { +function CountdownItem({ value, label }: { value: number; label: string }) { return ( {value} diff --git a/app/_layout.tsx b/app/_layout.tsx index 2e37cdd..1e09716 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -2,19 +2,47 @@ import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native import { useFonts } from 'expo-font'; import { Stack } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; -import { useEffect } from 'react'; import 'react-native-reanimated'; - import { useColorScheme } from '@/hooks/useColorScheme'; +import React, { useState, useEffect } from 'react'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import UserSelection from '@/components/UserSelection'; +import { TouchableOpacity, Text } from 'react-native'; + +type User = { + id: number; + name: string; + message: string; +}; // Prevent the splash screen from auto-hiding before asset loading is complete. SplashScreen.preventAutoHideAsync(); export default function RootLayout() { const colorScheme = useColorScheme(); - const [loaded] = useFonts({ + const [loaded, error] = useFonts({ SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), }); + const [user, setUser] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + async function prepare() { + try { + // Load the user + const storedUser = await AsyncStorage.getItem('@user'); + if (storedUser) { + setUser(JSON.parse(storedUser)); + } + } catch (e) { + console.error('Failed to load user', e); + } finally { + setIsLoading(false); + } + } + + prepare(); + }, []); useEffect(() => { if (loaded) { @@ -22,14 +50,49 @@ export default function RootLayout() { } }, [loaded]); - if (!loaded) { - return null; + const handleUserSelected = async (selectedUser: User) => { + setUser(selectedUser); + try { + await AsyncStorage.setItem('@user', JSON.stringify(selectedUser)); + } catch (e) { + console.error('Failed to save user', e); + } + }; + + const handleSwitchUser = async () => { + try { + await AsyncStorage.removeItem('@user'); + setUser(null); + } catch (e) { + console.error('Failed to remove user', e); + } + }; + + if (!loaded || isLoading) { + return null; // or a loading indicator + } + + if (!user) { + return ; } return ( - + ( + + + Switch User + + + ), + }} + /> diff --git a/components/HelloWave.tsx b/components/HelloWave.tsx deleted file mode 100644 index f4b6ea5..0000000 --- a/components/HelloWave.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { StyleSheet } from 'react-native'; -import Animated, { - useSharedValue, - useAnimatedStyle, - withTiming, - withRepeat, - withSequence, -} from 'react-native-reanimated'; - -import { ThemedText } from '@/components/ThemedText'; - -export function HelloWave() { - const rotationAnimation = useSharedValue(0); - - rotationAnimation.value = withRepeat( - withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })), - 4 // Run the animation 4 times - ); - - const animatedStyle = useAnimatedStyle(() => ({ - transform: [{ rotate: `${rotationAnimation.value}deg` }], - })); - - return ( - - 👋 - - ); -} - -const styles = StyleSheet.create({ - text: { - fontSize: 28, - lineHeight: 32, - marginTop: -6, - }, -}); diff --git a/components/ParallaxScrollView.tsx b/components/ParallaxScrollView.tsx deleted file mode 100644 index 0a35419..0000000 --- a/components/ParallaxScrollView.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import type { PropsWithChildren, ReactElement } from 'react'; -import { StyleSheet, useColorScheme } from 'react-native'; -import Animated, { - interpolate, - useAnimatedRef, - useAnimatedStyle, - useScrollViewOffset, -} from 'react-native-reanimated'; - -import { ThemedView } from '@/components/ThemedView'; - -const HEADER_HEIGHT = 250; - -type Props = PropsWithChildren<{ - headerImage: ReactElement; - headerBackgroundColor: { dark: string; light: string }; -}>; - -export default function ParallaxScrollView({ - children, - headerImage, - headerBackgroundColor, -}: Props) { - const colorScheme = useColorScheme() ?? 'light'; - const scrollRef = useAnimatedRef(); - const scrollOffset = useScrollViewOffset(scrollRef); - - const headerAnimatedStyle = useAnimatedStyle(() => { - return { - transform: [ - { - translateY: interpolate( - scrollOffset.value, - [-HEADER_HEIGHT, 0, HEADER_HEIGHT], - [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75] - ), - }, - { - scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]), - }, - ], - }; - }); - - return ( - - - - {headerImage} - - {children} - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - header: { - height: 250, - overflow: 'hidden', - }, - content: { - flex: 1, - padding: 32, - gap: 16, - overflow: 'hidden', - }, -}); diff --git a/components/UserSelection.tsx b/components/UserSelection.tsx new file mode 100644 index 0000000..a3632d6 --- /dev/null +++ b/components/UserSelection.tsx @@ -0,0 +1,116 @@ +import React, { useState, useEffect } from 'react'; +import { TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'; +import { ThemedView } from '@/components/ThemedView'; +import { ThemedText } from '@/components/ThemedText'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import axios from 'axios'; + +interface User { + id: number; + name: string; + message: string; +} + +interface UserSelectionProps { + onUserSelected: (user: User) => void; +} + +const API_KEY = 'I_Love_Madeline'; +//const BASE_URL = 'https://ismadelinethecutest.gibbyb.com/api'; +const BASE_URL = 'http://192.168.0.39:3000/api'; + +const UserSelection: React.FC = ({ onUserSelected }) => { + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + checkStoredUser(); + }, []); + + const checkStoredUser = async () => { + try { + const storedUser = await AsyncStorage.getItem('@user'); + if (storedUser) { + onUserSelected(JSON.parse(storedUser)); + } else { + fetchUsers(); + } + } catch (e) { + console.error('Failed to load user', e); + fetchUsers(); + } + }; + + const fetchUsers = async () => { + try { + const response = await axios.get(`${BASE_URL}/getUsers`, { + params: { apiKey: API_KEY } + }); + setUsers(response.data); + } catch (e) { + console.error('Failed to fetch users', e); + } finally { + setLoading(false); + } + }; + + const selectUser = async (user: User) => { + try { + await AsyncStorage.setItem('@user', JSON.stringify(user)); + onUserSelected(user); + } catch (e) { + console.error('Failed to save user', e); + } + }; + + if (loading) { + return ( + + + + ); + } + + return ( + + Who are you? + {users.map((user) => ( + selectUser(user)} + > + {user.name} + + ))} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + title: { + fontSize: 24, + fontWeight: 'bold', + marginBottom: 20, + }, + button: { + backgroundColor: '#007AFF', + padding: 15, + borderRadius: 5, + marginVertical: 10, + width: 200, + alignItems: 'center', + }, + buttonText: { + color: 'white', + fontSize: 18, + fontWeight: 'bold', + }, +}); + +export default UserSelection; diff --git a/package-lock.json b/package-lock.json index fa88307..c4b0bfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "dependencies": { "@expo/vector-icons": "^14.0.2", "@react-native-async-storage/async-storage": "^2.0.0", + "@react-native-community/datetimepicker": "^8.2.0", "@react-navigation/native": "^6.0.2", + "axios": "^1.7.7", "expo": "~51.0.28", "expo-constants": "~16.0.2", "expo-font": "~12.0.9", @@ -6329,6 +6331,29 @@ "node": ">=8" } }, + "node_modules/@react-native-community/datetimepicker": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-8.2.0.tgz", + "integrity": "sha512-qrUPhiBvKGuG9Y+vOqsc56RPFcHa1SU2qbAMT0hfGkoFIj3FodE0VuPVrEa8fgy7kcD5NQmkZIKgHOBLV0+hWg==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4" + }, + "peerDependencies": { + "expo": ">=50.0.0", + "react": "*", + "react-native": "*", + "react-native-windows": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "react-native-windows": { + "optional": true + } + } + }, "node_modules/@react-native/assets-registry": { "version": "0.74.87", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz", @@ -7737,6 +7762,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -10583,6 +10633,26 @@ "node": ">=0.4.0" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fontfaceobserver": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", @@ -16563,6 +16633,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", diff --git a/package.json b/package.json index e50e165..1ef20ea 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,9 @@ "dependencies": { "@expo/vector-icons": "^14.0.2", "@react-native-async-storage/async-storage": "^2.0.0", + "@react-native-community/datetimepicker": "^8.2.0", "@react-navigation/native": "^6.0.2", + "axios": "^1.7.7", "expo": "~51.0.28", "expo-constants": "~16.0.2", "expo-font": "~12.0.9",