diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 833ad164..a14cc354 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -9,8 +9,8 @@ /* Begin PBXBuildFile section */ 5302F82A2658791C00647A2E /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 5302F8292658791C00647A2E /* Sentry */; }; 53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; }; + 5335256E265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5335256D265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift */; }; 5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5338F74D263B61370014BF09 /* ConnectToServerView.swift */; }; - 5338F751263B62E80014BF09 /* HidingViews in Frameworks */ = {isa = PBXBuildFile; productRef = 5338F750263B62E80014BF09 /* HidingViews */; }; 5338F754263B65E10014BF09 /* SwiftyRequest in Frameworks */ = {isa = PBXBuildFile; productRef = 5338F753263B65E10014BF09 /* SwiftyRequest */; }; 5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5338F756263B7E2E0014BF09 /* KeychainSwift */; }; 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; }; @@ -34,7 +34,6 @@ 53987CA82657424A00E7EA70 /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */; }; 539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */; }; 53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A089CF264DA9DA00D57806 /* MovieItemView.swift */; }; - 53D2F74A264C69F6005792BB /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 53D2F749264C69F6005792BB /* Introspect */; }; 53D5E3DD264B47EE00BADDC8 /* MobileVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; }; 53D5E3DE264B47EE00BADDC8 /* MobileVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; }; @@ -72,6 +71,7 @@ /* Begin PBXFileReference section */ 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = ""; }; + 5335256D265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewRefactored.swift; sourceTree = ""; }; 5338F74D263B61370014BF09 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = ""; }; 535BAE9E2649E569005FA86D /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = ""; }; 535BAEA4264A151C005FA86D /* VLCPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayer.swift; sourceTree = ""; }; @@ -116,9 +116,7 @@ 53D5E3DD264B47EE00BADDC8 /* MobileVLCKit.xcframework in Frameworks */, 5338F754263B65E10014BF09 /* SwiftyRequest in Frameworks */, 5302F82A2658791C00647A2E /* Sentry in Frameworks */, - 53D2F74A264C69F6005792BB /* Introspect in Frameworks */, 5389277A263CBFE70035E14B /* SwiftyJSON in Frameworks */, - 5338F751263B62E80014BF09 /* HidingViews in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -176,6 +174,7 @@ 53987CA526572F0700E7EA70 /* SeriesItemView.swift */, 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */, 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */, + 5335256D265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift */, ); path = JellyfinPlayer; sourceTree = ""; @@ -247,12 +246,10 @@ ); name = JellyfinPlayer; packageProductDependencies = ( - 5338F750263B62E80014BF09 /* HidingViews */, 5338F753263B65E10014BF09 /* SwiftyRequest */, 5338F756263B7E2E0014BF09 /* KeychainSwift */, 53892779263CBFE70035E14B /* SwiftyJSON */, 538CD953263E3DC100BB5AF0 /* SDWebImageSwiftUI */, - 53D2F749264C69F6005792BB /* Introspect */, 5302F8292658791C00647A2E /* Sentry */, ); productName = JellyfinPlayer; @@ -286,12 +283,10 @@ ); mainGroup = 5377CBE8263B596A003A4E83; packageReferences = ( - 5338F74F263B62E80014BF09 /* XCRemoteSwiftPackageReference "HidingViews" */, 5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */, 5338F755263B7E2E0014BF09 /* XCRemoteSwiftPackageReference "keychain-swift" */, 53892778263CBFE70035E14B /* XCRemoteSwiftPackageReference "SwiftyJSON" */, 538CD952263E3DC100BB5AF0 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */, - 53D2F748264C69F6005792BB /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, 5302F8282658791C00647A2E /* XCRemoteSwiftPackageReference "sentry-cocoa" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; @@ -341,6 +336,7 @@ 53987CA82657424A00E7EA70 /* EpisodeItemView.swift in Sources */, 5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */, 53987CA626572F0700E7EA70 /* SeriesItemView.swift in Sources */, + 5335256E265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift in Sources */, 539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */, AE8C3156265D616A008AA076 /* SettingsViewModel.swift in Sources */, 5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */, @@ -557,14 +553,6 @@ minimumVersion = 7.1.0; }; }; - 5338F74F263B62E80014BF09 /* XCRemoteSwiftPackageReference "HidingViews" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/GeorgeElsham/HidingViews"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.1.1; - }; - }; 5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Kitura/SwiftyRequest"; @@ -597,14 +585,6 @@ minimumVersion = 2.0.2; }; }; - 53D2F748264C69F6005792BB /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/siteline/SwiftUI-Introspect"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.1.3; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -613,11 +593,6 @@ package = 5302F8282658791C00647A2E /* XCRemoteSwiftPackageReference "sentry-cocoa" */; productName = Sentry; }; - 5338F750263B62E80014BF09 /* HidingViews */ = { - isa = XCSwiftPackageProductDependency; - package = 5338F74F263B62E80014BF09 /* XCRemoteSwiftPackageReference "HidingViews" */; - productName = HidingViews; - }; 5338F753263B65E10014BF09 /* SwiftyRequest */ = { isa = XCSwiftPackageProductDependency; package = 5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */; @@ -638,11 +613,6 @@ package = 538CD952263E3DC100BB5AF0 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; productName = SDWebImageSwiftUI; }; - 53D2F749264C69F6005792BB /* Introspect */ = { - isa = XCSwiftPackageProductDependency; - package = 53D2F748264C69F6005792BB /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; - productName = Introspect; - }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/JellyfinPlayer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/JellyfinPlayer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 03ecf1f5..20f88694 100644 --- a/JellyfinPlayer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/JellyfinPlayer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -19,15 +19,6 @@ "version": "5.0.200" } }, - { - "package": "HidingViews", - "repositoryURL": "https://github.com/GeorgeElsham/HidingViews", - "state": { - "branch": null, - "revision": "7fde89eaeb2f0d3a07f8bf517507c6e27af8e4c3", - "version": "1.1.1" - } - }, { "package": "KeychainSwift", "repositoryURL": "https://github.com/evgenyneu/keychain-swift", @@ -109,15 +100,6 @@ "version": "2.13.1" } }, - { - "package": "Introspect", - "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect", - "state": { - "branch": null, - "revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2", - "version": "0.1.3" - } - }, { "package": "SwiftyJSON", "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON", diff --git a/JellyfinPlayer/ConnectToServerView.swift b/JellyfinPlayer/ConnectToServerView.swift index ccb669c6..692bb5be 100644 --- a/JellyfinPlayer/ConnectToServerView.swift +++ b/JellyfinPlayer/ConnectToServerView.swift @@ -6,12 +6,11 @@ // import SwiftUI -import HidingViews + import SwiftyRequest import SwiftyJSON import CoreData import KeychainSwift -import Introspect import Sentry import SDWebImageSwiftUI @@ -231,7 +230,9 @@ struct ConnectToServerView: View { HStack { Text("Connect") Spacer() - ProgressView().isHidden(!isWorking) + if(isWorking == true) { + ProgressView() + } } }.disabled(isWorking || uri.isEmpty) }.alert(isPresented: $isErrored) { @@ -252,7 +253,9 @@ struct ConnectToServerView: View { HStack { Text("Login") Spacer() - ProgressView().isHidden(!isWorking) + if(isWorking) { + ProgressView() + } } }.disabled(isWorking || username.isEmpty) .alert(isPresented: $isSignInErrored) { diff --git a/JellyfinPlayer/ContentView.swift b/JellyfinPlayer/ContentView.swift index bcaa4f56..b4625692 100644 --- a/JellyfinPlayer/ContentView.swift +++ b/JellyfinPlayer/ContentView.swift @@ -6,171 +6,13 @@ // import SwiftUI + import KeychainSwift import SwiftyRequest import SwiftyJSON -import Introspect import Sentry import SDWebImageSwiftUI -class GlobalData: ObservableObject { - @Published var user: SignedInUser? - @Published var authToken: String = "" - @Published var server: Server? - @Published var authHeader: String = "" - @Published var isInNetwork: Bool = true; -} - -extension View { - func withHostingWindow(_ callback: @escaping (UIWindow?) -> Void) -> some View { - self.background(HostingWindowFinder(callback: callback)) - } -} - -struct HostingWindowFinder: UIViewRepresentable { - var callback: (UIWindow?) -> () - - func makeUIView(context: Context) -> UIView { - let view = UIView() - DispatchQueue.main.async { [weak view] in - callback(view?.window) - } - return view - } - - func updateUIView(_ uiView: UIView, context: Context) { - } -} - -struct PrefersHomeIndicatorAutoHiddenPreferenceKey: PreferenceKey { - typealias Value = Bool - - static var defaultValue: Value = false - - static func reduce(value: inout Value, nextValue: () -> Value) { - value = nextValue() || value - } -} - -struct ViewPreferenceKey: PreferenceKey { - typealias Value = UIUserInterfaceStyle - - static var defaultValue: UIUserInterfaceStyle = .unspecified - - static func reduce(value: inout UIUserInterfaceStyle, nextValue: () -> UIUserInterfaceStyle) { - value = nextValue() - } -} - -struct SupportedOrientationsPreferenceKey: PreferenceKey { - typealias Value = UIInterfaceOrientationMask - static var defaultValue: UIInterfaceOrientationMask = .allButUpsideDown - - static func reduce(value: inout UIInterfaceOrientationMask, nextValue: () -> UIInterfaceOrientationMask) { - // use the most restrictive set from the stack - value.formIntersection(nextValue()) - } -} - -extension View { - - /// Navigate to a new view. - /// - Parameters: - /// - view: View to navigate to. - /// - binding: Only navigates when this condition is `true`. - func navigate(to view: NewView, when binding: Binding) -> some View { - NavigationView { - ZStack { - self - .navigationBarTitle("") - .navigationBarHidden(true) - - NavigationLink( - destination: view - .navigationBarTitle("") - .navigationBarHidden(true), - isActive: binding - ) { - EmptyView() - } - } - } - } -} - -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 - } - )) - box.value = self - } - - @objc required dynamic init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - super.modalPresentationStyle = .fullScreen - } - - private class Box { - weak var value: PreferenceUIHostingController? - init() {} - } - - // MARK: Prefers Home Indicator Auto Hidden - - public var _prefersHomeIndicatorAutoHidden = false { - didSet { setNeedsUpdateOfHomeIndicatorAutoHidden() } - } - override var prefersHomeIndicatorAutoHidden: Bool { - _prefersHomeIndicatorAutoHidden - } - - // MARK: Lock orientation - - public var _orientations: UIInterfaceOrientationMask = .allButUpsideDown { - didSet { - UIViewController.attemptRotationToDeviceOrientation(); - if(_orientations == .landscape) { - let value = UIInterfaceOrientation.landscapeRight.rawValue; - UIDevice.current.setValue(value, forKey: "orientation") - } - } - }; - override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - _orientations - } - - public var _viewPreference: UIUserInterfaceStyle = .unspecified { - didSet { - overrideUserInterfaceStyle = _viewPreference - } - }; -} - -extension View { - // Controls the application's preferred home indicator auto-hiding when this view is shown. - func prefersHomeIndicatorAutoHidden(_ value: Bool) -> some View { - preference(key: PrefersHomeIndicatorAutoHiddenPreferenceKey.self, value: value) - } - - func supportedOrientations(_ supportedOrientations: UIInterfaceOrientationMask) -> some View { - // When rendered, export the requested orientations upward to Root - preference(key: SupportedOrientationsPreferenceKey.self, value: supportedOrientations) - } - - func overrideViewPreference(_ viewPreference: UIUserInterfaceStyle) -> some View { - // When rendered, export the requested orientations upward to Root - preference(key: ViewPreferenceKey.self, value: viewPreference) - } -} - struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext @EnvironmentObject var orientationInfo: OrientationInfo @@ -327,38 +169,38 @@ struct ContentView: View { if(needsToSelectServer) { NavigationView() { ConnectToServerView(isActive: $needsToSelectServer) - }.environmentObject(globalData).navigationViewStyle(StackNavigationViewStyle()) + } + .navigationViewStyle(StackNavigationViewStyle()) + .environmentObject(globalData) } else if(isSignInErrored) { NavigationView() { ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored) - }.environmentObject(globalData).navigationViewStyle(StackNavigationViewStyle()) + } + .navigationViewStyle(StackNavigationViewStyle()) + .environmentObject(globalData) } else { if(!jsi.did) { LoadingView(isShowing: $isLoading) { - TabView(selection: $tabSelection) { - NavigationView() { - VStack { - if(!needsToSelectServer && !isSignInErrored) { - VStack(alignment: .leading) { - ScrollView() { - Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 15) - ContinueWatchingView() - NextUpView().padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) - ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in - VStack(alignment: .leading) { - HStack() { - Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16)) - Spacer() - NavigationLink(destination: LibraryView(prefill: library_id, names: [library_id: library_names[library_id] ?? ""], libraries: [library_id], filter: "&SortBy=DateCreated&SortOrder=Descending")) { - Text("See All").font(.subheadline).fontWeight(.bold) - } - }.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) - LatestMediaView(library: library_id) - }.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) - } - Spacer().frame(height: 7) - } + NavigationView() { + TabView(selection: $tabSelection) { + VStack(alignment: .leading) { + ScrollView() { + Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 15) + ContinueWatchingView() + NextUpView().padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) + ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in + VStack(alignment: .leading) { + HStack() { + Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16)) + Spacer() + NavigationLink(destination: LibraryView(prefill: library_id, names: [library_id: library_names[library_id] ?? ""], libraries: [library_id], filter: "&SortBy=DateCreated&SortOrder=Descending")) { + Text("See All").font(.subheadline).fontWeight(.bold) + } + }.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) + LatestMediaView(library: library_id) + }.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) } + Spacer().frame(height: 7) } } .navigationTitle("Home") @@ -370,32 +212,29 @@ struct ContentView: View { Image(systemName: "gear") } } - }.fullScreenCover( isPresented: $showSettingsPopover) { SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover).environmentObject(globalData) } + }.fullScreenCover( isPresented: $showSettingsPopover) { SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover) } + .tabItem({ + Text("Home") + Image(systemName: "house") + }) + .tag("Home") + + NavigationView() { + LibraryView(prefill: "", names: library_names, libraries: libraries) + }.navigationViewStyle(StackNavigationViewStyle()) + .tabItem({ + Text("All Media") + Image(systemName: "folder") + }) + .tag("All Media") } - .navigationViewStyle(StackNavigationViewStyle()) - .tabItem({ - Text("Home") - Image(systemName: "house") - }) - .tag("Home") - NavigationView() { - LibraryView(prefill: "", names: library_names, libraries: libraries) - .navigationTitle("Library") - } - .tabItem({ - Text("All Media") - Image(systemName: "folder") - }) - .tag("All Media") - } - }.environmentObject(globalData) + .navigationViewStyle(StackNavigationViewStyle()) + } + .environmentObject(globalData) .onAppear(perform: startup) - .navigationViewStyle(StackNavigationViewStyle()) .alert(isPresented: $isNetworkErrored) { Alert(title: Text("Network Error"), message: Text("Couldn't connect to Jellyfin"), dismissButton: .default(Text("Ok"))) - }.introspectTabBarController { (UITabBarController) in - UITabBarController.tabBar.isHidden = false } } else { Text("Signing in...") @@ -410,9 +249,3 @@ struct ContentView: View { } } } - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) - } -} diff --git a/JellyfinPlayer/ContinueWatchingView.swift b/JellyfinPlayer/ContinueWatchingView.swift index 8bce8cce..569ee357 100644 --- a/JellyfinPlayer/ContinueWatchingView.swift +++ b/JellyfinPlayer/ContinueWatchingView.swift @@ -143,7 +143,6 @@ struct ContinueWatchingView: View { .frame(width: CGFloat((item.ItemProgress/100)*320), height: 7) .padding(0), alignment: .bottomLeading ) - .shadow(radius: 5) } else { WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=550&quality=80&tag=\(item.Image)")!) .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size @@ -162,7 +161,6 @@ struct ContinueWatchingView: View { .frame(width: CGFloat((item.ItemProgress/100)*320), height: 7) .padding(0), alignment: .bottomLeading ) - .shadow(radius: 5) } Text("\(item.Type == "Episode" ? item.SeriesName ?? "" : item.Name)") .font(.callout) diff --git a/JellyfinPlayer/EpisodeItemView.swift b/JellyfinPlayer/EpisodeItemView.swift index dd2f2293..ce6a7d97 100644 --- a/JellyfinPlayer/EpisodeItemView.swift +++ b/JellyfinPlayer/EpisodeItemView.swift @@ -6,10 +6,8 @@ // import SwiftUI -import UIKit import SwiftyRequest import SwiftyJSON -import Introspect import SDWebImageSwiftUI struct EpisodeItemView: View { @@ -136,7 +134,7 @@ struct EpisodeItemView: View { let imageTag = person["PrimaryImageTag"].string ?? ""; cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? ""; cast.Role = person["Role"].string ?? ""; - cast.Image = URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(cast.Id)/Images/Primary?maxHeight=150&quality=90&tag=\(imageTag)")! + cast.Image = URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(cast.Id)/Images/Primary?maxHeight=250&quality=85&tag=\(imageTag)")! fullItem.Cast.append(cast); } } @@ -191,52 +189,237 @@ struct EpisodeItemView: View { } var body: some View { - if(playing) { - VideoPlayerView(item: fullItem, playing: $playing) - .supportedOrientations(.landscape) - .overrideViewPreference(.dark) - .prefersHomeIndicatorAutoHidden(true) - .introspectTabBarController { (UITabBarController) in - UITabBarController.tabBar.isHidden = true - } - } else { - LoadingView(isShowing: $isLoading) { - VStack(alignment:.leading) { - if(!isLoading) { - if(orientationInfo.orientation == .portrait) { - GeometryReader { geometry in - VStack() { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=450&quality=90&tag=\(fullItem.Backdrop)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!) - .resizable() - .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625) - } - - .opacity(0.3) - .aspectRatio(contentMode: .fill) - .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625) - .shadow(radius: 5) - .overlay( + LoadingView(isShowing: $isLoading) { + VStack(alignment:.leading) { + if(!isLoading) { + if(orientationInfo.orientation == .portrait) { + GeometryReader { geometry in + VStack() { + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=450&quality=90&tag=\(fullItem.Backdrop)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!) + .resizable() + .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625) + } + + .opacity(0.3) + .aspectRatio(contentMode: .fill) + .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625) + .shadow(radius: 5) + .overlay( + HStack() { + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 32, height: 32))!) + .resizable() + .frame(width: 120, height: 180) + .cornerRadius(10) + }.aspectRatio(contentMode: .fill) + .frame(width: 120, height: 180) + .cornerRadius(10) + VStack(alignment: .leading) { + Spacer() + Text(fullItem.Name).font(.headline) + .fontWeight(.semibold) + .foregroundColor(.primary) + .fixedSize(horizontal: false, vertical: true) + .offset(y: -4) + HStack() { + Text(String(fullItem.ProductionYear)).font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.secondary) + .lineLimit(1) + Text(fullItem.Runtime).font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.secondary) + .lineLimit(1) + if(fullItem.OfficialRating != "") { + Text(fullItem.OfficialRating).font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.secondary) + .lineLimit(1) + .padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4)) + .overlay( + RoundedRectangle(cornerRadius: 2) + .stroke(Color.secondary, lineWidth: 1) + ) + } + if(fullItem.CommunityRating != "0") { + HStack() { + Image(systemName: "star").foregroundColor(.secondary) + Text(fullItem.CommunityRating).font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.secondary) + .lineLimit(1) + .offset(x: -7, y: 0.7) + } + } + }.frame(maxWidth: .infinity, alignment: .leading) + }.frame(maxWidth: .infinity, alignment: .leading).offset(x: 0, y: UIDevice.current.userInterfaceIdiom == .pad ? -98 : -46).padding(.trailing, 16) + }.offset(x: 16, y: UIDevice.current.userInterfaceIdiom == .pad ? 135 : 40) + , alignment: .bottomLeading) + VStack(alignment: .leading) { + HStack() { + //Play button + NavigationLink(destination: VideoPlayerViewRefactored()) { HStack() { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 32, height: 32))!) - .resizable() - .frame(width: 120, height: 180) - .cornerRadius(10) - }.aspectRatio(contentMode: .fill) + Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold) + Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) + } + .frame(width: 120, height: 35) + .background(Color(red: 172/255, green: 92/255, blue: 195/255)) + .cornerRadius(10) + } + Spacer() + HStack() { + Button() { + favorite.toggle() + } label: { + if(!favorite) { + Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20)) + } else { + Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20)) + } + } + Button() { + watched.toggle() + } label: { + if(watched) { + Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20)) + } else { + Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20)) + } + } + } + }.padding(.leading, 16).padding(.trailing,16) + ScrollView() { + VStack(alignment: .leading) { + if(fullItem.Tagline != "") { + Text(fullItem.Tagline).font(.body).italic().padding(.top, 7).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing,16) + } + Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing,16) + if(fullItem.Genres.count != 0) { + ScrollView(.horizontal, showsIndicators: false) { + HStack() { + Text("Genres:").font(.callout).fontWeight(.semibold) + ForEach(fullItem.Genres, id: \.Id) {genre in + NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) { + Text(genre.Name).font(.footnote) + } + } + }.padding(.leading, 16).padding(.trailing,16) + } + } + if(fullItem.Cast.count != 0) { + ScrollView(.horizontal, showsIndicators: false) { + VStack() { + Spacer().frame(height: 8); + HStack() { + Spacer().frame(width: 16) + ForEach(fullItem.Cast, id: \.Id) { cast in + NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) { + VStack() { + WebImage(url: cast.Image) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 4, height: 4))!) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 70, height: 70) + .cornerRadius(10) + } + .aspectRatio(contentMode: .fill) + .frame(width: 100, height: 100) + .cornerRadius(10).shadow(radius: 6) + Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary) + if(cast.Role != "") { + Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100) + } + } + } + Spacer().frame(width: 10) + } + Spacer().frame(width: 16) + } + } + }.padding(.top, -3) + } + if(fullItem.Directors.count != 0) { + HStack() { + Text("Directors:").font(.callout).fontWeight(.semibold) + Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing,16) + } + if(fullItem.Writers.count != 0) { + HStack() { + Text("Writers:").font(.callout).fontWeight(.semibold) + Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing,16) + } + if(fullItem.Studios.count != 0) { + HStack() { + Text("Studios:").font(.callout).fontWeight(.semibold) + Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing,16) + } + Spacer().frame(height: 3) + } + } + }.padding(EdgeInsets(top: UIDevice.current.userInterfaceIdiom == .pad ? 54 : 24, leading: 0, bottom: 0, trailing: 0)) + } + } + } else { + GeometryReader { geometry in + ZStack() { + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=750&quality=90&tag=\(fullItem.Backdrop)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!) + .resizable() + .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) + } + + .opacity(0.3) + .aspectRatio(contentMode: .fill) + .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) + .edgesIgnoringSafeArea(.all) + HStack() { + VStack() { + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 32, height: 32))!) + .resizable() .frame(width: 120, height: 180) - .cornerRadius(10) + } + .aspectRatio(contentMode: .fill) + .frame(width: 120, height: 180) + .cornerRadius(10) + .shadow(radius: 5) + Spacer().frame(height: 15) + NavigationLink(destination: VideoPlayerViewRefactored()) { + HStack() { + Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold) + Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) + } + .frame(width: 120, height: 35) + .background(Color(red: 172/255, green: 92/255, blue: 195/255)) + .cornerRadius(10) + } + Spacer() + } + ScrollView() { + VStack(alignment: .leading) { + HStack() { VStack(alignment: .leading) { - Spacer() Text(fullItem.Name).font(.headline) .fontWeight(.semibold) .foregroundColor(.primary) .fixedSize(horizontal: false, vertical: true) - .offset(y: -4) + .offset(x: 14, y: 0) + Spacer().frame(height: 1) HStack() { Text(String(fullItem.ProductionYear)).font(.subheadline) .fontWeight(.medium) @@ -267,319 +450,115 @@ struct EpisodeItemView: View { .offset(x: -7, y: 0.7) } } - }.frame(maxWidth: .infinity, alignment: .leading) - }.frame(maxWidth: .infinity, alignment: .leading).offset(x: 0, y: UIDevice.current.userInterfaceIdiom == .pad ? -98 : -46).padding(.trailing, 16) - }.offset(x: 16, y: UIDevice.current.userInterfaceIdiom == .pad ? 135 : 40) - , alignment: .bottomLeading) - VStack(alignment: .leading) { - HStack() { - //Play button - Button() { - playing = true; - } label: { - HStack() { - Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold) - Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) - } - .frame(width: 120, height: 35) - .background(Color(red: 172/255, green: 92/255, blue: 195/255)) - .cornerRadius(10) - }.buttonStyle(PlainButtonStyle()) - .frame(width: 120, height: 25) - Spacer() - HStack() { - Button() { - favorite.toggle() - } label: { - if(!favorite) { - Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20)) - } else { - Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20)) - } - } - Button() { - watched.toggle() - } label: { - if(watched) { - Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20)) - } else { - Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20)) - } - } - } - }.padding(.leading, 16).padding(.trailing,16) - ScrollView() { - VStack(alignment: .leading) { - if(fullItem.Tagline != "") { - Text(fullItem.Tagline).font(.body).italic().padding(.top, 7).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing,16) - } - Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing,16) - if(fullItem.Genres.count != 0) { - ScrollView(.horizontal, showsIndicators: false) { - HStack() { - Text("Genres:").font(.callout).fontWeight(.semibold) - ForEach(fullItem.Genres, id: \.Id) {genre in - NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) { - Text(genre.Name).font(.footnote) - } - } - }.padding(.leading, 16).padding(.trailing,16) - } - } - if(fullItem.Cast.count != 0) { - ScrollView(.horizontal, showsIndicators: false) { - VStack() { - Spacer().frame(height: 8); - HStack() { - Spacer().frame(width: 16) - ForEach(fullItem.Cast, id: \.Id) { cast in - NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) { - VStack() { - WebImage(url: cast.Image) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 4, height: 4))!) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 70, height: 70) - .cornerRadius(10) - } - .aspectRatio(contentMode: .fill) - .frame(width: 100, height: 100) - .cornerRadius(10).shadow(radius: 6) - Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary) - if(cast.Role != "") { - Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100) - } - } - } - Spacer().frame(width: 10) - } - Spacer().frame(width: 16) - } - } - }.padding(.top, -3) - } - if(fullItem.Directors.count != 0) { - HStack() { - Text("Directors:").font(.callout).fontWeight(.semibold) - Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing,16) - } - if(fullItem.Writers.count != 0) { - HStack() { - Text("Writers:").font(.callout).fontWeight(.semibold) - Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing,16) - } - if(fullItem.Studios.count != 0) { - HStack() { - Text("Studios:").font(.callout).fontWeight(.semibold) - Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing,16) - } - Spacer().frame(height: 3) - } - } - }.padding(EdgeInsets(top: UIDevice.current.userInterfaceIdiom == .pad ? 54 : 24, leading: 0, bottom: 0, trailing: 0)) - } - } - } else { - GeometryReader { geometry in - ZStack() { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=750&quality=90&tag=\(fullItem.Backdrop)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!) - .resizable() - .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) - } - - .opacity(0.3) - .aspectRatio(contentMode: .fill) - .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) - .edgesIgnoringSafeArea(.all) - HStack() { - VStack() { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 32, height: 32))!) - .resizable() - .frame(width: 120, height: 180) - } - .aspectRatio(contentMode: .fill) - .frame(width: 120, height: 180) - .cornerRadius(10) - .shadow(radius: 5) - Spacer().frame(height: 15) - Button() { - playing = true; - } label: { - HStack() { - Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold) - Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) - } - .frame(width: 120, height: 35) - .background(Color(red: 172/255, green: 92/255, blue: 195/255)) - .cornerRadius(10) - }.buttonStyle(PlainButtonStyle()) - .frame(width: 120, height: 25) - Spacer() - } - ScrollView() { - VStack(alignment: .leading) { - HStack() { - VStack(alignment: .leading) { - Text(fullItem.Name).font(.headline) - .fontWeight(.semibold) - .foregroundColor(.primary) - .fixedSize(horizontal: false, vertical: true) - .offset(x: 14, y: 0) - Spacer().frame(height: 1) - HStack() { - Text(String(fullItem.ProductionYear)).font(.subheadline) - .fontWeight(.medium) - .foregroundColor(.secondary) - .lineLimit(1) - Text(fullItem.Runtime).font(.subheadline) - .fontWeight(.medium) - .foregroundColor(.secondary) - .lineLimit(1) - if(fullItem.OfficialRating != "") { - Text(fullItem.OfficialRating).font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(.secondary) - .lineLimit(1) - .padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4)) - .overlay( - RoundedRectangle(cornerRadius: 2) - .stroke(Color.secondary, lineWidth: 1) - ) - } - if(fullItem.CommunityRating != "0") { - HStack() { - Image(systemName: "star").foregroundColor(.secondary) - Text(fullItem.CommunityRating).font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(.secondary) - .lineLimit(1) - .offset(x: -7, y: 0.7) - } - } - Spacer() - }.frame(maxWidth: .infinity) - .offset(x: 14) + Spacer() }.frame(maxWidth: .infinity) - Spacer() + .offset(x: 14) + }.frame(maxWidth: .infinity) + Spacer() + HStack() { + Button() { + favorite.toggle() + } label: { + if(!favorite) { + Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20)) + } else { + Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20)) + } + } + Button() { + watched.toggle() + } label: { + if(watched) { + Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20)) + } else { + Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20)) + } + } + } + }.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + if(fullItem.Tagline != "") { + Text(fullItem.Tagline).font(.body).italic().padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + } + Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + if(fullItem.Genres.count != 0) { + ScrollView(.horizontal, showsIndicators: false) { HStack() { - Button() { - favorite.toggle() - } label: { - if(!favorite) { - Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20)) - } else { - Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20)) + Text("Genres:").font(.callout).fontWeight(.semibold) + ForEach(fullItem.Genres, id: \.Id) {genre in + NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) { + Text(genre.Name).font(.footnote) } } - Button() { - watched.toggle() - } label: { - if(watched) { - Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20)) - } else { - Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20)) - } - } - } - }.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - if(fullItem.Tagline != "") { - Text(fullItem.Tagline).font(.body).italic().padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + }.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) } - Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - if(fullItem.Genres.count != 0) { - ScrollView(.horizontal, showsIndicators: false) { + } + if(fullItem.Cast.count != 0) { + ScrollView(.horizontal, showsIndicators: false) { + VStack() { + Spacer().frame(height: 8); HStack() { - Text("Genres:").font(.callout).fontWeight(.semibold) - ForEach(fullItem.Genres, id: \.Id) {genre in - NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) { - Text(genre.Name).font(.footnote) - } - } - }.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - } - } - if(fullItem.Cast.count != 0) { - ScrollView(.horizontal, showsIndicators: false) { - VStack() { - Spacer().frame(height: 8); - HStack() { - Spacer().frame(width: 16) - ForEach(fullItem.Cast, id: \.Id) { cast in - NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) { - VStack() { - WebImage(url: cast.Image) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 32, height: 32))!) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 100, height: 100) - .cornerRadius(10) - } - .aspectRatio(contentMode: .fill) - .frame(width: 100, height: 100) - .cornerRadius(10).shadow(radius: 6) - Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary) - if(cast.Role != "") { - Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100) + Spacer().frame(width: 16) + ForEach(fullItem.Cast, id: \.Id) { cast in + NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) { + VStack() { + WebImage(url: cast.Image) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 32, height: 32))!) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 100, height: 100) + .cornerRadius(10) } + .aspectRatio(contentMode: .fill) + .frame(width: 100, height: 100) + .cornerRadius(10).shadow(radius: 6) + Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary) + if(cast.Role != "") { + Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100) } } - Spacer().frame(width: 10) } - Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + Spacer().frame(width: 10) } + Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) } - }.padding(.top, -3) - } - if(fullItem.Directors.count != 0) { - HStack() { - Text("Directors:").font(.callout).fontWeight(.semibold) - Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - } - if(fullItem.Writers.count != 0) { - HStack() { - Text("Writers:").font(.callout).fontWeight(.semibold) - Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - } - if(fullItem.Studios.count != 0) { - HStack() { - Text("Studios:").font(.callout).fontWeight(.semibold) - Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - } - Spacer().frame(height: 100); - }.frame(maxHeight: .infinity) - } - }.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55).edgesIgnoringSafeArea(.leading) - } + } + }.padding(.top, -3) + } + if(fullItem.Directors.count != 0) { + HStack() { + Text("Directors:").font(.callout).fontWeight(.semibold) + Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + } + if(fullItem.Writers.count != 0) { + HStack() { + Text("Writers:").font(.callout).fontWeight(.semibold) + Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + } + if(fullItem.Studios.count != 0) { + HStack() { + Text("Studios:").font(.callout).fontWeight(.semibold) + Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + } + Spacer().frame(height: 100); + }.frame(maxHeight: .infinity) + } + }.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55).edgesIgnoringSafeArea(.leading) } } } } - .navigationBarTitleDisplayMode(.inline) - .navigationTitle("\(fullItem.Name) - S\(String(fullItem.ParentIndexNumber ?? 0)):E\(String(fullItem.IndexNumber ?? 0)) - \(fullItem.SeriesName ?? "")") - .introspectTabBarController { (UITabBarController) in - UITabBarController.tabBar.isHidden = false - } - }.onAppear(perform: loadData) - .supportedOrientations(.allButUpsideDown) - .overrideViewPreference(.unspecified) - .preferredColorScheme(.none) - .prefersHomeIndicatorAutoHidden(false) - } + } + .navigationBarTitleDisplayMode(.inline) + .navigationTitle("\(fullItem.Name) - S\(String(fullItem.ParentIndexNumber ?? 0)):E\(String(fullItem.IndexNumber ?? 0)) - \(fullItem.SeriesName ?? "")") + }.onAppear(perform: loadData) + .supportedOrientations(.allButUpsideDown) + .overrideViewPreference(.unspecified) + .preferredColorScheme(.none) + .prefersHomeIndicatorAutoHidden(false) } } diff --git a/JellyfinPlayer/ItemView.swift b/JellyfinPlayer/ItemView.swift index c8e6a843..5fe78993 100644 --- a/JellyfinPlayer/ItemView.swift +++ b/JellyfinPlayer/ItemView.swift @@ -6,14 +6,8 @@ // import SwiftUI -import SwiftyRequest -import SwiftyJSON -import Introspect -import SDWebImageSwiftUI struct ItemView: View { - @EnvironmentObject var globalData: GlobalData - @State private var isLoading: Bool = false; var item: ResumeItem; init(item: ResumeItem) { diff --git a/JellyfinPlayer/JellyfinPlayerApp.swift b/JellyfinPlayer/JellyfinPlayerApp.swift index 3eac288c..079863ab 100644 --- a/JellyfinPlayer/JellyfinPlayerApp.swift +++ b/JellyfinPlayer/JellyfinPlayerApp.swift @@ -11,6 +11,14 @@ class justSignedIn: ObservableObject { @Published var did: Bool = false } +class GlobalData: ObservableObject { + @Published var user: SignedInUser? + @Published var authToken: String = "" + @Published var server: Server? + @Published var authHeader: String = "" + @Published var isInNetwork: Bool = true; +} + extension UIDevice { var hasNotch: Bool { let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0 @@ -49,6 +57,130 @@ class OrientationInfo: ObservableObject { } } +extension View { + func withHostingWindow(_ callback: @escaping (UIWindow?) -> Void) -> some View { + self.background(HostingWindowFinder(callback: callback)) + } +} + +struct HostingWindowFinder: UIViewRepresentable { + var callback: (UIWindow?) -> () + + func makeUIView(context: Context) -> UIView { + let view = UIView() + DispatchQueue.main.async { [weak view] in + callback(view?.window) + } + return view + } + + func updateUIView(_ uiView: UIView, context: Context) { + } +} + +struct PrefersHomeIndicatorAutoHiddenPreferenceKey: PreferenceKey { + typealias Value = Bool + + static var defaultValue: Value = false + + static func reduce(value: inout Value, nextValue: () -> Value) { + value = nextValue() || value + } +} + +struct ViewPreferenceKey: PreferenceKey { + typealias Value = UIUserInterfaceStyle + + static var defaultValue: UIUserInterfaceStyle = .unspecified + + static func reduce(value: inout UIUserInterfaceStyle, nextValue: () -> UIUserInterfaceStyle) { + value = nextValue() + } +} + +struct SupportedOrientationsPreferenceKey: PreferenceKey { + typealias Value = UIInterfaceOrientationMask + static var defaultValue: UIInterfaceOrientationMask = .allButUpsideDown + + static func reduce(value: inout UIInterfaceOrientationMask, nextValue: () -> UIInterfaceOrientationMask) { + // use the most restrictive set from the stack + value.formIntersection(nextValue()) + } +} + +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 + } + )) + box.value = self + } + + @objc required dynamic init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + super.modalPresentationStyle = .fullScreen + } + + private class Box { + weak var value: PreferenceUIHostingController? + init() {} + } + + // MARK: Prefers Home Indicator Auto Hidden + + public var _prefersHomeIndicatorAutoHidden = false { + didSet { setNeedsUpdateOfHomeIndicatorAutoHidden() } + } + override var prefersHomeIndicatorAutoHidden: Bool { + _prefersHomeIndicatorAutoHidden + } + + // MARK: Lock orientation + + public var _orientations: UIInterfaceOrientationMask = .allButUpsideDown { + didSet { + UIViewController.attemptRotationToDeviceOrientation(); + if(_orientations == .landscape) { + let value = UIInterfaceOrientation.landscapeRight.rawValue; + UIDevice.current.setValue(value, forKey: "orientation") + } + } + }; + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + _orientations + } + + public var _viewPreference: UIUserInterfaceStyle = .unspecified { + didSet { + overrideUserInterfaceStyle = _viewPreference + } + }; +} + +extension View { + // Controls the application's preferred home indicator auto-hiding when this view is shown. + func prefersHomeIndicatorAutoHidden(_ value: Bool) -> some View { + preference(key: PrefersHomeIndicatorAutoHiddenPreferenceKey.self, value: value) + } + + func supportedOrientations(_ supportedOrientations: UIInterfaceOrientationMask) -> some View { + // When rendered, export the requested orientations upward to Root + preference(key: SupportedOrientationsPreferenceKey.self, value: supportedOrientations) + } + + func overrideViewPreference(_ viewPreference: UIUserInterfaceStyle) -> some View { + // When rendered, export the requested orientations upward to Root + preference(key: ViewPreferenceKey.self, value: viewPreference) + } +} + @main struct JellyfinPlayerApp: App { let persistenceController = PersistenceController.shared diff --git a/JellyfinPlayer/LatestMediaView.swift b/JellyfinPlayer/LatestMediaView.swift index a2f11bb4..0c3d3c16 100644 --- a/JellyfinPlayer/LatestMediaView.swift +++ b/JellyfinPlayer/LatestMediaView.swift @@ -119,7 +119,7 @@ struct LatestMediaView: View { .cornerRadius(10.0) .padding(3), alignment: .topTrailing - ).shadow(radius: 6) + ) } else { Spacer().frame(height:10) WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")!) @@ -132,7 +132,6 @@ struct LatestMediaView: View { } .frame(width: 100, height: 150) .cornerRadius(10) - .shadow(radius: 6) } Text(item.Name) .font(.caption) diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index 43ebc1ad..838b081f 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -218,7 +218,6 @@ struct LibraryView: View { } .frame(width:100, height: 150) .cornerRadius(10) - .shadow(radius: 5) } else { WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) .resizable() @@ -246,7 +245,7 @@ struct LibraryView: View { .opacity(0.8) .cornerRadius(10.0) .padding(3), alignment: .topTrailing - ).shadow(radius: 5) + ) } Text(item.Name) .font(.caption) diff --git a/JellyfinPlayer/MovieItemView.swift b/JellyfinPlayer/MovieItemView.swift index 27b55bca..9835dd29 100644 --- a/JellyfinPlayer/MovieItemView.swift +++ b/JellyfinPlayer/MovieItemView.swift @@ -8,7 +8,6 @@ import SwiftUI import SwiftyRequest import SwiftyJSON -import Introspect import SDWebImageSwiftUI class DetailItem: ObservableObject { @@ -182,7 +181,7 @@ struct MovieItemView: View { let imageTag = person["PrimaryImageTag"].string ?? ""; cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? ""; cast.Role = person["Role"].string ?? ""; - cast.Image = URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(cast.Id)/Images/Primary?maxWidth=150&quality=85&tag=\(imageTag)")! + cast.Image = URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(cast.Id)/Images/Primary?maxWidth=250&quality=85&tag=\(imageTag)")! fullItem.Cast.append(cast); } } @@ -237,53 +236,236 @@ struct MovieItemView: View { } var body: some View { - if(playing) { - VideoPlayerView(item: fullItem, playing: $playing) - .supportedOrientations(.landscape) - .preferredColorScheme(.dark) - .overrideViewPreference(.dark) - .prefersHomeIndicatorAutoHidden(true) - .introspectTabBarController { (UITabBarController) in - UITabBarController.tabBar.isHidden = true - } - } else { - LoadingView(isShowing: $isLoading) { - VStack(alignment:.leading) { - if(!isLoading) { - if(orientationInfo.orientation == .portrait) { - GeometryReader { geometry in - VStack() { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=450&quality=90&tag=\(fullItem.Backdrop)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!) - .resizable() - .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625) - } - - .opacity(0.3) - .aspectRatio(contentMode: .fill) - .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625) - .shadow(radius: 5) - .overlay( + LoadingView(isShowing: $isLoading) { + VStack(alignment:.leading) { + if(!isLoading) { + if(orientationInfo.orientation == .portrait) { + GeometryReader { geometry in + VStack() { + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=450&quality=90&tag=\(fullItem.Backdrop)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!) + .resizable() + .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625) + } + + .opacity(0.3) + .aspectRatio(contentMode: .fill) + .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625) + .shadow(radius: 5) + .overlay( + HStack() { + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 32, height: 32))!) + .resizable() + .frame(width: 120, height: 180) + .cornerRadius(10) + }.aspectRatio(contentMode: .fill) + .frame(width: 120, height: 180) + .cornerRadius(10) + VStack(alignment: .leading) { + Spacer() + Text(fullItem.Name).font(.headline) + .fontWeight(.semibold) + .foregroundColor(.primary) + .fixedSize(horizontal: false, vertical: true) + .offset(y: -4) + HStack() { + Text(String(fullItem.ProductionYear)).font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.secondary) + .lineLimit(1) + Text(fullItem.Runtime).font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.secondary) + .lineLimit(1) + if(fullItem.OfficialRating != "") { + Text(fullItem.OfficialRating).font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.secondary) + .lineLimit(1) + .padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4)) + .overlay( + RoundedRectangle(cornerRadius: 2) + .stroke(Color.secondary, lineWidth: 1) + ) + } + if(fullItem.CommunityRating != "0") { + HStack() { + Image(systemName: "star").foregroundColor(.secondary) + Text(fullItem.CommunityRating).font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.secondary) + .lineLimit(1) + .offset(x: -7, y: 0.7) + } + } + }.frame(maxWidth: .infinity, alignment: .leading) + }.offset(x: 0, y: UIDevice.current.userInterfaceIdiom == .pad ? -98 : -46).padding(.trailing, 16) + }.offset(x: 16, y: UIDevice.current.userInterfaceIdiom == .pad ? 135 : 40) + , alignment: .bottomLeading) + VStack(alignment: .leading) { + HStack() { + //Play button + NavigationLink(destination: VideoPlayerViewRefactored()) { HStack() { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 32, height: 32))!) - .resizable() - .frame(width: 120, height: 180) - .cornerRadius(10) - }.aspectRatio(contentMode: .fill) + Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold) + Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) + } + .frame(width: 120, height: 35) + .background(Color(red: 172/255, green: 92/255, blue: 195/255)) + .cornerRadius(10) + } + Spacer() + HStack() { + Button() { + favorite.toggle() + } label: { + if(!favorite) { + Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20)) + } else { + Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20)) + } + } + Button() { + watched.toggle() + } label: { + if(watched) { + Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20)) + } else { + Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20)) + } + } + } + }.padding(.leading, 16).padding(.trailing,16) + ScrollView() { + VStack(alignment: .leading) { + if(fullItem.Tagline != "") { + Text(fullItem.Tagline).font(.body).italic().padding(.top, 7).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing,16) + } + Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing,16) + if(fullItem.Genres.count != 0) { + ScrollView(.horizontal, showsIndicators: false) { + HStack() { + Text("Genres:").font(.callout).fontWeight(.semibold) + ForEach(fullItem.Genres, id: \.Id) {genre in + NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) { + Text(genre.Name).font(.footnote) + } + } + }.padding(.leading, 16).padding(.trailing,16) + } + } + if(fullItem.Cast.count != 0) { + ScrollView(.horizontal, showsIndicators: false) { + VStack() { + Spacer().frame(height: 8); + HStack() { + Spacer().frame(width: 16) + ForEach(fullItem.Cast, id: \.Id) { cast in + NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) { + VStack() { + WebImage(url: cast.Image) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 16, height: 16))!) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 100, height: 100) + .cornerRadius(10) + } + .aspectRatio(contentMode: .fill) + .frame(width: 100, height: 100) + .cornerRadius(10).shadow(radius: 6) + Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary) + if(cast.Role != "") { + Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100) + } + } + } + Spacer().frame(width: 10) + } + Spacer().frame(width: 16) + } + } + }.padding(.top, -3) + } + if(fullItem.Directors.count != 0) { + HStack() { + Text("Directors:").font(.callout).fontWeight(.semibold) + Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing,16) + } + if(fullItem.Writers.count != 0) { + HStack() { + Text("Writers:").font(.callout).fontWeight(.semibold) + Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing,16) + } + if(fullItem.Studios.count != 0) { + HStack() { + Text("Studios:").font(.callout).fontWeight(.semibold) + Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing,16) + } + Spacer().frame(height: 3) + } + } + }.padding(EdgeInsets(top: UIDevice.current.userInterfaceIdiom == .pad ? 54 : 24, leading: 0, bottom: 0, trailing: 0)) + } + } + } else { + GeometryReader { geometry in + ZStack() { + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=750&quality=90&tag=\(fullItem.Backdrop)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 16, height: 16))!) + .resizable() + .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) + } + + .opacity(0.3) + .aspectRatio(contentMode: .fill) + .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) + .edgesIgnoringSafeArea(.all) + HStack() { + VStack() { + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 16, height: 16))!) + .resizable() .frame(width: 120, height: 180) - .cornerRadius(10) + } + .frame(width: 120, height: 180) + .cornerRadius(10) + .shadow(radius: 5) + Spacer().frame(height: 15) + NavigationLink(destination: VideoPlayerViewRefactored()) { + HStack() { + Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold) + Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) + } + .frame(width: 120, height: 35) + .background(Color(red: 172/255, green: 92/255, blue: 195/255)) + .cornerRadius(10) + } + Spacer() + } + ScrollView() { + VStack(alignment: .leading) { + HStack() { VStack(alignment: .leading) { - Spacer() Text(fullItem.Name).font(.headline) .fontWeight(.semibold) .foregroundColor(.primary) .fixedSize(horizontal: false, vertical: true) - .offset(y: -4) + .offset(x: 14, y: 0) + Spacer().frame(height: 1) HStack() { Text(String(fullItem.ProductionYear)).font(.subheadline) .fontWeight(.medium) @@ -314,318 +496,115 @@ struct MovieItemView: View { .offset(x: -7, y: 0.7) } } + Spacer() }.frame(maxWidth: .infinity, alignment: .leading) - }.offset(x: 0, y: UIDevice.current.userInterfaceIdiom == .pad ? -98 : -46).padding(.trailing, 16) - }.offset(x: 16, y: UIDevice.current.userInterfaceIdiom == .pad ? 135 : 40) - , alignment: .bottomLeading) - VStack(alignment: .leading) { - HStack() { - //Play button - Button() { - playing = true; - } label: { + .offset(x: 14) + }.frame(maxWidth: .infinity, alignment: .leading) + Spacer() HStack() { - Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold) - Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) - } - .frame(width: 120, height: 35) - .background(Color(red: 172/255, green: 92/255, blue: 195/255)) - .cornerRadius(10) - }.buttonStyle(PlainButtonStyle()) - .frame(width: 120, height: 25) - Spacer() - HStack() { - Button() { - favorite.toggle() - } label: { - if(!favorite) { - Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20)) - } else { - Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20)) + Button() { + favorite.toggle() + } label: { + if(!favorite) { + Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20)) + } else { + Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20)) + } + } + Button() { + watched.toggle() + } label: { + if(watched) { + Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20)) + } else { + Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20)) + } } } - Button() { - watched.toggle() - } label: { - if(watched) { - Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20)) - } else { - Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20)) - } + }.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + if(fullItem.Tagline != "") { + Text(fullItem.Tagline).font(.body).italic().padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + } + Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + if(fullItem.Genres.count != 0) { + ScrollView(.horizontal, showsIndicators: false) { + HStack() { + Text("Genres:").font(.callout).fontWeight(.semibold) + ForEach(fullItem.Genres, id: \.Id) {genre in + NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) { + Text(genre.Name).font(.footnote) + } + } + }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) } } - }.padding(.leading, 16).padding(.trailing,16) - ScrollView() { - VStack(alignment: .leading) { - if(fullItem.Tagline != "") { - Text(fullItem.Tagline).font(.body).italic().padding(.top, 7).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing,16) - } - Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing,16) - if(fullItem.Genres.count != 0) { - ScrollView(.horizontal, showsIndicators: false) { + if(fullItem.Cast.count != 0) { + ScrollView(.horizontal, showsIndicators: false) { + VStack() { + Spacer().frame(height: 8); HStack() { - Text("Genres:").font(.callout).fontWeight(.semibold) - ForEach(fullItem.Genres, id: \.Id) {genre in - NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) { - Text(genre.Name).font(.footnote) - } - } - }.padding(.leading, 16).padding(.trailing,16) - } - } - if(fullItem.Cast.count != 0) { - ScrollView(.horizontal, showsIndicators: false) { - VStack() { - Spacer().frame(height: 8); - HStack() { - Spacer().frame(width: 16) - ForEach(fullItem.Cast, id: \.Id) { cast in - NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) { - VStack() { - WebImage(url: cast.Image) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 16, height: 16))!) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 100, height: 100) - .cornerRadius(10) - } - .aspectRatio(contentMode: .fill) - .frame(width: 100, height: 100) - .cornerRadius(10).shadow(radius: 6) - Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary) - if(cast.Role != "") { - Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100) + Spacer().frame(width: 16) + ForEach(fullItem.Cast, id: \.Id) { cast in + NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) { + VStack() { + WebImage(url: cast.Image) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .placeholder { + Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 16, height: 16))!) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 100, height: 100) + .cornerRadius(10) } + .aspectRatio(contentMode: .fill) + .frame(width: 100, height: 100) + .cornerRadius(10).shadow(radius: 6) + Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary) + if(cast.Role != "") { + Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100) } } - Spacer().frame(width: 10) } - Spacer().frame(width: 16) + Spacer().frame(width: 10) } + Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) } - }.padding(.top, -3) - } - if(fullItem.Directors.count != 0) { - HStack() { - Text("Directors:").font(.callout).fontWeight(.semibold) - Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing,16) - } - if(fullItem.Writers.count != 0) { - HStack() { - Text("Writers:").font(.callout).fontWeight(.semibold) - Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing,16) - } - if(fullItem.Studios.count != 0) { - HStack() { - Text("Studios:").font(.callout).fontWeight(.semibold) - Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing,16) - } - Spacer().frame(height: 3) + } + }.padding(.top, -3).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? -55 : 0) } - } - }.padding(EdgeInsets(top: UIDevice.current.userInterfaceIdiom == .pad ? 54 : 24, leading: 0, bottom: 0, trailing: 0)) - } - } - } else { - GeometryReader { geometry in - ZStack() { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=750&quality=90&tag=\(fullItem.Backdrop)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 16, height: 16))!) - .resizable() - .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) - } - - .opacity(0.3) - .aspectRatio(contentMode: .fill) - .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) - .edgesIgnoringSafeArea(.all) - HStack() { - VStack() { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 16, height: 16))!) - .resizable() - .frame(width: 120, height: 180) - } - .frame(width: 120, height: 180) - .cornerRadius(10) - .shadow(radius: 5) - Spacer().frame(height: 15) - Button() { - playing = true; - } label: { + if(fullItem.Directors.count != 0) { HStack() { - Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold) - Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) - } - .frame(width: 120, height: 35) - .background(Color(red: 172/255, green: 92/255, blue: 195/255)) - .cornerRadius(10) - }.buttonStyle(PlainButtonStyle()) - .frame(width: 120, height: 25) - Spacer() - } - ScrollView() { - VStack(alignment: .leading) { + Text("Directors:").font(.callout).fontWeight(.semibold) + Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + } + if(fullItem.Writers.count != 0) { HStack() { - VStack(alignment: .leading) { - Text(fullItem.Name).font(.headline) - .fontWeight(.semibold) - .foregroundColor(.primary) - .fixedSize(horizontal: false, vertical: true) - .offset(x: 14, y: 0) - Spacer().frame(height: 1) - HStack() { - Text(String(fullItem.ProductionYear)).font(.subheadline) - .fontWeight(.medium) - .foregroundColor(.secondary) - .lineLimit(1) - Text(fullItem.Runtime).font(.subheadline) - .fontWeight(.medium) - .foregroundColor(.secondary) - .lineLimit(1) - if(fullItem.OfficialRating != "") { - Text(fullItem.OfficialRating).font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(.secondary) - .lineLimit(1) - .padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4)) - .overlay( - RoundedRectangle(cornerRadius: 2) - .stroke(Color.secondary, lineWidth: 1) - ) - } - if(fullItem.CommunityRating != "0") { - HStack() { - Image(systemName: "star").foregroundColor(.secondary) - Text(fullItem.CommunityRating).font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(.secondary) - .lineLimit(1) - .offset(x: -7, y: 0.7) - } - } - Spacer() - }.frame(maxWidth: .infinity, alignment: .leading) - .offset(x: 14) - }.frame(maxWidth: .infinity, alignment: .leading) - Spacer() - HStack() { - Button() { - favorite.toggle() - } label: { - if(!favorite) { - Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20)) - } else { - Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20)) - } - } - Button() { - watched.toggle() - } label: { - if(watched) { - Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20)) - } else { - Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20)) - } - } - } - }.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - if(fullItem.Tagline != "") { - Text(fullItem.Tagline).font(.body).italic().padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - } - Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - if(fullItem.Genres.count != 0) { - ScrollView(.horizontal, showsIndicators: false) { - HStack() { - Text("Genres:").font(.callout).fontWeight(.semibold) - ForEach(fullItem.Genres, id: \.Id) {genre in - NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) { - Text(genre.Name).font(.footnote) - } - } - }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - } - } - if(fullItem.Cast.count != 0) { - ScrollView(.horizontal, showsIndicators: false) { - VStack() { - Spacer().frame(height: 8); - HStack() { - Spacer().frame(width: 16) - ForEach(fullItem.Cast, id: \.Id) { cast in - NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) { - VStack() { - WebImage(url: cast.Image) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 16, height: 16))!) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 100, height: 100) - .cornerRadius(10) - } - .aspectRatio(contentMode: .fill) - .frame(width: 100, height: 100) - .cornerRadius(10).shadow(radius: 6) - Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary) - if(cast.Role != "") { - Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100) - } - } - } - Spacer().frame(width: 10) - } - Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - } - } - }.padding(.top, -3).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? -55 : 0) - } - if(fullItem.Directors.count != 0) { - HStack() { - Text("Directors:").font(.callout).fontWeight(.semibold) - Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - } - if(fullItem.Writers.count != 0) { - HStack() { - Text("Writers:").font(.callout).fontWeight(.semibold) - Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - } - if(fullItem.Studios.count != 0) { - HStack() { - Text("Studios:").font(.callout).fontWeight(.semibold) - Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - } - Spacer().frame(height: 195); - }.frame(maxHeight: .infinity) - } - }.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55).edgesIgnoringSafeArea(.leading) - } + Text("Writers:").font(.callout).fontWeight(.semibold) + Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + } + if(fullItem.Studios.count != 0) { + HStack() { + Text("Studios:").font(.callout).fontWeight(.semibold) + Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + } + Spacer().frame(height: 195); + }.frame(maxHeight: .infinity) + } + }.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55).edgesIgnoringSafeArea(.leading) } } } } - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(fullItem.Name) - .introspectTabBarController { (UITabBarController) in - UITabBarController.tabBar.isHidden = false - } - }.onAppear(perform: loadData) - .supportedOrientations(.allButUpsideDown) - .overrideViewPreference(.unspecified) - .preferredColorScheme(.none) - .prefersHomeIndicatorAutoHidden(false) - } + } + .navigationBarTitleDisplayMode(.inline) + .navigationTitle(fullItem.Name) + }.onAppear(perform: loadData) + .supportedOrientations(.allButUpsideDown) + .overrideViewPreference(.unspecified) + .preferredColorScheme(.none) + .prefersHomeIndicatorAutoHidden(false) } } diff --git a/JellyfinPlayer/NextUpView.swift b/JellyfinPlayer/NextUpView.swift index f228a223..e32242c6 100644 --- a/JellyfinPlayer/NextUpView.swift +++ b/JellyfinPlayer/NextUpView.swift @@ -89,7 +89,6 @@ struct NextUpView: View { } .frame(width: 100, height: 150) .cornerRadius(10) - .shadow(radius: 6) Text(item.SeriesName ?? "") .font(.caption) .fontWeight(.semibold) diff --git a/JellyfinPlayer/SeasonItemView.swift b/JellyfinPlayer/SeasonItemView.swift index c76e4776..59dbeaa7 100644 --- a/JellyfinPlayer/SeasonItemView.swift +++ b/JellyfinPlayer/SeasonItemView.swift @@ -8,7 +8,6 @@ import SwiftUI import SwiftyRequest import SwiftyJSON -import Introspect import SDWebImageSwiftUI struct SeasonItemView: View { diff --git a/JellyfinPlayer/VideoPlayerView.swift b/JellyfinPlayer/VideoPlayerView.swift index b1acf80f..a58f990b 100644 --- a/JellyfinPlayer/VideoPlayerView.swift +++ b/JellyfinPlayer/VideoPlayerView.swift @@ -392,9 +392,6 @@ struct VideoPlayerView: View { vlcplayer.stop() }).padding(EdgeInsets(top: 0, leading: UIDevice.current.hasNotch ? 30 : 0, bottom: 0, trailing: UIDevice.current.hasNotch ? 30 : 0)) } - .introspectTabBarController { (UITabBarController) in - UITabBarController.tabBar.isHidden = true - } .overlay( VStack() { HStack() { @@ -489,7 +486,6 @@ struct VideoPlayerView: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(.black).opacity(0.4)) - .isHidden(inactivity) , alignment: .topLeading) .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) .onAppear(perform: startStream) diff --git a/JellyfinPlayer/VideoPlayerViewRefactored.swift b/JellyfinPlayer/VideoPlayerViewRefactored.swift new file mode 100644 index 00000000..ba88f1fe --- /dev/null +++ b/JellyfinPlayer/VideoPlayerViewRefactored.swift @@ -0,0 +1,27 @@ +// +// VideoPlayerViewRefactored.swift +// JellyfinPlayer +// +// Created by Aiden Vigue on 5/26/21. +// + +import SwiftUI +import MobileVLCKit +import Introspect + +struct VideoPlayerViewRefactored: View { + @State private var shouldShowLoadingView: Bool = true; + + var body: some View { + LoadingView(isShowing: $shouldShowLoadingView) { + Text("content") + } + .navigationBarHidden(true) + .navigationBarBackButtonHidden(true) + .statusBar(hidden: true) + .prefersHomeIndicatorAutoHidden(true) + .preferredColorScheme(.dark) + .edgesIgnoringSafeArea(.all) + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) + } +}