Commit Graph
123 Commits
Author SHA1 Message Date
zarzet f1fb01604d perf(download): reduce progress wakeups and harden retries 2026-08-29 23:47:58 +07:00
zarzet 97fdc85513 fix(security): redact secrets from exported logs 2026-08-29 18:57:06 +07:00
zarzet 5dac33dfa4 fix(download): preserve finalization metadata and library labels 2026-08-29 00:31:25 +07:00
zarzet 2b117415ae feat: add file format library quality label 2026-08-27 16:50:46 +07:00
zarzet 47deb9f34e fix(extensions): resume metadata after verification 2026-08-27 13:08:44 +07:00
zarzet 1a7c20111a feat(library): add lyrics filtering and metadata actions 2026-08-27 01:44:06 +07:00
zarzet 747dea886c fix(runtime): bound resources and quarantine stalled extensions 2026-08-26 21:09:05 +07:00
zarzet 778f58e6e4 fix(lints): await asynchronous work in guarded blocks 2026-08-26 18:35:21 +07:00
zarzet 73051a5feb fix(player): smooth timed lyrics highlighting 2026-08-23 15:06:46 +07:00
zarzet c6437d8428 feat(library): add bit depth quality label mode 2026-08-23 11:42:47 +07:00
zarzet aec7bf7a1b fix(library): show complete quality labels 2026-08-23 11:36:15 +07:00
zarzet 7557ffbf85 fix(metadata): preserve explicit advisory across conversions 2026-08-22 23:24:56 +07:00
zarzet 89d39c4074 fix(metadata): show readable SAF file locations 2026-08-21 01:02:02 +07:00
zarzet 8b231be19a fix(download): unblock queue after verification cancel 2026-08-18 20:50:48 +07:00
zarzet a7423b7f89 feat(metadata): edit release tags and render UPC barcodes 2026-08-18 19:29:12 +07:00
zarzet b378297a99 fix(library): preserve retained files during duplicate cleanup 2026-08-17 21:03:02 +07:00
zarzet 5374883875 fix(download): normalize MP4 audio extension by codec (#512) 2026-08-11 19:07:00 +07:00
Abelardo Ramirez bed95efb29 fix(playlists): thread the source provider id through to recent playlists
Fixes #368.

Reopening a Spotify playlist from "recent access" showed no tracks,
while the first view (right after pasting the URL) worked fine.

Root cause traced across two layers:

1. Go: ExtURLHandleResult (the parsed shape of an extension's handleUrl()
   return value) never captured a top-level `id` for the handled
   resource. Track/album/artist results carry their own id inside their
   nested metadata, but a plain playlist result has no such object, so
   its id was silently dropped everywhere from the goja parser through
   to the JSON the Dart side receives.

2. Dart: TrackState had nowhere to put that id even if it existed
   (only playlistName), so recording a "recent" playlist access stored
   the playlist's *name* as if it were its id. Reopening it later fed
   that name into PlaylistScreen's provider-guessing logic
   (legacyProviderIdFromResourceId, which only recognizes legacy
   "provider:id" prefixes), which naturally failed and fell through to
   a hardcoded Deezer metadata fetch using a Spotify playlist's name as
   the resource id - guaranteed to return nothing.

Fix, matching the "generic API, not per-provider checks" architecture
in CONTRIBUTING.md:

- go_backend/extension_provider_wrapper.go: add ExtURLHandleResult.ID.
- go_backend/extension_goja_convert.go: parse it from the handler's
  return value.
- go_backend/exports_extensions.go: surface it in the JSON response.
- lib/providers/track_provider.dart: add TrackState.playlistId,
  populated from the response's `id` field for playlist results.
- lib/screens/home_tab.dart: record the real playlist id (falling back
  to the name only if a provider never supplies one) and pass both the
  id and the already-known provider id forward to PlaylistScreen.
- lib/screens/playlist_screen.dart: PlaylistScreen gains a
  metadataProviderId param that takes priority over guessing from the
  id's shape.
- lib/screens/home_tab_recent.dart: pass the recent-access entry's
  stored providerId through when reopening a playlist.
- lib/utils/provider_resource_ids.dart: extract the
  known-id-vs-guessed-id preference into a small, directly testable
  resolvePreferredMetadataProviderId helper.

Note: this fixes the common case (an extension already reported its
own id as the source provider for the URL it handled). If a provider
never supplies an id for its handleUrl() playlist result, playlistId
stays null and behavior is unchanged from before this fix - no
regression, just not a complete fix for that narrower case, since
that would require changes in extension-side JS code outside this
repo.

Tests:
- go_backend/extension_goja_convert_url_handle_test.go: the new ID
  field round-trips from a handler's return value, and stays empty
  when the handler doesn't supply one.
- test/provider_resource_ids_test.dart: resolvePreferredMetadataProviderId
  prefers a known provider id, falls back to the legacy-prefix guess,
  treats a blank known id as unknown, and returns null for the
  unprefixed-id-with-no-known-provider case from #368 itself.

Verification: go build/vet/test and flutter analyze/test all green.
2026-08-11 17:29:41 +07:00
Abelardo Ramirez 555fd77cc8 fix(ios): stop dropping the security-scoped download folder bookmark
Fixes #439, likely also #302.

Reported: on iOS, the download folder silently reverts to the default
SpotiFLAC folder after starting a download (and stays reverted after
closing and reopening the app).

_processQueue ran an iCloud/writable-path shape check against the
persisted downloadDirectory string and, if it looked like iCloud Drive
or failed the structural writable-path check, called
setDownloadDirectory() to reset it to the default folder. That call
passes no iosBookmark argument, so it also wipes the user's
security-scoped bookmark in the persisted settings - permanently, not
just for the current run.

This check ran unconditionally, even when a bookmark was already
present for that folder. A folder picked from Files can legitimately
have a persisted path that looks like iCloud Drive, or that fails this
generic structural check, while the bookmark itself still grants real
write access - the bookmark is the actual source of truth, and the
queue already has a separate, correct bookmark-resolution step right
after this one (StartAccessingIosBookmark, with its own proper
failure handling that fails the queued items with a clear message
instead of destroying the setting). The earlier path-shape check just
never deferred to it.

lib/utils/file_access.dart: extract the run/skip decision into
shouldValidateIosOutputDir(isIOS, isSafMode, outputDir,
downloadDirectoryBookmark) - skips whenever a bookmark is present -
and use it in download_queue_provider.dart in place of the inline
condition. Kept as a plain function taking isIOS as a parameter since
Platform.isIOS itself can't be exercised from a host test run.

test/file_access_ios_test.dart covers: runs for a plain app-folder
path, skips when a bookmark is present (the exact #439 case), skips
off iOS, skips in SAF mode, and skips when there's no output dir yet.

Verification: flutter analyze and flutter test (224 tests, all green,
including the 5 new ones) both clean.
2026-08-11 17:28:56 +07:00
zarzet f7439c6793 feat(library): add combined quality label #511 2026-08-11 16:56:12 +07:00
zarzet 28538395c2 fix(metadata): match by primary artist and review results #511 2026-08-11 16:20:03 +07:00
zarzet dacc5abde0 feat(logs): copy selected entries #511 2026-08-11 16:12:05 +07:00
zarzet d5bfc1e365 fix(metadata): prompt verification during autofill #511 2026-08-11 15:38:39 +07:00
zarzet 5237aed25e feat(download): add automatic lossy conversion 2026-08-11 15:19:45 +07:00
zarzet ed60cafb90 fix(search): expose go to album for sparse tracks 2026-08-01 18:01:16 +07:00
zarzet 645a324a5f feat(navigation): add go to album actions 2026-08-01 16:06:08 +07:00
zarzet dc58353c07 fix(lyrics): reject empty embedded payloads 2026-08-01 15:02:03 +07:00
zarzet d4bed9c643 feat(player): polish actions and synced lyrics 2026-07-29 19:29:59 +07:00
zarzet 1e973d5ecb style(dart): apply repository formatting 2026-07-29 14:14:42 +07:00
zarzet e7fbaf7b75 fix(download): suffix only colliding quality variants 2026-07-29 02:43:35 +07:00
zarzet 662558e3e6 fix(ui): sanitize user-facing errors 2026-07-29 01:43:04 +07:00
zarzet 7bf6f32802 fix(extensions): serialize verification callbacks 2026-07-28 14:09:06 +07:00
zarzet 80d9d87870 perf(metadata): avoid SAF copies during quality probes 2026-07-28 01:50:41 +07:00
zarzet 26570792a9 fix(library): persist lossless bitrate metadata 2026-07-28 00:50:20 +07:00
zarzet 454ef53977 feat(library): add quality label display modes 2026-07-27 17:25:39 +07:00
zarzet e4cd52f29f feat(library): highlight high-bitrate quality badges 2026-07-27 16:58:58 +07:00
zarzet 1cdf4b4547 fix(library): retain records when deletion fails 2026-07-27 14:24:39 +07:00
zarzet c42a1be76c feat(tablet): cap wide-layout margins at 80dp so content uses the screen 2026-07-27 01:39:22 +07:00
zarzet 5f5186a23e fix(reenrich): preserve release identity in batch actions 2026-07-26 12:28:00 +07:00
zarzet 1cfda41ef2 feat(audio): use VHQ settings for SoXR resampling 2026-07-25 15:58:42 +07:00
zarzet f0988070bf fix(convert): simplify output names and harden writes 2026-07-22 15:56:07 +07:00
zarzet 87376ee73b fix(metadata): resolve audio format and local artwork 2026-07-22 15:55:30 +07:00
zarzet b69f67a786 fix(download): name variants by measured quality 2026-07-16 09:20:35 +07:00
zarzet 491998e5c5 feat(convert): optionally retain original files 2026-07-16 08:27:24 +07:00
zarzet 82ba586b72 feat(convert): support same-format lossless re-encoding 2026-07-15 22:03:32 +07:00
zarzet 35128a6bfc fix(reenrich): always clean temporary artifacts 2026-07-15 21:33:13 +07:00
zarzet 11567fee7d refactor(reenrich): share the FFmpeg re-enrich apply flow
local_album and the queue batch action carried byte-identical ~110-line copies
of the FFmpeg re-enrich apply+SAF-writeback+temp-cleanup flow (differing only in
helper names). Extract one applyFfmpegReEnrichResult() so the SAF temp-file
cleanup — the class of bug fixed in fb7d101 — has a single source. The
track-detail copy stays: it drives snackbars and does not preserve metadata.
2026-07-14 18:33:18 +07:00
zarzet 1893f3faa8 feat(tablet): clamp remaining screens - settings, queue lists, wizard
- SettingsGroup centers itself at 720dp via its incoming constraint
  (not screen width, so groups inside clamped sheets are never
  over-inset; unbounded-width contexts guarded); priority scaffold,
  bespoke hero cards, and donate page get the same inset - all 18+
  settings pages clamp from shared widgets
- queue list-mode, local/downloaded album track lists, favorite
  artists, home recent-access, and extension detail sections adopt
  wideListInset like the screens fixed earlier
- track metadata content clamped at 720dp, which also tames the
  width-derived spectrogram height
- announcement dialog capped at 480dp wide; setup wizard and
  tutorial content at 560dp with a 400dp action button
2026-07-14 09:10:16 +07:00
zarzet f5b5af6eea feat(tablet): navigation rail, landscape now-playing, clamped track lists
- shell switches to a NavigationRail at >=600dp (same destinations,
  badges included); the bottom bar and its blur stay phone-only while
  the mini player remains anchored bottom in both modes
- now-playing gains a two-pane landscape layout (artwork left,
  metadata/controls right) built from the same widget pieces
- track lists on album/playlist/library-folder screens and search
  result sections center at 720dp via wideListInset instead of
  stretching across the full tablet width; explore carousels stay
  full-bleed intentionally
2026-07-14 09:10:00 +07:00
zarzet de32b8b5f6 refactor(queue): extract shared payload, history, and polling helpers
- _buildDownloadRequestPayload/_historyItemFromResult shared by the
  inline and native-worker paths; _buildOutputDir now derives from
  _buildRelativeOutputDir
- shouldAbortWork extended (deleteFileOnAbort, null-item check) and
  used at all ten guard sites; _purgeAlbumRgEntry replaces six copies
- ProgressStreamPoller shared by download queue and local library;
  openVerificationAndAwaitGrant shared by queue and track provider
2026-07-12 21:09:31 +07:00