jellyflood/Swiftfin/Views/LiveTVChannelItemWideElemen...

154 lines
5.8 KiB
Swift

//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//
import JellyfinAPI
import SwiftUI
struct LiveTVChannelItemWideElement: View {
@FocusState
private var focused: Bool
@State
private var loading: Bool = false
@State
private var isFocused: Bool = false
var channel: BaseItemDto
var currentProgram: BaseItemDto?
var currentProgramText: LiveTVChannelViewProgram
var nextProgramsText: [LiveTVChannelViewProgram]
var onSelect: (@escaping (Bool) -> Void) -> Void
var progressPercent: Double {
if let currentProgram = currentProgram {
let progressPercent = currentProgram.getLiveProgressPercentage()
if progressPercent > 1.0 {
return 1.0
} else {
return progressPercent
}
}
return 0
}
private var detailText: String {
guard let program = currentProgram else {
return ""
}
var text = ""
if let season = program.parentIndexNumber,
let episode = program.indexNumber
{
text.append("\(season)x\(episode) ")
} else if let episode = program.indexNumber {
text.append("\(episode) ")
}
if let title = program.episodeTitle {
text.append("\(title) ")
}
if let year = program.productionYear {
text.append("\(year) ")
}
if let rating = program.officialRating {
text.append("\(rating)")
}
return text
}
var body: some View {
ZStack {
ZStack {
HStack {
ZStack(alignment: .center) {
ImageView(channel.imageURL(.primary, maxWidth: 128))
.aspectRatio(contentMode: .fit)
.padding(.init(top: 0, leading: 0, bottom: 8, trailing: 0))
VStack(alignment: .center) {
Spacer()
.frame(maxHeight: .infinity)
GeometryReader { gp in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 3)
.fill(Color.gray)
.opacity(0.4)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 6, maxHeight: 6)
RoundedRectangle(cornerRadius: 6)
.fill(Color.jellyfinPurple)
.frame(width: CGFloat(progressPercent * gp.size.width), height: 6)
}
}
.frame(height: 6, alignment: .center)
.padding(.init(top: 0, leading: 4, bottom: 0, trailing: 4))
}
if loading {
ProgressView()
}
}
.aspectRatio(1.0, contentMode: .fit)
VStack(alignment: .leading) {
let channelNumber = channel.number != nil ? "\(channel.number ?? "") " : ""
let channelName = "\(channelNumber)\(channel.name ?? "?")"
Text(channelName)
.font(.body)
.lineLimit(1)
.foregroundColor(Color.jellyfinPurple)
.frame(alignment: .leading)
.padding(.init(top: 0, leading: 0, bottom: 4, trailing: 0))
programLabel(
timeText: currentProgramText.timeDisplay,
titleText: currentProgramText.title,
color: Color(.textHighlight)
)
if nextProgramsText.isNotEmpty {
let nextItem = nextProgramsText[0]
programLabel(timeText: nextItem.timeDisplay, titleText: nextItem.title, color: Color.gray)
}
if nextProgramsText.count > 1 {
let nextItem2 = nextProgramsText[1]
programLabel(timeText: nextItem2.timeDisplay, titleText: nextItem2.title, color: Color.gray)
}
Spacer()
}
Spacer()
}
.frame(alignment: .leading)
.padding()
.opacity(loading ? 0.5 : 1.0)
}
.background(RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color("BackgroundSecondaryColor")))
.frame(height: 128)
.onTapGesture {
onSelect { loadingState in
loading = loadingState
}
}
}
.background {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color("BackgroundColor"))
.shadow(color: Color(.shadow), radius: 4, x: 0, y: 0)
}
}
@ViewBuilder
func programLabel(timeText: String, titleText: String, color: Color) -> some View {
HStack(alignment: .top) {
Text(timeText)
.font(.footnote)
.lineLimit(2)
.foregroundColor(color)
.frame(width: 38, alignment: .leading)
Text(titleText)
.font(.footnote)
.lineLimit(2)
.foregroundColor(color)
}
}
}