mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-21 15:36:50 +02:00
refactor: enable strict analysis options and fix type safety across codebase
Enable strict-casts, strict-inference, and strict-raw-types in analysis_options.yaml. Add custom_lint with riverpod_lint. Fix all resulting type warnings with explicit type parameters and safer casts. Also improves APK update checker to detect device ABIs for correct variant selection and fixes Deezer artist name parsing edge case.
This commit is contained in:
@@ -9,6 +9,19 @@
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- build/**
|
||||
- .dart_tool/**
|
||||
- lib/**/*.g.dart
|
||||
- lib/l10n/*.dart
|
||||
language:
|
||||
strict-casts: true
|
||||
strict-inference: true
|
||||
strict-raw-types: true
|
||||
plugins:
|
||||
- custom_lint
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
@@ -23,6 +36,13 @@ linter:
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
avoid_dynamic_calls: true
|
||||
cancel_subscriptions: true
|
||||
close_sinks: true
|
||||
|
||||
custom_lint:
|
||||
rules:
|
||||
- avoid_public_notifier_properties
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
||||
@@ -304,6 +304,7 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
".mp3" -> "audio/mpeg"
|
||||
".opus" -> "audio/ogg"
|
||||
".flac" -> "audio/flac"
|
||||
".lrc" -> "application/octet-stream"
|
||||
else -> "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,7 +510,7 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
|
||||
}
|
||||
|
||||
if ((c + 1) % _safRepairBatchSize == 0) {
|
||||
await Future.delayed(const Duration(milliseconds: 16));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 16));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -762,7 +762,7 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
|
||||
_historyLog.d('Added new history entry: ${mergedItem.trackName}');
|
||||
}
|
||||
|
||||
_db.upsert(mergedItem.toJson()).catchError((e) {
|
||||
_db.upsert(mergedItem.toJson()).catchError((Object e) {
|
||||
_historyLog.e('Failed to save to database: $e');
|
||||
});
|
||||
}
|
||||
@@ -771,7 +771,7 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
|
||||
state = state.copyWith(
|
||||
items: state.items.where((item) => item.id != id).toList(),
|
||||
);
|
||||
_db.deleteById(id).catchError((e) {
|
||||
_db.deleteById(id).catchError((Object e) {
|
||||
_historyLog.e('Failed to delete from database: $e');
|
||||
});
|
||||
}
|
||||
@@ -780,7 +780,7 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
|
||||
state = state.copyWith(
|
||||
items: state.items.where((item) => item.spotifyId != spotifyId).toList(),
|
||||
);
|
||||
_db.deleteBySpotifyId(spotifyId).catchError((e) {
|
||||
_db.deleteBySpotifyId(spotifyId).catchError((Object e) {
|
||||
_historyLog.e('Failed to delete from database: $e');
|
||||
});
|
||||
_historyLog.d('Removed item with spotifyId: $spotifyId');
|
||||
@@ -1081,7 +1081,7 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
|
||||
|
||||
void clearHistory() {
|
||||
state = DownloadHistoryState();
|
||||
_db.clearAll().catchError((e) {
|
||||
_db.clearAll().catchError((Object e) {
|
||||
_historyLog.e('Failed to clear database: $e');
|
||||
});
|
||||
}
|
||||
@@ -3602,7 +3602,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
_log.d('Queue is paused, waiting for active downloads...');
|
||||
await Future.any([
|
||||
Future.wait(activeDownloads.values),
|
||||
Future.delayed(_queueSchedulingInterval),
|
||||
Future<void>.delayed(_queueSchedulingInterval),
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
@@ -3647,10 +3647,10 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
if (activeDownloads.isNotEmpty) {
|
||||
await Future.any([
|
||||
Future.any(activeDownloads.values),
|
||||
Future.delayed(_queueSchedulingInterval),
|
||||
Future<void>.delayed(_queueSchedulingInterval),
|
||||
]);
|
||||
} else {
|
||||
await Future.delayed(_queueSchedulingInterval);
|
||||
await Future<void>.delayed(_queueSchedulingInterval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ class UserPlaylistCollection {
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
tracks: tracksRaw
|
||||
.whereType<Map>()
|
||||
.whereType<Map<Object?, Object?>>()
|
||||
.map(
|
||||
(e) => CollectionTrackEntry.fromJson(Map<String, dynamic>.from(e)),
|
||||
)
|
||||
@@ -233,19 +233,19 @@ class LibraryCollectionsState {
|
||||
|
||||
return LibraryCollectionsState(
|
||||
wishlist: wishlistRaw
|
||||
.whereType<Map>()
|
||||
.whereType<Map<Object?, Object?>>()
|
||||
.map(
|
||||
(e) => CollectionTrackEntry.fromJson(Map<String, dynamic>.from(e)),
|
||||
)
|
||||
.toList(growable: false),
|
||||
loved: lovedRaw
|
||||
.whereType<Map>()
|
||||
.whereType<Map<Object?, Object?>>()
|
||||
.map(
|
||||
(e) => CollectionTrackEntry.fromJson(Map<String, dynamic>.from(e)),
|
||||
)
|
||||
.toList(growable: false),
|
||||
playlists: playlistsRaw
|
||||
.whereType<Map>()
|
||||
.whereType<Map<Object?, Object?>>()
|
||||
.map(
|
||||
(e) =>
|
||||
UserPlaylistCollection.fromJson(Map<String, dynamic>.from(e)),
|
||||
|
||||
@@ -34,7 +34,9 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
final prefs = await _prefs;
|
||||
final json = prefs.getString(_settingsKey);
|
||||
if (json != null) {
|
||||
state = AppSettings.fromJson(jsonDecode(json));
|
||||
state = AppSettings.fromJson(
|
||||
Map<String, dynamic>.from(jsonDecode(json) as Map),
|
||||
);
|
||||
|
||||
await _runMigrations(prefs);
|
||||
await _normalizeIosDownloadDirectoryIfNeeded();
|
||||
@@ -52,7 +54,9 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
void _syncLyricsSettingsToBackend() {
|
||||
if (!PlatformBridge.supportsCoreBackend) return;
|
||||
|
||||
PlatformBridge.setLyricsProviders(state.lyricsProviders).catchError((e) {
|
||||
PlatformBridge.setLyricsProviders(state.lyricsProviders).catchError((
|
||||
Object e,
|
||||
) {
|
||||
_log.w('Failed to sync lyrics providers to backend: $e');
|
||||
});
|
||||
|
||||
@@ -61,7 +65,7 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
'include_romanization_netease': state.lyricsIncludeRomanizationNetease,
|
||||
'multi_person_word_by_word': state.lyricsMultiPersonWordByWord,
|
||||
'musixmatch_language': state.musixmatchLanguage,
|
||||
}).catchError((e) {
|
||||
}).catchError((Object e) {
|
||||
_log.w('Failed to sync lyrics fetch options to backend: $e');
|
||||
});
|
||||
}
|
||||
@@ -73,7 +77,7 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
PlatformBridge.setNetworkCompatibilityOptions(
|
||||
allowHttp: compatibilityMode,
|
||||
insecureTls: compatibilityMode,
|
||||
).catchError((e) {
|
||||
).catchError((Object e) {
|
||||
_log.w('Failed to sync network compatibility options to backend: $e');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ class TrackNotifier extends Notifier<TrackState> {
|
||||
}
|
||||
|
||||
if (attempt < 3) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,10 +275,12 @@ class TrackNotifier extends Notifier<TrackState> {
|
||||
state = TrackState(
|
||||
tracks: tracks,
|
||||
isLoading: false,
|
||||
albumId: result['album']?['id'] as String?,
|
||||
albumId:
|
||||
(result['album'] as Map<String, dynamic>?)?['id'] as String?,
|
||||
albumName:
|
||||
result['name'] as String? ??
|
||||
result['album']?['name'] as String?,
|
||||
(result['album'] as Map<String, dynamic>?)?['name']
|
||||
as String?,
|
||||
playlistName: type == 'playlist'
|
||||
? result['name'] as String?
|
||||
: null,
|
||||
@@ -825,8 +827,7 @@ class TrackNotifier extends Notifier<TrackState> {
|
||||
isLoading: true,
|
||||
hasSearchText: state.hasSearchText,
|
||||
isShowingRecentAccess: state.isShowingRecentAccess,
|
||||
selectedSearchFilter:
|
||||
state.selectedSearchFilter,
|
||||
selectedSearchFilter: state.selectedSearchFilter,
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -921,8 +922,7 @@ class TrackNotifier extends Notifier<TrackState> {
|
||||
final tracks = List<Track>.from(state.tracks);
|
||||
tracks[index] = updatedTrack;
|
||||
state = state.copyWith(tracks: tracks);
|
||||
} catch (_) {
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
|
||||
@@ -805,7 +805,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
);
|
||||
final singleTracks = singles.fold<int>(0, (sum, a) => sum + a.totalTracks);
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -939,7 +939,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) => _FetchingProgressDialog(
|
||||
@@ -1121,6 +1121,10 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
Track _parseTrackFromDeezer(Map<String, dynamic> data, ArtistAlbum album) {
|
||||
int durationMs = 0;
|
||||
final durationValue = data['duration'];
|
||||
final artistData = data['artist'];
|
||||
final artistName = artistData is Map<String, dynamic>
|
||||
? (artistData['name'] as String? ?? widget.artistName)
|
||||
: (artistData?.toString() ?? widget.artistName);
|
||||
if (durationValue is int) {
|
||||
durationMs = durationValue * 1000; // Deezer returns seconds
|
||||
} else if (durationValue is double) {
|
||||
@@ -1130,9 +1134,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
return Track(
|
||||
id: 'deezer:${data['id']}',
|
||||
name: (data['title'] ?? data['name'] ?? '').toString(),
|
||||
artistName:
|
||||
(data['artist']?['name'] ?? data['artist'] ?? widget.artistName)
|
||||
.toString(),
|
||||
artistName: artistName,
|
||||
albumName: album.name,
|
||||
albumArtist: widget.artistName,
|
||||
artistId: widget.artistId,
|
||||
@@ -1938,7 +1940,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
if (album.providerId != null && album.providerId!.isNotEmpty) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionAlbumScreen(
|
||||
extensionId: album.providerId!,
|
||||
albumId: album.id,
|
||||
@@ -1950,7 +1952,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => AlbumScreen(
|
||||
albumId: album.id,
|
||||
albumName: album.name,
|
||||
|
||||
@@ -309,7 +309,7 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
|
||||
if (!mounted) return;
|
||||
|
||||
final result = await navigator.push(
|
||||
slidePageRoute(page: TrackMetadataScreen(item: item)),
|
||||
slidePageRoute<bool>(page: TrackMetadataScreen(item: item)),
|
||||
);
|
||||
await DownloadedEmbeddedCoverResolver.scheduleRefreshForPath(
|
||||
item.filePath,
|
||||
@@ -932,7 +932,7 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
|
||||
? '320k'
|
||||
: (selectedFormat == 'Opus' ? '128k' : '320k');
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
|
||||
+24
-24
@@ -556,7 +556,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
pending != query &&
|
||||
mounted &&
|
||||
_urlController.text.trim() == pending) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||
if (mounted && _urlController.text.trim() == pending) {
|
||||
_executeLiveSearch(pending);
|
||||
}
|
||||
@@ -681,7 +681,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
final extensionId = trackState.searchExtensionId;
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => AlbumScreen(
|
||||
albumId: trackState.albumId!,
|
||||
albumName: trackState.albumName!,
|
||||
@@ -708,7 +708,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => PlaylistScreen(
|
||||
playlistName: trackState.playlistName!,
|
||||
coverUrl: trackState.coverUrl,
|
||||
@@ -729,7 +729,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
final extensionId = trackState.searchExtensionId;
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ArtistScreen(
|
||||
artistId: trackState.artistId!,
|
||||
artistName: trackState.artistName!,
|
||||
@@ -798,7 +798,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
if (progressDialogInitialized || !mounted) return;
|
||||
progressDialogInitialized = true;
|
||||
progressDialogVisible = true;
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: this.context,
|
||||
useRootNavigator: false,
|
||||
barrierDismissible: false,
|
||||
@@ -1691,7 +1691,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
case 'album':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionAlbumScreen(
|
||||
extensionId: extensionId,
|
||||
albumId: item.id,
|
||||
@@ -1704,7 +1704,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
case 'playlist':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionPlaylistScreen(
|
||||
extensionId: extensionId,
|
||||
playlistId: item.id,
|
||||
@@ -1717,7 +1717,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
case 'artist':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionArtistScreen(
|
||||
extensionId: extensionId,
|
||||
artistId: item.id,
|
||||
@@ -1738,7 +1738,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
void _showTrackBottomSheet(ExploreItem item) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surface,
|
||||
@@ -1884,7 +1884,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
if (item.albumId != null && item.albumId!.isNotEmpty) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionAlbumScreen(
|
||||
extensionId: item.providerId ?? 'spotify-web',
|
||||
albumId: item.albumId!,
|
||||
@@ -2148,7 +2148,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
item.providerId != 'qobuz') {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionArtistScreen(
|
||||
extensionId: item.providerId!,
|
||||
artistId: item.id,
|
||||
@@ -2160,7 +2160,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ArtistScreen(
|
||||
artistId: item.id,
|
||||
artistName: item.name,
|
||||
@@ -2174,7 +2174,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
if (item.providerId == 'download') {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => DownloadedAlbumScreen(
|
||||
albumName: item.name,
|
||||
artistName: item.subtitle ?? '',
|
||||
@@ -2190,7 +2190,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
item.providerId != 'qobuz') {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionAlbumScreen(
|
||||
extensionId: item.providerId!,
|
||||
albumId: item.id,
|
||||
@@ -2202,7 +2202,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => AlbumScreen(
|
||||
albumId: item.id,
|
||||
albumName: item.name,
|
||||
@@ -2240,7 +2240,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
item.providerId != 'qobuz') {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionPlaylistScreen(
|
||||
extensionId: item.providerId!,
|
||||
playlistId: item.id,
|
||||
@@ -2252,7 +2252,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => PlaylistScreen(
|
||||
playlistName: item.name,
|
||||
coverUrl: item.imageUrl,
|
||||
@@ -2275,7 +2275,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
);
|
||||
if (!mounted) return;
|
||||
final result = await navigator.push(
|
||||
slidePageRoute(page: TrackMetadataScreen(item: item)),
|
||||
slidePageRoute<bool>(page: TrackMetadataScreen(item: item)),
|
||||
);
|
||||
await DownloadedEmbeddedCoverResolver.scheduleRefreshForPath(
|
||||
item.filePath,
|
||||
@@ -2910,7 +2910,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ArtistScreen(
|
||||
artistId: artistId,
|
||||
artistName: artistName,
|
||||
@@ -2936,7 +2936,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
// Keep the full ID with prefix (e.g., "deezer:123") for AlbumScreen to detect source
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => AlbumScreen(
|
||||
albumId: album.id,
|
||||
albumName: album.name,
|
||||
@@ -2963,7 +2963,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
// Keep the full ID with prefix (e.g., "deezer:123") for PlaylistScreen to detect source
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => PlaylistScreen(
|
||||
playlistName: playlist.name,
|
||||
coverUrl: playlist.imageUrl,
|
||||
@@ -2999,7 +2999,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionAlbumScreen(
|
||||
extensionId: extensionId,
|
||||
albumId: albumItem.id,
|
||||
@@ -3035,7 +3035,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionPlaylistScreen(
|
||||
extensionId: extensionId,
|
||||
playlistId: playlistItem.id,
|
||||
@@ -3070,7 +3070,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionArtistScreen(
|
||||
extensionId: extensionId,
|
||||
artistId: artistItem.id,
|
||||
|
||||
@@ -119,7 +119,7 @@ class LibraryPlaylistsScreen extends ConsumerWidget {
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => LibraryTracksFolderScreen(
|
||||
mode: LibraryTracksFolderMode.playlist,
|
||||
playlistId: playlist.id,
|
||||
@@ -149,7 +149,7 @@ class LibraryPlaylistsScreen extends ConsumerWidget {
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
|
||||
@@ -847,7 +847,7 @@ class _LibraryTracksFolderScreenState
|
||||
|
||||
void _confirmDownloadAll(List<Track> tracks) {
|
||||
if (tracks.isEmpty) return;
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
final colorScheme = Theme.of(dialogContext).colorScheme;
|
||||
@@ -980,7 +980,7 @@ class _LibraryTracksFolderScreenState
|
||||
void _showCoverOptionsSheet(BuildContext context, bool hasCustomCover) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -1338,7 +1338,7 @@ class _CollectionTrackTile extends ConsumerWidget {
|
||||
final showAddToPlaylist =
|
||||
mode != LibraryTracksFolderMode.wishlist || isDownloaded;
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -1523,9 +1523,9 @@ class _CollectionTrackTile extends ConsumerWidget {
|
||||
);
|
||||
|
||||
if (historyItem != null) {
|
||||
await Navigator.of(
|
||||
context,
|
||||
).push(slidePageRoute(page: TrackMetadataScreen(item: historyItem)));
|
||||
await Navigator.of(context).push(
|
||||
slidePageRoute<void>(page: TrackMetadataScreen(item: historyItem)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1540,9 +1540,9 @@ class _CollectionTrackTile extends ConsumerWidget {
|
||||
localItem ??= localState.findByTrackAndArtist(track.name, track.artistName);
|
||||
|
||||
if (localItem != null) {
|
||||
await Navigator.of(
|
||||
context,
|
||||
).push(slidePageRoute(page: TrackMetadataScreen(localItem: localItem)));
|
||||
await Navigator.of(context).push(
|
||||
slidePageRoute<void>(page: TrackMetadataScreen(localItem: localItem)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1180,7 +1180,7 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
? '320k'
|
||||
: (selectedFormat == 'Opus' ? '128k' : '320k');
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
|
||||
@@ -79,7 +79,7 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
_log.d('Received shared URL from stream: $url');
|
||||
_handleSharedUrl(url);
|
||||
},
|
||||
onError: (error) {
|
||||
onError: (Object error) {
|
||||
_log.e('Share stream error: $error');
|
||||
},
|
||||
cancelOnError: false,
|
||||
@@ -92,7 +92,7 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
if (!extState.isInitialized) {
|
||||
_log.d('Waiting for extensions to initialize before handling URL...');
|
||||
for (int i = 0; i < 50; i++) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||
if (!mounted) return;
|
||||
if (ref.read(extensionProvider).isInitialized) {
|
||||
_log.d('Extensions initialized, proceeding with URL handling');
|
||||
@@ -177,7 +177,7 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) => AlertDialog(
|
||||
|
||||
@@ -578,7 +578,7 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
|
||||
|
||||
void _confirmDownloadAll(BuildContext context) {
|
||||
if (_tracks.isEmpty) return;
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
final colorScheme = Theme.of(dialogContext).colorScheme;
|
||||
|
||||
@@ -656,8 +656,8 @@ final _queueFilteredAlbumsProvider =
|
||||
});
|
||||
|
||||
Map<String, List<String>> _filterHistoryInIsolate(Map<String, Object> payload) {
|
||||
final entries = (payload['entries'] as List).cast<List>();
|
||||
final albumCounts = (payload['albumCounts'] as Map).cast<String, int>();
|
||||
final entries = (payload['entries'] as List).cast<List<Object?>>();
|
||||
final albumCounts = Map<String, int>.from(payload['albumCounts'] as Map);
|
||||
final query = (payload['query'] as String?) ?? '';
|
||||
final hasQuery = query.isNotEmpty;
|
||||
|
||||
@@ -1968,7 +1968,7 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
String? tempFormat = _filterFormat;
|
||||
String tempSortMode = _sortMode;
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
@@ -2280,7 +2280,7 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
final beforeModTime = await _readFileModTimeMillis(historyItem.filePath);
|
||||
if (!mounted) return;
|
||||
final result = await navigator.push(
|
||||
slidePageRoute(page: TrackMetadataScreen(item: historyItem)),
|
||||
slidePageRoute<bool>(page: TrackMetadataScreen(item: historyItem)),
|
||||
);
|
||||
_searchFocusNode.unfocus();
|
||||
if (result == true) {
|
||||
@@ -2306,7 +2306,7 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
final beforeModTime = await _readFileModTimeMillis(item.filePath);
|
||||
if (!mounted) return;
|
||||
final result = await navigator.push(
|
||||
slidePageRoute(page: TrackMetadataScreen(item: item)),
|
||||
slidePageRoute<bool>(page: TrackMetadataScreen(item: item)),
|
||||
);
|
||||
_searchFocusNode.unfocus();
|
||||
if (result == true) {
|
||||
@@ -2327,7 +2327,7 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
_searchFocusNode.unfocus();
|
||||
Navigator.push(
|
||||
context,
|
||||
slidePageRoute(page: TrackMetadataScreen(localItem: item)),
|
||||
slidePageRoute<void>(page: TrackMetadataScreen(localItem: item)),
|
||||
).then((_) => _searchFocusNode.unfocus());
|
||||
}
|
||||
|
||||
@@ -4711,7 +4711,7 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
_hideSelectionOverlay();
|
||||
_hidePlaylistSelectionOverlay();
|
||||
|
||||
await showModalBottomSheet(
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
|
||||
@@ -770,7 +770,7 @@ class _LanguageSelector extends StatelessWidget {
|
||||
|
||||
void _showLanguagePicker(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surface,
|
||||
|
||||
@@ -510,7 +510,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
),
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => const LyricsProviderPriorityPage(),
|
||||
),
|
||||
),
|
||||
@@ -853,7 +853,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
WidgetRef ref,
|
||||
String current,
|
||||
) {
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => SafeArea(
|
||||
@@ -1002,7 +1002,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
@@ -1220,7 +1220,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
final settings = ref.read(settingsProvider);
|
||||
final isSafMode =
|
||||
settings.storageMode == 'saf' && settings.downloadTreeUri.isNotEmpty;
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -1298,7 +1298,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
|
||||
void _showIOSDirectoryOptions(BuildContext context, WidgetRef ref) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -1493,7 +1493,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
String current,
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -1598,7 +1598,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
String current,
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -1685,7 +1685,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final controller = TextEditingController(text: currentLanguage);
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -1771,7 +1771,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
String current,
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -1843,7 +1843,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final normalizedCurrent = current.trim().toUpperCase();
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -1911,7 +1911,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
String current,
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
|
||||
@@ -832,9 +832,9 @@ class _SettingItemState extends State<_SettingItem> {
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(context.l10n.snackbarError(e.toString()))));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.snackbarError(e.toString()))),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
@@ -849,7 +849,7 @@ class _SettingItemState extends State<_SettingItem> {
|
||||
);
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(widget.setting.label),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/models/settings.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/providers/explore_provider.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
@@ -212,7 +213,7 @@ class _ExtensionsPageState extends ConsumerState<ExtensionsPage> {
|
||||
showDivider: index < extState.extensions.length - 1,
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) =>
|
||||
ExtensionDetailPage(extensionId: ext.id),
|
||||
),
|
||||
@@ -469,7 +470,9 @@ class _DownloadPriorityItem extends ConsumerWidget {
|
||||
onTap: hasDownloadExtensions
|
||||
? () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const ProviderPriorityPage()),
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => const ProviderPriorityPage(),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Padding(
|
||||
@@ -534,7 +537,7 @@ class _MetadataPriorityItem extends ConsumerWidget {
|
||||
onTap: hasMetadataExtensions
|
||||
? () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => const MetadataProviderPriorityPage(),
|
||||
),
|
||||
)
|
||||
@@ -678,12 +681,12 @@ class _SearchProviderSelector extends ConsumerWidget {
|
||||
void _showSearchProviderPicker(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
dynamic settings,
|
||||
AppSettings settings,
|
||||
List<Extension> searchProviders,
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
@@ -859,12 +862,12 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
void _showHomeFeedProviderPicker(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
dynamic settings,
|
||||
AppSettings settings,
|
||||
List<Extension> homeFeedProviders,
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
|
||||
@@ -255,7 +255,7 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
|
||||
|
||||
void _showAutoScanPicker(BuildContext context, String current) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
|
||||
@@ -92,7 +92,7 @@ class _LogScreenState extends State<LogScreen> {
|
||||
}
|
||||
|
||||
void _clearLogs() {
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.l10n.logClearLogsTitle),
|
||||
|
||||
@@ -241,7 +241,7 @@ class OptionsSettingsPage extends ConsumerWidget {
|
||||
WidgetRef ref,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.l10n.dialogClearHistoryTitle),
|
||||
@@ -273,7 +273,7 @@ class OptionsSettingsPage extends ConsumerWidget {
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
) async {
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
|
||||
@@ -151,6 +151,6 @@ class SettingsTab extends ConsumerWidget {
|
||||
|
||||
void _navigateTo(BuildContext context, Widget page) {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
Navigator.of(context).push(slidePageRoute(page: page));
|
||||
Navigator.of(context).push(slidePageRoute<void>(page: page));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
final shouldOpen = await _showAndroid11StorageDialog();
|
||||
if (shouldOpen == true) {
|
||||
await Permission.manageExternalStorage.request();
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
manageStatus = await Permission.manageExternalStorage.status;
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
}
|
||||
|
||||
Future<void> _showPermissionDeniedDialog(String permissionType) async {
|
||||
await showDialog(
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.l10n.setupPermissionRequired(permissionType)),
|
||||
@@ -286,7 +286,7 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
|
||||
Future<void> _showIOSDirectoryOptions() async {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
await showModalBottomSheet(
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
|
||||
@@ -416,7 +416,7 @@ class _StoreTabState extends ConsumerState<StoreTab> {
|
||||
|
||||
void _showChangeRepoDialog(String currentUrl) {
|
||||
final changeUrlController = TextEditingController(text: currentUrl);
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.l10n.storeRepoDialogTitle),
|
||||
@@ -583,7 +583,7 @@ class _StoreTabState extends ConsumerState<StoreTab> {
|
||||
|
||||
void _showExtensionDetails(StoreExtension ext) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => ExtensionDetailsScreen(extension: ext),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2135,7 +2135,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
treeUri: treeUri,
|
||||
relativeDir: relativeDir,
|
||||
fileName: '$baseName.lrc',
|
||||
mimeType: 'text/plain',
|
||||
mimeType: 'application/octet-stream',
|
||||
srcPath: tempOutput,
|
||||
);
|
||||
try {
|
||||
@@ -2533,7 +2533,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
WidgetRef ref,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: screenContext,
|
||||
useRootNavigator: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
@@ -2824,7 +2824,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
bool isLosslessTarget =
|
||||
selectedFormat == 'ALAC' || selectedFormat == 'FLAC';
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
@@ -3023,7 +3023,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: this.context,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
@@ -3186,7 +3186,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
required String date,
|
||||
required List<CueSplitTrackInfo> tracks,
|
||||
}) {
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertDialog(
|
||||
@@ -3442,7 +3442,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
final isLossless =
|
||||
targetFormat.toUpperCase() == 'ALAC' ||
|
||||
targetFormat.toUpperCase() == 'FLAC';
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertDialog(
|
||||
@@ -3792,7 +3792,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
WidgetRef ref,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: screenContext,
|
||||
useRootNavigator: true,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
|
||||
@@ -527,7 +527,7 @@ class _InteractiveDownloadExampleState
|
||||
|
||||
for (int i = 0; i <= 100; i += 5) {
|
||||
if (!mounted) return;
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
setState(() => _progress = i / 100);
|
||||
}
|
||||
|
||||
@@ -536,7 +536,7 @@ class _InteractiveDownloadExampleState
|
||||
_isCompleted = true;
|
||||
});
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isCompleted = false;
|
||||
|
||||
@@ -119,7 +119,7 @@ class AppStateDatabase {
|
||||
final db = await database;
|
||||
await db.transaction((txn) async {
|
||||
final batch = txn.batch();
|
||||
for (final entry in decoded.whereType<Map>()) {
|
||||
for (final entry in decoded.whereType<Map<Object?, Object?>>()) {
|
||||
final map = Map<String, dynamic>.from(entry);
|
||||
final id = map['id'] as String?;
|
||||
if (id == null || id.isEmpty) continue;
|
||||
@@ -179,7 +179,7 @@ class AppStateDatabase {
|
||||
final decoded = jsonDecode(rawRecent);
|
||||
if (decoded is List) {
|
||||
final batch = txn.batch();
|
||||
for (final entry in decoded.whereType<Map>()) {
|
||||
for (final entry in decoded.whereType<Map<Object?, Object?>>()) {
|
||||
final map = Map<String, dynamic>.from(entry);
|
||||
final type = map['type'] as String?;
|
||||
final id = map['id'] as String?;
|
||||
|
||||
@@ -124,7 +124,7 @@ class CsvImportService {
|
||||
);
|
||||
|
||||
if (i < tracks.length - 1) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ class HistoryDatabase {
|
||||
}
|
||||
|
||||
try {
|
||||
final List<dynamic> jsonList = jsonDecode(jsonStr);
|
||||
final jsonList = List<dynamic>.from(jsonDecode(jsonStr) as List);
|
||||
_log.i(
|
||||
'Migrating ${jsonList.length} items from SharedPreferences to SQLite',
|
||||
);
|
||||
@@ -233,7 +233,7 @@ class HistoryDatabase {
|
||||
final batch = db.batch();
|
||||
|
||||
for (final json in jsonList) {
|
||||
final map = json as Map<String, dynamic>;
|
||||
final map = Map<String, dynamic>.from(json as Map);
|
||||
batch.insert(
|
||||
'history',
|
||||
_jsonToDbRow(map),
|
||||
|
||||
@@ -155,11 +155,11 @@ class LibraryCollectionsDatabase {
|
||||
|
||||
final db = await database;
|
||||
await db.transaction((txn) async {
|
||||
for (final entry in wishlistRaw.whereType<Map>()) {
|
||||
for (final entry in wishlistRaw.whereType<Map<Object?, Object?>>()) {
|
||||
final map = Map<String, dynamic>.from(entry);
|
||||
final trackKey = map['key'] as String?;
|
||||
final track = map['track'];
|
||||
if (trackKey == null || track is! Map) continue;
|
||||
if (trackKey == null || track is! Map<Object?, Object?>) continue;
|
||||
final addedAt = (map['addedAt'] as String?) ?? nowIso;
|
||||
await txn.insert(_tableWishlist, {
|
||||
'track_key': trackKey,
|
||||
@@ -168,11 +168,11 @@ class LibraryCollectionsDatabase {
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
for (final entry in lovedRaw.whereType<Map>()) {
|
||||
for (final entry in lovedRaw.whereType<Map<Object?, Object?>>()) {
|
||||
final map = Map<String, dynamic>.from(entry);
|
||||
final trackKey = map['key'] as String?;
|
||||
final track = map['track'];
|
||||
if (trackKey == null || track is! Map) continue;
|
||||
if (trackKey == null || track is! Map<Object?, Object?>) continue;
|
||||
final addedAt = (map['addedAt'] as String?) ?? nowIso;
|
||||
await txn.insert(_tableLoved, {
|
||||
'track_key': trackKey,
|
||||
@@ -181,7 +181,8 @@ class LibraryCollectionsDatabase {
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
for (final playlistEntry in playlistsRaw.whereType<Map>()) {
|
||||
for (final playlistEntry
|
||||
in playlistsRaw.whereType<Map<Object?, Object?>>()) {
|
||||
final playlist = Map<String, dynamic>.from(playlistEntry);
|
||||
final playlistId = playlist['id'] as String?;
|
||||
if (playlistId == null || playlistId.isEmpty) continue;
|
||||
@@ -197,11 +198,12 @@ class LibraryCollectionsDatabase {
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
|
||||
final tracksRaw = (playlist['tracks'] as List?) ?? const [];
|
||||
for (final trackEntry in tracksRaw.whereType<Map>()) {
|
||||
for (final trackEntry
|
||||
in tracksRaw.whereType<Map<Object?, Object?>>()) {
|
||||
final trackMap = Map<String, dynamic>.from(trackEntry);
|
||||
final trackKey = trackMap['key'] as String?;
|
||||
final track = trackMap['track'];
|
||||
if (trackKey == null || track is! Map) continue;
|
||||
if (trackKey == null || track is! Map<Object?, Object?>) continue;
|
||||
final addedAt = (trackMap['addedAt'] as String?) ?? nowIso;
|
||||
await txn.insert(_tablePlaylistTracks, {
|
||||
'playlist_id': playlistId,
|
||||
|
||||
@@ -67,8 +67,8 @@ class PlatformBridge {
|
||||
if (response['success'] == true) {
|
||||
final service = response['service'] ?? payload.service;
|
||||
final filePath = response['file_path'] ?? '';
|
||||
final bitDepth = response['actual_bit_depth'];
|
||||
final sampleRate = response['actual_sample_rate'];
|
||||
final bitDepth = response['actual_bit_depth'] as num?;
|
||||
final sampleRate = response['actual_sample_rate'] as num?;
|
||||
final qualityStr = bitDepth != null && sampleRate != null
|
||||
? ' ($bitDepth-bit/${(sampleRate / 1000).toStringAsFixed(1)}kHz)'
|
||||
: '';
|
||||
|
||||
@@ -65,7 +65,7 @@ class ShareIntentService {
|
||||
|
||||
_mediaSubscription = ReceiveSharingIntent.instance.getMediaStream().listen(
|
||||
_handleSharedMedia,
|
||||
onError: (err) => _log.e('Error: $err'),
|
||||
onError: (Object err) => _log.e('Error: $err'),
|
||||
);
|
||||
|
||||
final initialMedia = await ReceiveSharingIntent.instance.getInitialMedia();
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:spotiflac_android/constants/app_info.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart';
|
||||
|
||||
final _log = AppLogger('UpdateChecker');
|
||||
|
||||
enum _ApkVariant { arm64, arm32, universal }
|
||||
|
||||
class _ApkAsset {
|
||||
final String name;
|
||||
final String url;
|
||||
final _ApkVariant variant;
|
||||
|
||||
const _ApkAsset({
|
||||
required this.name,
|
||||
required this.url,
|
||||
required this.variant,
|
||||
});
|
||||
}
|
||||
|
||||
class UpdateInfo {
|
||||
final String version;
|
||||
final String changelog;
|
||||
@@ -94,32 +109,15 @@ class UpdateChecker {
|
||||
DateTime.tryParse(releaseData['published_at'] as String? ?? '') ??
|
||||
DateTime.now();
|
||||
|
||||
String? arm64Url;
|
||||
String? universalUrl;
|
||||
|
||||
final assets = releaseData['assets'] as List<dynamic>? ?? [];
|
||||
for (final asset in assets) {
|
||||
final name = (asset['name'] as String? ?? '').toLowerCase();
|
||||
if (name.endsWith('.apk')) {
|
||||
final downloadUrl = asset['browser_download_url'] as String?;
|
||||
final uri = downloadUrl != null ? Uri.tryParse(downloadUrl) : null;
|
||||
if (uri == null || uri.scheme != 'https') {
|
||||
_log.w('Skipping non-HTTPS APK URL: $downloadUrl');
|
||||
continue;
|
||||
}
|
||||
if (name.contains('arm64') || name.contains('v8a')) {
|
||||
arm64Url = downloadUrl;
|
||||
} else if (name.contains('universal')) {
|
||||
universalUrl = downloadUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only arm64 is supported; fall back to universal if available
|
||||
final apkUrl = arm64Url ?? universalUrl;
|
||||
final assets = _collectApkAssets(
|
||||
releaseData['assets'] as List<dynamic>? ?? const [],
|
||||
);
|
||||
final selectedAsset = await _selectApkForCurrentDevice(assets);
|
||||
final apkUrl = selectedAsset?.url;
|
||||
|
||||
_log.i(
|
||||
'Update available: $latestVersion (prerelease: $isPrerelease), APK URL: $apkUrl',
|
||||
'Update available: $latestVersion (prerelease: $isPrerelease), '
|
||||
'APK asset: ${selectedAsset?.name ?? 'none'}, APK URL: $apkUrl',
|
||||
);
|
||||
|
||||
return UpdateInfo(
|
||||
@@ -169,4 +167,128 @@ class UpdateChecker {
|
||||
}
|
||||
|
||||
static String get currentVersion => AppInfo.version;
|
||||
|
||||
static List<_ApkAsset> _collectApkAssets(List<dynamic> assets) {
|
||||
final apkAssets = <_ApkAsset>[];
|
||||
|
||||
for (final asset in assets.whereType<Map<Object?, Object?>>()) {
|
||||
final assetMap = Map<String, dynamic>.from(asset);
|
||||
final name = (assetMap['name'] as String? ?? '').trim();
|
||||
final normalizedName = name.toLowerCase();
|
||||
if (!normalizedName.endsWith('.apk')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final downloadUrl = assetMap['browser_download_url'] as String?;
|
||||
final uri = downloadUrl != null ? Uri.tryParse(downloadUrl) : null;
|
||||
if (uri == null || uri.scheme != 'https') {
|
||||
_log.w('Skipping non-HTTPS APK URL: $downloadUrl');
|
||||
continue;
|
||||
}
|
||||
|
||||
final variant = _apkVariantFromName(normalizedName);
|
||||
if (variant == null) {
|
||||
_log.w('Skipping APK with unknown variant: $name');
|
||||
continue;
|
||||
}
|
||||
|
||||
apkAssets.add(
|
||||
_ApkAsset(name: name, url: uri.toString(), variant: variant),
|
||||
);
|
||||
}
|
||||
|
||||
return apkAssets;
|
||||
}
|
||||
|
||||
static _ApkVariant? _apkVariantFromName(String name) {
|
||||
if (name.contains('universal')) {
|
||||
return _ApkVariant.universal;
|
||||
}
|
||||
if (name.contains('arm64') || name.contains('arm64-v8a')) {
|
||||
return _ApkVariant.arm64;
|
||||
}
|
||||
if (name.contains('arm32') ||
|
||||
name.contains('armeabi') ||
|
||||
name.contains('armv7') ||
|
||||
name.contains('v7a')) {
|
||||
return _ApkVariant.arm32;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<_ApkAsset?> _selectApkForCurrentDevice(
|
||||
List<_ApkAsset> assets,
|
||||
) async {
|
||||
if (assets.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
_ApkAsset? arm64Asset;
|
||||
_ApkAsset? arm32Asset;
|
||||
_ApkAsset? universalAsset;
|
||||
for (final asset in assets) {
|
||||
switch (asset.variant) {
|
||||
case _ApkVariant.arm64:
|
||||
arm64Asset ??= asset;
|
||||
break;
|
||||
case _ApkVariant.arm32:
|
||||
arm32Asset ??= asset;
|
||||
break;
|
||||
case _ApkVariant.universal:
|
||||
universalAsset ??= asset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final supportedAbis = await _getSupportedAndroidAbis();
|
||||
final hasArm64 = supportedAbis.any(_isArm64Abi);
|
||||
final hasArm32 = supportedAbis.any(_isArm32Abi);
|
||||
|
||||
if (hasArm64) {
|
||||
return arm64Asset ?? universalAsset ?? arm32Asset;
|
||||
}
|
||||
if (hasArm32) {
|
||||
return arm32Asset ?? universalAsset;
|
||||
}
|
||||
|
||||
if (universalAsset != null) {
|
||||
_log.w(
|
||||
'Could not match APK asset to supported ABIs ${supportedAbis.join(', ')}; '
|
||||
'falling back to universal APK.',
|
||||
);
|
||||
return universalAsset;
|
||||
}
|
||||
|
||||
_log.w(
|
||||
'Could not match APK asset to supported ABIs ${supportedAbis.join(', ')}; '
|
||||
'no universal APK available.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<List<String>> _getSupportedAndroidAbis() async {
|
||||
if (!Platform.isAndroid) {
|
||||
return const [];
|
||||
}
|
||||
|
||||
try {
|
||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
final supportedAbis = androidInfo.supportedAbis
|
||||
.map((abi) => abi.toLowerCase())
|
||||
.where((abi) => abi.isNotEmpty)
|
||||
.toSet()
|
||||
.toList();
|
||||
_log.i('Detected supported Android ABIs: ${supportedAbis.join(', ')}');
|
||||
return supportedAbis;
|
||||
} catch (e) {
|
||||
_log.w('Failed to detect supported Android ABIs: $e');
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
static bool _isArm64Abi(String abi) =>
|
||||
abi.contains('arm64') || abi.contains('aarch64');
|
||||
|
||||
static bool _isArm32Abi(String abi) =>
|
||||
abi.contains('armeabi') || abi.contains('armv7') || abi.contains('arm');
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ void _pushViaPreferredNavigator(BuildContext context, WidgetBuilder builder) {
|
||||
identical(currentNavigator, rootNavigator) && activeTabNavigator != null;
|
||||
|
||||
if (!shouldRouteToTabNavigator) {
|
||||
currentNavigator.push(MaterialPageRoute(builder: builder));
|
||||
currentNavigator.push(MaterialPageRoute<void>(builder: builder));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -264,12 +264,12 @@ void _pushViaPreferredNavigator(BuildContext context, WidgetBuilder builder) {
|
||||
currentNavigator.pop();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!activeTabNavigator.mounted) return;
|
||||
activeTabNavigator.push(MaterialPageRoute(builder: builder));
|
||||
activeTabNavigator.push(MaterialPageRoute<void>(builder: builder));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
activeTabNavigator.push(MaterialPageRoute(builder: builder));
|
||||
activeTabNavigator.push(MaterialPageRoute<void>(builder: builder));
|
||||
}
|
||||
|
||||
void _showLoadingSnackBar(BuildContext context, String message) {
|
||||
|
||||
@@ -179,15 +179,16 @@ class LogBuffer extends ChangeNotifier {
|
||||
final nextIndex = result['next_index'] as int? ?? _lastGoLogIndex;
|
||||
final keepNonErrorLogs = _loggingEnabled;
|
||||
|
||||
for (final log in logs) {
|
||||
final level = log['level'] as String? ?? 'INFO';
|
||||
for (final log in logs.whereType<Map<Object?, Object?>>()) {
|
||||
final logMap = Map<String, dynamic>.from(log);
|
||||
final level = logMap['level'] as String? ?? 'INFO';
|
||||
if (!keepNonErrorLogs && level != 'ERROR' && level != 'FATAL') {
|
||||
continue;
|
||||
}
|
||||
|
||||
final timestamp = log['timestamp'] as String? ?? '';
|
||||
final tag = log['tag'] as String? ?? 'Go';
|
||||
final message = log['message'] as String? ?? '';
|
||||
final timestamp = logMap['timestamp'] as String? ?? '';
|
||||
final tag = logMap['tag'] as String? ?? 'Go';
|
||||
final message = logMap['message'] as String? ?? '';
|
||||
|
||||
DateTime parsedTime = DateTime.now();
|
||||
if (timestamp.isNotEmpty) {
|
||||
|
||||
@@ -239,7 +239,9 @@ class _AudioAnalysisCardState extends State<AudioAnalysisCard> {
|
||||
final file = File('${dir.path}/$key.json');
|
||||
if (!await file.exists()) return null;
|
||||
|
||||
final json = jsonDecode(await file.readAsString());
|
||||
final json = Map<String, dynamic>.from(
|
||||
jsonDecode(await file.readAsString()) as Map,
|
||||
);
|
||||
final cachedSize = json['fileSize'] as int;
|
||||
|
||||
if (!filePath.startsWith('content://')) {
|
||||
|
||||
@@ -110,7 +110,7 @@ class DownloadServicePicker extends ConsumerStatefulWidget {
|
||||
}) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
|
||||
@@ -19,7 +19,7 @@ class TrackCollectionQuickActions extends ConsumerWidget {
|
||||
Track track,
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
|
||||
+66
-2
@@ -9,14 +9,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "91.0.0"
|
||||
analysis_server_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analysis_server_plugin
|
||||
sha256: "26844e7f977087567135d62532b67d5639fe206c5194c3f410ba75e1a04a2747"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
|
||||
sha256: a40a0cee526a7e1f387c6847bd8a5ccbf510a75952ef8a28338e989558072cb0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.4.1"
|
||||
version: "8.4.0"
|
||||
analyzer_buffer:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -25,6 +33,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.11"
|
||||
analyzer_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer_plugin
|
||||
sha256: "08cfefa90b4f4dd3b447bda831cecf644029f9f8e22820f6ee310213ebe2dd53"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.10"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -145,6 +161,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
ci:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ci
|
||||
sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -241,6 +265,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
custom_lint:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: custom_lint
|
||||
sha256: "751ee9440920f808266c3ec2553420dea56d3c7837dd2d62af76b11be3fcece5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.1"
|
||||
custom_lint_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_core
|
||||
sha256: "85b339346154d5646952d44d682965dfe9e12cae5febd706f0db3aa5010d6423"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.1"
|
||||
custom_lint_visitor:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_visitor
|
||||
sha256: "91f2a81e9f0abb4b9f3bb529f78b6227ce6050300d1ae5b1e2c69c66c7a566d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0+8.4.0"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -925,6 +973,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0+1"
|
||||
riverpod_lint:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_lint
|
||||
sha256: "4d2eb0d19bbe7e3323bd0ce4553b2e6170d161a13914bfdd85a3612329edcb43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1402,6 +1458,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
yaml_edit:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml_edit
|
||||
sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.4"
|
||||
sdks:
|
||||
dart: ">=3.10.0 <4.0.0"
|
||||
flutter: ">=3.38.1"
|
||||
|
||||
+3
-1
@@ -13,7 +13,7 @@ dependencies:
|
||||
# Localization
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: any
|
||||
intl: ^0.20.2
|
||||
|
||||
# State Management
|
||||
flutter_riverpod: ^3.1.0
|
||||
@@ -68,7 +68,9 @@ dev_dependencies:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
build_runner: ^2.10.4
|
||||
custom_lint: ^0.8.1
|
||||
riverpod_generator: ^4.0.0
|
||||
riverpod_lint: ^3.1.0
|
||||
json_serializable: ^6.11.2
|
||||
flutter_launcher_icons: ^0.14.3
|
||||
|
||||
|
||||
Reference in New Issue
Block a user