Shuffle play (#816)
This commit is contained in:
parent
a2f9da506c
commit
eb99dfe30b
|
@ -28,7 +28,7 @@ internal enum L10n {
|
|||
internal static let allGenres = L10n.tr("Localizable", "allGenres", fallback: "All Genres")
|
||||
/// All Media
|
||||
internal static let allMedia = L10n.tr("Localizable", "allMedia", fallback: "All Media")
|
||||
/// Appearance
|
||||
/// Represents the Appearance setting label
|
||||
internal static let appearance = L10n.tr("Localizable", "appearance", fallback: "Appearance")
|
||||
/// App Icon
|
||||
internal static let appIcon = L10n.tr("Localizable", "appIcon", fallback: "App Icon")
|
||||
|
@ -106,7 +106,7 @@ internal enum L10n {
|
|||
internal static let currentPosition = L10n.tr("Localizable", "currentPosition", fallback: "Current Position")
|
||||
/// Customize
|
||||
internal static let customize = L10n.tr("Localizable", "customize", fallback: "Customize")
|
||||
/// Dark
|
||||
/// Represents the dark theme setting
|
||||
internal static let dark = L10n.tr("Localizable", "dark", fallback: "Dark")
|
||||
/// Default Scheme
|
||||
internal static let defaultScheme = L10n.tr("Localizable", "defaultScheme", fallback: "Default Scheme")
|
||||
|
@ -156,6 +156,8 @@ internal enum L10n {
|
|||
internal static let genres = L10n.tr("Localizable", "genres", fallback: "Genres")
|
||||
/// Green
|
||||
internal static let green = L10n.tr("Localizable", "green", fallback: "Green")
|
||||
/// Grid
|
||||
internal static let grid = L10n.tr("Localizable", "grid", fallback: "Grid")
|
||||
/// Haptic Feedback
|
||||
internal static let hapticFeedback = L10n.tr("Localizable", "hapticFeedback", fallback: "Haptic Feedback")
|
||||
/// Home
|
||||
|
@ -194,8 +196,10 @@ internal enum L10n {
|
|||
}
|
||||
/// Library
|
||||
internal static let library = L10n.tr("Localizable", "library", fallback: "Library")
|
||||
/// Light
|
||||
/// Represents the light theme setting
|
||||
internal static let light = L10n.tr("Localizable", "light", fallback: "Light")
|
||||
/// List
|
||||
internal static let list = L10n.tr("Localizable", "list", fallback: "List")
|
||||
/// Live TV
|
||||
internal static let liveTV = L10n.tr("Localizable", "liveTV", fallback: "Live TV")
|
||||
/// Loading
|
||||
|
@ -340,6 +344,8 @@ internal enum L10n {
|
|||
internal static let quickConnectStep3 = L10n.tr("Localizable", "quickConnectStep3", fallback: "3. Enter the following code:")
|
||||
/// Authorizing Quick Connect successful. Please continue on your other device.
|
||||
internal static let quickConnectSuccessMessage = L10n.tr("Localizable", "quickConnectSuccessMessage", fallback: "Authorizing Quick Connect successful. Please continue on your other device.")
|
||||
/// Random
|
||||
internal static let random = L10n.tr("Localizable", "random", fallback: "Random")
|
||||
/// Random Image
|
||||
internal static let randomImage = L10n.tr("Localizable", "randomImage", fallback: "Random Image")
|
||||
/// Rated
|
||||
|
@ -480,7 +486,7 @@ internal enum L10n {
|
|||
internal static let suggestions = L10n.tr("Localizable", "suggestions", fallback: "Suggestions")
|
||||
/// Switch User
|
||||
internal static let switchUser = L10n.tr("Localizable", "switchUser", fallback: "Switch User")
|
||||
/// System
|
||||
/// Represents the system theme setting
|
||||
internal static let system = L10n.tr("Localizable", "system", fallback: "System")
|
||||
/// System Control Gestures Enabled
|
||||
internal static let systemControlGesturesEnabled = L10n.tr("Localizable", "systemControlGesturesEnabled", fallback: "System Control Gestures Enabled")
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import Combine
|
||||
import Foundation
|
||||
import Get
|
||||
import JellyfinAPI
|
||||
|
||||
final class ItemTypeLibraryViewModel: PagingLibraryViewModel {
|
||||
|
@ -35,25 +36,8 @@ final class ItemTypeLibraryViewModel: PagingLibraryViewModel {
|
|||
hasNextPage = true
|
||||
}
|
||||
|
||||
let genreIDs = filters.genres.compactMap(\.id)
|
||||
let sortBy: [String] = filters.sortBy.map(\.filterName).appending("IsFolder")
|
||||
let sortOrder = filters.sortOrder.map { SortOrder(rawValue: $0.filterName) ?? .ascending }
|
||||
let itemFilters: [ItemFilter] = filters.filters.compactMap { .init(rawValue: $0.filterName) }
|
||||
|
||||
Task {
|
||||
let parameters = Paths.GetItemsParameters(
|
||||
userID: userSession.user.id,
|
||||
startIndex: currentPage * pageItemSize,
|
||||
limit: pageItemSize,
|
||||
isRecursive: true,
|
||||
sortOrder: sortOrder,
|
||||
fields: ItemFields.allCases,
|
||||
includeItemTypes: itemTypes,
|
||||
filters: itemFilters,
|
||||
sortBy: sortBy,
|
||||
enableUserData: true,
|
||||
genreIDs: genreIDs
|
||||
)
|
||||
var parameters = self._getDefaultParams()
|
||||
let request = Paths.getItems(parameters: parameters)
|
||||
let response = try await userSession.client.send(request)
|
||||
|
||||
|
@ -68,6 +52,30 @@ final class ItemTypeLibraryViewModel: PagingLibraryViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
override func _getDefaultParams() -> Paths.GetItemsParameters? {
|
||||
let filters = filterViewModel.currentFilters
|
||||
let genreIDs = filters.genres.compactMap(\.id)
|
||||
let sortBy: [String] = filters.sortBy.map(\.filterName).appending("IsFolder")
|
||||
let sortOrder = filters.sortOrder.map { SortOrder(rawValue: $0.filterName) ?? .ascending }
|
||||
let itemFilters: [ItemFilter] = filters.filters.compactMap { .init(rawValue: $0.filterName) }
|
||||
|
||||
let parameters = Paths.GetItemsParameters(
|
||||
userID: userSession.user.id,
|
||||
startIndex: currentPage * pageItemSize,
|
||||
limit: pageItemSize,
|
||||
isRecursive: true,
|
||||
sortOrder: sortOrder,
|
||||
fields: ItemFields.allCases,
|
||||
includeItemTypes: itemTypes,
|
||||
filters: itemFilters,
|
||||
sortBy: sortBy,
|
||||
enableUserData: true,
|
||||
genreIDs: genreIDs
|
||||
)
|
||||
|
||||
return parameters
|
||||
}
|
||||
|
||||
override func _requestNextPage() {
|
||||
requestItems(with: filterViewModel.currentFilters)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import Combine
|
||||
import Defaults
|
||||
import Factory
|
||||
import Get
|
||||
import JellyfinAPI
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
@ -65,67 +66,21 @@ final class LibraryViewModel: PagingLibraryViewModel {
|
|||
self.hasNextPage = true
|
||||
}
|
||||
|
||||
var libraryID: String?
|
||||
var personIDs: [String]?
|
||||
var studioIDs: [String]?
|
||||
|
||||
if let parent = parent {
|
||||
switch type {
|
||||
case .library, .folders:
|
||||
libraryID = parent.id
|
||||
case .person:
|
||||
personIDs = [parent].compactMap(\.id)
|
||||
case .studio:
|
||||
studioIDs = [parent].compactMap(\.id)
|
||||
}
|
||||
}
|
||||
|
||||
var recursive = true
|
||||
let includeItemTypes: [BaseItemKind]
|
||||
|
||||
if filters.filters.contains(ItemFilter.isFavorite.filter) {
|
||||
includeItemTypes = [.movie, .boxSet, .series, .season, .episode]
|
||||
} else if type == .folders {
|
||||
recursive = false
|
||||
includeItemTypes = [.movie, .boxSet, .series, .folder, .collectionFolder]
|
||||
} else {
|
||||
includeItemTypes = [.movie, .boxSet, .series]
|
||||
}
|
||||
|
||||
var excludedIDs: [String]?
|
||||
var parameters = _getDefaultParams()
|
||||
parameters?.limit = pageItemSize
|
||||
parameters?.startIndex = currentPage * pageItemSize
|
||||
parameters?.sortOrder = filters.sortOrder.map { SortOrder(rawValue: $0.filterName) ?? .ascending }
|
||||
parameters?.sortBy = filters.sortBy.map(\.filterName).appending("IsFolder")
|
||||
|
||||
if filters.sortBy.first == SortBy.random.filter {
|
||||
excludedIDs = items.compactMap(\.id)
|
||||
parameters?.excludeItemIDs = items.compactMap(\.id)
|
||||
}
|
||||
|
||||
let genreIDs = filters.genres.compactMap(\.id)
|
||||
let sortBy: [String] = filters.sortBy.map(\.filterName).appending("IsFolder")
|
||||
let sortOrder = filters.sortOrder.map { SortOrder(rawValue: $0.filterName) ?? .ascending }
|
||||
let itemFilters: [ItemFilter] = filters.filters.compactMap { .init(rawValue: $0.filterName) }
|
||||
|
||||
Task {
|
||||
await MainActor.run {
|
||||
self.isLoading = true
|
||||
}
|
||||
|
||||
let parameters = Paths.GetItemsParameters(
|
||||
userID: userSession.user.id,
|
||||
excludeItemIDs: excludedIDs,
|
||||
startIndex: currentPage * pageItemSize,
|
||||
limit: pageItemSize,
|
||||
isRecursive: recursive,
|
||||
sortOrder: sortOrder,
|
||||
parentID: libraryID,
|
||||
fields: ItemFields.allCases,
|
||||
includeItemTypes: includeItemTypes,
|
||||
filters: itemFilters,
|
||||
sortBy: sortBy,
|
||||
enableUserData: true,
|
||||
personIDs: personIDs,
|
||||
studioIDs: studioIDs,
|
||||
genreIDs: genreIDs,
|
||||
enableImages: true
|
||||
)
|
||||
let request = Paths.getItems(parameters: parameters)
|
||||
let response = try await userSession.client.send(request)
|
||||
|
||||
|
@ -144,4 +99,53 @@ final class LibraryViewModel: PagingLibraryViewModel {
|
|||
override func _requestNextPage() {
|
||||
requestItems(with: filterViewModel.currentFilters)
|
||||
}
|
||||
|
||||
override func _getDefaultParams() -> Paths.GetItemsParameters? {
|
||||
|
||||
let filters = filterViewModel.currentFilters
|
||||
var libraryID: String?
|
||||
var personIDs: [String]?
|
||||
var studioIDs: [String]?
|
||||
let includeItemTypes: [BaseItemKind]
|
||||
var recursive = true
|
||||
|
||||
if let parent = parent {
|
||||
switch type {
|
||||
case .library, .folders:
|
||||
libraryID = parent.id
|
||||
case .person:
|
||||
personIDs = [parent].compactMap(\.id)
|
||||
case .studio:
|
||||
studioIDs = [parent].compactMap(\.id)
|
||||
}
|
||||
}
|
||||
|
||||
if filters.filters.contains(ItemFilter.isFavorite.filter) {
|
||||
includeItemTypes = [.movie, .boxSet, .series, .season, .episode]
|
||||
} else if type == .folders {
|
||||
recursive = false
|
||||
includeItemTypes = [.movie, .boxSet, .series, .folder, .collectionFolder]
|
||||
} else {
|
||||
includeItemTypes = [.movie, .boxSet, .series]
|
||||
}
|
||||
|
||||
let genreIDs = filters.genres.compactMap(\.id)
|
||||
let itemFilters: [ItemFilter] = filters.filters.compactMap { .init(rawValue: $0.filterName) }
|
||||
|
||||
let parameters = Paths.GetItemsParameters(
|
||||
userID: userSession.user.id,
|
||||
isRecursive: recursive,
|
||||
parentID: libraryID,
|
||||
fields: ItemFields.allCases,
|
||||
includeItemTypes: includeItemTypes,
|
||||
filters: itemFilters,
|
||||
enableUserData: true,
|
||||
personIDs: personIDs,
|
||||
studioIDs: studioIDs,
|
||||
genreIDs: genreIDs,
|
||||
enableImages: true
|
||||
)
|
||||
|
||||
return parameters
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import Defaults
|
||||
import Foundation
|
||||
import Get
|
||||
import JellyfinAPI
|
||||
import OrderedCollections
|
||||
import UIKit
|
||||
|
@ -28,6 +29,30 @@ class PagingLibraryViewModel: ViewModel {
|
|||
return UIScreen.main.maxChildren(width: libraryGridPosterType.width, height: height)
|
||||
}
|
||||
|
||||
public func getRandomItemFromLibrary() async throws -> BaseItemDtoQueryResult {
|
||||
|
||||
var parameters = _getDefaultParams()
|
||||
parameters?.limit = 1
|
||||
parameters?.sortBy = [SortBy.random.rawValue]
|
||||
|
||||
await MainActor.run {
|
||||
self.isLoading = true
|
||||
}
|
||||
|
||||
let request = Paths.getItems(parameters: parameters)
|
||||
let response = try await userSession.client.send(request)
|
||||
|
||||
await MainActor.run {
|
||||
self.isLoading = false
|
||||
}
|
||||
|
||||
return response.value
|
||||
}
|
||||
|
||||
func _getDefaultParams() -> Paths.GetItemsParameters? {
|
||||
Paths.GetItemsParameters()
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
currentPage = 0
|
||||
hasNextPage = true
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
4E8B34EA2AB91B6E0018F305 /* FilterDrawerSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* FilterDrawerSelection.swift */; };
|
||||
4E8B34EB2AB91B6E0018F305 /* FilterDrawerSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* FilterDrawerSelection.swift */; };
|
||||
4EAA35BB2AB9699B00D840DD /* FilterDrawerButtonSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAA35BA2AB9699B00D840DD /* FilterDrawerButtonSelectorView.swift */; };
|
||||
4F1282B12A7F3E8F005BCA29 /* RandomItemButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F1282B02A7F3E8F005BCA29 /* RandomItemButton.swift */; };
|
||||
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; };
|
||||
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; };
|
||||
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; };
|
||||
|
@ -777,6 +778,7 @@
|
|||
4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
|
||||
4E8B34E92AB91B6E0018F305 /* FilterDrawerSelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterDrawerSelection.swift; sourceTree = "<group>"; };
|
||||
4EAA35BA2AB9699B00D840DD /* FilterDrawerButtonSelectorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterDrawerButtonSelectorView.swift; sourceTree = "<group>"; };
|
||||
4F1282B02A7F3E8F005BCA29 /* RandomItemButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomItemButton.swift; sourceTree = "<group>"; };
|
||||
531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
||||
531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlainNavigationLinkButton.swift; sourceTree = "<group>"; };
|
||||
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = "<group>"; };
|
||||
|
@ -1817,6 +1819,7 @@
|
|||
E1581E26291EF59800D6C640 /* SplitContentView.swift */,
|
||||
E157562F29355B7900976E1F /* UpdateView.swift */,
|
||||
E192607F28D28AAD002314B4 /* UserProfileButton.swift */,
|
||||
4F1282B02A7F3E8F005BCA29 /* RandomItemButton.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3374,6 +3377,7 @@
|
|||
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
|
||||
E17AC9712954F636003D2BC2 /* DownloadListCoordinator.swift in Sources */,
|
||||
E10EAA4F277BBCC4000269ED /* CGSize.swift in Sources */,
|
||||
4F1282B12A7F3E8F005BCA29 /* RandomItemButton.swift in Sources */,
|
||||
E18E01EB288747230022598C /* MovieItemContentView.swift in Sources */,
|
||||
E17FB55B28C1266400311DFE /* GenresHStack.swift in Sources */,
|
||||
E18E01FA288747580022598C /* AboutAppView.swift in Sources */,
|
||||
|
|
|
@ -25,9 +25,9 @@ struct LibraryViewTypeToggle: View {
|
|||
} label: {
|
||||
switch libraryViewType {
|
||||
case .grid:
|
||||
Image(systemName: "list.dash")
|
||||
Label(L10n.list, systemImage: "list.dash")
|
||||
case .list:
|
||||
Image(systemName: "square.grid.2x2")
|
||||
Label(L10n.grid, systemImage: "square.grid.2x2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// 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 (c) 2023 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import Defaults
|
||||
import JellyfinAPI
|
||||
import SwiftUI
|
||||
|
||||
struct RandomItemButton: View {
|
||||
|
||||
@ObservedObject
|
||||
private var viewModel: PagingLibraryViewModel
|
||||
private var onSelect: (BaseItemDtoQueryResult) -> Void
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
Task {
|
||||
let response = try await viewModel.getRandomItemFromLibrary()
|
||||
onSelect(response)
|
||||
}
|
||||
} label: {
|
||||
Label(L10n.random, systemImage: "dice.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension RandomItemButton {
|
||||
init(viewModel: PagingLibraryViewModel) {
|
||||
self.init(
|
||||
viewModel: viewModel,
|
||||
onSelect: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
func onSelect(_ action: @escaping (BaseItemDtoQueryResult) -> Void) -> Self {
|
||||
copy(modifying: \.onSelect, with: action)
|
||||
}
|
||||
}
|
|
@ -58,8 +58,17 @@ struct BasicLibraryView: View {
|
|||
if viewModel.isLoading && !viewModel.items.isEmpty {
|
||||
ProgressView()
|
||||
}
|
||||
|
||||
LibraryViewTypeToggle(libraryViewType: $libraryViewType)
|
||||
Menu {
|
||||
LibraryViewTypeToggle(libraryViewType: $libraryViewType)
|
||||
RandomItemButton(viewModel: viewModel)
|
||||
.onSelect { response in
|
||||
if let item = response.items?.first {
|
||||
router.route(to: \.item, item)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,12 +84,20 @@ struct LibraryView: View {
|
|||
}
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
|
||||
if viewModel.isLoading && !viewModel.items.isEmpty {
|
||||
ProgressView()
|
||||
}
|
||||
|
||||
LibraryViewTypeToggle(libraryViewType: $libraryViewType)
|
||||
Menu {
|
||||
LibraryViewTypeToggle(libraryViewType: $libraryViewType)
|
||||
RandomItemButton(viewModel: viewModel)
|
||||
.onSelect { response in
|
||||
if let item = response.items?.first {
|
||||
router.route(to: \.item, item)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue