From 3a090aaf4e5f0ec1b165e8587f76d0771aecee0b Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Sat, 21 Aug 2021 19:55:32 +0900 Subject: [PATCH] Add Stinsen Add ConnectToServerCoodinator Add HomeCoordinator Add LibraryListCoordinator Add MainCoordinator Add MainTabCoordinator --- JellyfinPlayer.xcodeproj/project.pbxproj | 49 ++++++++++++++++-- .../xcshareddata/swiftpm/Package.resolved | 9 ++++ JellyfinPlayer/ConnectToServerView.swift | 3 ++ .../ConnectToServerCoodinator.swift | 25 ++++++++++ .../Coordinators/HomeCoordinator.swift | 25 ++++++++++ .../Coordinators/LibraryListCoordinator.swift | 25 ++++++++++ .../Coordinators/MainCoordinator.swift | 35 +++++++++++++ .../Coordinators/MainTabCoordinator.swift | 48 ++++++++++++++++++ JellyfinPlayer/HomeView.swift | 2 +- JellyfinPlayer/JellyfinPlayerApp.swift | 50 ++++++++++--------- JellyfinPlayer/MainTabView.swift | 46 ----------------- JellyfinPlayer/SettingsView.swift | 43 ++++++++-------- JellyfinPlayer/SplashView.swift | 16 +++--- JellyfinPlayer/VideoPlayer.swift | 5 ++ .../ViewModels/ConnectToServerViewModel.swift | 30 +++++++---- 15 files changed, 297 insertions(+), 114 deletions(-) create mode 100644 JellyfinPlayer/Coordinators/ConnectToServerCoodinator.swift create mode 100644 JellyfinPlayer/Coordinators/HomeCoordinator.swift create mode 100644 JellyfinPlayer/Coordinators/LibraryListCoordinator.swift create mode 100644 JellyfinPlayer/Coordinators/MainCoordinator.swift create mode 100644 JellyfinPlayer/Coordinators/MainTabCoordinator.swift delete mode 100644 JellyfinPlayer/MainTabView.swift diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 1bb21ac6..f053e3ff 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -141,7 +141,6 @@ 624C21752685CF60007F1390 /* SearchablePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624C21742685CF60007F1390 /* SearchablePickerView.swift */; }; 625CB5682678B6FB00530A6E /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5672678B6FB00530A6E /* SplashView.swift */; }; 625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5692678B71200530A6E /* SplashViewModel.swift */; }; - 625CB56C2678C0FD00530A6E /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB56B2678C0FD00530A6E /* MainTabView.swift */; }; 625CB56F2678C23300530A6E /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB56E2678C23300530A6E /* HomeView.swift */; }; 625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5722678C32A00530A6E /* HomeViewModel.swift */; }; 625CB5752678C33500530A6E /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* LibraryListViewModel.swift */; }; @@ -166,6 +165,12 @@ 628B95382670CDAB0091AF3B /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBFF263B596B003A4E83 /* Model.xcdatamodeld */; }; 628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 628B95392670CE250091AF3B /* KeychainSwift */; }; 628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; }; + 62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */ = {isa = PBXBuildFile; productRef = 62C29E9B26D0FE4200C1D2E7 /* Stinsen */; }; + 62C29E9F26D1016600C1D2E7 /* MainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29E9E26D1016600C1D2E7 /* MainCoordinator.swift */; }; + 62C29EA126D102A500C1D2E7 /* MainTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA026D102A500C1D2E7 /* MainTabCoordinator.swift */; }; + 62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */; }; + 62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */; }; + 62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */; }; 62CB3F462685BAF7003D0A6F /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 62CB3F452685BAF7003D0A6F /* Defaults */; }; 62CB3F482685BB3B003D0A6F /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 62CB3F472685BB3B003D0A6F /* Defaults */; }; 62CB3F4B2685BB77003D0A6F /* DefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */; }; @@ -352,7 +357,6 @@ 624C21742685CF60007F1390 /* SearchablePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchablePickerView.swift; sourceTree = ""; }; 625CB5672678B6FB00530A6E /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; 625CB5692678B71200530A6E /* SplashViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewModel.swift; sourceTree = ""; }; - 625CB56B2678C0FD00530A6E /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; 625CB56E2678C23300530A6E /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; 625CB5722678C32A00530A6E /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; 625CB5742678C33500530A6E /* LibraryListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListViewModel.swift; sourceTree = ""; }; @@ -369,6 +373,11 @@ 628B952A2670CABE0091AF3B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 628B95362670CB800091AF3B /* JellyfinWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinWidget.swift; sourceTree = ""; }; 628B953B2670D1FC0091AF3B /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = ""; }; + 62C29E9E26D1016600C1D2E7 /* MainCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainCoordinator.swift; sourceTree = ""; }; + 62C29EA026D102A500C1D2E7 /* MainTabCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabCoordinator.swift; sourceTree = ""; }; + 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerCoodinator.swift; sourceTree = ""; }; + 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCoordinator.swift; sourceTree = ""; }; + 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListCoordinator.swift; sourceTree = ""; }; 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsExtension.swift; sourceTree = ""; }; 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaViewModel.swift; sourceTree = ""; }; 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchViewModel.swift; sourceTree = ""; }; @@ -419,6 +428,7 @@ buildActionMask = 2147483647; files = ( 53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */, + 62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */, 62CB3F462685BAF7003D0A6F /* Defaults in Frameworks */, 5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */, 53EC6E25267EB10F006DD26A /* SwiftyJSON in Frameworks */, @@ -632,6 +642,7 @@ 5377CBF3263B596A003A4E83 /* JellyfinPlayer */ = { isa = PBXGroup; children = ( + 62C29E9D26D0FE5900C1D2E7 /* Coordinators */, 53F866422687A45400DCD1D7 /* Components */, 53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */, 5377CBF8263B596B003A4E83 /* Assets.xcassets */, @@ -660,7 +671,6 @@ 532E68CE267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift */, 53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */, 625CB5672678B6FB00530A6E /* SplashView.swift */, - 625CB56B2678C0FD00530A6E /* MainTabView.swift */, 625CB56E2678C23300530A6E /* HomeView.swift */, ); path = JellyfinPlayer; @@ -746,6 +756,18 @@ path = WidgetExtension; sourceTree = ""; }; + 62C29E9D26D0FE5900C1D2E7 /* Coordinators */ = { + isa = PBXGroup; + children = ( + 62C29E9E26D1016600C1D2E7 /* MainCoordinator.swift */, + 62C29EA026D102A500C1D2E7 /* MainTabCoordinator.swift */, + 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */, + 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */, + 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */, + ); + path = Coordinators; + sourceTree = ""; + }; 62EC352A26766657000E9F2D /* Singleton */ = { isa = PBXGroup; children = ( @@ -850,6 +872,7 @@ 62CB3F452685BAF7003D0A6F /* Defaults */, 53649AAC269CFAEA00A2D8B7 /* Puppy */, 6260FFF826A09754003FA968 /* CombineExt */, + 62C29E9B26D0FE4200C1D2E7 /* Stinsen */, ); productName = JellyfinPlayer; productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */; @@ -925,6 +948,7 @@ 53272533268BF9710035FBF1 /* XCRemoteSwiftPackageReference "SwiftUIFocusGuide" */, 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */, 6260FFF726A09754003FA968 /* XCRemoteSwiftPackageReference "CombineExt" */, + 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; projectDirPath = ""; @@ -1165,11 +1189,14 @@ 53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */, 62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */, 625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */, + 62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */, 62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */, 5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */, + 62C29E9F26D1016600C1D2E7 /* MainCoordinator.swift in Sources */, 5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */, 53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */, 53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */, + 62C29EA126D102A500C1D2E7 /* MainTabCoordinator.swift in Sources */, 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */, 6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */, 625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */, @@ -1180,6 +1207,7 @@ 53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */, 53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */, 62133890265F83A900A81A2A /* LibraryListView.swift in Sources */, + 62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */, 0959A5FD2686D29800C7C9A9 /* VideoUpNextView.swift in Sources */, 62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */, 625CB56F2678C23300530A6E /* HomeView.swift in Sources */, @@ -1204,6 +1232,7 @@ 53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */, 53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */, 621338B32660A07800A81A2A /* LazyView.swift in Sources */, + 62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */, 531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */, 62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */, 624C21752685CF60007F1390 /* SearchablePickerView.swift in Sources */, @@ -1216,7 +1245,6 @@ 6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */, 62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */, 5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */, - 625CB56C2678C0FD00530A6E /* MainTabView.swift in Sources */, 539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */, 5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */, 09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */, @@ -1718,6 +1746,14 @@ kind = branch; }; }; + 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/rundfunk47/stinsen"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; + }; + }; 62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/acvigue/Defaults"; @@ -1844,6 +1880,11 @@ package = 5338F755263B7E2E0014BF09 /* XCRemoteSwiftPackageReference "keychain-swift" */; productName = KeychainSwift; }; + 62C29E9B26D0FE4200C1D2E7 /* Stinsen */ = { + isa = XCSwiftPackageProductDependency; + package = 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */; + productName = Stinsen; + }; 62CB3F452685BAF7003D0A6F /* Defaults */ = { isa = XCSwiftPackageProductDependency; package = 62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */; diff --git a/JellyfinPlayer.xcworkspace/xcshareddata/swiftpm/Package.resolved b/JellyfinPlayer.xcworkspace/xcshareddata/swiftpm/Package.resolved index 462ac202..1a18b422 100644 --- a/JellyfinPlayer.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/JellyfinPlayer.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -109,6 +109,15 @@ "version": "0.3.0" } }, + { + "package": "Stinsen", + "repositoryURL": "https://github.com/rundfunk47/stinsen", + "state": { + "branch": null, + "revision": "e72c20b2c4bde0d6c3a911d4eda688fee7aa3bba", + "version": "1.1.0" + } + }, { "package": "swift-log", "repositoryURL": "https://github.com/apple/swift-log.git", diff --git a/JellyfinPlayer/ConnectToServerView.swift b/JellyfinPlayer/ConnectToServerView.swift index 0a565880..71f0caeb 100644 --- a/JellyfinPlayer/ConnectToServerView.swift +++ b/JellyfinPlayer/ConnectToServerView.swift @@ -6,8 +6,10 @@ */ import SwiftUI +import Stinsen struct ConnectToServerView: View { + @EnvironmentObject var main: ViewRouter @StateObject var viewModel = ConnectToServerViewModel() @State var username = "" @State var password = "" @@ -59,6 +61,7 @@ struct ConnectToServerView: View { if SessionManager.current.doesUserHaveSavedSession(userID: publicUser.id!) { let user = SessionManager.current.getSavedSession(userID: publicUser.id!) SessionManager.current.loginWithSavedSession(user: user) + main.route(to: .mainTab) } else { username = publicUser.name ?? "" viewModel.selectedPublicUser = publicUser diff --git a/JellyfinPlayer/Coordinators/ConnectToServerCoodinator.swift b/JellyfinPlayer/Coordinators/ConnectToServerCoodinator.swift new file mode 100644 index 00000000..ec5e712b --- /dev/null +++ b/JellyfinPlayer/Coordinators/ConnectToServerCoodinator.swift @@ -0,0 +1,25 @@ +// +/* + * 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 Foundation +import Stinsen +import SwiftUI + +final class ConnectToServerCoodinator: NavigationCoordinatable { + var navigationStack = NavigationStack() + + enum Route: NavigationRoute {} + + func resolveRoute(route: Route) -> Transition {} + + @ViewBuilder + func start() -> some View { + ConnectToServerView() + } +} diff --git a/JellyfinPlayer/Coordinators/HomeCoordinator.swift b/JellyfinPlayer/Coordinators/HomeCoordinator.swift new file mode 100644 index 00000000..4b0fbeb8 --- /dev/null +++ b/JellyfinPlayer/Coordinators/HomeCoordinator.swift @@ -0,0 +1,25 @@ +// +/* + * 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 Foundation +import Stinsen +import SwiftUI + +final class HomeCoordinator: NavigationCoordinatable { + var navigationStack = NavigationStack() + + enum Route: NavigationRoute {} + + func resolveRoute(route: Route) -> Transition {} + + @ViewBuilder + func start() -> some View { + HomeView() + } +} diff --git a/JellyfinPlayer/Coordinators/LibraryListCoordinator.swift b/JellyfinPlayer/Coordinators/LibraryListCoordinator.swift new file mode 100644 index 00000000..3bbf0728 --- /dev/null +++ b/JellyfinPlayer/Coordinators/LibraryListCoordinator.swift @@ -0,0 +1,25 @@ +// +/* + * 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 Foundation +import Stinsen +import SwiftUI + +final class LibraryListCoordinator: NavigationCoordinatable { + var navigationStack = NavigationStack() + + enum Route: NavigationRoute {} + + func resolveRoute(route: Route) -> Transition {} + + @ViewBuilder + func start() -> some View { + LibraryListView() + } +} diff --git a/JellyfinPlayer/Coordinators/MainCoordinator.swift b/JellyfinPlayer/Coordinators/MainCoordinator.swift new file mode 100644 index 00000000..af41d6de --- /dev/null +++ b/JellyfinPlayer/Coordinators/MainCoordinator.swift @@ -0,0 +1,35 @@ +// +/* + * 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 Foundation +import Stinsen +import SwiftUI + +final class MainCoordinator: ViewCoordinatable { + var children = ViewChild() + + enum Route: ViewRoute { + case mainTab + case connectToServer + } + + func resolveRoute(route: Route) -> AnyCoordinatable { + switch route { + case .mainTab: + return MainTabCoordinator().eraseToAnyCoordinatable() + case .connectToServer: + return NavigationViewCoordinator(ConnectToServerCoodinator()).eraseToAnyCoordinatable() + } + } + + @ViewBuilder + func start() -> some View { + SplashView() + } +} diff --git a/JellyfinPlayer/Coordinators/MainTabCoordinator.swift b/JellyfinPlayer/Coordinators/MainTabCoordinator.swift new file mode 100644 index 00000000..e9a825de --- /dev/null +++ b/JellyfinPlayer/Coordinators/MainTabCoordinator.swift @@ -0,0 +1,48 @@ +// +/* + * 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 Foundation +import SwiftUI + +import Stinsen + +final class MainTabCoordinator: TabCoordinatable { + lazy var children = TabChild(self, tabRoutes: [.home, .allMedia]) + + enum Route: TabRoute { + case home + case allMedia + } + + func tabItem(forTab tab: Int) -> some View { + switch tab { + case 0: + Group { + Text("Home") + Image(systemName: "house") + } + case 1: + Group { + Text("Projects") + Image(systemName: "folder") + } + default: + fatalError() + } + } + + func resolveRoute(route: Route) -> AnyCoordinatable { + switch route { + case .home: + return NavigationViewCoordinator(HomeCoordinator()).eraseToAnyCoordinatable() + case .allMedia: + return NavigationViewCoordinator(LibraryListCoordinator()).eraseToAnyCoordinatable() + } + } +} diff --git a/JellyfinPlayer/HomeView.swift b/JellyfinPlayer/HomeView.swift index 72e282ac..bc80d7a2 100644 --- a/JellyfinPlayer/HomeView.swift +++ b/JellyfinPlayer/HomeView.swift @@ -20,7 +20,7 @@ struct HomeView: View { ProgressView() } else { ScrollView { - VStack(alignment: .leading) { + LazyVStack(alignment: .leading) { if !viewModel.resumeItems.isEmpty { ContinueWatchingView(items: viewModel.resumeItems) } diff --git a/JellyfinPlayer/JellyfinPlayerApp.swift b/JellyfinPlayer/JellyfinPlayerApp.swift index fd5bcf3e..bad2fc34 100644 --- a/JellyfinPlayer/JellyfinPlayerApp.swift +++ b/JellyfinPlayer/JellyfinPlayerApp.swift @@ -5,9 +5,10 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ -import SwiftUI -import MessageUI import Defaults +import MessageUI +import Stinsen +import SwiftUI // The notification we'll send when a shake gesture happens. extension UIDevice { @@ -16,11 +17,11 @@ extension UIDevice { // Override the default behavior of shake gestures to send our notification instead. extension UIWindow { - open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) { + override open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) { if motion == .motionShake { NotificationCenter.default.post(name: UIDevice.deviceDidShakeNotification, object: nil) } - } + } } // A view modifier that detects shaking and calls a function of our choosing. @@ -39,20 +40,20 @@ struct DeviceShakeViewModifier: ViewModifier { // A View extension to make the modifier easier to use. extension View { func onShake(perform action: @escaping () -> Void) -> some View { - self.modifier(DeviceShakeViewModifier(action: action)) + modifier(DeviceShakeViewModifier(action: action)) } } extension UIDevice { var hasNotch: Bool { - let bottom = UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.safeAreaInsets.bottom ?? 0 + let bottom = UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.safeAreaInsets.bottom ?? 0 return bottom > 0 } } extension View { func withHostingWindow(_ callback: @escaping (UIWindow?) -> Void) -> some View { - self.background(HostingWindowFinder(callback: callback)) + background(HostingWindowFinder(callback: callback)) } } @@ -67,8 +68,7 @@ struct HostingWindowFinder: UIViewRepresentable { return view } - func updateUIView(_ uiView: UIView, context: Context) { - } + func updateUIView(_ uiView: UIView, context: Context) {} } struct PrefersHomeIndicatorAutoHiddenPreferenceKey: PreferenceKey { @@ -105,18 +105,17 @@ class PreferenceUIHostingController: UIHostingController { init(wrappedView: V) { let box = Box() super.init(rootView: AnyView(wrappedView - .onPreferenceChange(PrefersHomeIndicatorAutoHiddenPreferenceKey.self) { - box.value?._prefersHomeIndicatorAutoHidden = $0 - }.onPreferenceChange(SupportedOrientationsPreferenceKey.self) { - box.value?._orientations = $0 - }.onPreferenceChange(ViewPreferenceKey.self) { - box.value?._viewPreference = $0 - } - )) + .onPreferenceChange(PrefersHomeIndicatorAutoHiddenPreferenceKey.self) { + box.value?._prefersHomeIndicatorAutoHidden = $0 + }.onPreferenceChange(SupportedOrientationsPreferenceKey.self) { + box.value?._orientations = $0 + }.onPreferenceChange(ViewPreferenceKey.self) { + box.value?._viewPreference = $0 + })) box.value = self } - @objc required dynamic init?(coder aDecoder: NSCoder) { + @objc dynamic required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) super.modalPresentationStyle = .fullScreen } @@ -131,6 +130,7 @@ class PreferenceUIHostingController: UIHostingController { public var _prefersHomeIndicatorAutoHidden = false { didSet { setNeedsUpdateOfHomeIndicatorAutoHidden() } } + override var prefersHomeIndicatorAutoHidden: Bool { _prefersHomeIndicatorAutoHidden } @@ -146,6 +146,7 @@ class PreferenceUIHostingController: UIHostingController { } } } + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { _orientations } @@ -176,7 +177,7 @@ extension View { class EmailHelper: NSObject, MFMailComposeViewControllerDelegate { public static let shared = EmailHelper() - private override init() { + override private init() { // } @@ -192,7 +193,9 @@ class EmailHelper: NSObject, MFMailComposeViewControllerDelegate { let data = fileManager.contents(atPath: logURL.path) picker.setSubject("[DEV-BUG] SwiftFin") - picker.setMessageBody("Please don't edit this email.\n Please don't change the subject. \nUDID: \(UIDevice.current.identifierForVendor?.uuidString ?? "NIL")\n", isHTML: false) + picker + .setMessageBody("Please don't edit this email.\n Please don't change the subject. \nUDID: \(UIDevice.current.identifierForVendor?.uuidString ?? "NIL")\n", + isHTML: false) picker.setToRecipients(["SwiftFin Bug Reports "]) picker.addAttachmentData(data!, mimeType: "text/plain", fileName: logURL.lastPathComponent) picker.mailComposeDelegate = self @@ -218,13 +221,15 @@ struct JellyfinPlayerApp: App { var body: some Scene { WindowGroup { - SplashView() + EmptyView() .environment(\.managedObjectContext, persistenceController.container.viewContext) .onAppear(perform: { setupAppearance() }) .withHostingWindow { window in - window?.rootViewController = PreferenceUIHostingController(wrappedView: SplashView().environment(\.managedObjectContext, persistenceController.container.viewContext)) + window? + .rootViewController = PreferenceUIHostingController(wrappedView: CoordinatorView(MainCoordinator()) + .environment(\.managedObjectContext, persistenceController.container.viewContext)) } .onShake { EmailHelper.shared.sendLogs(logURL: LogManager.shared.logFileURL()) @@ -239,7 +244,6 @@ struct JellyfinPlayerApp: App { } class AppDelegate: NSObject, UIApplicationDelegate { - static var orientationLock = UIInterfaceOrientationMask.all func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { diff --git a/JellyfinPlayer/MainTabView.swift b/JellyfinPlayer/MainTabView.swift deleted file mode 100644 index fabf9b8c..00000000 --- a/JellyfinPlayer/MainTabView.swift +++ /dev/null @@ -1,46 +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 2021 Aiden Vigue & Jellyfin Contributors - */ - -import Foundation -import SwiftUI - -struct MainTabView: View { - @State private var tabSelection: Tab = .home - - var body: some View { - TabView(selection: $tabSelection) { - NavigationView { - HomeView() - } - .navigationViewStyle(StackNavigationViewStyle()) - .tabItem { - Text("Home") - Image(systemName: "house") - } - .tag(Tab.home) - NavigationView { - LibraryListView() - } - .navigationViewStyle(StackNavigationViewStyle()) - .tabItem { - Text("All Media") - Image(systemName: "folder") - } - .tag(Tab.allMedia) - } - } -} - -extension MainTabView { - - enum Tab: String { - case home - case allMedia - } -} diff --git a/JellyfinPlayer/SettingsView.swift b/JellyfinPlayer/SettingsView.swift index 5637d055..6abdb076 100644 --- a/JellyfinPlayer/SettingsView.swift +++ b/JellyfinPlayer/SettingsView.swift @@ -6,10 +6,12 @@ */ import CoreData -import SwiftUI import Defaults +import Stinsen +import SwiftUI struct SettingsView: View { + @EnvironmentObject var main: ViewRouter @Environment(\.managedObjectContext) private var viewContext @ObservedObject var viewModel: SettingsViewModel @@ -44,13 +46,13 @@ struct SettingsView: View { Text(bitrate.name).tag(bitrate.value) } } - + Picker("Jump Forward Length", selection: $jumpForwardLength) { ForEach(self.viewModel.videoPlayerJumpLengths, id: \.self) { length in Text(length.label).tag(length.rawValue) } } - + Picker("Jump Backward Length", selection: $jumpBackwardLength) { ForEach(self.viewModel.videoPlayerJumpLengths, id: \.self) { length in Text(length.label).tag(length.rawValue) @@ -63,19 +65,22 @@ struct SettingsView: View { SearchablePicker(label: "Preferred subtitle language", options: viewModel.langs, optionToString: { $0.name }, - selected: Binding( - get: { viewModel.langs.first(where: { $0.isoCode == autoSelectSubtitlesLangcode }) ?? .auto }, - set: {autoSelectSubtitlesLangcode = $0.isoCode} - ) - ) + selected: Binding(get: { + viewModel.langs + .first(where: { $0.isoCode == autoSelectSubtitlesLangcode + }) ?? + .auto + }, + set: { autoSelectSubtitlesLangcode = $0.isoCode })) SearchablePicker(label: "Preferred audio language", options: viewModel.langs, optionToString: { $0.name }, - selected: Binding( - get: { viewModel.langs.first(where: { $0.isoCode == autoSelectAudioLangcode }) ?? .auto }, - set: { autoSelectAudioLangcode = $0.isoCode} - ) - ) + selected: Binding(get: { + viewModel.langs + .first(where: { $0.isoCode == autoSelectAudioLangcode }) ?? + .auto + }, + set: { autoSelectAudioLangcode = $0.isoCode })) Picker(NSLocalizedString("Appearance", comment: ""), selection: $appAppearance) { ForEach(self.viewModel.appearances, id: \.self) { appearance in Text(appearance.localizedName).tag(appearance.rawValue) @@ -92,22 +97,16 @@ struct SettingsView: View { Spacer() Button { print("logging out") + main.route(to: .connectToServer) close = false - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - let nc = NotificationCenter.default - nc.post(name: Notification.Name("didSignOut"), object: nil) - } } label: { Text("Switch user").font(.callout) } } Button { + SessionManager.current.logout() + main.route(to: .connectToServer) close = false - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - SessionManager.current.logout() - let nc = NotificationCenter.default - nc.post(name: Notification.Name("didSignOut"), object: nil) - } } label: { Text("Sign out").font(.callout) } diff --git a/JellyfinPlayer/SplashView.swift b/JellyfinPlayer/SplashView.swift index 1b264644..d69e6dd5 100644 --- a/JellyfinPlayer/SplashView.swift +++ b/JellyfinPlayer/SplashView.swift @@ -7,19 +7,21 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ +import Stinsen import SwiftUI struct SplashView: View { + @EnvironmentObject var main: ViewRouter @StateObject var viewModel = SplashViewModel() var body: some View { - if viewModel.isLoggedIn { - MainTabView() - } else { - NavigationView { - ConnectToServerView() + ProgressView() + .onReceive(viewModel.$isLoggedIn) { flag in + if flag { + main.route(to: .mainTab) + } else { + main.route(to: .connectToServer) + } } - .navigationViewStyle(StackNavigationViewStyle()) - } } } diff --git a/JellyfinPlayer/VideoPlayer.swift b/JellyfinPlayer/VideoPlayer.swift index cb76ed87..0fe168b6 100644 --- a/JellyfinPlayer/VideoPlayer.swift +++ b/JellyfinPlayer/VideoPlayer.swift @@ -13,6 +13,7 @@ import Combine import GoogleCast import SwiftyJSON import Defaults +import Stinsen enum PlayerDestination { case remote @@ -27,6 +28,9 @@ protocol PlayerViewControllerDelegate: AnyObject { class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRemoteMediaClientListener { + @RouterObject + var main: ViewRouter? + weak var delegate: PlayerViewControllerDelegate? var cancellables = Set() @@ -508,6 +512,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe case .error(401, _, _, _): self.delegate?.exitPlayer(self) SessionManager.current.logout() + main?.route(to: .connectToServer) case .error: self.delegate?.exitPlayer(self) } diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index 33a7e893..d3cf7794 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -10,8 +10,11 @@ import Combine import Foundation import JellyfinAPI +import Stinsen final class ConnectToServerViewModel: ViewModel { + @RouterObject + var main: ViewRouter? @Published var isConnectedServer = false @@ -23,13 +26,14 @@ final class ConnectToServerViewModel: ViewModel { @Published var publicUsers = [UserDto]() @Published var selectedPublicUser = UserDto() - private let discovery: ServerDiscovery = ServerDiscovery() + private let discovery = ServerDiscovery() @Published var servers: [ServerDiscovery.ServerLookupResponse] = [] @Published var searching = false func getPublicUsers() { if ServerEnvironment.current.server != nil { - LogManager.shared.log.debug("Attempting to read public users from \(ServerEnvironment.current.server.baseURI!)", tag: "getPublicUsers") + LogManager.shared.log.debug("Attempting to read public users from \(ServerEnvironment.current.server.baseURI!)", + tag: "getPublicUsers") UserAPI.getPublicUsers() .trackActivity(loading) .sink(receiveCompletion: { completion in @@ -46,12 +50,12 @@ final class ConnectToServerViewModel: ViewModel { } func hidePublicUsers() { - self.lastPublicUsers = publicUsers + lastPublicUsers = publicUsers publicUsers = [] } func showPublicUsers() { - self.publicUsers = lastPublicUsers + publicUsers = lastPublicUsers lastPublicUsers = [] } @@ -60,7 +64,8 @@ final class ConnectToServerViewModel: ViewModel { ServerEnvironment.current.create(with: uriSubject.value) .trackActivity(loading) .sink(receiveCompletion: { completion in - self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "connectToServer", completion: completion) + self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "connectToServer", + completion: completion) }, receiveValue: { _ in LogManager.shared.log.debug("Connected to server at \"\(self.uriSubject.value)\"", tag: "connectToServer") self.getPublicUsers() @@ -70,7 +75,7 @@ final class ConnectToServerViewModel: ViewModel { func connectToServer(at url: URL) { uriSubject.send(url.absoluteString) - self.connectToServer() + connectToServer() } func discoverServers() { @@ -81,7 +86,7 @@ final class ConnectToServerViewModel: ViewModel { self.searching = false } - discovery.locateServer { [self] (server) in + discovery.locateServer { [self] server in if let server = server, !servers.contains(server) { servers.append(server) } @@ -91,13 +96,16 @@ final class ConnectToServerViewModel: ViewModel { func login() { LogManager.shared.log.debug("Attempting to login to server at \"\(uriSubject.value)\"", tag: "login") - LogManager.shared.log.debug("username == \"\": \(usernameSubject.value.isEmpty), password == \"\": \(passwordSubject.value.isEmpty)", tag: "login") + LogManager.shared.log + .debug("username == \"\": \(usernameSubject.value.isEmpty), password == \"\": \(passwordSubject.value.isEmpty)", + tag: "login") SessionManager.current.login(username: usernameSubject.value, password: passwordSubject.value) .trackActivity(loading) .sink(receiveCompletion: { completion in - self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "login", completion: completion) - }, receiveValue: { _ in - + self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "login", + completion: completion) + }, receiveValue: { [weak self] _ in + self?.main?.route(to: .mainTab) }) .store(in: &cancellables) }