swiftformat
This commit is contained in:
parent
a8f8a93efc
commit
80477c4bbd
|
@ -20,8 +20,8 @@ final class LibraryListCoordinator: NavigationCoordinatable {
|
||||||
var search = makeSearch
|
var search = makeSearch
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var library = makeLibrary
|
var library = makeLibrary
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var liveTV = makeLiveTV
|
var liveTV = makeLiveTV
|
||||||
|
|
||||||
let viewModel: LibraryListViewModel
|
let viewModel: LibraryListViewModel
|
||||||
|
|
||||||
|
@ -36,10 +36,10 @@ final class LibraryListCoordinator: NavigationCoordinatable {
|
||||||
func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator {
|
func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator {
|
||||||
SearchCoordinator(viewModel: viewModel)
|
SearchCoordinator(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLiveTV() -> LiveTVCoordinator {
|
func makeLiveTV() -> LiveTVCoordinator {
|
||||||
LiveTVCoordinator()
|
LiveTVCoordinator()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
|
|
|
@ -24,7 +24,7 @@ final class LiveTVChannelsCoordinator: NavigationCoordinatable {
|
||||||
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
||||||
NavigationViewCoordinator(ItemCoordinator(item: item))
|
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeVideoPlayer(viewModel: VideoPlayerViewModel) -> NavigationViewCoordinator<LiveTVVideoPlayerCoordinator> {
|
func makeVideoPlayer(viewModel: VideoPlayerViewModel) -> NavigationViewCoordinator<LiveTVVideoPlayerCoordinator> {
|
||||||
NavigationViewCoordinator(LiveTVVideoPlayerCoordinator(viewModel: viewModel))
|
NavigationViewCoordinator(LiveTVVideoPlayerCoordinator(viewModel: viewModel))
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,19 +12,19 @@ import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
final class LiveTVCoordinator: NavigationCoordinatable {
|
final class LiveTVCoordinator: NavigationCoordinatable {
|
||||||
let stack = NavigationStack(initial: \LiveTVCoordinator.start)
|
let stack = NavigationStack(initial: \LiveTVCoordinator.start)
|
||||||
|
|
||||||
@Root
|
@Root
|
||||||
var start = makeStart
|
var start = makeStart
|
||||||
@Route(.fullScreen)
|
@Route(.fullScreen)
|
||||||
var videoPlayer = makeVideoPlayer
|
var videoPlayer = makeVideoPlayer
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
LiveTVChannelsView()
|
LiveTVChannelsView()
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeVideoPlayer(viewModel: VideoPlayerViewModel) -> NavigationViewCoordinator<LiveTVVideoPlayerCoordinator> {
|
func makeVideoPlayer(viewModel: VideoPlayerViewModel) -> NavigationViewCoordinator<LiveTVVideoPlayerCoordinator> {
|
||||||
NavigationViewCoordinator(LiveTVVideoPlayerCoordinator(viewModel: viewModel))
|
NavigationViewCoordinator(LiveTVVideoPlayerCoordinator(viewModel: viewModel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,28 +13,28 @@ import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
final class LiveTVVideoPlayerCoordinator: NavigationCoordinatable {
|
final class LiveTVVideoPlayerCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \LiveTVVideoPlayerCoordinator.start)
|
let stack = NavigationStack(initial: \LiveTVVideoPlayerCoordinator.start)
|
||||||
|
|
||||||
@Root
|
@Root
|
||||||
var start = makeStart
|
var start = makeStart
|
||||||
|
|
||||||
let viewModel: VideoPlayerViewModel
|
let viewModel: VideoPlayerViewModel
|
||||||
|
|
||||||
init(viewModel: VideoPlayerViewModel) {
|
init(viewModel: VideoPlayerViewModel) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
if Defaults[.Experimental.liveTVNativePlayer] {
|
if Defaults[.Experimental.liveTVNativePlayer] {
|
||||||
LiveTVNativePlayerView(viewModel: viewModel)
|
LiveTVNativePlayerView(viewModel: viewModel)
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
} else {
|
} else {
|
||||||
LiveTVPlayerView(viewModel: viewModel)
|
LiveTVPlayerView(viewModel: viewModel)
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ struct LiveTVChannelProgram: Hashable {
|
||||||
let id = UUID()
|
let id = UUID()
|
||||||
let channel: BaseItemDto
|
let channel: BaseItemDto
|
||||||
let currentProgram: BaseItemDto?
|
let currentProgram: BaseItemDto?
|
||||||
let programs: [BaseItemDto]
|
let programs: [BaseItemDto]
|
||||||
}
|
}
|
||||||
|
|
||||||
final class LiveTVChannelsViewModel: ViewModel {
|
final class LiveTVChannelsViewModel: ViewModel {
|
||||||
|
|
|
@ -16,17 +16,17 @@ struct LibraryListView: View {
|
||||||
var libraryListRouter: LibraryListCoordinator.Router
|
var libraryListRouter: LibraryListCoordinator.Router
|
||||||
@StateObject
|
@StateObject
|
||||||
var viewModel = LibraryListViewModel()
|
var viewModel = LibraryListViewModel()
|
||||||
|
|
||||||
@Default(.Experimental.liveTVAlphaEnabled)
|
@Default(.Experimental.liveTVAlphaEnabled)
|
||||||
var liveTVAlphaEnabled
|
var liveTVAlphaEnabled
|
||||||
|
|
||||||
var supportedCollectionTypes: [String] {
|
var supportedCollectionTypes: [String] {
|
||||||
if liveTVAlphaEnabled {
|
if liveTVAlphaEnabled {
|
||||||
return ["movies", "tvshows", "livetv", "boxsets", "other"]
|
return ["movies", "tvshows", "livetv", "boxsets", "other"]
|
||||||
} else {
|
} else {
|
||||||
return ["movies", "tvshows", "boxsets", "other"]
|
return ["movies", "tvshows", "boxsets", "other"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
@ -59,13 +59,13 @@ struct LibraryListView: View {
|
||||||
return self.supportedCollectionTypes.contains(collectionType)
|
return self.supportedCollectionTypes.contains(collectionType)
|
||||||
}, id: \.id) { library in
|
}, id: \.id) { library in
|
||||||
Button {
|
Button {
|
||||||
if library.collectionType == "livetv" {
|
if library.collectionType == "livetv" {
|
||||||
libraryListRouter.route(to: \.liveTV)
|
libraryListRouter.route(to: \.liveTV)
|
||||||
} else {
|
} else {
|
||||||
libraryListRouter.route(to: \.library,
|
libraryListRouter.route(to: \.library,
|
||||||
(viewModel: LibraryViewModel(parentID: library.id),
|
(viewModel: LibraryViewModel(parentID: library.id),
|
||||||
title: library.name ?? ""))
|
title: library.name ?? ""))
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
ImageView(library.getPrimaryImage(maxWidth: 500), blurHash: library.getPrimaryImageBlurHash())
|
ImageView(library.getPrimaryImage(maxWidth: 500), blurHash: library.getPrimaryImageBlurHash())
|
||||||
|
|
|
@ -10,113 +10,113 @@ import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct LiveTVChannelItemElement: View {
|
struct LiveTVChannelItemElement: View {
|
||||||
@FocusState
|
@FocusState
|
||||||
private var focused: Bool
|
private var focused: Bool
|
||||||
@State
|
@State
|
||||||
private var loading: Bool = false
|
private var loading: Bool = false
|
||||||
@State
|
@State
|
||||||
private var isFocused: Bool = false
|
private var isFocused: Bool = false
|
||||||
|
|
||||||
var channel: BaseItemDto
|
var channel: BaseItemDto
|
||||||
var program: BaseItemDto?
|
var program: BaseItemDto?
|
||||||
var startString = " "
|
var startString = " "
|
||||||
var endString = " "
|
var endString = " "
|
||||||
var progressPercent = Double(0)
|
var progressPercent = Double(0)
|
||||||
var onSelect: (@escaping (Bool) -> Void) -> Void
|
var onSelect: (@escaping (Bool) -> Void) -> Void
|
||||||
|
|
||||||
private var detailText: String {
|
private var detailText: String {
|
||||||
guard let program = program else {
|
guard let program = program else {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var text = ""
|
var text = ""
|
||||||
if let season = program.parentIndexNumber,
|
if let season = program.parentIndexNumber,
|
||||||
let episode = program.indexNumber
|
let episode = program.indexNumber
|
||||||
{
|
{
|
||||||
text.append("\(season)x\(episode) ")
|
text.append("\(season)x\(episode) ")
|
||||||
} else if let episode = program.indexNumber {
|
} else if let episode = program.indexNumber {
|
||||||
text.append("\(episode) ")
|
text.append("\(episode) ")
|
||||||
}
|
}
|
||||||
if let title = program.episodeTitle {
|
if let title = program.episodeTitle {
|
||||||
text.append("\(title) ")
|
text.append("\(title) ")
|
||||||
}
|
}
|
||||||
if let year = program.productionYear {
|
if let year = program.productionYear {
|
||||||
text.append("\(year) ")
|
text.append("\(year) ")
|
||||||
}
|
}
|
||||||
if let rating = program.officialRating {
|
if let rating = program.officialRating {
|
||||||
text.append("\(rating)")
|
text.append("\(rating)")
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
Text(channel.number ?? "")
|
Text(channel.number ?? "")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.frame(alignment: .leading)
|
.frame(alignment: .leading)
|
||||||
.padding()
|
.padding()
|
||||||
Spacer()
|
Spacer()
|
||||||
}.frame(alignment: .top)
|
}.frame(alignment: .top)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
VStack {
|
VStack {
|
||||||
ImageView(channel.getPrimaryImage(maxWidth: 128))
|
ImageView(channel.getPrimaryImage(maxWidth: 128))
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.frame(width: 128, alignment: .center)
|
.frame(width: 128, alignment: .center)
|
||||||
.padding(.init(top: 8, leading: 0, bottom: 0, trailing: 0))
|
.padding(.init(top: 8, leading: 0, bottom: 0, trailing: 0))
|
||||||
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)
|
Text(program?.name ?? L10n.notAvailableSlash)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.foregroundColor(.green)
|
.foregroundColor(.green)
|
||||||
Text(detailText)
|
Text(detailText)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.foregroundColor(.green)
|
.foregroundColor(.green)
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack(alignment: .bottom) {
|
HStack(alignment: .bottom) {
|
||||||
VStack {
|
VStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack {
|
HStack {
|
||||||
Text(startString)
|
Text(startString)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.frame(alignment: .leading)
|
.frame(alignment: .leading)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text(endString)
|
Text(endString)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.frame(alignment: .trailing)
|
.frame(alignment: .trailing)
|
||||||
}
|
}
|
||||||
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: 12, maxHeight: 12)
|
||||||
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: 12)
|
||||||
}
|
}
|
||||||
.frame(alignment: .bottom)
|
.frame(alignment: .bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.opacity(loading ? 0.5 : 1.0)
|
.opacity(loading ? 0.5 : 1.0)
|
||||||
|
|
||||||
if loading {
|
if loading {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay(RoundedRectangle(cornerRadius: 0)
|
.overlay(RoundedRectangle(cornerRadius: 0)
|
||||||
.stroke(Color.blue, lineWidth: 0))
|
.stroke(Color.blue, lineWidth: 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,143 +10,142 @@ import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct LiveTVChannelItemWideElement: View {
|
struct LiveTVChannelItemWideElement: View {
|
||||||
@FocusState
|
@FocusState
|
||||||
private var focused: Bool
|
private var focused: Bool
|
||||||
@State
|
@State
|
||||||
private var loading: Bool = false
|
private var loading: Bool = false
|
||||||
@State
|
@State
|
||||||
private var isFocused: Bool = false
|
private var isFocused: Bool = false
|
||||||
|
|
||||||
var channel: BaseItemDto
|
var channel: BaseItemDto
|
||||||
var currentProgram: BaseItemDto?
|
var currentProgram: BaseItemDto?
|
||||||
var currentProgramText: LiveTVChannelViewProgram
|
var currentProgramText: LiveTVChannelViewProgram
|
||||||
var nextProgramsText: [LiveTVChannelViewProgram]
|
var nextProgramsText: [LiveTVChannelViewProgram]
|
||||||
var onSelect: (@escaping (Bool) -> Void) -> Void
|
var onSelect: (@escaping (Bool) -> Void) -> Void
|
||||||
|
|
||||||
var progressPercent: Double {
|
var progressPercent: Double {
|
||||||
if let currentProgram = currentProgram {
|
if let currentProgram = currentProgram {
|
||||||
let progressPercent = currentProgram.getLiveProgressPercentage()
|
let progressPercent = currentProgram.getLiveProgressPercentage()
|
||||||
if progressPercent > 1.0 {
|
if progressPercent > 1.0 {
|
||||||
return 1.0
|
return 1.0
|
||||||
} else {
|
} else {
|
||||||
return progressPercent
|
return progressPercent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var detailText: String {
|
||||||
private var detailText: String {
|
guard let program = currentProgram else {
|
||||||
guard let program = currentProgram else {
|
return ""
|
||||||
return ""
|
}
|
||||||
}
|
var text = ""
|
||||||
var text = ""
|
if let season = program.parentIndexNumber,
|
||||||
if let season = program.parentIndexNumber,
|
let episode = program.indexNumber
|
||||||
let episode = program.indexNumber
|
{
|
||||||
{
|
text.append("\(season)x\(episode) ")
|
||||||
text.append("\(season)x\(episode) ")
|
} else if let episode = program.indexNumber {
|
||||||
} else if let episode = program.indexNumber {
|
text.append("\(episode) ")
|
||||||
text.append("\(episode) ")
|
}
|
||||||
}
|
if let title = program.episodeTitle {
|
||||||
if let title = program.episodeTitle {
|
text.append("\(title) ")
|
||||||
text.append("\(title) ")
|
}
|
||||||
}
|
if let year = program.productionYear {
|
||||||
if let year = program.productionYear {
|
text.append("\(year) ")
|
||||||
text.append("\(year) ")
|
}
|
||||||
}
|
if let rating = program.officialRating {
|
||||||
if let rating = program.officialRating {
|
text.append("\(rating)")
|
||||||
text.append("\(rating)")
|
}
|
||||||
}
|
return text
|
||||||
return text
|
}
|
||||||
}
|
|
||||||
|
var body: some View {
|
||||||
var body: some View {
|
ZStack {
|
||||||
ZStack {
|
ZStack {
|
||||||
ZStack {
|
HStack {
|
||||||
HStack {
|
ZStack(alignment: .center) {
|
||||||
ZStack(alignment: .center) {
|
ImageView(channel.getPrimaryImage(maxWidth: 128))
|
||||||
ImageView(channel.getPrimaryImage(maxWidth: 128))
|
.aspectRatio(contentMode: .fit)
|
||||||
.aspectRatio(contentMode: .fit)
|
.padding(.init(top: 0, leading: 0, bottom: 8, trailing: 0))
|
||||||
.padding(.init(top: 0, leading: 0, bottom: 8, trailing: 0))
|
VStack(alignment: .center) {
|
||||||
VStack(alignment: .center) {
|
Spacer()
|
||||||
Spacer()
|
.frame(maxHeight: .infinity)
|
||||||
.frame(maxHeight: .infinity)
|
GeometryReader { gp in
|
||||||
GeometryReader { gp in
|
ZStack(alignment: .leading) {
|
||||||
ZStack(alignment: .leading) {
|
RoundedRectangle(cornerRadius: 3)
|
||||||
RoundedRectangle(cornerRadius: 3)
|
.fill(Color.gray)
|
||||||
.fill(Color.gray)
|
.opacity(0.4)
|
||||||
.opacity(0.4)
|
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 6, maxHeight: 6)
|
||||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 6, maxHeight: 6)
|
RoundedRectangle(cornerRadius: 6)
|
||||||
RoundedRectangle(cornerRadius: 6)
|
.fill(Color.jellyfinPurple)
|
||||||
.fill(Color.jellyfinPurple)
|
.frame(width: CGFloat(progressPercent * gp.size.width), height: 6)
|
||||||
.frame(width: CGFloat(progressPercent * gp.size.width), height: 6)
|
}
|
||||||
}
|
}
|
||||||
}
|
.frame(height: 6, alignment: .center)
|
||||||
.frame(height: 6, alignment: .center)
|
.padding(.init(top: 0, leading: 4, bottom: 0, trailing: 4))
|
||||||
.padding(.init(top: 0, leading: 4, bottom: 0, trailing: 4))
|
}
|
||||||
}
|
if loading {
|
||||||
if loading {
|
|
||||||
|
ProgressView()
|
||||||
ProgressView()
|
}
|
||||||
|
}
|
||||||
}
|
.aspectRatio(1.0, contentMode: .fit)
|
||||||
}
|
VStack(alignment: .leading) {
|
||||||
.aspectRatio(1.0, contentMode: .fit)
|
let channelNumber = channel.number != nil ? "\(channel.number ?? "") " : ""
|
||||||
VStack(alignment: .leading) {
|
let channelName = "\(channelNumber)\(channel.name ?? "?")"
|
||||||
let channelNumber = channel.number != nil ? "\(channel.number ?? "") " : ""
|
Text(channelName)
|
||||||
let channelName = "\(channelNumber)\(channel.name ?? "?")"
|
.font(.body)
|
||||||
Text(channelName)
|
.lineLimit(1)
|
||||||
.font(.body)
|
.foregroundColor(Color.jellyfinPurple)
|
||||||
.lineLimit(1)
|
.frame(alignment: .leading)
|
||||||
.foregroundColor(Color.jellyfinPurple)
|
.padding(.init(top: 0, leading: 0, bottom: 4, trailing: 0))
|
||||||
.frame(alignment: .leading)
|
programLabel(timeText: currentProgramText.timeDisplay, titleText: currentProgramText.title,
|
||||||
.padding(.init(top: 0, leading: 0, bottom: 4, trailing: 0))
|
color: Color("TextHighlightColor"))
|
||||||
programLabel(timeText: currentProgramText.timeDisplay, titleText: currentProgramText.title, color: Color("TextHighlightColor"))
|
if !nextProgramsText.isEmpty,
|
||||||
if nextProgramsText.count > 0,
|
let nextItem = nextProgramsText[0]
|
||||||
let nextItem = nextProgramsText[0] {
|
{
|
||||||
programLabel(timeText: nextItem.timeDisplay, titleText: nextItem.title, color: Color.gray)
|
programLabel(timeText: nextItem.timeDisplay, titleText: nextItem.title, color: Color.gray)
|
||||||
}
|
}
|
||||||
if nextProgramsText.count > 1,
|
if nextProgramsText.count > 1,
|
||||||
let nextItem2 = nextProgramsText[1] {
|
let nextItem2 = nextProgramsText[1]
|
||||||
programLabel(timeText: nextItem2.timeDisplay, titleText: nextItem2.title, color: Color.gray)
|
{
|
||||||
}
|
programLabel(timeText: nextItem2.timeDisplay, titleText: nextItem2.title, color: Color.gray)
|
||||||
Spacer()
|
}
|
||||||
}
|
Spacer()
|
||||||
Spacer()
|
}
|
||||||
}
|
Spacer()
|
||||||
.frame(alignment: .leading)
|
}
|
||||||
.padding()
|
.frame(alignment: .leading)
|
||||||
.opacity(loading ? 0.5 : 1.0)
|
.padding()
|
||||||
}
|
.opacity(loading ? 0.5 : 1.0)
|
||||||
.background(
|
}
|
||||||
RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color("BackgroundSecondaryColor"))
|
.background(RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color("BackgroundSecondaryColor")))
|
||||||
)
|
.frame(height: 128)
|
||||||
.frame(height: 128)
|
.onTapGesture {
|
||||||
.onTapGesture {
|
onSelect { loadingState in
|
||||||
onSelect { loadingState in
|
loading = loadingState
|
||||||
loading = loadingState
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.background {
|
||||||
.background{
|
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
.fill(Color("BackgroundColor"))
|
||||||
.fill(Color("BackgroundColor"))
|
.shadow(color: Color("ShadowColor"), radius: 4, x: 0, y: 0)
|
||||||
.shadow(color: Color("ShadowColor"), radius: 4, x: 0, y: 0)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ViewBuilder
|
||||||
@ViewBuilder
|
func programLabel(timeText: String, titleText: String, color: Color) -> some View {
|
||||||
func programLabel(timeText: String, titleText: String, color: Color) -> some View {
|
HStack(alignment: .top) {
|
||||||
HStack(alignment: .top) {
|
Text(timeText)
|
||||||
Text(timeText)
|
.font(.footnote)
|
||||||
.font(.footnote)
|
.lineLimit(2)
|
||||||
.lineLimit(2)
|
.foregroundColor(color)
|
||||||
.foregroundColor(color)
|
.frame(width: 38, alignment: .leading)
|
||||||
.frame(width: 38, alignment: .leading)
|
Text(titleText)
|
||||||
Text(titleText)
|
.font(.footnote)
|
||||||
.font(.footnote)
|
.lineLimit(2)
|
||||||
.lineLimit(2)
|
.foregroundColor(color)
|
||||||
.foregroundColor(color)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,204 +14,179 @@ import SwiftUICollection
|
||||||
typealias LiveTVChannelViewProgram = (timeDisplay: String, title: String)
|
typealias LiveTVChannelViewProgram = (timeDisplay: String, title: String)
|
||||||
|
|
||||||
struct LiveTVChannelsView: View {
|
struct LiveTVChannelsView: View {
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var router: LiveTVCoordinator.Router
|
var router: LiveTVCoordinator.Router
|
||||||
@StateObject
|
@StateObject
|
||||||
var viewModel = LiveTVChannelsViewModel()
|
var viewModel = LiveTVChannelsViewModel()
|
||||||
@State private var isPortrait = false
|
@State
|
||||||
|
private var isPortrait = false
|
||||||
var body: some View {
|
|
||||||
if viewModel.isLoading == true {
|
|
||||||
ProgressView()
|
|
||||||
} else if !viewModel.rows.isEmpty {
|
|
||||||
CollectionView(rows: viewModel.rows) { _, _ in
|
|
||||||
createGridLayout()
|
|
||||||
} cell: { indexPath, cell in
|
|
||||||
makeCellView(indexPath: indexPath, cell: cell)
|
|
||||||
} supplementaryView: { _, indexPath in
|
|
||||||
EmptyView()
|
|
||||||
.accessibilityIdentifier("\(indexPath.section).\(indexPath.row)")
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.ignoresSafeArea()
|
|
||||||
.onAppear {
|
|
||||||
viewModel.startScheduleCheckTimer()
|
|
||||||
self.checkOrientation()
|
|
||||||
}
|
|
||||||
.onDisappear {
|
|
||||||
viewModel.stopScheduleCheckTimer()
|
|
||||||
}
|
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
|
||||||
self.checkOrientation()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
VStack {
|
|
||||||
Text("No results.")
|
|
||||||
Button {
|
|
||||||
viewModel.getChannels()
|
|
||||||
} label: {
|
|
||||||
Text("Reload")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
var body: some View {
|
||||||
func makeCellView(indexPath: IndexPath, cell: LiveTVChannelRowCell) -> some View {
|
if viewModel.isLoading == true {
|
||||||
let item = cell.item
|
ProgressView()
|
||||||
let channel = item.channel
|
} else if !viewModel.rows.isEmpty {
|
||||||
let currentProgramDisplayText = item.currentProgram?.programDisplayText(timeFormatter: viewModel.timeFormatter) ?? LiveTVChannelViewProgram(timeDisplay: "", title: "")
|
CollectionView(rows: viewModel.rows) { _, _ in
|
||||||
let nextItems = item.programs.filter { program in
|
createGridLayout()
|
||||||
guard let start = program.startDate else {
|
} cell: { indexPath, cell in
|
||||||
return false
|
makeCellView(indexPath: indexPath, cell: cell)
|
||||||
}
|
} supplementaryView: { _, indexPath in
|
||||||
guard let currentStart = item.currentProgram?.startDate else {
|
EmptyView()
|
||||||
return false
|
.accessibilityIdentifier("\(indexPath.section).\(indexPath.row)")
|
||||||
}
|
}
|
||||||
return start > currentStart
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
}
|
.ignoresSafeArea()
|
||||||
LiveTVChannelItemWideElement(channel: channel,
|
.onAppear {
|
||||||
currentProgram: item.currentProgram,
|
viewModel.startScheduleCheckTimer()
|
||||||
currentProgramText: currentProgramDisplayText,
|
self.checkOrientation()
|
||||||
nextProgramsText: nextProgramsDisplayText(nextItems: nextItems, timeFormatter: viewModel.timeFormatter),
|
}
|
||||||
onSelect: { loadingAction in
|
.onDisappear {
|
||||||
loadingAction(true)
|
viewModel.stopScheduleCheckTimer()
|
||||||
self.viewModel.fetchVideoPlayerViewModel(item: channel) { playerViewModel in
|
}
|
||||||
self.router.route(to: \.videoPlayer, playerViewModel)
|
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
self.checkOrientation()
|
||||||
loadingAction(false)
|
}
|
||||||
}
|
} else {
|
||||||
}
|
VStack {
|
||||||
})
|
Text("No results.")
|
||||||
}
|
Button {
|
||||||
|
viewModel.getChannels()
|
||||||
private func createGridLayout() -> NSCollectionLayoutSection {
|
} label: {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
Text("Reload")
|
||||||
let itemSize = NSCollectionLayoutSize(
|
}
|
||||||
widthDimension: .absolute((UIScreen.main.bounds.width / 2) - 16),
|
}
|
||||||
heightDimension: .fractionalHeight(1)
|
}
|
||||||
)
|
}
|
||||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
|
||||||
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(
|
|
||||||
leading: .flexible(0), top: nil,
|
|
||||||
trailing: .flexible(2), bottom: .flexible(2)
|
|
||||||
)
|
|
||||||
let item2 = NSCollectionLayoutItem(layoutSize: itemSize)
|
|
||||||
item2.edgeSpacing = NSCollectionLayoutEdgeSpacing(
|
|
||||||
leading: nil, top: nil,
|
|
||||||
trailing: .flexible(0), bottom: .flexible(2)
|
|
||||||
)
|
|
||||||
let groupSize = NSCollectionLayoutSize(
|
|
||||||
widthDimension: .fractionalWidth(1.0),
|
|
||||||
heightDimension: .absolute(144)
|
|
||||||
)
|
|
||||||
let group = NSCollectionLayoutGroup.horizontal(
|
|
||||||
layoutSize: groupSize,
|
|
||||||
subitems: [item, item2]
|
|
||||||
)
|
|
||||||
let section = NSCollectionLayoutSection(group: group)
|
|
||||||
return section
|
|
||||||
} else {
|
|
||||||
if isPortrait {
|
|
||||||
let itemSize = NSCollectionLayoutSize(
|
|
||||||
widthDimension: .absolute(UIScreen.main.bounds.width - 32),
|
|
||||||
heightDimension: .fractionalHeight(1)
|
|
||||||
)
|
|
||||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
|
||||||
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(
|
|
||||||
leading: .flexible(0), top: nil,
|
|
||||||
trailing: .flexible(2), bottom: .flexible(2)
|
|
||||||
)
|
|
||||||
let groupSize = NSCollectionLayoutSize(
|
|
||||||
widthDimension: .fractionalWidth(1.0),
|
|
||||||
heightDimension: .absolute(144)
|
|
||||||
)
|
|
||||||
let group = NSCollectionLayoutGroup.horizontal(
|
|
||||||
layoutSize: groupSize,
|
|
||||||
subitems: [item]
|
|
||||||
)
|
|
||||||
let section = NSCollectionLayoutSection(group: group)
|
|
||||||
return section
|
|
||||||
} else {
|
|
||||||
|
|
||||||
let scenes = UIApplication.shared.connectedScenes
|
|
||||||
let windowScene = scenes.first as? UIWindowScene
|
|
||||||
var width = (UIScreen.main.bounds.width / 2) - 32
|
|
||||||
if let safeArea = windowScene?.keyWindow?.safeAreaInsets {
|
|
||||||
width = (UIScreen.main.bounds.width / 2) - safeArea.left - safeArea.right
|
|
||||||
}
|
|
||||||
|
|
||||||
let itemSize = NSCollectionLayoutSize(
|
@ViewBuilder
|
||||||
widthDimension: .absolute(width),
|
func makeCellView(indexPath: IndexPath, cell: LiveTVChannelRowCell) -> some View {
|
||||||
heightDimension: .fractionalHeight(1)
|
let item = cell.item
|
||||||
)
|
let channel = item.channel
|
||||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
let currentProgramDisplayText = item.currentProgram?
|
||||||
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(
|
.programDisplayText(timeFormatter: viewModel.timeFormatter) ?? LiveTVChannelViewProgram(timeDisplay: "", title: "")
|
||||||
leading: .flexible(0), top: nil,
|
let nextItems = item.programs.filter { program in
|
||||||
trailing: .flexible(2), bottom: .flexible(2)
|
guard let start = program.startDate else {
|
||||||
)
|
return false
|
||||||
let item2 = NSCollectionLayoutItem(layoutSize: itemSize)
|
}
|
||||||
item2.edgeSpacing = NSCollectionLayoutEdgeSpacing(
|
guard let currentStart = item.currentProgram?.startDate else {
|
||||||
leading: nil, top: nil,
|
return false
|
||||||
trailing: .flexible(0), bottom: .flexible(2)
|
}
|
||||||
)
|
return start > currentStart
|
||||||
let groupSize = NSCollectionLayoutSize(
|
}
|
||||||
widthDimension: .fractionalWidth(1.0),
|
LiveTVChannelItemWideElement(channel: channel,
|
||||||
heightDimension: .absolute(144)
|
currentProgram: item.currentProgram,
|
||||||
)
|
currentProgramText: currentProgramDisplayText,
|
||||||
let group = NSCollectionLayoutGroup.horizontal(
|
nextProgramsText: nextProgramsDisplayText(nextItems: nextItems,
|
||||||
layoutSize: groupSize,
|
timeFormatter: viewModel.timeFormatter),
|
||||||
subitems: [item, item2]
|
onSelect: { loadingAction in
|
||||||
)
|
loadingAction(true)
|
||||||
let section = NSCollectionLayoutSection(group: group)
|
self.viewModel.fetchVideoPlayerViewModel(item: channel) { playerViewModel in
|
||||||
return section
|
self.router.route(to: \.videoPlayer, playerViewModel)
|
||||||
}
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||||
}
|
loadingAction(false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private func checkOrientation() {
|
})
|
||||||
let scenes = UIApplication.shared.connectedScenes
|
}
|
||||||
let windowScene = scenes.first as? UIWindowScene
|
|
||||||
guard let scene = windowScene else { return }
|
private func createGridLayout() -> NSCollectionLayoutSection {
|
||||||
self.isPortrait = scene.interfaceOrientation.isPortrait
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||||
}
|
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute((UIScreen.main.bounds.width / 2) - 16),
|
||||||
|
heightDimension: .fractionalHeight(1))
|
||||||
private func nextProgramsDisplayText(nextItems: [BaseItemDto], timeFormatter: DateFormatter) -> [LiveTVChannelViewProgram] {
|
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
var programsDisplayText: [LiveTVChannelViewProgram] = []
|
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(0), top: nil,
|
||||||
for item in nextItems {
|
trailing: .flexible(2), bottom: .flexible(2))
|
||||||
programsDisplayText.append(item.programDisplayText(timeFormatter: timeFormatter))
|
let item2 = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
}
|
item2.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: nil, top: nil,
|
||||||
return programsDisplayText
|
trailing: .flexible(0), bottom: .flexible(2))
|
||||||
}
|
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
|
||||||
|
heightDimension: .absolute(144))
|
||||||
|
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
|
||||||
|
subitems: [item, item2])
|
||||||
|
let section = NSCollectionLayoutSection(group: group)
|
||||||
|
return section
|
||||||
|
} else {
|
||||||
|
if isPortrait {
|
||||||
|
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(UIScreen.main.bounds.width - 32),
|
||||||
|
heightDimension: .fractionalHeight(1))
|
||||||
|
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
|
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(0), top: nil,
|
||||||
|
trailing: .flexible(2), bottom: .flexible(2))
|
||||||
|
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
|
||||||
|
heightDimension: .absolute(144))
|
||||||
|
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
|
||||||
|
subitems: [item])
|
||||||
|
let section = NSCollectionLayoutSection(group: group)
|
||||||
|
return section
|
||||||
|
} else {
|
||||||
|
|
||||||
|
let scenes = UIApplication.shared.connectedScenes
|
||||||
|
let windowScene = scenes.first as? UIWindowScene
|
||||||
|
var width = (UIScreen.main.bounds.width / 2) - 32
|
||||||
|
if let safeArea = windowScene?.keyWindow?.safeAreaInsets {
|
||||||
|
width = (UIScreen.main.bounds.width / 2) - safeArea.left - safeArea.right
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(width),
|
||||||
|
heightDimension: .fractionalHeight(1))
|
||||||
|
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
|
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(0), top: nil,
|
||||||
|
trailing: .flexible(2), bottom: .flexible(2))
|
||||||
|
let item2 = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
|
item2.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: nil, top: nil,
|
||||||
|
trailing: .flexible(0), bottom: .flexible(2))
|
||||||
|
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
|
||||||
|
heightDimension: .absolute(144))
|
||||||
|
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
|
||||||
|
subitems: [item, item2])
|
||||||
|
let section = NSCollectionLayoutSection(group: group)
|
||||||
|
return section
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func checkOrientation() {
|
||||||
|
let scenes = UIApplication.shared.connectedScenes
|
||||||
|
let windowScene = scenes.first as? UIWindowScene
|
||||||
|
guard let scene = windowScene else { return }
|
||||||
|
self.isPortrait = scene.interfaceOrientation.isPortrait
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
private extension BaseItemDto {
|
||||||
func programDisplayText(timeFormatter: DateFormatter) -> LiveTVChannelViewProgram {
|
func programDisplayText(timeFormatter: DateFormatter) -> LiveTVChannelViewProgram {
|
||||||
var timeText = ""
|
var timeText = ""
|
||||||
if let start = self.startDate {
|
if let start = self.startDate {
|
||||||
timeText.append(timeFormatter.string(from: start) + " ")
|
timeText.append(timeFormatter.string(from: start) + " ")
|
||||||
}
|
}
|
||||||
var displayText = ""
|
var displayText = ""
|
||||||
if let season = self.parentIndexNumber,
|
if let season = self.parentIndexNumber,
|
||||||
let episode = self.indexNumber
|
let episode = self.indexNumber
|
||||||
{
|
{
|
||||||
displayText.append("\(season)x\(episode) ")
|
displayText.append("\(season)x\(episode) ")
|
||||||
} else if let episode = self.indexNumber {
|
} else if let episode = self.indexNumber {
|
||||||
displayText.append("\(episode) ")
|
displayText.append("\(episode) ")
|
||||||
}
|
}
|
||||||
if let name = self.name {
|
if let name = self.name {
|
||||||
displayText.append("\(name) ")
|
displayText.append("\(name) ")
|
||||||
}
|
}
|
||||||
if let title = self.episodeTitle {
|
if let title = self.episodeTitle {
|
||||||
displayText.append("\(title) ")
|
displayText.append("\(title) ")
|
||||||
}
|
}
|
||||||
if let year = self.productionYear {
|
if let year = self.productionYear {
|
||||||
displayText.append("\(year) ")
|
displayText.append("\(year) ")
|
||||||
}
|
}
|
||||||
if let rating = self.officialRating {
|
if let rating = self.officialRating {
|
||||||
displayText.append("\(rating)")
|
displayText.append("\(rating)")
|
||||||
}
|
}
|
||||||
|
|
||||||
return LiveTVChannelViewProgram(timeDisplay: timeText, title: displayText)
|
return LiveTVChannelViewProgram(timeDisplay: timeText, title: displayText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,201 +10,201 @@ import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct LiveTVProgramsView: View {
|
struct LiveTVProgramsView: View {
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var programsRouter: LiveTVProgramsCoordinator.Router
|
var programsRouter: LiveTVProgramsCoordinator.Router
|
||||||
@StateObject
|
@StateObject
|
||||||
var viewModel = LiveTVProgramsViewModel()
|
var viewModel = LiveTVProgramsViewModel()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(alignment: .leading) {
|
LazyVStack(alignment: .leading) {
|
||||||
if !viewModel.recommendedItems.isEmpty,
|
if !viewModel.recommendedItems.isEmpty,
|
||||||
let items = viewModel.recommendedItems
|
let items = viewModel.recommendedItems
|
||||||
{
|
{
|
||||||
Text("On Now")
|
Text("On Now")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding(.leading, 90)
|
.padding(.leading, 90)
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
LazyHStack {
|
LazyHStack {
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
ForEach(items, id: \.id) { item in
|
ForEach(items, id: \.id) { item in
|
||||||
Button {
|
Button {
|
||||||
if let chanId = item.channelId,
|
if let chanId = item.channelId,
|
||||||
let chan = viewModel.findChannel(id: chanId)
|
let chan = viewModel.findChannel(id: chanId)
|
||||||
{
|
{
|
||||||
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
||||||
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
#elseif os(tvOS)
|
#elseif os(tvOS)
|
||||||
LandscapeItemElement(item: item)
|
LandscapeItemElement(item: item)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
}
|
}
|
||||||
}.frame(height: 350)
|
}.frame(height: 350)
|
||||||
}
|
}
|
||||||
if !viewModel.seriesItems.isEmpty,
|
if !viewModel.seriesItems.isEmpty,
|
||||||
let items = viewModel.seriesItems
|
let items = viewModel.seriesItems
|
||||||
{
|
{
|
||||||
Text("Shows")
|
Text("Shows")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding(.leading, 90)
|
.padding(.leading, 90)
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
LazyHStack {
|
LazyHStack {
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
ForEach(items, id: \.id) { item in
|
ForEach(items, id: \.id) { item in
|
||||||
Button {
|
Button {
|
||||||
if let chanId = item.channelId,
|
if let chanId = item.channelId,
|
||||||
let chan = viewModel.findChannel(id: chanId)
|
let chan = viewModel.findChannel(id: chanId)
|
||||||
{
|
{
|
||||||
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
||||||
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
#elseif os(tvOS)
|
#elseif os(tvOS)
|
||||||
LandscapeItemElement(item: item)
|
LandscapeItemElement(item: item)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
}
|
}
|
||||||
}.frame(height: 350)
|
}.frame(height: 350)
|
||||||
}
|
}
|
||||||
if !viewModel.movieItems.isEmpty,
|
if !viewModel.movieItems.isEmpty,
|
||||||
let items = viewModel.movieItems
|
let items = viewModel.movieItems
|
||||||
{
|
{
|
||||||
Text("Movies")
|
Text("Movies")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding(.leading, 90)
|
.padding(.leading, 90)
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
LazyHStack {
|
LazyHStack {
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
ForEach(items, id: \.id) { item in
|
ForEach(items, id: \.id) { item in
|
||||||
Button {
|
Button {
|
||||||
if let chanId = item.channelId,
|
if let chanId = item.channelId,
|
||||||
let chan = viewModel.findChannel(id: chanId)
|
let chan = viewModel.findChannel(id: chanId)
|
||||||
{
|
{
|
||||||
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
||||||
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
#elseif os(tvOS)
|
#elseif os(tvOS)
|
||||||
LandscapeItemElement(item: item)
|
LandscapeItemElement(item: item)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
}
|
}
|
||||||
}.frame(height: 350)
|
}.frame(height: 350)
|
||||||
}
|
}
|
||||||
if !viewModel.sportsItems.isEmpty,
|
if !viewModel.sportsItems.isEmpty,
|
||||||
let items = viewModel.sportsItems
|
let items = viewModel.sportsItems
|
||||||
{
|
{
|
||||||
Text("Sports")
|
Text("Sports")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding(.leading, 90)
|
.padding(.leading, 90)
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
LazyHStack {
|
LazyHStack {
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
ForEach(items, id: \.id) { item in
|
ForEach(items, id: \.id) { item in
|
||||||
Button {
|
Button {
|
||||||
if let chanId = item.channelId,
|
if let chanId = item.channelId,
|
||||||
let chan = viewModel.findChannel(id: chanId)
|
let chan = viewModel.findChannel(id: chanId)
|
||||||
{
|
{
|
||||||
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
||||||
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
#elseif os(tvOS)
|
#elseif os(tvOS)
|
||||||
LandscapeItemElement(item: item)
|
LandscapeItemElement(item: item)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
}
|
}
|
||||||
}.frame(height: 350)
|
}.frame(height: 350)
|
||||||
}
|
}
|
||||||
if !viewModel.kidsItems.isEmpty,
|
if !viewModel.kidsItems.isEmpty,
|
||||||
let items = viewModel.kidsItems
|
let items = viewModel.kidsItems
|
||||||
{
|
{
|
||||||
Text("Kids")
|
Text("Kids")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding(.leading, 90)
|
.padding(.leading, 90)
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
LazyHStack {
|
LazyHStack {
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
ForEach(items, id: \.id) { item in
|
ForEach(items, id: \.id) { item in
|
||||||
Button {
|
Button {
|
||||||
if let chanId = item.channelId,
|
if let chanId = item.channelId,
|
||||||
let chan = viewModel.findChannel(id: chanId)
|
let chan = viewModel.findChannel(id: chanId)
|
||||||
{
|
{
|
||||||
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
||||||
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
#elseif os(tvOS)
|
#elseif os(tvOS)
|
||||||
LandscapeItemElement(item: item)
|
LandscapeItemElement(item: item)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
}
|
}
|
||||||
}.frame(height: 350)
|
}.frame(height: 350)
|
||||||
}
|
}
|
||||||
if !viewModel.newsItems.isEmpty,
|
if !viewModel.newsItems.isEmpty,
|
||||||
let items = viewModel.newsItems
|
let items = viewModel.newsItems
|
||||||
{
|
{
|
||||||
Text("News")
|
Text("News")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding(.leading, 90)
|
.padding(.leading, 90)
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
LazyHStack {
|
LazyHStack {
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
ForEach(items, id: \.id) { item in
|
ForEach(items, id: \.id) { item in
|
||||||
Button {
|
Button {
|
||||||
if let chanId = item.channelId,
|
if let chanId = item.channelId,
|
||||||
let chan = viewModel.findChannel(id: chanId)
|
let chan = viewModel.findChannel(id: chanId)
|
||||||
{
|
{
|
||||||
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
|
||||||
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
#elseif os(tvOS)
|
#elseif os(tvOS)
|
||||||
LandscapeItemElement(item: item)
|
LandscapeItemElement(item: item)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 45)
|
Spacer().frame(width: 45)
|
||||||
}
|
}
|
||||||
}.frame(height: 350)
|
}.frame(height: 350)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,13 @@ struct ExperimentalSettingsView: View {
|
||||||
var syncSubtitleStateWithAdjacent
|
var syncSubtitleStateWithAdjacent
|
||||||
@Default(.Experimental.nativePlayer)
|
@Default(.Experimental.nativePlayer)
|
||||||
var nativePlayer
|
var nativePlayer
|
||||||
@Default(.Experimental.liveTVAlphaEnabled)
|
@Default(.Experimental.liveTVAlphaEnabled)
|
||||||
var liveTVAlphaEnabled
|
var liveTVAlphaEnabled
|
||||||
@Default(.Experimental.liveTVForceDirectPlay)
|
@Default(.Experimental.liveTVForceDirectPlay)
|
||||||
var liveTVForceDirectPlay
|
var liveTVForceDirectPlay
|
||||||
@Default(.Experimental.liveTVNativePlayer)
|
@Default(.Experimental.liveTVNativePlayer)
|
||||||
var liveTVNativePlayer
|
var liveTVNativePlayer
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
|
@ -37,18 +37,18 @@ struct ExperimentalSettingsView: View {
|
||||||
} header: {
|
} header: {
|
||||||
L10n.experimental.text
|
L10n.experimental.text
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
|
||||||
Toggle("Live TV (Alpha)", isOn: $liveTVAlphaEnabled)
|
Toggle("Live TV (Alpha)", isOn: $liveTVAlphaEnabled)
|
||||||
|
|
||||||
Toggle("Live TV Force Direct Play", isOn: $liveTVForceDirectPlay)
|
Toggle("Live TV Force Direct Play", isOn: $liveTVForceDirectPlay)
|
||||||
|
|
||||||
Toggle("Live TV Native Player", isOn: $liveTVNativePlayer)
|
Toggle("Live TV Native Player", isOn: $liveTVNativePlayer)
|
||||||
|
|
||||||
} header: {
|
} header: {
|
||||||
Text("Live TV")
|
Text("Live TV")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,28 +11,28 @@ import UIKit
|
||||||
|
|
||||||
struct LiveTVNativePlayerView: UIViewControllerRepresentable {
|
struct LiveTVNativePlayerView: UIViewControllerRepresentable {
|
||||||
|
|
||||||
let viewModel: VideoPlayerViewModel
|
let viewModel: VideoPlayerViewModel
|
||||||
|
|
||||||
typealias UIViewControllerType = LiveTVNativePlayerViewController
|
typealias UIViewControllerType = LiveTVNativePlayerViewController
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> LiveTVNativePlayerViewController {
|
func makeUIViewController(context: Context) -> LiveTVNativePlayerViewController {
|
||||||
|
|
||||||
LiveTVNativePlayerViewController(viewModel: viewModel)
|
LiveTVNativePlayerViewController(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: LiveTVNativePlayerViewController, context: Context) {}
|
func updateUIViewController(_ uiViewController: LiveTVNativePlayerViewController, context: Context) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LiveTVPlayerView: UIViewControllerRepresentable {
|
struct LiveTVPlayerView: UIViewControllerRepresentable {
|
||||||
|
|
||||||
let viewModel: VideoPlayerViewModel
|
let viewModel: VideoPlayerViewModel
|
||||||
|
|
||||||
typealias UIViewControllerType = LiveTVPlayerViewController
|
typealias UIViewControllerType = LiveTVPlayerViewController
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> LiveTVPlayerViewController {
|
func makeUIViewController(context: Context) -> LiveTVPlayerViewController {
|
||||||
|
|
||||||
LiveTVPlayerViewController(viewModel: viewModel)
|
LiveTVPlayerViewController(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: LiveTVPlayerViewController, context: Context) {}
|
func updateUIViewController(_ uiViewController: LiveTVPlayerViewController, context: Context) {}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue