Start flattening hierarchy

This commit is contained in:
Aiden Vigue 2021-05-26 11:00:22 -04:00
parent 3cefdb2ad4
commit eb895a0805
No known key found for this signature in database
GPG Key ID: E7570472648F4544
15 changed files with 849 additions and 960 deletions

View File

@ -9,8 +9,8 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
5302F82A2658791C00647A2E /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 5302F8292658791C00647A2E /* Sentry */; }; 5302F82A2658791C00647A2E /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 5302F8292658791C00647A2E /* Sentry */; };
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; }; 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 */; }; 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 */; }; 5338F754263B65E10014BF09 /* SwiftyRequest in Frameworks */ = {isa = PBXBuildFile; productRef = 5338F753263B65E10014BF09 /* SwiftyRequest */; };
5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5338F756263B7E2E0014BF09 /* KeychainSwift */; }; 5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5338F756263B7E2E0014BF09 /* KeychainSwift */; };
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; }; 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 */; }; 53987CA82657424A00E7EA70 /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */; };
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */; }; 539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */; };
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A089CF264DA9DA00D57806 /* MovieItemView.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 */; }; 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, ); }; }; 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 */; }; 53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; };
@ -72,6 +71,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = "<group>"; }; 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = "<group>"; };
5335256D265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewRefactored.swift; sourceTree = "<group>"; };
5338F74D263B61370014BF09 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; }; 5338F74D263B61370014BF09 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; };
535BAE9E2649E569005FA86D /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; }; 535BAE9E2649E569005FA86D /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
535BAEA4264A151C005FA86D /* VLCPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayer.swift; sourceTree = "<group>"; }; 535BAEA4264A151C005FA86D /* VLCPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayer.swift; sourceTree = "<group>"; };
@ -116,9 +116,7 @@
53D5E3DD264B47EE00BADDC8 /* MobileVLCKit.xcframework in Frameworks */, 53D5E3DD264B47EE00BADDC8 /* MobileVLCKit.xcframework in Frameworks */,
5338F754263B65E10014BF09 /* SwiftyRequest in Frameworks */, 5338F754263B65E10014BF09 /* SwiftyRequest in Frameworks */,
5302F82A2658791C00647A2E /* Sentry in Frameworks */, 5302F82A2658791C00647A2E /* Sentry in Frameworks */,
53D2F74A264C69F6005792BB /* Introspect in Frameworks */,
5389277A263CBFE70035E14B /* SwiftyJSON in Frameworks */, 5389277A263CBFE70035E14B /* SwiftyJSON in Frameworks */,
5338F751263B62E80014BF09 /* HidingViews in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -176,6 +174,7 @@
53987CA526572F0700E7EA70 /* SeriesItemView.swift */, 53987CA526572F0700E7EA70 /* SeriesItemView.swift */,
53987CA72657424A00E7EA70 /* EpisodeItemView.swift */, 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */,
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */, 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */,
5335256D265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift */,
); );
path = JellyfinPlayer; path = JellyfinPlayer;
sourceTree = "<group>"; sourceTree = "<group>";
@ -247,12 +246,10 @@
); );
name = JellyfinPlayer; name = JellyfinPlayer;
packageProductDependencies = ( packageProductDependencies = (
5338F750263B62E80014BF09 /* HidingViews */,
5338F753263B65E10014BF09 /* SwiftyRequest */, 5338F753263B65E10014BF09 /* SwiftyRequest */,
5338F756263B7E2E0014BF09 /* KeychainSwift */, 5338F756263B7E2E0014BF09 /* KeychainSwift */,
53892779263CBFE70035E14B /* SwiftyJSON */, 53892779263CBFE70035E14B /* SwiftyJSON */,
538CD953263E3DC100BB5AF0 /* SDWebImageSwiftUI */, 538CD953263E3DC100BB5AF0 /* SDWebImageSwiftUI */,
53D2F749264C69F6005792BB /* Introspect */,
5302F8292658791C00647A2E /* Sentry */, 5302F8292658791C00647A2E /* Sentry */,
); );
productName = JellyfinPlayer; productName = JellyfinPlayer;
@ -286,12 +283,10 @@
); );
mainGroup = 5377CBE8263B596A003A4E83; mainGroup = 5377CBE8263B596A003A4E83;
packageReferences = ( packageReferences = (
5338F74F263B62E80014BF09 /* XCRemoteSwiftPackageReference "HidingViews" */,
5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */, 5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */,
5338F755263B7E2E0014BF09 /* XCRemoteSwiftPackageReference "keychain-swift" */, 5338F755263B7E2E0014BF09 /* XCRemoteSwiftPackageReference "keychain-swift" */,
53892778263CBFE70035E14B /* XCRemoteSwiftPackageReference "SwiftyJSON" */, 53892778263CBFE70035E14B /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
538CD952263E3DC100BB5AF0 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */, 538CD952263E3DC100BB5AF0 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */,
53D2F748264C69F6005792BB /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
5302F8282658791C00647A2E /* XCRemoteSwiftPackageReference "sentry-cocoa" */, 5302F8282658791C00647A2E /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
); );
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
@ -341,6 +336,7 @@
53987CA82657424A00E7EA70 /* EpisodeItemView.swift in Sources */, 53987CA82657424A00E7EA70 /* EpisodeItemView.swift in Sources */,
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */, 5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
53987CA626572F0700E7EA70 /* SeriesItemView.swift in Sources */, 53987CA626572F0700E7EA70 /* SeriesItemView.swift in Sources */,
5335256E265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift in Sources */,
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */, 539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
AE8C3156265D616A008AA076 /* SettingsViewModel.swift in Sources */, AE8C3156265D616A008AA076 /* SettingsViewModel.swift in Sources */,
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */, 5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
@ -557,14 +553,6 @@
minimumVersion = 7.1.0; 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" */ = { 5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Kitura/SwiftyRequest"; repositoryURL = "https://github.com/Kitura/SwiftyRequest";
@ -597,14 +585,6 @@
minimumVersion = 2.0.2; 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 */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@ -613,11 +593,6 @@
package = 5302F8282658791C00647A2E /* XCRemoteSwiftPackageReference "sentry-cocoa" */; package = 5302F8282658791C00647A2E /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
productName = Sentry; productName = Sentry;
}; };
5338F750263B62E80014BF09 /* HidingViews */ = {
isa = XCSwiftPackageProductDependency;
package = 5338F74F263B62E80014BF09 /* XCRemoteSwiftPackageReference "HidingViews" */;
productName = HidingViews;
};
5338F753263B65E10014BF09 /* SwiftyRequest */ = { 5338F753263B65E10014BF09 /* SwiftyRequest */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */; package = 5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */;
@ -638,11 +613,6 @@
package = 538CD952263E3DC100BB5AF0 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; package = 538CD952263E3DC100BB5AF0 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */;
productName = SDWebImageSwiftUI; productName = SDWebImageSwiftUI;
}; };
53D2F749264C69F6005792BB /* Introspect */ = {
isa = XCSwiftPackageProductDependency;
package = 53D2F748264C69F6005792BB /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
productName = Introspect;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */ /* Begin XCVersionGroup section */

View File

@ -19,15 +19,6 @@
"version": "5.0.200" "version": "5.0.200"
} }
}, },
{
"package": "HidingViews",
"repositoryURL": "https://github.com/GeorgeElsham/HidingViews",
"state": {
"branch": null,
"revision": "7fde89eaeb2f0d3a07f8bf517507c6e27af8e4c3",
"version": "1.1.1"
}
},
{ {
"package": "KeychainSwift", "package": "KeychainSwift",
"repositoryURL": "https://github.com/evgenyneu/keychain-swift", "repositoryURL": "https://github.com/evgenyneu/keychain-swift",
@ -109,15 +100,6 @@
"version": "2.13.1" "version": "2.13.1"
} }
}, },
{
"package": "Introspect",
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect",
"state": {
"branch": null,
"revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2",
"version": "0.1.3"
}
},
{ {
"package": "SwiftyJSON", "package": "SwiftyJSON",
"repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON", "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON",

View File

@ -6,12 +6,11 @@
// //
import SwiftUI import SwiftUI
import HidingViews
import SwiftyRequest import SwiftyRequest
import SwiftyJSON import SwiftyJSON
import CoreData import CoreData
import KeychainSwift import KeychainSwift
import Introspect
import Sentry import Sentry
import SDWebImageSwiftUI import SDWebImageSwiftUI
@ -231,7 +230,9 @@ struct ConnectToServerView: View {
HStack { HStack {
Text("Connect") Text("Connect")
Spacer() Spacer()
ProgressView().isHidden(!isWorking) if(isWorking == true) {
ProgressView()
}
} }
}.disabled(isWorking || uri.isEmpty) }.disabled(isWorking || uri.isEmpty)
}.alert(isPresented: $isErrored) { }.alert(isPresented: $isErrored) {
@ -252,7 +253,9 @@ struct ConnectToServerView: View {
HStack { HStack {
Text("Login") Text("Login")
Spacer() Spacer()
ProgressView().isHidden(!isWorking) if(isWorking) {
ProgressView()
}
} }
}.disabled(isWorking || username.isEmpty) }.disabled(isWorking || username.isEmpty)
.alert(isPresented: $isSignInErrored) { .alert(isPresented: $isSignInErrored) {

View File

@ -6,171 +6,13 @@
// //
import SwiftUI import SwiftUI
import KeychainSwift import KeychainSwift
import SwiftyRequest import SwiftyRequest
import SwiftyJSON import SwiftyJSON
import Introspect
import Sentry import Sentry
import SDWebImageSwiftUI 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<NewView: View>(to view: NewView, when binding: Binding<Bool>) -> some View {
NavigationView {
ZStack {
self
.navigationBarTitle("")
.navigationBarHidden(true)
NavigationLink(
destination: view
.navigationBarTitle("")
.navigationBarHidden(true),
isActive: binding
) {
EmptyView()
}
}
}
}
}
class PreferenceUIHostingController: UIHostingController<AnyView> {
init<V: View>(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 { struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext @Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var orientationInfo: OrientationInfo @EnvironmentObject var orientationInfo: OrientationInfo
@ -327,38 +169,38 @@ struct ContentView: View {
if(needsToSelectServer) { if(needsToSelectServer) {
NavigationView() { NavigationView() {
ConnectToServerView(isActive: $needsToSelectServer) ConnectToServerView(isActive: $needsToSelectServer)
}.environmentObject(globalData).navigationViewStyle(StackNavigationViewStyle()) }
.navigationViewStyle(StackNavigationViewStyle())
.environmentObject(globalData)
} else if(isSignInErrored) { } else if(isSignInErrored) {
NavigationView() { NavigationView() {
ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored) 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 { } else {
if(!jsi.did) { if(!jsi.did) {
LoadingView(isShowing: $isLoading) { LoadingView(isShowing: $isLoading) {
TabView(selection: $tabSelection) { NavigationView() {
NavigationView() { TabView(selection: $tabSelection) {
VStack { VStack(alignment: .leading) {
if(!needsToSelectServer && !isSignInErrored) { ScrollView() {
VStack(alignment: .leading) { Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 15)
ScrollView() { ContinueWatchingView()
Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 15) NextUpView().padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
ContinueWatchingView() ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in
NextUpView().padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) VStack(alignment: .leading) {
ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in HStack() {
VStack(alignment: .leading) { Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
HStack() { Spacer()
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16)) NavigationLink(destination: LibraryView(prefill: library_id, names: [library_id: library_names[library_id] ?? ""], libraries: [library_id], filter: "&SortBy=DateCreated&SortOrder=Descending")) {
Spacer() Text("See All").font(.subheadline).fontWeight(.bold)
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: 0, leading: 16, bottom: 0, trailing: 16)) }.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
LatestMediaView(library: library_id)
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
}
Spacer().frame(height: 7)
}
} }
Spacer().frame(height: 7)
} }
} }
.navigationTitle("Home") .navigationTitle("Home")
@ -370,32 +212,29 @@ struct ContentView: View {
Image(systemName: "gear") 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) .onAppear(perform: startup)
.navigationViewStyle(StackNavigationViewStyle())
.alert(isPresented: $isNetworkErrored) { .alert(isPresented: $isNetworkErrored) {
Alert(title: Text("Network Error"), message: Text("Couldn't connect to Jellyfin"), dismissButton: .default(Text("Ok"))) Alert(title: Text("Network Error"), message: Text("Couldn't connect to Jellyfin"), dismissButton: .default(Text("Ok")))
}.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = false
} }
} else { } else {
Text("Signing in...") 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)
}
}

View File

@ -143,7 +143,6 @@ struct ContinueWatchingView: View {
.frame(width: CGFloat((item.ItemProgress/100)*320), height: 7) .frame(width: CGFloat((item.ItemProgress/100)*320), height: 7)
.padding(0), alignment: .bottomLeading .padding(0), alignment: .bottomLeading
) )
.shadow(radius: 5)
} else { } else {
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=550&quality=80&tag=\(item.Image)")!) 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 .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) .frame(width: CGFloat((item.ItemProgress/100)*320), height: 7)
.padding(0), alignment: .bottomLeading .padding(0), alignment: .bottomLeading
) )
.shadow(radius: 5)
} }
Text("\(item.Type == "Episode" ? item.SeriesName ?? "" : item.Name)") Text("\(item.Type == "Episode" ? item.SeriesName ?? "" : item.Name)")
.font(.callout) .font(.callout)

View File

@ -6,10 +6,8 @@
// //
import SwiftUI import SwiftUI
import UIKit
import SwiftyRequest import SwiftyRequest
import SwiftyJSON import SwiftyJSON
import Introspect
import SDWebImageSwiftUI import SDWebImageSwiftUI
struct EpisodeItemView: View { struct EpisodeItemView: View {
@ -136,7 +134,7 @@ struct EpisodeItemView: View {
let imageTag = person["PrimaryImageTag"].string ?? ""; let imageTag = person["PrimaryImageTag"].string ?? "";
cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? ""; cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? "";
cast.Role = person["Role"].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); fullItem.Cast.append(cast);
} }
} }
@ -191,52 +189,237 @@ struct EpisodeItemView: View {
} }
var body: some View { var body: some View {
if(playing) { LoadingView(isShowing: $isLoading) {
VideoPlayerView(item: fullItem, playing: $playing) VStack(alignment:.leading) {
.supportedOrientations(.landscape) if(!isLoading) {
.overrideViewPreference(.dark) if(orientationInfo.orientation == .portrait) {
.prefersHomeIndicatorAutoHidden(true) GeometryReader { geometry in
.introspectTabBarController { (UITabBarController) in VStack() {
UITabBarController.tabBar.isHidden = true 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
} else { .placeholder {
LoadingView(isShowing: $isLoading) { Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!)
VStack(alignment:.leading) { .resizable()
if(!isLoading) { .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)
if(orientationInfo.orientation == .portrait) { }
GeometryReader { geometry in
VStack() { .opacity(0.3)
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=450&quality=90&tag=\(fullItem.Backdrop)")!) .aspectRatio(contentMode: .fill)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size .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)
.placeholder { .shadow(radius: 5)
Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!) .overlay(
.resizable() HStack() {
.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) 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 {
.opacity(0.3) Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 32, height: 32))!)
.aspectRatio(contentMode: .fill) .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) .frame(width: 120, height: 180)
.shadow(radius: 5) .cornerRadius(10)
.overlay( }.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() { HStack() {
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
.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))!) .frame(width: 120, height: 35)
.resizable() .background(Color(red: 172/255, green: 92/255, blue: 195/255))
.frame(width: 120, height: 180) .cornerRadius(10)
.cornerRadius(10) }
}.aspectRatio(contentMode: .fill) 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) .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) { VStack(alignment: .leading) {
Spacer()
Text(fullItem.Name).font(.headline) Text(fullItem.Name).font(.headline)
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(.primary) .foregroundColor(.primary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.offset(y: -4) .offset(x: 14, y: 0)
Spacer().frame(height: 1)
HStack() { HStack() {
Text(String(fullItem.ProductionYear)).font(.subheadline) Text(String(fullItem.ProductionYear)).font(.subheadline)
.fontWeight(.medium) .fontWeight(.medium)
@ -267,319 +450,115 @@ struct EpisodeItemView: View {
.offset(x: -7, y: 0.7) .offset(x: -7, y: 0.7)
} }
} }
}.frame(maxWidth: .infinity, alignment: .leading) 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: {
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)
}.frame(maxWidth: .infinity) }.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() { HStack() {
Button() { Text("Genres:").font(.callout).fontWeight(.semibold)
favorite.toggle() ForEach(fullItem.Genres, id: \.Id) {genre in
} label: { NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
if(!favorite) { Text(genre.Name).font(.footnote)
Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20))
} else {
Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20))
} }
} }
Button() { }.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
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) { if(fullItem.Cast.count != 0) {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
VStack() {
Spacer().frame(height: 8);
HStack() { HStack() {
Text("Genres:").font(.callout).fontWeight(.semibold) Spacer().frame(width: 16)
ForEach(fullItem.Genres, id: \.Id) {genre in ForEach(fullItem.Cast, id: \.Id) { cast in
NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) { NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
Text(genre.Name).font(.footnote) VStack() {
} WebImage(url: cast.Image)
} .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
}.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) .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()
if(fullItem.Cast.count != 0) { .aspectRatio(contentMode: .fill)
ScrollView(.horizontal, showsIndicators: false) { .frame(width: 100, height: 100)
VStack() { .cornerRadius(10)
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)
} }
.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) }
} }.padding(.top, -3)
if(fullItem.Directors.count != 0) { }
HStack() { if(fullItem.Directors.count != 0) {
Text("Directors:").font(.callout).fontWeight(.semibold) HStack() {
Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) Text("Directors:").font(.callout).fontWeight(.semibold)
}.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) 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() { if(fullItem.Writers.count != 0) {
Text("Writers:").font(.callout).fontWeight(.semibold) HStack() {
Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) Text("Writers:").font(.callout).fontWeight(.semibold)
}.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) 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() { if(fullItem.Studios.count != 0) {
Text("Studios:").font(.callout).fontWeight(.semibold) HStack() {
Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary) Text("Studios:").font(.callout).fontWeight(.semibold)
}.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) 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) Spacer().frame(height: 100);
} }.frame(maxHeight: .infinity)
}.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55).edgesIgnoringSafeArea(.leading) }
} }.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 ?? "")") .navigationBarTitleDisplayMode(.inline)
.introspectTabBarController { (UITabBarController) in .navigationTitle("\(fullItem.Name) - S\(String(fullItem.ParentIndexNumber ?? 0)):E\(String(fullItem.IndexNumber ?? 0)) - \(fullItem.SeriesName ?? "")")
UITabBarController.tabBar.isHidden = false }.onAppear(perform: loadData)
} .supportedOrientations(.allButUpsideDown)
}.onAppear(perform: loadData) .overrideViewPreference(.unspecified)
.supportedOrientations(.allButUpsideDown) .preferredColorScheme(.none)
.overrideViewPreference(.unspecified) .prefersHomeIndicatorAutoHidden(false)
.preferredColorScheme(.none)
.prefersHomeIndicatorAutoHidden(false)
}
} }
} }

View File

@ -6,14 +6,8 @@
// //
import SwiftUI import SwiftUI
import SwiftyRequest
import SwiftyJSON
import Introspect
import SDWebImageSwiftUI
struct ItemView: View { struct ItemView: View {
@EnvironmentObject var globalData: GlobalData
@State private var isLoading: Bool = false;
var item: ResumeItem; var item: ResumeItem;
init(item: ResumeItem) { init(item: ResumeItem) {

View File

@ -11,6 +11,14 @@ class justSignedIn: ObservableObject {
@Published var did: Bool = false @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 { extension UIDevice {
var hasNotch: Bool { var hasNotch: Bool {
let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0 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<AnyView> {
init<V: View>(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 @main
struct JellyfinPlayerApp: App { struct JellyfinPlayerApp: App {
let persistenceController = PersistenceController.shared let persistenceController = PersistenceController.shared

View File

@ -119,7 +119,7 @@ struct LatestMediaView: View {
.cornerRadius(10.0) .cornerRadius(10.0)
.padding(3), alignment: .topTrailing .padding(3), alignment: .topTrailing
).shadow(radius: 6) )
} else { } else {
Spacer().frame(height:10) Spacer().frame(height:10)
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")!) 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) .frame(width: 100, height: 150)
.cornerRadius(10) .cornerRadius(10)
.shadow(radius: 6)
} }
Text(item.Name) Text(item.Name)
.font(.caption) .font(.caption)

View File

@ -218,7 +218,6 @@ struct LibraryView: View {
} }
.frame(width:100, height: 150) .frame(width:100, height: 150)
.cornerRadius(10) .cornerRadius(10)
.shadow(radius: 5)
} else { } else {
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)"))
.resizable() .resizable()
@ -246,7 +245,7 @@ struct LibraryView: View {
.opacity(0.8) .opacity(0.8)
.cornerRadius(10.0) .cornerRadius(10.0)
.padding(3), alignment: .topTrailing .padding(3), alignment: .topTrailing
).shadow(radius: 5) )
} }
Text(item.Name) Text(item.Name)
.font(.caption) .font(.caption)

View File

@ -8,7 +8,6 @@
import SwiftUI import SwiftUI
import SwiftyRequest import SwiftyRequest
import SwiftyJSON import SwiftyJSON
import Introspect
import SDWebImageSwiftUI import SDWebImageSwiftUI
class DetailItem: ObservableObject { class DetailItem: ObservableObject {
@ -182,7 +181,7 @@ struct MovieItemView: View {
let imageTag = person["PrimaryImageTag"].string ?? ""; let imageTag = person["PrimaryImageTag"].string ?? "";
cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? ""; cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? "";
cast.Role = person["Role"].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); fullItem.Cast.append(cast);
} }
} }
@ -237,53 +236,236 @@ struct MovieItemView: View {
} }
var body: some View { var body: some View {
if(playing) { LoadingView(isShowing: $isLoading) {
VideoPlayerView(item: fullItem, playing: $playing) VStack(alignment:.leading) {
.supportedOrientations(.landscape) if(!isLoading) {
.preferredColorScheme(.dark) if(orientationInfo.orientation == .portrait) {
.overrideViewPreference(.dark) GeometryReader { geometry in
.prefersHomeIndicatorAutoHidden(true) VStack() {
.introspectTabBarController { (UITabBarController) in WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=450&quality=90&tag=\(fullItem.Backdrop)")!)
UITabBarController.tabBar.isHidden = true .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
} .placeholder {
} else { Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!)
LoadingView(isShowing: $isLoading) { .resizable()
VStack(alignment:.leading) { .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)
if(!isLoading) { }
if(orientationInfo.orientation == .portrait) {
GeometryReader { geometry in .opacity(0.3)
VStack() { .aspectRatio(contentMode: .fill)
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=450&quality=90&tag=\(fullItem.Backdrop)")!) .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)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size .shadow(radius: 5)
.placeholder { .overlay(
Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!) HStack() {
.resizable() WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!)
.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) .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))!)
.opacity(0.3) .resizable()
.aspectRatio(contentMode: .fill) .frame(width: 120, height: 180)
.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) .cornerRadius(10)
.shadow(radius: 5) }.aspectRatio(contentMode: .fill)
.overlay( .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() { HStack() {
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
.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))!) .frame(width: 120, height: 35)
.resizable() .background(Color(red: 172/255, green: 92/255, blue: 195/255))
.frame(width: 120, height: 180) .cornerRadius(10)
.cornerRadius(10) }
}.aspectRatio(contentMode: .fill) 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) .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) { VStack(alignment: .leading) {
Spacer()
Text(fullItem.Name).font(.headline) Text(fullItem.Name).font(.headline)
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(.primary) .foregroundColor(.primary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.offset(y: -4) .offset(x: 14, y: 0)
Spacer().frame(height: 1)
HStack() { HStack() {
Text(String(fullItem.ProductionYear)).font(.subheadline) Text(String(fullItem.ProductionYear)).font(.subheadline)
.fontWeight(.medium) .fontWeight(.medium)
@ -314,318 +496,115 @@ struct MovieItemView: View {
.offset(x: -7, y: 0.7) .offset(x: -7, y: 0.7)
} }
} }
Spacer()
}.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: 14)
}.offset(x: 16, y: UIDevice.current.userInterfaceIdiom == .pad ? 135 : 40) }.frame(maxWidth: .infinity, alignment: .leading)
, alignment: .bottomLeading) Spacer()
VStack(alignment: .leading) {
HStack() {
//Play button
Button() {
playing = true;
} label: {
HStack() { HStack() {
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold) Button() {
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) favorite.toggle()
} } label: {
.frame(width: 120, height: 35) if(!favorite) {
.background(Color(red: 172/255, green: 92/255, blue: 195/255)) Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20))
.cornerRadius(10) } else {
}.buttonStyle(PlainButtonStyle()) Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20))
.frame(width: 120, height: 25) }
Spacer() }
HStack() { Button() {
Button() { watched.toggle()
favorite.toggle() } label: {
} label: { if(watched) {
if(!favorite) { Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20))
Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20)) } else {
} else { Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20))
Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20)) }
} }
} }
Button() { }.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
watched.toggle() if(fullItem.Tagline != "") {
} label: { 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)
if(watched) { }
Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20)) 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)
} else { if(fullItem.Genres.count != 0) {
Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20)) 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) if(fullItem.Cast.count != 0) {
ScrollView() { ScrollView(.horizontal, showsIndicators: false) {
VStack(alignment: .leading) { VStack() {
if(fullItem.Tagline != "") { Spacer().frame(height: 8);
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() { HStack() {
Text("Genres:").font(.callout).fontWeight(.semibold) Spacer().frame(width: 16)
ForEach(fullItem.Genres, id: \.Id) {genre in ForEach(fullItem.Cast, id: \.Id) { cast in
NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) { NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
Text(genre.Name).font(.footnote) VStack() {
} WebImage(url: cast.Image)
} .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
}.padding(.leading, 16).padding(.trailing,16) .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()
if(fullItem.Cast.count != 0) { .aspectRatio(contentMode: .fill)
ScrollView(.horizontal, showsIndicators: false) { .frame(width: 100, height: 100)
VStack() { .cornerRadius(10)
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)
} }
.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) }
} }.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,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)
} }
} if(fullItem.Directors.count != 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: {
HStack() { HStack() {
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold) Text("Directors:").font(.callout).fontWeight(.semibold)
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
} }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
.frame(width: 120, height: 35) }
.background(Color(red: 172/255, green: 92/255, blue: 195/255)) if(fullItem.Writers.count != 0) {
.cornerRadius(10)
}.buttonStyle(PlainButtonStyle())
.frame(width: 120, height: 25)
Spacer()
}
ScrollView() {
VStack(alignment: .leading) {
HStack() { HStack() {
VStack(alignment: .leading) { Text("Writers:").font(.callout).fontWeight(.semibold)
Text(fullItem.Name).font(.headline) Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
.fontWeight(.semibold) }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
.foregroundColor(.primary) }
.fixedSize(horizontal: false, vertical: true) if(fullItem.Studios.count != 0) {
.offset(x: 14, y: 0) HStack() {
Spacer().frame(height: 1) Text("Studios:").font(.callout).fontWeight(.semibold)
HStack() { Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
Text(String(fullItem.ProductionYear)).font(.subheadline) }.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
.fontWeight(.medium) }
.foregroundColor(.secondary) Spacer().frame(height: 195);
.lineLimit(1) }.frame(maxHeight: .infinity)
Text(fullItem.Runtime).font(.subheadline) }
.fontWeight(.medium) }.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55).edgesIgnoringSafeArea(.leading)
.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)
}
} }
} }
} }
} }
.navigationBarTitleDisplayMode(.inline) }
.navigationTitle(fullItem.Name) .navigationBarTitleDisplayMode(.inline)
.introspectTabBarController { (UITabBarController) in .navigationTitle(fullItem.Name)
UITabBarController.tabBar.isHidden = false }.onAppear(perform: loadData)
} .supportedOrientations(.allButUpsideDown)
}.onAppear(perform: loadData) .overrideViewPreference(.unspecified)
.supportedOrientations(.allButUpsideDown) .preferredColorScheme(.none)
.overrideViewPreference(.unspecified) .prefersHomeIndicatorAutoHidden(false)
.preferredColorScheme(.none)
.prefersHomeIndicatorAutoHidden(false)
}
} }
} }

View File

@ -89,7 +89,6 @@ struct NextUpView: View {
} }
.frame(width: 100, height: 150) .frame(width: 100, height: 150)
.cornerRadius(10) .cornerRadius(10)
.shadow(radius: 6)
Text(item.SeriesName ?? "") Text(item.SeriesName ?? "")
.font(.caption) .font(.caption)
.fontWeight(.semibold) .fontWeight(.semibold)

View File

@ -8,7 +8,6 @@
import SwiftUI import SwiftUI
import SwiftyRequest import SwiftyRequest
import SwiftyJSON import SwiftyJSON
import Introspect
import SDWebImageSwiftUI import SDWebImageSwiftUI
struct SeasonItemView: View { struct SeasonItemView: View {

View File

@ -392,9 +392,6 @@ struct VideoPlayerView: View {
vlcplayer.stop() vlcplayer.stop()
}).padding(EdgeInsets(top: 0, leading: UIDevice.current.hasNotch ? 30 : 0, bottom: 0, trailing: UIDevice.current.hasNotch ? 30 : 0)) }).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( .overlay(
VStack() { VStack() {
HStack() { HStack() {
@ -489,7 +486,6 @@ struct VideoPlayerView: View {
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(.black).opacity(0.4)) .background(Color(.black).opacity(0.4))
.isHidden(inactivity)
, alignment: .topLeading) , alignment: .topLeading)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.onAppear(perform: startStream) .onAppear(perform: startStream)

View File

@ -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)
}
}