refactor filters system

add LibraryFilterViewModel
some func name lowercased
This commit is contained in:
PangMo5 2021-06-19 07:16:17 +09:00
parent f8a70051ac
commit 39a5b6a2e7
17 changed files with 257 additions and 102 deletions

View File

@ -131,6 +131,8 @@
62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */; };
62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */; };
62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */; };
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */; };
62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */; };
62EC3527267665D8000E9F2D /* MobileVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; };
62EC3528267665D8000E9F2D /* MobileVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
@ -286,6 +288,7 @@
62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeItemViewModel.swift; sourceTree = "<group>"; };
62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeasonItemViewModel.swift; sourceTree = "<group>"; };
62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeriesItemViewModel.swift; sourceTree = "<group>"; };
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterViewModel.swift; sourceTree = "<group>"; };
62EC352B26766675000E9F2D /* ServerEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerEnvironment.swift; sourceTree = "<group>"; };
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
@ -354,6 +357,7 @@
62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */,
62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */,
62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */,
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
@ -730,6 +734,7 @@
62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */,
62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */,
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */,
62E632E7267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
@ -800,6 +805,7 @@
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */,
62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */,
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */,
6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */,

View File

@ -5,75 +5,80 @@
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/
import SwiftUI
import JellyfinAPI
import SwiftUI
struct LibraryFilterView: View {
@Binding var filter: LibraryFilters
@Environment(\.presentationMode)
var presentationMode
@Binding
var filters: LibraryFilters
@StateObject
var viewModel: LibraryFilterViewModel
init(filters: Binding<LibraryFilters>, enabledFilterType: [FilterType]) {
_filters = filters
_viewModel = StateObject(wrappedValue: .init(filters: filters.wrappedValue, enabledFilterType: enabledFilterType))
}
var body: some View {
EmptyView()
/*
NavigationView {
LoadingView(isShowing: $isLoading) {
ZStack {
Form {
Toggle("Only show unplayed items", isOn: $onlyUnplayed)
.onChange(of: onlyUnplayed) { value in
if value {
filter.filterTypes.append(.isUnplayed)
} else {
filter.filterTypes.removeAll { $0 == .isUnplayed }
}
}
MultiSelector(label: "Genres",
options: allGenres,
optionToString: { $0.name },
selected: $selectedGenres)
.onChange(of: selectedGenres) { genres in
filter.genres = genres.map(\.id)
}
MultiSelector(label: "Parental Ratings",
options: allRatings,
optionToString: { $0.name },
selected: $selectedRatings)
.onChange(of: selectedRatings) { ratings in
filter.officialRatings = ratings.map(\.id)
}
Section(header: Text("Sort settings")) {
Picker("Sort by", selection: $sortBySelection) {
Text("Name").tag("SortName")
Text("Date Added").tag("DateCreated")
Text("Date Played").tag("DatePlayed")
Text("Date Released").tag("PremiereDate")
Text("Runtime").tag("Runtime")
}.onChange(of: sortBySelection) { value in
guard let sort = SortType(rawValue: value) else { return }
filter.sort = sort
}
Picker("Sort order", selection: $sortOrder) {
Text("Ascending").tag("Ascending")
Text("Descending").tag("Descending")
}.onChange(of: sortOrder) { order in
guard let asc = ASC(rawValue: order) else { return }
filter.asc = asc
}
if viewModel.enabledFilterType.contains(.genre) {
MultiSelector(label: "Genres",
options: viewModel.allGenres,
optionToString: { $0.name ?? "" },
selected: $viewModel.modifyedFilters.withGenres)
}
if viewModel.enabledFilterType.contains(.filter) {
MultiSelector(label: "Filters",
options: viewModel.allItemFilters,
optionToString: { $0.localized },
selected: $viewModel.modifyedFilters.filters)
}
if viewModel.enabledFilterType.contains(.tag) {
MultiSelector(label: "Tags",
options: viewModel.allTags,
optionToString: { $0 },
selected: $viewModel.modifyedFilters.tags)
}
if viewModel.enabledFilterType.contains(.sortBy) {
MultiSelector(label: "Sort by",
options: viewModel.allSortBys,
optionToString: { $0.localized },
selected: $viewModel.modifyedFilters.sortBy)
}
if viewModel.enabledFilterType.contains(.sortOrder) {
MultiSelector(label: "Sort Order",
options: viewModel.allSortOrders,
optionToString: { $0.localized },
selected: $viewModel.modifyedFilters.sortOrder)
}
}
}.onAppear(perform: onAppear)
.navigationBarTitle("Filters", displayMode: .inline)
.toolbar {
ToolbarItemGroup(placement: .navigationBarLeading) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
HStack {
Text("Back").font(.callout)
}
}
if viewModel.isLoading {
ProgressView()
}
}
.navigationBarTitle("Filters", displayMode: .inline)
.toolbar {
ToolbarItemGroup(placement: .navigationBarLeading) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: "xmark")
}
}
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
self.filters = viewModel.modifyedFilters
presentationMode.wrappedValue.dismiss()
} label: {
Text("Apply")
}
}
}
}
*/
}
}

View File

@ -20,6 +20,8 @@ struct LibraryView: View {
@State
var isShowingSearchView = false
@State
var isShowingFilterView = false
@State
private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125)
@ -109,6 +111,11 @@ struct LibraryView: View {
Image(systemName: "chevron.right")
}
}
Button(action: {
isShowingFilterView = true
}) {
Image(systemName: "line.horizontal.3.decrease.circle")
}
Button(action: {
isShowingSearchView = true
}) {
@ -116,6 +123,9 @@ struct LibraryView: View {
}
}
}
.sheet(isPresented: $isShowingFilterView) {
LibraryFilterView(filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType)
}
.background(
NavigationLink(destination: LibrarySearchView(viewModel: .init(parentID: viewModel.parentID)),
isActive: $isShowingSearchView) {

View File

@ -7,44 +7,44 @@
import SwiftUI
private struct MultiSelectionView<Selectable: Identifiable & Hashable>: View {
private struct MultiSelectionView<Selectable: Hashable>: View {
let options: [Selectable]
let optionToString: (Selectable) -> String
let label: String
@Binding var selected: Set<Selectable>
@Binding var selected: Array<Selectable>
var body: some View {
List {
ForEach(options) { selectable in
ForEach(options, id: \.self) { selectable in
Button(action: { toggleSelection(selectable: selectable) }) {
HStack {
Text(optionToString(selectable)).foregroundColor(Color.primary)
Spacer()
if selected.contains { $0.id == selectable.id } {
if selected.contains { $0 == selectable } {
Image(systemName: "checkmark").foregroundColor(.accentColor)
}
}
}.tag(selectable.id)
}.tag(selectable)
}
}.listStyle(GroupedListStyle())
}
private func toggleSelection(selectable: Selectable) {
if let existingIndex = selected.firstIndex(where: { $0.id == selectable.id }) {
if let existingIndex = selected.firstIndex(where: { $0 == selectable }) {
selected.remove(at: existingIndex)
} else {
selected.insert(selectable)
selected.append(selectable)
}
}
}
struct MultiSelector<Selectable: Identifiable & Hashable>: View {
struct MultiSelector<Selectable: Hashable>: View {
let label: String
let options: [Selectable]
let optionToString: (Selectable) -> String
var selected: Binding<Set<Selectable>>
var selected: Binding<Array<Selectable>>
private var formattedSelectedListString: String {
ListFormatter.localizedString(byJoining: selected.wrappedValue.map { optionToString($0) })

View File

@ -5,15 +5,16 @@
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/
import Foundation
import Combine
import Foundation
import JellyfinAPI
struct LibraryFilters: Codable, Hashable {
var filters: [ItemFilter] = []
var sortOrder: [APISortOrder] = [.descending]
var withGenres: [NameGuidPair] = []
var sortBy: [String] = ["SortName"]
var tags: [String] = []
var sortBy: [SortBy] = [.name]
}
public enum SortBy: String, Codable, CaseIterable {
@ -22,3 +23,54 @@ public enum SortBy: String, Codable, CaseIterable {
case name = "SortName"
case dateAdded = "DateCreated"
}
extension SortBy {
var localized: String {
switch self {
case .productionYear:
return "Production year"
case .premiereDate:
return "Premiere date"
case .name:
return "Name"
case .dateAdded:
return "Date created"
}
}
}
extension ItemFilter {
var localized: String {
switch self {
case .isFolder:
return "Is folder"
case .isNotFolder:
return "Is not folder"
case .isUnplayed:
return "Is unplayed"
case .isPlayed:
return "Is played"
case .isFavorite:
return "Is favorite"
case .isResumable:
return "Is resumable"
case .likes:
return "Likes"
case .dislikes:
return "Dislikes"
case .isFavoriteOrLikes:
return "Is favorite or likes"
}
}
}
extension APISortOrder {
var localized: String {
switch self {
case .ascending:
return "Ascending"
case .descending:
return "Descending"
}
}
}

View File

@ -37,7 +37,7 @@ final class ConnectToServerViewModel: ViewModel {
if ServerEnvironment.current.server != nil {
UserAPI.getPublicUsers()
.sink(receiveCompletion: { completion in
self.HandleAPIRequestCompletion(completion: completion)
self.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in
self.publicUsers = response
self.isConnectedServer = true
@ -74,7 +74,7 @@ final class ConnectToServerViewModel: ViewModel {
func login() {
SessionManager.current.login(username: username, password: password)
.sink(receiveCompletion: { completion in
self.HandleAPIRequestCompletion(completion: completion)
self.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { _ in
})

View File

@ -33,7 +33,7 @@ final class EpisodeItemViewModel: ViewModel {
PlaystateAPI.markUnplayedItem(userId: SessionManager.current.user.user_id!, itemId: id)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] _ in
self?.isWatched = false
})
@ -42,7 +42,7 @@ final class EpisodeItemViewModel: ViewModel {
PlaystateAPI.markPlayedItem(userId: SessionManager.current.user.user_id!, itemId: id)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] _ in
self?.isWatched = true
})
@ -56,7 +56,7 @@ final class EpisodeItemViewModel: ViewModel {
UserLibraryAPI.unmarkFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: id)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] _ in
self?.isFavorited = false
})
@ -65,7 +65,7 @@ final class EpisodeItemViewModel: ViewModel {
UserLibraryAPI.markFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: id)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] _ in
self?.isFavorited = true
})

View File

@ -24,7 +24,7 @@ final class HomeViewModel: ViewModel {
var nextUpItems = [BaseItemDto]()
// temp
var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: ["DateCreated"])
var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded])
override init() {
super.init()
@ -36,7 +36,7 @@ final class HomeViewModel: ViewModel {
UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!)
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.HandleAPIRequestCompletion(completion: completion)
self.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in
response.items!.forEach { item in
if item.collectionType == "movies" || item.collectionType == "tvshows" {
@ -47,7 +47,7 @@ final class HomeViewModel: ViewModel {
UserAPI.getCurrentUser()
.trackActivity(self.loading)
.sink(receiveCompletion: { completion in
self.HandleAPIRequestCompletion(completion: completion)
self.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in
self.libraries.forEach { library in
if !(response.configuration?.latestItemsExcludes?.contains(library.id!))! {
@ -64,7 +64,7 @@ final class HomeViewModel: ViewModel {
mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.HandleAPIRequestCompletion(completion: completion)
self.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in
self.resumeItems = response.items ?? []
})
@ -74,7 +74,7 @@ final class HomeViewModel: ViewModel {
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.HandleAPIRequestCompletion(completion: completion)
self.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in
self.nextUpItems = response.items ?? []
})

View File

@ -37,7 +37,7 @@ final class LatestMediaViewModel: ViewModel {
enableUserData: true, limit: 12)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] response in
self?.items = response
})

View File

@ -0,0 +1,61 @@
//
/*
* SwiftFin is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/
import Combine
import Foundation
import JellyfinAPI
enum FilterType {
case tag
case genre
case sortOrder
case sortBy
case filter
}
final class LibraryFilterViewModel: ViewModel {
@Published
var modifyedFilters = LibraryFilters()
@Published
var allGenres = [NameGuidPair]()
@Published
var allTags = [String]()
@Published
var allSortOrders = APISortOrder.allCases
@Published
var allSortBys = SortBy.allCases
@Published
var allItemFilters = ItemFilter.allCases
@Published
var enabledFilterType: [FilterType]
init(filters: LibraryFilters? = nil,
enabledFilterType: [FilterType] = [.tag, .genre, .sortBy, .sortOrder, .filter]) {
self.enabledFilterType = enabledFilterType
super.init()
if let filters = filters {
self.modifyedFilters = filters
}
refresh()
}
func refresh() {
FilterAPI.getQueryFilters(userId: SessionManager.current.user.user_id!)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] quertFilters in
guard let self = self else { return }
self.allGenres = quertFilters.genres ?? []
self.allTags = quertFilters.tags ?? []
})
.store(in: &cancellables)
}
}

View File

@ -29,7 +29,7 @@ final class LibraryListViewModel: ViewModel {
UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!)
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.HandleAPIRequestCompletion(completion: completion)
self.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in
self.libraries.append(contentsOf: response.items ?? [])
})

View File

@ -36,7 +36,7 @@ final class LibrarySearchViewModel: ViewModel {
includeItemTypes: ["Movie", "Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] response in
self?.items = response.items ?? []
})

View File

@ -30,13 +30,22 @@ final class LibraryViewModel: ViewModel {
var isCanPreviousPaging = false
// temp
@Published
var filters: LibraryFilters
var enabledFilterType: [FilterType] {
if genre == nil {
return [.tag, .genre, .sortBy, .sortOrder, .filter]
} else {
return [.tag, .sortBy, .sortOrder, .filter]
}
}
init(parentID: String? = nil,
person: BaseItemPerson? = nil,
genre: NameGuidPair? = nil,
studio: NameGuidPair? = nil,
filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: ["SortName"]))
filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name]))
{
self.parentID = parentID
self.person = person
@ -45,18 +54,30 @@ final class LibraryViewModel: ViewModel {
self.filters = filters
super.init()
refresh()
$filters
.sink(receiveValue: refresh(with:))
.store(in: &cancellables)
}
func refresh() {
func refresh(with filters: LibraryFilters) {
let personIDs: [String] = [person].compactMap(\.?.id)
let studioIDs: [String] = [studio].compactMap(\.?.id)
let genreIDs: [String] = [genre].compactMap(\.?.id)
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: ["Movie", "Series"], filters: filters.filters, sortBy: filters.sortBy, enableUserData: true, personIds: personIDs, studioIds: studioIDs, genreIds: genreIDs, enableImages: true)
let genreIDs: [String]
if filters.withGenres.isEmpty {
genreIDs = [genre].compactMap(\.?.id)
} else {
genreIDs = filters.withGenres.compactMap(\.id)
}
let sortBy = filters.sortBy.map(\.rawValue)
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true,
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,
enableUserData: true, personIds: personIDs, studioIds: studioIDs, genreIds: genreIDs, enableImages: true)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] response in
guard let self = self else { return }
let totalPages = ceil(Double(response.totalRecordCount ?? 0) / 100.0)
@ -67,14 +88,14 @@ final class LibraryViewModel: ViewModel {
})
.store(in: &cancellables)
}
func requestNextPage() {
currentPage += 1
refresh()
refresh(with: filters)
}
func requestPreviousPage() {
currentPage -= 1
refresh()
refresh(with: filters)
}
}

View File

@ -33,7 +33,7 @@ final class MovieItemViewModel: ViewModel {
PlaystateAPI.markUnplayedItem(userId: SessionManager.current.user.user_id!, itemId: id)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] _ in
self?.isWatched = false
})
@ -42,7 +42,7 @@ final class MovieItemViewModel: ViewModel {
PlaystateAPI.markPlayedItem(userId: SessionManager.current.user.user_id!, itemId: id)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] _ in
self?.isWatched = true
})
@ -56,7 +56,7 @@ final class MovieItemViewModel: ViewModel {
UserLibraryAPI.unmarkFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: id)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] _ in
self?.isFavorited = false
})
@ -65,7 +65,7 @@ final class MovieItemViewModel: ViewModel {
UserLibraryAPI.markFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: id)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] _ in
self?.isFavorited = true
})

View File

@ -31,7 +31,7 @@ final class SeasonItemViewModel: ViewModel {
seasonId: item.id ?? "")
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] response in
self?.episodes = response.items ?? []
})

View File

@ -29,7 +29,7 @@ final class SeriesItemViewModel: ViewModel {
TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.HandleAPIRequestCompletion(completion: completion)
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] response in
self?.seasons = response.items ?? []
})

View File

@ -32,7 +32,7 @@ class ViewModel: ObservableObject {
loading.loading.assign(to: \.isLoading, on: self).store(in: &cancellables)
}
func HandleAPIRequestCompletion(completion: Subscribers.Completion<Error>) {
func handleAPIRequestCompletion(completion: Subscribers.Completion<Error>) {
switch completion {
case .finished:
break