refactor: route spotify URLs through extensions

This commit is contained in:
zarzet
2026-03-29 16:35:16 +07:00
parent cd3e5b4b28
commit fc70a912bf
30 changed files with 181 additions and 415 deletions
+7 -7
View File
@@ -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
+1 -1
View File
@@ -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';
+7 -7
View File
@@ -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 =>
+2 -2
View File
@@ -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 =>
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+8 -7
View File
@@ -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 =>
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+2 -2
View File
@@ -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 =>
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+2 -2
View File
@@ -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 =>
+7 -7
View File
@@ -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
View File
@@ -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"
},
+5 -84
View File
@@ -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(
+86 -21
View File
@@ -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) {
+1 -16
View File
@@ -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 [];
}
+2 -3
View File
@@ -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
View File
@@ -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,
+1 -1
View File
@@ -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,
-16
View File
@@ -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>;
+6 -6
View File
@@ -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;
}