Consolidate item views.

Add watched badges
Add remaining episode badges
Add favorite badges.
Fix genres to only show genres from current library.
Show watched episodes in series view.
Add progress bar to currently watching items in library.
Fix showing favorites.
This commit is contained in:
Aiden Vigue 2021-06-26 15:04:57 -04:00
parent 3a2328fbee
commit 19c5e3e4c8
No known key found for this signature in database
GPG Key ID: B9A09843AB079D5B
12 changed files with 92 additions and 148 deletions

View File

@ -101,6 +101,7 @@
53EC6E25267EB10F006DD26A /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 53EC6E24267EB10F006DD26A /* SwiftyJSON */; };
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* LibrarySearchView.swift */; };
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */; };
53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F866432687A45F00DCD1D7 /* PortraitItemView.swift */; };
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */; };
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6213388F265F83A900A81A2A /* LibraryListView.swift */; };
621338932660107500A81A2A /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; };
@ -285,6 +286,7 @@
53E4E648263F725B00F67C6B /* MultiSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelectorView.swift; sourceTree = "<group>"; };
53EE24E5265060780068F029 /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = "<group>"; };
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerSettingsView.swift; sourceTree = "<group>"; };
53F866432687A45F00DCD1D7 /* PortraitItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemView.swift; sourceTree = "<group>"; };
53FF7F29263CF3F500585C35 /* LatestMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaView.swift; sourceTree = "<group>"; };
6213388F265F83A900A81A2A /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = "<group>"; };
621338922660107500A81A2A /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = "<group>"; };
@ -404,23 +406,23 @@
532175392671BCED005491E6 /* ViewModels */ = {
isa = PBXGroup;
children = (
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */,
625CB5692678B71200530A6E /* SplashViewModel.swift */,
625CB5722678C32A00530A6E /* HomeViewModel.swift */,
625CB5742678C33500530A6E /* LibraryListViewModel.swift */,
625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */,
625CB57B2678CE1000530A6E /* ViewModel.swift */,
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */,
62E632F2267D54030063E547 /* DetailItemViewModel.swift */,
62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */,
625CB5722678C32A00530A6E /* HomeViewModel.swift */,
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */,
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */,
625CB5742678C33500530A6E /* LibraryListViewModel.swift */,
62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */,
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */,
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */,
62E632E2267D3BA60063E547 /* MovieItemViewModel.swift */,
62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */,
62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */,
62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */,
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */,
62E632F2267D54030063E547 /* DetailItemViewModel.swift */,
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */,
625CB5692678B71200530A6E /* SplashViewModel.swift */,
09389CC626819B4500AE350E /* VideoPlayerModel.swift */,
625CB57B2678CE1000530A6E /* ViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
@ -494,10 +496,10 @@
53D5E3DB264B47EE00BADDC8 /* Frameworks */,
5377CBF3263B596A003A4E83 /* JellyfinPlayer */,
535870612669D21600D05A09 /* JellyfinPlayer tvOS */,
C78797A232E2B8774099D1E9 /* Pods */,
5377CBF2263B596A003A4E83 /* Products */,
535870752669D60C00D05A09 /* Shared */,
628B95252670CABD0091AF3B /* WidgetExtension */,
C78797A232E2B8774099D1E9 /* Pods */,
);
sourceTree = "<group>";
};
@ -514,6 +516,7 @@
5377CBF3263B596A003A4E83 /* JellyfinPlayer */ = {
isa = PBXGroup;
children = (
53F866422687A45400DCD1D7 /* Components */,
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */,
5377CBF8263B596B003A4E83 /* Assets.xcassets */,
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
@ -586,6 +589,14 @@
name = Frameworks;
sourceTree = "<group>";
};
53F866422687A45400DCD1D7 /* Components */ = {
isa = PBXGroup;
children = (
53F866432687A45F00DCD1D7 /* PortraitItemView.swift */,
);
path = Components;
sourceTree = "<group>";
};
621338912660106C00A81A2A /* Extensions */ = {
isa = PBXGroup;
children = (
@ -992,6 +1003,7 @@
62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */,
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */,
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */,
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,

View File

@ -11,12 +11,14 @@ import SwiftUI
struct LibraryFilterView: View {
@Environment(\.presentationMode) var presentationMode
@Binding var filters: LibraryFilters
var parentId: String = ""
@StateObject var viewModel: LibraryFilterViewModel
init(filters: Binding<LibraryFilters>, enabledFilterType: [FilterType]) {
init(filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String) {
_filters = filters
_viewModel = StateObject(wrappedValue: .init(filters: filters.wrappedValue, enabledFilterType: enabledFilterType))
self.parentId = parentId
_viewModel = StateObject(wrappedValue: .init(filters: filters.wrappedValue, enabledFilterType: enabledFilterType, parentId: parentId))
}
var body: some View {

View File

@ -56,32 +56,38 @@ struct LibraryListView: View {
.shadow(radius: 5)
.padding(.bottom, 15)
ForEach(viewModel.libraries, id: \.id) { library in
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
NavigationLink(destination: LazyView {
LibraryView(viewModel: .init(parentID: library.id), title: library.name ?? "")
}) {
ZStack {
ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
.opacity(0.4)
HStack {
Spacer()
Text(library.name ?? "")
.foregroundColor(.white)
.font(.title2)
.fontWeight(.semibold)
Spacer()
}.padding(32)
}.background(Color.black)
.frame(minWidth: 100, maxWidth: .infinity)
.frame(height: 72)
if(!viewModel.isLoading) {
ForEach(viewModel.libraries, id: \.id) { library in
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
NavigationLink(destination: LazyView {
LibraryView(viewModel: .init(parentID: library.id), title: library.name ?? "")
}) {
ZStack {
ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
.opacity(0.4)
HStack {
Spacer()
VStack() {
Text(library.name ?? "")
.foregroundColor(.white)
.font(.title2)
.fontWeight(.semibold)
}
Spacer()
}.padding(32)
}.background(Color.black)
.frame(minWidth: 100, maxWidth: .infinity)
.frame(height: 100)
}
.cornerRadius(10)
.shadow(radius: 5)
.padding(.bottom, 5)
} else {
EmptyView()
}
.cornerRadius(10)
.shadow(radius: 5)
.padding(.bottom, 5)
} else {
EmptyView()
}
} else {
ProgressView()
}
}.padding(.leading, 16)
.padding(.trailing, 16)

View File

@ -30,36 +30,7 @@ struct LibrarySearchView: View {
Spacer().frame(height: 16)
LazyVGrid(columns: tracks) {
ForEach(viewModel.items, id: \.id) { item in
NavigationLink(destination: ItemView(item: item)) {
VStack(alignment: .leading) {
ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash())
.frame(width: 100, height: 150)
.cornerRadius(10)
.overlay(
ZStack {
if item.userData!.played ?? false {
Image(systemName: "circle.fill")
.foregroundColor(.white)
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(.systemBlue))
}
}.padding(2)
.opacity(1), alignment: .topTrailing).opacity(1)
Text(item.name ?? "")
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.primary)
.lineLimit(1)
if item.productionYear != nil {
Text(String(item.productionYear!))
.foregroundColor(.secondary)
.font(.caption)
.fontWeight(.medium)
} else {
Text(item.type ?? "")
}
}.frame(width: 100)
}
PortraitItemView(item: item)
}
Spacer().frame(height: 16)
}

View File

@ -34,38 +34,8 @@ struct LibraryView: View {
Spacer().frame(height: 16)
LazyVGrid(columns: tracks) {
ForEach(viewModel.items, id: \.id) { item in
NavigationLink(destination: ItemView(item: item)) {
VStack(alignment: .leading) {
ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash())
.frame(width: 100, height: 150)
.cornerRadius(10)
.overlay(
ZStack {
if item.userData!.played ?? false {
Image(systemName: "circle.fill")
.foregroundColor(.white)
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(.systemBlue))
}
}.padding(2)
.opacity(1), alignment: .topTrailing).opacity(1)
Text(item.name ?? "")
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.primary)
.lineLimit(1)
if item.productionYear != nil {
Text(String(item.productionYear!))
.foregroundColor(.secondary)
.font(.caption)
.fontWeight(.medium)
} else {
Text(item.type ?? "")
.foregroundColor(.secondary)
.font(.caption)
.fontWeight(.medium)
}
}.frame(width: 100)
if(item.type != "Folder") {
PortraitItemView(item: item)
}
}
}.onRotate { _ in
@ -82,8 +52,8 @@ struct LibraryView: View {
.font(.system(size: 25))
}.disabled(!viewModel.hasPreviousPage)
Text("Page \(String(viewModel.currentPage + 1)) of \(String(viewModel.totalPages))")
.font(.headline)
.fontWeight(.semibold)
.font(.subheadline)
.fontWeight(.medium)
Button {
viewModel.requestNextPage()
} label: {
@ -109,14 +79,14 @@ struct LibraryView: View {
viewModel.requestPreviousPage()
} label: {
Image(systemName: "chevron.left")
}
}.disabled(viewModel.isLoading)
}
if viewModel.hasNextPage {
Button {
viewModel.requestNextPage()
} label: {
Image(systemName: "chevron.right")
}
}.disabled(viewModel.isLoading)
}
Label("Icon One", systemImage: "line.horizontal.3.decrease.circle")
.foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange))
@ -131,7 +101,7 @@ struct LibraryView: View {
}
}
.sheet(isPresented: $isShowingFilterView) {
LibraryFilterView(filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType)
LibraryFilterView(filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType, parentId: viewModel.parentID ?? "")
}
.background(
NavigationLink(destination: LibrarySearchView(viewModel: .init(parentID: viewModel.parentID)),

View File

@ -22,24 +22,7 @@ struct NextUpView: View {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(items, id: \.id) { item in
NavigationLink(destination: LazyView { ItemView(item: item) }) {
VStack(alignment: .leading) {
ImageView(src: item.getSeriesPrimaryImage(maxWidth: 100), bh: item.getSeriesPrimaryImageBlurHash())
.frame(width: 100, height: 150)
.cornerRadius(10)
.shadow(radius: 4)
Text(item.seriesName!)
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.primary)
.lineLimit(1)
Text("S\(item.parentIndexNumber ?? 0):E\(item.indexNumber ?? 0)")
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.lineLimit(1)
}.frame(width: 100)
}
PortraitItemView(item: item)
}.padding(.trailing, 16)
}
.padding(.leading, 20)

View File

@ -73,12 +73,27 @@ struct SeasonItemView: View {
Rectangle()
.fill(Color(red: 172/255, green: 92/255, blue: 195/255))
.mask(ProgressBar())
.frame(width: CGFloat(episode.userData!.playedPercentage ?? 0 * 1.5), height: 7)
.frame(width: CGFloat(episode.userData?.playedPercentage ?? 0 * 1.5), height: 7)
.padding(0), alignment: .bottomLeading
)
.overlay(
ZStack {
if episode.userData!.played ?? false {
if episode.userData?.isFavorite ?? false {
Image(systemName: "circle.fill")
.foregroundColor(.white)
.opacity(0.6)
Image(systemName: "heart.fill")
.foregroundColor(Color(.systemRed))
.font(.system(size: 10))
}
}
.padding(.leading, 2)
.padding(.bottom, episode.userData?.playedPercentage == nil ? 2 : 9)
.opacity(1)
, alignment: .bottomLeading)
.overlay(
ZStack {
if episode.userData?.played ?? false {
Image(systemName: "circle.fill")
.foregroundColor(.white)
Image(systemName: "checkmark.circle.fill")

View File

@ -24,25 +24,7 @@ struct SeriesItemView: View {
Spacer().frame(height: 16)
LazyVGrid(columns: tracks) {
ForEach(viewModel.seasons, id: \.id) { season in
NavigationLink(destination: ItemView(item: season)) {
VStack(alignment: .leading) {
ImageView(src: season.getPrimaryImage(maxWidth: 100), bh: season.getPrimaryImageBlurHash())
.frame(width: 100, height: 150)
.cornerRadius(10)
.shadow(radius: 5)
Text(season.name ?? "")
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.primary)
.lineLimit(1)
if season.productionYear != nil {
Text(String(season.productionYear!))
.foregroundColor(.secondary)
.font(.caption)
.fontWeight(.medium)
}
}.frame(width: 100)
}
PortraitItemView(item: season)
}
Spacer().frame(height: 2)
}.onRotate { _ in

View File

@ -111,7 +111,7 @@ extension BaseItemDto {
func getItemRuntime() -> String {
let timeHMSFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .brief
formatter.unitsStyle = .abbreviated
formatter.allowedUnits = [.hour, .minute]
return formatter
}()

View File

@ -39,6 +39,8 @@ final class LibraryFilterViewModel: ViewModel {
var selectedSortOrder: APISortOrder = .descending
@Published
var selectedSortBy: SortBy = .name
var parentId: String = ""
func updateModifiedFilter() {
modifiedFilters.sortOrder = [selectedSortOrder]
@ -50,10 +52,11 @@ final class LibraryFilterViewModel: ViewModel {
}
init(filters: LibraryFilters? = nil,
enabledFilterType: [FilterType] = [.tag, .genre, .sortBy, .sortOrder, .filter]) {
enabledFilterType: [FilterType] = [.tag, .genre, .sortBy, .sortOrder, .filter], parentId: String) {
self.enabledFilterType = enabledFilterType
self.selectedSortBy = filters!.sortBy.first!
self.selectedSortOrder = filters!.sortOrder.first!
self.parentId = parentId
super.init()
if let filters = filters {
@ -63,7 +66,7 @@ final class LibraryFilterViewModel: ViewModel {
}
func requestQueryFilters() {
FilterAPI.getQueryFilters(userId: SessionManager.current.user.user_id!)
FilterAPI.getQueryFilters(userId: SessionManager.current.user.user_id!, parentId: self.parentId)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestCompletion(completion: completion)

View File

@ -69,10 +69,10 @@ final class LibraryViewModel: ViewModel {
}
let sortBy = filters.sortBy.map(\.rawValue)
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true,
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: filters.filters.contains(.isFavorite) ? true : false,
searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
includeItemTypes: ["Movie", "Series"], filters: filters.filters, sortBy: sortBy, tags: filters.tags,
filters: filters.filters, sortBy: sortBy, tags: filters.tags,
enableUserData: true, personIds: personIDs, studioIds: studioIDs, genreIds: genreIDs, enableImages: true)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in

View File

@ -26,7 +26,7 @@ final class SeriesItemViewModel: ViewModel {
}
func requestSeasons() {
TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
TvShowsAPI.getSeasons(seriesId: item.id ?? "", userId: SessionManager.current.user.user_id!, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], enableUserData: true)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestCompletion(completion: completion)