- "Share ↗" on every song (web and mobile): the phone's share sheet, or copy the link on desktop. Links go to a new song page, /t/<id>?from=you, with big artwork and open-in buttons for every service. - Server-rendered previews (Open Graph / Twitter cards) for song pages and profiles: title, who shared it or what they're playing, and the album art, avatar or logo. The web server fetches from the API over the compose network (INTERNAL_API_URL). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { Image } from 'expo-image';
|
||||
import { Linking, Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import { SERVICE_LABELS, STREAMING_SERVICES, openUrl, type StreamingService, type TrackDTO } from '@musicshare/shared';
|
||||
import { Linking, Pressable, Share, StyleSheet, Text, View } from 'react-native';
|
||||
import { SERVICE_LABELS, STREAMING_SERVICES, openUrl, shareText, trackUrl, type StreamingService, type TrackDTO } from '@musicshare/shared';
|
||||
import { WEB_URL } from '@/lib/config';
|
||||
import { useSession } from '@/lib/session';
|
||||
import { showError } from '@/lib/errors';
|
||||
import { colors, fonts, radius, space } from '@/lib/theme';
|
||||
import { Button } from './ui';
|
||||
@@ -11,7 +13,15 @@ import { Button } from './ui';
|
||||
*/
|
||||
const open = (url: string) => Linking.openURL(url).catch((e) => showError(e, "Couldn't open link"));
|
||||
|
||||
/** The phone's share sheet, with a link to the song's page (which opens it in any service). */
|
||||
function shareTrack(track: TrackDTO, from: string | undefined) {
|
||||
const url = trackUrl(WEB_URL, track.id, from);
|
||||
// Android shows only `message`; iOS shows `url` as a rich link next to it.
|
||||
Share.share({ message: `${shareText(track)} ${url}`, url, title: shareText(track) }).catch((e) => showError(e, "Couldn't share"));
|
||||
}
|
||||
|
||||
export function TrackCard({ track, preferred }: { track: TrackDTO; preferred: StreamingService | null | undefined }) {
|
||||
const { me } = useSession();
|
||||
const primary = preferred ? openUrl(track, preferred) : null;
|
||||
const others = STREAMING_SERVICES.filter((s) => s !== preferred);
|
||||
|
||||
@@ -50,6 +60,9 @@ export function TrackCard({ track, preferred }: { track: TrackDTO; preferred: St
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
<Pressable onPress={() => shareTrack(track, me?.username)} hitSlop={6} accessibilityRole="button" accessibilityLabel="Share this song">
|
||||
<Text style={[styles.service, styles.share]}>Share ↗</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -72,6 +85,7 @@ const styles = StyleSheet.create({
|
||||
title: { color: colors.text, fontFamily: fonts.display, fontSize: 18, letterSpacing: -0.2 },
|
||||
artist: { color: colors.text, opacity: 0.85, fontSize: 14 },
|
||||
muted: { color: colors.muted },
|
||||
share: { backgroundColor: 'rgba(123,47,247,0.28)', borderColor: 'rgba(123,47,247,0.5)' },
|
||||
primary: { alignSelf: 'flex-start', marginTop: space.sm },
|
||||
services: { flexDirection: 'row', flexWrap: 'wrap', gap: space.xs + 2, marginTop: space.sm + 2 },
|
||||
service: {
|
||||
|
||||
Reference in New Issue
Block a user