import type { MediaAsset, MediaCollection, MediaProject, PlatformProfileSummary, } from "@rakazo/contracts"; import { Stack, useLocalSearchParams, useRouter } from "expo-router"; import { useEffect, useState } from "react"; import { ActivityIndicator, Alert, FlatList, Image, Pressable, StyleSheet, Text, useWindowDimensions, View, } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; import { NativeSymbol } from "../components/native-symbol"; import { authHeaders, currentApiBase } from "../lib/api"; import { mobileTokens } from "../lib/appearance"; import { useI18n } from "../lib/i18n"; import { mediaRpc } from "../lib/media-api"; import { assetThumbUrl, formatDuration } from "../lib/media-project"; import { presentMessageActionSheet } from "../lib/message-action-sheet"; import { native, useResolvedAppearance, useThemedStyles } from "../lib/native"; const COLUMNS = 3; const GAP = 2; export default function MediaSession() { const styles = useThemedStyles(createStyles); const { t } = useI18n(); const router = useRouter(); const appearance = useResolvedAppearance(); const { width } = useWindowDimensions(); const { collectionId } = useLocalSearchParams<{ collectionId: string }>(); const [collection, setCollection] = useState(null); const [assets, setAssets] = useState([]); const [headers, setHeaders] = useState>({}); const [creating, setCreating] = useState(false); const [error, setError] = useState(null); const tile = (width - GAP * (COLUMNS - 1)) / COLUMNS; useEffect(() => { if (!collectionId) return; void authHeaders().then(setHeaders); void mediaRpc<{ collection: MediaCollection; assets: MediaAsset[] }>("collections/get", { collectionId, }) .then((result) => { setCollection(result.collection); setAssets(result.assets); }) .catch((reason) => setError(reason instanceof Error ? reason.message : String(reason))); }, [collectionId]); async function create(profileId: string) { if (!collection) return; setCreating(true); try { const project = await mediaRpc("projects/create", { title: collection.title.slice(0, 120) || t("Media"), goal: "teaser", collectionIds: [collection.id], profileIds: [profileId], }); router.replace({ pathname: "/media-project", params: { projectId: project.id, upload: "1" }, }); } catch (reason) { Alert.alert(reason instanceof Error ? reason.message : String(reason)); } finally { setCreating(false); } } async function chooseProfile() { try { const profiles = await mediaRpc("profiles/list"); if (profiles.length === 1 && profiles[0]) { await create(profiles[0].id); return; } presentMessageActionSheet({ actions: profiles.map((profile) => ({ text: profile.name, onPress: () => void create(profile.id), })), cancel: t("Cancel"), more: t("More"), colorScheme: appearance, }); } catch (reason) { Alert.alert(reason instanceof Error ? reason.message : String(reason)); } } return ( asset.id} numColumns={COLUMNS} columnWrapperStyle={{ gap: GAP }} contentContainerStyle={{ gap: GAP }} contentInsetAdjustmentBehavior="automatic" ListHeaderComponent={error ? {error} : null} renderItem={({ item }) => ( {item.hasThumbnail ? ( ) : ( )} {item.kind === "video" && item.durationMs ? ( {formatDuration(item.durationMs)} ) : null} )} /> {collection ? ( [styles.primaryButton, pressed && styles.pressed]} onPress={() => void chooseProfile()} > {creating ? ( ) : ( {t("Upload and create project")} )} ) : null} ); } function createStyles() { const tokens = mobileTokens(); return StyleSheet.create({ screen: { flex: 1, backgroundColor: native.page }, tile: { backgroundColor: native.fill, alignItems: "center", justifyContent: "center", overflow: "hidden", }, duration: { position: "absolute", right: 6, bottom: 4, color: tokens.foreground, fontSize: 12, fontWeight: "600", fontVariant: ["tabular-nums"], textShadowColor: tokens.background, textShadowRadius: 3, }, footer: { paddingHorizontal: 16, paddingTop: 8, backgroundColor: native.page }, primaryButton: { minHeight: 50, borderRadius: 14, backgroundColor: tokens.primary, alignItems: "center", justifyContent: "center", marginBottom: 8, }, pressed: { opacity: 0.8 }, primaryLabel: { color: tokens.primaryForeground, fontSize: 16, fontWeight: "600" }, error: { color: tokens.destructive, fontSize: 14, padding: 16 }, }); }