more cinematic views
This commit is contained in:
parent
6ad3b3e0f2
commit
6c2d153df4
|
@ -63,5 +63,6 @@ struct PortraitItemsRowView: View {
|
|||
}
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
}
|
||||
.focusSection()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import JellyfinAPI
|
||||
import SwiftUI
|
||||
|
||||
struct SingleSeasonEpisodesRowView: View {
|
||||
|
||||
@EnvironmentObject var itemRouter: ItemCoordinator.Router
|
||||
@ObservedObject var viewModel: SingleSeasonEpisodesRowViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
Text("Episodes")
|
||||
.font(.title3)
|
||||
.padding(.horizontal, 50)
|
||||
|
||||
ScrollView(.horizontal) {
|
||||
ScrollViewReader { reader in
|
||||
HStack(alignment: .top) {
|
||||
if viewModel.isLoading {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
ZStack {
|
||||
Color.secondary.ignoresSafeArea()
|
||||
|
||||
ProgressView()
|
||||
}
|
||||
.mask(Rectangle().frame(width: 500, height: 280))
|
||||
.frame(width: 500, height: 280)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("S-E-")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text("--")
|
||||
.font(.footnote)
|
||||
.padding(.bottom, 1)
|
||||
Text("--")
|
||||
.font(.caption)
|
||||
.fontWeight(.light)
|
||||
.lineLimit(4)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(width: 500)
|
||||
.focusable()
|
||||
} else if viewModel.episodes.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
Color.secondary
|
||||
.mask(Rectangle().frame(width: 500, height: 280))
|
||||
.frame(width: 500, height: 280)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("--")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text("No episodes available")
|
||||
.font(.footnote)
|
||||
.padding(.bottom, 1)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(width: 500)
|
||||
.focusable()
|
||||
} else {
|
||||
ForEach(viewModel.episodes, id:\.self) { episode in
|
||||
Button {
|
||||
itemRouter.route(to: \.item, episode)
|
||||
} label: {
|
||||
HStack(alignment: .top) {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
ImageView(src: episode.getBackdropImage(maxWidth: 445),
|
||||
bh: episode.getBackdropImageBlurHash())
|
||||
.mask(Rectangle().frame(width: 500, height: 280))
|
||||
.frame(width: 500, height: 280)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(episode.getEpisodeLocator() ?? "")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text(episode.name ?? "")
|
||||
.font(.footnote)
|
||||
.padding(.bottom, 1)
|
||||
Text(episode.overview ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.light)
|
||||
.lineLimit(4)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(width: 500)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.id(episode.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 50)
|
||||
.padding(.vertical)
|
||||
}
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,14 @@ struct CinematicEpisodeItemView: View {
|
|||
@State var wrappedScrollView: UIScrollView?
|
||||
@Default(.showPosterLabels) var showPosterLabels
|
||||
|
||||
func generateSubtitle() -> String? {
|
||||
guard let seriesName = viewModel.item.seriesName, let episodeLocator = viewModel.item.getEpisodeLocator() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return "\(seriesName) - \(episodeLocator)"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
|
||||
|
@ -28,7 +36,10 @@ struct CinematicEpisodeItemView: View {
|
|||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
|
||||
CinematicItemViewTopRow(viewModel: viewModel, wrappedScrollView: wrappedScrollView)
|
||||
CinematicItemViewTopRow(viewModel: viewModel,
|
||||
wrappedScrollView: wrappedScrollView,
|
||||
title: viewModel.item.name ?? "",
|
||||
subtitle: generateSubtitle())
|
||||
.focusSection()
|
||||
.frame(height: UIScreen.main.bounds.height - 10)
|
||||
|
||||
|
@ -44,6 +55,13 @@ struct CinematicEpisodeItemView: View {
|
|||
EpisodesRowView(viewModel: EpisodesRowViewModel(episodeItemViewModel: viewModel))
|
||||
.focusSection()
|
||||
|
||||
if let seriesItem = viewModel.series {
|
||||
PortraitItemsRowView(rowTitle: "Series",
|
||||
items: [seriesItem]) { seriesItem in
|
||||
itemRouter.route(to: \.item, seriesItem)
|
||||
}
|
||||
}
|
||||
|
||||
if !viewModel.similarItems.isEmpty {
|
||||
PortraitItemsRowView(rowTitle: "Recommended",
|
||||
items: viewModel.similarItems,
|
||||
|
|
|
@ -16,6 +16,8 @@ struct CinematicItemViewTopRow: View {
|
|||
@Environment(\.isFocused) var envFocused: Bool
|
||||
@State var focused: Bool = false
|
||||
@State var wrappedScrollView: UIScrollView?
|
||||
@State var title: String
|
||||
@State var subtitle: String?
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .bottom) {
|
||||
|
@ -34,7 +36,11 @@ struct CinematicItemViewTopRow: View {
|
|||
|
||||
// MARK: Play
|
||||
Button {
|
||||
itemRouter.route(to: \.videoPlayer, viewModel.itemVideoPlayerViewModel!)
|
||||
if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel {
|
||||
itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel)
|
||||
} else {
|
||||
LogManager.shared.log.error("Attempted to play item but no playback information available")
|
||||
}
|
||||
} label: {
|
||||
HStack(spacing: 15) {
|
||||
Image(systemName: "play.fill")
|
||||
|
@ -42,48 +48,46 @@ struct CinematicItemViewTopRow: View {
|
|||
.font(.title3)
|
||||
Text(viewModel.playButtonText())
|
||||
.foregroundColor(viewModel.playButtonItem == nil ? Color(UIColor.secondaryLabel) : Color.black)
|
||||
// .font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.frame(width: 230, height: 100)
|
||||
.background(viewModel.playButtonItem == nil ? Color.secondarySystemFill : Color.white)
|
||||
.cornerRadius(10)
|
||||
|
||||
// ZStack {
|
||||
// Color.white.frame(width: 230, height: 100)
|
||||
//
|
||||
// Text("Play")
|
||||
// .font(.title3)
|
||||
// .foregroundColor(.black)
|
||||
// }
|
||||
}
|
||||
.buttonStyle(CardButtonStyle())
|
||||
.disabled(viewModel.itemVideoPlayerViewModel == nil)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text(viewModel.item.name ?? "")
|
||||
Text(title)
|
||||
.font(.title2)
|
||||
.lineLimit(2)
|
||||
|
||||
if let seriesName = viewModel.item.seriesName, let episodeLocator = viewModel.item.getEpisodeLocator() {
|
||||
Text("\(seriesName) - \(episodeLocator)")
|
||||
if let subtitle = subtitle {
|
||||
Text(subtitle)
|
||||
}
|
||||
|
||||
HStack(alignment: .PlayInformationAlignmentGuide, spacing: 20) {
|
||||
|
||||
if let runtime = viewModel.item.getItemRuntime() {
|
||||
Text(runtime)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
}
|
||||
|
||||
if let productionYear = viewModel.item.productionYear {
|
||||
Text(String(productionYear))
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
.lineLimit(1)
|
||||
if viewModel.item.itemType == .series {
|
||||
if let airTime = viewModel.item.airTime {
|
||||
Text(airTime)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
}
|
||||
} else {
|
||||
if let runtime = viewModel.item.getItemRuntime() {
|
||||
Text(runtime)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
}
|
||||
|
||||
if let productionYear = viewModel.item.productionYear {
|
||||
Text(String(productionYear))
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if let officialRating = viewModel.item.officialRating {
|
||||
|
@ -121,17 +125,6 @@ struct CinematicItemViewTopRow: View {
|
|||
}
|
||||
}
|
||||
|
||||
extension HorizontalAlignment {
|
||||
|
||||
private struct TitleSubtitleAlignment: AlignmentID {
|
||||
static func defaultValue(in context: ViewDimensions) -> CGFloat {
|
||||
context[HorizontalAlignment.leading]
|
||||
}
|
||||
}
|
||||
|
||||
static let EpisodeSeriesAlignmentGuide = HorizontalAlignment(TitleSubtitleAlignment.self)
|
||||
}
|
||||
|
||||
extension VerticalAlignment {
|
||||
|
||||
private struct PlayInformationAlignment: AlignmentID {
|
||||
|
|
|
@ -28,7 +28,10 @@ struct CinematicMovieItemView: View {
|
|||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
|
||||
CinematicItemViewTopRow(viewModel: viewModel, wrappedScrollView: wrappedScrollView)
|
||||
CinematicItemViewTopRow(viewModel: viewModel,
|
||||
wrappedScrollView: wrappedScrollView,
|
||||
title: viewModel.item.name ?? "",
|
||||
subtitle: nil)
|
||||
.focusSection()
|
||||
.frame(height: UIScreen.main.bounds.height - 10)
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct CinematicSeasonItemView: View {
|
||||
|
||||
@EnvironmentObject var itemRouter: ItemCoordinator.Router
|
||||
@ObservedObject var viewModel: SeasonItemViewModel
|
||||
@State var wrappedScrollView: UIScrollView?
|
||||
@Default(.showPosterLabels) var showPosterLabels
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
|
||||
ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), bh: viewModel.item.getBackdropImageBlurHash())
|
||||
.ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
|
||||
if let seriesItem = viewModel.seriesItem {
|
||||
CinematicItemViewTopRow(viewModel: viewModel,
|
||||
wrappedScrollView: wrappedScrollView,
|
||||
title: viewModel.item.name ?? "",
|
||||
subtitle: seriesItem.name)
|
||||
.focusSection()
|
||||
.frame(height: UIScreen.main.bounds.height - 10)
|
||||
} else {
|
||||
CinematicItemViewTopRow(viewModel: viewModel,
|
||||
wrappedScrollView: wrappedScrollView,
|
||||
title: viewModel.item.name ?? "")
|
||||
.focusSection()
|
||||
.frame(height: UIScreen.main.bounds.height - 10)
|
||||
}
|
||||
|
||||
ZStack(alignment: .topLeading) {
|
||||
|
||||
Color.black.ignoresSafeArea()
|
||||
.frame(minHeight: UIScreen.main.bounds.height)
|
||||
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
|
||||
CinematicItemAboutView(viewModel: viewModel)
|
||||
|
||||
SingleSeasonEpisodesRowView(viewModel: SingleSeasonEpisodesRowViewModel(seasonItemViewModel: viewModel))
|
||||
|
||||
if let seriesItem = viewModel.seriesItem {
|
||||
PortraitItemsRowView(rowTitle: "Series",
|
||||
items: [seriesItem]) { seriesItem in
|
||||
itemRouter.route(to: \.item, seriesItem)
|
||||
}
|
||||
}
|
||||
|
||||
if !viewModel.similarItems.isEmpty {
|
||||
PortraitItemsRowView(rowTitle: "Recommended",
|
||||
items: viewModel.similarItems,
|
||||
showItemTitles: showPosterLabels) { item in
|
||||
itemRouter.route(to: \.item, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
.introspectScrollView { scrollView in
|
||||
wrappedScrollView = scrollView
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct CinematicSeriesItemView: View {
|
||||
|
||||
@EnvironmentObject var itemRouter: ItemCoordinator.Router
|
||||
@ObservedObject var viewModel: SeriesItemViewModel
|
||||
@State var wrappedScrollView: UIScrollView?
|
||||
@Default(.showPosterLabels) var showPosterLabels
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
|
||||
ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), bh: viewModel.item.getBackdropImageBlurHash())
|
||||
.ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
|
||||
CinematicItemViewTopRow(viewModel: viewModel,
|
||||
wrappedScrollView: wrappedScrollView,
|
||||
title: viewModel.item.name ?? "",
|
||||
subtitle: nil)
|
||||
.focusSection()
|
||||
.frame(height: UIScreen.main.bounds.height - 10)
|
||||
|
||||
ZStack(alignment: .topLeading) {
|
||||
|
||||
Color.black.ignoresSafeArea()
|
||||
.frame(minHeight: UIScreen.main.bounds.height)
|
||||
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
|
||||
CinematicItemAboutView(viewModel: viewModel)
|
||||
|
||||
PortraitItemsRowView(rowTitle: "Seasons",
|
||||
items: viewModel.seasons,
|
||||
showItemTitles: showPosterLabels) { season in
|
||||
itemRouter.route(to: \.item, season)
|
||||
}
|
||||
|
||||
if !viewModel.similarItems.isEmpty {
|
||||
PortraitItemsRowView(rowTitle: "Recommended",
|
||||
items: viewModel.similarItems,
|
||||
showItemTitles: showPosterLabels) { item in
|
||||
itemRouter.route(to: \.item, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
.introspectScrollView { scrollView in
|
||||
wrappedScrollView = scrollView
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,8 +25,7 @@ struct ItemNavigationView: View {
|
|||
|
||||
struct ItemView: View {
|
||||
|
||||
@Default(.tvOSEpisodeItemCinematicView) var tvOSEpisodeItemCinematicView
|
||||
@Default(.tvOSMovieItemCinematicView) var tvOSMovieItemCinematicView
|
||||
@Default(.tvOSCinematicViews) var tvOSCinematicViews
|
||||
|
||||
private var item: BaseItemDto
|
||||
|
||||
|
@ -36,23 +35,32 @@ struct ItemView: View {
|
|||
|
||||
var body: some View {
|
||||
Group {
|
||||
if item.type == "Movie" {
|
||||
if tvOSMovieItemCinematicView {
|
||||
switch item.itemType {
|
||||
case .movie:
|
||||
if tvOSCinematicViews {
|
||||
CinematicMovieItemView(viewModel: MovieItemViewModel(item: item))
|
||||
} else {
|
||||
MovieItemView(viewModel: MovieItemViewModel(item: item))
|
||||
}
|
||||
} else if item.type == "Series" {
|
||||
SeriesItemView(viewModel: .init(item: item))
|
||||
} else if item.type == "Season" {
|
||||
SeasonItemView(viewModel: .init(item: item))
|
||||
} else if item.type == "Episode" {
|
||||
if tvOSEpisodeItemCinematicView {
|
||||
case .episode:
|
||||
if tvOSCinematicViews {
|
||||
CinematicEpisodeItemView(viewModel: EpisodeItemViewModel(item: item))
|
||||
} else {
|
||||
EpisodeItemView(viewModel: EpisodeItemViewModel(item: item))
|
||||
}
|
||||
} else {
|
||||
case .season:
|
||||
if tvOSCinematicViews {
|
||||
CinematicSeasonItemView(viewModel: SeasonItemViewModel(item: item))
|
||||
} else {
|
||||
SeasonItemView(viewModel: .init(item: item))
|
||||
}
|
||||
case .series:
|
||||
if tvOSCinematicViews {
|
||||
CinematicSeriesItemView(viewModel: SeriesItemViewModel(item: item))
|
||||
} else {
|
||||
SeriesItemView(viewModel: SeriesItemViewModel(item: item))
|
||||
}
|
||||
default:
|
||||
Text(L10n.notImplementedYetWithType(item.type ?? ""))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,7 @@ struct SettingsView: View {
|
|||
@Default(.videoPlayerJumpBackward) var jumpBackwardLength
|
||||
@Default(.downActionShowsMenu) var downActionShowsMenu
|
||||
@Default(.confirmClose) var confirmClose
|
||||
@Default(.tvOSEpisodeItemCinematicView) var tvOSEpisodeItemCinematicView
|
||||
@Default(.tvOSMovieItemCinematicView) var tvOSMovieItemCinematicView
|
||||
@Default(.tvOSCinematicViews) var tvOSCinematicViews
|
||||
@Default(.showPosterLabels) var showPosterLabels
|
||||
@Default(.resumeOffset) var resumeOffset
|
||||
|
||||
|
@ -111,8 +110,7 @@ struct SettingsView: View {
|
|||
}
|
||||
|
||||
Section {
|
||||
Toggle("Episode Item Cinematic View", isOn: $tvOSEpisodeItemCinematicView)
|
||||
Toggle("Movie Item Cinematic View", isOn: $tvOSMovieItemCinematicView)
|
||||
Toggle("Cinematic Views", isOn: $tvOSCinematicViews)
|
||||
Toggle("Show Poster Labels", isOn: $showPosterLabels)
|
||||
|
||||
} header: {
|
||||
|
|
|
@ -307,6 +307,9 @@
|
|||
E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3F82717E961009D4DAF /* UserListViewModel.swift */; };
|
||||
E13DD3FC2717EAE8009D4DAF /* UserListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3FB2717EAE8009D4DAF /* UserListView.swift */; };
|
||||
E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */; };
|
||||
E13F26AF278754E300DF4761 /* CinematicSeriesItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F26AE278754E300DF4761 /* CinematicSeriesItemView.swift */; };
|
||||
E13F26B12787589300DF4761 /* CinematicSeasonItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F26B02787589300DF4761 /* CinematicSeasonItemView.swift */; };
|
||||
E13F26B32787597300DF4761 /* SingleSeasonEpisodesRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F26B22787597300DF4761 /* SingleSeasonEpisodesRowView.swift */; };
|
||||
E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E14F7D0626DB36EF007C3AE6 /* ItemPortraitMainView.swift */; };
|
||||
E14F7D0926DB36F7007C3AE6 /* ItemLandscapeMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E14F7D0826DB36F7007C3AE6 /* ItemLandscapeMainView.swift */; };
|
||||
E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */; };
|
||||
|
@ -650,6 +653,9 @@
|
|||
E13DD3F82717E961009D4DAF /* UserListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListViewModel.swift; sourceTree = "<group>"; };
|
||||
E13DD3FB2717EAE8009D4DAF /* UserListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListView.swift; sourceTree = "<group>"; };
|
||||
E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListCoordinator.swift; sourceTree = "<group>"; };
|
||||
E13F26AE278754E300DF4761 /* CinematicSeriesItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicSeriesItemView.swift; sourceTree = "<group>"; };
|
||||
E13F26B02787589300DF4761 /* CinematicSeasonItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicSeasonItemView.swift; sourceTree = "<group>"; };
|
||||
E13F26B22787597300DF4761 /* SingleSeasonEpisodesRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleSeasonEpisodesRowView.swift; sourceTree = "<group>"; };
|
||||
E14F7D0626DB36EF007C3AE6 /* ItemPortraitMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemPortraitMainView.swift; sourceTree = "<group>"; };
|
||||
E14F7D0826DB36F7007C3AE6 /* ItemLandscapeMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemLandscapeMainView.swift; sourceTree = "<group>"; };
|
||||
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = "<group>"; };
|
||||
|
@ -950,11 +956,15 @@
|
|||
536D3D77267BB9650004248C /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */,
|
||||
E13F26B22787597300DF4761 /* SingleSeasonEpisodesRowView.swift */,
|
||||
E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */,
|
||||
531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */,
|
||||
E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */,
|
||||
53272531268BF09D0035FBF1 /* MediaViewActionButton.swift */,
|
||||
53116A18268B947A003024C9 /* PlainLinkButton.swift */,
|
||||
536D3D80267BDFC60004248C /* PortraitItemElement.swift */,
|
||||
E1E5D5412783B33900692DFE /* PortraitItemsRowView.swift */,
|
||||
536D3D87267C17350004248C /* PublicUserButton.swift */,
|
||||
E17885A3278105170094FBCF /* SFSymbolButton.swift */,
|
||||
);
|
||||
|
@ -1368,6 +1378,17 @@
|
|||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E13F26AD27874ECC00DF4761 /* CompactItemView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
53272538268C20100035FBF1 /* EpisodeItemView.swift */,
|
||||
53CD2A41268A4B38002ABD4E /* MovieItemView.swift */,
|
||||
53272536268C1DBB0035FBF1 /* SeasonItemView.swift */,
|
||||
53116A16268B919A003024C9 /* SeriesItemView.swift */,
|
||||
);
|
||||
path = CompactItemView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E14F7D0A26DB3714007C3AE6 /* ItemView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1445,14 +1466,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
E1E5D53C2783A85F00692DFE /* CinematicItemView */,
|
||||
53272538268C20100035FBF1 /* EpisodeItemView.swift */,
|
||||
E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */,
|
||||
E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */,
|
||||
E13F26AD27874ECC00DF4761 /* CompactItemView */,
|
||||
53CD2A3F268A49C2002ABD4E /* ItemView.swift */,
|
||||
53CD2A41268A4B38002ABD4E /* MovieItemView.swift */,
|
||||
E1E5D5412783B33900692DFE /* PortraitItemsRowView.swift */,
|
||||
53272536268C1DBB0035FBF1 /* SeasonItemView.swift */,
|
||||
53116A16268B919A003024C9 /* SeriesItemView.swift */,
|
||||
);
|
||||
path = ItemView;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1528,6 +1543,8 @@
|
|||
E1E5D53A2783A80900692DFE /* CinematicItemViewTopRow.swift */,
|
||||
E1E5D53F2783B0C000692DFE /* CinematicItemViewTopRowButton.swift */,
|
||||
E1E5D53D2783B05200692DFE /* CinematicMovieItemView.swift */,
|
||||
E13F26B02787589300DF4761 /* CinematicSeasonItemView.swift */,
|
||||
E13F26AE278754E300DF4761 /* CinematicSeriesItemView.swift */,
|
||||
);
|
||||
path = CinematicItemView;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2049,6 +2066,7 @@
|
|||
E1D4BF852719D25A00A11E64 /* TrackLanguage.swift in Sources */,
|
||||
53272532268BF09D0035FBF1 /* MediaViewActionButton.swift in Sources */,
|
||||
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */,
|
||||
E13F26B32787597300DF4761 /* SingleSeasonEpisodesRowView.swift in Sources */,
|
||||
E193D53427193F7F00900D82 /* HomeCoordinator.swift in Sources */,
|
||||
E193D5502719430400900D82 /* ServerDetailView.swift in Sources */,
|
||||
E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */,
|
||||
|
@ -2071,6 +2089,7 @@
|
|||
E1E5D5402783B0C000692DFE /* CinematicItemViewTopRowButton.swift in Sources */,
|
||||
5398514626B64DBB00101B49 /* SearchablePickerView.swift in Sources */,
|
||||
53ABFDEE26799DCD00886593 /* ImageView.swift in Sources */,
|
||||
E13F26AF278754E300DF4761 /* CinematicSeriesItemView.swift in Sources */,
|
||||
62E632E4267D3BA60063E547 /* MovieItemViewModel.swift in Sources */,
|
||||
53649AB2269D019100A2D8B7 /* LogManager.swift in Sources */,
|
||||
E1E5D553278419D900692DFE /* ConfirmCloseOverlay.swift in Sources */,
|
||||
|
@ -2108,6 +2127,7 @@
|
|||
C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */,
|
||||
E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */,
|
||||
E1E5D5392783A56B00692DFE /* EpisodesRowView.swift in Sources */,
|
||||
E13F26B12787589300DF4761 /* CinematicSeasonItemView.swift in Sources */,
|
||||
E1E5D5442783BB5100692DFE /* ItemDetailsView.swift in Sources */,
|
||||
536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */,
|
||||
E10D87E327852FD000BD264C /* EpisodesRowViewModel.swift in Sources */,
|
||||
|
|
|
@ -67,6 +67,5 @@ extension Defaults.Keys {
|
|||
// tvos specific
|
||||
static let downActionShowsMenu = Key<Bool>("downActionShowsMenu", default: true, suite: SwiftfinStore.Defaults.generalSuite)
|
||||
static let confirmClose = Key<Bool>("confirmClose", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
||||
static let tvOSEpisodeItemCinematicView = Key<Bool>("tvOSEpisodeItemCinematicView", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
||||
static let tvOSMovieItemCinematicView = Key<Bool>("tvOSMovieItemCinematicView", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
||||
static let tvOSCinematicViews = Key<Bool>("tvOSCinematicViews", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import SwiftUI
|
|||
|
||||
final class EpisodesRowViewModel: ViewModel {
|
||||
|
||||
// TODO: Protocol these viewmodels for generalization instead of Episode
|
||||
|
||||
@ObservedObject var episodeItemViewModel: EpisodeItemViewModel
|
||||
@Published var seasonsEpisodes: [BaseItemDto: [BaseItemDto]] = [:]
|
||||
@Published var selectedSeason: BaseItemDto? {
|
||||
|
@ -63,3 +65,17 @@ final class EpisodesRowViewModel: ViewModel {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
final class SingleSeasonEpisodesRowViewModel: ViewModel {
|
||||
|
||||
// TODO: Protocol these viewmodels for generalization instead of Season
|
||||
|
||||
@ObservedObject var seasonItemViewModel: SeasonItemViewModel
|
||||
@Published var episodes: [BaseItemDto]
|
||||
|
||||
init(seasonItemViewModel: SeasonItemViewModel) {
|
||||
self.seasonItemViewModel = seasonItemViewModel
|
||||
self.episodes = seasonItemViewModel.episodes
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,18 @@ import UIKit
|
|||
class ItemViewModel: ViewModel {
|
||||
|
||||
@Published var item: BaseItemDto
|
||||
@Published var playButtonItem: BaseItemDto?
|
||||
@Published var playButtonItem: BaseItemDto? {
|
||||
didSet {
|
||||
playButtonItem?.createVideoPlayerViewModel()
|
||||
.sink { completion in
|
||||
self.handleAPIRequestError(completion: completion)
|
||||
} receiveValue: { videoPlayerViewModel in
|
||||
self.itemVideoPlayerViewModel = videoPlayerViewModel
|
||||
self.mediaItems = videoPlayerViewModel.item.createMediaItems()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
@Published var similarItems: [BaseItemDto] = []
|
||||
@Published var isWatched = false
|
||||
@Published var isFavorited = false
|
||||
|
|
|
@ -13,13 +13,15 @@ import JellyfinAPI
|
|||
import Stinsen
|
||||
|
||||
final class SeasonItemViewModel: ItemViewModel {
|
||||
|
||||
@RouterObject var itemRouter: ItemCoordinator.Router?
|
||||
@Published private(set) var episodes: [BaseItemDto] = []
|
||||
@Published var episodes: [BaseItemDto] = []
|
||||
@Published var seriesItem: BaseItemDto?
|
||||
|
||||
override init(item: BaseItemDto) {
|
||||
super.init(item: item)
|
||||
self.item = item
|
||||
|
||||
getSeriesItem()
|
||||
requestEpisodes()
|
||||
}
|
||||
|
||||
|
@ -70,7 +72,7 @@ final class SeasonItemViewModel: ItemViewModel {
|
|||
playButtonItem = firstEpisode
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func routeToSeriesItem() {
|
||||
guard let id = item.seriesId else { return }
|
||||
UserLibraryAPI.getItem(userId: SessionManager.main.currentLogin.user.id, itemId: id)
|
||||
|
@ -82,4 +84,17 @@ final class SeasonItemViewModel: ItemViewModel {
|
|||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func getSeriesItem() {
|
||||
guard let seriesID = item.seriesId else { return }
|
||||
UserLibraryAPI.getItem(userId: SessionManager.main.currentLogin.user.id,
|
||||
itemId: seriesID)
|
||||
.trackActivity(loading)
|
||||
.sink { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
} receiveValue: { [weak self] seriesItem in
|
||||
self?.seriesItem = seriesItem
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue