Add a fourth Playlists chip/page to the Library filter pager, rendered
as an album-style grid of playlist cells (search filters by name, page
provider short-circuits since playlists come from libraryCollections).
Add a defaultLibraryView setting (last used / all / albums / singles /
playlists) in Library settings, applied on startup and whenever the
shell switches back to the Library tab.
The download-time ISRC index now also parses mp3, m4a, ogg, and opus
tags through the existing native readers instead of skipping everything
but .flac. Library settings gain a Review duplicates sheet that groups
history and local-library rows sharing an ISRC via SQL over the
attached databases (no filesystem walk, SAF-safe), shows quality
badges, and offers keep-best and per-copy delete with confirmation.
Reads REPLAYGAIN_TRACK_GAIN (album gain fallback) from FLAC/MP3/M4A tags
and converts Opus R128_TRACK_GAIN/R128_ALBUM_GAIN (Q7.8, -23 LUFS) to the
same ReplayGain dB representation on the Go side, then attenuates the
internal player's volume per track. Off by default; toggle lives under
Library > Playback. Opus header output gain is left to the decoder, which
already applies it.
Closes#465
- 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
Every settings-style page carried the same 40-line SliverAppBar block;
titles, custom leadings (discard-confirm back handlers), and action
buttons are preserved through the widget's slots.
The download/library folder picker on iOS previously took the bare path
string returned by file_picker and rebuilt a URL from it to create a
security-scoped bookmark. By then the picker's access grant is gone, so
bookmark creation either failed (folder selection silently rejected) or
produced a bookmark that could not be re-opened, after which the first
download run overwrote the user's chosen folder back to app Documents.
- Add a native pickIosDirectory method channel that presents
UIDocumentPickerViewController itself and creates the bookmark inside
the delegate callback, while the security-scoped grant is active
- Use it for the download directory (settings + first-run setup) and
the local library folder pickers
- Stop overwriting the saved download directory when the bookmark fails
at queue start; fail queued items with a clear message instead
- Skip the launch-time iOS path normalizer when a bookmark exists, so
it no longer rewrites external folder paths and drops their bookmark
Fixes#454
Move hardcoded strings in library settings, announcement link errors,
and unknown track title/artist fallbacks to AppLocalizations. Sync
locale-dependent fallback strings from MainShell.
The HIGH-quality lossy format picker can now produce an AAC/M4A 320 kbps output alongside MP3 and Opus. FFmpegService.convertM4aToLossy/convertAudioFormat, the Dart queue pipeline, the Kotlin finalizer, and the library database format helper all route .m4a through a unified aac codec path and tag the resulting file with the M4A metadata writer. The Lossy Format setting gains a new option, and the track metadata convert dialog lists AAC next to the other targets.
Apple Music lyrics gain a 'eLRC word sync' switch (default off). When disabled the pax-to-LRC formatter strips inline word timestamps, producing line-synced LRC that is safer for players that choke on eLRC; enabling it restores the previous word-by-word behaviour. The change propagates through SetLyricsFetchOptions and invalidates the global lyrics cache on toggle.
Broad l10n migration: roughly 400 previously hardcoded English strings across queue, settings, track metadata, repo, audio analysis, setup and extension screens now live in the ARB catalog, with matching plural/placeholder forms. No behaviour change beyond localisation. Existing and new unit tests (lyrics eLRC toggle and Dart settings round-trip) pass.
Replace the full in-memory List<LocalLibraryItem> in LocalLibraryState
with a lightweight lookup index (ISRCs, matchKeys, filePathById) and
database-backed FutureProvider.family pagination providers.
Database changes:
- Add library schema v7 with normalized lookup columns (track_name_norm,
artist_name_norm, album_name_norm, album_artist_norm, match_key,
album_key) and corresponding indexes
- Backfill normalized columns on migration from v6
- Add getPage, getPageCount, getAlbumPage, getAlbumCount, getLookupIndex,
getCoverPaths, getByFilePath, findFirstByTrackAndArtist DB methods
Provider changes:
- LocalLibraryState no longer holds items list; uses totalCount and
loadedIndexVersion for change tracking
- Deprecate synchronous getByIsrc/findByTrackAndArtist (return null);
add async findExistingAsync, getByIsrcAsync, getById on notifier
- Add localLibraryPageProvider, localLibraryAlbumPageProvider,
localLibraryAllItemsProvider family providers for paginated access
- Add localLibraryCoverProvider and localLibraryFirstCoverProvider
for async cover path resolution from DB
Screen migrations:
- album/artist/playlist screens use findExistingAsync for playback
- library_tracks_folder_screen uses async cover providers and
existsInLibrary for local library indicator
- queue_tab watches localLibraryAllItemsProvider instead of state.items
- library_settings_page uses state.totalCount
- playback_provider uses findExistingAsync
Track metadata screen:
- Replace pushReplacement navigation with in-place state swap using
AnimatedSwitcher for smooth cross-fade transitions on track swipe
- Add metadataLoadGeneration counter to prevent stale async callbacks
- Reset all transient state (lyrics, cover, file check) on track change
Resolve API (api.zarz.moe):
- Refactor songlink.go: Spotify URLs use resolve API, non-Spotify uses SongLink API
- Add SongLink fallback when resolve API fails for Spotify (two-layer resilience)
- Remove dead code: page parser, XOR-obfuscated keys, legacy helpers
Multi-artist tag fix (#288):
- Add RewriteSplitArtistTags() in Go to rewrite ARTIST/ALBUMARTIST as split Vorbis comments
- Wire method channel handler in Android (MainActivity.kt) and iOS (AppDelegate.swift)
- Add PlatformBridge.rewriteSplitArtistTags() in Dart
- Call native FLAC rewriter after FFmpeg embed when split_vorbis mode is active
- Extract deezerTrackArtistDisplay() helper to use Contributors in album/playlist tracks
Code cleanup:
- Remove unused imports, dead code, and redundant comments across Go and Dart
- Fix build: remove stale getQobuzDebugKey() reference in deezer_download.go
- Add artist_tag_mode setting (joined / split_vorbis) for FLAC/Opus multi-artist tags
- Split 'Artist A, Artist B' into separate ARTIST= Vorbis comments when split mode is enabled
- Join repeated ARTIST/ALBUMARTIST Vorbis comments when reading metadata
- Propagate artistTagMode through download pipeline, re-enrich, and metadata editor
- Improve library scan progress: separate polling intervals, finalizing state, indeterminate progress
- Add initial progress snapshot on library scan stream connect
- Use req.ArtistName consistently for Qobuz downloads instead of track.Performer.Name
- Add l10n keys for artist tag mode, library files unit, and scan finalizing status
Enable strict-casts, strict-inference, and strict-raw-types in
analysis_options.yaml. Add custom_lint with riverpod_lint. Fix all
resulting type warnings with explicit type parameters and safer casts.
Also improves APK update checker to detect device ABIs for correct
variant selection and fixes Deezer artist name parsing edge case.
- Add SearchAll() for Tidal and Qobuz in Go backend (tracks, artists, albums)
- Add searchTidalAll/searchQobuzAll platform routing for Android and iOS
- Add Tidal/Qobuz options to search provider dropdown in home tab
- Show (Recommended) label and auto-select service in download picker
Add a new 'Auto Scan' setting under Local Library with four modes:
off, every app open (10min cooldown), daily, and weekly. The app
uses WidgetsBindingObserver to trigger incremental scans on launch
and when resuming from background, respecting the configured
cooldown based on the last scan timestamp.
- Add responsive scaling across album, artist, playlist, downloaded album, local album, queue, setup, and tutorial screens to prevent overflow on smaller devices
- Add new Storage & Cache management page (Settings > Storage & Cache) with per-category clear and cleanup actions
- Extract normalizedHeaderTopPadding utility for consistent app bar padding
- Improve home search Recent Access behavior: show when focused with empty input, hide stale results during active recent mode
- Add excluded-downloaded-count tracking to local library scan stats
- Add recentEmpty and recentShowAllDownloads l10n keys (EN + ID)
- Add full cache management l10n keys (EN + ID)
- Fix about_page indentation and formatting consistency
- Fix appearance_settings_page formatting
- Fix downloaded_album_screen and local_album_screen formatting and responsive sizing
- SAF Storage Access Framework for Android 10+ downloads
- Redesigned Setup/Tutorial screens with Material 3 Expressive
- Library scan hero card now shows real-time scanned count
- Library folder picker uses SAF (no MANAGE_EXTERNAL_STORAGE needed)
- SAF migration prompt for users updating from pre-SAF versions
- Home feed caching, donate page, per-app language support
- Merged 3.6.0-beta.1 changelog entries into 3.5.0
- Add SAF tree picker and persistent URI storage in settings
- Implement SAF file operations: exists, delete, stat, copy, create
- Update download pipeline to support SAF content URIs
- Add fallback to app-private storage when SAF write fails
- Support SAF in library scan with DocumentFile traversal
- Add history item repair for missing SAF URIs
- Create file_access.dart utilities for abstracted file operations
- Update Tidal/Qobuz/Amazon/Extensions for SAF-aware output
- Add runPostProcessingV2 API for SAF content URIs
- Update screens (album, artist, queue, track) for SAF awareness
Resolves Android 10+ scoped storage permission issues
This feature was deemed unnecessary and was adding complexity to the app.
Removed:
- lib/services/cloud_upload_service.dart
- lib/providers/upload_queue_provider.dart
- lib/screens/settings/cloud_settings_page.dart
- lib/screens/settings/widgets/ (cloud status hero card)
- All cloud* settings from AppSettings model
- All cloud-related localization keys (~70 keys)
- Cloud settings from settings_provider.dart
- Cloud menu item from settings_tab.dart
- Cloud upload trigger from download_queue_provider.dart
Dependencies removed:
- webdav_client: ^1.2.2
- dartssh2: ^2.13.0
CHANGELOG updated to remove all cloud save references.
- Rename bottom navigation 'History' to 'Library'
- Add Local Library section showing scanned tracks below downloaded tracks
- Add source badge to each item (Downloaded/Local) for clear identification
- Add new localization strings for Library tab and source badges
- Local library items can be played directly from the library tab
- Add Go backend library scanner for FLAC, M4A, MP3, Opus, OGG files
- Read metadata from file tags (ISRC, track name, artist, album, bit depth, sample rate)
- Fallback to filename parsing when tags unavailable
- Add SQLite database for O(1) duplicate lookups
- Show 'In Library' badge on search results for existing tracks
- Match by ISRC (exact) or track name + artist (fuzzy)
- Add Library Settings page with scan, cleanup, and clear actions
- Add 30+ localization strings for library feature