v3.7.0: roll back from v4, remove internal player — v3 is already complete

Version rolled back from v4.x to v3.7.0. After extensive work on v4's
internal streaming engine, smart queue, DASH pipeline, and media controls,
we realized v3 was already feature-complete. Adding more big features
only made maintenance increasingly difficult and the developer's life
miserable. Stripped back to what works: external player only, cleaner
codebase, sustainable long-term.

- Remove just_audio, audio_service, audio_session and entire internal
  playback engine (smart queue, notification, shuffle/repeat, prefetch)
- Remove PlaybackItem model, MiniPlayerBar widget, notification drawables
- Remove playerMode setting (external-only now)
- Migrate MainActivity from AudioServiceFragmentActivity to
  FlutterFragmentActivity
- Migrate Qobuz to MusicDL API
- Update changelog with v3.7.0 rollback explanation
This commit is contained in:
zarzet
2026-03-02 22:18:53 +07:00
parent 4747119a7f
commit 98abaf6635
56 changed files with 687 additions and 10106 deletions
+29 -91
View File
@@ -1,105 +1,43 @@
# Changelog
## [4.0.1] - 2026-02-26
## [3.7.0] - 2026-03-04
### Added
- **Clickable Metadata Navigation**: Added reusable `ClickableArtistName` and `ClickableAlbumName`
- **Love Action in Media Notification**: Added custom notification action (`toggle_love`) with new Android favorite/favorite-border status icons
Hey everyone, thank you so much for sticking with SpotiFLAC Mobile.
Starting from this release, we're rolling the version back from **v4.x to v3.x**.
### Removed
- **Internal Audio Player** — Removed `just_audio`, `audio_service`, and `audio_session` dependencies entirely. The internal playback engine (smart queue, media notification, shuffle/repeat, lyrics sync, prefetch, playback state persistence) has been completely removed. Playback now delegates to the system's external player.
- **PlaybackItem Model** — No longer needed without internal playback.
- **MiniPlayerBar Widget** — Removed the in-app mini player UI.
- **Media Notification Controls** — Removed notification drawables (`ic_stat_favorite`, `ic_stat_favorite_border`) and the `keep.xml` resource file.
- **Player Mode Setting** — The `playerMode` setting has been removed since external player is now the only mode.
- **Online Playback Feature** — Online streaming mode, DASH pipeline, and related components introduced in v4.0.0 are gone from the main branch.
### Changed
- **Track Metadata Model Expansion**: `Track` now carries `artistId` and `albumId`, propagated across search, queue, playback, CSV import, and extension mapping flows
- **Full-Screen Player UX**: Top bar now supports swipe-down dismiss; artist/album text is now tappable; and in-player love toggle is available next to track metadata
- **Playlist Picker Flow Refactor**: Reworked playlist picker sheet into stateful multi-select flow with explicit Done action and improved create-playlist handling
- **CSV Import Interaction Flow**: Added single-flight import guard, more reliable progress dialog lifecycle, and safer local navigator usage
- **Amazon API**: Amazon metadata fetch `amzn.afkarxyz.fun`
- **Qobuz URL Resolution Strategy**: Removed legacy/Jumo fallback path; now uses standard API pool (deeb)
- **Update Checker Asset Targeting**: Update selection now prioritizes arm64/universal assets only
- **Donate Page Supporters**: Updated highlighted donor/supporter list entries
- **MainActivity** now extends `FlutterFragmentActivity` directly (previously `AudioServiceFragmentActivity`).
- **PlaybackController** simplified from ~1200 lines to ~87 lines — now only resolves local file paths and opens them via external player.
- **ProGuard rules** cleaned up — removed audio_service/just_audio/audio_session rules.
- **Qobuz** migrated to MusicDL API (Thanks @Ruubiiiii for Hosting the API).
### Fixed
### Note
There are three main reasons behind this decision:
- **FLAC External Lyrics Output**: External `.lrc` writing now works consistently for lyrics mode `external`/`both`, with SAF conversion paths avoiding duplicate writes
- **Loved-State Notification Sync**: Playback notification controls now refresh correctly when loved state changes
- **Queue Selection Touch Handling**: Selection overlays/check indicators no longer block tap gestures in queue and playlist selection modes
- **Vorbis-to-ID3 Tag Mapping Robustness**: FFmpeg metadata conversion now normalizes keys and handles aliases like `TRCK` and `TPOS`
- **Nested Dialog Navigation Safety**: Adjusted dialog navigator scope in CSV import and track-delete flows to prevent navigator mismatch issues
- **Artist/Album Routing Reliability**: Track metadata routing now reuses resolved artist/album IDs across album/artist/home/search/queue/player surfaces
- **Release Workflow Go Toolchain**: Pinned CI release workflow Go version to `1.25.7` for consistent build behavior
1. **Respecting the API providers** — After giving it some thought, we realized that the streaming feature was indirectly hurting the API providers who have been generous enough to make their services available. They already offer streaming directly on their own websites, and it only feels right to direct streaming usage back to their platforms.
2. **Long-term sustainability** — We want SpotiFLAC to be around for as long as possible. Keeping certain features in the app could attract unwanted attention and put the project's continued existence at risk. Removing them is a proactive step to keep things running smoothly for everyone.
**Still want online playback? Check out these services:**
- [DabMusic](https://dabmusic.xyz)
- [SquidWTF](https://tidal.squid.wtf)
Thank you for your understanding and continued support. This decision was made to ensure the long-term sustainability of the app and to respect the ecosystem that has been supporting SpotiFLAC all along. You guys are the best, and we truly appreciate each and every one of you!
---
## [4.0.0] - 2026-02-22
> **Major update warning:** This release introduces a large streaming-focused refactor and broad cross-app behavior changes.
>
> **Diff scope (`cdc583678558223ecbb552176b53727d303ae218..HEAD`):** 121 files changed, 28,354 insertions(+), 4,598 deletions(-).
### Added
- **End-to-End Streaming Mode**: Full streaming playback flow with full-screen player, synced lyrics, media controls, and queue-aware tap behavior across album, artist, playlist, home, and search screens
- **Smart Queue System**: ML-based queue auto-curation with related artist discovery, plus a dedicated playback queue view
- **DASH Streaming Pipeline**: Native DASH manifest playback support with local proxy integration and FFmpeg tunnel fallback for unsupported paths
- **Playback State Persistence**: Player state and queue continuity restored across app restarts
- **Adaptive Playback Engine**: EventChannel-driven playback/progress updates (replacing polling) and adaptive prefetch behavior
- **Queue Reliability Controls**: New auto-skip unavailable tracks option during queue playback
- **Player Quick Action**: New download button in full-screen player top bar
- **Metadata Control**: New global master switch for embed metadata behavior
- **Setup Flow Update**: Initial setup now prioritizes mode selection instead of Spotify API setup
- **Library Workflow Expansion**: Playlist-first library redesign, drag-and-drop categorization, folder multi-select, and batch playlist picker flows
- **SongLink Region Setting**: Region selection support for metadata/linking behavior
- **Track Interaction UX**: Long-press context menus for track actions across major collection screens
- **Batch Tools**: Multi-select share, batch convert, and batch re-enrich improvements for downloaded/local/queue workflows
### Changed
- **Global Mode-Driven Actions**: Interaction mode now drives behavior app-wide (download-oriented vs streaming-oriented actions)
- **UI Redesign and Responsiveness**: Full-screen cover/parallax rollout and responsive fixes for filter sheets and full-screen player in small screens/landscape
- **Performance Optimizations**: Granular Riverpod consumers, selective provider watching, computation caching, debounced extension storage writes, and lifecycle cleanups
- **Lyrics Loading Strategy**: Lyrics are now lazy-loaded only when the lyrics view is visible
- **Persistence Backend Refactor**: Core persistence paths migrated to SQLite-backed stores for app state and library collections
- **Shared Code Refactor**: Duplicated logic extracted into shared Dart/Go utilities for cleaner boundaries and maintainability
### Fixed
- **iOS Build Compatibility**: Resolved `RepeatMode` naming collision with Flutter SDK symbols
- **Playback Completion Handling**: Fixed track completion restart issues and queue-end completion synchronization
- **Streaming Stability**: Added guards for playback race conditions during queue/stream state transitions
- **Provider I/O Safety**: Improved Android/Go file descriptor handling for SAF-based outputs
- **Metadata Matching Robustness**: Improved title matching with strict emoji handling and name+artist fallback lookup behavior
- **Navigation Behavior**: Back button now exits app correctly instead of unexpectedly returning to home
---
## [4.0.0] - 2026-02-22
### Added
- **Interaction Mode Setting**: New "Interaction Mode" toggle in Options settings to switch between Downloader Mode (tap to queue downloads) and Streaming Mode (tap to play instantly)
- Affects album, artist discography, playlist, home explore, and search screens
- All action buttons (Download All, Download Selected, Download Discography) dynamically switch to Play equivalents when in Streaming Mode
- **Streaming Playback Integration**: Tapping tracks in Streaming Mode plays them via `playTrackStreamAndSetQueue` with full queue support across all collection screens (album, artist, playlist, home, search)
- **Long-Press Track Context Menus**: Added `onLongPress` handler on track items across album, artist, home, playlist, and search screens to open the track options bottom sheet via `TrackCollectionQuickActions.showTrackOptionsSheet`
- **USDT TRC20 Crypto Donation**: Added USDT (TRC20) wallet address to Donate page with tap-to-copy-to-clipboard functionality and snackbar confirmation
- **Localization**: Added interaction mode and streaming playback strings across all 14 supported locales (`optionsInteractionMode`, `modeDownloader`, `modeDownloaderSubtitle`, `modeStreaming`, `modeStreamingSubtitle`, `playAllCount`, `discographyPlay`, `discographyPlayAll`, `discographyPlaySelected`)
- **Indonesian (ID) Localization**: Full translations for all new streaming mode strings
### Changed
- **Mini Player Bar Layout**: Media section (cover art / lyrics) now uses fixed-height `SizedBox` (50% screen height, clamped 300560px) instead of `Expanded` for more consistent layout
- **Lyrics Font Size Increase**: Synced lyrics current line 22→24px, non-current 18→19px; word-by-word highlight 22→24px; unsynced 18→19px
- **Playback Media Controls**: Removed stop button from notification media controls for cleaner transport bar
- **Playback Queue Exhaustion**: Player now properly syncs `ProcessingState.completed` state when queue is exhausted instead of silently stopping
- **`TrackCollectionQuickActions.showTrackOptionsSheet` Made Static**: Extracted to a public static method so all screens can invoke it directly for long-press handling
- **Bottom Spacing in Mini Player**: Reduced from 16px to 4px for tighter layout
### Fixed
- **Playback State Not Updating on Queue End**: Fixed playback notification staying in "playing" state when all tracks in queue have finished
---
## [3.7.0] - 2026-02-19
## [3.6.0] - 2026-02-19
### Added
-10
View File
@@ -80,16 +80,6 @@
-keep class io.flutter.plugins.pathprovider.** { *; }
-keep class dev.flutter.pigeon.** { *; }
# Audio Service (media playback notification) - CRITICAL for release builds
-keep class com.ryanheise.audioservice.** { *; }
-keep class com.ryanheise.audio_session.** { *; }
-keep class com.ryanheise.just_audio.** { *; }
# AndroidX Media / MediaSession (used by audio_service)
-keep class androidx.media.** { *; }
-keep class android.support.v4.media.** { *; }
-dontwarn android.support.v4.media.**
# Local Notifications
-keep class com.dexterous.** { *; }
-keep class com.dexterous.flutterlocalnotifications.** { *; }
@@ -7,7 +7,7 @@ import android.os.Build
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.documentfile.provider.DocumentFile
import com.ryanheise.audioservice.AudioServiceFragmentActivity
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode
import io.flutter.embedding.android.FlutterFragment
import io.flutter.embedding.android.RenderMode
@@ -32,7 +32,7 @@ import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.Locale
class MainActivity: AudioServiceFragmentActivity() {
class MainActivity: FlutterFragmentActivity() {
private val CHANNEL = "com.zarz.spotiflac/backend"
private val DOWNLOAD_PROGRESS_STREAM_CHANNEL =
"com.zarz.spotiflac/download_progress_stream"
@@ -50,6 +50,7 @@ class MainActivity: AudioServiceFragmentActivity() {
private var libraryScanProgressStreamJob: Job? = null
private var libraryScanProgressEventSink: EventChannel.EventSink? = null
private var lastLibraryScanProgressPayload: String? = null
private var flutterBackCallback: OnBackPressedCallback? = null
@Volatile private var safScanCancel = false
@Volatile private var safScanActive = false
private val safTreeLauncher = registerForActivityResult(
@@ -1370,11 +1371,12 @@ class MainActivity: AudioServiceFragmentActivity() {
// which disables Flutter's own OnBackPressedCallback and causes the
// system default (finish activity) to run. This callback guarantees
// popRoute is always forwarded to Flutter, where PopScope handles it.
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
flutterBackCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
flutterEngine.navigationChannel.popRoute()
}
})
}
onBackPressedDispatcher.addCallback(this, flutterBackCallback!!)
val messenger = flutterEngine.dartExecutor.binaryMessenger
@@ -1416,6 +1418,15 @@ class MainActivity: AudioServiceFragmentActivity() {
scope.launch {
try {
when (call.method) {
"exitApp" -> {
flutterBackCallback?.isEnabled = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
finishAndRemoveTask()
} else {
finish()
}
result.success(null)
}
"parseSpotifyUrl" -> {
val url = call.argument<String>("url") ?: ""
val response = withContext(Dispatchers.IO) {
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/>
</vector>
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M16.5,3C14.76,3 13.09,3.81 12,5.09C10.91,3.81 9.24,3 7.5,3C4.42,3 2,5.42 2,8.5C2,12.28 5.4,15.36 10.55,20.03L12,21.35L13.45,20.03C18.6,15.36 22,12.28 22,8.5C22,5.42 19.58,3 16.5,3ZM12.1,18.55L12,18.65L11.9,18.55C7.14,14.24 4,11.39 4,8.5C4,6.5 5.5,5 7.5,5C9.04,5 10.54,5.99 11.07,7.36H12.94C13.46,5.99 14.96,5 16.5,5C18.5,5 20,6.5 20,8.5C20,11.39 16.86,14.24 12.1,18.55Z"/>
</vector>
-3
View File
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/ic_stat_favorite,@drawable/ic_stat_favorite_border" />
+265 -71
View File
@@ -2,6 +2,7 @@ package gobackend
import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
@@ -30,8 +31,21 @@ var (
const (
qobuzTrackGetBaseURL = "https://www.qobuz.com/api.json/0.2/track/get?track_id="
qobuzTrackSearchBaseURL = "https://www.qobuz.com/api.json/0.2/track/search?query="
qobuzTrackPlayBaseURL = "https://play.qobuz.com/track/"
qobuzDownloadAPIURL = "https://www.musicdl.me/api/qobuz/download"
qobuzDabMusicAPIURL = "https://dabmusic.xyz/api/stream?trackId="
qobuzDeebAPIURL = "https://dab.yeet.su/api/stream?trackId="
qobuzDebugKeyXORMask = byte(0x5A)
)
var qobuzDebugKeyObfuscated = []byte{
0x69, 0x3b, 0x38, 0x3e, 0x36, 0x37, 0x35, 0x2f, 0x36, 0x3b,
0x33, 0x29, 0x2e, 0x32, 0x3f, 0x3d, 0x35, 0x3b, 0x2e, 0x3b,
0x34, 0x3e, 0x34, 0x35, 0x35, 0x34, 0x3f, 0x39, 0x35, 0x37,
0x3f, 0x29, 0x3f, 0x2c, 0x3f, 0x34, 0x39, 0x36, 0x35, 0x29,
0x3f,
}
type QobuzTrack struct {
ID int64 `json:"id"`
Title string `json:"title"`
@@ -368,45 +382,181 @@ func (q *QobuzDownloader) GetTrackByID(trackID int64) (*QobuzTrack, error) {
func (q *QobuzDownloader) GetAvailableAPIs() []string {
return []string{
"https://dab.yeet.su/api/stream?trackId=",
"https://dabmusic.xyz/api/stream?trackId=",
qobuzDownloadAPIURL,
}
}
func extractQobuzDownloadURLFromBody(body []byte) (string, error) {
type qobuzAPIProvider struct {
Name string
URL string
Kind string
}
const (
qobuzAPIKindMusicDL = "musicdl"
qobuzAPIKindStandard = "standard"
)
func (q *QobuzDownloader) GetAvailableProviders() []qobuzAPIProvider {
return []qobuzAPIProvider{
{Name: "musicdl", URL: qobuzDownloadAPIURL, Kind: qobuzAPIKindMusicDL},
{Name: "dabmusic", URL: qobuzDabMusicAPIURL, Kind: qobuzAPIKindStandard},
// "deeb" is mapped from the legacy reference fallback endpoint.
{Name: "deeb", URL: qobuzDeebAPIURL, Kind: qobuzAPIKindStandard},
}
}
type qobuzDownloadInfo struct {
DownloadURL string
BitDepth int
SampleRate int
}
func extractQobuzDownloadInfoFromBody(body []byte) (qobuzDownloadInfo, error) {
var raw map[string]any
if err := json.Unmarshal(body, &raw); err != nil {
return "", fmt.Errorf("invalid JSON: %v", err)
return qobuzDownloadInfo{}, fmt.Errorf("invalid JSON: %v", err)
}
if errMsg, ok := raw["error"].(string); ok && strings.TrimSpace(errMsg) != "" {
return "", fmt.Errorf("%s", errMsg)
return qobuzDownloadInfo{}, fmt.Errorf("%s", errMsg)
}
if detail, ok := raw["detail"].(string); ok && strings.TrimSpace(detail) != "" {
return qobuzDownloadInfo{}, fmt.Errorf("%s", detail)
}
if success, ok := raw["success"].(bool); ok && !success {
if msg, ok := raw["message"].(string); ok && strings.TrimSpace(msg) != "" {
return "", fmt.Errorf("%s", msg)
return qobuzDownloadInfo{}, fmt.Errorf("%s", msg)
}
return "", fmt.Errorf("api returned success=false")
return qobuzDownloadInfo{}, fmt.Errorf("api returned success=false")
}
info := qobuzDownloadInfo{
BitDepth: qobuzParseBitDepth(raw["bit_depth"]),
SampleRate: qobuzParseSampleRate(raw["sampling_rate"]),
}
if urlVal, ok := raw["download_url"].(string); ok && strings.TrimSpace(urlVal) != "" {
info.DownloadURL = strings.TrimSpace(urlVal)
return info, nil
}
if urlVal, ok := raw["url"].(string); ok && strings.TrimSpace(urlVal) != "" {
return strings.TrimSpace(urlVal), nil
info.DownloadURL = strings.TrimSpace(urlVal)
return info, nil
}
if linkVal, ok := raw["link"].(string); ok && strings.TrimSpace(linkVal) != "" {
return strings.TrimSpace(linkVal), nil
info.DownloadURL = strings.TrimSpace(linkVal)
return info, nil
}
if data, ok := raw["data"].(map[string]any); ok {
if info.BitDepth == 0 {
info.BitDepth = qobuzParseBitDepth(data["bit_depth"])
}
if info.SampleRate == 0 {
info.SampleRate = qobuzParseSampleRate(data["sampling_rate"])
}
if urlVal, ok := data["download_url"].(string); ok && strings.TrimSpace(urlVal) != "" {
info.DownloadURL = strings.TrimSpace(urlVal)
return info, nil
}
if urlVal, ok := data["url"].(string); ok && strings.TrimSpace(urlVal) != "" {
return strings.TrimSpace(urlVal), nil
info.DownloadURL = strings.TrimSpace(urlVal)
return info, nil
}
if linkVal, ok := data["link"].(string); ok && strings.TrimSpace(linkVal) != "" {
return strings.TrimSpace(linkVal), nil
info.DownloadURL = strings.TrimSpace(linkVal)
return info, nil
}
}
return "", fmt.Errorf("no download URL in response")
return qobuzDownloadInfo{}, fmt.Errorf("no download URL in response")
}
func extractQobuzDownloadURLFromBody(body []byte) (string, error) {
info, err := extractQobuzDownloadInfoFromBody(body)
if err != nil {
return "", err
}
return info.DownloadURL, nil
}
func qobuzParseBitDepth(value any) int {
switch v := value.(type) {
case float64:
return int(v)
case int:
return v
case int64:
return int(v)
case json.Number:
n, _ := v.Int64()
return int(n)
default:
return 0
}
}
func qobuzParseSampleRate(value any) int {
switch v := value.(type) {
case float64:
if v > 0 && v < 1000 {
return int(v * 1000)
}
return int(v)
case int:
if v > 0 && v < 1000 {
return v * 1000
}
return v
case int64:
if v > 0 && v < 1000 {
return int(v * 1000)
}
return int(v)
case json.Number:
if n, err := v.Float64(); err == nil {
if n > 0 && n < 1000 {
return int(n * 1000)
}
return int(n)
}
return 0
default:
return 0
}
}
func normalizeQobuzQualityCode(quality string) string {
switch strings.ToLower(strings.TrimSpace(quality)) {
case "", "5", "6", "cd", "lossless":
return "6"
case "7", "hi-res":
return "7"
case "27", "hi-res-max":
return "27"
default:
return "6"
}
}
func mapQobuzQualityCodeToAPI(qualityCode string) string {
switch normalizeQobuzQualityCode(qualityCode) {
case "27":
return "hi-res-max"
case "7":
return "hi-res"
default:
return "cd"
}
}
func getQobuzDebugKey() string {
decoded := make([]byte, len(qobuzDebugKeyObfuscated))
for i, b := range qobuzDebugKeyObfuscated {
decoded[i] = b ^ qobuzDebugKeyXORMask
}
return string(decoded)
}
func (q *QobuzDownloader) SearchTrackByISRC(isrc string) (*QobuzTrack, error) {
@@ -688,10 +838,10 @@ func (q *QobuzDownloader) SearchTrackByMetadataWithDuration(trackName, artistNam
}
type qobuzAPIResult struct {
apiURL string
downloadURL string
err error
duration time.Duration
provider qobuzAPIProvider
info qobuzDownloadInfo
err error
duration time.Duration
}
// Qobuz API timeout configuration
@@ -711,35 +861,72 @@ func getQobuzAPITimeout() time.Duration {
}
// fetchQobuzURLWithRetry fetches download URL from a single Qobuz API with retry logic
func fetchQobuzURLWithRetry(api string, trackID int64, quality string, timeout time.Duration) (string, error) {
return fetchQobuzURLSingleAttempt(api, trackID, quality, timeout, "")
func fetchQobuzURLWithRetry(provider qobuzAPIProvider, trackID int64, quality string, timeout time.Duration) (qobuzDownloadInfo, error) {
return fetchQobuzURLSingleAttempt(provider, trackID, quality, timeout, "")
}
// fetchQobuzURLSingleAttempt fetches download URL with retry logic for a single API+country combination
func fetchQobuzURLSingleAttempt(api string, trackID int64, quality string, timeout time.Duration, country string) (string, error) {
func fetchQobuzURLSingleAttempt(provider qobuzAPIProvider, trackID int64, quality string, timeout time.Duration, country string) (qobuzDownloadInfo, error) {
var lastErr error
retryDelay := qobuzRetryDelay
var payloadBytes []byte
if provider.Kind == qobuzAPIKindMusicDL {
requestQuality := mapQobuzQualityCodeToAPI(quality)
payload := map[string]any{
"quality": requestQuality,
"upload_to_r2": false,
"url": fmt.Sprintf("%s%d", qobuzTrackPlayBaseURL, trackID),
}
var err error
payloadBytes, err = json.Marshal(payload)
if err != nil {
return qobuzDownloadInfo{}, fmt.Errorf("failed to encode qobuz request: %w", err)
}
}
for attempt := 0; attempt <= qobuzMaxRetries; attempt++ {
if attempt > 0 {
GoLog("[Qobuz] Retry %d/%d for %s after %v\n", attempt, qobuzMaxRetries, api, retryDelay)
GoLog("[Qobuz] Retry %d/%d for %s after %v\n", attempt, qobuzMaxRetries, provider.Name, retryDelay)
time.Sleep(retryDelay)
retryDelay *= 2 // Exponential backoff
}
client := NewHTTPClientWithTimeout(timeout)
reqURL := fmt.Sprintf("%s%d&quality=%s", api, trackID, quality)
reqURL := provider.URL
if country != "" {
reqURL += "&country=" + country
reqURL += "?country=" + url.QueryEscape(country)
}
req, err := http.NewRequest("GET", reqURL, nil)
var (
req *http.Request
err error
)
if provider.Kind == qobuzAPIKindStandard {
separator := "&"
if !strings.Contains(reqURL, "?") {
separator = "?"
}
reqURL = fmt.Sprintf(
"%s%d%squality=%s",
reqURL,
trackID,
separator,
url.QueryEscape(normalizeQobuzQualityCode(quality)),
)
req, err = http.NewRequest("GET", reqURL, nil)
} else {
req, err = http.NewRequest("POST", reqURL, bytes.NewReader(payloadBytes))
}
if err != nil {
lastErr = err
continue
}
if provider.Kind == qobuzAPIKindMusicDL {
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Debug-Key", getQobuzDebugKey())
}
resp, err := client.Do(req)
resp, err := DoRequestWithUserAgent(client, req)
if err != nil {
lastErr = err
// Check for retryable errors (timeout, connection reset)
@@ -772,7 +959,7 @@ func fetchQobuzURLSingleAttempt(api string, trackID int64, quality string, timeo
if resp.StatusCode != 200 {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
return "", fmt.Errorf("HTTP %d", resp.StatusCode)
return qobuzDownloadInfo{}, fmt.Errorf("HTTP %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
@@ -783,117 +970,115 @@ func fetchQobuzURLSingleAttempt(api string, trackID int64, quality string, timeo
}
if len(body) > 0 && body[0] == '<' {
return "", fmt.Errorf("received HTML instead of JSON")
return qobuzDownloadInfo{}, fmt.Errorf("received HTML instead of JSON")
}
urlVal, parseErr := extractQobuzDownloadURLFromBody(body)
info, parseErr := extractQobuzDownloadInfoFromBody(body)
if parseErr == nil {
return urlVal, nil
return info, nil
}
lastErr = parseErr
continue
}
if lastErr != nil {
return "", lastErr
return qobuzDownloadInfo{}, lastErr
}
return "", fmt.Errorf("all retries failed")
return qobuzDownloadInfo{}, fmt.Errorf("all retries failed")
}
func getQobuzDownloadURLParallel(apis []string, trackID int64, quality string) (string, string, error) {
if len(apis) == 0 {
return "", "", fmt.Errorf("no APIs available")
func getQobuzDownloadURLParallel(providers []qobuzAPIProvider, trackID int64, quality string) (qobuzAPIProvider, qobuzDownloadInfo, error) {
if len(providers) == 0 {
return qobuzAPIProvider{}, qobuzDownloadInfo{}, fmt.Errorf("no APIs available")
}
GoLog("[Qobuz] Requesting download URL from %d APIs in parallel (with retry)...\n", len(apis))
GoLog("[Qobuz] Requesting download URL from %d APIs in parallel (with retry)...\n", len(providers))
resultChan := make(chan qobuzAPIResult, len(apis))
resultChan := make(chan qobuzAPIResult, len(providers))
startTime := time.Now()
timeout := getQobuzAPITimeout()
for _, apiURL := range apis {
go func(api string) {
for _, provider := range providers {
go func(provider qobuzAPIProvider) {
reqStart := time.Now()
downloadURL, err := fetchQobuzURLWithRetry(api, trackID, quality, timeout)
info, err := fetchQobuzURLWithRetry(provider, trackID, quality, timeout)
resultChan <- qobuzAPIResult{
apiURL: api,
downloadURL: downloadURL,
err: err,
duration: time.Since(reqStart),
provider: provider,
info: info,
err: err,
duration: time.Since(reqStart),
}
}(apiURL)
}(provider)
}
var errors []string
for i := 0; i < len(apis); i++ {
for i := 0; i < len(providers); i++ {
result := <-resultChan
if result.err == nil {
GoLog("[Qobuz] [Parallel] Got response from %s in %v\n", result.apiURL, result.duration)
GoLog("[Qobuz] [Parallel] Got response from %s in %v\n", result.provider.Name, result.duration)
go func(remaining int) {
for j := 0; j < remaining; j++ {
<-resultChan
}
}(len(apis) - i - 1)
}(len(providers) - i - 1)
GoLog("[Qobuz] [Parallel] Total time: %v (first success)\n", time.Since(startTime))
return result.apiURL, result.downloadURL, nil
return result.provider, result.info, nil
}
errMsg := result.err.Error()
if len(errMsg) > 50 {
errMsg = errMsg[:50] + "..."
}
errors = append(errors, fmt.Sprintf("%s: %s", result.apiURL, errMsg))
errors = append(errors, fmt.Sprintf("%s: %s", result.provider.Name, errMsg))
}
GoLog("[Qobuz] [Parallel] All %d APIs failed in %v\n", len(apis), time.Since(startTime))
return "", "", fmt.Errorf("all %d Qobuz APIs failed. Errors: %v", len(apis), errors)
GoLog("[Qobuz] [Parallel] All %d APIs failed in %v\n", len(providers), time.Since(startTime))
return qobuzAPIProvider{}, qobuzDownloadInfo{}, fmt.Errorf("all %d Qobuz APIs failed. Errors: %v", len(providers), errors)
}
func (q *QobuzDownloader) GetDownloadURL(trackID int64, quality string) (string, error) {
apis := q.GetAvailableAPIs()
if len(apis) == 0 {
return "", fmt.Errorf("no Qobuz API available")
func (q *QobuzDownloader) GetDownloadURL(trackID int64, quality string) (qobuzDownloadInfo, error) {
providers := q.GetAvailableProviders()
if len(providers) == 0 {
return qobuzDownloadInfo{}, fmt.Errorf("no Qobuz API available")
}
qualityCode := strings.TrimSpace(quality)
if qualityCode == "" || qualityCode == "5" {
qualityCode = "6"
}
qualityCode := normalizeQobuzQualityCode(quality)
downloadFunc := func(qual string) (string, error) {
_, downloadURL, err := getQobuzDownloadURLParallel(apis, trackID, qual)
downloadFunc := func(qual string) (qobuzDownloadInfo, error) {
provider, info, err := getQobuzDownloadURLParallel(providers, trackID, qual)
if err != nil {
return "", err
return qobuzDownloadInfo{}, err
}
return downloadURL, nil
GoLog("[Qobuz] Download URL resolved via %s\n", provider.Name)
return info, nil
}
downloadURL, err := downloadFunc(qualityCode)
downloadInfo, err := downloadFunc(qualityCode)
if err == nil {
return downloadURL, nil
return downloadInfo, nil
}
currentQuality := qualityCode
if currentQuality == "27" {
GoLog("[Qobuz] Hi-res (27) failed, trying 24-bit (7)...\n")
downloadURL, err = downloadFunc("7")
downloadInfo, err = downloadFunc("7")
if err == nil {
return downloadURL, nil
return downloadInfo, nil
}
currentQuality = "7"
}
if currentQuality == "7" {
GoLog("[Qobuz] 24-bit failed, trying 16-bit (6)...\n")
downloadURL, err = downloadFunc("6")
downloadInfo, err = downloadFunc("6")
if err == nil {
return downloadURL, nil
return downloadInfo, nil
}
}
return "", fmt.Errorf("all Qobuz APIs failed: %w", err)
return qobuzDownloadInfo{}, fmt.Errorf("all Qobuz APIs failed: %w", err)
}
func (q *QobuzDownloader) DownloadFile(downloadURL, outputPath string, outputFD int, itemID string) error {
@@ -1150,10 +1335,19 @@ func downloadFromQobuz(req DownloadRequest) (QobuzDownloadResult, error) {
actualSampleRate := int(track.MaximumSamplingRate * 1000)
GoLog("[Qobuz] Actual quality: %d-bit/%.1fkHz\n", actualBitDepth, track.MaximumSamplingRate)
downloadURL, err := downloader.GetDownloadURL(track.ID, qobuzQuality)
downloadInfo, err := downloader.GetDownloadURL(track.ID, qobuzQuality)
if err != nil {
return QobuzDownloadResult{}, fmt.Errorf("failed to get download URL: %w", err)
}
if downloadInfo.BitDepth > 0 {
actualBitDepth = downloadInfo.BitDepth
}
if downloadInfo.SampleRate > 0 {
actualSampleRate = downloadInfo.SampleRate
}
if actualBitDepth > 0 || actualSampleRate > 0 {
GoLog("[Qobuz] API returned quality: %d-bit/%dHz\n", actualBitDepth, actualSampleRate)
}
var parallelResult *ParallelDownloadResult
parallelDone := make(chan struct{})
@@ -1176,7 +1370,7 @@ func downloadFromQobuz(req DownloadRequest) (QobuzDownloadResult, error) {
)
}()
if err := downloader.DownloadFile(downloadURL, outputPath, req.OutputFD, req.ItemID); err != nil {
if err := downloader.DownloadFile(downloadInfo.DownloadURL, outputPath, req.OutputFD, req.ItemID); err != nil {
if errors.Is(err, ErrDownloadCancelled) {
return QobuzDownloadResult{}, ErrDownloadCancelled
}
+88
View File
@@ -3,6 +3,24 @@ package gobackend
import "testing"
func TestExtractQobuzDownloadURLFromBody(t *testing.T) {
t.Run("reads top-level download_url and quality metadata", func(t *testing.T) {
body := []byte(`{"success":true,"download_url":"https://example.test/new.flac","bit_depth":24,"sampling_rate":96}`)
info, err := extractQobuzDownloadInfoFromBody(body)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if info.DownloadURL != "https://example.test/new.flac" {
t.Fatalf("unexpected URL: %q", info.DownloadURL)
}
if info.BitDepth != 24 {
t.Fatalf("unexpected bit depth: %d", info.BitDepth)
}
if info.SampleRate != 96000 {
t.Fatalf("unexpected sample rate: %d", info.SampleRate)
}
})
t.Run("reads nested data.url", func(t *testing.T) {
body := []byte(`{"success":true,"data":{"url":"https://example.test/audio.flac"}}`)
@@ -44,4 +62,74 @@ func TestExtractQobuzDownloadURLFromBody(t *testing.T) {
t.Fatalf("expected blocked error, got %v", err)
}
})
t.Run("returns detail error", func(t *testing.T) {
body := []byte(`{"detail":"Invalid quality 'lossless'. Choose from: ['mp3', 'cd', 'hi-res', 'hi-res-max']"}`)
_, err := extractQobuzDownloadURLFromBody(body)
if err == nil || err.Error() != "Invalid quality 'lossless'. Choose from: ['mp3', 'cd', 'hi-res', 'hi-res-max']" {
t.Fatalf("expected detail error, got %v", err)
}
})
}
func TestNormalizeQobuzQualityCode(t *testing.T) {
tests := map[string]string{
"": "6",
"5": "6",
"6": "6",
"cd": "6",
"lossless": "6",
"7": "7",
"hi-res": "7",
"27": "27",
"hi-res-max": "27",
"unexpected": "6",
}
for input, want := range tests {
if got := normalizeQobuzQualityCode(input); got != want {
t.Fatalf("normalizeQobuzQualityCode(%q) = %q, want %q", input, got, want)
}
}
}
func TestGetQobuzDebugKey(t *testing.T) {
got := getQobuzDebugKey()
if len(got) != len(qobuzDebugKeyObfuscated) {
t.Fatalf("unexpected debug key length: %d", len(got))
}
for i := range got {
if got[i]^qobuzDebugKeyXORMask != qobuzDebugKeyObfuscated[i] {
t.Fatalf("unexpected debug key reconstruction at index %d", i)
}
}
}
func TestQobuzAvailableProviders(t *testing.T) {
providers := NewQobuzDownloader().GetAvailableProviders()
if len(providers) != 3 {
t.Fatalf("expected 3 Qobuz providers, got %d", len(providers))
}
want := map[string]string{
"musicdl": qobuzAPIKindMusicDL,
"dabmusic": qobuzAPIKindStandard,
"deeb": qobuzAPIKindStandard,
}
for _, provider := range providers {
wantKind, ok := want[provider.Name]
if !ok {
t.Fatalf("unexpected provider %q", provider.Name)
}
if provider.Kind != wantKind {
t.Fatalf("provider %q has kind %q, want %q", provider.Name, provider.Kind, wantKind)
}
delete(want, provider.Name)
}
if len(want) != 0 {
t.Fatalf("missing providers: %v", want)
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
/// App version and info constants
/// Update version here only - all other files will reference this
class AppInfo {
static const String version = '4.0.1';
static const String buildNumber = '102';
static const String version = '3.7.0';
static const String buildNumber = '103';
static const String fullVersion = '$version+$buildNumber';
-312
View File
@@ -5699,318 +5699,6 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'You can switch between modes anytime in Settings.'**
String get setupModeChangeableLater;
/// Title for Smart Queue toggle in settings
///
/// In en, this message translates to:
/// **'Smart Queue'**
String get settingsSmartQueueTitle;
/// Subtitle for Smart Queue toggle in settings
///
/// In en, this message translates to:
/// **'Automatically discover and add similar tracks to your queue'**
String get settingsSmartQueueSubtitle;
/// Title for the What's New screen
///
/// In en, this message translates to:
/// **'What\'s New in 4.0'**
String get whatsNewTitle;
/// Subtitle for the What's New screen
///
/// In en, this message translates to:
/// **'SpotiFLAC has evolved — here\'s what changed since 3.x'**
String get whatsNewSubtitle;
/// Welcome page title in What's New screen
///
/// In en, this message translates to:
/// **'SpotiFLAC Mobile 4.0'**
String get whatsNewWelcomeTitle;
/// Welcome page description in What's New screen
///
/// In en, this message translates to:
/// **'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.'**
String get whatsNewWelcomeDesc;
/// Welcome page tip 1
///
/// In en, this message translates to:
/// **'New streaming mode with instant playback'**
String get whatsNewWelcomeTip1;
/// Welcome page tip 2
///
/// In en, this message translates to:
/// **'Redesigned library and full-screen player'**
String get whatsNewWelcomeTip2;
/// Welcome page tip 3
///
/// In en, this message translates to:
/// **'Batch tools, performance boosts, and more'**
String get whatsNewWelcomeTip3;
/// What's New feature: Streaming Mode title
///
/// In en, this message translates to:
/// **'Streaming Mode'**
String get whatsNewStreamingTitle;
/// What's New feature: Streaming Mode description
///
/// In en, this message translates to:
/// **'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.'**
String get whatsNewStreamingDesc;
/// What's New feature: Smart Queue title
///
/// In en, this message translates to:
/// **'Smart Queue'**
String get whatsNewSmartQueueTitle;
/// What's New feature: Smart Queue description
///
/// In en, this message translates to:
/// **'Your queue auto-curates with related tracks and artist discovery. Never run out of music.'**
String get whatsNewSmartQueueDesc;
/// What's New feature: Dual Mode title
///
/// In en, this message translates to:
/// **'Dual Mode'**
String get whatsNewDualModeTitle;
/// What's New feature: Dual Mode description
///
/// In en, this message translates to:
/// **'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.'**
String get whatsNewDualModeDesc;
/// What's New feature: Library redesign title
///
/// In en, this message translates to:
/// **'Redesigned Library'**
String get whatsNewLibraryTitle;
/// What's New feature: Library redesign description
///
/// In en, this message translates to:
/// **'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.'**
String get whatsNewLibraryDesc;
/// What's New feature: Full-Screen Player title
///
/// In en, this message translates to:
/// **'Full-Screen Player'**
String get whatsNewPlayerTitle;
/// What's New feature: Full-Screen Player description
///
/// In en, this message translates to:
/// **'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.'**
String get whatsNewPlayerDesc;
/// What's New feature: Context Menus title
///
/// In en, this message translates to:
/// **'Long-Press Menus'**
String get whatsNewContextMenuTitle;
/// What's New feature: Context Menus description
///
/// In en, this message translates to:
/// **'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.'**
String get whatsNewContextMenuDesc;
/// What's New feature: Performance title
///
/// In en, this message translates to:
/// **'Performance'**
String get whatsNewPerformanceTitle;
/// What's New feature: Performance description
///
/// In en, this message translates to:
/// **'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.'**
String get whatsNewPerformanceDesc;
/// What's New feature: Batch Tools title
///
/// In en, this message translates to:
/// **'Batch Tools'**
String get whatsNewBatchToolsTitle;
/// What's New feature: Batch Tools description
///
/// In en, this message translates to:
/// **'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.'**
String get whatsNewBatchToolsDesc;
/// What's New tip: streaming instant play
///
/// In en, this message translates to:
/// **'Tap any track to start playing instantly'**
String get whatsNewStreamingTip1;
/// What's New tip: streaming synced lyrics
///
/// In en, this message translates to:
/// **'Synced lyrics in the full-screen player'**
String get whatsNewStreamingTip2;
/// What's New tip: streaming download from player
///
/// In en, this message translates to:
/// **'Download tracks directly from the player'**
String get whatsNewStreamingTip3;
/// What's New tip: smart queue auto-fill
///
/// In en, this message translates to:
/// **'Queue auto-fills with related tracks'**
String get whatsNewSmartQueueTip1;
/// What's New tip: smart queue artist discovery
///
/// In en, this message translates to:
/// **'Discover new artists as you listen'**
String get whatsNewSmartQueueTip2;
/// What's New tip: smart queue endless
///
/// In en, this message translates to:
/// **'Never run out of music to play'**
String get whatsNewSmartQueueTip3;
/// What's New tip: dual mode switch
///
/// In en, this message translates to:
/// **'Switch modes anytime in Settings'**
String get whatsNewDualModeTip1;
/// What's New tip: dual mode adaptive UI
///
/// In en, this message translates to:
/// **'UI buttons adapt to your current mode'**
String get whatsNewDualModeTip2;
/// What's New tip: dual mode use cases
///
/// In en, this message translates to:
/// **'Download for offline, stream for instant play'**
String get whatsNewDualModeTip3;
/// What's New tip: library drag and drop
///
/// In en, this message translates to:
/// **'Drag and drop to organize playlists'**
String get whatsNewLibraryTip1;
/// What's New tip: library custom covers
///
/// In en, this message translates to:
/// **'Set custom cover images for playlists'**
String get whatsNewLibraryTip2;
/// What's New tip: library multi-select
///
/// In en, this message translates to:
/// **'Multi-select tracks for batch actions'**
String get whatsNewLibraryTip3;
/// What's New tip: player parallax
///
/// In en, this message translates to:
/// **'Cover art with parallax scrolling effect'**
String get whatsNewPlayerTip1;
/// What's New tip: player persistence
///
/// In en, this message translates to:
/// **'Playback persists across app restarts'**
String get whatsNewPlayerTip2;
/// What's New tip: player lyrics
///
/// In en, this message translates to:
/// **'Synced lyrics while you listen'**
String get whatsNewPlayerTip3;
/// What's New tip: context menu add to playlist
///
/// In en, this message translates to:
/// **'Add tracks to any playlist instantly'**
String get whatsNewContextMenuTip1;
/// What's New tip: context menu share/convert
///
/// In en, this message translates to:
/// **'Share or convert with one tap'**
String get whatsNewContextMenuTip2;
/// What's New tip: context menu re-enrich
///
/// In en, this message translates to:
/// **'Re-enrich metadata when needed'**
String get whatsNewContextMenuTip3;
/// What's New tip: batch share
///
/// In en, this message translates to:
/// **'Share multiple tracks at once'**
String get whatsNewBatchToolsTip1;
/// What's New tip: batch convert
///
/// In en, this message translates to:
/// **'Batch convert to MP3 or Opus format'**
String get whatsNewBatchToolsTip2;
/// What's New tip: batch re-enrich
///
/// In en, this message translates to:
/// **'Re-enrich metadata across your library'**
String get whatsNewBatchToolsTip3;
/// What's New tip: performance startup
///
/// In en, this message translates to:
/// **'Faster app startup time'**
String get whatsNewPerformanceTip1;
/// What's New tip: performance memory
///
/// In en, this message translates to:
/// **'Reduced memory usage during playback'**
String get whatsNewPerformanceTip2;
/// What's New tip: performance SQLite
///
/// In en, this message translates to:
/// **'SQLite-backed storage for reliability'**
String get whatsNewPerformanceTip3;
/// Ready card message on last What's New page
///
/// In en, this message translates to:
/// **'You\'re all set — enjoy the new SpotiFLAC!'**
String get whatsNewReadyMessage;
/// Button text to dismiss What's New screen
///
/// In en, this message translates to:
/// **'Let\'s Go'**
String get whatsNewGetStarted;
/// Page indicator text in What's New screen
///
/// In en, this message translates to:
/// **'{current} of {total}'**
String whatsNewPageIndicator(int current, int total);
}
class _AppLocalizationsDelegate
-173
View File
@@ -3288,177 +3288,4 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get setupModeChangeableLater =>
'Du kannst jederzeit in den Einstellungen zwischen den Modi wechseln.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Automatisch ähnliche Titel entdecken und zu deiner Warteschlange hinzufügen';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
-173
View File
@@ -3266,177 +3266,4 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get setupModeChangeableLater =>
'You can switch between modes anytime in Settings.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Automatically discover and add similar tracks to your queue';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
-180
View File
@@ -3267,179 +3267,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get setupModeChangeableLater =>
'Puedes cambiar entre modos en cualquier momento en Ajustes.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Descubre y añade automáticamente pistas similares a tu cola de reproducción';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
/// The translations for Spanish Castilian, as used in Spain (`es_ES`).
@@ -6447,11 +6274,4 @@ class AppLocalizationsEsEs extends AppLocalizationsEs {
@override
String get setupModeChangeableLater =>
'Puedes cambiar entre modos en cualquier momento en Ajustes.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Descubre y añade automáticamente pistas similares a tu cola de reproducción';
}
-173
View File
@@ -3273,177 +3273,4 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get setupModeChangeableLater =>
'Vous pouvez changer de mode à tout moment dans les Paramètres.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Découvrir et ajouter automatiquement des pistes similaires à votre file d\'attente';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
-173
View File
@@ -3267,177 +3267,4 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get setupModeChangeableLater =>
'आप सेटिंग्स में कभी भी मोड बदल सकते हैं।';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'स्वचालित रूप से समान ट्रैक खोजें और अपनी कतार में जोड़ें';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
-182
View File
@@ -3282,186 +3282,4 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get setupModeChangeableLater =>
'Anda dapat beralih antar mode kapan saja di Pengaturan.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Secara otomatis temukan dan tambahkan trek serupa ke antrean Anda';
@override
String get whatsNewTitle => 'Yang Baru di 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC telah berevolusi — inilah yang berubah sejak 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Selamat datang kembali! Ini pembaruan besar dengan banyak fitur baru. Geser untuk melihat apa yang berubah.';
@override
String get whatsNewWelcomeTip1 =>
'Mode streaming baru dengan pemutaran instan';
@override
String get whatsNewWelcomeTip2 =>
'Perpustakaan dan pemutar layar penuh yang didesain ulang';
@override
String get whatsNewWelcomeTip3 =>
'Alat massal, peningkatan performa, dan lainnya';
@override
String get whatsNewStreamingTitle => 'Mode Streaming';
@override
String get whatsNewStreamingDesc =>
'Ketuk trek apa pun untuk langsung diputar — tanpa perlu mengunduh. Pemutar layar penuh dengan lirik tersinkron dan kontrol media.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Antrean Anda otomatis mengkurasi trek terkait dan penemuan artis. Tak pernah kehabisan musik.';
@override
String get whatsNewDualModeTitle => 'Mode Ganda';
@override
String get whatsNewDualModeDesc =>
'Beralih antara mode Pengunduh dan Streaming kapan saja. Semua tombol menyesuaikan secara otomatis.';
@override
String get whatsNewLibraryTitle => 'Perpustakaan Baru';
@override
String get whatsNewLibraryDesc =>
'Tata letak berbasis playlist dengan kategorisasi seret-dan-lepas, sampul kustom, dan aksi massal multi-pilih.';
@override
String get whatsNewPlayerTitle => 'Pemutar Layar Penuh';
@override
String get whatsNewPlayerDesc =>
'Paralaks seni sampul, lirik tersinkron, pemutaran tetap tersimpan saat restart, dan tombol unduh di pemutar.';
@override
String get whatsNewContextMenuTitle => 'Menu Tekan Lama';
@override
String get whatsNewContextMenuDesc =>
'Tekan lama trek apa pun untuk aksi cepat — tambah ke playlist, bagikan, konversi, atau perbarui metadata.';
@override
String get whatsNewPerformanceTitle => 'Performa';
@override
String get whatsNewPerformanceDesc =>
'Startup lebih cepat, penggunaan memori berkurang, penyimpanan berbasis SQLite, dan pembaruan UI yang lebih efisien.';
@override
String get whatsNewBatchToolsTitle => 'Alat Massal';
@override
String get whatsNewBatchToolsDesc =>
'Berbagi multi-pilih, konversi massal ke MP3/Opus, dan perbarui metadata secara massal di seluruh perpustakaan.';
@override
String get whatsNewStreamingTip1 =>
'Ketuk trek apa pun untuk langsung memutar';
@override
String get whatsNewStreamingTip2 => 'Lirik tersinkron di pemutar layar penuh';
@override
String get whatsNewStreamingTip3 => 'Unduh trek langsung dari pemutar';
@override
String get whatsNewSmartQueueTip1 =>
'Antrean terisi otomatis dengan trek terkait';
@override
String get whatsNewSmartQueueTip2 => 'Temukan artis baru saat mendengarkan';
@override
String get whatsNewSmartQueueTip3 =>
'Tak pernah kehabisan musik untuk diputar';
@override
String get whatsNewDualModeTip1 => 'Beralih mode kapan saja di Pengaturan';
@override
String get whatsNewDualModeTip2 => 'Tombol UI menyesuaikan dengan mode Anda';
@override
String get whatsNewDualModeTip3 =>
'Unduh untuk offline, streaming untuk putar langsung';
@override
String get whatsNewLibraryTip1 => 'Seret dan lepas untuk mengatur playlist';
@override
String get whatsNewLibraryTip2 => 'Atur gambar sampul kustom untuk playlist';
@override
String get whatsNewLibraryTip3 => 'Pilih banyak trek untuk aksi massal';
@override
String get whatsNewPlayerTip1 => 'Seni sampul dengan efek paralaks';
@override
String get whatsNewPlayerTip2 => 'Pemutaran tetap tersimpan saat restart';
@override
String get whatsNewPlayerTip3 => 'Lirik tersinkron saat mendengarkan';
@override
String get whatsNewContextMenuTip1 =>
'Tambahkan trek ke playlist mana pun langsung';
@override
String get whatsNewContextMenuTip2 =>
'Bagikan atau konversi dengan satu ketukan';
@override
String get whatsNewContextMenuTip3 => 'Perbarui metadata saat diperlukan';
@override
String get whatsNewBatchToolsTip1 => 'Bagikan banyak trek sekaligus';
@override
String get whatsNewBatchToolsTip2 =>
'Konversi massal ke format MP3 atau Opus';
@override
String get whatsNewBatchToolsTip3 =>
'Perbarui metadata di seluruh perpustakaan';
@override
String get whatsNewPerformanceTip1 => 'Waktu startup aplikasi lebih cepat';
@override
String get whatsNewPerformanceTip2 =>
'Penggunaan memori berkurang saat pemutaran';
@override
String get whatsNewPerformanceTip3 =>
'Penyimpanan berbasis SQLite untuk keandalan';
@override
String get whatsNewReadyMessage => 'Siap — nikmati SpotiFLAC yang baru!';
@override
String get whatsNewGetStarted => 'Ayo Mulai';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current dari $total';
}
}
-172
View File
@@ -3246,176 +3246,4 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get setupModeChangeableLater => '設定からいつでもモードを切り替えられます。';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle => '類似トラックを自動的に検出してキューに追加';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
-172
View File
@@ -3259,176 +3259,4 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get setupModeChangeableLater => '설정에서 언제든지 모드를 전환할 수 있습니다.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle => '유사한 트랙을 자동으로 검색하여 대기열에 추가';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
-173
View File
@@ -3267,177 +3267,4 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get setupModeChangeableLater =>
'Je kunt op elk moment wisselen tussen modi in Instellingen.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Ontdek automatisch vergelijkbare nummers en voeg ze toe aan je wachtrij';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
-180
View File
@@ -3267,179 +3267,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get setupModeChangeableLater =>
'Você pode alternar entre os modos a qualquer momento nas Configurações.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Descubra e adicione automaticamente faixas semelhantes à sua fila';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
/// The translations for Portuguese, as used in Portugal (`pt_PT`).
@@ -6441,11 +6268,4 @@ class AppLocalizationsPtPt extends AppLocalizationsPt {
@override
String get setupModeChangeableLater =>
'Pode alternar entre modos a qualquer momento nas Definições.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Descubra e adicione automaticamente faixas semelhantes à sua fila';
}
-173
View File
@@ -3365,177 +3365,4 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get setupModeChangeableLater =>
'Вы можете переключаться между режимами в любое время в Настройках.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Автоматически находите и добавляйте похожие треки в очередь воспроизведения';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
-173
View File
@@ -3281,177 +3281,4 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get setupModeChangeableLater =>
'Ayarlar\'dan istediğiniz zaman modlar arasında geçiş yapabilirsiniz.';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle =>
'Sıranıza otomatik olarak benzer parçalar keşfedin ve ekleyin';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
-184
View File
@@ -3259,178 +3259,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get setupModeChangeableLater => '您可以随时在设置中切换模式。';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle => '自动发现并将相似曲目添加到您的队列中';
@override
String get whatsNewTitle => 'What\'s New in 4.0';
@override
String get whatsNewSubtitle =>
'SpotiFLAC has evolved — here\'s what changed since 3.x';
@override
String get whatsNewWelcomeTitle => 'SpotiFLAC Mobile 4.0';
@override
String get whatsNewWelcomeDesc =>
'Welcome back! This is a major update packed with new features. Swipe through to see what\'s changed.';
@override
String get whatsNewWelcomeTip1 => 'New streaming mode with instant playback';
@override
String get whatsNewWelcomeTip2 => 'Redesigned library and full-screen player';
@override
String get whatsNewWelcomeTip3 => 'Batch tools, performance boosts, and more';
@override
String get whatsNewStreamingTitle => 'Streaming Mode';
@override
String get whatsNewStreamingDesc =>
'Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.';
@override
String get whatsNewSmartQueueTitle => 'Smart Queue';
@override
String get whatsNewSmartQueueDesc =>
'Your queue auto-curates with related tracks and artist discovery. Never run out of music.';
@override
String get whatsNewDualModeTitle => 'Dual Mode';
@override
String get whatsNewDualModeDesc =>
'Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.';
@override
String get whatsNewLibraryTitle => 'Redesigned Library';
@override
String get whatsNewLibraryDesc =>
'Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.';
@override
String get whatsNewPlayerTitle => 'Full-Screen Player';
@override
String get whatsNewPlayerDesc =>
'Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.';
@override
String get whatsNewContextMenuTitle => 'Long-Press Menus';
@override
String get whatsNewContextMenuDesc =>
'Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.';
@override
String get whatsNewPerformanceTitle => 'Performance';
@override
String get whatsNewPerformanceDesc =>
'Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.';
@override
String get whatsNewBatchToolsTitle => 'Batch Tools';
@override
String get whatsNewBatchToolsDesc =>
'Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.';
@override
String get whatsNewStreamingTip1 =>
'Tap any track to start playing instantly';
@override
String get whatsNewStreamingTip2 => 'Synced lyrics in the full-screen player';
@override
String get whatsNewStreamingTip3 =>
'Download tracks directly from the player';
@override
String get whatsNewSmartQueueTip1 => 'Queue auto-fills with related tracks';
@override
String get whatsNewSmartQueueTip2 => 'Discover new artists as you listen';
@override
String get whatsNewSmartQueueTip3 => 'Never run out of music to play';
@override
String get whatsNewDualModeTip1 => 'Switch modes anytime in Settings';
@override
String get whatsNewDualModeTip2 => 'UI buttons adapt to your current mode';
@override
String get whatsNewDualModeTip3 =>
'Download for offline, stream for instant play';
@override
String get whatsNewLibraryTip1 => 'Drag and drop to organize playlists';
@override
String get whatsNewLibraryTip2 => 'Set custom cover images for playlists';
@override
String get whatsNewLibraryTip3 => 'Multi-select tracks for batch actions';
@override
String get whatsNewPlayerTip1 => 'Cover art with parallax scrolling effect';
@override
String get whatsNewPlayerTip2 => 'Playback persists across app restarts';
@override
String get whatsNewPlayerTip3 => 'Synced lyrics while you listen';
@override
String get whatsNewContextMenuTip1 => 'Add tracks to any playlist instantly';
@override
String get whatsNewContextMenuTip2 => 'Share or convert with one tap';
@override
String get whatsNewContextMenuTip3 => 'Re-enrich metadata when needed';
@override
String get whatsNewBatchToolsTip1 => 'Share multiple tracks at once';
@override
String get whatsNewBatchToolsTip2 => 'Batch convert to MP3 or Opus format';
@override
String get whatsNewBatchToolsTip3 => 'Re-enrich metadata across your library';
@override
String get whatsNewPerformanceTip1 => 'Faster app startup time';
@override
String get whatsNewPerformanceTip2 => 'Reduced memory usage during playback';
@override
String get whatsNewPerformanceTip3 => 'SQLite-backed storage for reliability';
@override
String get whatsNewReadyMessage =>
'You\'re all set — enjoy the new SpotiFLAC!';
@override
String get whatsNewGetStarted => 'Let\'s Go';
@override
String whatsNewPageIndicator(int current, int total) {
return '$current of $total';
}
}
/// The translations for Chinese, as used in China (`zh_CN`).
@@ -6397,12 +6225,6 @@ class AppLocalizationsZhCn extends AppLocalizationsZh {
@override
String get setupModeChangeableLater => '您可以随时在设置中切换模式。';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle => '自动发现并将相似曲目添加到您的队列中';
}
/// The translations for Chinese, as used in Taiwan (`zh_TW`).
@@ -9369,10 +9191,4 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override
String get setupModeChangeableLater => '您可以隨時在設定中切換模式。';
@override
String get settingsSmartQueueTitle => 'Smart Queue';
@override
String get settingsSmartQueueSubtitle => '自動探索並將相似曲目新增到您的佇列中';
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "Streame Titel sofort ohne Herunterladen",
"setupModeStreamingFeature2": "Smart Queue entdeckt automatisch neue Musik für dich",
"setupModeStreamingFeature3": "Spiele jeden Titel auf Abruf mit Wiedergabesteuerung",
"setupModeChangeableLater": "Du kannst jederzeit in den Einstellungen zwischen den Modi wechseln.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "Automatisch ähnliche Titel entdecken und zu deiner Warteschlange hinzufügen"
"setupModeChangeableLater": "Du kannst jederzeit in den Einstellungen zwischen den Modi wechseln."
}
+1 -113
View File
@@ -2495,117 +2495,5 @@
"setupModeStreamingFeature3": "Play any track on demand with playback controls",
"@setupModeStreamingFeature3": {"description": "Streaming mode feature 3"},
"setupModeChangeableLater": "You can switch between modes anytime in Settings.",
"@setupModeChangeableLater": {"description": "Hint that mode can be changed later"},
"settingsSmartQueueTitle": "Smart Queue",
"@settingsSmartQueueTitle": {"description": "Title for Smart Queue toggle in settings"},
"settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue",
"@settingsSmartQueueSubtitle": {"description": "Subtitle for Smart Queue toggle in settings"},
"whatsNewTitle": "What's New in 4.0",
"@whatsNewTitle": {"description": "Title for the What's New screen"},
"whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x",
"@whatsNewSubtitle": {"description": "Subtitle for the What's New screen"},
"whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0",
"@whatsNewWelcomeTitle": {"description": "Welcome page title in What's New screen"},
"whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.",
"@whatsNewWelcomeDesc": {"description": "Welcome page description in What's New screen"},
"whatsNewWelcomeTip1": "New streaming mode with instant playback",
"@whatsNewWelcomeTip1": {"description": "Welcome page tip 1"},
"whatsNewWelcomeTip2": "Redesigned library and full-screen player",
"@whatsNewWelcomeTip2": {"description": "Welcome page tip 2"},
"whatsNewWelcomeTip3": "Batch tools, performance boosts, and more",
"@whatsNewWelcomeTip3": {"description": "Welcome page tip 3"},
"whatsNewStreamingTitle": "Streaming Mode",
"@whatsNewStreamingTitle": {"description": "What's New feature: Streaming Mode title"},
"whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.",
"@whatsNewStreamingDesc": {"description": "What's New feature: Streaming Mode description"},
"whatsNewSmartQueueTitle": "Smart Queue",
"@whatsNewSmartQueueTitle": {"description": "What's New feature: Smart Queue title"},
"whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.",
"@whatsNewSmartQueueDesc": {"description": "What's New feature: Smart Queue description"},
"whatsNewDualModeTitle": "Dual Mode",
"@whatsNewDualModeTitle": {"description": "What's New feature: Dual Mode title"},
"whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.",
"@whatsNewDualModeDesc": {"description": "What's New feature: Dual Mode description"},
"whatsNewLibraryTitle": "Redesigned Library",
"@whatsNewLibraryTitle": {"description": "What's New feature: Library redesign title"},
"whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.",
"@whatsNewLibraryDesc": {"description": "What's New feature: Library redesign description"},
"whatsNewPlayerTitle": "Full-Screen Player",
"@whatsNewPlayerTitle": {"description": "What's New feature: Full-Screen Player title"},
"whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.",
"@whatsNewPlayerDesc": {"description": "What's New feature: Full-Screen Player description"},
"whatsNewContextMenuTitle": "Long-Press Menus",
"@whatsNewContextMenuTitle": {"description": "What's New feature: Context Menus title"},
"whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.",
"@whatsNewContextMenuDesc": {"description": "What's New feature: Context Menus description"},
"whatsNewPerformanceTitle": "Performance",
"@whatsNewPerformanceTitle": {"description": "What's New feature: Performance title"},
"whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.",
"@whatsNewPerformanceDesc": {"description": "What's New feature: Performance description"},
"whatsNewBatchToolsTitle": "Batch Tools",
"@whatsNewBatchToolsTitle": {"description": "What's New feature: Batch Tools title"},
"whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.",
"@whatsNewBatchToolsDesc": {"description": "What's New feature: Batch Tools description"},
"whatsNewStreamingTip1": "Tap any track to start playing instantly",
"@whatsNewStreamingTip1": {"description": "What's New tip: streaming instant play"},
"whatsNewStreamingTip2": "Synced lyrics in the full-screen player",
"@whatsNewStreamingTip2": {"description": "What's New tip: streaming synced lyrics"},
"whatsNewStreamingTip3": "Download tracks directly from the player",
"@whatsNewStreamingTip3": {"description": "What's New tip: streaming download from player"},
"whatsNewSmartQueueTip1": "Queue auto-fills with related tracks",
"@whatsNewSmartQueueTip1": {"description": "What's New tip: smart queue auto-fill"},
"whatsNewSmartQueueTip2": "Discover new artists as you listen",
"@whatsNewSmartQueueTip2": {"description": "What's New tip: smart queue artist discovery"},
"whatsNewSmartQueueTip3": "Never run out of music to play",
"@whatsNewSmartQueueTip3": {"description": "What's New tip: smart queue endless"},
"whatsNewDualModeTip1": "Switch modes anytime in Settings",
"@whatsNewDualModeTip1": {"description": "What's New tip: dual mode switch"},
"whatsNewDualModeTip2": "UI buttons adapt to your current mode",
"@whatsNewDualModeTip2": {"description": "What's New tip: dual mode adaptive UI"},
"whatsNewDualModeTip3": "Download for offline, stream for instant play",
"@whatsNewDualModeTip3": {"description": "What's New tip: dual mode use cases"},
"whatsNewLibraryTip1": "Drag and drop to organize playlists",
"@whatsNewLibraryTip1": {"description": "What's New tip: library drag and drop"},
"whatsNewLibraryTip2": "Set custom cover images for playlists",
"@whatsNewLibraryTip2": {"description": "What's New tip: library custom covers"},
"whatsNewLibraryTip3": "Multi-select tracks for batch actions",
"@whatsNewLibraryTip3": {"description": "What's New tip: library multi-select"},
"whatsNewPlayerTip1": "Cover art with parallax scrolling effect",
"@whatsNewPlayerTip1": {"description": "What's New tip: player parallax"},
"whatsNewPlayerTip2": "Playback persists across app restarts",
"@whatsNewPlayerTip2": {"description": "What's New tip: player persistence"},
"whatsNewPlayerTip3": "Synced lyrics while you listen",
"@whatsNewPlayerTip3": {"description": "What's New tip: player lyrics"},
"whatsNewContextMenuTip1": "Add tracks to any playlist instantly",
"@whatsNewContextMenuTip1": {"description": "What's New tip: context menu add to playlist"},
"whatsNewContextMenuTip2": "Share or convert with one tap",
"@whatsNewContextMenuTip2": {"description": "What's New tip: context menu share/convert"},
"whatsNewContextMenuTip3": "Re-enrich metadata when needed",
"@whatsNewContextMenuTip3": {"description": "What's New tip: context menu re-enrich"},
"whatsNewBatchToolsTip1": "Share multiple tracks at once",
"@whatsNewBatchToolsTip1": {"description": "What's New tip: batch share"},
"whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format",
"@whatsNewBatchToolsTip2": {"description": "What's New tip: batch convert"},
"whatsNewBatchToolsTip3": "Re-enrich metadata across your library",
"@whatsNewBatchToolsTip3": {"description": "What's New tip: batch re-enrich"},
"whatsNewPerformanceTip1": "Faster app startup time",
"@whatsNewPerformanceTip1": {"description": "What's New tip: performance startup"},
"whatsNewPerformanceTip2": "Reduced memory usage during playback",
"@whatsNewPerformanceTip2": {"description": "What's New tip: performance memory"},
"whatsNewPerformanceTip3": "SQLite-backed storage for reliability",
"@whatsNewPerformanceTip3": {"description": "What's New tip: performance SQLite"},
"whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!",
"@whatsNewReadyMessage": {"description": "Ready card message on last What's New page"},
"whatsNewGetStarted": "Let's Go",
"@whatsNewGetStarted": {"description": "Button text to dismiss What's New screen"},
"whatsNewPageIndicator": "{current} of {total}",
"@whatsNewPageIndicator": {
"description": "Page indicator text in What's New screen",
"placeholders": {
"current": {"type": "int"},
"total": {"type": "int"}
}
}
"@setupModeChangeableLater": {"description": "Hint that mode can be changed later"}
}
+1 -3
View File
@@ -2576,7 +2576,5 @@
"setupModeStreamingFeature1": "Transmite pistas al instante sin descargar",
"setupModeStreamingFeature2": "Smart Queue descubre automáticamente nueva música para ti",
"setupModeStreamingFeature3": "Reproduce cualquier pista bajo demanda con controles de reproducción",
"setupModeChangeableLater": "Puedes cambiar entre modos en cualquier momento en Ajustes.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "Descubre y añade automáticamente pistas similares a tu cola de reproducción"
"setupModeChangeableLater": "Puedes cambiar entre modos en cualquier momento en Ajustes."
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "Transmite pistas al instante sin descargar",
"setupModeStreamingFeature2": "Smart Queue descubre automáticamente nueva música para ti",
"setupModeStreamingFeature3": "Reproduce cualquier pista bajo demanda con controles de reproducción",
"setupModeChangeableLater": "Puedes cambiar entre modos en cualquier momento en Ajustes.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "Descubre y añade automáticamente pistas similares a tu cola de reproducción"
"setupModeChangeableLater": "Puedes cambiar entre modos en cualquier momento en Ajustes."
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "Diffusez des pistes instantanément sans télécharger",
"setupModeStreamingFeature2": "Smart Queue découvre automatiquement de nouvelle musique pour vous",
"setupModeStreamingFeature3": "Écoutez n'importe quelle piste à la demande avec les contrôles de lecture",
"setupModeChangeableLater": "Vous pouvez changer de mode à tout moment dans les Paramètres.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "Découvrir et ajouter automatiquement des pistes similaires à votre file d'attente"
"setupModeChangeableLater": "Vous pouvez changer de mode à tout moment dans les Paramètres."
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "बिना डाउनलोड किए तुरंत ट्रैक स्ट्रीम करें",
"setupModeStreamingFeature2": "Smart Queue स्वचालित रूप से आपके लिए नया संगीत खोजता है",
"setupModeStreamingFeature3": "प्लेबैक नियंत्रण के साथ किसी भी ट्रैक को मांग पर चलाएं",
"setupModeChangeableLater": "आप सेटिंग्स में कभी भी मोड बदल सकते हैं।",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "स्वचालित रूप से समान ट्रैक खोजें और अपनी कतार में जोड़ें"
"setupModeChangeableLater": "आप सेटिंग्स में कभी भी मोड बदल सकते हैं।"
}
-110
View File
@@ -4201,9 +4201,6 @@
"setupModeStreamingFeature2": "Smart Queue secara otomatis menemukan musik baru untuk Anda",
"setupModeStreamingFeature3": "Putar trek apa pun sesuai permintaan dengan kontrol pemutaran",
"setupModeChangeableLater": "Anda dapat beralih antar mode kapan saja di Pengaturan.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "Secara otomatis temukan dan tambahkan trek serupa ke antrean Anda",
"selectionShareCount": "Bagikan {count} {count, plural, =1{trek} other{trek}}",
"@selectionShareCount": {
"description": "Share button text with count in selection mode",
@@ -4249,112 +4246,5 @@
"total": {"type": "int"},
"format": {"type": "String"}
}
},
"whatsNewTitle": "Yang Baru di 4.0",
"@whatsNewTitle": {"description": "Title for the What's New screen"},
"whatsNewSubtitle": "SpotiFLAC telah berevolusi — inilah yang berubah sejak 3.x",
"@whatsNewSubtitle": {"description": "Subtitle for the What's New screen"},
"whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0",
"@whatsNewWelcomeTitle": {"description": "Welcome page title in What's New screen"},
"whatsNewWelcomeDesc": "Selamat datang kembali! Ini pembaruan besar dengan banyak fitur baru. Geser untuk melihat apa yang berubah.",
"@whatsNewWelcomeDesc": {"description": "Welcome page description in What's New screen"},
"whatsNewWelcomeTip1": "Mode streaming baru dengan pemutaran instan",
"@whatsNewWelcomeTip1": {"description": "Welcome page tip 1"},
"whatsNewWelcomeTip2": "Perpustakaan dan pemutar layar penuh yang didesain ulang",
"@whatsNewWelcomeTip2": {"description": "Welcome page tip 2"},
"whatsNewWelcomeTip3": "Alat massal, peningkatan performa, dan lainnya",
"@whatsNewWelcomeTip3": {"description": "Welcome page tip 3"},
"whatsNewStreamingTitle": "Mode Streaming",
"@whatsNewStreamingTitle": {"description": "What's New feature: Streaming Mode title"},
"whatsNewStreamingDesc": "Ketuk trek apa pun untuk langsung diputar — tanpa perlu mengunduh. Pemutar layar penuh dengan lirik tersinkron dan kontrol media.",
"@whatsNewStreamingDesc": {"description": "What's New feature: Streaming Mode description"},
"whatsNewSmartQueueTitle": "Smart Queue",
"@whatsNewSmartQueueTitle": {"description": "What's New feature: Smart Queue title"},
"whatsNewSmartQueueDesc": "Antrean Anda otomatis mengkurasi trek terkait dan penemuan artis. Tak pernah kehabisan musik.",
"@whatsNewSmartQueueDesc": {"description": "What's New feature: Smart Queue description"},
"whatsNewDualModeTitle": "Mode Ganda",
"@whatsNewDualModeTitle": {"description": "What's New feature: Dual Mode title"},
"whatsNewDualModeDesc": "Beralih antara mode Pengunduh dan Streaming kapan saja. Semua tombol menyesuaikan secara otomatis.",
"@whatsNewDualModeDesc": {"description": "What's New feature: Dual Mode description"},
"whatsNewLibraryTitle": "Perpustakaan Baru",
"@whatsNewLibraryTitle": {"description": "What's New feature: Library redesign title"},
"whatsNewLibraryDesc": "Tata letak berbasis playlist dengan kategorisasi seret-dan-lepas, sampul kustom, dan aksi massal multi-pilih.",
"@whatsNewLibraryDesc": {"description": "What's New feature: Library redesign description"},
"whatsNewPlayerTitle": "Pemutar Layar Penuh",
"@whatsNewPlayerTitle": {"description": "What's New feature: Full-Screen Player title"},
"whatsNewPlayerDesc": "Paralaks seni sampul, lirik tersinkron, pemutaran tetap tersimpan saat restart, dan tombol unduh di pemutar.",
"@whatsNewPlayerDesc": {"description": "What's New feature: Full-Screen Player description"},
"whatsNewContextMenuTitle": "Menu Tekan Lama",
"@whatsNewContextMenuTitle": {"description": "What's New feature: Context Menus title"},
"whatsNewContextMenuDesc": "Tekan lama trek apa pun untuk aksi cepat — tambah ke playlist, bagikan, konversi, atau perbarui metadata.",
"@whatsNewContextMenuDesc": {"description": "What's New feature: Context Menus description"},
"whatsNewPerformanceTitle": "Performa",
"@whatsNewPerformanceTitle": {"description": "What's New feature: Performance title"},
"whatsNewPerformanceDesc": "Startup lebih cepat, penggunaan memori berkurang, penyimpanan berbasis SQLite, dan pembaruan UI yang lebih efisien.",
"@whatsNewPerformanceDesc": {"description": "What's New feature: Performance description"},
"whatsNewBatchToolsTitle": "Alat Massal",
"@whatsNewBatchToolsTitle": {"description": "What's New feature: Batch Tools title"},
"whatsNewBatchToolsDesc": "Berbagi multi-pilih, konversi massal ke MP3/Opus, dan perbarui metadata secara massal di seluruh perpustakaan.",
"@whatsNewBatchToolsDesc": {"description": "What's New feature: Batch Tools description"},
"whatsNewStreamingTip1": "Ketuk trek apa pun untuk langsung memutar",
"@whatsNewStreamingTip1": {"description": "What's New tip: streaming instant play"},
"whatsNewStreamingTip2": "Lirik tersinkron di pemutar layar penuh",
"@whatsNewStreamingTip2": {"description": "What's New tip: streaming synced lyrics"},
"whatsNewStreamingTip3": "Unduh trek langsung dari pemutar",
"@whatsNewStreamingTip3": {"description": "What's New tip: streaming download from player"},
"whatsNewSmartQueueTip1": "Antrean terisi otomatis dengan trek terkait",
"@whatsNewSmartQueueTip1": {"description": "What's New tip: smart queue auto-fill"},
"whatsNewSmartQueueTip2": "Temukan artis baru saat mendengarkan",
"@whatsNewSmartQueueTip2": {"description": "What's New tip: smart queue artist discovery"},
"whatsNewSmartQueueTip3": "Tak pernah kehabisan musik untuk diputar",
"@whatsNewSmartQueueTip3": {"description": "What's New tip: smart queue endless"},
"whatsNewDualModeTip1": "Beralih mode kapan saja di Pengaturan",
"@whatsNewDualModeTip1": {"description": "What's New tip: dual mode switch"},
"whatsNewDualModeTip2": "Tombol UI menyesuaikan dengan mode Anda",
"@whatsNewDualModeTip2": {"description": "What's New tip: dual mode adaptive UI"},
"whatsNewDualModeTip3": "Unduh untuk offline, streaming untuk putar langsung",
"@whatsNewDualModeTip3": {"description": "What's New tip: dual mode use cases"},
"whatsNewLibraryTip1": "Seret dan lepas untuk mengatur playlist",
"@whatsNewLibraryTip1": {"description": "What's New tip: library drag and drop"},
"whatsNewLibraryTip2": "Atur gambar sampul kustom untuk playlist",
"@whatsNewLibraryTip2": {"description": "What's New tip: library custom covers"},
"whatsNewLibraryTip3": "Pilih banyak trek untuk aksi massal",
"@whatsNewLibraryTip3": {"description": "What's New tip: library multi-select"},
"whatsNewPlayerTip1": "Seni sampul dengan efek paralaks",
"@whatsNewPlayerTip1": {"description": "What's New tip: player parallax"},
"whatsNewPlayerTip2": "Pemutaran tetap tersimpan saat restart",
"@whatsNewPlayerTip2": {"description": "What's New tip: player persistence"},
"whatsNewPlayerTip3": "Lirik tersinkron saat mendengarkan",
"@whatsNewPlayerTip3": {"description": "What's New tip: player lyrics"},
"whatsNewContextMenuTip1": "Tambahkan trek ke playlist mana pun langsung",
"@whatsNewContextMenuTip1": {"description": "What's New tip: context menu add to playlist"},
"whatsNewContextMenuTip2": "Bagikan atau konversi dengan satu ketukan",
"@whatsNewContextMenuTip2": {"description": "What's New tip: context menu share/convert"},
"whatsNewContextMenuTip3": "Perbarui metadata saat diperlukan",
"@whatsNewContextMenuTip3": {"description": "What's New tip: context menu re-enrich"},
"whatsNewBatchToolsTip1": "Bagikan banyak trek sekaligus",
"@whatsNewBatchToolsTip1": {"description": "What's New tip: batch share"},
"whatsNewBatchToolsTip2": "Konversi massal ke format MP3 atau Opus",
"@whatsNewBatchToolsTip2": {"description": "What's New tip: batch convert"},
"whatsNewBatchToolsTip3": "Perbarui metadata di seluruh perpustakaan",
"@whatsNewBatchToolsTip3": {"description": "What's New tip: batch re-enrich"},
"whatsNewPerformanceTip1": "Waktu startup aplikasi lebih cepat",
"@whatsNewPerformanceTip1": {"description": "What's New tip: performance startup"},
"whatsNewPerformanceTip2": "Penggunaan memori berkurang saat pemutaran",
"@whatsNewPerformanceTip2": {"description": "What's New tip: performance memory"},
"whatsNewPerformanceTip3": "Penyimpanan berbasis SQLite untuk keandalan",
"@whatsNewPerformanceTip3": {"description": "What's New tip: performance SQLite"},
"whatsNewReadyMessage": "Siap — nikmati SpotiFLAC yang baru!",
"@whatsNewReadyMessage": {"description": "Ready card message on last What's New page"},
"whatsNewGetStarted": "Ayo Mulai",
"@whatsNewGetStarted": {"description": "Button text to dismiss What's New screen"},
"whatsNewPageIndicator": "{current} dari {total}",
"@whatsNewPageIndicator": {
"description": "Page indicator text in What's New screen",
"placeholders": {
"current": {"type": "int"},
"total": {"type": "int"}
}
}
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "ダウンロードせずにトラックを即座にストリーミング",
"setupModeStreamingFeature2": "Smart Queueが自動的に新しい音楽を見つけます",
"setupModeStreamingFeature3": "再生コントロールで任意のトラックをオンデマンド再生",
"setupModeChangeableLater": "設定からいつでもモードを切り替えられます。",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "類似トラックを自動的に検出してキューに追加"
"setupModeChangeableLater": "設定からいつでもモードを切り替えられます。"
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "다운로드 없이 트랙을 즉시 스트리밍",
"setupModeStreamingFeature2": "Smart Queue가 자동으로 새로운 음악을 발견합니다",
"setupModeStreamingFeature3": "재생 컨트롤로 원하는 트랙을 온디맨드 재생",
"setupModeChangeableLater": "설정에서 언제든지 모드를 전환할 수 있습니다.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "유사한 트랙을 자동으로 검색하여 대기열에 추가"
"setupModeChangeableLater": "설정에서 언제든지 모드를 전환할 수 있습니다."
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "Stream nummers direct zonder te downloaden",
"setupModeStreamingFeature2": "Smart Queue ontdekt automatisch nieuwe muziek voor je",
"setupModeStreamingFeature3": "Speel elk nummer op aanvraag af met afspeelbediening",
"setupModeChangeableLater": "Je kunt op elk moment wisselen tussen modi in Instellingen.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "Ontdek automatisch vergelijkbare nummers en voeg ze toe aan je wachtrij"
"setupModeChangeableLater": "Je kunt op elk moment wisselen tussen modi in Instellingen."
}
+1 -3
View File
@@ -2576,7 +2576,5 @@
"setupModeStreamingFeature1": "Transmita faixas instantaneamente sem baixar",
"setupModeStreamingFeature2": "Smart Queue descobre automaticamente novas músicas para você",
"setupModeStreamingFeature3": "Reproduza qualquer faixa sob demanda com controles de reprodução",
"setupModeChangeableLater": "Você pode alternar entre os modos a qualquer momento nas Configurações.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "Descubra e adicione automaticamente faixas semelhantes à sua fila"
"setupModeChangeableLater": "Você pode alternar entre os modos a qualquer momento nas Configurações."
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "Transmita faixas instantaneamente sem transferir",
"setupModeStreamingFeature2": "Smart Queue descobre automaticamente novas músicas para si",
"setupModeStreamingFeature3": "Reproduza qualquer faixa a pedido com controlos de reprodução",
"setupModeChangeableLater": "Pode alternar entre modos a qualquer momento nas Definições.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "Descubra e adicione automaticamente faixas semelhantes à sua fila"
"setupModeChangeableLater": "Pode alternar entre modos a qualquer momento nas Definições."
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "Слушайте треки мгновенно без скачивания",
"setupModeStreamingFeature2": "Smart Queue автоматически подбирает новую музыку для вас",
"setupModeStreamingFeature3": "Воспроизводите любой трек по запросу с элементами управления",
"setupModeChangeableLater": "Вы можете переключаться между режимами в любое время в Настройках.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "Автоматически находите и добавляйте похожие треки в очередь воспроизведения"
"setupModeChangeableLater": "Вы можете переключаться между режимами в любое время в Настройках."
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "İndirmeden parçaları anında yayınlayın",
"setupModeStreamingFeature2": "Smart Queue sizin için otomatik olarak yeni müzik keşfeder",
"setupModeStreamingFeature3": "İstediğiniz parçayı oynatma kontrolleriyle çalın",
"setupModeChangeableLater": "Ayarlar'dan istediğiniz zaman modlar arasında geçiş yapabilirsiniz.",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "Sıranıza otomatik olarak benzer parçalar keşfedin ve ekleyin"
"setupModeChangeableLater": "Ayarlar'dan istediğiniz zaman modlar arasında geçiş yapabilirsiniz."
}
+1 -3
View File
@@ -2576,7 +2576,5 @@
"setupModeStreamingFeature1": "无需下载即可即时播放曲目",
"setupModeStreamingFeature2": "Smart Queue 自动为您发现新音乐",
"setupModeStreamingFeature3": "通过播放控件随时点播任意曲目",
"setupModeChangeableLater": "您可以随时在设置中切换模式。",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "自动发现并将相似曲目添加到您的队列中"
"setupModeChangeableLater": "您可以随时在设置中切换模式。"
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "无需下载即可即时播放曲目",
"setupModeStreamingFeature2": "Smart Queue 自动为您发现新音乐",
"setupModeStreamingFeature3": "通过播放控件随时点播任意曲目",
"setupModeChangeableLater": "您可以随时在设置中切换模式。",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "自动发现并将相似曲目添加到您的队列中"
"setupModeChangeableLater": "您可以随时在设置中切换模式。"
}
+1 -3
View File
@@ -3879,7 +3879,5 @@
"setupModeStreamingFeature1": "無需下載即可即時串流曲目",
"setupModeStreamingFeature2": "Smart Queue 自動為您探索新音樂",
"setupModeStreamingFeature3": "透過播放控制項隨時點播任意曲目",
"setupModeChangeableLater": "您可以隨時在設定中切換模式。",
"settingsSmartQueueTitle": "Smart Queue",
"settingsSmartQueueSubtitle": "自動探索並將相似曲目新增到您的佇列中"
"setupModeChangeableLater": "您可以隨時在設定中切換模式。"
}
-91
View File
@@ -1,91 +0,0 @@
import 'package:spotiflac_android/models/track.dart';
class PlaybackItem {
final String id;
final String title;
final String artist;
final String album;
final String coverUrl;
final String sourceUri;
final bool isLocal;
final String service;
final int durationMs;
// Stream quality metadata
final String format;
final int bitDepth;
final int sampleRate;
final int bitrate;
// Original track reference for queue operations
final Track? track;
const PlaybackItem({
required this.id,
required this.title,
required this.artist,
this.album = '',
this.coverUrl = '',
required this.sourceUri,
this.isLocal = false,
this.service = '',
this.durationMs = 0,
this.format = '',
this.bitDepth = 0,
this.sampleRate = 0,
this.bitrate = 0,
this.track,
});
PlaybackItem copyWith({
String? sourceUri,
String? service,
String? format,
int? bitDepth,
int? sampleRate,
int? bitrate,
}) {
return PlaybackItem(
id: id,
title: title,
artist: artist,
album: album,
coverUrl: coverUrl,
sourceUri: sourceUri ?? this.sourceUri,
isLocal: isLocal,
service: service ?? this.service,
durationMs: durationMs,
format: format ?? this.format,
bitDepth: bitDepth ?? this.bitDepth,
sampleRate: sampleRate ?? this.sampleRate,
bitrate: bitrate ?? this.bitrate,
track: track,
);
}
/// Human-readable quality label for UI display
String get qualityLabel {
final parts = <String>[];
if (format.isNotEmpty) {
parts.add(format.toUpperCase());
}
if (bitDepth > 0 && sampleRate > 0) {
final srKhz = sampleRate >= 1000
? '${(sampleRate / 1000).toStringAsFixed(sampleRate % 1000 == 0 ? 0 : 1)}kHz'
: '${sampleRate}Hz';
parts.add('$bitDepth-bit / $srKhz');
} else if (bitrate > 0) {
parts.add('${bitrate}kbps');
}
return parts.join(' ');
}
/// Whether this item has cover art that is a local file path
bool get hasLocalCover {
if (coverUrl.isEmpty) return false;
return !coverUrl.startsWith('http://') && !coverUrl.startsWith('https://');
}
}
+1 -14
View File
@@ -11,9 +11,6 @@ class AppSettings {
final String storageMode; // 'app' or 'saf'
final String downloadTreeUri; // SAF persistable tree URI
final bool autoFallback;
final bool autoSkipUnavailableTracks;
final String playerMode; // 'internal' or 'external'
final bool smartQueueEnabled; // Enable smart curated autoplay queue
final bool embedMetadata; // Master switch for metadata/cover/lyrics embedding
final bool embedLyrics;
final bool maxQualityCover;
@@ -92,9 +89,6 @@ class AppSettings {
this.storageMode = 'app',
this.downloadTreeUri = '',
this.autoFallback = true,
this.autoSkipUnavailableTracks = true,
this.playerMode = 'internal',
this.smartQueueEnabled = true,
this.embedMetadata = true,
this.embedLyrics = true,
this.maxQualityCover = true,
@@ -160,10 +154,7 @@ class AppSettings {
String? downloadDirectory,
String? storageMode,
String? downloadTreeUri,
bool? autoFallback,
bool? autoSkipUnavailableTracks,
String? playerMode,
bool? smartQueueEnabled,
bool? autoFallback,
bool? embedMetadata,
bool? embedLyrics,
bool? maxQualityCover,
@@ -223,10 +214,6 @@ class AppSettings {
storageMode: storageMode ?? this.storageMode,
downloadTreeUri: downloadTreeUri ?? this.downloadTreeUri,
autoFallback: autoFallback ?? this.autoFallback,
autoSkipUnavailableTracks:
autoSkipUnavailableTracks ?? this.autoSkipUnavailableTracks,
playerMode: playerMode ?? this.playerMode,
smartQueueEnabled: smartQueueEnabled ?? this.smartQueueEnabled,
embedMetadata: embedMetadata ?? this.embedMetadata,
embedLyrics: embedLyrics ?? this.embedLyrics,
maxQualityCover: maxQualityCover ?? this.maxQualityCover,
-6
View File
@@ -14,9 +14,6 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
storageMode: json['storageMode'] as String? ?? 'app',
downloadTreeUri: json['downloadTreeUri'] as String? ?? '',
autoFallback: json['autoFallback'] as bool? ?? true,
autoSkipUnavailableTracks: json['autoSkipUnavailableTracks'] as bool? ?? true,
playerMode: json['playerMode'] as String? ?? 'internal',
smartQueueEnabled: json['smartQueueEnabled'] as bool? ?? true,
embedMetadata: json['embedMetadata'] as bool? ?? true,
embedLyrics: json['embedLyrics'] as bool? ?? true,
maxQualityCover: json['maxQualityCover'] as bool? ?? true,
@@ -93,9 +90,6 @@ Map<String, dynamic> _$AppSettingsToJson(
'storageMode': instance.storageMode,
'downloadTreeUri': instance.downloadTreeUri,
'autoFallback': instance.autoFallback,
'autoSkipUnavailableTracks': instance.autoSkipUnavailableTracks,
'playerMode': instance.playerMode,
'smartQueueEnabled': instance.smartQueueEnabled,
'embedMetadata': instance.embedMetadata,
'embedLyrics': instance.embedLyrics,
'maxQualityCover': instance.maxQualityCover,
File diff suppressed because it is too large Load Diff
-16
View File
@@ -279,22 +279,6 @@ class SettingsNotifier extends Notifier<AppSettings> {
_saveSettings();
}
void setAutoSkipUnavailableTracks(bool enabled) {
state = state.copyWith(autoSkipUnavailableTracks: enabled);
_saveSettings();
}
void setPlayerMode(String mode) {
final normalized = mode == 'external' ? 'external' : 'internal';
state = state.copyWith(playerMode: normalized);
_saveSettings();
}
void setSmartQueueEnabled(bool enabled) {
state = state.copyWith(smartQueueEnabled: enabled);
_saveSettings();
}
void setEmbedLyrics(bool enabled) {
state = state.copyWith(embedLyrics: enabled);
_saveSettings();
+138 -53
View File
@@ -10,7 +10,6 @@ import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/download_queue_provider.dart';
import 'package:spotiflac_android/providers/library_collections_provider.dart';
import 'package:spotiflac_android/providers/local_library_provider.dart';
import 'package:spotiflac_android/providers/playback_provider.dart';
import 'package:spotiflac_android/services/library_database.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/services/cover_cache_manager.dart';
@@ -793,12 +792,11 @@ class _LibraryTracksFolderScreenState
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (widget.mode !=
LibraryTracksFolderMode.wishlist) ...[
_buildShuffleButton(entries),
const SizedBox(width: 12),
],
_buildPlayAllCenterButton(entries),
_buildHeaderActionPlaceholder(),
const SizedBox(width: 12),
_buildDownloadAllCenterButton(entries),
const SizedBox(width: 12),
_buildHeaderActionPlaceholder(),
],
),
],
@@ -831,35 +829,16 @@ class _LibraryTracksFolderScreenState
);
}
// Shuffle / Play buttons
// Header actions
Widget _buildShuffleButton(List<CollectionTrackEntry> entries) {
return Container(
width: 48,
height: 48,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withValues(alpha: 0.15),
border: Border.all(
color: Colors.white.withValues(alpha: 0.3),
width: 1,
),
),
child: IconButton(
onPressed: entries.isEmpty ? null : () => _shufflePlay(entries),
icon: const Icon(Icons.shuffle_rounded, size: 22, color: Colors.white),
tooltip: 'Shuffle Play',
padding: EdgeInsets.zero,
),
);
}
Widget _buildHeaderActionPlaceholder() => const SizedBox(width: 48, height: 48);
Widget _buildPlayAllCenterButton(List<CollectionTrackEntry> entries) {
Widget _buildDownloadAllCenterButton(List<CollectionTrackEntry> entries) {
final tracks = entries.map((e) => e.track).toList(growable: false);
return FilledButton.icon(
onPressed: tracks.isEmpty ? null : () => _playAll(tracks),
icon: const Icon(Icons.play_arrow_rounded, size: 18),
label: Text(context.l10n.playAllCount(tracks.length)),
onPressed: tracks.isEmpty ? null : () => _confirmDownloadAll(tracks),
icon: const Icon(Icons.download_rounded, size: 18),
label: Text(context.l10n.downloadAllCount(tracks.length)),
style: FilledButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black87,
@@ -869,28 +848,70 @@ class _LibraryTracksFolderScreenState
);
}
void _shufflePlay(List<CollectionTrackEntry> entries) {
final tracks = entries.map((e) => e.track).toList(growable: false);
void _confirmDownloadAll(List<Track> tracks) {
if (tracks.isEmpty) return;
final shuffled = [...tracks]..shuffle();
final messenger = ScaffoldMessenger.of(context);
ref.read(playbackProvider.notifier).playTrackList(shuffled).catchError((e) {
if (!mounted) return;
messenger.showSnackBar(
SnackBar(content: Text('Cannot shuffle play local tracks: $e')),
);
});
showDialog(
context: context,
builder: (dialogContext) {
final colorScheme = Theme.of(dialogContext).colorScheme;
return AlertDialog(
backgroundColor: colorScheme.surfaceContainerHigh,
title: const Text('Download All'),
content: Text('Download ${tracks.length} tracks?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(context.l10n.dialogCancel),
),
FilledButton(
onPressed: () {
Navigator.pop(dialogContext);
_downloadAll(tracks);
},
child: const Text('Download'),
),
],
);
},
);
}
void _playAll(List<Track> tracks) {
void _downloadAll(List<Track> tracks) {
if (tracks.isEmpty) return;
final messenger = ScaffoldMessenger.of(context);
ref.read(playbackProvider.notifier).playTrackList(tracks).catchError((e) {
if (!mounted) return;
messenger.showSnackBar(
SnackBar(content: Text('Cannot play local tracks: $e')),
final settings = ref.read(settingsProvider);
if (settings.askQualityBeforeDownload) {
DownloadServicePicker.show(
context,
trackName: '${tracks.length} tracks',
artistName: switch (widget.mode) {
LibraryTracksFolderMode.wishlist => context.l10n.collectionWishlist,
LibraryTracksFolderMode.loved => context.l10n.collectionLoved,
LibraryTracksFolderMode.playlist => context.l10n.collectionPlaylist,
},
onSelect: (quality, service) {
ref
.read(downloadQueueProvider.notifier)
.addMultipleToQueue(tracks, service, qualityOverride: quality);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
context.l10n.snackbarAddedTracksToQueue(tracks.length),
),
),
);
},
);
});
} else {
ref
.read(downloadQueueProvider.notifier)
.addMultipleToQueue(tracks, settings.defaultService);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.snackbarAddedTracksToQueue(tracks.length)),
),
);
}
}
void _showCoverOptionsSheet(BuildContext context, bool hasCustomCover) {
@@ -1000,6 +1021,32 @@ class _CollectionTrackTile extends ConsumerWidget {
final track = entry.track;
final colorScheme = Theme.of(context).colorScheme;
final effectiveCoverUrl = _resolveCoverUrl(track);
final isInHistory = ref.watch(
downloadHistoryProvider.select((state) {
if (state.isDownloaded(track.id)) return true;
final isrc = track.isrc?.trim();
if (isrc != null && isrc.isNotEmpty && state.getByIsrc(isrc) != null) {
return true;
}
return state.findByTrackAndArtist(track.name, track.artistName) != null;
}),
);
final showLocalLibraryIndicator = ref.watch(
settingsProvider.select(
(s) => s.localLibraryEnabled && s.localLibraryShowDuplicates,
),
);
final isInLocalLibrary = showLocalLibraryIndicator
? ref.watch(
localLibraryProvider.select(
(state) => state.existsInLibrary(
isrc: track.isrc,
trackName: track.name,
artistName: track.artistName,
),
),
)
: false;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
@@ -1059,10 +1106,48 @@ class _CollectionTrackTile extends ConsumerWidget {
],
),
title: Text(track.name, maxLines: 1, overflow: TextOverflow.ellipsis),
subtitle: Text(
track.artistName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
subtitle: Row(
children: [
Flexible(
child: Text(
track.artistName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (isInLocalLibrary || isInHistory) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: colorScheme.tertiaryContainer,
borderRadius: BorderRadius.circular(4),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.folder_outlined,
size: 10,
color: colorScheme.onTertiaryContainer,
),
const SizedBox(width: 3),
Text(
context.l10n.libraryInLibrary,
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w500,
color: colorScheme.onTertiaryContainer,
),
),
],
),
),
],
],
),
trailing: isSelectionMode
? null
+9 -21
View File
@@ -20,7 +20,6 @@ import 'package:spotiflac_android/services/shell_navigation_service.dart';
import 'package:spotiflac_android/services/share_intent_service.dart';
import 'package:spotiflac_android/services/update_checker.dart';
import 'package:spotiflac_android/widgets/update_dialog.dart';
import 'package:spotiflac_android/widgets/mini_player_bar.dart';
import 'package:spotiflac_android/utils/logger.dart';
final _log = AppLogger('MainShell');
@@ -375,7 +374,7 @@ class _MainShellState extends ConsumerState<MainShell> {
if (_lastBackPress != null &&
now.difference(_lastBackPress!) < const Duration(seconds: 2)) {
_log.i('Back: step 8 - double-tap exit');
SystemNavigator.pop();
unawaited(PlatformBridge.exitApp());
} else {
_log.i('Back: step 7 - first tap, showing exit snackbar');
_lastBackPress = now;
@@ -487,28 +486,17 @@ class _MainShellState extends ConsumerState<MainShell> {
});
}
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) {
return;
}
return BackButtonListener(
onBackButtonPressed: () async {
_handleBackPress();
return true;
},
child: Scaffold(
body: Column(
children: [
Expanded(
child: PageView(
controller: _pageController,
onPageChanged: _onPageChanged,
physics: const NeverScrollableScrollPhysics(),
children: tabs,
),
),
const MiniPlayerBar(),
],
body: PageView(
controller: _pageController,
onPageChanged: _onPageChanged,
physics: const NeverScrollableScrollPhysics(),
children: tabs,
),
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex.clamp(0, maxIndex),
+6
View File
@@ -149,6 +149,12 @@ class AboutPage extends StatelessWidget {
subtitle:
'Partner lyrics proxy for Apple Music and QQ Music sources',
onTap: () => _launchUrl('https://lyrics.paxsenix.org'),
showDivider: true,
),
_ContributorItem(
name: 'Ruubiiiii',
description: 'Provided Qobuz API for the project',
githubUsername: 'Ruubiiiii',
showDivider: false,
),
],
+36 -25
View File
@@ -166,19 +166,7 @@ class _RecentDonorsCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
const donorNames = [
'NinoBrown',
'@nino_sandzak',
'IMJ',
'J',
'Julian',
'matt_3050',
'Daniel',
'283Fabio',
'laflame',
'Elias el Autentico',
'Faylyne',
];
const donorNames = <String>[];
// Match SettingsGroup color logic
final cardColor = isDark
@@ -221,16 +209,39 @@ class _RecentDonorsCard extends StatelessWidget {
),
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: donorNames
.map(
(name) =>
_SupporterChip(name: name, colorScheme: colorScheme),
)
.toList(),
),
if (donorNames.isEmpty)
Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
children: [
Icon(
Icons.emoji_events_outlined,
size: 32,
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.4),
),
const SizedBox(height: 8),
Text(
'No supporters yet — be the first!',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
),
),
],
),
),
)
else
Wrap(
spacing: 8,
runSpacing: 8,
children: donorNames
.map(
(name) =>
_SupporterChip(name: name, colorScheme: colorScheme),
)
.toList(),
),
],
),
),
@@ -463,8 +474,8 @@ int _cr(String v) {
for (final c in v.codeUnits) { r = (r * 31 + c) & 0x7FFFFFFF; }
return r;
}
// Highlighted supporters (hashes of names): Julian, J, NinoBrown, @nino_sandzak, IMJ.
const _cv = {1825257268, 1035, 1497948283, 398058782, 996135};
// Highlighted supporters (hashes of names): none for now.
const _cv = <int>{};
class _SupporterChip extends StatelessWidget {
final String name;
@@ -152,40 +152,6 @@ class OptionsSettingsPage extends ConsumerWidget {
onChanged: (v) =>
ref.read(settingsProvider.notifier).setAutoFallback(v),
),
SettingsSwitchItem(
icon: Icons.skip_next_rounded,
title: context.l10n.optionsAutoSkipUnavailableTracks,
subtitle: settings.autoSkipUnavailableTracks
? context
.l10n
.optionsAutoSkipUnavailableTracksSubtitleOn
: context
.l10n
.optionsAutoSkipUnavailableTracksSubtitleOff,
value: settings.autoSkipUnavailableTracks,
onChanged: (v) => ref
.read(settingsProvider.notifier)
.setAutoSkipUnavailableTracks(v),
),
SettingsItem(
icon: Icons.headphones,
title: 'Music Player',
subtitle: _playerModeLabel(settings.playerMode),
onTap: () => _showPlayerModePicker(
context,
ref,
settings.playerMode,
),
),
SettingsSwitchItem(
icon: Icons.queue_music_rounded,
title: context.l10n.settingsSmartQueueTitle,
subtitle: context.l10n.settingsSmartQueueSubtitle,
value: settings.smartQueueEnabled,
onChanged: (v) => ref
.read(settingsProvider.notifier)
.setSmartQueueEnabled(v),
),
if (hasExtensions)
SettingsSwitchItem(
icon: Icons.extension,
@@ -328,74 +294,6 @@ class OptionsSettingsPage extends ConsumerWidget {
);
}
String _playerModeLabel(String mode) {
if (mode == 'external') {
return 'External app (Poweramp, etc.)';
}
return 'Internal player';
}
void _showPlayerModePicker(
BuildContext context,
WidgetRef ref,
String currentMode,
) {
showModalBottomSheet<void>(
context: context,
useRootNavigator: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
builder: (sheetContext) {
final colorScheme = Theme.of(sheetContext).colorScheme;
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 8),
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: colorScheme.outlineVariant,
borderRadius: BorderRadius.circular(999),
),
),
const SizedBox(height: 12),
ListTile(
leading: const Icon(Icons.play_circle_outline),
title: const Text('Internal Player'),
subtitle: const Text('Use built-in app playback and queue'),
trailing: currentMode == 'internal'
? Icon(Icons.check, color: colorScheme.primary)
: null,
onTap: () {
ref.read(settingsProvider.notifier).setPlayerMode('internal');
Navigator.pop(sheetContext);
},
),
ListTile(
leading: const Icon(Icons.open_in_new),
title: const Text('External Player'),
subtitle: const Text(
'Open songs with apps like Poweramp, Musicolet, etc.',
),
trailing: currentMode == 'external'
? Icon(Icons.check, color: colorScheme.primary)
: null,
onTap: () {
ref.read(settingsProvider.notifier).setPlayerMode('external');
Navigator.pop(sheetContext);
},
),
const SizedBox(height: 8),
],
),
);
},
);
}
void _showClearHistoryDialog(
BuildContext context,
WidgetRef ref,
+4
View File
@@ -142,6 +142,10 @@ class PlatformBridge {
});
}
static Future<void> exitApp() async {
await _channel.invokeMethod('exitApp');
}
static Future<void> initItemProgress(String itemId) async {
await _channel.invokeMethod('initItemProgress', {'item_id': itemId});
}
+15
View File
@@ -74,6 +74,13 @@ class UpdateChecker {
return null;
}
// Ignore releases from a different major version (e.g. v4.x when we
// rolled back to v3.x). Only offer updates within the same major line.
if (_majorVersion(latestVersion) != _majorVersion(AppInfo.version)) {
_log.i('Skipping update from different major version (current: ${AppInfo.version}, latest: $latestVersion)');
return null;
}
final body = releaseData['body'] as String? ?? 'No changelog available';
final htmlUrl = releaseData['html_url'] as String? ?? '${AppInfo.githubUrl}/releases';
final publishedAt = DateTime.tryParse(releaseData['published_at'] as String? ?? '') ?? DateTime.now();
@@ -118,6 +125,14 @@ class UpdateChecker {
}
}
static int _majorVersion(String version) {
try {
return int.parse(version.split('-').first.split('.').first);
} catch (_) {
return -1;
}
}
static bool _isNewerVersion(String latest, String current) {
try {
final latestBase = latest.split('-').first;
File diff suppressed because it is too large Load Diff
@@ -1,18 +1,10 @@
import 'dart:async';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/download_queue_provider.dart';
import 'package:spotiflac_android/providers/library_collections_provider.dart';
import 'package:spotiflac_android/providers/local_library_provider.dart';
import 'package:spotiflac_android/providers/playback_provider.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/services/cover_cache_manager.dart';
import 'package:spotiflac_android/utils/file_access.dart';
import 'package:spotiflac_android/widgets/download_service_picker.dart';
import 'package:spotiflac_android/widgets/playlist_picker_sheet.dart';
import 'package:spotiflac_android/utils/clickable_metadata.dart';
@@ -64,9 +56,6 @@ class _TrackOptionsSheet extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final settings = ref.watch(settingsProvider);
final rootContext = Navigator.of(context, rootNavigator: true).context;
final container = ProviderScope.containerOf(rootContext, listen: false);
final isLoved = ref.watch(
libraryCollectionsProvider.select((state) => state.isLoved(track)),
@@ -174,46 +163,6 @@ class _TrackOptionsSheet extends ConsumerWidget {
),
// Action items (matches _QualityOption style)
_OptionTile(
icon: Icons.download_rounded,
title: 'Download & Play',
onTap: () async {
Navigator.pop(context);
final playedLocal = await _playLocalIfAvailable(
container,
rootContext,
);
if (playedLocal) {
return;
}
if (!rootContext.mounted) {
return;
}
if (settings.askQualityBeforeDownload) {
DownloadServicePicker.show(
rootContext,
trackName: track.name,
artistName: track.artistName,
coverUrl: track.coverUrl,
onSelect: (quality, service) {
_enqueueDownloadAndAutoPlay(
container: container,
context: rootContext,
service: service,
quality: quality,
);
},
);
} else {
_enqueueDownloadAndAutoPlay(
container: container,
context: rootContext,
service: settings.defaultService,
);
}
},
),
_OptionTile(
icon: isLoved ? Icons.favorite : Icons.favorite_border,
iconColor: isLoved ? colorScheme.error : null,
@@ -282,138 +231,6 @@ class _TrackOptionsSheet extends ConsumerWidget {
),
);
}
Future<bool> _playLocalIfAvailable(
ProviderContainer container,
BuildContext context,
) async {
final localState = container.read(localLibraryProvider);
final historyState = container.read(downloadHistoryProvider);
final historyNotifier = container.read(downloadHistoryProvider.notifier);
try {
DownloadHistoryItem? historyItem = historyNotifier.getBySpotifyId(
track.id,
);
final isrc = track.isrc?.trim();
historyItem ??= (isrc != null && isrc.isNotEmpty)
? historyNotifier.getByIsrc(isrc)
: null;
historyItem ??= historyState.findByTrackAndArtist(
track.name,
track.artistName,
);
if (historyItem != null) {
final exists = await fileExists(historyItem.filePath);
if (exists) {
await container
.read(playbackProvider.notifier)
.playLocalPath(
path: historyItem.filePath,
title: track.name,
artist: track.artistName,
album: track.albumName,
coverUrl: track.coverUrl ?? '',
);
return true;
}
historyNotifier.removeFromHistory(historyItem.id);
}
var localItem = (isrc != null && isrc.isNotEmpty)
? localState.getByIsrc(isrc)
: null;
localItem ??= localState.findByTrackAndArtist(
track.name,
track.artistName,
);
if (localItem != null && await fileExists(localItem.filePath)) {
await container
.read(playbackProvider.notifier)
.playLocalPath(
path: localItem.filePath,
title: localItem.trackName,
artist: localItem.artistName,
album: localItem.albumName,
coverUrl: localItem.coverPath ?? track.coverUrl ?? '',
);
return true;
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.snackbarCannotOpenFile('$e'))),
);
}
return true;
}
return false;
}
void _enqueueDownloadAndAutoPlay({
required ProviderContainer container,
required BuildContext context,
required String service,
String? quality,
}) {
container
.read(downloadQueueProvider.notifier)
.addToQueue(track, service, qualityOverride: quality);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.snackbarAddedToQueue(track.name))),
);
}
unawaited(_waitForDownloadedFileAndPlay(container, context));
}
Future<void> _waitForDownloadedFileAndPlay(
ProviderContainer container,
BuildContext context,
) async {
const maxAttempts = 180; // up to ~3 minutes
for (var i = 0; i < maxAttempts; i++) {
final item = _findHistoryMatch(container);
if (item != null && await fileExists(item.filePath)) {
try {
await container
.read(playbackProvider.notifier)
.playLocalPath(
path: item.filePath,
title: track.name,
artist: track.artistName,
album: track.albumName,
coverUrl: track.coverUrl ?? '',
);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.snackbarCannotOpenFile('$e')),
),
);
}
}
return;
}
await Future.delayed(const Duration(seconds: 1));
}
}
DownloadHistoryItem? _findHistoryMatch(ProviderContainer container) {
final historyState = container.read(downloadHistoryProvider);
final historyNotifier = container.read(downloadHistoryProvider.notifier);
final isrc = track.isrc?.trim();
return historyNotifier.getBySpotifyId(track.id) ??
((isrc != null && isrc.isNotEmpty)
? historyNotifier.getByIsrc(isrc)
: null) ??
historyState.findByTrackAndArtist(track.name, track.artistName);
}
}
/// Styled like _QualityOption in download_service_picker.dart
-56
View File
@@ -49,38 +49,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.13.0"
audio_service:
dependency: "direct main"
description:
name: audio_service
sha256: cb122c7c2639d2a992421ef96b67948ad88c5221da3365ccef1031393a76e044
url: "https://pub.dev"
source: hosted
version: "0.18.18"
audio_service_platform_interface:
dependency: transitive
description:
name: audio_service_platform_interface
sha256: "6283782851f6c8b501b60904a32fc7199dc631172da0629d7301e66f672ab777"
url: "https://pub.dev"
source: hosted
version: "0.1.3"
audio_service_web:
dependency: transitive
description:
name: audio_service_web
sha256: b8ea9243201ee53383157fbccf13d5d2a866b5dda922ec19d866d1d5d70424df
url: "https://pub.dev"
source: hosted
version: "0.1.4"
audio_session:
dependency: "direct main"
description:
name: audio_session
sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
boolean_selector:
dependency: transitive
description:
@@ -613,30 +581,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.11.2"
just_audio:
dependency: "direct main"
description:
name: just_audio
sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908"
url: "https://pub.dev"
source: hosted
version: "0.10.5"
just_audio_platform_interface:
dependency: transitive
description:
name: just_audio_platform_interface
sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a"
url: "https://pub.dev"
source: hosted
version: "4.6.0"
just_audio_web:
dependency: transitive
description:
name: just_audio_web
sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663"
url: "https://pub.dev"
source: hosted
version: "0.4.16"
leak_tracker:
dependency: transitive
description:
+1 -4
View File
@@ -1,7 +1,7 @@
name: spotiflac_android
description: Download Spotify tracks in FLAC from Tidal, Qobuz & Amazon Music
publish_to: "none"
version: 4.0.1+102
version: 3.7.0+103
environment:
sdk: ^3.10.0
@@ -61,9 +61,6 @@ dependencies:
# Notifications
flutter_local_notifications: 20.0.0
just_audio: ^0.10.5
audio_session: ^0.2.2
audio_service: ^0.18.17
dev_dependencies:
flutter_test: