mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-16 08:00:23 +02:00
v3.7.0: roll back from v4, remove internal player — v3 is already complete
Version rolled back from v4.x to v3.7.0. After extensive work on v4's internal streaming engine, smart queue, DASH pipeline, and media controls, we realized v3 was already feature-complete. Adding more big features only made maintenance increasingly difficult and the developer's life miserable. Stripped back to what works: external player only, cleaner codebase, sustainable long-term. - Remove just_audio, audio_service, audio_session and entire internal playback engine (smart queue, notification, shuffle/repeat, prefetch) - Remove PlaybackItem model, MiniPlayerBar widget, notification drawables - Remove playerMode setting (external-only now) - Migrate MainActivity from AudioServiceFragmentActivity to FlutterFragmentActivity - Migrate Qobuz to MusicDL API - Update changelog with v3.7.0 rollback explanation
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/providers/download_queue_provider.dart';
|
||||
import 'package:spotiflac_android/providers/library_collections_provider.dart';
|
||||
import 'package:spotiflac_android/providers/local_library_provider.dart';
|
||||
import 'package:spotiflac_android/providers/playback_provider.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/services/cover_cache_manager.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/widgets/download_service_picker.dart';
|
||||
import 'package:spotiflac_android/widgets/playlist_picker_sheet.dart';
|
||||
import 'package:spotiflac_android/utils/clickable_metadata.dart';
|
||||
|
||||
@@ -64,9 +56,6 @@ class _TrackOptionsSheet extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final settings = ref.watch(settingsProvider);
|
||||
final rootContext = Navigator.of(context, rootNavigator: true).context;
|
||||
final container = ProviderScope.containerOf(rootContext, listen: false);
|
||||
|
||||
final isLoved = ref.watch(
|
||||
libraryCollectionsProvider.select((state) => state.isLoved(track)),
|
||||
@@ -174,46 +163,6 @@ class _TrackOptionsSheet extends ConsumerWidget {
|
||||
),
|
||||
|
||||
// Action items (matches _QualityOption style)
|
||||
_OptionTile(
|
||||
icon: Icons.download_rounded,
|
||||
title: 'Download & Play',
|
||||
onTap: () async {
|
||||
Navigator.pop(context);
|
||||
final playedLocal = await _playLocalIfAvailable(
|
||||
container,
|
||||
rootContext,
|
||||
);
|
||||
if (playedLocal) {
|
||||
return;
|
||||
}
|
||||
if (!rootContext.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.askQualityBeforeDownload) {
|
||||
DownloadServicePicker.show(
|
||||
rootContext,
|
||||
trackName: track.name,
|
||||
artistName: track.artistName,
|
||||
coverUrl: track.coverUrl,
|
||||
onSelect: (quality, service) {
|
||||
_enqueueDownloadAndAutoPlay(
|
||||
container: container,
|
||||
context: rootContext,
|
||||
service: service,
|
||||
quality: quality,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
_enqueueDownloadAndAutoPlay(
|
||||
container: container,
|
||||
context: rootContext,
|
||||
service: settings.defaultService,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
_OptionTile(
|
||||
icon: isLoved ? Icons.favorite : Icons.favorite_border,
|
||||
iconColor: isLoved ? colorScheme.error : null,
|
||||
@@ -282,138 +231,6 @@ class _TrackOptionsSheet extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _playLocalIfAvailable(
|
||||
ProviderContainer container,
|
||||
BuildContext context,
|
||||
) async {
|
||||
final localState = container.read(localLibraryProvider);
|
||||
final historyState = container.read(downloadHistoryProvider);
|
||||
final historyNotifier = container.read(downloadHistoryProvider.notifier);
|
||||
|
||||
try {
|
||||
DownloadHistoryItem? historyItem = historyNotifier.getBySpotifyId(
|
||||
track.id,
|
||||
);
|
||||
final isrc = track.isrc?.trim();
|
||||
historyItem ??= (isrc != null && isrc.isNotEmpty)
|
||||
? historyNotifier.getByIsrc(isrc)
|
||||
: null;
|
||||
historyItem ??= historyState.findByTrackAndArtist(
|
||||
track.name,
|
||||
track.artistName,
|
||||
);
|
||||
|
||||
if (historyItem != null) {
|
||||
final exists = await fileExists(historyItem.filePath);
|
||||
if (exists) {
|
||||
await container
|
||||
.read(playbackProvider.notifier)
|
||||
.playLocalPath(
|
||||
path: historyItem.filePath,
|
||||
title: track.name,
|
||||
artist: track.artistName,
|
||||
album: track.albumName,
|
||||
coverUrl: track.coverUrl ?? '',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
historyNotifier.removeFromHistory(historyItem.id);
|
||||
}
|
||||
|
||||
var localItem = (isrc != null && isrc.isNotEmpty)
|
||||
? localState.getByIsrc(isrc)
|
||||
: null;
|
||||
localItem ??= localState.findByTrackAndArtist(
|
||||
track.name,
|
||||
track.artistName,
|
||||
);
|
||||
|
||||
if (localItem != null && await fileExists(localItem.filePath)) {
|
||||
await container
|
||||
.read(playbackProvider.notifier)
|
||||
.playLocalPath(
|
||||
path: localItem.filePath,
|
||||
title: localItem.trackName,
|
||||
artist: localItem.artistName,
|
||||
album: localItem.albumName,
|
||||
coverUrl: localItem.coverPath ?? track.coverUrl ?? '',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.snackbarCannotOpenFile('$e'))),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void _enqueueDownloadAndAutoPlay({
|
||||
required ProviderContainer container,
|
||||
required BuildContext context,
|
||||
required String service,
|
||||
String? quality,
|
||||
}) {
|
||||
container
|
||||
.read(downloadQueueProvider.notifier)
|
||||
.addToQueue(track, service, qualityOverride: quality);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.snackbarAddedToQueue(track.name))),
|
||||
);
|
||||
}
|
||||
unawaited(_waitForDownloadedFileAndPlay(container, context));
|
||||
}
|
||||
|
||||
Future<void> _waitForDownloadedFileAndPlay(
|
||||
ProviderContainer container,
|
||||
BuildContext context,
|
||||
) async {
|
||||
const maxAttempts = 180; // up to ~3 minutes
|
||||
for (var i = 0; i < maxAttempts; i++) {
|
||||
final item = _findHistoryMatch(container);
|
||||
if (item != null && await fileExists(item.filePath)) {
|
||||
try {
|
||||
await container
|
||||
.read(playbackProvider.notifier)
|
||||
.playLocalPath(
|
||||
path: item.filePath,
|
||||
title: track.name,
|
||||
artist: track.artistName,
|
||||
album: track.albumName,
|
||||
coverUrl: track.coverUrl ?? '',
|
||||
);
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.snackbarCannotOpenFile('$e')),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
}
|
||||
|
||||
DownloadHistoryItem? _findHistoryMatch(ProviderContainer container) {
|
||||
final historyState = container.read(downloadHistoryProvider);
|
||||
final historyNotifier = container.read(downloadHistoryProvider.notifier);
|
||||
final isrc = track.isrc?.trim();
|
||||
|
||||
return historyNotifier.getBySpotifyId(track.id) ??
|
||||
((isrc != null && isrc.isNotEmpty)
|
||||
? historyNotifier.getByIsrc(isrc)
|
||||
: null) ??
|
||||
historyState.findByTrackAndArtist(track.name, track.artistName);
|
||||
}
|
||||
}
|
||||
|
||||
/// Styled like _QualityOption in download_service_picker.dart
|
||||
|
||||
Reference in New Issue
Block a user