modified: files to ios26 ui/ux
This commit is contained in:
@@ -1,23 +1,32 @@
|
||||
import { PersonMini } from "@/apis/personHistoryApi";
|
||||
import styles from "@/app/stackStyles/participantStyles";
|
||||
import { usePersonHistory, AppearanceGroup } from "@/hooks/usePersonHistory";
|
||||
import { usePersonHistory } from "@/hooks/usePersonHistory";
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import React from "react";
|
||||
import { Image, Text, TouchableOpacity, View } from "react-native";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
import {
|
||||
GestureHandlerRootView,
|
||||
ScrollView,
|
||||
} from "react-native-gesture-handler";
|
||||
|
||||
export default function ParticipantScreen() {
|
||||
const { name, participantId } = useLocalSearchParams();
|
||||
const { name, participantId, imageUri } = useLocalSearchParams();
|
||||
|
||||
const pid = Array.isArray(participantId)
|
||||
? Number(participantId[0])
|
||||
: Number(participantId);
|
||||
|
||||
const imageUriString = Array.isArray(imageUri) ? imageUri[0] : imageUri;
|
||||
const isPravatar = imageUriString?.includes("pravatar");
|
||||
|
||||
const { data: appearances = [], isLoading, isError } = usePersonHistory(pid);
|
||||
|
||||
const formatYear = (iso?: string | null) => {
|
||||
@@ -27,7 +36,7 @@ export default function ParticipantScreen() {
|
||||
};
|
||||
|
||||
const [expandedShows, setExpandedShows] = React.useState<Set<number>>(
|
||||
new Set()
|
||||
new Set(),
|
||||
);
|
||||
const toggleExpand = React.useCallback((showId: number) => {
|
||||
setExpandedShows((prev) => {
|
||||
@@ -45,193 +54,240 @@ export default function ParticipantScreen() {
|
||||
const goToPerson = React.useCallback(
|
||||
(p: PersonMini) => {
|
||||
if (!p?.personId) return;
|
||||
|
||||
if (p.personId === pid) return;
|
||||
router.push({
|
||||
pathname: "/participant",
|
||||
params: { participantId: String(p.personId), name: p.name },
|
||||
params: {
|
||||
participantId: String(p.personId),
|
||||
name: p.name,
|
||||
imageUri: p.imageUrl || "",
|
||||
},
|
||||
});
|
||||
},
|
||||
[pid]
|
||||
[pid],
|
||||
);
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView style={styles.mainContainer}>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{ paddingBottom: 20, paddingTop: 10}}
|
||||
|
||||
contentContainerStyle={{ paddingBottom: 40, paddingTop: 100 }}
|
||||
>
|
||||
<Text style={styles.participantName}>{name}</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.closeIcon}
|
||||
onPress={() => router.back()}
|
||||
>
|
||||
<Ionicons name="close-circle-outline" size={38} color="white" />
|
||||
</TouchableOpacity>
|
||||
{/* Profile Hero */}
|
||||
<View style={styles.profileHero}>
|
||||
<View style={styles.profileImageContainer}>
|
||||
<Image
|
||||
source={{ uri: imageUriString || undefined }}
|
||||
style={styles.profileImage}
|
||||
resizeMode="cover"
|
||||
blurRadius={isPravatar ? 16 : 0}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.participantName}>{name}</Text>
|
||||
<Text style={styles.participantSubtitle}>
|
||||
{appearances.length}{" "}
|
||||
{appearances.length === 1 ? "Auftritt" : "Auftritte"}
|
||||
</Text>
|
||||
<View style={styles.heroButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.searchButton}
|
||||
onPress={() =>
|
||||
WebBrowser.openBrowserAsync(
|
||||
"https://www.google.com/search?udm=2&q=" +
|
||||
encodeURIComponent(String(name)),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Ionicons name="images-outline" size={18} color="#199edb" />
|
||||
<Text style={styles.searchButtonText}>Bilder</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={styles.instagramButton}
|
||||
onPress={() =>
|
||||
WebBrowser.openBrowserAsync(
|
||||
"https://www.google.com/search?q=" +
|
||||
encodeURIComponent(`${String(name)} Instagram`),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Ionicons name="logo-instagram" size={18} color="#E1306C" />
|
||||
<Text style={styles.instagramButtonText}>Instagram</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.performedShowsSection}>
|
||||
<TouchableOpacity
|
||||
style={styles.searchButton}
|
||||
onPress={() =>
|
||||
WebBrowser.openBrowserAsync(
|
||||
"https://www.google.com/search?udm=2&q=" +
|
||||
encodeURIComponent(String(name))
|
||||
)
|
||||
}
|
||||
>
|
||||
<Ionicons name="images-outline" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
{/* Loading */}
|
||||
{isLoading && (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color="#199edb" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Text style={styles.performedShowsTitle}>Auftritte:</Text>
|
||||
{/* Appearances */}
|
||||
{!isLoading && appearances.length > 0 && (
|
||||
<View style={styles.performedShowsSection}>
|
||||
<Text style={styles.performedShowsTitle}>Auftritte</Text>
|
||||
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={{
|
||||
gap: 20,
|
||||
paddingHorizontal: 15,
|
||||
paddingLeft: 30,
|
||||
}}
|
||||
>
|
||||
{appearances.toReversed().map(({ show, seasons }) => {
|
||||
const partners = Array.from(
|
||||
new Map(
|
||||
seasons
|
||||
.map((s) => s.partner)
|
||||
.filter((p): p is NonNullable<typeof p> => !!p)
|
||||
.map((p) => [p.personId, p])
|
||||
).values()
|
||||
);
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={{
|
||||
gap: 20,
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 12,
|
||||
}}
|
||||
>
|
||||
{appearances.toReversed().map(({ show, seasons }) => {
|
||||
const partners = Array.from(
|
||||
new Map(
|
||||
seasons
|
||||
.map((s) => s.partner)
|
||||
.filter((p): p is NonNullable<typeof p> => !!p)
|
||||
.map((p) => [p.personId, p]),
|
||||
).values(),
|
||||
);
|
||||
|
||||
const allParticipants = Array.from(
|
||||
new Map(
|
||||
seasons
|
||||
.flatMap((s) => s.participants)
|
||||
.filter((p) => p.personId !== pid)
|
||||
.map((p) => [p.personId, p])
|
||||
).values()
|
||||
);
|
||||
const allParticipants = Array.from(
|
||||
new Map(
|
||||
seasons
|
||||
.flatMap((s) => s.participants)
|
||||
.filter((p) => p.personId !== pid)
|
||||
.map((p) => [p.personId, p]),
|
||||
).values(),
|
||||
);
|
||||
|
||||
const isExpanded = expandedShows.has(show.id);
|
||||
const visible = isExpanded
|
||||
? allParticipants
|
||||
: allParticipants.slice(0, 12);
|
||||
const restCount = Math.max(
|
||||
allParticipants.length - visible.length,
|
||||
0
|
||||
);
|
||||
const isExpanded = expandedShows.has(show.id);
|
||||
const visible = isExpanded
|
||||
? allParticipants
|
||||
: allParticipants.slice(0, 12);
|
||||
const restCount = Math.max(
|
||||
allParticipants.length - visible.length,
|
||||
0,
|
||||
);
|
||||
|
||||
return (
|
||||
<View key={show.id} style={styles.card}>
|
||||
<TouchableOpacity
|
||||
style={styles.showContainer}
|
||||
onPress={() => goToShow(show.id)}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: show.bannerUri || show.thumbnailUri }}
|
||||
style={styles.showImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.showTitle} numberOfLines={1}>
|
||||
{show.title}
|
||||
</Text>
|
||||
<Text style={styles.showSeason}>
|
||||
({formatYear(seasons[0]?.startDate)})
|
||||
</Text>
|
||||
<Text style={styles.showSeason}>
|
||||
Staffel {seasons.map((s) => s.seasonNumber).join(" und ")}
|
||||
</Text>
|
||||
return (
|
||||
<View key={show.id} style={styles.card}>
|
||||
<TouchableOpacity
|
||||
style={styles.showContainer}
|
||||
onPress={() => goToShow(show.id)}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: show.bannerUri || show.thumbnailUri }}
|
||||
style={styles.showImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.horizontalLine} />
|
||||
|
||||
<Text style={[styles.participantLabel, { marginTop: 10 }]}>
|
||||
Weitere Teilnehmer
|
||||
</Text>
|
||||
|
||||
<View style={styles.participantContainer}>
|
||||
<View style={styles.participantRow}>
|
||||
{visible.map((p) => (
|
||||
<TouchableOpacity
|
||||
key={p.personId}
|
||||
style={styles.participantChip}
|
||||
onPress={() => goToPerson(p)}
|
||||
>
|
||||
<Text
|
||||
style={styles.participantChipText}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{p.name}
|
||||
<View style={styles.cardInfo}>
|
||||
<View style={styles.cardTitleRow}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.showTitle} numberOfLines={1}>
|
||||
{show.title}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
|
||||
{!isExpanded && restCount > 0 && (
|
||||
<TouchableOpacity
|
||||
onPress={() => toggleExpand(show.id)}
|
||||
style={styles.moreChip}
|
||||
>
|
||||
<Text style={styles.moreChipText}>
|
||||
+{restCount} mehr
|
||||
<Text style={styles.showSeason}>
|
||||
Staffel{" "}
|
||||
{seasons.map((s) => s.seasonNumber).join(" & ")}
|
||||
{" · "}
|
||||
{formatYear(seasons[0]?.startDate)}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={styles.cardSearchButton}
|
||||
onPress={() =>
|
||||
WebBrowser.openBrowserAsync(
|
||||
"https://www.google.com/search?udm=2&q=" +
|
||||
encodeURIComponent(
|
||||
`${String(name)} ${show.title}`,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Ionicons
|
||||
name="images-outline"
|
||||
size={16}
|
||||
color="#199edb"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{partners.length > 0 && (
|
||||
<View style={styles.partnerSection}>
|
||||
<Text style={styles.sectionLabel}>Partner</Text>
|
||||
<View style={styles.partnerRow}>
|
||||
{partners.map((p) => (
|
||||
<TouchableOpacity
|
||||
key={p.personId}
|
||||
style={styles.partnerChip}
|
||||
onPress={() => goToPerson(p)}
|
||||
>
|
||||
<Ionicons
|
||||
name="heart"
|
||||
size={12}
|
||||
color="#e74c8b"
|
||||
/>
|
||||
<Text
|
||||
style={styles.partnerChipText}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{p.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{isExpanded && allParticipants.length > 12 && (
|
||||
<TouchableOpacity
|
||||
onPress={() => toggleExpand(show.id)}
|
||||
style={styles.moreChip}
|
||||
>
|
||||
<Text style={styles.moreChipText}>Weniger</Text>
|
||||
</TouchableOpacity>
|
||||
{allParticipants.length > 0 && (
|
||||
<View style={styles.participantsSection}>
|
||||
<Text style={styles.sectionLabel}>
|
||||
Weitere Teilnehmer
|
||||
</Text>
|
||||
<View style={styles.participantRow}>
|
||||
{visible.map((p) => (
|
||||
<TouchableOpacity
|
||||
key={p.personId}
|
||||
style={styles.participantChip}
|
||||
onPress={() => goToPerson(p)}
|
||||
>
|
||||
<Text
|
||||
style={styles.participantChipText}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{p.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
|
||||
{!isExpanded && restCount > 0 && (
|
||||
<TouchableOpacity
|
||||
onPress={() => toggleExpand(show.id)}
|
||||
style={styles.moreChip}
|
||||
>
|
||||
<Text style={styles.moreChipText}>
|
||||
+{restCount} mehr
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{isExpanded && allParticipants.length > 12 && (
|
||||
<TouchableOpacity
|
||||
onPress={() => toggleExpand(show.id)}
|
||||
style={styles.moreChip}
|
||||
>
|
||||
<Text style={styles.moreChipText}>Weniger</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{partners.length > 0 && (
|
||||
<>
|
||||
<View style={styles.horizontalLine} />
|
||||
<Text
|
||||
style={[styles.participantLabel, { marginTop: 10 }]}
|
||||
>
|
||||
Partner
|
||||
</Text>
|
||||
<View
|
||||
style={[
|
||||
styles.showContainer,
|
||||
{
|
||||
backgroundColor: "hsl(221, 39%, 12%)",
|
||||
width: 150,
|
||||
marginTop: 20,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
style={styles.showImage}
|
||||
blurRadius={20}
|
||||
source={{
|
||||
uri: `https://i.pravatar.cc/300?img=${Math.floor(Math.random() * 70)}`,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{partners.map((p) => (
|
||||
<Text
|
||||
key={p.personId}
|
||||
style={styles.partnerLabel}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{p.name}
|
||||
</Text>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user