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

Merge pull request #61 from teal-fm/natb/time-on-play-view

home: show time on play view

authored by

natalie and committed by
GitHub
c3b3be71 e85e000c

+83 -12
+1 -1
apps/amethyst/app/(tabs)/(stamp)/stamp/index.tsx
··· 87 87 headerBackButtonDisplayMode: "generic", 88 88 }} 89 89 /> 90 - i {/* Search Form */} 90 + {/* Search Form */} 91 91 <View className="flex gap-2 max-w-2xl w-screen px-4"> 92 92 <Text className="font-bold text-lg">Search for a track</Text> 93 93 <Input
+38 -5
apps/amethyst/app/(tabs)/(stamp)/stamp/submit.tsx
··· 16 16 import { Switch, View } from "react-native"; 17 17 import { MusicBrainzRecording, PlaySubmittedData } from "@/lib/oldStamp"; 18 18 import { Text } from "@/components/ui/text"; 19 + import { Textarea } from "@/components/ui/textarea"; 19 20 import { ExternalLink } from "@/components/ExternalLink"; 20 21 import { StampContext, StampContextValue, StampStep } from "./_layout"; 21 22 import { Image } from "react-native"; 22 23 import { Artist } from "@teal/lexicons/src/types/fm/teal/alpha/feed/defs"; 24 + import { cn } from "@/lib/utils"; 23 25 24 26 type CardyBResponse = { 25 27 error: string; ··· 162 164 163 165 const [isSubmitting, setIsSubmitting] = useState<boolean>(false); 164 166 const [shareWithBluesky, setShareWithBluesky] = useState<boolean>(false); 167 + const [blueskyPostText, setBlueskyPostText] = useState<string>(""); 165 168 166 169 const [blueskyEmbedCard, setBlueskyEmbedCard] = useState<EmbedCard | null>( 167 170 null, ··· 170 173 const selectedTrack = 171 174 state.step === StampStep.SUBMITTING ? state.submittingStamp : null; 172 175 176 + // Effect to initialize blueskyPostText when selectedTrack changes 177 + useEffect(() => { 178 + if (selectedTrack) { 179 + const defaultText = `💮 now playing: 180 + ${selectedTrack.title} by ${selectedTrack["artist-credit"]?.map((a) => a.artist.name).join(", ")} 181 + 182 + powered by @teal.fm`; 183 + setBlueskyPostText(defaultText); 184 + } 185 + }, [selectedTrack]); 186 + 173 187 useEffect(() => { 174 188 const fetchEmbedData = async (id: string) => { 175 189 try { ··· 249 263 if (shareWithBluesky && agent) { 250 264 // lol this type 251 265 const rt = new RichText({ 252 - text: `💮 now playing: 253 - ${record.trackName} by ${record.artists?.map((a) => a.artistName).join(", ")} 254 - 255 - powered by @teal.fm`, 266 + text: blueskyPostText, 256 267 }); 257 268 await rt.detectFacets(agent); 258 269 let embedInfo = await getEmbedInfo(selectedTrack.id); ··· 330 341 </Text> 331 342 </View> 332 343 333 - <View className="flex-col gap-4 items-center"> 344 + <View className="flex-col gap-4 items-center w-full"> 334 345 {blueskyEmbedCard && shareWithBluesky ? ( 335 346 <View className="gap-2 w-full"> 336 347 <Text className="text-sm text-muted-foreground text-center"> ··· 359 370 jsyk: there won't be an embed card on your post. 360 371 </Text> 361 372 ) 373 + )} 374 + {shareWithBluesky && ( 375 + <View className="text-sm text-muted-foreground w-full items-end"> 376 + <Textarea 377 + className="w-full p-2 pb-4 border border-border rounded-md text-card-foreground bg-card min-h-[100px] max-h-[200px]" 378 + multiline 379 + value={blueskyPostText} 380 + onChangeText={setBlueskyPostText} 381 + placeholder="Enter your Bluesky post text here..." 382 + /> 383 + <Text 384 + className={cn( 385 + "text-sm text-muted-foreground text-center absolute bottom-1 right-2", 386 + blueskyPostText.length > 150 387 + ? "text-gray-600 dark:text-gray-300" 388 + : "", 389 + blueskyPostText.length > 290 ? "text-red-500" : "", 390 + )} 391 + > 392 + {blueskyPostText.length}/300 393 + </Text> 394 + </View> 362 395 )} 363 396 <View className="flex-row gap-2 items-center"> 364 397 <Switch
+1 -1
apps/amethyst/components/play/actorPlaysView.tsx
··· 42 42 {play.map((p) => ( 43 43 <PlayView 44 44 key={p.playedTime + p.trackName} 45 - releaseTitle={p.releaseName} 45 + dateListened={p.playedTime ? new Date(p.playedTime) : undefined} 46 46 trackTitle={p.trackName} 47 47 artistName={p.artists.map((a) => a.artistName).join(", ")} 48 48 releaseMbid={p.releaseMbId}
+6 -5
apps/amethyst/components/play/playView.tsx
··· 1 1 import { View, Image } from "react-native"; 2 2 import { Text } from "@/components/ui/text"; 3 + import { timeAgo } from "@/lib/utils"; 3 4 4 5 export default function PlayView({ 5 6 releaseMbid, 6 7 trackTitle, 7 8 artistName, 8 - releaseTitle, 9 + dateListened, 9 10 }: { 10 11 releaseMbid?: string; 11 12 trackTitle: string; 12 13 artistName?: string; 13 - releaseTitle?: string; 14 + dateListened?: Date; 14 15 }) { 15 16 return ( 16 17 <View className="flex flex-row gap-2 max-w-full"> ··· 27 28 {trackTitle} 28 29 </Text> 29 30 {artistName && ( 30 - <Text className=" text-left text-muted-foreground line-clamp-1 overflow-ellipsis"> 31 + <Text className="text-sm text-left text-foreground line-clamp-1 overflow-ellipsis"> 31 32 {artistName} 32 33 </Text> 33 34 )} 34 - {releaseTitle && ( 35 + {dateListened && ( 35 36 <Text className="text-sm text-left text-muted-foreground line-clamp-1 overflow-ellipsis"> 36 - {releaseTitle} 37 + played {timeAgo(dateListened)} 37 38 </Text> 38 39 )} 39 40 </View>
+37
apps/amethyst/lib/utils.ts
··· 10 10 let first = arr.shift()?.toUpperCase(); 11 11 return (first || "") + arr.join(""); 12 12 } 13 + 14 + export function timeAgo(date: Date) { 15 + const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000); 16 + let interval = Math.floor(seconds / 31536000); 17 + 18 + // return date.toLocaleDateString("en-US"); 19 + if (interval > 1) { 20 + const formatter = new Intl.DateTimeFormat("en-US", { 21 + year: "numeric", 22 + month: "short", 23 + day: "numeric", 24 + hour: "numeric", 25 + minute: "numeric", 26 + }); 27 + return "on " + formatter.format(date); 28 + } 29 + interval = Math.floor(seconds / 86400); 30 + // return date without years 31 + if (interval > 1) { 32 + const formatter = new Intl.DateTimeFormat("en-US", { 33 + month: "short", 34 + day: "numeric", 35 + hour: "numeric", 36 + minute: "numeric", 37 + }); 38 + return "on " + formatter.format(date); 39 + } 40 + interval = Math.floor(seconds / 3600); 41 + if (interval > 1) { 42 + return interval + " hours ago"; 43 + } 44 + interval = Math.floor(seconds / 60); 45 + if (interval > 1) { 46 + return interval + " minutes ago"; 47 + } 48 + return Math.floor(seconds) + " seconds ago"; 49 + }