Implement ItemPeopleView

This commit is contained in:
Shav Kinderlehrer 2024-01-09 12:32:06 -05:00
parent 4ec0f962b2
commit 6edc39791a
14 changed files with 209 additions and 63 deletions

View File

@ -21,6 +21,7 @@
3D3816CE2B4B78BB006414D7 /* VisibilityTrackingScrollView in Frameworks */ = {isa = PBXBuildFile; productRef = 3D3816CD2B4B78BB006414D7 /* VisibilityTrackingScrollView */; };
3D41D1FA2B2CAE0000E58234 /* ItemIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D41D1F92B2CAE0000E58234 /* ItemIconView.swift */; };
3D4C15722B3CAA670035373E /* DashboardSectionTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D4C15712B3CAA670035373E /* DashboardSectionTitleView.swift */; };
3D58F07E2B4DB19300DB2936 /* TextRatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D58F07D2B4DB19300DB2936 /* TextRatingView.swift */; };
3D7709392B29139700199889 /* Pulse in Frameworks */ = {isa = PBXBuildFile; productRef = 3D7709382B29139700199889 /* Pulse */; };
3D77093B2B29139700199889 /* PulseUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3D77093A2B29139700199889 /* PulseUI */; };
3D8AB2A52B36440D005BD7D0 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D8AB2A42B36440D005BD7D0 /* BlurHashDecode.swift */; };
@ -38,10 +39,11 @@
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 */; };
3DAFA8EC2B394F9F00D71AD1 /* ViewConditionalMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8EB2B394F9F00D71AD1 /* ViewConditionalMethod.swift */; };
3DAFA8EC2B394F9F00D71AD1 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8EB2B394F9F00D71AD1 /* ViewExtensions.swift */; };
3DAFA8EF2B3B707B00D71AD1 /* ItemMovieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8EE2B3B707B00D71AD1 /* ItemMovieView.swift */; };
3DBAC9E22B4C31BE005F8764 /* ItemPeopleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBAC9E12B4C31BE005F8764 /* ItemPeopleView.swift */; };
3DBAC9E42B4C7404005F8764 /* UIScreenCurrent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBAC9E32B4C7404005F8764 /* UIScreenCurrent.swift */; };
3DBAC9EA2B4C8927005F8764 /* ItemPersonIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBAC9E92B4C8927005F8764 /* ItemPersonIconView.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 */; };
@ -91,6 +93,7 @@
3D3816C82B4B5648006414D7 /* ItemGenresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemGenresView.swift; sourceTree = "<group>"; };
3D41D1F92B2CAE0000E58234 /* ItemIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemIconView.swift; sourceTree = "<group>"; };
3D4C15712B3CAA670035373E /* DashboardSectionTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardSectionTitleView.swift; sourceTree = "<group>"; };
3D58F07D2B4DB19300DB2936 /* TextRatingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRatingView.swift; sourceTree = "<group>"; };
3D8AB2A42B36440D005BD7D0 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
3D8AB2A72B366353005BD7D0 /* LibraryDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryDetailView.swift; sourceTree = "<group>"; };
3D9063C72B279A310063DD2A /* Jel.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Jel.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -109,10 +112,11 @@
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>"; };
3DAFA8EB2B394F9F00D71AD1 /* ViewConditionalMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewConditionalMethod.swift; sourceTree = "<group>"; };
3DAFA8EB2B394F9F00D71AD1 /* ViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtensions.swift; sourceTree = "<group>"; };
3DAFA8EE2B3B707B00D71AD1 /* ItemMovieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemMovieView.swift; sourceTree = "<group>"; };
3DBAC9E12B4C31BE005F8764 /* ItemPeopleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemPeopleView.swift; sourceTree = "<group>"; };
3DBAC9E32B4C7404005F8764 /* UIScreenCurrent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScreenCurrent.swift; sourceTree = "<group>"; };
3DBAC9E92B4C8927005F8764 /* ItemPersonIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemPersonIconView.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>"; };
@ -182,7 +186,7 @@
3D8AB2A42B36440D005BD7D0 /* BlurHashDecode.swift */,
3D13F9682B389FA300E91913 /* ViewOffsetKey.swift */,
3DAFA8E92B39039900D71AD1 /* JellyfinKitExtensions.swift */,
3DAFA8EB2B394F9F00D71AD1 /* ViewConditionalMethod.swift */,
3DAFA8EB2B394F9F00D71AD1 /* ViewExtensions.swift */,
);
path = Models;
sourceTree = "<group>";
@ -191,12 +195,12 @@
isa = PBXGroup;
children = (
3DAFA8ED2B3B707100D71AD1 /* Types */,
3DBAC9E82B4C891C005F8764 /* Person */,
3D13F95E2B375DB800E91913 /* ItemView.swift */,
3D13F9602B37637500E91913 /* ItemMediaView.swift */,
3DAFA8E72B38AFED00D71AD1 /* ItemInfoView.swift */,
3D13F9642B37EC7A00E91913 /* ItemHeaderView.swift */,
3D3816C82B4B5648006414D7 /* ItemGenresView.swift */,
3DBAC9E12B4C31BE005F8764 /* ItemPeopleView.swift */,
);
path = Item;
sourceTree = "<group>";
@ -205,6 +209,7 @@
isa = PBXGroup;
children = (
3D13F96E2B38A32500E91913 /* StickyHeaderView.swift */,
3D58F07D2B4DB19300DB2936 /* TextRatingView.swift */,
);
path = Utility;
sourceTree = "<group>";
@ -298,6 +303,15 @@
path = Types;
sourceTree = "<group>";
};
3DBAC9E82B4C891C005F8764 /* Person */ = {
isa = PBXGroup;
children = (
3DBAC9E12B4C31BE005F8764 /* ItemPeopleView.swift */,
3DBAC9E92B4C8927005F8764 /* ItemPersonIconView.swift */,
);
path = Person;
sourceTree = "<group>";
};
3DDD67902B293B780026781E /* Dashboard */ = {
isa = PBXGroup;
children = (
@ -467,11 +481,12 @@
3DAFA8EA2B39039900D71AD1 /* JellyfinKitExtensions.swift in Sources */,
3D13F9652B37EC7A00E91913 /* ItemHeaderView.swift in Sources */,
3D3816C92B4B5648006414D7 /* ItemGenresView.swift in Sources */,
3DAFA8EC2B394F9F00D71AD1 /* ViewConditionalMethod.swift in Sources */,
3DAFA8EC2B394F9F00D71AD1 /* ViewExtensions.swift in Sources */,
3D9063CB2B279A310063DD2A /* JelApp.swift in Sources */,
3DBAC9E42B4C7404005F8764 /* UIScreenCurrent.swift in Sources */,
3D13F9692B389FA300E91913 /* ViewOffsetKey.swift in Sources */,
3D91FDCD2B2907E800919017 /* JellyfinDateFormatter.swift in Sources */,
3D58F07E2B4DB19300DB2936 /* TextRatingView.swift in Sources */,
3D91FDC92B28C62800919017 /* SignInView.swift in Sources */,
3DAFA8EF2B3B707B00D71AD1 /* ItemMovieView.swift in Sources */,
3D8AB2A82B366353005BD7D0 /* LibraryDetailView.swift in Sources */,
@ -479,6 +494,7 @@
3D13F9612B37637500E91913 /* ItemMediaView.swift in Sources */,
3D41D1FA2B2CAE0000E58234 /* ItemIconView.swift in Sources */,
3D8AB2A52B36440D005BD7D0 /* BlurHashDecode.swift in Sources */,
3DBAC9EA2B4C8927005F8764 /* ItemPersonIconView.swift in Sources */,
3DC6BA2D2B2A422300416B9F /* SettingsController.swift in Sources */,
3DAFA8E82B38AFED00D71AD1 /* ItemInfoView.swift in Sources */,
3D91FDCB2B28CA2500919017 /* SignInToServerView.swift in Sources */,

View File

@ -7,7 +7,7 @@
<key>Jel.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
<integer>1</integer>
</dict>
<key>JellyfinClient.xcscheme_^#shared#^_</key>
<dict>

View File

@ -1,5 +1,5 @@
//
// ViewConditionalMethod.swift
// ViewExtensions.swift
// Jel
//
// Created by zerocool on 12/25/23.
@ -9,10 +9,6 @@ import SwiftUI
extension View {
/// Applies the given transform if the given condition evaluates to `true`.
/// - Parameters:
/// - condition: The condition to evaluate.
/// - transform: The transform to apply to the source `View`.
/// - Returns: Either the original `View` or the modified `View` if the condition is `true`.
@ViewBuilder func `if`<Content: View>(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View {
if condition() {
transform(self)
@ -21,3 +17,15 @@ extension View {
}
}
}
extension View {
/// Applies an inverse mask to the given view
public func inverseMask<Content: View>(_ mask: Content) -> some View {
let inverseMask = mask
.foregroundStyle(.black)
.background(.white)
.compositingGroup()
.luminanceToAlpha()
return self.mask(inverseMask)
}
}

View File

@ -20,6 +20,7 @@ struct ItemGenresView: View {
VStack(alignment: .leading) {
Text("Genres")
.font(.title2)
.padding(.horizontal)
ScrollView(.horizontal) {
HStack {
@ -43,18 +44,15 @@ struct ItemGenresView: View {
.clipShape(.capsule)
}
}
.padding(.horizontal)
}
.scrollIndicators(.hidden)
}
.onAppear {
Task {
let parameters = Paths.GetItemsParameters(
userID: authState.userId ?? "",
isRecursive: true,
fields: [.primaryImageAspectRatio,
.genres,
.taglines,
.overview,
.parentID],
includeItemTypes: [.movie, .series],
genres: item.genres ?? []
)

View File

@ -25,7 +25,7 @@ struct ItemHeaderView: View {
StickyHeaderView(minHeight: 400) {
ItemIconView(item: item, imageType: "Backdrop", contentMode: .fill)
.setCornerRadius(0)
.overlay(overlayGradient.opacity(0.8))
.overlay(overlayGradient)
}
HStack {

View File

@ -24,14 +24,7 @@ struct ItemInfoView: View {
Text(item.getRuntime() ?? "-:--")
}
if let officialRating = item.officialRating {
Text(officialRating)
.bold()
.padding(2)
.overlay {
RoundedRectangle(cornerSize: CGSize(width: 2, height: 2), style: .continuous)
.stroke(.gray)
}
.foregroundStyle(.gray)
TextRatingView(officialRating, fillStyle: .stroke)
}
}
}

View File

@ -1,27 +0,0 @@
//
// ItemPeopleView.swift
// Jel
//
// Created by zerocool on 1/8/24.
//
import SwiftUI
import JellyfinKit
struct ItemPeopleView: View {
var item: BaseItemDto
var body: some View {
ScrollView(.horizontal) {
HStack {
ForEach(item.people ?? []) {person in
Text(person.name ?? "---")
}
}
}
}
}
//#Preview {
// ItemPeopleView()
//}

View File

@ -21,6 +21,7 @@ struct ItemView: View {
}
}
}
.scrollIndicators(.hidden)
}
}

View File

@ -0,0 +1,37 @@
//
// ItemPeopleView.swift
// Jel
//
// Created by zerocool on 1/8/24.
//
import SwiftUI
import JellyfinKit
import NukeUI
struct ItemPeopleView: View {
var item: BaseItemDto
var body: some View {
VStack(alignment: .leading) {
Text("Cast and Crew")
.font(.title2)
.padding(.leading)
ScrollView(.horizontal) {
LazyHStack(alignment: .top) {
ForEach(item.people ?? [], id: \.iterId) {person in
ItemPersonIconView(person: person)
}
}
.padding(.horizontal)
}
.scrollIndicators(.hidden)
}
}
}
//#Preview {
// ItemPeopleView()
//}

View File

@ -0,0 +1,70 @@
//
// ItemPersonIconView.swift
// Jel
//
// Created by zerocool on 1/8/24.
//
import SwiftUI
import JellyfinKit
import NukeUI
struct ItemPersonIconPlaceholderView: View {
var body: some View {
VStack {
Image(systemName: "person")
.resizable()
.padding()
.scaledToFit()
}
}
}
struct ItemPersonIconView: View {
@StateObject var authState: AuthStateController = AuthStateController.shared
@EnvironmentObject var jellyfinClient: JellyfinClientController
var person: BaseItemPerson
@State var personImageUrl: URL?
@State var loading: Bool = true
var body: some View {
VStack() {
LazyImage(url: personImageUrl) {state in
if let image = state.image {
image
.resizable()
.clipShape(RoundedRectangle(cornerRadius: 5))
} else {
ItemPersonIconPlaceholderView()
}
}
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 170)
VStack {
Text(person.name ?? "---")
.font(.callout)
Text(person.role ?? "---")
.font(.caption)
.foregroundStyle(.gray)
}
.frame(width: 100)
}
// .redacted(reason: loading ? .placeholder : [])
.onAppear {
Task {
let request = Paths.getItemImage(itemID: person.id ?? "", imageType: "Primary")
let serverUrl = jellyfinClient.getUrl()
personImageUrl = serverUrl?.appending(path: request.url?.absoluteString ?? "")
// loading = false
}
}
}
}
//#Preview {
// ItemPersonView()
//}

View File

@ -23,7 +23,7 @@ struct ItemMovieView: View {
.onChange(of: geo.frame(in: .global).minY) {
let minY = geo.frame(in: .global).minY
pageScrolled = minY < 0
pageScrolled = minY < -100
}
}
}
@ -32,10 +32,8 @@ struct ItemMovieView: View {
.padding()
ItemGenresView(item: item)
.padding()
ItemPeopleView(item: item)
.padding()
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(item.name ?? "Untitled")

View File

@ -22,7 +22,7 @@ struct ItemIconView: View {
@State var imageUrl: URL?
@State var contentMode: ContentMode = .fit
@State var placeHolder: AnyView?
var placeHolder: AnyView?
var shouldShowCaption: Bool = false
var imageCornerRadius: CGFloat = 5

View File

@ -83,13 +83,7 @@ struct LibraryDetailView: View {
Task {
let params = Paths.GetItemsParameters(
userID: authState.userId,
parentID: library.id,
fields: [.primaryImageAspectRatio,
.genres,
.taglines,
.overview,
.parentID,
.people]
parentID: library.id
)
let request = Paths.getItems(parameters: params)

View File

@ -0,0 +1,58 @@
//
// TextRatingView.swift
// Jel
//
// Created by zerocool on 1/9/24.
//
import SwiftUI
enum TextRatingViewStyle {
case stroke
case fill
}
struct TextRatingView: View {
var text: String
var fillStyle: TextRatingViewStyle
init(_ text: String, fillStyle: TextRatingViewStyle = .stroke) {
self.text = text
self.fillStyle = fillStyle
}
var body: some View {
switch (fillStyle) {
case .stroke:
Text(text)
.font(.caption)
.bold()
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
.overlay {
RoundedRectangle(cornerRadius: 2, style: .continuous)
.stroke(.gray, lineWidth: 1.5)
}
.foregroundStyle(.gray)
case .fill:
Text(text)
.font(.caption)
.bold()
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
.hidden()
.background {
Color(.gray)
.clipShape(RoundedRectangle(cornerRadius: 2, style: .continuous))
.inverseMask(
Text(text)
.font(.caption)
.bold()
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
)
}
}
}
}
//#Preview {
// TextRatingView()
//}