Your music, beautifully tracked. All yours. (coming soon) teal.fm
teal-fm atproto

add actorplayview, clean up general ui a tad

+118 -50
+4 -4
apps/amethyst/app/(tabs)/_layout.tsx
··· 12 12 function TabBarIcon(props: { name: LucideIcon; color: string }) { 13 13 const Name = props.name; 14 14 iconWithClassName(Name); 15 - return <Name size={28} className="" {...props} />; 15 + return <Name size={28} className="text-muted" {...props} />; 16 16 } 17 17 18 18 export default function TabLayout() { ··· 33 33 tabBarShowLabel: true, 34 34 tabBarStyle: { 35 35 //height: 75, 36 - display: hideTabBar ? "none" : "flex", 36 + display: "flex", 37 37 }, 38 38 }} 39 39 > 40 40 <Tabs.Screen 41 41 name="index" 42 42 options={{ 43 - title: "Tab One", 43 + title: "Home", 44 44 tabBarIcon: ({ color }) => <TabBarIcon name={Home} color={color} />, 45 45 headerRight: () => ( 46 46 <Link href="/auth/logoutModal" asChild> ··· 48 48 {({ pressed }) => ( 49 49 <Icon 50 50 icon={LogOut} 51 - className="text-2xl mr-4" 51 + className="text-2xl mr-4 text-muted-foreground" 52 52 name="log-out" 53 53 /> 54 54 )}
+31 -43
apps/amethyst/app/(tabs)/index.tsx
··· 1 1 import * as React from "react"; 2 2 import { ActivityIndicator, View } from "react-native"; 3 3 import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 4 - import { 5 - Card, 6 - CardContent, 7 - CardHeader, 8 - CardTitle, 9 - } from "../../components/ui/card"; 4 + import { CardHeader, CardTitle } from "../../components/ui/card"; 10 5 import { Text } from "@/components/ui/text"; 11 6 import { useStore } from "@/stores/mainStore"; 12 7 import AuthOptions from "../auth/options"; 13 8 14 9 import { Response } from "@atproto/api/src/client/types/app/bsky/actor/getProfile"; 15 - import { Link, Stack } from "expo-router"; 16 - import { Button } from "@/components/ui/button"; 10 + import { Stack } from "expo-router"; 11 + import ActorPlaysView from "@/components/play/actorPlaysView"; 17 12 18 13 const GITHUB_AVATAR_URI = 19 14 "https://i.pinimg.com/originals/ef/a2/8d/efa28d18a04e7fa40ed49eeb0ab660db.jpg"; ··· 54 49 } 55 50 56 51 return ( 57 - <View className="flex-1 justify-center items-center gap-5 p-6 bg-background"> 52 + <View className="flex-1 justify-start items-start gap-5 p-6 bg-background"> 58 53 <Stack.Screen 59 54 options={{ 60 55 title: "Home", 61 56 headerBackButtonDisplayMode: "minimal", 62 - headerShown: false, 57 + headerShown: true, 63 58 }} 64 59 /> 65 - <Card className="py-6 rounded-2xl border-2 border-foreground"> 66 - <CardHeader className="items-center pb-0"> 67 - <Avatar alt="Rick Sanchez's Avatar" className="w-24 h-24"> 68 - <AvatarImage 69 - source={{ uri: profile?.data.avatar ?? GITHUB_AVATAR_URI }} 70 - /> 71 - <AvatarFallback> 72 - <Text> 73 - {profile?.data.displayName?.substring(0, 1) ?? " Richard"} 60 + <CardHeader className="items-start pb-0"> 61 + <Avatar alt="Rick Sanchez's Avatar" className="w-24 h-24"> 62 + <AvatarImage 63 + source={{ uri: profile?.data.avatar ?? GITHUB_AVATAR_URI }} 64 + /> 65 + <AvatarFallback> 66 + <Text> 67 + {profile?.data.displayName?.substring(0, 1) ?? " Richard"} 68 + </Text> 69 + </AvatarFallback> 70 + </Avatar> 71 + <View className="px-3" /> 72 + <CardTitle className="text-center"> 73 + {profile?.data.displayName ?? " Richard"} 74 + </CardTitle> 75 + {profile 76 + ? profile.data?.description?.split("\n").map((str, i) => ( 77 + <Text className="text-start self-start place-self-start" key={i}> 78 + {str} 74 79 </Text> 75 - </AvatarFallback> 76 - </Avatar> 77 - <View className="px-3" /> 78 - <CardTitle className="text-center"> 79 - {profile?.data.displayName ?? " Richard"} 80 - </CardTitle> 81 - <CardContent className="text-center w-full"> 82 - {profile 83 - ? profile.data?.description?.split("\n").map((str, i) => ( 84 - <Text className="text-center" key={i}> 85 - {str} 86 - </Text> 87 - )) || "A very mysterious person" 88 - : "Loading..."} 89 - </CardContent> 90 - </CardHeader> 91 - <CardContent className="flex flex-row justify-center items-center p-0"> 92 - <Link href="/stamp"> 93 - <Button> 94 - <Text className="text-center">Ready to stamp!</Text>{" "} 95 - </Button> 96 - </Link> 97 - </CardContent> 98 - </Card> 80 + )) || "A very mysterious person" 81 + : "Loading..."} 82 + </CardHeader> 83 + <View className="max-w-xl w-full gap-2 pl-6"> 84 + <Text className="text-left text-3xl font-serif">Your Stamps</Text> 85 + <ActorPlaysView repo={profile.data.did} /> 86 + </View> 99 87 </View> 100 88 ); 101 89 }
+1 -1
apps/amethyst/app/_layout.tsx
··· 84 84 85 85 return ( 86 86 <SafeAreaView className="flex-1 flex flex-row min-h-screen justify-center bg-background"> 87 - <View className="max-w-screen-lg flex flex-1 border-x border-muted-foreground/20"> 87 + <View className="max-w-screen-md flex flex-1 border-x border-muted-foreground/20"> 88 88 {<RootLayoutNav />} 89 89 </View> 90 90 </SafeAreaView>
+1 -1
apps/amethyst/app/auth/logoutModal.tsx
··· 18 18 }; 19 19 return ( 20 20 <TouchableOpacity 21 - className="flex relative justify-center items-center bg-muted-foreground/60 w-screen h-screen backdrop-blur-sm" 21 + className="flex relative justify-center items-center bg-muted/60 w-full h-screen backdrop-blur-sm" 22 22 onPress={() => handleGoBack()} 23 23 > 24 24 <Icon icon={X} className="top-2 right-2 absolute" name="x" />
+1 -1
apps/amethyst/app/auth/signup.tsx
··· 4 4 import { Text } from "@/components/ui/text"; 5 5 import { Button } from "@/components/ui/button"; 6 6 import { Icon } from "@/lib/icons/iconWithClassName"; 7 - import { ArrowRight, Info } from "lucide-react-native"; 7 + import { ArrowRight } from "lucide-react-native"; 8 8 9 9 import { Stack, router } from "expo-router"; 10 10 import { FontAwesome6 } from "@expo/vector-icons";
+48
apps/amethyst/components/play/actorPlaysView.tsx
··· 1 + import { useStore } from "@/stores/mainStore"; 2 + import { Record as Play } from "@teal/lexicons/src/types/fm/teal/alpha/feed/play"; 3 + import { useEffect, useState } from "react"; 4 + import { View, Text, ScrollView } from "react-native"; 5 + import PlayView from "./playView"; 6 + interface ActorPlaysViewProps { 7 + repo: string; 8 + } 9 + interface PlayWrapper { 10 + cid: string; 11 + uri: string; 12 + value: Play; 13 + } 14 + const ActorPlaysView = ({ repo }: ActorPlaysViewProps) => { 15 + const [play, setPlay] = useState<PlayWrapper[] | null>(null); 16 + const agent = useStore((state) => state.pdsAgent); 17 + const isReady = useStore((state) => state.isAgentReady); 18 + useEffect(() => { 19 + if (agent) { 20 + agent 21 + .call("com.atproto.repo.listRecords", { 22 + repo, 23 + collection: "fm.teal.alpha.feed.play", 24 + }) 25 + .then((profile) => { 26 + profile.data.records as PlayWrapper[]; 27 + return setPlay(profile.data.records); 28 + }) 29 + .catch((e) => { 30 + console.log(e); 31 + }); 32 + } else { 33 + console.log("No agent"); 34 + } 35 + }, [isReady, agent, repo]); 36 + if (!play) { 37 + return <Text>Loading...</Text>; 38 + } 39 + return ( 40 + <ScrollView className="w-full *:gap-4"> 41 + {play.map((p) => ( 42 + <PlayView play={p.value} /> 43 + ))} 44 + </ScrollView> 45 + ); 46 + }; 47 + 48 + export default ActorPlaysView;
+32
apps/amethyst/components/play/playView.tsx
··· 1 + import { Record as Play } from "@teal/lexicons/src/types/fm/teal/alpha/feed/play"; 2 + import { View, Image, Text } from "react-native"; 3 + 4 + const PlayView = ({ play }: { play: Play }) => { 5 + return ( 6 + <View className="flex flex-row gap-2 max-w-full"> 7 + <Image 8 + className="w-20 h-20 rounded-lg bg-gray-500/50" 9 + source={{ 10 + uri: `https://coverartarchive.org/release/${play.releaseMbId}/front-250`, 11 + }} 12 + /> 13 + <View className="shrink"> 14 + <Text className="text-lg text-foreground line-clamp-1 overflow-ellipsis"> 15 + {play.trackName} 16 + </Text> 17 + {play.artistNames && ( 18 + <Text className="text-lg text-left text-muted-foreground"> 19 + {play.artistNames.join(", ")} 20 + </Text> 21 + )} 22 + {play.releaseName && ( 23 + <Text className="text-left text-muted-foreground line-clamp-1 overflow-ellipsis"> 24 + {play.releaseName} 25 + </Text> 26 + )} 27 + </View> 28 + </View> 29 + ); 30 + }; 31 + 32 + export default PlayView;