:api added seasonApi to fetch seasons for a show
This commit is contained in:
77
apis/seasonApi.ts
Normal file
77
apis/seasonApi.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
export type RawSeasonParticipant = {
|
||||
id: { seasonId: number; personId: number };
|
||||
person: {
|
||||
personId: number;
|
||||
name: string;
|
||||
birthDate: string;
|
||||
imageUrl: string | null;
|
||||
};
|
||||
partner: unknown | null;
|
||||
};
|
||||
|
||||
export type RawSeason = {
|
||||
seasonId: number;
|
||||
show: number;
|
||||
seasonNumber: number;
|
||||
startDate?: string;
|
||||
endDate?: string | null;
|
||||
moderators: unknown[];
|
||||
seasonParticipants: RawSeasonParticipant[];
|
||||
};
|
||||
|
||||
export type SeasonParticipant = {
|
||||
id: number;
|
||||
name: string;
|
||||
birthYear?: number;
|
||||
imageUri: string;
|
||||
};
|
||||
|
||||
export type Season = {
|
||||
id: number;
|
||||
showId: number;
|
||||
seasonNumber: number;
|
||||
startDate?: string;
|
||||
endDate?: string | null;
|
||||
participants: SeasonParticipant[];
|
||||
};
|
||||
|
||||
const SEASON_BASE_URL = "http://45.157.177.99:8080/shows";
|
||||
|
||||
export async function getSeason(
|
||||
showId: number,
|
||||
seasonNumber: number
|
||||
): Promise<Season | null> {
|
||||
// WICHTIG: trailing Slash entfernt
|
||||
const url = `${SEASON_BASE_URL}/${showId}/seasons/${seasonNumber}`;
|
||||
try {
|
||||
console.log("[getSeason] Fetch:", url);
|
||||
const res = await fetch(url);
|
||||
console.log("[getSeason] Status:", res.status);
|
||||
if (res.status === 404) return null;
|
||||
if (!res.ok) throw new Error(`Season fetch failed: ${res.status}`);
|
||||
const raw: RawSeason = await res.json();
|
||||
const participants: SeasonParticipant[] = raw.seasonParticipants.map(
|
||||
(p) => ({
|
||||
id: p.person.personId,
|
||||
name: p.person.name,
|
||||
birthYear: p.person.birthDate
|
||||
? Number(p.person.birthDate.slice(0, 4))
|
||||
: undefined,
|
||||
imageUri:
|
||||
p.person.imageUrl ??
|
||||
"https://via.placeholder.com/300x400.png?text=No+Image",
|
||||
})
|
||||
);
|
||||
return {
|
||||
id: raw.seasonId,
|
||||
showId: raw.show,
|
||||
seasonNumber: raw.seasonNumber,
|
||||
startDate: raw.startDate,
|
||||
endDate: raw.endDate,
|
||||
participants,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("getSeason error:", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,11 @@ export type Show = {
|
||||
running: boolean;
|
||||
};
|
||||
|
||||
const API_URL = "http://45.157.177.99:8080/shows";
|
||||
const SHOW_API_URL = "http://45.157.177.99:8080/shows";
|
||||
|
||||
export async function getShows(): Promise<Show[]> {
|
||||
try {
|
||||
const response = await fetch(API_URL);
|
||||
const response = await fetch(SHOW_API_URL);
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ export default function HomeScreen() {
|
||||
router.push({
|
||||
pathname: "/showDetails",
|
||||
params: {
|
||||
id: String(show.id),
|
||||
title: show.title,
|
||||
bannerUri: show.bannerUri,
|
||||
description: show.description,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { ShowProvider } from "@/contexts/ShowContext";
|
||||
import { SeasonProvider } from "@/contexts/SeasonContext";
|
||||
import { Stack } from "expo-router";
|
||||
import "react-native-reanimated";
|
||||
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<ShowProvider>
|
||||
<SeasonProvider>
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen
|
||||
@@ -21,6 +23,7 @@ export default function RootLayout() {
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</SeasonProvider>
|
||||
</ShowProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,84 @@
|
||||
import { View, Image, Text, TouchableOpacity } from "react-native";
|
||||
import styles from "@/app/stackStyles/participantStyles";
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
import React, {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { router } from "expo-router";
|
||||
import Feather from "@expo/vector-icons/Feather";
|
||||
import BottomSheet, { BottomSheetScrollView } from "@gorhom/bottom-sheet";
|
||||
import {
|
||||
ScrollView,
|
||||
GestureHandlerRootView,
|
||||
} from "react-native-gesture-handler";
|
||||
import { useShowContext } from "@/contexts/ShowContext";
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
withRepeat,
|
||||
withSequence,
|
||||
Easing,
|
||||
cancelAnimation,
|
||||
} from "react-native-reanimated";
|
||||
|
||||
export default function ParticipantScreen() {
|
||||
const { shows, error, loading } = useShowContext();
|
||||
|
||||
const bottomSheetRef = useRef<BottomSheet>(null);
|
||||
|
||||
const [sheetIndex, setSheetIndex] = useState(1);
|
||||
|
||||
const handleSheetChange = useCallback((index: number) => {
|
||||
setSheetIndex(index);
|
||||
}, []);
|
||||
|
||||
const snapPoints = useMemo(() => ["10%", "10%", "45%"], []);
|
||||
|
||||
const bounce = useSharedValue(0);
|
||||
const expanded = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (sheetIndex === 2) {
|
||||
cancelAnimation(bounce);
|
||||
expanded.value = withTiming(1, { duration: 120 });
|
||||
bounce.value = withTiming(-12, { duration: 120 });
|
||||
} else {
|
||||
expanded.value = withTiming(0, { duration: 100 });
|
||||
bounce.value = withRepeat(
|
||||
withSequence(
|
||||
withTiming(-6, { duration: 250, easing: Easing.out(Easing.quad) }),
|
||||
withTiming(0, { duration: 250, easing: Easing.inOut(Easing.quad) })
|
||||
),
|
||||
-1,
|
||||
true
|
||||
);
|
||||
}
|
||||
return () => {
|
||||
cancelAnimation(bounce);
|
||||
};
|
||||
}, [sheetIndex, bounce, expanded]);
|
||||
|
||||
const iconAnimatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [
|
||||
{ translateY: bounce.value },
|
||||
{ rotate: `${expanded.value * 180}deg` },
|
||||
],
|
||||
opacity: 1 - expanded.value * 0.3,
|
||||
}));
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView style={styles.mainContainer}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
<Text style={styles.participantName}>Calvin Ogara</Text>
|
||||
<TouchableOpacity style={styles.closeIcon} onPress={() => router.back()}>
|
||||
<TouchableOpacity
|
||||
style={styles.closeIcon}
|
||||
onPress={() => router.back()}
|
||||
>
|
||||
<Ionicons name="close-circle-outline" size={38} color="white" />
|
||||
</TouchableOpacity>
|
||||
<View style={styles.participantInfoSection}>
|
||||
@@ -39,11 +106,39 @@ export default function ParticipantScreen() {
|
||||
marginTop: 15,
|
||||
}}
|
||||
>
|
||||
{[...Array(5)].map((show, index) => (
|
||||
<View style={styles.showContainer} key={index}></View>
|
||||
{shows.map((show, i) => (
|
||||
<View style={styles.showContainer} key={i}>
|
||||
<Image
|
||||
source={{ uri: show.thumbnailUri }}
|
||||
style={styles.showImage}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
<BottomSheet
|
||||
ref={bottomSheetRef}
|
||||
index={1}
|
||||
snapPoints={snapPoints}
|
||||
enableDynamicSizing={false}
|
||||
onChange={handleSheetChange}
|
||||
backgroundStyle={{ backgroundColor: "hsl(221, 39%, 12%)" }}
|
||||
handleIndicatorStyle={{ backgroundColor: "transparent" }}
|
||||
>
|
||||
<BottomSheetScrollView
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
{ alignSelf: "center", marginBottom: 20 },
|
||||
iconAnimatedStyle,
|
||||
]}
|
||||
>
|
||||
<Feather name="chevrons-up" size={40} color="white" />
|
||||
</Animated.View>
|
||||
</BottomSheetScrollView>
|
||||
</BottomSheet>
|
||||
</ScrollView>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useLocalSearchParams, router } from "expo-router";
|
||||
import ShowInfo from "@/components/ui/ShowInfo";
|
||||
import ParticipantDetails from "@/components/ParticipantDeatails";
|
||||
import React from "react";
|
||||
import { useSeasonContext } from "@/contexts/SeasonContext";
|
||||
import {
|
||||
Dimensions,
|
||||
Image,
|
||||
@@ -11,15 +12,58 @@ import {
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import styles from "./stackStyles/showDetailStyles";
|
||||
import { parseQueryParams } from "expo-router/build/fork/getStateFromPath-forks";
|
||||
|
||||
export default function ShowDetails() {
|
||||
const { bannerUri, description, concept, genres, streamingService } =
|
||||
const { bannerUri, description, concept, genres, streamingService, id } =
|
||||
useLocalSearchParams();
|
||||
const [selectedParticipants, setSelectedParticipants] =
|
||||
React.useState<boolean>(true);
|
||||
const [selectedSeason, setSelectedSeason] = React.useState<number>(1);
|
||||
const showId = Number(id);
|
||||
const { fetchSeasonParticipants, fetchSeasonCount } = useSeasonContext();
|
||||
const [seasonCount, setSeasonCount] = React.useState<number>(0);
|
||||
const [participants, setParticipants] = React.useState<
|
||||
{ id: number; name: string; imageUri: string }[]
|
||||
>([]);
|
||||
const [pLoading, setPLoading] = React.useState(false);
|
||||
const [pError, setPError] = React.useState<string | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!showId) return;
|
||||
let active = true;
|
||||
(async () => {
|
||||
const count = await fetchSeasonCount(showId);
|
||||
if (active) {
|
||||
setSeasonCount(count);
|
||||
if (count > 0 && selectedSeason > count) setSelectedSeason(1);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [showId, fetchSeasonCount]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!showId || !selectedSeason) return;
|
||||
let active = true;
|
||||
(async () => {
|
||||
setPError(null);
|
||||
setPLoading(true);
|
||||
try {
|
||||
const data = await fetchSeasonParticipants(showId, selectedSeason);
|
||||
if (active) setParticipants(data);
|
||||
} catch {
|
||||
if (active) setPError("Fehler beim Laden");
|
||||
} finally {
|
||||
if (active) setPLoading(false);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [showId, selectedSeason, fetchSeasonParticipants]);
|
||||
|
||||
return (
|
||||
<View style={styles.mainContainer}>
|
||||
@@ -37,8 +81,8 @@ export default function ShowDetails() {
|
||||
style={styles.showImage}
|
||||
/>
|
||||
<ShowInfo
|
||||
seasons={10}
|
||||
participants={150}
|
||||
seasons={seasonCount}
|
||||
participants={participants.length}
|
||||
streamingService={streamingService as string}
|
||||
/>
|
||||
|
||||
@@ -88,23 +132,25 @@ export default function ShowDetails() {
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.seasonList}
|
||||
>
|
||||
{[...Array(10).keys()].map((season) => (
|
||||
{Array.from({ length: seasonCount }, (_, idx) => idx + 1).map(
|
||||
(season) => (
|
||||
<TouchableOpacity
|
||||
key={season}
|
||||
style={[
|
||||
styles.seasonContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
selectedSeason === season + 1
|
||||
selectedSeason === season
|
||||
? "#199edb"
|
||||
: "hsl(0, 0%, 20%)",
|
||||
},
|
||||
]}
|
||||
onPress={() => setSelectedSeason(season + 1)}
|
||||
onPress={() => setSelectedSeason(season)}
|
||||
>
|
||||
<Text style={styles.seasonLabel}>{season + 1}</Text>
|
||||
<Text style={styles.seasonLabel}>{season}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
)
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
@@ -114,90 +160,38 @@ export default function ShowDetails() {
|
||||
styles.participantSection,
|
||||
]}
|
||||
>
|
||||
{[0, 1, 2].map((column) => (
|
||||
{pError && (
|
||||
<Text style={{ color: "tomato", marginBottom: 8 }}>
|
||||
{pError}
|
||||
</Text>
|
||||
)}
|
||||
{!pLoading && !pError && participants.length === 0 && (
|
||||
<Text style={{ color: "gray" }}>Keine Teilnehmer.</Text>
|
||||
)}
|
||||
{participants.map((p) => (
|
||||
<TouchableOpacity
|
||||
key={column}
|
||||
key={p.id}
|
||||
style={styles.participantContainer}
|
||||
onPress={() =>
|
||||
router.push({
|
||||
pathname: "/participant",
|
||||
params: { participantId: p.id, name: p.name },
|
||||
})
|
||||
}
|
||||
>
|
||||
{column === 0 && (
|
||||
<>
|
||||
<Image
|
||||
source={{
|
||||
uri: "https://amp.infranken.de/storage/image/2/2/7/8/4408722_hat-calvin-o-bei-vip-are-you-the-one-eine-favoritin-die-indizien_noscale_1EywMa_HqGfqa.jpg",
|
||||
}}
|
||||
source={{ uri: p.imageUri }}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: 10,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text style={styles.participantLabel}>
|
||||
Calvin Lesra Ogara
|
||||
<Text style={styles.participantLabel} numberOfLines={2}>
|
||||
{p.name}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
{column === 1 && (
|
||||
<>
|
||||
<Image
|
||||
source={{
|
||||
uri: "https://content.promiflash.de/article-images/square600/love-island-granate-sandra-2.jpg",
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: 10,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text style={styles.participantLabel}>Sandra Janina</Text>
|
||||
</>
|
||||
)}
|
||||
{column === 2 && (
|
||||
<>
|
||||
<Image
|
||||
source={{
|
||||
uri: "https://static.wikia.nocookie.net/toohottohandle/images/e/e4/GER_S1_Kevin_Njie.jpg/revision/latest?cb=20240225192711",
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: 10,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text style={styles.participantLabel}>Kevin Njie</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
{[0, 1, 2].map((column) => (
|
||||
<View
|
||||
key={column}
|
||||
style={[styles.participantContainer, { marginTop: 20 }]}
|
||||
>
|
||||
{column === 0 && (
|
||||
<>
|
||||
<Image
|
||||
source={{
|
||||
uri: "https://content.promiflash.de/article-images/square600/sidar-are-you-the-one-kandidat-2023.jpg",
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: 10,
|
||||
}}
|
||||
/>
|
||||
<Text style={styles.participantLabel}>Single Sidar</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -49,11 +49,10 @@ const styles = StyleSheet.create({
|
||||
marginTop: 2,
|
||||
},
|
||||
performedShowsSection: {
|
||||
marginTop: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 10,
|
||||
height: 375,
|
||||
paddingLeft: 15,
|
||||
paddingBottom: 20,
|
||||
backgroundColor: "hsl(221, 39%, 0%)",
|
||||
},
|
||||
performedShowsTitle: {
|
||||
@@ -69,6 +68,27 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 10,
|
||||
marginRight: 15,
|
||||
},
|
||||
showImage: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: 10,
|
||||
},
|
||||
showLabel: {
|
||||
color: "white",
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
textAlign: "center",
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
alignItems: "center",
|
||||
},
|
||||
itemContainer: {
|
||||
padding: 6,
|
||||
margin: 6,
|
||||
backgroundColor: "#eee",
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
||||
|
||||
@@ -68,6 +68,7 @@ const styles = StyleSheet.create({
|
||||
width: 110,
|
||||
backgroundColor: "hsl(336, 79%, 63%)",
|
||||
borderRadius: 10,
|
||||
marginTop: 30,
|
||||
},
|
||||
participantSection: {
|
||||
flexDirection: "row",
|
||||
|
||||
74
contexts/SeasonContext.tsx
Normal file
74
contexts/SeasonContext.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { getSeason, SeasonParticipant } from "@/apis/seasonApi";
|
||||
import React, { createContext, useContext, useState, useCallback } from "react";
|
||||
|
||||
type SeasonContextType = {
|
||||
fetchSeasonParticipants: (
|
||||
showId: number,
|
||||
seasonNumber: number
|
||||
) => Promise<SeasonParticipant[]>;
|
||||
fetchSeasonCount: (showId: number) => Promise<number>;
|
||||
};
|
||||
|
||||
const SeasonContext = createContext<SeasonContextType | null>(null);
|
||||
|
||||
export const SeasonProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [seasonCache, setSeasonCache] = useState<
|
||||
Record<string, SeasonParticipant[]>
|
||||
>({});
|
||||
|
||||
const [seasonCountCache, setSeasonCountCache] = useState<
|
||||
Record<number, number>
|
||||
>({});
|
||||
|
||||
const fetchSeasonParticipants = useCallback(
|
||||
async (showId: number, seasonNumber: number) => {
|
||||
const key = `${showId}-${seasonNumber}`;
|
||||
if (seasonCache[key]) return seasonCache[key];
|
||||
|
||||
try {
|
||||
const season = await getSeason(showId, seasonNumber);
|
||||
const participants = season?.participants ?? [];
|
||||
setSeasonCache((c) => ({ ...c, [key]: participants }));
|
||||
return participants;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
[seasonCache]
|
||||
);
|
||||
|
||||
const fetchSeasonCount = useCallback(
|
||||
async (showId: number) => {
|
||||
if (seasonCountCache[showId] !== undefined)
|
||||
return seasonCountCache[showId];
|
||||
let n = 0;
|
||||
for (let s = 1; s <= 50; s++) {
|
||||
try {
|
||||
const season = await getSeason(showId, s);
|
||||
if (!season) break;
|
||||
n = s;
|
||||
} catch {
|
||||
break;
|
||||
}
|
||||
}
|
||||
setSeasonCountCache((c) => ({ ...c, [showId]: n }));
|
||||
return n;
|
||||
},
|
||||
[seasonCountCache]
|
||||
);
|
||||
|
||||
return (
|
||||
<SeasonContext.Provider
|
||||
value={{ fetchSeasonParticipants, fetchSeasonCount }}
|
||||
>
|
||||
{children}
|
||||
</SeasonContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSeasonContext = () => {
|
||||
const context = useContext(SeasonContext);
|
||||
if (!context)
|
||||
throw new Error("useSeasonContext must be used within a SeasonProvider");
|
||||
return context;
|
||||
};
|
||||
48
package-lock.json
generated
48
package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@expo/metro-runtime": "~6.1.2",
|
||||
"@expo/vector-icons": "^15.0.2",
|
||||
"@gorhom/bottom-sheet": "^5",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/elements": "^2.3.8",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
@@ -18,7 +19,7 @@
|
||||
"expo-constants": "~18.0.9",
|
||||
"expo-font": "~14.0.8",
|
||||
"expo-haptics": "~15.0.7",
|
||||
"expo-image": "~3.0.8",
|
||||
"expo-image": "~3.0.9",
|
||||
"expo-linking": "~8.0.8",
|
||||
"expo-router": "~6.0.10",
|
||||
"expo-splash-screen": "~31.0.10",
|
||||
@@ -2265,6 +2266,45 @@
|
||||
"@babel/highlight": "^7.10.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@gorhom/bottom-sheet": {
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-5.2.6.tgz",
|
||||
"integrity": "sha512-vmruJxdiUGDg+ZYcDmS30XDhq/h/+QkINOI5LY/uGjx8cPGwgJW0H6AB902gNTKtccbiKe/rr94EwdmIEz+LAQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@gorhom/portal": "1.0.14",
|
||||
"invariant": "^2.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-native": "*",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": ">=2.16.1",
|
||||
"react-native-reanimated": ">=3.16.0 || >=4.0.0-"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@gorhom/portal": {
|
||||
"version": "1.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@gorhom/portal/-/portal-1.0.14.tgz",
|
||||
"integrity": "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@@ -6699,9 +6739,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/expo-image": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/expo-image/-/expo-image-3.0.8.tgz",
|
||||
"integrity": "sha512-L83fTHVjvE5hACxUXPk3dpABteI/IypeqxKMeOAAcT2eB/jbqT53ddsYKEvKAP86eoByQ7+TCtw9AOUizEtaTQ==",
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/expo-image/-/expo-image-3.0.9.tgz",
|
||||
"integrity": "sha512-GkPIjeqrODMBdpbRWOzbwiq8ztxjgq1rdZrnqwt/pzQavgXPlr4rW/7aigue9Jm5t5vebhMNAuc1A/XIXXqpcA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo": "*",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"dependencies": {
|
||||
"@expo/metro-runtime": "~6.1.2",
|
||||
"@expo/vector-icons": "^15.0.2",
|
||||
"@gorhom/bottom-sheet": "^5",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/elements": "^2.3.8",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
@@ -21,7 +22,7 @@
|
||||
"expo-constants": "~18.0.9",
|
||||
"expo-font": "~14.0.8",
|
||||
"expo-haptics": "~15.0.7",
|
||||
"expo-image": "~3.0.8",
|
||||
"expo-image": "~3.0.9",
|
||||
"expo-linking": "~8.0.8",
|
||||
"expo-router": "~6.0.10",
|
||||
"expo-splash-screen": "~31.0.10",
|
||||
|
||||
Reference in New Issue
Block a user