Add home screen refresh
This commit is contained in:
parent
ccc09d5718
commit
54482f9ed6
|
@ -328,6 +328,9 @@
|
||||||
E1D4BF8C2719F39F00A11E64 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF802719D22800A11E64 /* AppAppearance.swift */; };
|
E1D4BF8C2719F39F00A11E64 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF802719D22800A11E64 /* AppAppearance.swift */; };
|
||||||
E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
|
E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
|
||||||
E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */; };
|
E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */; };
|
||||||
|
E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; };
|
||||||
|
E1E48CCA271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; };
|
||||||
|
E1E48CCB271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; };
|
||||||
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
|
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
|
||||||
E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
|
E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
|
||||||
E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; };
|
E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; };
|
||||||
|
@ -576,6 +579,7 @@
|
||||||
E1D4BF862719D27100A11E64 /* Bitrates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bitrates.swift; sourceTree = "<group>"; };
|
E1D4BF862719D27100A11E64 /* Bitrates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bitrates.swift; sourceTree = "<group>"; };
|
||||||
E1D4BF892719D3D000A11E64 /* BasicAppSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsCoordinator.swift; sourceTree = "<group>"; };
|
E1D4BF892719D3D000A11E64 /* BasicAppSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsCoordinator.swift; sourceTree = "<group>"; };
|
||||||
E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = "<group>"; };
|
E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshHelper.swift; sourceTree = "<group>"; };
|
||||||
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = "<group>"; };
|
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = "<group>"; };
|
||||||
E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = "<group>"; };
|
E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = "<group>"; };
|
||||||
E1FCD09526C47118007C8DCF /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = "<group>"; };
|
E1FCD09526C47118007C8DCF /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = "<group>"; };
|
||||||
|
@ -786,6 +790,7 @@
|
||||||
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */,
|
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */,
|
||||||
E193D4DA27193CCA00900D82 /* PillStackable.swift */,
|
E193D4DA27193CCA00900D82 /* PillStackable.swift */,
|
||||||
E193D4D727193CAC00900D82 /* PortraitImageStackable.swift */,
|
E193D4D727193CAC00900D82 /* PortraitImageStackable.swift */,
|
||||||
|
E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */,
|
||||||
E1D4BF832719D25A00A11E64 /* TrackLanguage.swift */,
|
E1D4BF832719D25A00A11E64 /* TrackLanguage.swift */,
|
||||||
535870AC2669D8DD00D05A09 /* Typings.swift */,
|
535870AC2669D8DD00D05A09 /* Typings.swift */,
|
||||||
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */,
|
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */,
|
||||||
|
@ -1705,6 +1710,7 @@
|
||||||
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
|
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
|
||||||
C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */,
|
C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */,
|
||||||
531069582684E7EE00CFFDBA /* MediaInfoView.swift in Sources */,
|
531069582684E7EE00CFFDBA /* MediaInfoView.swift in Sources */,
|
||||||
|
E1E48CCA271E6D410021A2F9 /* RefreshHelper.swift in Sources */,
|
||||||
E1D4BF822719D22800A11E64 /* AppAppearance.swift in Sources */,
|
E1D4BF822719D22800A11E64 /* AppAppearance.swift in Sources */,
|
||||||
53272537268C1DBB0035FBF1 /* SeasonItemView.swift in Sources */,
|
53272537268C1DBB0035FBF1 /* SeasonItemView.swift in Sources */,
|
||||||
09389CC526814E4500AE350E /* DeviceProfileBuilder.swift in Sources */,
|
09389CC526814E4500AE350E /* DeviceProfileBuilder.swift in Sources */,
|
||||||
|
@ -1803,6 +1809,7 @@
|
||||||
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
|
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
|
||||||
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */,
|
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */,
|
||||||
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
|
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
|
||||||
|
E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */,
|
||||||
E1D4BF842719D25A00A11E64 /* TrackLanguage.swift in Sources */,
|
E1D4BF842719D25A00A11E64 /* TrackLanguage.swift in Sources */,
|
||||||
E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */,
|
E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */,
|
||||||
E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */,
|
E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */,
|
||||||
|
@ -1873,6 +1880,7 @@
|
||||||
628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */,
|
628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */,
|
||||||
E13DD3F72717E87D009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
|
E13DD3F72717E87D009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
|
||||||
E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */,
|
E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */,
|
||||||
|
E1E48CCB271E6D410021A2F9 /* RefreshHelper.swift in Sources */,
|
||||||
6220D0AF26D5EABE00B8E046 /* ViewExtensions.swift in Sources */,
|
6220D0AF26D5EABE00B8E046 /* ViewExtensions.swift in Sources */,
|
||||||
E13DD3D7271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */,
|
E13DD3D7271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */,
|
||||||
E1FCD09926C4F358007C8DCF /* NetworkError.swift in Sources */,
|
E1FCD09926C4F358007C8DCF /* NetworkError.swift in Sources */,
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Introspect
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct HomeView: View {
|
struct HomeView: View {
|
||||||
|
@ -15,6 +16,8 @@ struct HomeView: View {
|
||||||
@EnvironmentObject var homeRouter: HomeCoordinator.Router
|
@EnvironmentObject var homeRouter: HomeCoordinator.Router
|
||||||
@StateObject var viewModel = HomeViewModel()
|
@StateObject var viewModel = HomeViewModel()
|
||||||
|
|
||||||
|
private let refreshHelper = RefreshHelper()
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var innerBody: some View {
|
var innerBody: some View {
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
|
@ -28,19 +31,18 @@ struct HomeView: View {
|
||||||
if !viewModel.nextUpItems.isEmpty {
|
if !viewModel.nextUpItems.isEmpty {
|
||||||
NextUpView(items: viewModel.nextUpItems)
|
NextUpView(items: viewModel.nextUpItems)
|
||||||
}
|
}
|
||||||
if !viewModel.librariesShowRecentlyAddedIDs.isEmpty {
|
|
||||||
ForEach(viewModel.librariesShowRecentlyAddedIDs, id: \.self) { libraryID in
|
ForEach(viewModel.libraries, id: \.self) { library in
|
||||||
let library = viewModel.libraries.first(where: { $0.id == libraryID })
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Latest \(library?.name ?? "")")
|
Text("Latest \(library.name ?? "")")
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button {
|
Button {
|
||||||
homeRouter
|
homeRouter
|
||||||
.route(to: \.library, (viewModel: .init(parentID: libraryID,
|
.route(to: \.library, (viewModel: .init(parentID: library.id!,
|
||||||
filters: viewModel.recentFilterSet),
|
filters: viewModel.recentFilterSet),
|
||||||
title: library?.name ?? ""))
|
title: library.name ?? ""))
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text("See All").font(.subheadline).fontWeight(.bold)
|
Text("See All").font(.subheadline).fontWeight(.bold)
|
||||||
|
@ -49,12 +51,20 @@ struct HomeView: View {
|
||||||
}
|
}
|
||||||
}.padding(.leading, 16)
|
}.padding(.leading, 16)
|
||||||
.padding(.trailing, 16)
|
.padding(.trailing, 16)
|
||||||
LatestMediaView(viewModel: .init(libraryID: libraryID))
|
LatestMediaView(viewModel: .init(libraryID: library.id!))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.bottom, UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30)
|
.padding(.bottom, UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30)
|
||||||
}
|
}
|
||||||
|
.introspectScrollView { scrollView in
|
||||||
|
let control = UIRefreshControl()
|
||||||
|
|
||||||
|
refreshHelper.refreshControl = control
|
||||||
|
refreshHelper.refreshAction = viewModel.refresh
|
||||||
|
|
||||||
|
control.addTarget(refreshHelper, action: #selector(RefreshHelper.didRefresh), for: .valueChanged)
|
||||||
|
scrollView.refreshControl = control
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
* 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 UIKit
|
||||||
|
|
||||||
|
// A more general derivative of
|
||||||
|
// https://stackoverflow.com/questions/65812080/introspect-library-uirefreshcontrol-with-swiftui-not-working
|
||||||
|
class RefreshHelper {
|
||||||
|
var refreshControl: UIRefreshControl?
|
||||||
|
var refreshAction: (() -> Void)?
|
||||||
|
|
||||||
|
@objc func didRefresh() {
|
||||||
|
guard let refreshControl = refreshControl else { return }
|
||||||
|
refreshAction?()
|
||||||
|
refreshControl.endRefreshing()
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,10 +14,10 @@ import JellyfinAPI
|
||||||
|
|
||||||
final class HomeViewModel: ViewModel {
|
final class HomeViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var librariesShowRecentlyAddedIDs = [String]()
|
@Published var librariesShowRecentlyAddedIDs: [String] = []
|
||||||
@Published var libraries = [BaseItemDto]()
|
@Published var libraries: [BaseItemDto] = []
|
||||||
@Published var resumeItems = [BaseItemDto]()
|
@Published var resumeItems: [BaseItemDto] = []
|
||||||
@Published var nextUpItems = [BaseItemDto]()
|
@Published var nextUpItems: [BaseItemDto] = []
|
||||||
|
|
||||||
// temp
|
// temp
|
||||||
var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded])
|
var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded])
|
||||||
|
@ -32,26 +32,42 @@ final class HomeViewModel: ViewModel {
|
||||||
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
|
switch completion {
|
||||||
|
case .finished: ()
|
||||||
|
case .failure(_):
|
||||||
|
self.libraries = []
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
|
}
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
|
|
||||||
|
var newLibraries: [BaseItemDto] = []
|
||||||
|
|
||||||
response.items!.forEach { item in
|
response.items!.forEach { item in
|
||||||
LogManager.shared.log.debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")")
|
LogManager.shared.log.debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")")
|
||||||
if item.collectionType == "movies" || item.collectionType == "tvshows" {
|
if item.collectionType == "movies" || item.collectionType == "tvshows" {
|
||||||
self.libraries.append(item)
|
newLibraries.append(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UserAPI.getCurrentUser()
|
UserAPI.getCurrentUser()
|
||||||
.trackActivity(self.loading)
|
.trackActivity(self.loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
|
switch completion {
|
||||||
|
case .finished: ()
|
||||||
|
case .failure(_):
|
||||||
|
self.libraries = []
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
|
}
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
self.libraries.forEach { library in
|
let excludeIDs = response.configuration?.latestItemsExcludes != nil ? response.configuration!.latestItemsExcludes! : []
|
||||||
if !(response.configuration?.latestItemsExcludes?.contains(library.id!))! {
|
|
||||||
LogManager.shared.log.debug("Adding library \(library.id!) (\(library.name ?? "nil")) to recently added list")
|
for excludeID in excludeIDs {
|
||||||
self.librariesShowRecentlyAddedIDs.append(library.id!)
|
newLibraries.removeAll { library in
|
||||||
|
return library.id == excludeID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.libraries = newLibraries
|
||||||
})
|
})
|
||||||
.store(in: &self.cancellables)
|
.store(in: &self.cancellables)
|
||||||
})
|
})
|
||||||
|
@ -59,12 +75,20 @@ final class HomeViewModel: ViewModel {
|
||||||
|
|
||||||
ItemsAPI.getResumeItems(userId: SessionManager.main.currentLogin.user.id, limit: 12,
|
ItemsAPI.getResumeItems(userId: SessionManager.main.currentLogin.user.id, limit: 12,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||||
mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
|
mediaTypes: ["Video"],
|
||||||
|
imageTypeLimit: 1,
|
||||||
|
enableImageTypes: [.primary, .backdrop, .thumb])
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
|
switch completion {
|
||||||
|
case .finished: ()
|
||||||
|
case .failure(_):
|
||||||
|
self.resumeItems = []
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
|
}
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) resume items")
|
LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) resume items")
|
||||||
|
|
||||||
self.resumeItems = response.items ?? []
|
self.resumeItems = response.items ?? []
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
@ -73,9 +97,15 @@ final class HomeViewModel: ViewModel {
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
|
switch completion {
|
||||||
|
case .finished: ()
|
||||||
|
case .failure(_):
|
||||||
|
self.nextUpItems = []
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
|
}
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) nextup items")
|
LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) nextup items")
|
||||||
|
|
||||||
self.nextUpItems = response.items ?? []
|
self.nextUpItems = response.items ?? []
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
Loading…
Reference in New Issue