stremio/apps/ios/VMC/UI/QualityBadges.swift
Jos Vooges | STH 252bce838b Add native VMC iOS client with VLC playback.
SwiftUI app against master API: link, profiles, browse, live TV, favorites sync, and TestFlight-ready assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 19:16:29 +02:00

158 lines
5.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
enum ResolutionLogo: String {
case uhd4k = "4K"
case fhd1080 = "1080p"
case hd720 = "720p"
case sd = "SD"
}
enum FeatureLogo: String {
case dolbyVision = "Dolby Vision"
case hdr = "HDR"
case atmos = "Atmos"
case dtsX = "DTS:X"
}
struct MediaVersionBadges {
let resolution: ResolutionLogo?
let features: [FeatureLogo]
static func parse(from file: MediaFileInfo) -> MediaVersionBadges {
let hay = [file.resolution, file.hdrType, file.audioCodec, file.videoCodec, file.label]
.compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
.joined(separator: " ")
return MediaVersionBadges(
resolution: parseResolution(hay),
features: parseFeatures(hay)
)
}
private static func parseResolution(_ hay: String) -> ResolutionLogo? {
guard !hay.isEmpty else { return nil }
if hay.range(of: #"(?:^|[^\d])(2160\s*p?|3840\s*[x×]\s*2160|4\s*k|uhd)(?:[^\d]|$)"#, options: [.regularExpression, .caseInsensitive]) != nil {
return .uhd4k
}
if hay.range(of: #"(?:^|[^\d])(1080\s*p?|1920\s*[x×]\s*1080|fhd)(?:[^\d]|$)"#, options: [.regularExpression, .caseInsensitive]) != nil {
return .fhd1080
}
if hay.range(of: #"(?:^|[^\d])(720\s*p?|1280\s*[x×]\s*720)(?:[^\d]|$)"#, options: [.regularExpression, .caseInsensitive]) != nil {
return .hd720
}
if hay.range(of: #"(?:^|[^\d])(480\s*p?|576\s*p?|sd)(?:[^\d]|$)"#, options: [.regularExpression, .caseInsensitive]) != nil {
return .sd
}
return nil
}
private static func parseFeatures(_ hay: String) -> [FeatureLogo] {
guard !hay.isEmpty else { return [] }
var out: [FeatureLogo] = []
if hay.range(of: #"dolby\s*vision|\bdovi\b|\bdv\b"#, options: [.regularExpression, .caseInsensitive]) != nil {
out.append(.dolbyVision)
} else if hay.range(of: #"hdr10\+?|\bhdr\b"#, options: [.regularExpression, .caseInsensitive]) != nil {
out.append(.hdr)
}
if hay.range(of: #"atmos"#, options: [.regularExpression, .caseInsensitive]) != nil {
out.append(.atmos)
} else if hay.range(of: #"dts[\s.:_-]?x\b"#, options: [.regularExpression, .caseInsensitive]) != nil {
out.append(.dtsX)
}
return out
}
}
struct QualityBadgeRow: View {
let file: MediaFileInfo
var body: some View {
let badges = MediaVersionBadges.parse(from: file)
HStack(spacing: 8) {
if let res = badges.resolution {
ResolutionChip(logo: res)
} else {
Text("Versie")
.font(.system(size: 13, weight: .semibold, design: .rounded))
.foregroundStyle(McColors.textDim)
}
ForEach(badges.features, id: \.rawValue) { feature in
FeatureChip(logo: feature)
}
if file.nodeOnline == false {
Text("offline")
.font(.system(size: 11, weight: .bold, design: .rounded))
.foregroundStyle(McColors.danger)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(McColors.danger.opacity(0.15), in: RoundedRectangle(cornerRadius: 4, style: .continuous))
}
Spacer(minLength: 0)
}
}
}
private struct ResolutionChip: View {
let logo: ResolutionLogo
var body: some View {
switch logo {
case .uhd4k:
plate("4K", fg: .black, bg: Color(white: 0.95), weight: .black, tracking: 0.8)
case .fhd1080:
plate("1080p", fg: .white, bg: Color(white: 0.1), weight: .bold, border: Color.white.opacity(0.65))
case .hd720:
plate("720p", fg: Color(white: 0.88), bg: .clear, weight: .semibold, border: Color.white.opacity(0.45))
case .sd:
plate("SD", fg: Color(white: 0.74), bg: .clear, weight: .semibold, border: Color.white.opacity(0.35))
}
}
private func plate(
_ text: String,
fg: Color,
bg: Color,
weight: Font.Weight,
tracking: CGFloat = 0.2,
border: Color? = nil
) -> some View {
Text(text)
.font(.system(size: 12, weight: weight, design: .rounded))
.tracking(tracking)
.foregroundStyle(fg)
.padding(.horizontal, 9)
.frame(minWidth: 36, minHeight: 26)
.background(bg, in: RoundedRectangle(cornerRadius: 4, style: .continuous))
.overlay {
if let border {
RoundedRectangle(cornerRadius: 4, style: .continuous)
.strokeBorder(border, lineWidth: 1)
}
}
}
}
private struct FeatureChip: View {
let logo: FeatureLogo
var body: some View {
let text: String = {
switch logo {
case .dolbyVision: return "VISION"
case .hdr: return "HDR"
case .atmos: return "ATMOS"
case .dtsX: return "DTS:X"
}
}()
Text(text)
.font(.system(size: 10, weight: .bold, design: .rounded))
.tracking(0.6)
.foregroundStyle(McColors.accent)
.padding(.horizontal, 8)
.frame(minHeight: 26)
.overlay {
RoundedRectangle(cornerRadius: 4, style: .continuous)
.strokeBorder(McColors.accent.opacity(0.55), lineWidth: 1)
}
}
}