update live tv cells for tvOS

This commit is contained in:
jhays 2022-05-01 21:21:06 -05:00
parent 80477c4bbd
commit 8bc87282ee
3 changed files with 152 additions and 72 deletions

View File

@ -20,8 +20,10 @@ final class LibraryListCoordinator: NavigationCoordinatable {
var search = makeSearch var search = makeSearch
@Route(.push) @Route(.push)
var library = makeLibrary var library = makeLibrary
#if os(iOS)
@Route(.push) @Route(.push)
var liveTV = makeLiveTV var liveTV = makeLiveTV
#endif
let viewModel: LibraryListViewModel let viewModel: LibraryListViewModel
@ -37,9 +39,11 @@ final class LibraryListCoordinator: NavigationCoordinatable {
SearchCoordinator(viewModel: viewModel) SearchCoordinator(viewModel: viewModel)
} }
#if os(iOS)
func makeLiveTV() -> LiveTVCoordinator { func makeLiveTV() -> LiveTVCoordinator {
LiveTVCoordinator() LiveTVCoordinator()
} }
#endif
@ViewBuilder @ViewBuilder
func makeStart() -> some View { func makeStart() -> some View {

View File

@ -18,14 +18,25 @@ struct LiveTVChannelItemElement: View {
private var isFocused: Bool = false private var isFocused: Bool = false
var channel: BaseItemDto var channel: BaseItemDto
var program: BaseItemDto? var currentProgram: BaseItemDto?
var startString = " " var currentProgramText: LiveTVChannelViewProgram
var endString = " " var nextProgramsText: [LiveTVChannelViewProgram]
var progressPercent = Double(0)
var onSelect: (@escaping (Bool) -> Void) -> Void 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 { private var detailText: String {
guard let program = program else { guard let program = currentProgram else {
return "" return ""
} }
var text = "" var text = ""
@ -60,62 +71,63 @@ struct LiveTVChannelItemElement: View {
}.frame(alignment: .top) }.frame(alignment: .top)
Spacer() Spacer()
} }
GeometryReader { gp in
VStack { VStack {
ImageView(channel.getPrimaryImage(maxWidth: 128)) ImageView(channel.getPrimaryImage(maxWidth: 192))
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 128, alignment: .center) .frame(width: 192, alignment: .center)
.padding(.init(top: 8, leading: 0, bottom: 0, trailing: 0)) }
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.padding(.init(top: 16, leading: 8, bottom: gp.size.height / 2, trailing: 0))
VStack {
Text(channel.name ?? "?") Text(channel.name ?? "?")
.font(.footnote) .font(.footnote)
.lineLimit(1) .lineLimit(1)
.frame(alignment: .center) .frame(alignment: .center)
Text(program?.name ?? L10n.notAvailableSlash) .foregroundColor(Color.jellyfinPurple)
.font(.body) .padding(.init(top: 0, leading: 0, bottom: 8, trailing: 0))
.lineLimit(1)
.foregroundColor(.green)
Text(detailText)
.font(.body)
.lineLimit(1)
.foregroundColor(.green)
Spacer()
HStack(alignment: .bottom) {
VStack {
Spacer()
HStack {
Text(startString)
.font(.footnote)
.lineLimit(1)
.frame(alignment: .leading)
Spacer() programLabel(timeText: currentProgramText.timeDisplay, titleText: currentProgramText.title,
color: Color("TextHighlightColor"), font: Font.system(size: 20, weight: .bold, design: .default))
Text(endString) if !nextProgramsText.isEmpty,
.font(.footnote) let nextItem = nextProgramsText[0]
.lineLimit(1) {
.frame(alignment: .trailing) programLabel(timeText: nextItem.timeDisplay, titleText: nextItem.title, color: Color.gray,
font: Font.system(size: 20, design: .default))
} }
if nextProgramsText.count > 1,
let nextItem2 = nextProgramsText[1]
{
programLabel(timeText: nextItem2.timeDisplay, titleText: nextItem2.title, color: Color.gray,
font: Font.system(size: 20, design: .default))
}
}
.frame(maxHeight: .infinity, alignment: .top)
.padding(.init(top: gp.size.height / 2, leading: 16, bottom: 56, trailing: 16))
.opacity(loading ? 0.5 : 1.0)
}
if loading {
ProgressView()
}
VStack {
GeometryReader { gp in GeometryReader { gp in
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 6) RoundedRectangle(cornerRadius: 6)
.fill(Color.gray) .fill(Color.gray)
.opacity(0.4) .opacity(0.4)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 12, maxHeight: 12) .frame(minWidth: 100, maxWidth: .infinity, minHeight: 8, maxHeight: 8)
RoundedRectangle(cornerRadius: 6) RoundedRectangle(cornerRadius: 6)
.fill(Color.jellyfinPurple) .fill(Color.jellyfinPurple)
.frame(width: CGFloat(progressPercent * gp.size.width), height: 12) .frame(width: CGFloat(progressPercent * gp.size.width), height: 8)
} }
.frame(alignment: .bottom) .frame(maxHeight: .infinity, alignment: .bottom)
.padding(.init(top: 0, leading: 16, bottom: 32, trailing: 16))
} }
} }
} }
}
.padding()
.opacity(loading ? 0.5 : 1.0)
if loading {
ProgressView()
}
}
.overlay(RoundedRectangle(cornerRadius: 20) .overlay(RoundedRectangle(cornerRadius: 20)
.stroke(isFocused ? Color.blue : Color.clear, lineWidth: 4)) .stroke(isFocused ? Color.blue : Color.clear, lineWidth: 4))
.cornerRadius(20) .cornerRadius(20)
@ -133,4 +145,20 @@ struct LiveTVChannelItemElement: View {
} }
} }
} }
@ViewBuilder
func programLabel(timeText: String, titleText: String, color: Color, font: Font) -> some View {
HStack(alignment: .top, spacing: 4) {
Text(timeText)
.font(font)
.lineLimit(1)
.foregroundColor(color)
.frame(width: 54, alignment: .leading)
Text(titleText)
.font(font)
.lineLimit(2)
.foregroundColor(color)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
} }

View File

@ -11,6 +11,8 @@ import JellyfinAPI
import SwiftUI import SwiftUI
import SwiftUICollection import SwiftUICollection
typealias LiveTVChannelViewProgram = (timeDisplay: String, title: String)
struct LiveTVChannelsView: View { struct LiveTVChannelsView: View {
@EnvironmentObject @EnvironmentObject
var router: LiveTVChannelsCoordinator.Router var router: LiveTVChannelsCoordinator.Router
@ -53,13 +55,21 @@ struct LiveTVChannelsView: View {
func makeCellView(indexPath: IndexPath, cell: LiveTVChannelRowCell) -> some View { func makeCellView(indexPath: IndexPath, cell: LiveTVChannelRowCell) -> some View {
let item = cell.item let item = cell.item
let channel = item.channel let channel = item.channel
if channel.type != "Folder" { let currentProgramDisplayText = item.currentProgram?
let progressPercent = item.program?.getLiveProgressPercentage() ?? 0 .programDisplayText(timeFormatter: viewModel.timeFormatter) ?? LiveTVChannelViewProgram(timeDisplay: "", title: "")
let nextItems = item.programs.filter { program in
guard let start = program.startDate else {
return false
}
guard let currentStart = item.currentProgram?.startDate else {
return false
}
return start > currentStart
}
LiveTVChannelItemElement(channel: channel, LiveTVChannelItemElement(channel: channel,
program: item.program, currentProgram: item.currentProgram,
startString: item.program?.getLiveStartTimeString(formatter: viewModel.timeFormatter) ?? " ", currentProgramText: currentProgramDisplayText,
endString: item.program?.getLiveEndTimeString(formatter: viewModel.timeFormatter) ?? " ", nextProgramsText: nextProgramsDisplayText(nextItems: nextItems, timeFormatter: viewModel.timeFormatter),
progressPercent: progressPercent > 1.0 ? 1.0 : progressPercent,
onSelect: { loadingAction in onSelect: { loadingAction in
loadingAction(true) loadingAction(true)
self.viewModel.fetchVideoPlayerViewModel(item: channel) { playerViewModel in self.viewModel.fetchVideoPlayerViewModel(item: channel) { playerViewModel in
@ -70,7 +80,6 @@ struct LiveTVChannelsView: View {
} }
}) })
} }
}
private func createGridLayout() -> NSCollectionLayoutSection { private func createGridLayout() -> NSCollectionLayoutSection {
// I don't know why tvOS has a margin on the sides of a collection view // I don't know why tvOS has a margin on the sides of a collection view
@ -100,4 +109,43 @@ struct LiveTVChannelsView: View {
return section return section
} }
private func nextProgramsDisplayText(nextItems: [BaseItemDto], timeFormatter: DateFormatter) -> [LiveTVChannelViewProgram] {
var programsDisplayText: [LiveTVChannelViewProgram] = []
for item in nextItems {
programsDisplayText.append(item.programDisplayText(timeFormatter: timeFormatter))
}
return programsDisplayText
}
}
private extension BaseItemDto {
func programDisplayText(timeFormatter: DateFormatter) -> LiveTVChannelViewProgram {
var timeText = ""
if let start = self.startDate {
timeText.append(timeFormatter.string(from: start) + " ")
}
var displayText = ""
if let season = self.parentIndexNumber,
let episode = self.indexNumber
{
displayText.append("\(season)x\(episode) ")
} else if let episode = self.indexNumber {
displayText.append("\(episode) ")
}
if let name = self.name {
displayText.append("\(name) ")
}
if let title = self.episodeTitle {
displayText.append("\(title) ")
}
if let year = self.productionYear {
displayText.append("\(year) ")
}
if let rating = self.officialRating {
displayText.append("\(rating)")
}
return LiveTVChannelViewProgram(timeDisplay: timeText, title: displayText)
}
} }