summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShav Kinderlehrer <[email protected]>2023-12-24 20:01:52 -0500
committerShav Kinderlehrer <[email protected]>2023-12-24 20:01:52 -0500
commit69bed7745833add72cfe1b1516a61f62460b1765 (patch)
tree373d6a63cfa3d79b5fce59091822bfe7748bcab9
parent9da269a6ab44869e41a8b005836c3479424ae538 (diff)
downloadjel-69bed7745833add72cfe1b1516a61f62460b1765.tar.gz
jel-69bed7745833add72cfe1b1516a61f62460b1765.zip
Implement movieDetailView
-rw-r--r--Jel.xcodeproj/project.pbxproj24
-rw-r--r--Jel/Models/JellyfinKitExtensions.swift28
-rw-r--r--Jel/Models/ViewOffsetKey.swift17
-rw-r--r--Jel/Views/Library/Item/ItemHeaderView.swift42
-rw-r--r--Jel/Views/Library/Item/ItemInfoView.swift29
-rw-r--r--Jel/Views/Library/Item/ItemMovieView.swift82
-rw-r--r--Jel/Views/Library/LibraryDetailView.swift1
-rw-r--r--Jel/Views/Library/LibraryIconView.swift2
-rw-r--r--Jel/Views/Utility/StickyHeaderView.swift38
9 files changed, 199 insertions, 64 deletions
diff --git a/Jel.xcodeproj/project.pbxproj b/Jel.xcodeproj/project.pbxproj
index d427a34..be0a680 100644
--- a/Jel.xcodeproj/project.pbxproj
+++ b/Jel.xcodeproj/project.pbxproj
@@ -14,6 +14,8 @@
3D13F95F2B375DB800E91913 /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F95E2B375DB800E91913 /* ItemView.swift */; };
3D13F9612B37637500E91913 /* ItemMovieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F9602B37637500E91913 /* ItemMovieView.swift */; };
3D13F9652B37EC7A00E91913 /* ItemHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F9642B37EC7A00E91913 /* ItemHeaderView.swift */; };
+ 3D13F9692B389FA300E91913 /* ViewOffsetKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F9682B389FA300E91913 /* ViewOffsetKey.swift */; };
+ 3D13F96F2B38A32500E91913 /* StickyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F96E2B38A32500E91913 /* StickyHeaderView.swift */; };
3D16FC3C2B2CDFB500E6D8B3 /* DashboardLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D16FC3B2B2CDFB500E6D8B3 /* DashboardLibraryView.swift */; };
3D41D1F52B2C962500E58234 /* AppearancePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D41D1F42B2C962500E58234 /* AppearancePicker.swift */; };
3D41D1FA2B2CAE0000E58234 /* LibraryIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D41D1F92B2CAE0000E58234 /* LibraryIconView.swift */; };
@@ -32,6 +34,8 @@
3D91FDC92B28C62800919017 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D91FDC82B28C62800919017 /* SignInView.swift */; };
3D91FDCB2B28CA2500919017 /* SignInToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D91FDCA2B28CA2500919017 /* SignInToServerView.swift */; };
3D91FDCD2B2907E800919017 /* JellyfinDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */; };
+ 3DAFA8E82B38AFED00D71AD1 /* ItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8E72B38AFED00D71AD1 /* ItemInfoView.swift */; };
+ 3DAFA8EA2B39039900D71AD1 /* JellyfinKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8E92B39039900D71AD1 /* JellyfinKitExtensions.swift */; };
3DC6BA2D2B2A422300416B9F /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC6BA2C2B2A422300416B9F /* SettingsController.swift */; };
3DDD67932B293BC40026781E /* DashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDD67922B293BC40026781E /* DashboardView.swift */; };
3DDD67962B29E28B0026781E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDD67952B29E28B0026781E /* SettingsView.swift */; };
@@ -75,6 +79,8 @@
3D13F95E2B375DB800E91913 /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
3D13F9602B37637500E91913 /* ItemMovieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemMovieView.swift; sourceTree = "<group>"; };
3D13F9642B37EC7A00E91913 /* ItemHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemHeaderView.swift; sourceTree = "<group>"; };
+ 3D13F9682B389FA300E91913 /* ViewOffsetKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewOffsetKey.swift; sourceTree = "<group>"; };
+ 3D13F96E2B38A32500E91913 /* StickyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyHeaderView.swift; sourceTree = "<group>"; };
3D16FC3B2B2CDFB500E6D8B3 /* DashboardLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardLibraryView.swift; sourceTree = "<group>"; };
3D41D1F42B2C962500E58234 /* AppearancePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePicker.swift; sourceTree = "<group>"; };
3D41D1F92B2CAE0000E58234 /* LibraryIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryIconView.swift; sourceTree = "<group>"; };
@@ -94,6 +100,8 @@
3D91FDC82B28C62800919017 /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = "<group>"; };
3D91FDCA2B28CA2500919017 /* SignInToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInToServerView.swift; sourceTree = "<group>"; };
3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinDateFormatter.swift; sourceTree = "<group>"; };
+ 3DAFA8E72B38AFED00D71AD1 /* ItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemInfoView.swift; sourceTree = "<group>"; };
+ 3DAFA8E92B39039900D71AD1 /* JellyfinKitExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinKitExtensions.swift; sourceTree = "<group>"; };
3DC0E5802B2832B9001CCE96 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3DC6BA2C2B2A422300416B9F /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; };
3DDD67922B293BC40026781E /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = "<group>"; };
@@ -134,6 +142,7 @@
3D1015D72B27F54A00F5C29A /* Views */ = {
isa = PBXGroup;
children = (
+ 3D13F96D2B38A31300E91913 /* Utility */,
3D9063CC2B279A310063DD2A /* ContentView.swift */,
3DDD67902B293B780026781E /* Dashboard */,
3D8AB2A62B366309005BD7D0 /* Library */,
@@ -158,6 +167,8 @@
children = (
3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */,
3D8AB2A42B36440D005BD7D0 /* BlurHashDecode.swift */,
+ 3D13F9682B389FA300E91913 /* ViewOffsetKey.swift */,
+ 3DAFA8E92B39039900D71AD1 /* JellyfinKitExtensions.swift */,
);
path = Models;
sourceTree = "<group>";
@@ -167,11 +178,20 @@
children = (
3D13F95E2B375DB800E91913 /* ItemView.swift */,
3D13F9602B37637500E91913 /* ItemMovieView.swift */,
+ 3DAFA8E72B38AFED00D71AD1 /* ItemInfoView.swift */,
3D13F9642B37EC7A00E91913 /* ItemHeaderView.swift */,
);
path = Item;
sourceTree = "<group>";
};
+ 3D13F96D2B38A31300E91913 /* Utility */ = {
+ isa = PBXGroup;
+ children = (
+ 3D13F96E2B38A32500E91913 /* StickyHeaderView.swift */,
+ );
+ path = Utility;
+ sourceTree = "<group>";
+ };
3D8AB2A62B366309005BD7D0 /* Library */ = {
isa = PBXGroup;
children = (
@@ -414,10 +434,13 @@
buildActionMask = 2147483647;
files = (
3D9063CD2B279A310063DD2A /* ContentView.swift in Sources */,
+ 3D13F96F2B38A32500E91913 /* StickyHeaderView.swift in Sources */,
3DF1ED3E2B282836000AD8EA /* JellyfinClientController.swift in Sources */,
3D1015D92B27F57400F5C29A /* AddServerView.swift in Sources */,
+ 3DAFA8EA2B39039900D71AD1 /* JellyfinKitExtensions.swift in Sources */,
3D13F9652B37EC7A00E91913 /* ItemHeaderView.swift in Sources */,
3D9063CB2B279A310063DD2A /* JelApp.swift in Sources */,
+ 3D13F9692B389FA300E91913 /* ViewOffsetKey.swift in Sources */,
3D91FDCD2B2907E800919017 /* JellyfinDateFormatter.swift in Sources */,
3D91FDC92B28C62800919017 /* SignInView.swift in Sources */,
3D8AB2A82B366353005BD7D0 /* LibraryDetailView.swift in Sources */,
@@ -426,6 +449,7 @@
3D41D1FA2B2CAE0000E58234 /* LibraryIconView.swift in Sources */,
3D8AB2A52B36440D005BD7D0 /* BlurHashDecode.swift in Sources */,
3DC6BA2D2B2A422300416B9F /* SettingsController.swift in Sources */,
+ 3DAFA8E82B38AFED00D71AD1 /* ItemInfoView.swift in Sources */,
3D91FDCB2B28CA2500919017 /* SignInToServerView.swift in Sources */,
3D16FC3C2B2CDFB500E6D8B3 /* DashboardLibraryView.swift in Sources */,
3D1015E42B28000E00F5C29A /* AuthStateController.swift in Sources */,
diff --git a/Jel/Models/JellyfinKitExtensions.swift b/Jel/Models/JellyfinKitExtensions.swift
new file mode 100644
index 0000000..197731c
--- /dev/null
+++ b/Jel/Models/JellyfinKitExtensions.swift
@@ -0,0 +1,28 @@
+//
+// JellyfinKitExtensions.swift
+// Jel
+//
+// Created by zerocool on 12/24/23.
+//
+
+import Foundation
+import JellyfinKit
+
+extension BaseItemDto {
+ func getRuntime() -> String? {
+ let formatter: DateComponentsFormatter = {
+ let localFormatter = DateComponentsFormatter()
+ localFormatter.unitsStyle = .brief
+ localFormatter.allowedUnits = [.hour, .minute]
+
+ return localFormatter
+ }()
+
+ if let runTimeTicks = self.runTimeTicks {
+ let text = formatter.string(from: Double(runTimeTicks / 10_000_000))
+ return text
+ }
+
+ return nil
+ }
+}
diff --git a/Jel/Models/ViewOffsetKey.swift b/Jel/Models/ViewOffsetKey.swift
new file mode 100644
index 0000000..b118478
--- /dev/null
+++ b/Jel/Models/ViewOffsetKey.swift
@@ -0,0 +1,17 @@
+//
+// ViewOffsetKey.swift
+// Jel
+//
+// Created by zerocool on 12/24/23.
+//
+
+import SwiftUI
+
+/// A preference key to store ScrollView offset
+public struct ViewOffsetKey: PreferenceKey {
+ public typealias Value = CGFloat
+ public static var defaultValue = CGFloat.zero
+ public static func reduce(value: inout Value, nextValue: () -> Value) {
+ value += nextValue()
+ }
+}
diff --git a/Jel/Views/Library/Item/ItemHeaderView.swift b/Jel/Views/Library/Item/ItemHeaderView.swift
index d694c13..2c11719 100644
--- a/Jel/Views/Library/Item/ItemHeaderView.swift
+++ b/Jel/Views/Library/Item/ItemHeaderView.swift
@@ -13,35 +13,43 @@ struct ItemHeaderView: View {
let overlayGradient = LinearGradient(gradient: Gradient(stops: [
.init(color: .clear, location: 0),
- .init(color: .black, location: 0.3),
- .init(color: .black, location: 0.7),
-
- .init(color: .clear, location: 1)
+ .init(color: .black, location: 0.5),
+ // .init(color: .black, location: 0.7),
+ // .init(color: .clear, location: 1)
]), startPoint: .bottom, endPoint: .top)
var body: some View {
ZStack(alignment: .bottom) {
- LibraryIconView(library: item, imageType: "Backdrop", contentMode: .fill)
- .hideCaption()
- .setCornerRadius(0)
- .mask(overlayGradient)
-// .padding(.top, 50)
- .background {
- LibraryIconView(library: item, imageType: "Backdrop", contentMode: .fill)
- .hideCaption()
- .setCornerRadius(0)
- .blur(radius: 50)
- }
+ StickyHeaderView(minHeight: 300) {
+ LibraryIconView(library: item, imageType: "Backdrop", contentMode: .fill)
+ .hideCaption()
+ .setCornerRadius(0)
+ .mask(overlayGradient)
+ .background {
+ LibraryIconView(library: item, imageType: "Backdrop", contentMode: .fill)
+ .hideCaption()
+ .setCornerRadius(0)
+ .blur(radius: 50)
+ }
+ }
HStack {
LibraryIconView(library: item, imageType: "Logo", width: 200, height: 100, placeHolder: AnyView(Text(item.name ?? "Unknown").font(.title).bold().truncationMode(.middle)))
.hideCaption()
.setCornerRadius(0)
.shadow(radius: 10)
+ .frame(alignment: .leading)
Spacer()
+ ItemInfoView(item: item)
+ .foregroundStyle(.white)
}
- .frame(alignment: .leading)
- .padding(.leading)
+ .padding([.leading, .trailing])
+ }
+ .scrollTransition {content, phase in
+ content
+ .scaleEffect(phase.isIdentity ? 1 : 2)
+ .opacity(phase.isIdentity ? 1 : 0.1)
+ .blur(radius: phase.isIdentity ? 0 : 50)
}
}
}
diff --git a/Jel/Views/Library/Item/ItemInfoView.swift b/Jel/Views/Library/Item/ItemInfoView.swift
new file mode 100644
index 0000000..d48dfef
--- /dev/null
+++ b/Jel/Views/Library/Item/ItemInfoView.swift
@@ -0,0 +1,29 @@
+//
+// ItemInfoView.swift
+// Jel
+//
+// Created by zerocool on 12/24/23.
+//
+
+import SwiftUI
+import JellyfinKit
+
+struct ItemInfoView: View {
+ @State var item: BaseItemDto
+
+ var body: some View {
+ VStack(alignment: .leading) {
+ HStack {
+ Text(item.genres?.first ?? "---")
+ Text("•")
+ Text((item.productionYear != nil) ? String(item.productionYear!) : "---")
+ }
+ Text(item.getRuntime() ?? "-:--")
+ }
+ .font(.caption)
+ }
+}
+
+//#Preview {
+// ItemInfoView()
+//}
diff --git a/Jel/Views/Library/Item/ItemMovieView.swift b/Jel/Views/Library/Item/ItemMovieView.swift
index 68ba1b2..be12696 100644
--- a/Jel/Views/Library/Item/ItemMovieView.swift
+++ b/Jel/Views/Library/Item/ItemMovieView.swift
@@ -18,59 +18,50 @@ struct ItemMovieView: View {
@State var navigationTitle: String = ""
var body: some View {
- ScrollView {
- // ItemHeaderView(item: item)
- // .scrollTransition {content, phase in
- // content
- // .scaleEffect(phase.isIdentity ? 1 : 2)
- // .opacity(phase.isIdentity ? 1 : 0.1)
- // .blur(radius: phase.isIdentity ? 0 : 50)
- // }
- ItemHeaderView(item: item)
- .opacity(0) // this is the jankiest thing in existence
- .background {
- GeometryReader {geo in
- ItemHeaderView(item: item)
- .onChange(of: geo.frame(in: .global).minY) {
- navigationTitle = geo.frame(in: .global).minY < 0 ? item.name ?? "Unknown" : ""
- }
- .scaleEffect(1 + (geo.frame(in: .global).minY > 0 ? geo.frame(in: .global).minY * 0.001 : 0))
- .offset(y: 1 + (geo.frame(in: .global).minY > 0 ? geo.frame(in: .global).minY * 0.001 : 0))
- .scrollTransition {content, phase in
- content
- .scaleEffect(phase.isIdentity ? 1 : 2)
- .opacity(phase.isIdentity ? 1 : 0.1)
- .blur(radius: phase.isIdentity ? 0 : 50)
+ VStack {
+ if loading {
+ ProgressView()
+ .progressViewStyle(.circular)
+ } else {
+ ScrollView {
+ ItemHeaderView(item: item)
+ .padding(.bottom)
+ .background {
+ GeometryReader {geo in
+ EmptyView()
+ .onChange(of: geo.frame(in: .global).minY) {
+ let minY = geo.frame(in: .global).minY
+ if minY < 0 {
+ navigationTitle = item.name ?? ""
+ } else {
+ navigationTitle = ""
+ }
+ }
}
+ }
+
+ VStack(alignment: .leading) {
+ Text(item.taglines?.count ?? 0 > 0 ? item.taglines?[0] ?? "" : "")
+ .font(.headline)
+ .frame(maxWidth: .infinity, alignment: .leading)
+
+ Text(item.overview ?? "---")
}
- }
-
- VStack {
- Text(item.taglines?[0] ?? "Unknown")
- .font(.headline)
- .padding(.top, 20)
-
- Text(item.overview ?? "Unknown")
- .padding()
- Text(item.overview ?? "Unknown")
- .padding()
- Text(item.overview ?? "Unknown")
- .padding()
- Text(item.overview ?? "Unknown")
.padding()
+ }
}
}
- .redacted(reason: loading ? .placeholder : [])
- .ignoresSafeArea(edges: .top)
- .scrollIndicators(.hidden)
.toolbarRole(.editor)
- .navigationTitle(navigationTitle)
.navigationBarTitleDisplayMode(.inline)
+ .navigationTitle(navigationTitle)
+ .ignoresSafeArea(edges: .bottom)
+ .scrollIndicators(.hidden)
.onAppear {
Task {
do {
let request = Paths.getItem(userID: authState.userId ?? "", itemID: item.id ?? "")
- item = try await jellyfinClient.send(request).value
+ let response = try await jellyfinClient.send(request)
+ item = response.value
loading = false
} catch {
}
@@ -79,6 +70,7 @@ struct ItemMovieView: View {
}
}
-#Preview {
- ItemMovieView(item: BaseItemDto())
-}
+//#Preview {
+// ItemMovieView(item: BaseItemDto())
+
+//}
diff --git a/Jel/Views/Library/LibraryDetailView.swift b/Jel/Views/Library/LibraryDetailView.swift
index 4b140df..f4ff93c 100644
--- a/Jel/Views/Library/LibraryDetailView.swift
+++ b/Jel/Views/Library/LibraryDetailView.swift
@@ -47,6 +47,7 @@ struct LibraryDetailView: View {
do {
let res = try await jellyfinClient.send(request)
items = res.value.items
+ items?.sort(by: {$0.name?.lowercased() ?? "" < $1.name?.lowercased() ?? ""})
loading = false
} catch {
}
diff --git a/Jel/Views/Library/LibraryIconView.swift b/Jel/Views/Library/LibraryIconView.swift
index a3f5b55..a849446 100644
--- a/Jel/Views/Library/LibraryIconView.swift
+++ b/Jel/Views/Library/LibraryIconView.swift
@@ -32,8 +32,6 @@ struct LibraryIconView: View {
if let image = state.image {
image
.resizable()
- } else if state.error != nil {
- Color.red
} else {
if let content = placeHolder {
content
diff --git a/Jel/Views/Utility/StickyHeaderView.swift b/Jel/Views/Utility/StickyHeaderView.swift
new file mode 100644
index 0000000..52dc26d
--- /dev/null
+++ b/Jel/Views/Utility/StickyHeaderView.swift
@@ -0,0 +1,38 @@
+//
+// StickyHeaderView.swift
+// Jel
+//
+// Created by zerocool on 12/24/23.
+//
+
+import SwiftUI
+
+struct StickyHeaderView<Content: View>: View {
+
+ var minHeight: CGFloat
+ var content: Content
+
+ init(minHeight: CGFloat = 200, @ViewBuilder content: () -> Content) {
+ self.minHeight = minHeight
+ self.content = content()
+ }
+
+ var body: some View {
+ GeometryReader { geo in
+ if(geo.frame(in: .global).minY <= 0) {
+ content
+ .frame(width: geo.size.width, height: geo.size.height, alignment: .center)
+ } else {
+ content
+ .offset(y: -geo.frame(in: .global).minY)
+ .frame(width: geo.size.width, height: geo.size.height + geo.frame(in: .global).minY)
+ }
+ }.frame(minHeight: minHeight)
+ }
+}
+
+#Preview {
+ StickyHeaderView {
+ Text("Test")
+ }
+}