mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-09 22:18:45 +02:00
refactor: route spotify URLs through extensions
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
+10
-6
@@ -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"
|
||||
},
|
||||
|
||||
@@ -538,90 +538,11 @@ class TrackNotifier extends Notifier<TrackState> {
|
||||
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<String, dynamic> metadata;
|
||||
|
||||
try {
|
||||
metadata = await PlatformBridge.getSpotifyMetadataWithFallback(url);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
|
||||
if (!_isRequestValid(requestId)) return;
|
||||
|
||||
if (type == 'track') {
|
||||
final trackData = metadata['track'] as Map<String, dynamic>;
|
||||
final track = _parseTrack(trackData);
|
||||
state = TrackState(
|
||||
tracks: [track],
|
||||
isLoading: false,
|
||||
coverUrl: track.coverUrl,
|
||||
);
|
||||
} else if (type == 'album') {
|
||||
final albumInfo = metadata['album_info'] as Map<String, dynamic>;
|
||||
final trackList = metadata['track_list'] as List<dynamic>;
|
||||
final tracks = trackList
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>))
|
||||
.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<String, dynamic>;
|
||||
final trackList = metadata['track_list'] as List<dynamic>;
|
||||
final tracks = trackList
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>))
|
||||
.toList();
|
||||
final owner = playlistInfo['owner'] as Map<String, dynamic>?;
|
||||
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<String, dynamic>;
|
||||
final albumsList = metadata['albums'] as List<dynamic>;
|
||||
final albums = albumsList
|
||||
.map((a) => _parseArtistAlbum(a as Map<String, dynamic>))
|
||||
.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(
|
||||
|
||||
@@ -174,42 +174,107 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
|
||||
Future<void> _fetchTracks() async {
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
Map<String, dynamic> 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<dynamic>;
|
||||
final tracks = trackList
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
final albumInfo = metadata['album_info'] as Map<String, dynamic>?;
|
||||
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<dynamic>;
|
||||
final tracks = trackList
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
final albumInfo = metadata['album_info'] as Map<String, dynamic>?;
|
||||
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<dynamic>;
|
||||
final tracks = trackList
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
final albumInfo = metadata['album_info'] as Map<String, dynamic>?;
|
||||
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<dynamic>;
|
||||
final tracks = trackList
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>))
|
||||
.toList();
|
||||
final trackList = result['tracks'] as List<dynamic>;
|
||||
final tracks = trackList
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
final albumInfo = metadata['album_info'] as Map<String, dynamic>?;
|
||||
final artistId = (albumInfo?['artist_id'] ?? albumInfo?['artistId'])
|
||||
?.toString();
|
||||
final albumInfo = result['album'] as Map<String, dynamic>?;
|
||||
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) {
|
||||
|
||||
@@ -343,13 +343,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
headerImage = artistData['header_image'] as String?;
|
||||
listeners = artistData['listeners'] as int?;
|
||||
} else {
|
||||
final metadata = await PlatformBridge.getSpotifyMetadataWithFallback(
|
||||
url,
|
||||
);
|
||||
final albumsList = metadata['albums'] as List<dynamic>;
|
||||
albums = albumsList
|
||||
.map((a) => _parseArtistAlbum(a as Map<String, dynamic>))
|
||||
.toList();
|
||||
throw StateError('Failed to load artist metadata from extension');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1105,15 +1099,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>, album: album))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Fallback to direct Spotify metadata
|
||||
final metadata = await PlatformBridge.getSpotifyMetadataWithFallback(url);
|
||||
if (metadata['tracks'] != null) {
|
||||
final tracksList = metadata['tracks'] as List<dynamic>;
|
||||
return tracksList
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>, album: album))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -2313,7 +2313,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
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<HomeTab>
|
||||
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<HomeTab>
|
||||
return 'Search with ${ext.displayName}...';
|
||||
}
|
||||
}
|
||||
return 'Paste Spotify URL or search...';
|
||||
return 'Paste supported URL or search...';
|
||||
}
|
||||
|
||||
Widget _buildSearchFilterBar(
|
||||
|
||||
+21
-21
@@ -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<MainShell>
|
||||
ShellNavigationService.homeTabNavigatorKey;
|
||||
final GlobalKey<NavigatorState> _libraryTabNavigatorKey =
|
||||
ShellNavigationService.libraryTabNavigatorKey;
|
||||
final GlobalKey<NavigatorState> _storeTabNavigatorKey =
|
||||
ShellNavigationService.storeTabNavigatorKey;
|
||||
final GlobalKey<NavigatorState> _repoTabNavigatorKey =
|
||||
ShellNavigationService.repoTabNavigatorKey;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -58,7 +58,7 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
);
|
||||
ShellNavigationService.syncState(
|
||||
currentTabIndex: _currentIndex,
|
||||
showStoreTab: false,
|
||||
showRepoTab: false,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkForUpdates();
|
||||
@@ -268,7 +268,7 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
);
|
||||
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<MainShell>
|
||||
);
|
||||
ShellNavigationService.syncState(
|
||||
currentTabIndex: _currentIndex,
|
||||
showStoreTab: showStore,
|
||||
showRepoTab: showStore,
|
||||
);
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
}
|
||||
@@ -414,7 +414,7 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
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<MainShell>
|
||||
);
|
||||
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<MainShell>
|
||||
),
|
||||
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<MainShell>
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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<StoreTab> createState() => _StoreTabState();
|
||||
ConsumerState<RepoTab> createState() => _RepoTabState();
|
||||
}
|
||||
|
||||
class _StoreTabState extends ConsumerState<StoreTab> {
|
||||
class _RepoTabState extends ConsumerState<RepoTab> {
|
||||
final _searchController = TextEditingController();
|
||||
final _repoUrlController = TextEditingController();
|
||||
bool _isInitialized = false;
|
||||
@@ -323,7 +323,7 @@ class _StoreTabState extends ConsumerState<StoreTab> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.store_outlined,
|
||||
Icons.extension_outlined,
|
||||
size: 72,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
@@ -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,
|
||||
|
||||
@@ -185,7 +185,7 @@ class _TutorialScreenState extends ConsumerState<TutorialScreen> {
|
||||
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,
|
||||
|
||||
@@ -20,12 +20,6 @@ class PlatformBridge {
|
||||
static bool get supportsExtensionSystem =>
|
||||
Platform.isAndroid || Platform.isIOS;
|
||||
|
||||
static Future<Map<String, dynamic>> parseSpotifyUrl(String url) async {
|
||||
_log.d('parseSpotifyUrl: $url');
|
||||
final result = await _channel.invokeMethod('parseSpotifyUrl', {'url': url});
|
||||
return jsonDecode(result as String) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> checkAvailability(
|
||||
String spotifyId,
|
||||
String isrc,
|
||||
@@ -654,16 +648,6 @@ class PlatformBridge {
|
||||
return jsonDecode(result as String) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getSpotifyMetadataWithFallback(
|
||||
String url,
|
||||
) async {
|
||||
final result = await _channel.invokeMethod(
|
||||
'getSpotifyMetadataWithFallback',
|
||||
{'url': url},
|
||||
);
|
||||
return jsonDecode(result as String) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> getGoLogs() async {
|
||||
final result = await _channel.invokeMethod('getLogs');
|
||||
final logs = jsonDecode(result as String) as List<dynamic>;
|
||||
|
||||
@@ -5,25 +5,25 @@ class ShellNavigationService {
|
||||
GlobalKey<NavigatorState>();
|
||||
static final GlobalKey<NavigatorState> libraryTabNavigatorKey =
|
||||
GlobalKey<NavigatorState>();
|
||||
static final GlobalKey<NavigatorState> storeTabNavigatorKey =
|
||||
static final GlobalKey<NavigatorState> repoTabNavigatorKey =
|
||||
GlobalKey<NavigatorState>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user