From 143836a26c27cce482322d3d6f3cf01f38f11c26 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Tue, 11 Jan 2022 23:21:03 -0700 Subject: [PATCH] tvOS, fix seasons defaults, and localize --- Shared/Generated/Strings.swift | 6 + Shared/ViewModels/EpisodesRowManager.swift | 9 +- .../ItemViewModel/EpisodeItemViewModel.swift | 6 +- .../ItemViewModel/SeasonItemViewModel.swift | 8 +- .../ItemViewModel/SeriesItemViewModel.swift | 2 +- .../Components/EpisodesRowView.swift | 135 ------------------ .../EpisodesRowView/EpisodesRowCard.swift | 61 ++++++++ .../EpisodesRowView/EpisodesRowView.swift | 108 ++++++++++++++ .../SingleSeasonEpisodesRowView.swift | 123 ---------------- .../CinematicEpisodeItemView.swift | 2 +- .../CinematicItemViewTopRow.swift | 12 ++ .../CinematicSeasonItemView.swift | 3 +- .../MissingItemsSettingsView.swift | 30 ++++ .../Views/SettingsView/SettingsView.swift | 11 ++ Swiftfin.xcodeproj/project.pbxproj | 26 +++- .../EpisodesRowView/EpisodesRowView.swift | 108 +++++++------- Swiftfin/Views/ItemView/ItemViewBody.swift | 2 +- .../Landscape/ItemLandscapeMainView.swift | 4 +- .../MissingItemsSettingsView.swift | 6 +- .../Views/SettingsView/SettingsView.swift | 12 +- Translations/en.lproj/Localizable.strings | Bin 10916 -> 11176 bytes 21 files changed, 330 insertions(+), 344 deletions(-) delete mode 100644 Swiftfin tvOS/Components/EpisodesRowView.swift create mode 100644 Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift create mode 100644 Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowView.swift delete mode 100644 Swiftfin tvOS/Components/SingleSeasonEpisodesRowView.swift create mode 100644 Swiftfin tvOS/Views/SettingsView/MissingItemsSettingsView.swift diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index 14ddcc8f..7ab73115 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -150,6 +150,8 @@ internal enum L10n { internal static let media = L10n.tr("Localizable", "media") /// Missing internal static let missing = L10n.tr("Localizable", "missing") + /// Missing Items + internal static let missingItems = L10n.tr("Localizable", "missingItems") /// More Like This internal static let moreLikeThis = L10n.tr("Localizable", "moreLikeThis") /// Movies @@ -304,6 +306,10 @@ internal enum L10n { internal static let settings = L10n.tr("Localizable", "settings") /// Show Cast & Crew internal static let showCastAndCrew = L10n.tr("Localizable", "showCastAndCrew") + /// Show Missing Episodes + internal static let showMissingEpisodes = L10n.tr("Localizable", "showMissingEpisodes") + /// Show Missing Seasons + internal static let showMissingSeasons = L10n.tr("Localizable", "showMissingSeasons") /// Show Poster Labels internal static let showPosterLabels = L10n.tr("Localizable", "showPosterLabels") /// Signed in as %@ diff --git a/Shared/ViewModels/EpisodesRowManager.swift b/Shared/ViewModels/EpisodesRowManager.swift index 9d0d7011..a31d989b 100644 --- a/Shared/ViewModels/EpisodesRowManager.swift +++ b/Shared/ViewModels/EpisodesRowManager.swift @@ -21,11 +21,11 @@ protocol EpisodesRowManager: ViewModel { extension EpisodesRowManager { - // Also retrieves the current season episodes if available + // Also retrieves the current season episodes if available func retrieveSeasons() { TvShowsAPI.getSeasons(seriesId: item.seriesId ?? "", userId: SessionManager.main.currentLogin.user.id, - isMissing: Defaults[.shouldShowMissingEpisodes] ? nil : false) + isMissing: Defaults[.shouldShowMissingSeasons] ? nil : false) .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { response in @@ -35,7 +35,10 @@ extension EpisodesRowManager { if season.id == self.item.seasonId ?? "" { self.selectedSeason = season - self.retrieveEpisodesForSeason(season) + self.retrieveEpisodesForSeason(season) + } else if season.id == self.item.id ?? "" { + self.selectedSeason = season + self.retrieveEpisodesForSeason(season) } } } diff --git a/Shared/ViewModels/ItemViewModel/EpisodeItemViewModel.swift b/Shared/ViewModels/ItemViewModel/EpisodeItemViewModel.swift index c96fdc10..364f12db 100644 --- a/Shared/ViewModels/ItemViewModel/EpisodeItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/EpisodeItemViewModel.swift @@ -17,16 +17,16 @@ final class EpisodeItemViewModel: ItemViewModel, EpisodesRowManager { var itemRouter: ItemCoordinator.Router? @Published var series: BaseItemDto? - @Published + @Published var seasonsEpisodes: [BaseItemDto: [BaseItemDto]] = [:] - @Published + @Published var selectedSeason: BaseItemDto? override init(item: BaseItemDto) { super.init(item: item) getEpisodeSeries() - retrieveSeasons() + retrieveSeasons() } override func getItemDisplayName() -> String { diff --git a/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift b/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift index d10665f8..ac2f09bc 100644 --- a/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift @@ -19,17 +19,17 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager { var episodes: [BaseItemDto] = [] @Published var seriesItem: BaseItemDto? - @Published + @Published var seasonsEpisodes: [BaseItemDto: [BaseItemDto]] = [:] - @Published + @Published var selectedSeason: BaseItemDto? override init(item: BaseItemDto) { super.init(item: item) getSeriesItem() - selectedSeason = item - retrieveSeasons() + selectedSeason = item + retrieveSeasons() } override func playButtonText() -> String { diff --git a/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift b/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift index 6346df27..05dfb4e8 100644 --- a/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift @@ -81,7 +81,7 @@ final class SeriesItemViewModel: ItemViewModel { LogManager.shared.log.debug("Getting seasons of show \(self.item.id!) (\(self.item.name!))") TvShowsAPI.getSeasons(seriesId: item.id ?? "", userId: SessionManager.main.currentLogin.user.id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], - isMissing: Defaults[.shouldShowMissingEpisodes] ? nil : false, + isMissing: Defaults[.shouldShowMissingSeasons] ? nil : false, enableUserData: true) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in diff --git a/Swiftfin tvOS/Components/EpisodesRowView.swift b/Swiftfin tvOS/Components/EpisodesRowView.swift deleted file mode 100644 index 472edf69..00000000 --- a/Swiftfin tvOS/Components/EpisodesRowView.swift +++ /dev/null @@ -1,135 +0,0 @@ -// -// 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) 2022 Jellyfin & Jellyfin Contributors -// - -import JellyfinAPI -import SwiftUI - -struct EpisodesRowView: View { - - @EnvironmentObject - var itemRouter: ItemCoordinator.Router - @ObservedObject - var viewModel: EpisodesRowViewModel - - var body: some View { - VStack(alignment: .leading) { - - Text(viewModel.selectedSeason?.name ?? L10n.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 let selectedSeason = viewModel.selectedSeason { - if viewModel.seasonsEpisodes[selectedSeason]!.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) - L10n.noEpisodesAvailable.text - .font(.footnote) - .padding(.bottom, 1) - } - .padding(.horizontal) - - Spacer() - } - .frame(width: 500) - .focusable() - } else { - ForEach(viewModel.seasonsEpisodes[selectedSeason]!, id: \.self) { episode in - Button { - itemRouter.route(to: \.item, episode) - } label: { - HStack(alignment: .top) { - VStack(alignment: .leading) { - - ImageView(src: episode.getBackdropImage(maxWidth: 500), - 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) - .onChange(of: viewModel.selectedSeason) { _ in - if viewModel.selectedSeason?.id == viewModel.episodeItemViewModel.item.seasonId { - reader.scrollTo(viewModel.episodeItemViewModel.item.name) - } - } - .onChange(of: viewModel.seasonsEpisodes) { _ in - if viewModel.selectedSeason?.id == viewModel.episodeItemViewModel.item.seasonId { - reader.scrollTo(viewModel.episodeItemViewModel.item.name) - } - } - } - .edgesIgnoringSafeArea(.horizontal) - } - } - } -} diff --git a/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift b/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift new file mode 100644 index 00000000..4e6f3772 --- /dev/null +++ b/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift @@ -0,0 +1,61 @@ +// +// 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) 2022 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI +import SwiftUI + +struct EpisodeRowCard: View { + + @EnvironmentObject + var itemRouter: ItemCoordinator.Router + let viewModel: EpisodesRowManager + let episode: BaseItemDto + + var body: some View { + Button { + itemRouter.route(to: \.item, episode) + } label: { + HStack(alignment: .top) { + VStack(alignment: .leading) { + + ImageView(src: episode.getBackdropImage(maxWidth: 500), + 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) + + if episode.unaired { + Text(episode.airDateLabel ?? L10n.noOverviewAvailable) + .font(.caption) + .foregroundColor(.secondary) + .fontWeight(.light) + .lineLimit(3) + } else { + Text(episode.overview ?? "") + .font(.caption) + .fontWeight(.light) + .lineLimit(4) + } + } + .padding(.horizontal) + + Spacer() + } + .frame(width: 500) + } + } + .buttonStyle(PlainButtonStyle()) + } +} diff --git a/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowView.swift b/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowView.swift new file mode 100644 index 00000000..d9e5c42d --- /dev/null +++ b/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowView.swift @@ -0,0 +1,108 @@ +// +// 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) 2022 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI +import SwiftUI + +struct EpisodesRowView: View where RowManager: EpisodesRowManager { + + @EnvironmentObject + var itemRouter: ItemCoordinator.Router + @ObservedObject + var viewModel: RowManager + let onlyCurrentSeason: Bool + + var body: some View { + VStack(alignment: .leading) { + + Text(viewModel.selectedSeason?.name ?? L10n.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 let selectedSeason = viewModel.selectedSeason { + if let seasonEpisodes = viewModel.seasonsEpisodes[selectedSeason] { + if seasonEpisodes.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) + L10n.noEpisodesAvailable.text + .font(.footnote) + .padding(.bottom, 1) + } + .padding(.horizontal) + + Spacer() + } + .frame(width: 500) + .focusable() + } else { + ForEach(viewModel.seasonsEpisodes[selectedSeason]!, id: \.self) { episode in + EpisodeRowCard(viewModel: viewModel, episode: episode) + .id(episode.id) + } + } + } + } + } + .padding(.horizontal, 50) + .padding(.vertical) + .onChange(of: viewModel.selectedSeason) { _ in + if viewModel.selectedSeason?.id == viewModel.item.seasonId { + reader.scrollTo(viewModel.item.id) + } + } + .onChange(of: viewModel.seasonsEpisodes) { _ in + if viewModel.selectedSeason?.id == viewModel.item.seasonId { + reader.scrollTo(viewModel.item.id) + } + } + } + .edgesIgnoringSafeArea(.horizontal) + } + } + } +} diff --git a/Swiftfin tvOS/Components/SingleSeasonEpisodesRowView.swift b/Swiftfin tvOS/Components/SingleSeasonEpisodesRowView.swift deleted file mode 100644 index 3f56739c..00000000 --- a/Swiftfin tvOS/Components/SingleSeasonEpisodesRowView.swift +++ /dev/null @@ -1,123 +0,0 @@ -// -// 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) 2022 Jellyfin & 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) { - - L10n.episodes.text - .font(.title3) - .padding(.horizontal, 50) - - ScrollView(.horizontal) { - ScrollViewReader { _ 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) - L10n.noEpisodesAvailable.text - .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) - } - } - } -} diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift index b30c1778..8ce92dd3 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift @@ -56,7 +56,7 @@ struct CinematicEpisodeItemView: View { CinematicItemAboutView(viewModel: viewModel) - EpisodesRowView(viewModel: EpisodesRowViewModel(episodeItemViewModel: viewModel)) + EpisodesRowView(viewModel: viewModel, onlyCurrentSeason: true) .focusSection() if let seriesItem = viewModel.series { diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift index 31e0ae20..0c95276d 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift @@ -123,6 +123,18 @@ struct CinematicItemViewTopRow: View { .overlay(RoundedRectangle(cornerRadius: 2) .stroke(Color.secondary, lineWidth: 1)) } + + if viewModel.item.unaired { + if let premiereDate = viewModel.item.airDateLabel { + Text(premiereDate) + .font(.subheadline) + .fontWeight(.medium) + .lineLimit(1) + } + } + + // Dud text in case nothing was shown, something is necessary for proper alignment + Text("") } else { Text("") } diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeasonItemView.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeasonItemView.swift index 8fda6e65..e6e18eeb 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeasonItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeasonItemView.swift @@ -53,7 +53,8 @@ struct CinematicSeasonItemView: View { CinematicItemAboutView(viewModel: viewModel) - SingleSeasonEpisodesRowView(viewModel: SingleSeasonEpisodesRowViewModel(seasonItemViewModel: viewModel)) + EpisodesRowView(viewModel: viewModel, onlyCurrentSeason: true) + .focusSection() if let seriesItem = viewModel.seriesItem { PortraitItemsRowView(rowTitle: L10n.series, diff --git a/Swiftfin tvOS/Views/SettingsView/MissingItemsSettingsView.swift b/Swiftfin tvOS/Views/SettingsView/MissingItemsSettingsView.swift new file mode 100644 index 00000000..983a9604 --- /dev/null +++ b/Swiftfin tvOS/Views/SettingsView/MissingItemsSettingsView.swift @@ -0,0 +1,30 @@ +// +// 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) 2022 Jellyfin & Jellyfin Contributors +// + +import Defaults +import SwiftUI + +struct MissingItemsSettingsView: View { + + @Default(.shouldShowMissingSeasons) + var shouldShowMissingSeasons + + @Default(.shouldShowMissingEpisodes) + var shouldShowMissingEpisodes + + var body: some View { + Form { + Section { + Toggle(L10n.showMissingSeasons, isOn: $shouldShowMissingSeasons) + Toggle(L10n.showMissingEpisodes, isOn: $shouldShowMissingEpisodes) + } header: { + L10n.missingItems.text + } + } + } +} diff --git a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift index 452cd334..212c076b 100644 --- a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift +++ b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift @@ -133,6 +133,17 @@ struct SettingsView: View { Section(header: L10n.accessibility.text) { Toggle(L10n.showPosterLabels, isOn: $showPosterLabels) + Button { + settingsRouter.route(to: \.missingSettings) + } label: { + HStack { + L10n.missingItems.text + .foregroundColor(.primary) + Spacer() + Image(systemName: "chevron.right") + } + } + Picker(L10n.subtitleSize, selection: $subtitleSize) { ForEach(SubtitleSize.allCases, id: \.self) { size in Text(size.label).tag(size.rawValue) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 994b766b..496704a4 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -325,7 +325,6 @@ 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 */; }; @@ -390,6 +389,8 @@ E1B59FD92786AE4600A5287E /* NextUpCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B59FD82786AE4600A5287E /* NextUpCard.swift */; }; E1B6DCE8271A23780015B715 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1B6DCE7271A23780015B715 /* CombineExt */; }; E1B6DCEA271A23880015B715 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = E1B6DCE9271A23880015B715 /* SwiftyJSON */; }; + E1BDE359278E9ED2004E4022 /* MissingItemsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BDE358278E9ED2004E4022 /* MissingItemsSettingsView.swift */; }; + E1BDE35B278EA3A3004E4022 /* EpisodesRowCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BDE35A278EA3A3004E4022 /* EpisodesRowCard.swift */; }; E1C812BC277A8E5D00918266 /* PlaybackSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */; }; E1C812BD277A8E5D00918266 /* PlayerOverlayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812B5277A8E5D00918266 /* PlayerOverlayDelegate.swift */; }; E1C812BE277A8E5D00918266 /* VLCPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812B6277A8E5D00918266 /* VLCPlayerViewController.swift */; }; @@ -693,7 +694,6 @@ E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListCoordinator.swift; sourceTree = ""; }; E13F26AE278754E300DF4761 /* CinematicSeriesItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicSeriesItemView.swift; sourceTree = ""; }; E13F26B02787589300DF4761 /* CinematicSeasonItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicSeasonItemView.swift; sourceTree = ""; }; - E13F26B22787597300DF4761 /* SingleSeasonEpisodesRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleSeasonEpisodesRowView.swift; sourceTree = ""; }; E14F7D0626DB36EF007C3AE6 /* ItemPortraitMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemPortraitMainView.swift; sourceTree = ""; }; E14F7D0826DB36F7007C3AE6 /* ItemLandscapeMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemLandscapeMainView.swift; sourceTree = ""; }; E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = ""; }; @@ -728,6 +728,8 @@ E1AD106126D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemPortraitHeaderOverlayView.swift; sourceTree = ""; }; E1B59FD42786ADE500A5287E /* ContinueWatchingCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingCard.swift; sourceTree = ""; }; E1B59FD82786AE4600A5287E /* NextUpCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextUpCard.swift; sourceTree = ""; }; + E1BDE358278E9ED2004E4022 /* MissingItemsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissingItemsSettingsView.swift; sourceTree = ""; }; + E1BDE35A278EA3A3004E4022 /* EpisodesRowCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesRowCard.swift; sourceTree = ""; }; E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaybackSpeed.swift; sourceTree = ""; }; E1C812B5277A8E5D00918266 /* PlayerOverlayDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerOverlayDelegate.swift; sourceTree = ""; }; E1C812B6277A8E5D00918266 /* VLCPlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VLCPlayerViewController.swift; sourceTree = ""; }; @@ -993,7 +995,7 @@ 536D3D77267BB9650004248C /* Components */ = { isa = PBXGroup; children = ( - E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */, + E1BDE35C278EA3A7004E4022 /* EpisodesRowView */, E103A6A1278A7EB500820EC7 /* HomeCinematicView */, E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */, 531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */, @@ -1004,7 +1006,6 @@ E1E5D5412783B33900692DFE /* PortraitItemsRowView.swift */, 536D3D87267C17350004248C /* PublicUserButton.swift */, E17885A3278105170094FBCF /* SFSymbolButton.swift */, - E13F26B22787597300DF4761 /* SingleSeasonEpisodesRowView.swift */, ); path = Components; sourceTree = ""; @@ -1609,6 +1610,15 @@ path = NextUpView; sourceTree = ""; }; + E1BDE35C278EA3A7004E4022 /* EpisodesRowView */ = { + isa = PBXGroup; + children = ( + E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */, + E1BDE35A278EA3A3004E4022 /* EpisodesRowCard.swift */, + ); + path = EpisodesRowView; + sourceTree = ""; + }; E1C812CF277AE4C700918266 /* VideoPlayerCoordinator */ = { isa = PBXGroup; children = ( @@ -1656,6 +1666,7 @@ isa = PBXGroup; children = ( E1E5D5502783E67700692DFE /* ExperimentalSettingsView.swift */, + E1BDE358278E9ED2004E4022 /* MissingItemsSettingsView.swift */, E1E5D54E2783E67100692DFE /* OverlaySettingsView.swift */, 5398514426B64DA100101B49 /* SettingsView.swift */, ); @@ -2158,6 +2169,7 @@ E103A6A0278A7E4500820EC7 /* UICinematicBackgroundView.swift in Sources */, E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */, E17885A02780F55C0094FBCF /* tvOSVLCOverlay.swift in Sources */, + E1BDE359278E9ED2004E4022 /* MissingItemsSettingsView.swift in Sources */, E193D54D2719426600900D82 /* LibraryFilterView.swift in Sources */, C4BE07892728448B003F4AD1 /* LiveTVChannelsCoordinator.swift in Sources */, E193D53927193F8E00900D82 /* SearchCoordinator.swift in Sources */, @@ -2167,7 +2179,6 @@ 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 */, @@ -2239,6 +2250,7 @@ E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */, E193D547271941C500900D82 /* UserListView.swift in Sources */, E1D4BF7F2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */, + E1BDE35B278EA3A3004E4022 /* EpisodesRowCard.swift in Sources */, E193D53227193F7B00900D82 /* ConnectToServerCoodinator.swift in Sources */, 53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */, 5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */, @@ -2603,7 +2615,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"Swiftfin tvOS/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Swiftfin tvOS/Info.plist"; @@ -2633,7 +2645,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"Swiftfin tvOS/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Swiftfin tvOS/Info.plist"; diff --git a/Swiftfin/Components/EpisodesRowView/EpisodesRowView.swift b/Swiftfin/Components/EpisodesRowView/EpisodesRowView.swift index 68414789..10c5f2ff 100644 --- a/Swiftfin/Components/EpisodesRowView/EpisodesRowView.swift +++ b/Swiftfin/Components/EpisodesRowView/EpisodesRowView.swift @@ -15,39 +15,39 @@ struct EpisodesRowView: View where RowManager: EpisodesRowManager { var itemRouter: ItemCoordinator.Router @ObservedObject var viewModel: RowManager - let onlyCurrentSeason: Bool + let onlyCurrentSeason: Bool var body: some View { VStack(alignment: .leading, spacing: 0) { HStack { - - if onlyCurrentSeason { - if let currentSeason = Array(viewModel.seasonsEpisodes.keys).first(where: { $0.id == viewModel.item.id }) { - Text(currentSeason.name ?? L10n.noTitle) - } - } else { - Menu { - ForEach(Array(viewModel.seasonsEpisodes.keys).sorted(by: { $0.name ?? "" < $1.name ?? "" }), id: \.self) { season in - Button { - viewModel.select(season: season) - } label: { - if season.id == viewModel.selectedSeason?.id { - Label(season.name ?? L10n.season, systemImage: "checkmark") - } else { - Text(season.name ?? L10n.season) - } - } - } - } label: { - HStack(spacing: 5) { - Text(viewModel.selectedSeason?.name ?? L10n.unknown) - .fontWeight(.semibold) - .fixedSize() - Image(systemName: "chevron.down") - } - } - } + + if onlyCurrentSeason { + if let currentSeason = Array(viewModel.seasonsEpisodes.keys).first(where: { $0.id == viewModel.item.id }) { + Text(currentSeason.name ?? L10n.noTitle) + } + } else { + Menu { + ForEach(Array(viewModel.seasonsEpisodes.keys).sorted(by: { $0.name ?? "" < $1.name ?? "" }), id: \.self) { season in + Button { + viewModel.select(season: season) + } label: { + if season.id == viewModel.selectedSeason?.id { + Label(season.name ?? L10n.season, systemImage: "checkmark") + } else { + Text(season.name ?? L10n.season) + } + } + } + } label: { + HStack(spacing: 5) { + Text(viewModel.selectedSeason?.name ?? L10n.unknown) + .fontWeight(.semibold) + .fixedSize() + Image(systemName: "chevron.down") + } + } + } Spacer() } @@ -82,36 +82,36 @@ struct EpisodesRowView: View where RowManager: EpisodesRowManager { .frame(width: 200) .shadow(radius: 4, y: 2) } else if let selectedSeason = viewModel.selectedSeason { - if let seasonEpisodes = viewModel.seasonsEpisodes[selectedSeason] { - if seasonEpisodes.isEmpty { - VStack(alignment: .leading) { + if let seasonEpisodes = viewModel.seasonsEpisodes[selectedSeason] { + if seasonEpisodes.isEmpty { + VStack(alignment: .leading) { - Color.gray.ignoresSafeArea() - .mask(Rectangle().frame(width: 200, height: 112).cornerRadius(10)) - .frame(width: 200, height: 112) + Color.gray.ignoresSafeArea() + .mask(Rectangle().frame(width: 200, height: 112).cornerRadius(10)) + .frame(width: 200, height: 112) - VStack(alignment: .leading) { - Text("--") - .font(.footnote) - .foregroundColor(.secondary) + VStack(alignment: .leading) { + Text("--") + .font(.footnote) + .foregroundColor(.secondary) - L10n.noEpisodesAvailable.text - .font(.body) - .padding(.bottom, 1) - .lineLimit(2) - } + L10n.noEpisodesAvailable.text + .font(.body) + .padding(.bottom, 1) + .lineLimit(2) + } - Spacer() - } - .frame(width: 200) - .shadow(radius: 4, y: 2) - } else { - ForEach(seasonEpisodes, id: \.self) { episode in - EpisodeRowCard(viewModel: viewModel, episode: episode) - .id(episode.id) - } - } - } + Spacer() + } + .frame(width: 200) + .shadow(radius: 4, y: 2) + } else { + ForEach(seasonEpisodes, id: \.self) { episode in + EpisodeRowCard(viewModel: viewModel, episode: episode) + .id(episode.id) + } + } + } } } .padding(.horizontal) diff --git a/Swiftfin/Views/ItemView/ItemViewBody.swift b/Swiftfin/Views/ItemView/ItemViewBody.swift index 14b573ac..c7926c3b 100644 --- a/Swiftfin/Views/ItemView/ItemViewBody.swift +++ b/Swiftfin/Views/ItemView/ItemViewBody.swift @@ -86,7 +86,7 @@ struct ItemViewBody: View { if let episodeViewModel = viewModel as? EpisodeItemViewModel { EpisodesRowView(viewModel: episodeViewModel, onlyCurrentSeason: false) } else if let seasonViewModel = viewModel as? SeasonItemViewModel { - EpisodesRowView(viewModel: seasonViewModel, onlyCurrentSeason: true) + EpisodesRowView(viewModel: seasonViewModel, onlyCurrentSeason: true) } // MARK: Series diff --git a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift index 189cd09a..62c2c103 100644 --- a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift +++ b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift @@ -63,8 +63,8 @@ struct ItemLandscapeMainView: View { // MARK: ItemViewBody - ItemViewBody() - .environmentObject(viewModel) + ItemViewBody() + .environmentObject(viewModel) } } } diff --git a/Swiftfin/Views/SettingsView/MissingItemsSettingsView.swift b/Swiftfin/Views/SettingsView/MissingItemsSettingsView.swift index 92b3c571..983a9604 100644 --- a/Swiftfin/Views/SettingsView/MissingItemsSettingsView.swift +++ b/Swiftfin/Views/SettingsView/MissingItemsSettingsView.swift @@ -20,10 +20,10 @@ struct MissingItemsSettingsView: View { var body: some View { Form { Section { - Toggle("Show Missing Seasons", isOn: $shouldShowMissingSeasons) - Toggle("Show Missing Episodes", isOn: $shouldShowMissingEpisodes) + Toggle(L10n.showMissingSeasons, isOn: $shouldShowMissingSeasons) + Toggle(L10n.showMissingEpisodes, isOn: $shouldShowMissingEpisodes) } header: { - Text("Missing Items") + L10n.missingItems.text } } } diff --git a/Swiftfin/Views/SettingsView/SettingsView.swift b/Swiftfin/Views/SettingsView/SettingsView.swift index 82d80b5f..de5d8f39 100644 --- a/Swiftfin/Views/SettingsView/SettingsView.swift +++ b/Swiftfin/Views/SettingsView/SettingsView.swift @@ -144,12 +144,12 @@ struct SettingsView: View { Button { settingsRouter.route(to: \.missingSettings) } label: { - HStack { - Text("Missing Items") - .foregroundColor(.primary) - Spacer() - Image(systemName: "chevron.right") - } + HStack { + L10n.missingItems.text + .foregroundColor(.primary) + Spacer() + Image(systemName: "chevron.right") + } } Picker(L10n.appearance, selection: $appAppearance) { diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index e8333a082536459d636bd9e33b74956867541bdd..4d839fe9db6c7d8d67f299f5c574cb78c8932172 100644 GIT binary patch delta 206 zcmZ1yx*~kT5-mw3hFpeBhGHPhWXNMkXYgbwVMqmv6;D>wk~CL9@EMdCtQoj~WHCbq zLq0<}gD;A{V4$u