refactor: cleanup unused code and imports

This commit is contained in:
zarzet
2026-01-17 09:50:00 +07:00
parent 621582cf11
commit b99764b1ad
29 changed files with 0 additions and 294 deletions
-2
View File
@@ -9,7 +9,6 @@ import 'package:spotiflac_android/theme/dynamic_color_wrapper.dart';
import 'package:spotiflac_android/l10n/app_localizations.dart';
final _routerProvider = Provider<GoRouter>((ref) {
// Only watch isFirstLaunch to prevent router rebuild on other settings changes
final isFirstLaunch = ref.watch(settingsProvider.select((s) => s.isFirstLaunch));
return GoRouter(
@@ -35,7 +34,6 @@ class SpotiFLACApp extends ConsumerWidget {
final router = ref.watch(_routerProvider);
final localeString = ref.watch(settingsProvider.select((s) => s.locale));
// Convert locale string to Locale object
Locale? locale;
if (localeString != 'system') {
locale = Locale(localeString);
-1
View File
@@ -48,7 +48,6 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization> {
final extensionsDir = '${appDir.path}/extensions';
final dataDir = '${appDir.path}/extension_data';
// Create directories if needed
await Directory(extensionsDir).create(recursive: true);
await Directory(dataDir).create(recursive: true);
@@ -415,11 +415,9 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
state = state.copyWith(items: pendingItems);
_log.i('Restored ${pendingItems.length} pending items from storage');
// Auto-resume queue processing
Future.microtask(() => _processQueue());
} else {
_log.d('No pending items to restore');
// Clear storage since nothing to restore
await prefs.remove(_queueStorageKey);
}
} else {
@@ -603,7 +601,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
if (state.outputDir.isEmpty) {
try {
if (Platform.isIOS) {
// iOS: Use Documents directory (accessible via Files app)
final dir = await getApplicationDocumentsDirectory();
final musicDir = Directory('${dir.path}/SpotiFLAC');
if (!await musicDir.exists()) {
@@ -1347,7 +1344,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
return;
}
// Set currentDownload for UI reference
state = state.copyWith(currentDownload: item);
updateItemStatus(item.id, DownloadStatus.downloading);
@@ -1408,7 +1404,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
releaseDate: data['release_date'] as String?,
deezerId: rawId,
availability: trackToDownload.availability,
// Preserve albumType from API response or original track
albumType: (data['album_type'] as String?) ?? trackToDownload.albumType,
source: trackToDownload.source,
);
@@ -1439,7 +1434,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
albumFolderStructure: settings.albumFolderStructure,
);
// Use quality override if set, otherwise use default from settings
final quality = item.qualityOverride ?? state.audioQuality;
Map<String, dynamic> result;
@@ -1449,7 +1443,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
final useExtensions = settings.useExtensionProviders && hasActiveExtensions;
if (useExtensions) {
// Use extension providers (includes fallback to built-in services)
_log.d('Using extension providers for download');
_log.d(
'Quality: $quality${item.qualityOverride != null ? ' (override)' : ''}',
@@ -1528,7 +1521,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
);
if (currentItem.status == DownloadStatus.skipped) {
_log.i('Download was cancelled, skipping result processing');
// Delete the downloaded file if it exists
final filePath = result['file_path'] as String?;
if (filePath != null && result['success'] == true) {
try {
@@ -1614,7 +1606,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
'Backend metadata - Track: $backendTrackNum, Disc: $backendDiscNum, Year: $backendYear',
);
// Create updated track object with safety check for 0/null
final newTrackNumber =
(backendTrackNum != null && backendTrackNum > 0)
? backendTrackNum
@@ -1647,7 +1638,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
);
}
// Use enriched/updated track for metadata embedding
await _embedMetadataAndCover(flacPath, finalTrack);
_log.d('Metadata and cover embedded successfully');
} catch (e) {
@@ -1714,7 +1704,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
final backendSampleRate = result['actual_sample_rate'] as int?;
final backendISRC = result['isrc'] as String?;
// Log cover URL for debugging
_log.d('Saving to history - coverUrl: ${trackToDownload.coverUrl}');
final historyAlbumArtist =
@@ -1782,7 +1771,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
return;
}
// Convert error type string to enum
DownloadErrorType errorType;
switch (errorTypeStr) {
case 'not_found':
-7
View File
@@ -175,12 +175,10 @@ class SearchBehavior {
/// Get thumbnail size based on configuration
/// Returns (width, height) tuple
(double, double) getThumbnailSize({double defaultSize = 56}) {
// If custom dimensions specified, use them
if (thumbnailWidth != null && thumbnailHeight != null) {
return (thumbnailWidth!.toDouble(), thumbnailHeight!.toDouble());
}
// Otherwise use ratio presets
switch (thumbnailRatio) {
case 'wide': // 16:9 - YouTube style
return (defaultSize * 16 / 9, defaultSize);
@@ -558,10 +556,8 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
await PlatformBridge.setExtensionEnabled(extensionId, enabled);
_log.d('Set extension $extensionId enabled: $enabled');
// Get extension info before updating state
final ext = state.extensions.where((e) => e.id == extensionId).firstOrNull;
// Update local state
final extensions = state.extensions.map((e) {
if (e.id == extensionId) {
return e.copyWith(enabled: enabled);
@@ -571,18 +567,15 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
state = state.copyWith(extensions: extensions);
// If disabling an extension, reset related settings
if (!enabled && ext != null) {
final settings = ref.read(settingsProvider);
// If this extension was the search provider, clear it and reset to Deezer
if (settings.searchProvider == extensionId) {
ref.read(settingsProvider.notifier).setSearchProvider(null);
ref.read(settingsProvider.notifier).setMetadataSource('deezer');
_log.d('Cleared search provider and reset to Deezer because extension $extensionId was disabled');
}
// If this extension was the default download service, reset to Tidal
if (ext.hasDownloadProvider && settings.defaultService == extensionId) {
ref.read(settingsProvider.notifier).setDefaultService('tidal');
_log.d('Reset default service to Tidal because extension $extensionId was disabled');
-3
View File
@@ -89,14 +89,12 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
try {
Map<String, dynamic> metadata;
// Check if this is a Deezer album ID (format: "deezer:123456")
if (widget.albumId.startsWith('deezer:')) {
final deezerAlbumId = widget.albumId.replaceFirst('deezer:', '');
// ignore: avoid_print
print('[AlbumScreen] Fetching from Deezer: $deezerAlbumId');
metadata = await PlatformBridge.getDeezerMetadata('album', deezerAlbumId);
} else {
// Spotify album - use fallback method
// ignore: avoid_print
print('[AlbumScreen] Fetching from Spotify with fallback: ${widget.albumId}');
final url = 'https://open.spotify.com/album/${widget.albumId}';
@@ -448,7 +446,6 @@ class _AlbumTrackItem extends ConsumerWidget {
return state.items.where((item) => item.track.id == track.id).firstOrNull;
}));
// Check if track is in history (already downloaded before)
final isInHistory = ref.watch(downloadHistoryProvider.select((state) {
return state.isDownloaded(track.id);
}));
-9
View File
@@ -112,7 +112,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
);
});
// If this is an extension artist, use provided data only - don't fetch from Spotify/Deezer
if (widget.extensionId != null) {
_albums = widget.albums;
_topTracks = widget.topTracks;
@@ -122,8 +121,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
return;
}
// Priority: widget data > cache > fetch
// But always fetch if topTracks is missing (to get popular tracks)
final cached = _ArtistCache.get(widget.artistId);
if (widget.albums != null) {
@@ -132,7 +129,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
_headerImageUrl = widget.headerImageUrl;
_monthlyListeners = widget.monthlyListeners;
// If we have albums but no top tracks, fetch to get them
if (_topTracks == null || _topTracks!.isEmpty) {
_fetchDiscography();
}
@@ -159,14 +155,12 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
String? headerImage;
int? listeners;
// Check if this is a Deezer artist ID (format: "deezer:123456")
if (widget.artistId.startsWith('deezer:')) {
final deezerArtistId = widget.artistId.replaceFirst('deezer:', '');
final metadata = await PlatformBridge.getDeezerMetadata('artist', deezerArtistId);
final albumsList = metadata['albums'] as List<dynamic>;
albums = albumsList.map((a) => _parseArtistAlbum(a as Map<String, dynamic>)).toList();
} else {
// Spotify artist - use extension handler via URL
final url = 'https://open.spotify.com/artist/${widget.artistId}';
final result = await PlatformBridge.handleURLWithExtension(url);
@@ -302,8 +296,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
/// Build Spotify-style header with full-width image and artist name overlay
Widget _buildHeader(BuildContext context, ColorScheme colorScheme) {
// Use header image if available, otherwise fall back to cover URL
// Prefer: fetched header > widget header > widget cover
String? imageUrl = _headerImageUrl;
if (imageUrl == null || imageUrl.isEmpty) {
imageUrl = widget.headerImageUrl;
@@ -467,7 +459,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
return state.items.where((item) => item.track.id == track.id).firstOrNull;
}));
// Check if track is in history (already downloaded before)
final isInHistory = ref.watch(downloadHistoryProvider.select((state) {
return state.isDownloaded(track.id);
}));
-1
View File
@@ -159,7 +159,6 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
final colorScheme = Theme.of(context).colorScheme;
final bottomPadding = MediaQuery.of(context).padding.bottom;
// Watch history and get tracks for this album (reactive!)
final allHistoryItems = ref.watch(downloadHistoryProvider.select((s) => s.items));
final tracks = _getAlbumTracks(allHistoryItems);
-1
View File
@@ -329,7 +329,6 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
}
Future<void> _openCollection(Track track) async {
// Get the extension ID from the track source
final extensionId = track.source;
if (extensionId == null) return;
-18
View File
@@ -76,10 +76,8 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
} void _onSearchChanged() {
final text = _urlController.text.trim();
// Update search text state for MainShell back button handling
ref.read(trackProvider.notifier).setSearchText(text.isNotEmpty);
// Update typing state immediately for UI transition
if (text.isNotEmpty && !_isTyping) {
setState(() => _isTyping = true);
} else if (text.isEmpty && _isTyping) {
@@ -103,17 +101,13 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
if (_lastSearchQuery == searchKey) return;
_lastSearchQuery = searchKey;
// Check if extension search provider is set AND still enabled
final isExtensionEnabled = searchProvider != null &&
searchProvider.isNotEmpty &&
extState.extensions.any((e) => e.id == searchProvider && e.enabled);
if (isExtensionEnabled) {
// Use custom search from extension
await ref.read(trackProvider.notifier).customSearch(searchProvider, query);
} else {
// Use default search (Deezer/Spotify)
// Also clear searchProvider if it was set but extension is disabled
if (searchProvider != null && searchProvider.isNotEmpty && !isExtensionEnabled) {
ref.read(settingsProvider.notifier).setSearchProvider(null);
}
@@ -238,7 +232,6 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
int currentProgress = 0;
int totalTracks = 0;
// Use StatefulBuilder to update dialog content
bool dialogShown = false;
StateSetter? setDialogState;
@@ -322,8 +315,6 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
action: SnackBarAction(
label: l10n.snackbarViewQueue,
onPressed: () {
// Navigate to queue tab (handled by main_shell index)
// We don't have direct access to set index here easily without provider
},
),
),
@@ -348,14 +339,12 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
}
});
// Use select() to only rebuild when specific fields change
final tracks = ref.watch(trackProvider.select((s) => s.tracks));
final searchArtists = ref.watch(trackProvider.select((s) => s.searchArtists));
final isLoading = ref.watch(trackProvider.select((s) => s.isLoading));
final error = ref.watch(trackProvider.select((s) => s.error));
final hasSearchedBefore = ref.watch(settingsProvider.select((s) => s.hasSearchedBefore));
// Watch extension state to update search hint when extensions load/change
ref.watch(extensionProvider.select((s) => s.isInitialized));
ref.watch(extensionProvider.select((s) => s.extensions));
@@ -612,7 +601,6 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
// Merge with recent downloads to make the list more populated
final historyItems = ref.read(downloadHistoryProvider).items;
// Convert download history to RecentAccessItem format
final downloadItems = historyItems.take(10).where((h) => h.spotifyId != null && h.spotifyId!.isNotEmpty).map((h) => RecentAccessItem(
id: h.spotifyId!,
name: h.trackName,
@@ -748,7 +736,6 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
],
),
),
// Delete button (like Spotify's X)
IconButton(
icon: Icon(Icons.close, size: 20, color: colorScheme.onSurfaceVariant),
onPressed: () {
@@ -767,7 +754,6 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
switch (item.type) {
case RecentAccessType.artist:
// Check if artist is from extension (not spotify/deezer)
if (item.providerId != null && item.providerId!.isNotEmpty && item.providerId != 'deezer' && item.providerId != 'spotify') {
Navigator.push(context, MaterialPageRoute(
builder: (context) => ExtensionArtistScreen(
@@ -1389,16 +1375,13 @@ class _TrackItemWithStatus extends ConsumerWidget {
return state.items.where((item) => item.track.id == track.id).firstOrNull;
}));
// Check if track is in history (already downloaded before)
final isInHistory = ref.watch(downloadHistoryProvider.select((state) {
return state.isDownloaded(track.id);
}));
// Get thumbnail size from extension if track is from extension
double thumbWidth = 56;
double thumbHeight = 56;
// Get extension ID from track.source or from TrackState.searchExtensionId
final trackState = ref.watch(trackProvider);
final extensionId = track.source ?? trackState.searchExtensionId;
@@ -1499,7 +1482,6 @@ class _TrackItemWithStatus extends ConsumerWidget {
// If already in queue, do nothing
if (isQueued) return;
// If in history, check if file still exists
if (isInHistory) {
final historyItem = ref.read(downloadHistoryProvider.notifier).getBySpotifyId(track.id);
if (historyItem != null) {
-8
View File
@@ -36,7 +36,6 @@ class _MainShellState extends ConsumerState<MainShell> {
void initState() {
super.initState();
_pageController = PageController(initialPage: _currentIndex);
// Check for updates after first frame
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkForUpdates();
_setupShareListener();
@@ -44,7 +43,6 @@ class _MainShellState extends ConsumerState<MainShell> {
}
void _setupShareListener() {
// Check for pending URL that was received before listener was ready
final pendingUrl = ShareIntentService().consumePendingUrl();
if (pendingUrl != null) {
_log.d('Processing pending shared URL: $pendingUrl');
@@ -124,8 +122,6 @@ class _MainShellState extends ConsumerState<MainShell> {
void _onPageChanged(int index) {
if (_currentIndex != index) {
setState(() => _currentIndex = index);
// Unfocus any text field when switching tabs to prevent keyboard from appearing
// Use primaryFocus for more aggressive unfocus that works with keep-alive widgets
FocusManager.instance.primaryFocus?.unfocus();
}
}
@@ -134,7 +130,6 @@ class _MainShellState extends ConsumerState<MainShell> {
void _handleBackPress() {
final trackState = ref.read(trackProvider);
// Check if keyboard is visible - if so, just dismiss keyboard, don't clear search
final isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0;
if (isKeyboardVisible) {
FocusManager.instance.primaryFocus?.unfocus();
@@ -144,7 +139,6 @@ class _MainShellState extends ConsumerState<MainShell> {
// If on Home tab and showing recent access mode, exit it
if (_currentIndex == 0 && trackState.isShowingRecentAccess) {
ref.read(trackProvider.notifier).setShowingRecentAccess(false);
// Also unfocus search bar when exiting recent access mode
FocusManager.instance.primaryFocus?.unfocus();
return;
}
@@ -189,7 +183,6 @@ class _MainShellState extends ConsumerState<MainShell> {
final showStore = ref.watch(settingsProvider.select((s) => s.showExtensionStore));
final storeUpdatesCount = ref.watch(storeProvider.select((s) => s.updatesAvailableCount));
// Check if keyboard is visible (bottom inset > 0 means keyboard is showing)
final isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0;
// Determine if we can pop (for predictive back animation)
@@ -202,7 +195,6 @@ class _MainShellState extends ConsumerState<MainShell> {
!trackState.isShowingRecentAccess &&
!isKeyboardVisible;
// Build tabs and destinations based on settings
final tabs = <Widget>[
const HomeTab(),
QueueTab(
-1
View File
@@ -222,7 +222,6 @@ class _PlaylistTrackItem extends ConsumerWidget {
return state.items.where((item) => item.track.id == track.id).firstOrNull;
}));
// Check if track is in history (already downloaded before)
final isInHistory = ref.watch(downloadHistoryProvider.select((state) {
return state.isDownloaded(track.id);
}));
@@ -473,7 +473,6 @@ class DownloadSettingsPage extends ConsumerWidget {
// iOS: Show options dialog
_showIOSDirectoryOptions(context, ref);
} else {
// Android: Use file picker
final result = await FilePicker.platform.getDirectoryPath();
if (result != null) {
ref.read(settingsProvider.notifier).setDownloadDirectory(result);
@@ -500,7 +500,6 @@ class _MetadataPriorityItem extends ConsumerWidget {
final extState = ref.watch(extensionProvider);
final colorScheme = Theme.of(context).colorScheme;
// Check if any extension has metadata provider
final hasMetadataExtensions = extState.extensions
.any((e) => e.enabled && e.hasMetadataProvider);
@@ -838,7 +838,6 @@ class _MetadataSourceSelector extends ConsumerWidget {
// Not selected if extension is active
isSelected: currentSource == 'deezer' && !hasExtensionSearch,
onTap: () {
// If extension was active, reset it to default
if (hasExtensionSearch) {
ref.read(settingsProvider.notifier).setSearchProvider(null);
}
-4
View File
@@ -123,16 +123,12 @@ class SettingsTab extends ConsumerWidget {
}
void _navigateTo(BuildContext context, Widget page) {
// Unfocus any focused widget before navigating to prevent keyboard from appearing on return
FocusManager.instance.primaryFocus?.unfocus();
Navigator.of(context).push(
// Use PageRouteBuilder for better predictive back gesture support
// MaterialPageRoute can cause freeze on some devices with gesture navigation
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// Use slide transition similar to MaterialPageRoute
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
-1
View File
@@ -45,7 +45,6 @@ class DynamicColorWrapper extends ConsumerWidget {
darkScheme = _applyAmoledColors(darkScheme);
}
// Build themes
final lightTheme = AppTheme.light(dynamicScheme: lightScheme);
final darkTheme = AppTheme.dark(dynamicScheme: darkScheme, isAmoled: themeSettings.useAmoled);
-6
View File
@@ -55,7 +55,6 @@ class LogBuffer extends ChangeNotifier {
static bool get loggingEnabled => _loggingEnabled;
static set loggingEnabled(bool value) {
_loggingEnabled = value;
// Also notify Go backend about logging state
if (value) {
PlatformBridge.setGoLoggingEnabled(true).catchError((_) {});
} else {
@@ -121,7 +120,6 @@ class LogBuffer extends ChangeNotifier {
);
}
} catch (_) {
// Use current time if parsing fails
}
}
@@ -146,7 +144,6 @@ class LogBuffer extends ChangeNotifier {
void clear() {
_entries.clear();
_lastGoLogIndex = 0;
// Also clear Go backend logs
PlatformBridge.clearGoLogs().catchError((_) {});
notifyListeners();
}
@@ -249,8 +246,6 @@ class AppLogger {
late final Logger? _logger;
AppLogger(this._tag) {
// Only create Logger instance in debug mode
// In release mode, we write directly to LogBuffer
if (kDebugMode) {
_logger = Logger(
printer: SimplePrinter(printTime: false, colors: false),
@@ -276,7 +271,6 @@ class AppLogger {
if (kDebugMode) {
_logger?.d(message);
} else {
// In release mode, write directly to buffer
_addToBuffer('DEBUG', message);
}
}