diff --git a/flutter_client/lib/playlists/widgets/playlist_card.dart b/flutter_client/lib/playlists/widgets/playlist_card.dart index cf439056..c7a0a2c5 100644 --- a/flutter_client/lib/playlists/widgets/playlist_card.dart +++ b/flutter_client/lib/playlists/widgets/playlist_card.dart @@ -122,6 +122,16 @@ class PlaylistCard extends ConsumerWidget { ), ), ), + if (playlist.isSystem && _refreshedLabel(playlist.createdAt) != '') + Padding( + padding: const EdgeInsets.only(top: 2), + child: Text( + _refreshedLabel(playlist.createdAt), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: fs.ash, fontSize: 11), + ), + ), ]), ), ), @@ -131,6 +141,27 @@ class PlaylistCard extends ConsumerWidget { String get _refreshLabel => 'Refresh ${playlist.name}'; + /// #417: system playlists atomic-replace on rebuild, so createdAt + /// is the last-rotated time. Friendly wording mirrors the web + /// PlaylistCard. Empty string when the timestamp is unparseable. + String _refreshedLabel(String iso) { + final t = DateTime.tryParse(iso); + if (t == null) return ''; + final now = DateTime.now(); + final mins = now.difference(t).inMinutes; + if (mins < 5) return 'Refreshed just now'; + final startOfToday = DateTime(now.year, now.month, now.day); + if (!t.isBefore(startOfToday)) return 'Refreshed today'; + final days = startOfToday.difference(DateTime(t.year, t.month, t.day)).inDays; + if (days <= 1) return 'Refreshed yesterday'; + if (days < 7) return 'Refreshed $days days ago'; + const months = [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', + ]; + return 'Refreshed ${months[t.month - 1]} ${t.day}'; + } + /// Forces a server-side rebuild of this system playlist, then /// invalidates the aggregate list so the rotated UUID + new tracks /// land on the next read. ScaffoldMessenger is captured before the diff --git a/web/src/lib/components/PlaylistCard.svelte b/web/src/lib/components/PlaylistCard.svelte index 1436afca..cd401158 100644 --- a/web/src/lib/components/PlaylistCard.svelte +++ b/web/src/lib/components/PlaylistCard.svelte @@ -17,6 +17,27 @@ // (#411 R2) — no hardcoded kind list here. const refreshable = $derived(playlist.refreshable === true); + // #417: system playlists atomic-replace on rebuild, so created_at + // is the last-rotated time. Surface it so users know how fresh the + // mix is. Friendly wording, not the HistoryRow "m/h ago" style. + function refreshedLabel(iso: string): string { + const t = new Date(iso); + if (Number.isNaN(t.getTime())) return ''; + const now = new Date(); + const mins = Math.floor((now.getTime() - t.getTime()) / 60000); + if (mins < 5) return 'Refreshed just now'; + const startOfToday = new Date(now); + startOfToday.setHours(0, 0, 0, 0); + const days = Math.floor((startOfToday.getTime() - t.getTime()) / 86400000) + 1; + if (t.getTime() >= startOfToday.getTime()) return 'Refreshed today'; + if (days <= 1) return 'Refreshed yesterday'; + if (days < 7) return `Refreshed ${days} days ago`; + return `Refreshed ${t.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })}`; + } + const refreshed = $derived( + playlist.system_variant != null ? refreshedLabel(playlist.created_at) : '' + ); + const queryClient = useQueryClient(); let starting = $state(false); @@ -153,6 +174,9 @@ · by {playlist.owner_username} {/if} + {#if refreshed} +