mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-14 15:10:22 +02:00
chore: rebuild dev history without streaming-era commits
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import 'package:spotiflac_android/models/track.dart';
|
||||
|
||||
class PlaybackItem {
|
||||
final String id;
|
||||
final String title;
|
||||
final String artist;
|
||||
final String album;
|
||||
final String coverUrl;
|
||||
final String sourceUri;
|
||||
final bool isLocal;
|
||||
final String service;
|
||||
final int durationMs;
|
||||
|
||||
// Stream quality metadata
|
||||
final String format;
|
||||
final int bitDepth;
|
||||
final int sampleRate;
|
||||
final int bitrate;
|
||||
|
||||
// Original track reference for queue operations
|
||||
final Track? track;
|
||||
|
||||
const PlaybackItem({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.artist,
|
||||
this.album = '',
|
||||
this.coverUrl = '',
|
||||
required this.sourceUri,
|
||||
this.isLocal = false,
|
||||
this.service = '',
|
||||
this.durationMs = 0,
|
||||
this.format = '',
|
||||
this.bitDepth = 0,
|
||||
this.sampleRate = 0,
|
||||
this.bitrate = 0,
|
||||
this.track,
|
||||
});
|
||||
|
||||
PlaybackItem copyWith({
|
||||
String? sourceUri,
|
||||
String? service,
|
||||
String? format,
|
||||
int? bitDepth,
|
||||
int? sampleRate,
|
||||
int? bitrate,
|
||||
}) {
|
||||
return PlaybackItem(
|
||||
id: id,
|
||||
title: title,
|
||||
artist: artist,
|
||||
album: album,
|
||||
coverUrl: coverUrl,
|
||||
sourceUri: sourceUri ?? this.sourceUri,
|
||||
isLocal: isLocal,
|
||||
service: service ?? this.service,
|
||||
durationMs: durationMs,
|
||||
format: format ?? this.format,
|
||||
bitDepth: bitDepth ?? this.bitDepth,
|
||||
sampleRate: sampleRate ?? this.sampleRate,
|
||||
bitrate: bitrate ?? this.bitrate,
|
||||
track: track,
|
||||
);
|
||||
}
|
||||
|
||||
/// Human-readable quality label for UI display
|
||||
String get qualityLabel {
|
||||
final parts = <String>[];
|
||||
|
||||
if (format.isNotEmpty) {
|
||||
parts.add(format.toUpperCase());
|
||||
}
|
||||
|
||||
if (bitDepth > 0 && sampleRate > 0) {
|
||||
final srKhz = sampleRate >= 1000
|
||||
? '${(sampleRate / 1000).toStringAsFixed(sampleRate % 1000 == 0 ? 0 : 1)}kHz'
|
||||
: '${sampleRate}Hz';
|
||||
parts.add('$bitDepth-bit / $srKhz');
|
||||
} else if (bitrate > 0) {
|
||||
parts.add('${bitrate}kbps');
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
/// Whether this item has cover art that is a local file path
|
||||
bool get hasLocalCover {
|
||||
if (coverUrl.isEmpty) return false;
|
||||
return !coverUrl.startsWith('http://') && !coverUrl.startsWith('https://');
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ class AppSettings {
|
||||
final String storageMode; // 'app' or 'saf'
|
||||
final String downloadTreeUri; // SAF persistable tree URI
|
||||
final bool autoFallback;
|
||||
final bool autoSkipUnavailableTracks;
|
||||
final bool smartQueueEnabled; // Enable smart curated autoplay queue
|
||||
final bool embedMetadata; // Master switch for metadata/cover/lyrics embedding
|
||||
final bool embedLyrics;
|
||||
final bool maxQualityCover;
|
||||
final bool isFirstLaunch;
|
||||
@@ -76,6 +79,10 @@ class AppSettings {
|
||||
final String
|
||||
musixmatchLanguage; // Optional ISO language code for Musixmatch localized lyrics
|
||||
|
||||
// Version upgrade tracking
|
||||
final String
|
||||
lastSeenVersion; // Last app version the user has acknowledged (e.g. '3.7.0')
|
||||
|
||||
const AppSettings({
|
||||
this.defaultService = 'tidal',
|
||||
this.audioQuality = 'LOSSLESS',
|
||||
@@ -84,6 +91,9 @@ class AppSettings {
|
||||
this.storageMode = 'app',
|
||||
this.downloadTreeUri = '',
|
||||
this.autoFallback = true,
|
||||
this.autoSkipUnavailableTracks = true,
|
||||
this.smartQueueEnabled = true,
|
||||
this.embedMetadata = true,
|
||||
this.embedLyrics = true,
|
||||
this.maxQualityCover = true,
|
||||
this.isFirstLaunch = true,
|
||||
@@ -127,6 +137,7 @@ class AppSettings {
|
||||
// Lyrics providers default order
|
||||
this.lyricsProviders = const [
|
||||
'lrclib',
|
||||
'spotify_api',
|
||||
'musixmatch',
|
||||
'netease',
|
||||
'apple_music',
|
||||
@@ -136,6 +147,8 @@ class AppSettings {
|
||||
this.lyricsIncludeRomanizationNetease = false,
|
||||
this.lyricsMultiPersonWordByWord = false,
|
||||
this.musixmatchLanguage = '',
|
||||
// Version upgrade tracking
|
||||
this.lastSeenVersion = '',
|
||||
});
|
||||
|
||||
AppSettings copyWith({
|
||||
@@ -146,6 +159,9 @@ class AppSettings {
|
||||
String? storageMode,
|
||||
String? downloadTreeUri,
|
||||
bool? autoFallback,
|
||||
bool? autoSkipUnavailableTracks,
|
||||
bool? smartQueueEnabled,
|
||||
bool? embedMetadata,
|
||||
bool? embedLyrics,
|
||||
bool? maxQualityCover,
|
||||
bool? isFirstLaunch,
|
||||
@@ -193,6 +209,8 @@ class AppSettings {
|
||||
bool? lyricsIncludeRomanizationNetease,
|
||||
bool? lyricsMultiPersonWordByWord,
|
||||
String? musixmatchLanguage,
|
||||
// Version upgrade tracking
|
||||
String? lastSeenVersion,
|
||||
}) {
|
||||
return AppSettings(
|
||||
defaultService: defaultService ?? this.defaultService,
|
||||
@@ -202,6 +220,10 @@ class AppSettings {
|
||||
storageMode: storageMode ?? this.storageMode,
|
||||
downloadTreeUri: downloadTreeUri ?? this.downloadTreeUri,
|
||||
autoFallback: autoFallback ?? this.autoFallback,
|
||||
autoSkipUnavailableTracks:
|
||||
autoSkipUnavailableTracks ?? this.autoSkipUnavailableTracks,
|
||||
smartQueueEnabled: smartQueueEnabled ?? this.smartQueueEnabled,
|
||||
embedMetadata: embedMetadata ?? this.embedMetadata,
|
||||
embedLyrics: embedLyrics ?? this.embedLyrics,
|
||||
maxQualityCover: maxQualityCover ?? this.maxQualityCover,
|
||||
isFirstLaunch: isFirstLaunch ?? this.isFirstLaunch,
|
||||
@@ -264,6 +286,8 @@ class AppSettings {
|
||||
lyricsMultiPersonWordByWord:
|
||||
lyricsMultiPersonWordByWord ?? this.lyricsMultiPersonWordByWord,
|
||||
musixmatchLanguage: musixmatchLanguage ?? this.musixmatchLanguage,
|
||||
// Version upgrade tracking
|
||||
lastSeenVersion: lastSeenVersion ?? this.lastSeenVersion,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
|
||||
storageMode: json['storageMode'] as String? ?? 'app',
|
||||
downloadTreeUri: json['downloadTreeUri'] as String? ?? '',
|
||||
autoFallback: json['autoFallback'] as bool? ?? true,
|
||||
autoSkipUnavailableTracks: json['autoSkipUnavailableTracks'] as bool? ?? true,
|
||||
smartQueueEnabled: json['smartQueueEnabled'] as bool? ?? true,
|
||||
embedMetadata: json['embedMetadata'] as bool? ?? true,
|
||||
embedLyrics: json['embedLyrics'] as bool? ?? true,
|
||||
maxQualityCover: json['maxQualityCover'] as bool? ?? true,
|
||||
isFirstLaunch: json['isFirstLaunch'] as bool? ?? true,
|
||||
@@ -50,10 +53,7 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
|
||||
autoExportFailedDownloads:
|
||||
json['autoExportFailedDownloads'] as bool? ?? false,
|
||||
downloadNetworkMode: json['downloadNetworkMode'] as String? ?? 'any',
|
||||
networkCompatibilityMode:
|
||||
json['networkCompatibilityMode'] as bool? ??
|
||||
json['songLinkCompatibilityMode'] as bool? ??
|
||||
false,
|
||||
networkCompatibilityMode: json['networkCompatibilityMode'] as bool? ?? false,
|
||||
songLinkRegion: json['songLinkRegion'] as String? ?? 'US',
|
||||
localLibraryEnabled: json['localLibraryEnabled'] as bool? ?? false,
|
||||
localLibraryPath: json['localLibraryPath'] as String? ?? '',
|
||||
@@ -64,7 +64,14 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
|
||||
(json['lyricsProviders'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const ['lrclib', 'musixmatch', 'netease', 'apple_music', 'qqmusic'],
|
||||
const [
|
||||
'lrclib',
|
||||
'spotify_api',
|
||||
'musixmatch',
|
||||
'netease',
|
||||
'apple_music',
|
||||
'qqmusic',
|
||||
],
|
||||
lyricsIncludeTranslationNetease:
|
||||
json['lyricsIncludeTranslationNetease'] as bool? ?? false,
|
||||
lyricsIncludeRomanizationNetease:
|
||||
@@ -72,6 +79,7 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
|
||||
lyricsMultiPersonWordByWord:
|
||||
json['lyricsMultiPersonWordByWord'] as bool? ?? false,
|
||||
musixmatchLanguage: json['musixmatchLanguage'] as String? ?? '',
|
||||
lastSeenVersion: json['lastSeenVersion'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AppSettingsToJson(
|
||||
@@ -84,6 +92,9 @@ Map<String, dynamic> _$AppSettingsToJson(
|
||||
'storageMode': instance.storageMode,
|
||||
'downloadTreeUri': instance.downloadTreeUri,
|
||||
'autoFallback': instance.autoFallback,
|
||||
'autoSkipUnavailableTracks': instance.autoSkipUnavailableTracks,
|
||||
'smartQueueEnabled': instance.smartQueueEnabled,
|
||||
'embedMetadata': instance.embedMetadata,
|
||||
'embedLyrics': instance.embedLyrics,
|
||||
'maxQualityCover': instance.maxQualityCover,
|
||||
'isFirstLaunch': instance.isFirstLaunch,
|
||||
@@ -128,4 +139,5 @@ Map<String, dynamic> _$AppSettingsToJson(
|
||||
'lyricsIncludeRomanizationNetease': instance.lyricsIncludeRomanizationNetease,
|
||||
'lyricsMultiPersonWordByWord': instance.lyricsMultiPersonWordByWord,
|
||||
'musixmatchLanguage': instance.musixmatchLanguage,
|
||||
'lastSeenVersion': instance.lastSeenVersion,
|
||||
};
|
||||
|
||||
@@ -9,6 +9,8 @@ class Track {
|
||||
final String artistName;
|
||||
final String albumName;
|
||||
final String? albumArtist;
|
||||
final String? artistId;
|
||||
final String? albumId;
|
||||
final String? coverUrl;
|
||||
final String? isrc;
|
||||
final int duration;
|
||||
@@ -27,6 +29,8 @@ class Track {
|
||||
required this.artistName,
|
||||
required this.albumName,
|
||||
this.albumArtist,
|
||||
this.artistId,
|
||||
this.albumId,
|
||||
this.coverUrl,
|
||||
this.isrc,
|
||||
required this.duration,
|
||||
|
||||
@@ -12,6 +12,8 @@ Track _$TrackFromJson(Map<String, dynamic> json) => Track(
|
||||
artistName: json['artistName'] as String,
|
||||
albumName: json['albumName'] as String,
|
||||
albumArtist: json['albumArtist'] as String?,
|
||||
artistId: json['artistId'] as String?,
|
||||
albumId: json['albumId'] as String?,
|
||||
coverUrl: json['coverUrl'] as String?,
|
||||
isrc: json['isrc'] as String?,
|
||||
duration: (json['duration'] as num).toInt(),
|
||||
@@ -35,6 +37,8 @@ Map<String, dynamic> _$TrackToJson(Track instance) => <String, dynamic>{
|
||||
'artistName': instance.artistName,
|
||||
'albumName': instance.albumName,
|
||||
'albumArtist': instance.albumArtist,
|
||||
'artistId': instance.artistId,
|
||||
'albumId': instance.albumId,
|
||||
'coverUrl': instance.coverUrl,
|
||||
'isrc': instance.isrc,
|
||||
'duration': instance.duration,
|
||||
|
||||
Reference in New Issue
Block a user