diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 191aec31..a759e910 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -290,6 +290,9 @@ E18845F826DEA9C900B0C5B7 /* ItemViewBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18845F726DEA9C900B0C5B7 /* ItemViewBody.swift */; }; E188460026DECB9E00B0C5B7 /* ItemLandscapeTopBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18845FF26DECB9E00B0C5B7 /* ItemLandscapeTopBarView.swift */; }; E188460426DEF04800B0C5B7 /* EpisodeCardVStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E188460326DEF04800B0C5B7 /* EpisodeCardVStackView.swift */; }; + E19169CE272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; }; + E19169CF272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; }; + E19169D0272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; }; E193D4D827193CAC00900D82 /* PortraitImageStackable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D4D727193CAC00900D82 /* PortraitImageStackable.swift */; }; E193D4D927193CAC00900D82 /* PortraitImageStackable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D4D727193CAC00900D82 /* PortraitImageStackable.swift */; }; E193D4DB27193CCA00900D82 /* PillStackable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D4DA27193CCA00900D82 /* PillStackable.swift */; }; @@ -342,6 +345,7 @@ E1D4BF8C2719F39F00A11E64 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF802719D22800A11E64 /* AppAppearance.swift */; }; E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */; }; + E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; }; E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; }; @@ -575,6 +579,7 @@ E18845F726DEA9C900B0C5B7 /* ItemViewBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewBody.swift; sourceTree = ""; }; E18845FF26DECB9E00B0C5B7 /* ItemLandscapeTopBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemLandscapeTopBarView.swift; sourceTree = ""; }; E188460326DEF04800B0C5B7 /* EpisodeCardVStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeCardVStackView.swift; sourceTree = ""; }; + E19169CD272514760085832A /* HTTPScheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPScheme.swift; sourceTree = ""; }; E193D4D727193CAC00900D82 /* PortraitImageStackable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitImageStackable.swift; sourceTree = ""; }; E193D4DA27193CCA00900D82 /* PillStackable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillStackable.swift; sourceTree = ""; }; E193D5422719407E00900D82 /* tvOSMainCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSMainCoordinator.swift; sourceTree = ""; }; @@ -597,6 +602,7 @@ E1D4BF862719D27100A11E64 /* Bitrates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bitrates.swift; sourceTree = ""; }; E1D4BF892719D3D000A11E64 /* BasicAppSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsCoordinator.swift; sourceTree = ""; }; E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = ""; }; + E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshHelper.swift; sourceTree = ""; }; E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = ""; }; E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; E1FCD09526C47118007C8DCF /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = ""; }; @@ -807,6 +813,7 @@ E1AD104926D94822003E4A08 /* DetailItem.swift */, 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */, 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */, + E19169CD272514760085832A /* HTTPScheme.swift */, E193D4DA27193CCA00900D82 /* PillStackable.swift */, E193D4D727193CAC00900D82 /* PortraitImageStackable.swift */, E1D4BF832719D25A00A11E64 /* TrackLanguage.swift */, @@ -856,6 +863,7 @@ 5377CBF3263B596A003A4E83 /* JellyfinPlayer */ = { isa = PBXGroup; children = ( + E1DD1127271E7D15005BE12F /* Objects */, E13DD3BB27163C3E009D4DAF /* App */, 62ECA01926FA6D6900E8EBB7 /* AppURLHandler */, 5377CBF8263B596B003A4E83 /* Assets.xcassets */, @@ -1293,6 +1301,14 @@ path = Views; sourceTree = ""; }; + E1DD1127271E7D15005BE12F /* Objects */ = { + isa = PBXGroup; + children = ( + E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */, + ); + path = Objects; + sourceTree = ""; + }; E1FCD08E26C466F3007C8DCF /* Errors */ = { isa = PBXGroup; children = ( @@ -1734,6 +1750,7 @@ E193D549271941CC00900D82 /* UserSignInView.swift in Sources */, 535870AA2669D8AE00D05A09 /* BlurHashDecode.swift in Sources */, 53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */, + E19169CF272514760085832A /* HTTPScheme.swift in Sources */, C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */, 531069582684E7EE00CFFDBA /* MediaInfoView.swift in Sources */, E1D4BF822719D22800A11E64 /* AppAppearance.swift in Sources */, @@ -1808,6 +1825,7 @@ E173DA5426D050F500CC4EB7 /* ServerDetailViewModel.swift in Sources */, E188460426DEF04800B0C5B7 /* EpisodeCardVStackView.swift in Sources */, 53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */, + E19169CE272514760085832A /* HTTPScheme.swift in Sources */, 53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */, 62133890265F83A900A81A2A /* LibraryListView.swift in Sources */, 62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */, @@ -1841,6 +1859,7 @@ 6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */, 62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */, 53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */, + E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */, E1D4BF842719D25A00A11E64 /* TrackLanguage.swift in Sources */, E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */, E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */, @@ -1904,6 +1923,7 @@ files = ( 53649AB3269D3F5B00A2D8B7 /* LogManager.swift in Sources */, E13DD3CB27164BA8009D4DAF /* UIDeviceExtensions.swift in Sources */, + E19169D0272514760085832A /* HTTPScheme.swift in Sources */, 6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */, 628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */, 6267B3DB2671139400A7371D /* ImageExtensions.swift in Sources */, diff --git a/JellyfinPlayer/App/JellyfinPlayerApp.swift b/JellyfinPlayer/App/JellyfinPlayerApp.swift index 34e5c3fa..27440a56 100644 --- a/JellyfinPlayer/App/JellyfinPlayerApp.swift +++ b/JellyfinPlayer/App/JellyfinPlayerApp.swift @@ -19,8 +19,7 @@ struct JellyfinPlayerApp: App { var body: some Scene { WindowGroup { - // TODO: Replace with a SplashView - Color(appAppearance.style == .light ? UIColor.white : UIColor.black) + EmptyView() .ignoresSafeArea() .onAppear { setupAppearance() diff --git a/JellyfinPlayer/Assets.xcassets/LaunchScreenBackground.colorset/Contents.json b/JellyfinPlayer/Assets.xcassets/LaunchScreenBackground.colorset/Contents.json new file mode 100644 index 00000000..04256378 --- /dev/null +++ b/JellyfinPlayer/Assets.xcassets/LaunchScreenBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/Contents.json b/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/Contents.json new file mode 100644 index 00000000..e708d061 --- /dev/null +++ b/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "swiftfin-logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "swiftfin-logo-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "swiftfin-logo-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/swiftfin-logo-1.png b/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/swiftfin-logo-1.png new file mode 100644 index 00000000..efdfe428 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/swiftfin-logo-1.png differ diff --git a/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/swiftfin-logo-2.png b/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/swiftfin-logo-2.png new file mode 100644 index 00000000..efdfe428 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/swiftfin-logo-2.png differ diff --git a/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/swiftfin-logo.png b/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/swiftfin-logo.png new file mode 100644 index 00000000..efdfe428 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/swiftfin-logo.imageset/swiftfin-logo.png differ diff --git a/JellyfinPlayer/Info.plist b/JellyfinPlayer/Info.plist index b8c7ecaf..8ad4f31b 100644 --- a/JellyfinPlayer/Info.plist +++ b/JellyfinPlayer/Info.plist @@ -62,9 +62,14 @@ network. UIApplicationSupportsIndirectInputEvents UILaunchScreen - - UILaunchStoryboardName - VideoPlayer + + UIImageRespectsSafeAreaInsets + + UIImageName + swiftfin-logo + UIColorName + LaunchScreenBackground + UIRequiredDeviceCapabilities armv7 diff --git a/JellyfinPlayer/Objects/RefreshHelper.swift b/JellyfinPlayer/Objects/RefreshHelper.swift new file mode 100644 index 00000000..df2b7c3c --- /dev/null +++ b/JellyfinPlayer/Objects/RefreshHelper.swift @@ -0,0 +1,23 @@ +// + /* + * 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 UIKit + +// A more general derivative of +// https://stackoverflow.com/questions/65812080/introspect-library-uirefreshcontrol-with-swiftui-not-working +class RefreshHelper { + var refreshControl: UIRefreshControl? + var refreshAction: (() -> Void)? + + @objc func didRefresh() { + guard let refreshControl = refreshControl else { return } + refreshAction?() + refreshControl.endRefreshing() + } +} diff --git a/JellyfinPlayer/Views/BasicAppSettingsView.swift b/JellyfinPlayer/Views/BasicAppSettingsView.swift index 286f2163..ed73b75f 100644 --- a/JellyfinPlayer/Views/BasicAppSettingsView.swift +++ b/JellyfinPlayer/Views/BasicAppSettingsView.swift @@ -18,6 +18,7 @@ struct BasicAppSettingsView: View { @State var resetTapped: Bool = false @Default(.appAppearance) var appAppearance + @Default(.defaultHTTPScheme) var defaultHTTPScheme var body: some View { Form { @@ -33,6 +34,16 @@ struct BasicAppSettingsView: View { Text("Accessibility") } + Section { + Picker("Default Scheme", selection: $defaultHTTPScheme) { + ForEach(HTTPScheme.allCases, id: \.self) { scheme in + Text("\(scheme.rawValue)") + } + } + } header: { + Text("Networking") + } + Button { resetTapped = true } label: { diff --git a/JellyfinPlayer/Views/ConnectToServerView.swift b/JellyfinPlayer/Views/ConnectToServerView.swift index 5f65ab3a..624f623b 100644 --- a/JellyfinPlayer/Views/ConnectToServerView.swift +++ b/JellyfinPlayer/Views/ConnectToServerView.swift @@ -6,14 +6,17 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ -import SwiftUI +import Defaults import Stinsen +import SwiftUI struct ConnectToServerView: View { @ObservedObject var viewModel: ConnectToServerViewModel @State var uri = "" + @Default(.defaultHTTPScheme) var defaultHTTPScheme + var body: some View { List { Section { @@ -21,6 +24,11 @@ struct ConnectToServerView: View { .disableAutocorrection(true) .autocapitalization(.none) .keyboardType(.URL) + .onAppear { + if uri == "" { + uri = "\(defaultHTTPScheme.rawValue)://" + } + } if viewModel.isLoading { Button(role: .destructive) { diff --git a/JellyfinPlayer/Views/HomeView.swift b/JellyfinPlayer/Views/HomeView.swift index 15c3001c..51b3ae1e 100644 --- a/JellyfinPlayer/Views/HomeView.swift +++ b/JellyfinPlayer/Views/HomeView.swift @@ -8,12 +8,15 @@ */ import Foundation +import Introspect import SwiftUI struct HomeView: View { @EnvironmentObject var homeRouter: HomeCoordinator.Router @StateObject var viewModel = HomeViewModel() + + private let refreshHelper = RefreshHelper() @ViewBuilder var innerBody: some View { @@ -28,33 +31,40 @@ struct HomeView: View { if !viewModel.nextUpItems.isEmpty { NextUpView(items: viewModel.nextUpItems) } - if !viewModel.librariesShowRecentlyAddedIDs.isEmpty { - ForEach(viewModel.librariesShowRecentlyAddedIDs, id: \.self) { libraryID in - let library = viewModel.libraries.first(where: { $0.id == libraryID }) - HStack { - Text("Latest \(library?.name ?? "")") - .font(.title2) - .fontWeight(.bold) - Spacer() - Button { - homeRouter - .route(to: \.library, (viewModel: .init(parentID: libraryID, - filters: viewModel.recentFilterSet), - title: library?.name ?? "")) - } label: { - HStack { - Text("See All").font(.subheadline).fontWeight(.bold) - Image(systemName: "chevron.right").font(Font.subheadline.bold()) - } + + ForEach(viewModel.libraries, id: \.self) { library in + HStack { + Text("Latest \(library.name ?? "")") + .font(.title2) + .fontWeight(.bold) + Spacer() + Button { + homeRouter + .route(to: \.library, (viewModel: .init(parentID: library.id!, + filters: viewModel.recentFilterSet), + title: library.name ?? "")) + } label: { + HStack { + Text("See All").font(.subheadline).fontWeight(.bold) + Image(systemName: "chevron.right").font(Font.subheadline.bold()) } - }.padding(.leading, 16) - .padding(.trailing, 16) - LatestMediaView(viewModel: .init(libraryID: libraryID)) - } + } + }.padding(.leading, 16) + .padding(.trailing, 16) + LatestMediaView(viewModel: .init(libraryID: library.id!)) } } .padding(.bottom, UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30) } + .introspectScrollView { scrollView in + let control = UIRefreshControl() + + refreshHelper.refreshControl = control + refreshHelper.refreshAction = viewModel.refresh + + control.addTarget(refreshHelper, action: #selector(RefreshHelper.didRefresh), for: .valueChanged) + scrollView.refreshControl = control + } } } diff --git a/Shared/Objects/HTTPScheme.swift b/Shared/Objects/HTTPScheme.swift new file mode 100644 index 00000000..28152234 --- /dev/null +++ b/Shared/Objects/HTTPScheme.swift @@ -0,0 +1,16 @@ +// + /* + * 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 Foundation + +enum HTTPScheme: String, Defaults.Serializable, CaseIterable { + case http + case https +} diff --git a/Shared/ServerDiscovery/ServerDiscovery.swift b/Shared/ServerDiscovery/ServerDiscovery.swift index 7d718078..6f34789d 100644 --- a/Shared/ServerDiscovery/ServerDiscovery.swift +++ b/Shared/ServerDiscovery/ServerDiscovery.swift @@ -37,7 +37,7 @@ public class ServerDiscovery { if let port = components?.port { return port } - return 8096 + return 7359 } enum CodingKeys: String, CodingKey { diff --git a/Shared/Singleton/SessionManager.swift b/Shared/Singleton/SessionManager.swift index dbf3db37..333ba811 100644 --- a/Shared/Singleton/SessionManager.swift +++ b/Shared/Singleton/SessionManager.swift @@ -54,10 +54,14 @@ final class SessionManager { // Connects to a server at the given uri, storing if successful func connectToServer(with uri: String) -> AnyPublisher { - var uri = uri - if !uri.contains("http") { - uri = "https://" + uri + var uriComponents = URLComponents(string: uri) ?? URLComponents() + + if uriComponents.scheme == nil { + uriComponents.scheme = SwiftfinStore.Defaults.suite[.defaultHTTPScheme].rawValue } + + var uri = uriComponents.string ?? "" + if uri.last == "/" { uri = String(uri.dropLast()) } diff --git a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift index 53ce5f84..ab6a28d0 100644 --- a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift +++ b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift @@ -23,6 +23,7 @@ extension SwiftfinStore { extension Defaults.Keys { static let lastServerUserID = Defaults.Key("lastServerUserID", suite: SwiftfinStore.Defaults.suite) + static let defaultHTTPScheme = Key("defaultHTTPScheme", default: .http, suite: SwiftfinStore.Defaults.suite) static let inNetworkBandwidth = Key("InNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.suite) static let outOfNetworkBandwidth = Key("OutOfNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.suite) static let isAutoSelectSubtitles = Key("isAutoSelectSubtitles", default: false, suite: SwiftfinStore.Defaults.suite) diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index 88eb4188..bd2c1e24 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -14,10 +14,10 @@ import JellyfinAPI final class HomeViewModel: ViewModel { - @Published var librariesShowRecentlyAddedIDs = [String]() - @Published var libraries = [BaseItemDto]() - @Published var resumeItems = [BaseItemDto]() - @Published var nextUpItems = [BaseItemDto]() + @Published var librariesShowRecentlyAddedIDs: [String] = [] + @Published var libraries: [BaseItemDto] = [] + @Published var resumeItems: [BaseItemDto] = [] + @Published var nextUpItems: [BaseItemDto] = [] // temp var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded]) @@ -32,26 +32,42 @@ final class HomeViewModel: ViewModel { UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id) .trackActivity(loading) .sink(receiveCompletion: { completion in - self.handleAPIRequestError(completion: completion) + switch completion { + case .finished: () + case .failure(_): + self.libraries = [] + self.handleAPIRequestError(completion: completion) + } }, receiveValue: { response in + + var newLibraries: [BaseItemDto] = [] + response.items!.forEach { item in LogManager.shared.log.debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")") if item.collectionType == "movies" || item.collectionType == "tvshows" { - self.libraries.append(item) + newLibraries.append(item) } } UserAPI.getCurrentUser() .trackActivity(self.loading) .sink(receiveCompletion: { completion in - self.handleAPIRequestError(completion: completion) + switch completion { + case .finished: () + case .failure(_): + self.libraries = [] + self.handleAPIRequestError(completion: completion) + } }, receiveValue: { response in - self.libraries.forEach { library in - if !(response.configuration?.latestItemsExcludes?.contains(library.id!))! { - LogManager.shared.log.debug("Adding library \(library.id!) (\(library.name ?? "nil")) to recently added list") - self.librariesShowRecentlyAddedIDs.append(library.id!) + let excludeIDs = response.configuration?.latestItemsExcludes != nil ? response.configuration!.latestItemsExcludes! : [] + + for excludeID in excludeIDs { + newLibraries.removeAll { library in + return library.id == excludeID } } + + self.libraries = newLibraries }) .store(in: &self.cancellables) }) @@ -59,12 +75,20 @@ final class HomeViewModel: ViewModel { ItemsAPI.getResumeItems(userId: SessionManager.main.currentLogin.user.id, limit: 12, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], - mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb]) + mediaTypes: ["Video"], + imageTypeLimit: 1, + enableImageTypes: [.primary, .backdrop, .thumb]) .trackActivity(loading) .sink(receiveCompletion: { completion in - self.handleAPIRequestError(completion: completion) + switch completion { + case .finished: () + case .failure(_): + self.resumeItems = [] + self.handleAPIRequestError(completion: completion) + } }, receiveValue: { response in LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) resume items") + self.resumeItems = response.items ?? [] }) .store(in: &cancellables) @@ -73,9 +97,15 @@ final class HomeViewModel: ViewModel { fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) .trackActivity(loading) .sink(receiveCompletion: { completion in - self.handleAPIRequestError(completion: completion) + switch completion { + case .finished: () + case .failure(_): + self.nextUpItems = [] + self.handleAPIRequestError(completion: completion) + } }, receiveValue: { response in LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) nextup items") + self.nextUpItems = response.items ?? [] }) .store(in: &cancellables) diff --git a/Translations/ar.lproj/Localizable.strings b/Translations/ar.lproj/Localizable.strings new file mode 100644 index 00000000..ed60b89c Binary files /dev/null and b/Translations/ar.lproj/Localizable.strings differ diff --git a/Translations/cs.lproj/Localizable.strings b/Translations/cs.lproj/Localizable.strings index 5a1148c6..5394ced2 100644 Binary files a/Translations/cs.lproj/Localizable.strings and b/Translations/cs.lproj/Localizable.strings differ diff --git a/Translations/de.lproj/Localizable.strings b/Translations/de.lproj/Localizable.strings index bfa5f2c3..5eb5a3a0 100644 Binary files a/Translations/de.lproj/Localizable.strings and b/Translations/de.lproj/Localizable.strings differ diff --git a/Translations/el.lproj/Localizable.strings b/Translations/el.lproj/Localizable.strings index 7f22c5cb..b76ab027 100644 Binary files a/Translations/el.lproj/Localizable.strings and b/Translations/el.lproj/Localizable.strings differ diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index e190135f..5ce5535c 100644 Binary files a/Translations/en.lproj/Localizable.strings and b/Translations/en.lproj/Localizable.strings differ diff --git a/Translations/eo.lproj/Localizable.strings b/Translations/eo.lproj/Localizable.strings new file mode 100644 index 00000000..8561f8d7 Binary files /dev/null and b/Translations/eo.lproj/Localizable.strings differ diff --git a/Translations/es.lproj/Localizable.strings b/Translations/es.lproj/Localizable.strings index 59254ad1..6347d91c 100644 Binary files a/Translations/es.lproj/Localizable.strings and b/Translations/es.lproj/Localizable.strings differ diff --git a/Translations/it.lproj/Localizable.strings b/Translations/it.lproj/Localizable.strings index d86aa2b1..89444a6b 100644 Binary files a/Translations/it.lproj/Localizable.strings and b/Translations/it.lproj/Localizable.strings differ diff --git a/Translations/ko.lproj/Localizable.strings b/Translations/ko.lproj/Localizable.strings index 73b45129..ddc4290d 100644 Binary files a/Translations/ko.lproj/Localizable.strings and b/Translations/ko.lproj/Localizable.strings differ diff --git a/Translations/sk.lproj/Localizable.strings b/Translations/sk.lproj/Localizable.strings index 99b53e28..019d0bcb 100644 Binary files a/Translations/sk.lproj/Localizable.strings and b/Translations/sk.lproj/Localizable.strings differ diff --git a/Translations/sl.lproj/Localizable.strings b/Translations/sl.lproj/Localizable.strings index 18ddd012..372ef123 100644 Binary files a/Translations/sl.lproj/Localizable.strings and b/Translations/sl.lproj/Localizable.strings differ diff --git a/Translations/sv.lproj/Localizable.strings b/Translations/sv.lproj/Localizable.strings index 3c54a23b..17ef9415 100644 Binary files a/Translations/sv.lproj/Localizable.strings and b/Translations/sv.lproj/Localizable.strings differ diff --git a/Translations/ta.lproj/Localizable.strings b/Translations/ta.lproj/Localizable.strings index f342a313..15dcbfcb 100644 Binary files a/Translations/ta.lproj/Localizable.strings and b/Translations/ta.lproj/Localizable.strings differ diff --git a/Translations/vi.lproj/Localizable.strings b/Translations/vi.lproj/Localizable.strings index b1fc30cb..43a6af61 100644 Binary files a/Translations/vi.lproj/Localizable.strings and b/Translations/vi.lproj/Localizable.strings differ diff --git a/Translations/zh-Hans.lproj/Localizable.strings b/Translations/zh-Hans.lproj/Localizable.strings index 89366608..6caed672 100644 Binary files a/Translations/zh-Hans.lproj/Localizable.strings and b/Translations/zh-Hans.lproj/Localizable.strings differ diff --git a/Translations/zh-Hant.lproj/Localizable.strings b/Translations/zh-Hant.lproj/Localizable.strings index c0722236..dbfd1185 100644 Binary files a/Translations/zh-Hant.lproj/Localizable.strings and b/Translations/zh-Hant.lproj/Localizable.strings differ