modified: files to ios26 ui/ux
This commit is contained in:
@@ -1,356 +1,5 @@
|
||||
import styles from "@/app/tabStyles/indexStyles";
|
||||
import ShowCard from "@/components/ui/ShowCard";
|
||||
import { useShows } from "@/hooks/useShows";
|
||||
import { useStreamingServices } from "@/hooks/useStreamingServices";
|
||||
import Feather from "@expo/vector-icons/Feather";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import { router } from "expo-router";
|
||||
import React from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
RefreshControl,
|
||||
ScrollView as RNScrollView,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
import {
|
||||
GestureHandlerRootView,
|
||||
ScrollView, // horizontaler ScrollView bleibt aus RNGH
|
||||
} from "react-native-gesture-handler";
|
||||
import { Redirect } from "expo-router";
|
||||
|
||||
export default function HomeScreen() {
|
||||
const {
|
||||
data: shows = [],
|
||||
error,
|
||||
isLoading: loading,
|
||||
refetch: refetchShows, // ⬅️ refetch aus Hook
|
||||
} = useShows();
|
||||
|
||||
const {
|
||||
data: streamingServices = {},
|
||||
refetch: refetchServices, // ⬅️ refetch aus Hook
|
||||
} = useStreamingServices();
|
||||
|
||||
const [activeFilter, setActiveFilter] = React.useState<string>("all");
|
||||
const [refreshing, setRefreshing] = React.useState(false); // ⬅️ UI-State für Pull-to-Refresh
|
||||
|
||||
const haptikFeedback = () => {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
};
|
||||
|
||||
const handleFilter = (type: string) => {
|
||||
haptikFeedback();
|
||||
if (type === activeFilter) {
|
||||
setActiveFilter("all");
|
||||
} else {
|
||||
setActiveFilter(type);
|
||||
}
|
||||
};
|
||||
|
||||
// ⬅️ Pull-to-Refresh Handler
|
||||
const onRefresh = React.useCallback(async () => {
|
||||
haptikFeedback();
|
||||
setRefreshing(true);
|
||||
try {
|
||||
await Promise.all([
|
||||
typeof refetchShows === "function" ? refetchShows() : Promise.resolve(),
|
||||
typeof refetchServices === "function"
|
||||
? refetchServices()
|
||||
: Promise.resolve(),
|
||||
]);
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
}, [refetchShows, refetchServices]);
|
||||
|
||||
const filteredShows = React.useMemo(() => {
|
||||
if (activeFilter === "all") {
|
||||
return shows;
|
||||
}
|
||||
if (activeFilter === "live") {
|
||||
return shows.filter((show) => show.running);
|
||||
}
|
||||
return shows.filter((show) =>
|
||||
show.streamingService
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.includes(activeFilter)
|
||||
);
|
||||
}, [shows, activeFilter]);
|
||||
|
||||
const uniqueStreamingServices = React.useMemo(() => {
|
||||
const uniqueServices = new Set<string>();
|
||||
shows.forEach((show) => {
|
||||
const services = show.streamingService.split(", ").map((s) => s.trim());
|
||||
services.forEach((service) => uniqueServices.add(service));
|
||||
});
|
||||
return Array.from(uniqueServices);
|
||||
}, [shows]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.mainContainer,
|
||||
{ justifyContent: "center", alignItems: "center" },
|
||||
]}
|
||||
>
|
||||
<ActivityIndicator size="large" color="#ffffff" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.mainContainer,
|
||||
{ justifyContent: "center", alignItems: "center", padding: 20 },
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
backgroundColor: "rgba(255,255,255,0.05)",
|
||||
paddingVertical: 24,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 12,
|
||||
width: "85%",
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 36 }}>⚠️</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 18,
|
||||
fontWeight: "600",
|
||||
color: "white",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Fehler beim Laden
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 14,
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{error?.message || "Ein unerwarteter Fehler ist aufgetreten."}
|
||||
</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (typeof refetchShows === "function") refetchShows();
|
||||
if (typeof refetchServices === "function") refetchServices();
|
||||
}}
|
||||
style={{
|
||||
marginTop: 6,
|
||||
backgroundColor: "rgba(255,255,255,0.15)",
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 18,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "white", fontWeight: "600" }}>
|
||||
Erneut versuchen
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView>
|
||||
<View style={styles.mainContainer}>
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
router.push("/legal");
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 16,
|
||||
top: "63%",
|
||||
transform: [{ translateY: -12 }],
|
||||
height: 40,
|
||||
width: 40,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: 10,
|
||||
backgroundColor: "rgba(255,255,255,0.06)",
|
||||
}}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Menü öffnen"
|
||||
>
|
||||
<Feather name="info" size={22} color="#FFFFFF" />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text style={styles.title}>FLTR</Text>
|
||||
</View>
|
||||
|
||||
<RNScrollView
|
||||
contentContainerStyle={{ paddingBottom: 30 }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor="#FFFFFF" // iOS Spinner
|
||||
colors={["#FFFFFF"]} // Android Spinner
|
||||
progressBackgroundColor="hsla(0, 0%, 29%, 1.00)"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<View style={styles.filterSection}>
|
||||
{/* ⬅️ HORIZONTALER SCROLLBEREICH BLEIBT AUS RNGH */}
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 10,
|
||||
gap: 10,
|
||||
marginLeft: 10,
|
||||
}}
|
||||
>
|
||||
{activeFilter !== "all" && (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
padding: 5,
|
||||
height: 60,
|
||||
width: 60,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "hsla(0, 0%, 29%, 1.00)",
|
||||
borderRadius: 50,
|
||||
}}
|
||||
onPress={() => handleFilter("all")}
|
||||
>
|
||||
<Text style={{ fontWeight: "bold", color: "white" }}>
|
||||
ALLE
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{activeFilter !== "live" && (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
padding: 5,
|
||||
height: 60,
|
||||
width: 60,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "hsla(0, 0%, 29%, 1.00)",
|
||||
borderRadius: 50,
|
||||
}}
|
||||
onPress={() => handleFilter("live")}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "red",
|
||||
paddingHorizontal: 5,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 5,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontWeight: "bold", color: "white" }}>
|
||||
LIVE
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
height: 60,
|
||||
width: 2,
|
||||
backgroundColor: "hsla(0, 0%, 37%, 1.00)",
|
||||
marginHorizontal: 5,
|
||||
borderRadius: 5,
|
||||
}}
|
||||
/>
|
||||
|
||||
{uniqueStreamingServices.map((serviceName) => {
|
||||
const streamingService =
|
||||
streamingServices[
|
||||
`assets.images.streamingServices.${serviceName.toLowerCase()}`
|
||||
];
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={serviceName}
|
||||
style={{
|
||||
padding: 5,
|
||||
backgroundColor: "hsla(0, 0%, 29%, 1.00)",
|
||||
borderRadius: 50,
|
||||
borderWidth: serviceName.includes(activeFilter) ? 2 : 0,
|
||||
borderColor: "hsla(0, 100%, 50%, 1.00)",
|
||||
}}
|
||||
onPress={() => handleFilter(serviceName)}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: streamingService }}
|
||||
style={{
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: 30,
|
||||
resizeMode: "contain",
|
||||
}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
<View style={{ flex: 1, paddingHorizontal: 10 }}>
|
||||
{filteredShows.map((show) => {
|
||||
const showLiveBadge = show.running;
|
||||
return (
|
||||
<ShowCard
|
||||
key={show.id}
|
||||
title={show.title}
|
||||
onPress={() =>
|
||||
router.push({
|
||||
pathname: "/showDetails",
|
||||
params: {
|
||||
id: String(show.id),
|
||||
title: show.title,
|
||||
bannerUri: show.bannerUri,
|
||||
description: show.description,
|
||||
concept: show.concept,
|
||||
genres: show.genres,
|
||||
streamingService: show.streamingService,
|
||||
logoUri: show.logoUrl,
|
||||
running: String(show.running),
|
||||
},
|
||||
})
|
||||
}
|
||||
imageUri={show.bannerUri}
|
||||
streamingServicesUris={show.streamingService
|
||||
.split(", ")
|
||||
.map(
|
||||
(s) =>
|
||||
streamingServices[
|
||||
`assets.images.streamingServices.${s.toLowerCase()}`
|
||||
]
|
||||
)}
|
||||
genres={show.genres}
|
||||
{...(showLiveBadge
|
||||
? {
|
||||
liveBadgeText: "LIVE",
|
||||
liveBadgeContainerStyle: styles.liveBadgeContainer,
|
||||
}
|
||||
: {})}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</RNScrollView>
|
||||
</View>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
export default function () {
|
||||
return <Redirect href="/home" />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user