initial port for dynamic playlists

This commit is contained in:
Boof2015
2026-07-04 13:25:41 -04:00
parent 61e6147d3e
commit f9ef845800
20 changed files with 2226 additions and 36 deletions
@@ -120,6 +120,7 @@ export function CollapsingHeader({
backdropUri,
title,
heroMeta,
heroExtra,
disabled,
onBack,
onPlay,
@@ -136,6 +137,8 @@ export function CollapsingHeader({
title: string;
/** The middle of the hero block, between title and buttons (subtitle/meta or stat chips). */
heroMeta: ReactNode;
/** Optional compact control below meta, before the Play / Shuffle buttons. */
heroExtra?: ReactNode;
disabled?: boolean;
onBack: () => void;
onPlay: () => void;
@@ -235,6 +238,7 @@ export function CollapsingHeader({
{title}
</Text>
{heroMeta}
{heroExtra}
<Animated.View style={[styles.actionRow, heroButtonsStyle]}>
<Pressable
style={[styles.actionButton, styles.primaryAction, disabled && styles.disabledAction]}
+4
View File
@@ -20,6 +20,7 @@ export function PlaylistRow({
coverHash,
pinned = false,
remote = false,
dynamic = false,
onPress,
onLongPress,
}: {
@@ -31,6 +32,8 @@ export function PlaylistRow({
pinned?: boolean;
/** Synced from a remote server — shows a cloud marker. */
remote?: boolean;
/** Rule-owned playlist — shows a spark marker. */
dynamic?: boolean;
onPress: () => void;
onLongPress?: () => void;
}) {
@@ -63,6 +66,7 @@ export function PlaylistRow({
{name}
</Text>
{remote ? <Ionicons name="cloud" size={12} color={colors.accent} /> : null}
{dynamic ? <Ionicons name="sparkles" size={12} color={colors.accent} /> : null}
</View>
<Text variant="label" numberOfLines={1}>
{`${trackCount} ${trackCount === 1 ? 'track' : 'tracks'}`}
+29
View File
@@ -108,6 +108,23 @@ export function PlaylistsView({
]
: menuFor
? [
...(menuFor.kind === 'dynamic'
? [
{
key: 'edit-rules',
label: 'Edit rules',
icon: 'options-outline' as const,
onPress: () => {
const id = menuFor.id;
setMenuFor(null);
router.push({
pathname: '/library/playlist/edit-dynamic' as never,
params: { id: String(id) },
});
},
},
]
: []),
{
key: 'rename',
label: 'Rename…',
@@ -168,6 +185,7 @@ export function PlaylistsView({
missingCount={item.missing_track_count}
coverHash={item.auto_cover_hash}
remote={item.remote_source_id != null}
dynamic={item.kind === 'dynamic'}
onPress={() => router.push(`/library/playlist/${item.id}`)}
onLongPress={() => setMenuFor(item)}
/>
@@ -192,6 +210,16 @@ export function PlaylistsView({
New playlist
</Text>
</Pressable>
<Pressable
style={styles.action}
onPress={() => router.push('/library/playlist/edit-dynamic' as never)}
accessibilityRole="button"
>
<Ionicons name="sparkles-outline" size={16} color={colors.accent} />
<Text variant="body" color={colors.accent}>
New dynamic
</Text>
</Pressable>
<Pressable
style={styles.action}
onPress={() => void handleImport()}
@@ -240,6 +268,7 @@ const styles = StyleSheet.create({
},
actions: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: spacing.md,
marginTop: spacing.lg,
},