mirror of
https://github.com/jellyfin/Swiftfin.git
synced 2025-02-25 01:31:32 +00:00
undo filter change attempt
This commit is contained in:
parent
98b2b5071f
commit
0c95fb48a7
@ -7,11 +7,10 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import JellyfinAPI
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
typealias FilterCoordinatorParams = (libraryItem: BaseItemDto, filters: Binding<LibraryFilters>, enabledFilterType: [FilterType])
|
||||
typealias FilterCoordinatorParams = (filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String)
|
||||
|
||||
final class FilterCoordinator: NavigationCoordinatable {
|
||||
|
||||
@ -20,19 +19,19 @@ final class FilterCoordinator: NavigationCoordinatable {
|
||||
@Root
|
||||
var start = makeStart
|
||||
|
||||
let libraryItem: BaseItemDto
|
||||
@Binding
|
||||
var filters: LibraryFilters
|
||||
var enabledFilterType: [FilterType]
|
||||
var parentId: String = ""
|
||||
|
||||
init(libraryItem: BaseItemDto, filters: Binding<LibraryFilters>, enabledFilterType: [FilterType]) {
|
||||
self.libraryItem = libraryItem
|
||||
init(filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String) {
|
||||
_filters = filters
|
||||
self.enabledFilterType = enabledFilterType
|
||||
self.parentId = parentId
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: libraryItem.id!)
|
||||
LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: parentId)
|
||||
}
|
||||
}
|
||||
|
@ -32,8 +32,8 @@ final class HomeCoordinator: NavigationCoordinatable {
|
||||
NavigationViewCoordinator(SettingsCoordinator())
|
||||
}
|
||||
|
||||
func makeLibrary(viewModel: LibraryViewModel) -> LibraryCoordinator {
|
||||
LibraryCoordinator(viewModel: viewModel)
|
||||
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
|
||||
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
|
||||
}
|
||||
|
||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||
@ -44,8 +44,8 @@ final class HomeCoordinator: NavigationCoordinatable {
|
||||
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||
}
|
||||
|
||||
func makeModalLibrary(viewModel: LibraryViewModel) -> NavigationViewCoordinator<LibraryCoordinator> {
|
||||
NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel))
|
||||
func makeModalLibrary(params: LibraryCoordinatorParams) -> NavigationViewCoordinator<LibraryCoordinator> {
|
||||
NavigationViewCoordinator(LibraryCoordinator(viewModel: params.viewModel, title: params.title))
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
@ -32,8 +32,8 @@ final class ItemCoordinator: NavigationCoordinatable {
|
||||
self.itemDto = item
|
||||
}
|
||||
|
||||
func makeLibrary(viewModel: LibraryViewModel) -> LibraryCoordinator {
|
||||
LibraryCoordinator(viewModel: viewModel)
|
||||
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
|
||||
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
|
||||
}
|
||||
|
||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||
|
@ -11,6 +11,8 @@ import JellyfinAPI
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
typealias LibraryCoordinatorParams = (viewModel: LibraryViewModel, title: String)
|
||||
|
||||
final class LibraryCoordinator: NavigationCoordinatable {
|
||||
|
||||
let stack = NavigationStack(initial: \LibraryCoordinator.start)
|
||||
@ -27,14 +29,16 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
||||
var modalItem = makeModalItem
|
||||
|
||||
let viewModel: LibraryViewModel
|
||||
let title: String
|
||||
|
||||
init(viewModel: LibraryViewModel) {
|
||||
init(viewModel: LibraryViewModel, title: String) {
|
||||
self.viewModel = viewModel
|
||||
self.title = title
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
LibraryView(viewModel: self.viewModel)
|
||||
LibraryView(viewModel: self.viewModel, title: title)
|
||||
}
|
||||
|
||||
func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator {
|
||||
@ -42,9 +46,9 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
||||
}
|
||||
|
||||
func makeFilter(params: FilterCoordinatorParams) -> NavigationViewCoordinator<FilterCoordinator> {
|
||||
NavigationViewCoordinator(FilterCoordinator(libraryItem: viewModel.libraryItem,
|
||||
filters: params.filters,
|
||||
enabledFilterType: params.enabledFilterType))
|
||||
NavigationViewCoordinator(FilterCoordinator(filters: params.filters,
|
||||
enabledFilterType: params.enabledFilterType,
|
||||
parentId: params.parentId))
|
||||
}
|
||||
|
||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||
|
@ -27,8 +27,8 @@ final class LibraryListCoordinator: NavigationCoordinatable {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
func makeLibrary(viewModel: LibraryViewModel) -> LibraryCoordinator {
|
||||
LibraryCoordinator(viewModel: viewModel)
|
||||
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
|
||||
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
|
||||
}
|
||||
|
||||
func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator {
|
||||
|
@ -34,6 +34,6 @@ final class TVLibrariesCoordinator: NavigationCoordinatable {
|
||||
}
|
||||
|
||||
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
||||
LibraryCoordinator(viewModel: .init(libraryItem: <#T##BaseItemDto#>))
|
||||
LibraryCoordinator(viewModel: .init(libraryItem: <#T##BaseItemDto#>))
|
||||
}
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ public extension BaseItemDto {
|
||||
case episode = "Episode"
|
||||
case series = "Series"
|
||||
case boxset = "BoxSet"
|
||||
case collectionFolder = "CollectionFolder"
|
||||
case collectionFolder = "CollectionFolder"
|
||||
|
||||
case unknown
|
||||
|
||||
@ -229,7 +229,7 @@ public extension BaseItemDto {
|
||||
|
||||
func portraitHeaderViewURL(maxWidth: Int) -> URL {
|
||||
switch itemType {
|
||||
case .movie, .season, .series, .boxset, .collectionFolder:
|
||||
case .movie, .season, .series, .boxset, .collectionFolder:
|
||||
return getPrimaryImage(maxWidth: maxWidth)
|
||||
case .episode:
|
||||
return getSeriesPrimaryImage(maxWidth: maxWidth)
|
||||
|
@ -67,7 +67,8 @@ final class LibraryFilterViewModel: ViewModel {
|
||||
}
|
||||
|
||||
func requestQueryFilters() {
|
||||
FilterAPI.getQueryFilters(userId: SessionManager.main.currentLogin.user.id, parentId: self.parentId)
|
||||
FilterAPI.getQueryFilters(userId: SessionManager.main.currentLogin.user.id,
|
||||
parentId: self.parentId)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
|
@ -13,8 +13,8 @@ final class LibraryListViewModel: ViewModel {
|
||||
|
||||
@Published
|
||||
var libraries: [BaseItemDto] = []
|
||||
@Published
|
||||
var libraryRandomItems: [BaseItemDto: BaseItemDto] = [:]
|
||||
@Published
|
||||
var libraryRandomItems: [BaseItemDto: BaseItemDto] = [:]
|
||||
|
||||
// temp
|
||||
var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: [])
|
||||
@ -31,34 +31,34 @@ final class LibraryListViewModel: ViewModel {
|
||||
.sink(receiveCompletion: { completion in
|
||||
self.handleAPIRequestError(completion: completion)
|
||||
}, receiveValue: { response in
|
||||
if let libraries = response.items {
|
||||
self.libraries = libraries
|
||||
|
||||
for library in libraries {
|
||||
self.getRandomLibraryItem(for: library)
|
||||
}
|
||||
}
|
||||
if let libraries = response.items {
|
||||
self.libraries = libraries
|
||||
|
||||
for library in libraries {
|
||||
self.getRandomLibraryItem(for: library)
|
||||
}
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: Library random item
|
||||
|
||||
func getRandomLibraryItem(for library: BaseItemDto) {
|
||||
guard library.itemType == .collectionFolder else { return }
|
||||
|
||||
ItemsAPI.getItems(userId: SessionManager.main.currentLogin.user.id,
|
||||
limit: 1,
|
||||
parentId: library.id)
|
||||
.sink { completion in
|
||||
self.handleAPIRequestError(completion: completion)
|
||||
} receiveValue: { result in
|
||||
if let item = result.items?.first {
|
||||
self.libraryRandomItems[library] = item
|
||||
} else {
|
||||
self.libraryRandomItems[library] = library
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: Library random item
|
||||
|
||||
func getRandomLibraryItem(for library: BaseItemDto) {
|
||||
guard library.itemType == .collectionFolder else { return }
|
||||
|
||||
ItemsAPI.getItems(userId: SessionManager.main.currentLogin.user.id,
|
||||
limit: 1,
|
||||
parentId: library.id)
|
||||
.sink { completion in
|
||||
self.handleAPIRequestError(completion: completion)
|
||||
} receiveValue: { result in
|
||||
if let item = result.items?.first {
|
||||
self.libraryRandomItems[library] = item
|
||||
} else {
|
||||
self.libraryRandomItems[library] = library
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
@ -23,9 +23,9 @@ struct LibraryRowCell: Hashable {
|
||||
final class LibraryViewModel: ViewModel {
|
||||
|
||||
@Published
|
||||
var items: [BaseItemDto] = []
|
||||
var items: [BaseItemDto] = []
|
||||
@Published
|
||||
var rows: [LibraryRow] = []
|
||||
var rows: [LibraryRow] = []
|
||||
|
||||
@Published
|
||||
var totalPages = 0
|
||||
@ -37,13 +37,13 @@ final class LibraryViewModel: ViewModel {
|
||||
// temp
|
||||
@Published
|
||||
var filters: LibraryFilters
|
||||
|
||||
let libraryItem: BaseItemDto
|
||||
var person: BaseItemPerson?
|
||||
var genre: NameGuidPair?
|
||||
var studio: NameGuidPair?
|
||||
|
||||
var parentID: String?
|
||||
var person: BaseItemPerson?
|
||||
var genre: NameGuidPair?
|
||||
var studio: NameGuidPair?
|
||||
private let columns: Int
|
||||
private let pageItemSize: Int
|
||||
private let pageItemSize: Int
|
||||
|
||||
var enabledFilterType: [FilterType] {
|
||||
if genre == nil {
|
||||
@ -53,40 +53,38 @@ final class LibraryViewModel: ViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
init(libraryItem: BaseItemDto,
|
||||
init(parentID: String? = nil,
|
||||
person: BaseItemPerson? = nil,
|
||||
genre: NameGuidPair? = nil,
|
||||
studio: NameGuidPair? = nil,
|
||||
filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name]),
|
||||
columns: Int = 7)
|
||||
{
|
||||
self.libraryItem = libraryItem
|
||||
self.parentID = parentID
|
||||
self.person = person
|
||||
self.genre = genre
|
||||
self.studio = studio
|
||||
self.filters = filters
|
||||
self.columns = columns
|
||||
|
||||
// Size is typical size of portrait items
|
||||
self.pageItemSize = UIScreen.itemsFillableOnScreen(width: 130, height: 185)
|
||||
|
||||
print("Page item size: \(pageItemSize)")
|
||||
|
||||
|
||||
// Size is typical size of portrait items
|
||||
self.pageItemSize = UIScreen.itemsFillableOnScreen(width: 130, height: 185)
|
||||
|
||||
super.init()
|
||||
|
||||
$filters
|
||||
.sink(receiveValue: { newFilters in
|
||||
self.requestItemsAsync(with: newFilters, replaceCurrentItems: true)
|
||||
})
|
||||
.sink(receiveValue: { newFilters in
|
||||
self.requestItemsAsync(with: newFilters, replaceCurrentItems: true)
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func requestItemsAsync(with filters: LibraryFilters, replaceCurrentItems: Bool = false) {
|
||||
|
||||
if replaceCurrentItems {
|
||||
self.items = []
|
||||
}
|
||||
|
||||
func requestItemsAsync(with filters: LibraryFilters, replaceCurrentItems: Bool = false) {
|
||||
|
||||
if replaceCurrentItems {
|
||||
self.items = []
|
||||
}
|
||||
|
||||
let personIDs: [String] = [person].compactMap(\.?.id)
|
||||
let studioIDs: [String] = [studio].compactMap(\.?.id)
|
||||
let genreIDs: [String]
|
||||
@ -102,7 +100,7 @@ final class LibraryViewModel: ViewModel {
|
||||
recursive: true,
|
||||
searchTerm: nil,
|
||||
sortOrder: filters.sortOrder,
|
||||
parentId: libraryItem.id,
|
||||
parentId: parentID,
|
||||
fields: [
|
||||
.primaryImageAspectRatio,
|
||||
.seriesPrimaryImage,
|
||||
@ -122,14 +120,14 @@ final class LibraryViewModel: ViewModel {
|
||||
studioIds: studioIDs,
|
||||
genreIds: genreIDs,
|
||||
enableImages: true)
|
||||
.trackActivity(loading)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
}, receiveValue: { [weak self] response in
|
||||
|
||||
|
||||
guard let self = self else { return }
|
||||
let totalPages = ceil(Double(response.totalRecordCount ?? 0) / Double(self.pageItemSize))
|
||||
|
||||
let totalPages = ceil(Double(response.totalRecordCount ?? 0) / Double(self.pageItemSize))
|
||||
|
||||
self.totalPages = Int(totalPages)
|
||||
self.hasNextPage = self.currentPage < self.totalPages - 1
|
||||
self.items.append(contentsOf: response.items ?? [])
|
||||
@ -143,7 +141,7 @@ final class LibraryViewModel: ViewModel {
|
||||
requestItemsAsync(with: filters)
|
||||
}
|
||||
|
||||
// tvOS calculations for collection view
|
||||
// tvOS calculations for collection view
|
||||
private func calculateRows(for itemList: [BaseItemDto]) -> [LibraryRow] {
|
||||
guard !itemList.isEmpty else { return [] }
|
||||
let rowCount = itemList.count / columns
|
||||
@ -174,12 +172,12 @@ final class LibraryViewModel: ViewModel {
|
||||
}
|
||||
|
||||
extension UIScreen {
|
||||
|
||||
static func itemsFillableOnScreen(width: CGFloat, height: CGFloat) -> Int {
|
||||
|
||||
let screenSize = UIScreen.main.bounds.height * UIScreen.main.bounds.width
|
||||
let itemSize = width * height
|
||||
|
||||
return Int(screenSize / itemSize)
|
||||
}
|
||||
|
||||
static func itemsFillableOnScreen(width: CGFloat, height: CGFloat) -> Int {
|
||||
|
||||
let screenSize = UIScreen.main.bounds.height * UIScreen.main.bounds.width
|
||||
let itemSize = width * height
|
||||
|
||||
return Int(screenSize / itemSize)
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,18 @@
|
||||
},
|
||||
"idiom" : "iphone"
|
||||
},
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.765",
|
||||
"green" : "0.361",
|
||||
"red" : "0.667"
|
||||
}
|
||||
},
|
||||
"idiom" : "ipad"
|
||||
},
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
|
@ -11,89 +11,86 @@ import SwiftUI
|
||||
// https://stackoverflow.com/questions/56573373/swiftui-get-size-of-child
|
||||
|
||||
struct ChildSizeReader<Content: View>: View {
|
||||
@Binding var size: CGSize
|
||||
let content: () -> Content
|
||||
var body: some View {
|
||||
ZStack {
|
||||
content()
|
||||
.background(
|
||||
GeometryReader { proxy in
|
||||
Color.clear
|
||||
.preference(key: SizePreferenceKey.self, value: proxy.size)
|
||||
}
|
||||
)
|
||||
}
|
||||
.onPreferenceChange(SizePreferenceKey.self) { preferences in
|
||||
self.size = preferences
|
||||
}
|
||||
}
|
||||
@Binding
|
||||
var size: CGSize
|
||||
let content: () -> Content
|
||||
var body: some View {
|
||||
ZStack {
|
||||
content()
|
||||
.background(GeometryReader { proxy in
|
||||
Color.clear
|
||||
.preference(key: SizePreferenceKey.self, value: proxy.size)
|
||||
})
|
||||
}
|
||||
.onPreferenceChange(SizePreferenceKey.self) { preferences in
|
||||
self.size = preferences
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SizePreferenceKey: PreferenceKey {
|
||||
typealias Value = CGSize
|
||||
static var defaultValue: Value = .zero
|
||||
typealias Value = CGSize
|
||||
static var defaultValue: Value = .zero
|
||||
|
||||
static func reduce(value _: inout Value, nextValue: () -> Value) {
|
||||
_ = nextValue()
|
||||
}
|
||||
static func reduce(value _: inout Value, nextValue: () -> Value) {
|
||||
_ = nextValue()
|
||||
}
|
||||
}
|
||||
|
||||
struct ViewOffsetKey: PreferenceKey {
|
||||
typealias Value = CGFloat
|
||||
static var defaultValue = CGFloat.zero
|
||||
static func reduce(value: inout Value, nextValue: () -> Value) {
|
||||
value += nextValue()
|
||||
}
|
||||
typealias Value = CGFloat
|
||||
static var defaultValue = CGFloat.zero
|
||||
static func reduce(value: inout Value, nextValue: () -> Value) {
|
||||
value += nextValue()
|
||||
}
|
||||
}
|
||||
|
||||
struct DetectBottomScrollView<Content: View>: View {
|
||||
private let spaceName = "scroll"
|
||||
private let spaceName = "scroll"
|
||||
|
||||
@State private var wholeSize: CGSize = .zero
|
||||
@State private var scrollViewSize: CGSize = .zero
|
||||
@State private var previousDidReachBottom = false
|
||||
let content: () -> Content
|
||||
let didReachBottom: (Bool) -> Void
|
||||
|
||||
init(content: @escaping () -> Content,
|
||||
didReachBottom: @escaping (Bool) -> Void) {
|
||||
self.content = content
|
||||
self.didReachBottom = didReachBottom
|
||||
}
|
||||
@State
|
||||
private var wholeSize: CGSize = .zero
|
||||
@State
|
||||
private var scrollViewSize: CGSize = .zero
|
||||
@State
|
||||
private var previousDidReachBottom = false
|
||||
let content: () -> Content
|
||||
let didReachBottom: (Bool) -> Void
|
||||
|
||||
var body: some View {
|
||||
ChildSizeReader(size: $wholeSize) {
|
||||
ScrollView {
|
||||
ChildSizeReader(size: $scrollViewSize) {
|
||||
content()
|
||||
.background(
|
||||
GeometryReader { proxy in
|
||||
Color.clear.preference(
|
||||
key: ViewOffsetKey.self,
|
||||
value: -1 * proxy.frame(in: .named(spaceName)).origin.y
|
||||
)
|
||||
}
|
||||
)
|
||||
.onPreferenceChange(
|
||||
ViewOffsetKey.self,
|
||||
perform: { value in
|
||||
init(content: @escaping () -> Content,
|
||||
didReachBottom: @escaping (Bool) -> Void)
|
||||
{
|
||||
self.content = content
|
||||
self.didReachBottom = didReachBottom
|
||||
}
|
||||
|
||||
if value >= scrollViewSize.height - wholeSize.height {
|
||||
if !previousDidReachBottom {
|
||||
previousDidReachBottom = true
|
||||
didReachBottom(true)
|
||||
}
|
||||
} else {
|
||||
if previousDidReachBottom {
|
||||
previousDidReachBottom = false
|
||||
didReachBottom(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.coordinateSpace(name: spaceName)
|
||||
}
|
||||
}
|
||||
var body: some View {
|
||||
ChildSizeReader(size: $wholeSize) {
|
||||
ScrollView {
|
||||
ChildSizeReader(size: $scrollViewSize) {
|
||||
content()
|
||||
.background(GeometryReader { proxy in
|
||||
Color.clear.preference(key: ViewOffsetKey.self,
|
||||
value: -1 * proxy.frame(in: .named(spaceName)).origin.y)
|
||||
})
|
||||
.onPreferenceChange(ViewOffsetKey.self,
|
||||
perform: { value in
|
||||
|
||||
if value >= scrollViewSize.height - wholeSize.height {
|
||||
if !previousDidReachBottom {
|
||||
previousDidReachBottom = true
|
||||
didReachBottom(true)
|
||||
}
|
||||
} else {
|
||||
if previousDidReachBottom {
|
||||
previousDidReachBottom = false
|
||||
didReachBottom(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
.coordinateSpace(name: spaceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,60 +10,60 @@ import JellyfinAPI
|
||||
import SwiftUI
|
||||
|
||||
struct PortraitItemButton<ItemType: PortraitImageStackable>: View {
|
||||
|
||||
let item: ItemType
|
||||
let maxWidth: CGFloat
|
||||
let horizontalAlignment: HorizontalAlignment
|
||||
let textAlignment: TextAlignment
|
||||
let selectedAction: (ItemType) -> Void
|
||||
|
||||
init(item: ItemType,
|
||||
maxWidth: CGFloat = 110,
|
||||
horizontalAlignment: HorizontalAlignment = .leading,
|
||||
textAlignment: TextAlignment = .leading,
|
||||
selectedAction: @escaping (ItemType) -> Void)
|
||||
{
|
||||
self.item = item
|
||||
self.maxWidth = maxWidth
|
||||
self.horizontalAlignment = horizontalAlignment
|
||||
self.textAlignment = textAlignment
|
||||
self.selectedAction = selectedAction
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
selectedAction(item)
|
||||
} label: {
|
||||
VStack(alignment: horizontalAlignment) {
|
||||
ImageView(src: item.imageURLContsructor(maxWidth: Int(maxWidth)),
|
||||
bh: item.blurHash,
|
||||
failureInitials: item.failureInitials)
|
||||
.portraitPoster(width: maxWidth)
|
||||
.shadow(radius: 4, y: 2)
|
||||
|
||||
if item.showTitle {
|
||||
Text(item.title)
|
||||
.font(.footnote)
|
||||
.fontWeight(.regular)
|
||||
.foregroundColor(.primary)
|
||||
.multilineTextAlignment(textAlignment)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(2)
|
||||
}
|
||||
let item: ItemType
|
||||
let maxWidth: CGFloat
|
||||
let horizontalAlignment: HorizontalAlignment
|
||||
let textAlignment: TextAlignment
|
||||
let selectedAction: (ItemType) -> Void
|
||||
|
||||
if let description = item.subtitle {
|
||||
Text(description)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(textAlignment)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(2)
|
||||
}
|
||||
}
|
||||
.frame(width: maxWidth)
|
||||
}
|
||||
.frame(alignment: .top)
|
||||
.padding(.bottom)
|
||||
}
|
||||
init(item: ItemType,
|
||||
maxWidth: CGFloat = 110,
|
||||
horizontalAlignment: HorizontalAlignment = .leading,
|
||||
textAlignment: TextAlignment = .leading,
|
||||
selectedAction: @escaping (ItemType) -> Void)
|
||||
{
|
||||
self.item = item
|
||||
self.maxWidth = maxWidth
|
||||
self.horizontalAlignment = horizontalAlignment
|
||||
self.textAlignment = textAlignment
|
||||
self.selectedAction = selectedAction
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
selectedAction(item)
|
||||
} label: {
|
||||
VStack(alignment: horizontalAlignment) {
|
||||
ImageView(src: item.imageURLContsructor(maxWidth: Int(maxWidth)),
|
||||
bh: item.blurHash,
|
||||
failureInitials: item.failureInitials)
|
||||
.portraitPoster(width: maxWidth)
|
||||
.shadow(radius: 4, y: 2)
|
||||
|
||||
if item.showTitle {
|
||||
Text(item.title)
|
||||
.font(.footnote)
|
||||
.fontWeight(.regular)
|
||||
.foregroundColor(.primary)
|
||||
.multilineTextAlignment(textAlignment)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
if let description = item.subtitle {
|
||||
Text(description)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(textAlignment)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(2)
|
||||
}
|
||||
}
|
||||
.frame(width: maxWidth)
|
||||
}
|
||||
.frame(alignment: .top)
|
||||
.padding(.bottom)
|
||||
}
|
||||
}
|
||||
|
@ -90,9 +90,9 @@ struct HomeView: View {
|
||||
|
||||
Button {
|
||||
homeRouter
|
||||
// .route(to: \.library, (viewModel: .init(parentID: library.id!,
|
||||
// filters: viewModel.recentFilterSet),
|
||||
// title: library.name ?? ""))
|
||||
.route(to: \.library, (viewModel: .init(parentID: library.id!,
|
||||
filters: viewModel.recentFilterSet),
|
||||
title: library.name ?? ""))
|
||||
} label: {
|
||||
HStack {
|
||||
L10n.seeAll.text.font(.subheadline).fontWeight(.bold)
|
||||
|
@ -12,7 +12,7 @@ import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
struct LibraryListView: View {
|
||||
|
||||
|
||||
@EnvironmentObject
|
||||
var libraryListRouter: LibraryListCoordinator.Router
|
||||
@StateObject
|
||||
@ -23,31 +23,30 @@ struct LibraryListView: View {
|
||||
LazyVStack {
|
||||
Button {
|
||||
libraryListRouter.route(to: \.library,
|
||||
LibraryViewModel(libraryItem: BaseItemDto(), filters: viewModel.withFavorites))
|
||||
(viewModel: LibraryViewModel(filters: viewModel.withFavorites), title: L10n.favorites))
|
||||
} label: {
|
||||
HStack {
|
||||
Spacer()
|
||||
L10n.yourFavorites.text
|
||||
.foregroundColor(.black)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
Spacer()
|
||||
}
|
||||
.frame(height: 100)
|
||||
HStack {
|
||||
Spacer()
|
||||
L10n.yourFavorites.text
|
||||
.foregroundColor(.black)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
Spacer()
|
||||
}
|
||||
.frame(height: 100)
|
||||
.background(Color.white)
|
||||
}
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: 5)
|
||||
.padding()
|
||||
.padding()
|
||||
|
||||
if !viewModel.isLoading {
|
||||
|
||||
if let collectionsLibraryItem = viewModel.libraries.first(where: { $0.collectionType == "boxsets" }) {
|
||||
Button {
|
||||
libraryListRouter.route(to: \.library,
|
||||
LibraryViewModel(libraryItem: collectionsLibraryItem))
|
||||
// (viewModel: LibraryViewModel(parentID: collectionsLibraryItem.id),
|
||||
// title: collectionsLibraryItem.name ?? ""))
|
||||
(viewModel: LibraryViewModel(parentID: collectionsLibraryItem.id),
|
||||
title: collectionsLibraryItem.name ?? ""))
|
||||
} label: {
|
||||
ZStack {
|
||||
ImageView(src: collectionsLibraryItem.getPrimaryImage(maxWidth: 500),
|
||||
@ -64,15 +63,15 @@ struct LibraryListView: View {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.background(Color.black)
|
||||
.frame(height: 100)
|
||||
.background(Color.black)
|
||||
.frame(height: 100)
|
||||
}
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: 5)
|
||||
.padding()
|
||||
.padding()
|
||||
}
|
||||
|
||||
ForEach(Array(viewModel.libraryRandomItems.keys), id: \.id) { library in
|
||||
ForEach(Array(viewModel.libraryRandomItems.keys), id: \.id) { library in
|
||||
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
|
||||
Button {
|
||||
libraryListRouter.route(to: \.library,
|
||||
@ -82,22 +81,22 @@ struct LibraryListView: View {
|
||||
ZStack {
|
||||
// ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
|
||||
// .opacity(0.4)
|
||||
|
||||
ImageView(src: viewModel.libraryRandomItems[library]!.getBackdropImage(maxWidth: 500))
|
||||
|
||||
VStack {
|
||||
Text(library.name ?? "")
|
||||
.foregroundColor(.white)
|
||||
.font(.title2)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
|
||||
ImageView(src: viewModel.libraryRandomItems[library]!.getBackdropImage(maxWidth: 500))
|
||||
|
||||
VStack {
|
||||
Text(library.name ?? "")
|
||||
.foregroundColor(.white)
|
||||
.font(.title2)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
.background(Color.black)
|
||||
.frame(height: 100)
|
||||
.background(Color.black)
|
||||
.frame(height: 100)
|
||||
}
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: 5)
|
||||
.padding()
|
||||
.shadow(radius: 5)
|
||||
.padding()
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ struct LibrarySearchView: View {
|
||||
Button {
|
||||
searchRouter.route(to: \.item, item)
|
||||
} label: {
|
||||
PortraitItemElement(item: item)
|
||||
PortraitItemElement(item: item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,84 +10,87 @@ import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
struct LibraryView: View {
|
||||
|
||||
|
||||
@EnvironmentObject
|
||||
var libraryRouter: LibraryCoordinator.Router
|
||||
@StateObject
|
||||
var viewModel: LibraryViewModel
|
||||
var title: String
|
||||
|
||||
// MARK: tracks for grid
|
||||
|
||||
var defaultFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name])
|
||||
|
||||
@State
|
||||
private var tracks: [GridItem] = Array(repeating: .init(.flexible(), alignment: .top), count: Int(UIScreen.main.bounds.size.width) / 125)
|
||||
private var tracks: [GridItem] = Array(repeating: .init(.flexible(), alignment: .top),
|
||||
count: Int(UIScreen.main.bounds.size.width) / 125)
|
||||
|
||||
func recalcTracks() {
|
||||
tracks = Array(repeating: .init(.flexible(), alignment: .top), count: Int(UIScreen.main.bounds.size.width) / 125)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var loadingView: some View {
|
||||
ProgressView()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var noResultsView: some View {
|
||||
L10n.noResults.text
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var libraryItemsView: some View {
|
||||
DetectBottomScrollView {
|
||||
VStack {
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(viewModel.items, id: \.id) { item in
|
||||
if item.type != "Folder" {
|
||||
PortraitItemButton(item: item) { item in
|
||||
libraryRouter.route(to: \.item, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.listRowSeparator(.hidden)
|
||||
.onRotate { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
.frame(height: 30)
|
||||
}
|
||||
} didReachBottom: { newValue in
|
||||
if newValue && viewModel.hasNextPage {
|
||||
viewModel.requestNextPageAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var loadingView: some View {
|
||||
ProgressView()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var noResultsView: some View {
|
||||
L10n.noResults.text
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var libraryItemsView: some View {
|
||||
DetectBottomScrollView {
|
||||
VStack {
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(viewModel.items, id: \.id) { item in
|
||||
if item.type != "Folder" {
|
||||
PortraitItemButton(item: item) { item in
|
||||
libraryRouter.route(to: \.item, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.listRowSeparator(.hidden)
|
||||
.onRotate { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
.frame(height: 30)
|
||||
}
|
||||
} didReachBottom: { newValue in
|
||||
if newValue && viewModel.hasNextPage {
|
||||
viewModel.requestNextPageAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if viewModel.isLoading && viewModel.items.isEmpty {
|
||||
if viewModel.isLoading && viewModel.items.isEmpty {
|
||||
ProgressView()
|
||||
} else if !viewModel.items.isEmpty {
|
||||
libraryItemsView
|
||||
libraryItemsView
|
||||
} else {
|
||||
noResultsView
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
|
||||
Button {
|
||||
// libraryRouter
|
||||
// .route(to: \.filter, (filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType,
|
||||
// parentId: viewModel.parentID ?? ""))
|
||||
} label: {
|
||||
Image(systemName: "line.horizontal.3.decrease.circle")
|
||||
}
|
||||
.foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange))
|
||||
|
||||
|
||||
Button {
|
||||
libraryRouter
|
||||
.route(to: \.filter, (filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType,
|
||||
parentId: viewModel.parentID ?? ""))
|
||||
} label: {
|
||||
Image(systemName: "line.horizontal.3.decrease.circle")
|
||||
}
|
||||
.foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange))
|
||||
|
||||
Button {
|
||||
// libraryRouter.route(to: \.search, .init(parentID: viewModel.parentID))
|
||||
} label: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user