mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-26 05:51:18 +02:00
4c83aeafb5
Port of the explicit-content indicator from SpotiFLAC-Web (issue #456): - Go: add explicit to ExtTrackMetadata (parsed generically from any extension via explicit/is_explicit/isExplicit), TrackMetadata, and AlbumTrackMetadata; built-in Deezer maps explicit_lyrics and explicit_content_lyrics == 1 - Dart: add explicit to the Track model with a tolerant parseExplicitFlag helper wired into all backend track parsers - UI: new ExplicitBadge rendered through buildQualityBadges on album, playlist, search, and home track rows
101 lines
2.7 KiB
Dart
101 lines
2.7 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;
|
|
}
|
|
|
|
/// Parses the parental-advisory flag from backend/extension payloads, which
|
|
/// may arrive as a bool, a 0/1 number, or a "true"/"1" string.
|
|
bool? parseExplicitFlag(dynamic value) {
|
|
if (value == null) return null;
|
|
if (value is bool) return value;
|
|
if (value is num) return value == 1;
|
|
if (value is String) {
|
|
final normalized = value.trim().toLowerCase();
|
|
if (normalized.isEmpty || normalized == 'null') return null;
|
|
return normalized == 'true' || normalized == '1';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
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);
|
|
}
|