mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-04-01 01:20:21 +02:00
- Add homeFeedProvider field to AppSettings with picker UI in extensions page - Update explore_provider to respect user's home feed provider preference - Add normalizeCoverReference() and normalizeRemoteHttpUrl() to filter invalid cover URLs (no scheme, no host, protocol-relative) - Apply cover URL normalization across all screens and providers to prevent 'no host specified in URI' errors from Qobuz - Propagate CoverURL from QobuzDownloadResult through Go backend so cover art is available even when request metadata is incomplete
87 lines
2.2 KiB
Dart
87 lines
2.2 KiB
Dart
String? normalizeOptionalString(String? value) {
|
|
if (value == null) return null;
|
|
final trimmed = value.trim();
|
|
if (trimmed.isEmpty) return null;
|
|
if (trimmed.toLowerCase() == 'null') return null;
|
|
return trimmed;
|
|
}
|
|
|
|
final RegExp _windowsAbsolutePathPattern = RegExp(r'^[A-Za-z]:[\\/]');
|
|
|
|
bool _looksLikeLocalReference(String value) {
|
|
return value.startsWith('/') ||
|
|
value.startsWith('content://') ||
|
|
value.startsWith('file://') ||
|
|
_windowsAbsolutePathPattern.hasMatch(value);
|
|
}
|
|
|
|
String? normalizeCoverReference(String? value) {
|
|
final normalized = normalizeOptionalString(value);
|
|
if (normalized == null) return null;
|
|
|
|
if (normalized.startsWith('//')) {
|
|
return 'https:$normalized';
|
|
}
|
|
|
|
if (normalized.startsWith('http://') ||
|
|
normalized.startsWith('https://') ||
|
|
_looksLikeLocalReference(normalized)) {
|
|
return normalized;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
String? normalizeRemoteHttpUrl(String? value) {
|
|
final normalized = normalizeCoverReference(value);
|
|
if (normalized == null) return null;
|
|
if (normalized.startsWith('http://') || normalized.startsWith('https://')) {
|
|
return normalized;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String formatSampleRateKHz(int sampleRate) {
|
|
final khz = sampleRate / 1000;
|
|
final precision = sampleRate % 1000 == 0 ? 0 : 1;
|
|
return '${khz.toStringAsFixed(precision)}kHz';
|
|
}
|
|
|
|
String? buildDisplayAudioQuality({
|
|
int? bitDepth,
|
|
int? sampleRate,
|
|
int? bitrateKbps,
|
|
String? format,
|
|
String? storedQuality,
|
|
}) {
|
|
if (bitrateKbps != null && bitrateKbps > 0) {
|
|
final normalizedFormat = normalizeOptionalString(format)?.toUpperCase();
|
|
return normalizedFormat != null
|
|
? '$normalizedFormat ${bitrateKbps}kbps'
|
|
: '${bitrateKbps}kbps';
|
|
}
|
|
|
|
if (bitDepth != null &&
|
|
bitDepth > 0 &&
|
|
sampleRate != null &&
|
|
sampleRate > 0) {
|
|
return '$bitDepth-bit/${formatSampleRateKHz(sampleRate)}';
|
|
}
|
|
|
|
return normalizeOptionalString(storedQuality);
|
|
}
|
|
|
|
bool isPlaceholderQualityLabel(String? quality) {
|
|
final normalized = normalizeOptionalString(quality)?.toLowerCase();
|
|
if (normalized == null) return false;
|
|
|
|
return const {
|
|
'best',
|
|
'lossless',
|
|
'hi-res',
|
|
'hi-res-max',
|
|
'high',
|
|
'cd',
|
|
}.contains(normalized);
|
|
}
|