diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt index 497da4cf..93656a25 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt @@ -1941,13 +1941,6 @@ class MainActivity: FlutterFragmentActivity() { } result.success(null) } - "parseSpotifyUrl" -> { - val url = call.argument("url") ?: "" - val response = withContext(Dispatchers.IO) { - Gobackend.parseSpotifyURL(url) - } - result.success(response) - } "checkAvailability" -> { val spotifyId = call.argument("spotify_id") ?: "" val isrc = call.argument("isrc") ?: "" @@ -2711,13 +2704,6 @@ class MainActivity: FlutterFragmentActivity() { } result.success(response) } - "getSpotifyMetadataWithFallback" -> { - val url = call.argument("url") ?: "" - val response = withContext(Dispatchers.IO) { - Gobackend.getSpotifyMetadataWithDeezerFallback(url) - } - result.success(response) - } "checkAvailabilityFromDeezerID" -> { val deezerTrackId = call.argument("deezer_track_id") ?: "" val response = withContext(Dispatchers.IO) { diff --git a/go_backend/exports.go b/go_backend/exports.go index a5d17122..57b55f7d 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -13,25 +13,6 @@ import ( "github.com/dop251/goja" ) -func ParseSpotifyURL(url string) (string, error) { - parsed, err := parseSpotifyURI(url) - if err != nil { - return "", err - } - - result := map[string]string{ - "type": parsed.Type, - "id": parsed.ID, - } - - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil -} - func CheckAvailability(spotifyID, isrc string) (string, error) { client := NewSongLinkClient() availability, err := client.CheckTrackAvailability(spotifyID, isrc) @@ -1526,72 +1507,6 @@ func ConvertSpotifyToDeezer(resourceType, spotifyID string) (string, error) { return "", fmt.Errorf("Spotify to Deezer conversion only supported for tracks and albums. Please search by name for %s", resourceType) } -func GetSpotifyMetadataWithDeezerFallback(spotifyURL string) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - spotFetchData, apiErr := GetSpotifyDataWithAPI(ctx, spotifyURL, DefaultSpotFetchAPIBaseURL) - if apiErr == nil { - GoLog("[Fallback] Spotify metadata fetched via SpotFetch API\n") - jsonBytes, err := json.Marshal(spotFetchData) - if err != nil { - return "", err - } - return string(jsonBytes), nil - } - GoLog("[Fallback] SpotFetch API fallback failed: %v\n", apiErr) - - parsed, parseErr := parseSpotifyURI(spotifyURL) - if parseErr != nil { - return "", fmt.Errorf("SpotFetch fallback failed (%v) and URL parsing failed: %w", apiErr, parseErr) - } - - GoLog("[Fallback] Trying Deezer conversion fallback for %s...\n", parsed.Type) - - if parsed.Type == "track" || parsed.Type == "album" { - return ConvertSpotifyToDeezer(parsed.Type, parsed.ID) - } - - if parsed.Type == "artist" { - return "", fmt.Errorf("SpotFetch fallback failed (%v). Artist pages now require SpotFetch or a metadata extension such as spotify-web", apiErr) - } - - return "", fmt.Errorf("SpotFetch fallback failed (%v), and Deezer conversion is unavailable for playlists", apiErr) -} - -func shouldTrySpotFetchFallback(err error) bool { - if err == nil { - return false - } - if errors.Is(err, ErrNoSpotifyCredentials) { - return true - } - - errStr := strings.ToLower(err.Error()) - indicators := []string{ - "429", - "rate", - "limit", - "403", - "forbidden", - "401", - "unauthorized", - "timeout", - "connection", - "spotify error", - "access token", - "client token", - "eof", - } - - for _, indicator := range indicators { - if strings.Contains(errStr, indicator) { - return true - } - } - return false -} - func CheckAvailabilityFromDeezerID(deezerTrackID string) (string, error) { client := NewSongLinkClient() availability, err := client.CheckAvailabilityFromDeezer(deezerTrackID) diff --git a/go_backend/spotfetch_api.go b/go_backend/spotfetch_api.go deleted file mode 100644 index e12804a4..00000000 --- a/go_backend/spotfetch_api.go +++ /dev/null @@ -1,80 +0,0 @@ -package gobackend - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "time" -) - -const DefaultSpotFetchAPIBaseURL = "https://sp.afkarxyz.qzz.io/api" - -// GetSpotifyDataWithAPI fetches Spotify metadata through SpotFetch-compatible API. -// This is used as a fallback when direct Spotify API access is blocked/limited. -func GetSpotifyDataWithAPI(ctx context.Context, spotifyURL, apiBaseURL string) (interface{}, error) { - parsed, err := parseSpotifyURI(spotifyURL) - if err != nil { - return nil, fmt.Errorf("invalid Spotify URL: %w", err) - } - - base := strings.TrimSpace(apiBaseURL) - if base == "" { - base = DefaultSpotFetchAPIBaseURL - } - - endpoint := fmt.Sprintf("%s/%s/%s", strings.TrimSuffix(base, "/"), parsed.Type, parsed.ID) - req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) - if err != nil { - return nil, fmt.Errorf("failed to create SpotFetch API request: %w", err) - } - req.Header.Set("User-Agent", getRandomUserAgent()) - req.Header.Set("Accept", "application/json") - - client := NewHTTPClientWithTimeout(30 * time.Second) - resp, err := client.Do(req) - if err != nil { - return nil, fmt.Errorf("SpotFetch API request failed: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("SpotFetch API error: HTTP %d", resp.StatusCode) - } - - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read SpotFetch API response: %w", err) - } - - switch parsed.Type { - case "track": - var trackResp TrackResponse - if err := json.Unmarshal(bodyBytes, &trackResp); err != nil { - return nil, fmt.Errorf("failed to decode track response: %w", err) - } - return trackResp, nil - case "album": - var albumResp AlbumResponsePayload - if err := json.Unmarshal(bodyBytes, &albumResp); err != nil { - return nil, fmt.Errorf("failed to decode album response: %w", err) - } - return &albumResp, nil - case "playlist": - var playlistResp PlaylistResponsePayload - if err := json.Unmarshal(bodyBytes, &playlistResp); err != nil { - return nil, fmt.Errorf("failed to decode playlist response: %w", err) - } - return playlistResp, nil - case "artist": - var artistResp ArtistResponsePayload - if err := json.Unmarshal(bodyBytes, &artistResp); err != nil { - return nil, fmt.Errorf("failed to decode artist response: %w", err) - } - return &artistResp, nil - default: - return nil, fmt.Errorf("unsupported Spotify type: %s", parsed.Type) - } -} diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 740a9fd3..d2e74ea5 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -153,13 +153,6 @@ import Gobackend // Import Go framework var error: NSError? switch call.method { - case "parseSpotifyUrl": - let args = call.arguments as! [String: Any] - let url = args["url"] as! String - let response = GobackendParseSpotifyURL(url, &error) - if let error = error { throw error } - return response - case "checkAvailability": let args = call.arguments as! [String: Any] let spotifyId = args["spotify_id"] as! String @@ -469,13 +462,6 @@ import Gobackend // Import Go framework if let error = error { throw error } return response - case "getSpotifyMetadataWithFallback": - let args = call.arguments as! [String: Any] - let url = args["url"] as! String - let response = GobackendGetSpotifyMetadataWithDeezerFallback(url, &error) - if let error = error { throw error } - return response - case "checkAvailabilityFromDeezerID": let args = call.arguments as! [String: Any] let deezerTrackId = args["deezer_track_id"] as! String diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 64eee3fb..87228ea8 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -151,7 +151,7 @@ abstract class AppLocalizations { /// Bottom navigation - Extension store tab /// /// In en, this message translates to: - /// **'Store'** + /// **'Repo'** String get navStore; /// Home screen title @@ -163,7 +163,7 @@ abstract class AppLocalizations { /// Subtitle shown below search box /// /// In en, this message translates to: - /// **'Paste a Spotify link or search by name'** + /// **'Paste a supported URL or search by name'** String get homeSubtitle; /// Info text about supported URL types @@ -427,13 +427,13 @@ abstract class AppLocalizations { /// Show/hide store tab /// /// In en, this message translates to: - /// **'Extension Store'** + /// **'Extension Repo'** String get optionsExtensionStore; /// Subtitle for extension store toggle /// /// In en, this message translates to: - /// **'Show Store tab in navigation'** + /// **'Show Repo tab in navigation'** String get optionsExtensionStoreSubtitle; /// Auto update check toggle @@ -565,7 +565,7 @@ abstract class AppLocalizations { /// Store screen title /// /// In en, this message translates to: - /// **'Extension Store'** + /// **'Extension Repo'** String get storeTitle; /// Store search placeholder @@ -2365,7 +2365,7 @@ abstract class AppLocalizations { /// Error heading when the store cannot be loaded /// /// In en, this message translates to: - /// **'Failed to load store'** + /// **'Failed to load repository'** String get storeLoadError; /// Message when store has no extensions @@ -3613,7 +3613,7 @@ abstract class AppLocalizations { /// Tutorial extensions tip 1 /// /// In en, this message translates to: - /// **'Browse the Store tab to discover useful extensions'** + /// **'Browse the Repo tab to discover useful extensions'** String get tutorialExtensionsTip1; /// Tutorial extensions tip 2 diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 15888ae5..916db930 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1281,7 +1281,7 @@ class AppLocalizationsDe extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 210342d2..ea2cc6e6 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -21,13 +21,13 @@ class AppLocalizationsEn extends AppLocalizations { String get navSettings => 'Settings'; @override - String get navStore => 'Store'; + String get navStore => 'Repo'; @override String get homeTitle => 'Home'; @override - String get homeSubtitle => 'Paste a Spotify link or search by name'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => 'Supports: Track, Album, Playlist, Artist URLs'; @@ -170,10 +170,10 @@ class AppLocalizationsEn extends AppLocalizations { 'Parallel downloads may trigger rate limiting'; @override - String get optionsExtensionStore => 'Extension Store'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => 'Check for Updates'; @@ -250,7 +250,7 @@ class AppLocalizationsEn extends AppLocalizations { String get extensionsUninstall => 'Uninstall'; @override - String get storeTitle => 'Extension Store'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => 'Search extensions...'; @@ -1261,7 +1261,7 @@ class AppLocalizationsEn extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; @@ -1997,7 +1997,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index f39ba881..5ca7ef2b 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1261,7 +1261,7 @@ class AppLocalizationsEs extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; @@ -1997,7 +1997,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index f0e783a5..288273a2 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1263,7 +1263,7 @@ class AppLocalizationsFr extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index d36edd4b..6b20fe2e 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -1261,7 +1261,7 @@ class AppLocalizationsHi extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 45d363fa..b4b09433 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -21,13 +21,14 @@ class AppLocalizationsId extends AppLocalizations { String get navSettings => 'Pengaturan'; @override - String get navStore => 'Toko'; + String get navStore => 'Repo'; @override String get homeTitle => 'Beranda'; @override - String get homeSubtitle => 'Tempel link Spotify atau cari berdasarkan nama'; + String get homeSubtitle => + 'Tempel URL yang didukung atau cari berdasarkan nama'; @override String get homeSupports => 'Mendukung: URL Track, Album, Playlist, Artis'; @@ -173,10 +174,10 @@ class AppLocalizationsId extends AppLocalizations { 'Unduhan paralel dapat memicu pembatasan rate'; @override - String get optionsExtensionStore => 'Toko Ekstensi'; + String get optionsExtensionStore => 'Repo Ekstensi'; @override - String get optionsExtensionStoreSubtitle => 'Tampilkan tab Toko di navigasi'; + String get optionsExtensionStoreSubtitle => 'Tampilkan tab Repo di navigasi'; @override String get optionsCheckUpdates => 'Periksa Pembaruan'; @@ -252,7 +253,7 @@ class AppLocalizationsId extends AppLocalizations { String get extensionsUninstall => 'Copot'; @override - String get storeTitle => 'Toko Ekstensi'; + String get storeTitle => 'Repo Ekstensi'; @override String get storeSearch => 'Cari ekstensi...'; @@ -1267,7 +1268,7 @@ class AppLocalizationsId extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Gagal memuat repo'; @override String get storeEmptyNoExtensions => 'No extensions available'; @@ -2006,7 +2007,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Buka tab Repo untuk menemukan ekstensi yang berguna'; @override String get tutorialExtensionsTip2 => diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 3d64d7d0..48973518 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1255,7 +1255,7 @@ class AppLocalizationsJa extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 69036d66..4bc727cf 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1241,7 +1241,7 @@ class AppLocalizationsKo extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 1ee2d0b9..e91bec0b 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1261,7 +1261,7 @@ class AppLocalizationsNl extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 444a91e0..cc49bda1 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1261,7 +1261,7 @@ class AppLocalizationsPt extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; @@ -1997,7 +1997,7 @@ class AppLocalizationsPt extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index fcd6c96b..c987bfb4 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1282,7 +1282,7 @@ class AppLocalizationsRu extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index 42e95073..5d3eab8e 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -1267,7 +1267,7 @@ class AppLocalizationsTr extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 9b379784..69dd7d32 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1261,7 +1261,7 @@ class AppLocalizationsZh extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Failed to load store'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; @@ -1997,7 +1997,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 4107d8b9..337fbfa6 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Store", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Paste a Spotify link or search by name", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -211,11 +211,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Extension Store", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Show Store tab in navigation", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,7 +318,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Extension Store", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, @@ -1654,7 +1654,7 @@ "@storeNewRepoUrlLabel": { "description": "Label for the new repository URL field inside the dialog" }, - "storeLoadError": "Failed to load store", + "storeLoadError": "Failed to load repository", "@storeLoadError": { "description": "Error heading when the store cannot be loaded" }, @@ -2611,7 +2611,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 1cd89fba..714dc0ef 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Toko", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Tempel link Spotify atau cari berdasarkan nama", + "homeSubtitle": "Tempel URL yang didukung atau cari berdasarkan nama", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -211,11 +211,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Toko Ekstensi", + "optionsExtensionStore": "Repo Ekstensi", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Tampilkan tab Toko di navigasi", + "optionsExtensionStoreSubtitle": "Tampilkan tab Repo di navigasi", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,10 +318,14 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Toko Ekstensi", + "storeTitle": "Repo Ekstensi", "@storeTitle": { "description": "Store screen title" }, + "storeLoadError": "Gagal memuat repo", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, "storeSearch": "Cari ekstensi...", "@storeSearch": { "description": "Store search placeholder" @@ -2459,7 +2463,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Buka tab Repo untuk menemukan ekstensi yang berguna", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, diff --git a/lib/providers/track_provider.dart b/lib/providers/track_provider.dart index 20780c49..3f6a3343 100644 --- a/lib/providers/track_provider.dart +++ b/lib/providers/track_provider.dart @@ -538,90 +538,11 @@ class TrackNotifier extends Notifier { return; } - final isSpotifyUrl = - url.contains('open.spotify.com') || - url.contains('spotify.link') || - url.startsWith('spotify:'); - if (!isSpotifyUrl) { - state = TrackState( - isLoading: false, - error: 'url_not_recognized', - hasSearchText: state.hasSearchText, - ); - return; - } - - final parsed = await PlatformBridge.parseSpotifyUrl(url); - if (!_isRequestValid(requestId)) return; - - final type = parsed['type'] as String; - - Map metadata; - - try { - metadata = await PlatformBridge.getSpotifyMetadataWithFallback(url); - } catch (e) { - rethrow; - } - - if (!_isRequestValid(requestId)) return; - - if (type == 'track') { - final trackData = metadata['track'] as Map; - final track = _parseTrack(trackData); - state = TrackState( - tracks: [track], - isLoading: false, - coverUrl: track.coverUrl, - ); - } else if (type == 'album') { - final albumInfo = metadata['album_info'] as Map; - final trackList = metadata['track_list'] as List; - final tracks = trackList - .map((t) => _parseTrack(t as Map)) - .toList(); - state = TrackState( - tracks: tracks, - isLoading: false, - albumId: parsed['id'] as String?, - albumName: albumInfo['name'] as String?, - coverUrl: normalizeRemoteHttpUrl(albumInfo['images']?.toString()), - ); - _preWarmCacheForTracks(tracks); - } else if (type == 'playlist') { - final playlistInfo = metadata['playlist_info'] as Map; - final trackList = metadata['track_list'] as List; - final tracks = trackList - .map((t) => _parseTrack(t as Map)) - .toList(); - final owner = playlistInfo['owner'] as Map?; - final playlistName = - (playlistInfo['name'] ?? owner?['name']) as String?; - final coverUrl = normalizeRemoteHttpUrl( - (playlistInfo['images'] ?? owner?['images'])?.toString(), - ); - state = TrackState( - tracks: tracks, - isLoading: false, - playlistName: playlistName, - coverUrl: coverUrl, - ); - _preWarmCacheForTracks(tracks); - } else if (type == 'artist') { - final artistInfo = metadata['artist_info'] as Map; - final albumsList = metadata['albums'] as List; - final albums = albumsList - .map((a) => _parseArtistAlbum(a as Map)) - .toList(); - state = TrackState( - tracks: [], - isLoading: false, - artistId: artistInfo['id'] as String?, - artistName: artistInfo['name'] as String?, - coverUrl: normalizeRemoteHttpUrl(artistInfo['images']?.toString()), - artistAlbums: albums, - ); - } + state = TrackState( + isLoading: false, + error: 'url_not_recognized', + hasSearchText: state.hasSearchText, + ); } catch (e) { if (!_isRequestValid(requestId)) return; state = TrackState( diff --git a/lib/screens/album_screen.dart b/lib/screens/album_screen.dart index 888cc613..20bc79ac 100644 --- a/lib/screens/album_screen.dart +++ b/lib/screens/album_screen.dart @@ -174,42 +174,107 @@ class _AlbumScreenState extends ConsumerState { Future _fetchTracks() async { setState(() => _isLoading = true); try { - Map metadata; - if (widget.albumId.startsWith('deezer:')) { final deezerAlbumId = widget.albumId.replaceFirst('deezer:', ''); - metadata = await PlatformBridge.getDeezerMetadata( + final metadata = await PlatformBridge.getDeezerMetadata( 'album', deezerAlbumId, ); + final trackList = metadata['track_list'] as List; + final tracks = trackList + .map((t) => _parseTrack(t as Map)) + .toList(); + + final albumInfo = metadata['album_info'] as Map?; + final artistId = (albumInfo?['artist_id'] ?? albumInfo?['artistId']) + ?.toString(); + + _AlbumCache.set(widget.albumId, tracks); + + if (mounted) { + setState(() { + _tracks = tracks; + _artistId = artistId; + _isLoading = false; + }); + } + return; } else if (widget.albumId.startsWith('qobuz:')) { final qobuzAlbumId = widget.albumId.replaceFirst('qobuz:', ''); - metadata = await PlatformBridge.getQobuzMetadata('album', qobuzAlbumId); + final metadata = await PlatformBridge.getQobuzMetadata( + 'album', + qobuzAlbumId, + ); + final trackList = metadata['track_list'] as List; + final tracks = trackList + .map((t) => _parseTrack(t as Map)) + .toList(); + + final albumInfo = metadata['album_info'] as Map?; + final artistId = (albumInfo?['artist_id'] ?? albumInfo?['artistId']) + ?.toString(); + + _AlbumCache.set(widget.albumId, tracks); + + if (mounted) { + setState(() { + _tracks = tracks; + _artistId = artistId; + _isLoading = false; + }); + } + return; } else if (widget.albumId.startsWith('tidal:')) { final tidalAlbumId = widget.albumId.replaceFirst('tidal:', ''); - metadata = await PlatformBridge.getTidalMetadata('album', tidalAlbumId); + final metadata = await PlatformBridge.getTidalMetadata( + 'album', + tidalAlbumId, + ); + final trackList = metadata['track_list'] as List; + final tracks = trackList + .map((t) => _parseTrack(t as Map)) + .toList(); + + final albumInfo = metadata['album_info'] as Map?; + final artistId = (albumInfo?['artist_id'] ?? albumInfo?['artistId']) + ?.toString(); + + _AlbumCache.set(widget.albumId, tracks); + + if (mounted) { + setState(() { + _tracks = tracks; + _artistId = artistId; + _isLoading = false; + }); + } + return; } else { final url = 'https://open.spotify.com/album/${widget.albumId}'; - metadata = await PlatformBridge.getSpotifyMetadataWithFallback(url); - } + final result = await PlatformBridge.handleURLWithExtension(url); + if (result == null || result['tracks'] == null) { + throw StateError('Failed to load album metadata from extension'); + } - final trackList = metadata['track_list'] as List; - final tracks = trackList - .map((t) => _parseTrack(t as Map)) - .toList(); + final trackList = result['tracks'] as List; + final tracks = trackList + .map((t) => _parseTrack(t as Map)) + .toList(); - final albumInfo = metadata['album_info'] as Map?; - final artistId = (albumInfo?['artist_id'] ?? albumInfo?['artistId']) - ?.toString(); + final albumInfo = result['album'] as Map?; + final artistId = (albumInfo?['artist_id'] ?? albumInfo?['artistId']) + ?.toString(); - _AlbumCache.set(widget.albumId, tracks); + _AlbumCache.set(widget.albumId, tracks); - if (mounted) { - setState(() { - _tracks = tracks; - _artistId = artistId; - _isLoading = false; - }); + if (mounted) { + setState(() { + _tracks = tracks; + _artistId = artistId; + _isLoading = false; + }); + } + return; } } catch (e) { if (mounted) { diff --git a/lib/screens/artist_screen.dart b/lib/screens/artist_screen.dart index 1267b694..a1070de1 100644 --- a/lib/screens/artist_screen.dart +++ b/lib/screens/artist_screen.dart @@ -343,13 +343,7 @@ class _ArtistScreenState extends ConsumerState { headerImage = artistData['header_image'] as String?; listeners = artistData['listeners'] as int?; } else { - final metadata = await PlatformBridge.getSpotifyMetadataWithFallback( - url, - ); - final albumsList = metadata['albums'] as List; - albums = albumsList - .map((a) => _parseArtistAlbum(a as Map)) - .toList(); + throw StateError('Failed to load artist metadata from extension'); } } @@ -1105,15 +1099,6 @@ class _ArtistScreenState extends ConsumerState { .map((t) => _parseTrack(t as Map, album: album)) .toList(); } - - // Fallback to direct Spotify metadata - final metadata = await PlatformBridge.getSpotifyMetadataWithFallback(url); - if (metadata['tracks'] != null) { - final tracksList = metadata['tracks'] as List; - return tracksList - .map((t) => _parseTrack(t as Map, album: album)) - .toList(); - } } return []; } diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 5483bc7d..527c1475 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -2313,7 +2313,6 @@ class _HomeTabState extends ConsumerState error.contains('429') || error.toLowerCase().contains('rate limit') || error.toLowerCase().contains('too many requests'); - final isUrlNotRecognized = error == 'url_not_recognized'; if (isRateLimit) { @@ -3087,7 +3086,7 @@ class _HomeTabState extends ConsumerState final extState = ref.read(extensionProvider); if (!extState.isInitialized) { - return 'Paste Spotify URL or search...'; + return 'Paste supported URL or search...'; } if (searchProvider != null && searchProvider.isNotEmpty) { @@ -3108,7 +3107,7 @@ class _HomeTabState extends ConsumerState return 'Search with ${ext.displayName}...'; } } - return 'Paste Spotify URL or search...'; + return 'Paste supported URL or search...'; } Widget _buildSearchFilterBar( diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index 72a1e516..91cc568e 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -12,7 +12,7 @@ import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/providers/store_provider.dart'; import 'package:spotiflac_android/providers/track_provider.dart'; import 'package:spotiflac_android/screens/home_tab.dart'; -import 'package:spotiflac_android/screens/store_tab.dart'; +import 'package:spotiflac_android/screens/repo_tab.dart'; import 'package:spotiflac_android/screens/queue_tab.dart'; import 'package:spotiflac_android/screens/settings/settings_tab.dart'; import 'package:spotiflac_android/services/platform_bridge.dart'; @@ -44,8 +44,8 @@ class _MainShellState extends ConsumerState ShellNavigationService.homeTabNavigatorKey; final GlobalKey _libraryTabNavigatorKey = ShellNavigationService.libraryTabNavigatorKey; - final GlobalKey _storeTabNavigatorKey = - ShellNavigationService.storeTabNavigatorKey; + final GlobalKey _repoTabNavigatorKey = + ShellNavigationService.repoTabNavigatorKey; @override void initState() { @@ -58,7 +58,7 @@ class _MainShellState extends ConsumerState ); ShellNavigationService.syncState( currentTabIndex: _currentIndex, - showStoreTab: false, + showRepoTab: false, ); WidgetsBinding.instance.addPostFrameCallback((_) { _checkForUpdates(); @@ -268,7 +268,7 @@ class _MainShellState extends ConsumerState ); ShellNavigationService.syncState( currentTabIndex: _currentIndex, - showStoreTab: showStore, + showRepoTab: showStore, ); FocusManager.instance.primaryFocus?.unfocus(); // Jump directly when skipping intermediate tabs to avoid @@ -295,7 +295,7 @@ class _MainShellState extends ConsumerState ); ShellNavigationService.syncState( currentTabIndex: _currentIndex, - showStoreTab: showStore, + showRepoTab: showStore, ); FocusManager.instance.primaryFocus?.unfocus(); } @@ -414,7 +414,7 @@ class _MainShellState extends ConsumerState NavigatorState? _navigatorForTab(int index, bool showStore) { if (index == 0) return _homeTabNavigatorKey.currentState; if (index == 1) return _libraryTabNavigatorKey.currentState; - if (showStore && index == 2) return _storeTabNavigatorKey.currentState; + if (showStore && index == 2) return _repoTabNavigatorKey.currentState; return null; } @@ -428,9 +428,9 @@ class _MainShellState extends ConsumerState ); ShellNavigationService.syncState( currentTabIndex: _currentIndex, - showStoreTab: showStore, + showRepoTab: showStore, ); - final storeUpdatesCount = ref.watch( + final repoUpdatesCount = ref.watch( storeProvider.select((s) => s.updatesAvailableCount), ); @@ -447,9 +447,9 @@ class _MainShellState extends ConsumerState ), if (showStore) _TabNavigator( - key: const ValueKey('tab-store'), - navigatorKey: _storeTabNavigatorKey, - child: const StoreTab(), + key: const ValueKey('tab-repo'), + navigatorKey: _repoTabNavigatorKey, + child: const RepoTab(), ), const SettingsTab(), ]; @@ -485,20 +485,20 @@ class _MainShellState extends ConsumerState if (showStore) NavigationDestination( icon: AnimatedBadge( - count: storeUpdatesCount, + count: repoUpdatesCount, child: Badge( - isLabelVisible: storeUpdatesCount > 0, - label: Text('$storeUpdatesCount'), - child: const Icon(Icons.store_outlined), + isLabelVisible: repoUpdatesCount > 0, + label: Text('$repoUpdatesCount'), + child: const Icon(Icons.extension_outlined), ), ), - selectedIcon: SwingIcon( + selectedIcon: BouncingIcon( child: AnimatedBadge( - count: storeUpdatesCount, + count: repoUpdatesCount, child: Badge( - isLabelVisible: storeUpdatesCount > 0, - label: Text('$storeUpdatesCount'), - child: const Icon(Icons.store), + isLabelVisible: repoUpdatesCount > 0, + label: Text('$repoUpdatesCount'), + child: const Icon(Icons.extension), ), ), ), diff --git a/lib/screens/store_tab.dart b/lib/screens/repo_tab.dart similarity index 99% rename from lib/screens/store_tab.dart rename to lib/screens/repo_tab.dart index 8270bec3..feadbf5e 100644 --- a/lib/screens/store_tab.dart +++ b/lib/screens/repo_tab.dart @@ -8,14 +8,14 @@ import 'package:spotiflac_android/widgets/animation_utils.dart'; import 'package:spotiflac_android/screens/store/extension_details_screen.dart'; import 'package:spotiflac_android/utils/app_bar_layout.dart'; -class StoreTab extends ConsumerStatefulWidget { - const StoreTab({super.key}); +class RepoTab extends ConsumerStatefulWidget { + const RepoTab({super.key}); @override - ConsumerState createState() => _StoreTabState(); + ConsumerState createState() => _RepoTabState(); } -class _StoreTabState extends ConsumerState { +class _RepoTabState extends ConsumerState { final _searchController = TextEditingController(); final _repoUrlController = TextEditingController(); bool _isInitialized = false; @@ -323,7 +323,7 @@ class _StoreTabState extends ConsumerState { mainAxisSize: MainAxisSize.min, children: [ Icon( - Icons.store_outlined, + Icons.extension_outlined, size: 72, color: colorScheme.onSurfaceVariant, ), diff --git a/lib/screens/settings/options_settings_page.dart b/lib/screens/settings/options_settings_page.dart index 30fbb798..81f81f53 100644 --- a/lib/screens/settings/options_settings_page.dart +++ b/lib/screens/settings/options_settings_page.dart @@ -158,7 +158,7 @@ class OptionsSettingsPage extends ConsumerWidget { child: SettingsGroup( children: [ SettingsSwitchItem( - icon: Icons.store, + icon: Icons.extension, title: context.l10n.optionsExtensionStore, subtitle: context.l10n.optionsExtensionStoreSubtitle, value: settings.showExtensionStore, diff --git a/lib/screens/tutorial_screen.dart b/lib/screens/tutorial_screen.dart index a8d35705..f9156e99 100644 --- a/lib/screens/tutorial_screen.dart +++ b/lib/screens/tutorial_screen.dart @@ -185,7 +185,7 @@ class _TutorialScreenState extends ConsumerState { title: l10n.tutorialExtensionsTitle, description: l10n.tutorialExtensionsDesc, content: _buildFeatureList(context, [ - (Icons.storefront_rounded, l10n.tutorialExtensionsTip1), + (Icons.extension_rounded, l10n.tutorialExtensionsTip1), ( Icons.add_circle_outline_rounded, l10n.tutorialExtensionsTip2, diff --git a/lib/services/platform_bridge.dart b/lib/services/platform_bridge.dart index 0dfa2bcf..17f74a7c 100644 --- a/lib/services/platform_bridge.dart +++ b/lib/services/platform_bridge.dart @@ -20,12 +20,6 @@ class PlatformBridge { static bool get supportsExtensionSystem => Platform.isAndroid || Platform.isIOS; - static Future> parseSpotifyUrl(String url) async { - _log.d('parseSpotifyUrl: $url'); - final result = await _channel.invokeMethod('parseSpotifyUrl', {'url': url}); - return jsonDecode(result as String) as Map; - } - static Future> checkAvailability( String spotifyId, String isrc, @@ -654,16 +648,6 @@ class PlatformBridge { return jsonDecode(result as String) as Map; } - static Future> getSpotifyMetadataWithFallback( - String url, - ) async { - final result = await _channel.invokeMethod( - 'getSpotifyMetadataWithFallback', - {'url': url}, - ); - return jsonDecode(result as String) as Map; - } - static Future>> getGoLogs() async { final result = await _channel.invokeMethod('getLogs'); final logs = jsonDecode(result as String) as List; diff --git a/lib/services/shell_navigation_service.dart b/lib/services/shell_navigation_service.dart index 614628c4..4d79e152 100644 --- a/lib/services/shell_navigation_service.dart +++ b/lib/services/shell_navigation_service.dart @@ -5,25 +5,25 @@ class ShellNavigationService { GlobalKey(); static final GlobalKey libraryTabNavigatorKey = GlobalKey(); - static final GlobalKey storeTabNavigatorKey = + static final GlobalKey repoTabNavigatorKey = GlobalKey(); static int _currentTabIndex = 0; - static bool _showStoreTab = false; + static bool _showRepoTab = false; static void syncState({ required int currentTabIndex, - required bool showStoreTab, + required bool showRepoTab, }) { _currentTabIndex = currentTabIndex; - _showStoreTab = showStoreTab; + _showRepoTab = showRepoTab; } static NavigatorState? activeTabNavigator() { if (_currentTabIndex == 0) return homeTabNavigatorKey.currentState; if (_currentTabIndex == 1) return libraryTabNavigatorKey.currentState; - if (_showStoreTab && _currentTabIndex == 2) { - return storeTabNavigatorKey.currentState; + if (_showRepoTab && _currentTabIndex == 2) { + return repoTabNavigatorKey.currentState; } return null; }