From 28d2136a257cf2a729f2144dfc4bea26d64c869f Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Fri, 25 Jun 2021 00:35:21 -0400 Subject: [PATCH] add watched state to item image views. --- JellyfinPlayer.xcodeproj/project.pbxproj | 16 ++-- .../xcshareddata/swiftpm/Package.resolved | 4 +- JellyfinPlayer/ConnectToServerView.swift | 11 +-- JellyfinPlayer/ContinueWatchingView.swift | 35 ++++----- JellyfinPlayer/HomeView.swift | 4 +- JellyfinPlayer/LatestMediaView.swift | 11 +++ JellyfinPlayer/LibraryFilterView.swift | 78 +++++++++++-------- JellyfinPlayer/LibraryListView.swift | 1 + JellyfinPlayer/LibrarySearchView.swift | 11 +++ JellyfinPlayer/LibraryView.swift | 22 ++++-- JellyfinPlayer/SeasonItemView.swift | 11 +++ JellyfinPlayer/SettingsView.swift | 4 + Shared/Extensions/ImageView.swift | 14 ++-- Shared/ServerLocator/ServerDiscovery.swift | 3 - .../UDPBroadCastConnection.swift | 10 +-- Shared/Typings/Typings.swift | 7 +- .../ViewModels/LibraryFilterViewModel.swift | 16 ++++ 17 files changed, 161 insertions(+), 97 deletions(-) diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 088e4486..0b493e32 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -1066,7 +1066,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "JellyfinPlayer tvOS/JellyfinPlayer tvOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 53; + CURRENT_PROJECT_VERSION = 54; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_PREVIEWS = YES; @@ -1094,7 +1094,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "JellyfinPlayer tvOS/JellyfinPlayer tvOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 53; + CURRENT_PROJECT_VERSION = 54; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_PREVIEWS = YES; @@ -1243,7 +1243,7 @@ CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 53; + CURRENT_PROJECT_VERSION = 54; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_BITCODE = NO; @@ -1277,7 +1277,7 @@ CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 53; + CURRENT_PROJECT_VERSION = 54; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 9R8RREG67J; @@ -1309,7 +1309,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 53; + CURRENT_PROJECT_VERSION = 54; DEVELOPMENT_TEAM = 9R8RREG67J; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; @@ -1334,7 +1334,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 53; + CURRENT_PROJECT_VERSION = 54; DEVELOPMENT_TEAM = 9R8RREG67J; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; @@ -1438,8 +1438,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kean/NukeUI"; requirement = { - kind = exactVersion; - version = 0.3.0; + kind = upToNextMajorVersion; + minimumVersion = 0.3.0; }; }; 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */ = { diff --git a/JellyfinPlayer.xcworkspace/xcshareddata/swiftpm/Package.resolved b/JellyfinPlayer.xcworkspace/xcshareddata/swiftpm/Package.resolved index 94c97f78..f56b4837 100644 --- a/JellyfinPlayer.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/JellyfinPlayer.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -60,8 +60,8 @@ "repositoryURL": "https://github.com/kean/NukeUI", "state": { "branch": null, - "revision": "d2580b8d22b29c6244418d8e4b568f3162191460", - "version": "0.3.0" + "revision": "4516371912149ac024dec361827931b46a69c217", + "version": "0.6.2" } }, { diff --git a/JellyfinPlayer/ConnectToServerView.swift b/JellyfinPlayer/ConnectToServerView.swift index 00c42e6e..a5ad623d 100644 --- a/JellyfinPlayer/ConnectToServerView.swift +++ b/JellyfinPlayer/ConnectToServerView.swift @@ -105,7 +105,7 @@ struct ConnectToServerView: View { } } } else { - Section(header: Text("Server Information")) { + Section(header: Text("Manual Connection")) { TextField("Jellyfin Server URL", text: $uri) .disableAutocorrection(true) .autocapitalization(.none) @@ -123,23 +123,20 @@ struct ConnectToServerView: View { .disabled(viewModel.isLoading || uri.isEmpty) } - Section(header: Text("Local Servers")) { + Section(header: Text("Discovered Servers")) { if self.viewModel.searching { ProgressView() } ForEach(self.viewModel.servers, id: \.id) { server in Button(action: { - print(server.url) viewModel.connectToServer(at: server.url) }, label: { HStack { - VStack { Text(server.name) .font(.headline) - Text(server.host) + Text("• \(server.host)") .font(.subheadline) - - } + .foregroundColor(.secondary) Spacer() if viewModel.isLoading { ProgressView() diff --git a/JellyfinPlayer/ContinueWatchingView.swift b/JellyfinPlayer/ContinueWatchingView.swift index f9556d9a..d5bb2129 100644 --- a/JellyfinPlayer/ContinueWatchingView.swift +++ b/JellyfinPlayer/ContinueWatchingView.swift @@ -43,19 +43,6 @@ struct ContinueWatchingView: View { .frame(width: 320, height: 180) .cornerRadius(10) .shadow(radius: 4) - .overlay( - Group { - if item.type == "Episode" { - Text("\(item.name ?? "")") - .font(.caption) - .padding(6) - .foregroundColor(.white) - } - }.background(Color.black) - .opacity(0.8) - .cornerRadius(10.0) - .padding(6), alignment: .topTrailing - ) .overlay( Rectangle() .fill(Color(red: 172/255, green: 92/255, blue: 195/255)) @@ -63,12 +50,22 @@ struct ContinueWatchingView: View { .frame(width: CGFloat((item.userData?.playedPercentage ?? 0) * 3.2), height: 7) .padding(0), alignment: .bottomLeading ) - Text(item.seriesName ?? item.name ?? "") - .font(.callout) - .fontWeight(.semibold) - .foregroundColor(.primary) - .lineLimit(1) - .frame(width: 320, alignment: .leading) + HStack { + Text("\(item.seriesName ?? item.name ?? "")") + .font(.callout) + .fontWeight(.semibold) + .foregroundColor(.primary) + .lineLimit(1) + if(item.type == "Episode") { + Text("• S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0)) - \(item.name ?? "")") + .font(.callout) + .fontWeight(.semibold) + .foregroundColor(.secondary) + .lineLimit(1) + .offset(x: -1.4) + } + Spacer() + }.frame(width: 320, alignment: .leading) }.padding(.top, 10) .padding(.bottom, 5) } diff --git a/JellyfinPlayer/HomeView.swift b/JellyfinPlayer/HomeView.swift index afeec96e..e7f5b220 100644 --- a/JellyfinPlayer/HomeView.swift +++ b/JellyfinPlayer/HomeView.swift @@ -20,7 +20,7 @@ struct HomeView: View { ProgressView() } else { ScrollView { - LazyVStack(alignment: .leading) { + VStack(alignment: .leading) { if !viewModel.resumeItems.isEmpty { ContinueWatchingView(items: viewModel.resumeItems) } @@ -57,7 +57,6 @@ struct HomeView: View { var body: some View { innerBody .navigationTitle(MainTabView.Tab.home.localized) - /* .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { Button { @@ -70,6 +69,5 @@ struct HomeView: View { .fullScreenCover(isPresented: $showingSettings) { SettingsView(viewModel: SettingsViewModel(), close: $showingSettings) } - */ } } diff --git a/JellyfinPlayer/LatestMediaView.swift b/JellyfinPlayer/LatestMediaView.swift index 6fa24521..8d9e8402 100644 --- a/JellyfinPlayer/LatestMediaView.swift +++ b/JellyfinPlayer/LatestMediaView.swift @@ -21,6 +21,17 @@ struct LatestMediaView: View { .frame(width: 100, height: 150) .cornerRadius(10) .shadow(radius: 4) + .overlay( + ZStack { + if(item.userData!.played ?? false) { + Image(systemName: "circle.fill") + .foregroundColor(.white) + Image(systemName: "checkmark.circle.fill") + .foregroundColor(Color(.systemBlue)) + } + }.padding(2) + .opacity(1) + , alignment: .topTrailing).opacity(1) Text(item.seriesName ?? item.name ?? "") .font(.caption) .fontWeight(.semibold) diff --git a/JellyfinPlayer/LibraryFilterView.swift b/JellyfinPlayer/LibraryFilterView.swift index 0719d7d2..89ecb7e2 100644 --- a/JellyfinPlayer/LibraryFilterView.swift +++ b/JellyfinPlayer/LibraryFilterView.swift @@ -21,45 +21,54 @@ struct LibraryFilterView: View { var body: some View { NavigationView { - ZStack { - Form { - if viewModel.enabledFilterType.contains(.genre) { - MultiSelector(label: "Genres", - options: viewModel.possibleGenres, - optionToString: { $0.name ?? "" }, - selected: $viewModel.modifiedFilters.withGenres) - } - if viewModel.enabledFilterType.contains(.filter) { - MultiSelector(label: "Filters", - options: viewModel.possibleItemFilters, - optionToString: { $0.localized }, - selected: $viewModel.modifiedFilters.filters) - } - if viewModel.enabledFilterType.contains(.tag) { - MultiSelector(label: "Tags", - options: viewModel.possibleTags, - optionToString: { $0 }, - selected: $viewModel.modifiedFilters.tags) - } - if viewModel.enabledFilterType.contains(.sortBy) { - MultiSelector(label: "Sort by", - options: viewModel.possibleSortBys, - optionToString: { $0.localized }, - selected: $viewModel.modifiedFilters.sortBy) - } - if viewModel.enabledFilterType.contains(.sortOrder) { - Picker(selection: $viewModel.modifiedFilters.sortOrder, label: Text("Order")) { - ForEach(viewModel.possibleSortOrders, id: \.self) { so in - Text("\(so.rawValue)").tag(so.rawValue) + VStack { + if viewModel.isLoading { + ProgressView() + } else { + Form { + if viewModel.enabledFilterType.contains(.genre) { + MultiSelector(label: "Genres", + options: viewModel.possibleGenres, + optionToString: { $0.name ?? "" }, + selected: $viewModel.modifiedFilters.withGenres) + } + if viewModel.enabledFilterType.contains(.filter) { + MultiSelector(label: "Filters", + options: viewModel.possibleItemFilters, + optionToString: { $0.localized }, + selected: $viewModel.modifiedFilters.filters) + } + if viewModel.enabledFilterType.contains(.tag) { + MultiSelector(label: "Tags", + options: viewModel.possibleTags, + optionToString: { $0 }, + selected: $viewModel.modifiedFilters.tags) + } + if viewModel.enabledFilterType.contains(.sortBy) { + Picker(selection: $viewModel.selectedSortBy, label: Text("Sort by")) { + ForEach(viewModel.possibleSortBys, id: \.self) { so in + Text(so.localized).tag(so) + } + } + } + if viewModel.enabledFilterType.contains(.sortOrder) { + Picker(selection: $viewModel.selectedSortOrder, label: Text("Display order")) { + ForEach(viewModel.possibleSortOrders, id: \.self) { so in + Text(so.rawValue).tag(so) + } } } } - } - if viewModel.isLoading { - ProgressView() + Button { + viewModel.resetFilters() + self.filters = viewModel.modifiedFilters + presentationMode.wrappedValue.dismiss() + } label: { + Text("Reset") + } } } - .navigationBarTitle("Filters", displayMode: .inline) + .navigationBarTitle("Filter Results", displayMode: .inline) .toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { Button { @@ -70,6 +79,7 @@ struct LibraryFilterView: View { } ToolbarItemGroup(placement: .navigationBarTrailing) { Button { + viewModel.updateModifiedFilter() self.filters = viewModel.modifiedFilters presentationMode.wrappedValue.dismiss() } label: { diff --git a/JellyfinPlayer/LibraryListView.swift b/JellyfinPlayer/LibraryListView.swift index 9cf11ecd..5a1faef9 100644 --- a/JellyfinPlayer/LibraryListView.swift +++ b/JellyfinPlayer/LibraryListView.swift @@ -74,6 +74,7 @@ struct LibraryListView: View { }.padding(32) }.background(Color.black) .frame(minWidth: 100, maxWidth: .infinity) + .frame(height: 72) } .cornerRadius(10) .shadow(radius: 5) diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index bf086554..5a514538 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -35,6 +35,17 @@ struct LibrarySearchView: View { ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash()) .frame(width: 100, height: 150) .cornerRadius(10) + .overlay( + ZStack { + if(item.userData!.played ?? false) { + Image(systemName: "circle.fill") + .foregroundColor(.white) + Image(systemName: "checkmark.circle.fill") + .foregroundColor(Color(.systemBlue)) + } + }.padding(2) + .opacity(1) + , alignment: .topTrailing).opacity(1) Text(item.name ?? "") .font(.caption) .fontWeight(.semibold) diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index 0cc9004b..bd0b5bea 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -13,6 +13,7 @@ struct LibraryView: View { var title: String // MARK: tracks for grid + var defaultFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name]) @State var isShowingSearchView = false @State var isShowingFilterView = false @@ -38,6 +39,17 @@ struct LibraryView: View { ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash()) .frame(width: 100, height: 150) .cornerRadius(10) + .overlay( + ZStack { + if(item.userData!.played ?? false) { + Image(systemName: "circle.fill") + .foregroundColor(.white) + Image(systemName: "checkmark.circle.fill") + .foregroundColor(Color(.systemBlue)) + } + }.padding(2) + .opacity(1) + , alignment: .topTrailing).opacity(1) Text(item.name ?? "") .font(.caption) .fontWeight(.semibold) @@ -107,14 +119,14 @@ struct LibraryView: View { Image(systemName: "chevron.right") } } - Button(action: { + Label("Icon One", systemImage: "line.horizontal.3.decrease.circle") + .foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange)) + .onTapGesture { isShowingFilterView = true - }) { - Image(systemName: "line.horizontal.3.decrease.circle") } - Button(action: { + Button { isShowingSearchView = true - }) { + } label: { Image(systemName: "magnifyingglass") } } diff --git a/JellyfinPlayer/SeasonItemView.swift b/JellyfinPlayer/SeasonItemView.swift index 7215aa7d..2939fd3f 100644 --- a/JellyfinPlayer/SeasonItemView.swift +++ b/JellyfinPlayer/SeasonItemView.swift @@ -76,6 +76,17 @@ struct SeasonItemView: View { .frame(width: CGFloat(episode.userData!.playedPercentage ?? 0 * 1.5), height: 7) .padding(0), alignment: .bottomLeading ) + .overlay( + ZStack { + if(episode.userData!.played ?? false) { + Image(systemName: "circle.fill") + .foregroundColor(.white) + Image(systemName: "checkmark.circle.fill") + .foregroundColor(Color(.systemBlue)) + } + }.padding(2) + .opacity(1) + , alignment: .topTrailing).opacity(1) VStack(alignment: .leading) { HStack { Text("S\(String(episode.parentIndexNumber ?? 0)):E\(String(episode.indexNumber ?? 0))").font(.subheadline) diff --git a/JellyfinPlayer/SettingsView.swift b/JellyfinPlayer/SettingsView.swift index 1dd2035b..333bd721 100644 --- a/JellyfinPlayer/SettingsView.swift +++ b/JellyfinPlayer/SettingsView.swift @@ -65,6 +65,10 @@ struct SettingsView: View { Text("Signed in as \(username)").foregroundColor(.primary) Spacer() Button { + let nc = NotificationCenter.default + nc.post(name: Notification.Name("didSignOut"), object: nil) + + SessionManager.current.logout() } label: { Text("Log out").font(.callout) diff --git a/Shared/Extensions/ImageView.swift b/Shared/Extensions/ImageView.swift index 70b084a2..0ce4d739 100644 --- a/Shared/Extensions/ImageView.swift +++ b/Shared/Extensions/ImageView.swift @@ -24,14 +24,16 @@ struct ImageView: View { } var body: some View { - LazyImage(source: source) - .placeholder { + LazyImage(source: source) { state in + if let image = state.image { + image + } else if state.error != nil { + Rectangle() + .fill(Color.gray) + } else { Image(uiImage: UIImage(blurHash: blurhash, size: CGSize(width: 16, height: 16))!) .resizable() } - .failure { - Rectangle() - .fill(Color.gray) - } + } } } diff --git a/Shared/ServerLocator/ServerDiscovery.swift b/Shared/ServerLocator/ServerDiscovery.swift index bc8d96ed..d35a44e7 100644 --- a/Shared/ServerLocator/ServerDiscovery.swift +++ b/Shared/ServerLocator/ServerDiscovery.swift @@ -66,11 +66,9 @@ public class ServerDiscovery { public init() { func receiveHandler(_ ipAddress: String, _ port: Int, _ response: Data) { - print("RECIEVED \(ipAddress):\(String(port)) \(response)") } func errorHandler(error: UDPBroadcastConnection.ConnectionError) { - print(error) } self.broadcastConn = try! UDPBroadcastConnection(port: 7359, handler: receiveHandler, errorHandler: errorHandler) } @@ -81,7 +79,6 @@ public class ServerDiscovery { let response = try JSONDecoder().decode(ServerLookupResponse.self, from: data) completion(response) } catch { - print(error) completion(nil) } } diff --git a/Shared/ServerLocator/UDPBroadCastConnection.swift b/Shared/ServerLocator/UDPBroadCastConnection.swift index f6fdc9d0..d28ab54e 100644 --- a/Shared/ServerLocator/UDPBroadCastConnection.swift +++ b/Shared/ServerLocator/UDPBroadCastConnection.swift @@ -123,7 +123,7 @@ open class UDPBroadcastConnection { // Set up cancel handler newResponseSource.setCancelHandler { - debugPrint("Closing UDP socket") + //debugPrint("Closing UDP socket") let UDPSocket = Int32(newResponseSource.handle) shutdown(UDPSocket, SHUT_RDWR) close(UDPSocket) @@ -158,12 +158,12 @@ open class UDPBroadcastConnection { guard let endpoint = withUnsafePointer(to: &socketAddress, { self.getEndpointFromSocketAddress(socketAddressPointer: UnsafeRawPointer($0).bindMemory(to: sockaddr.self, capacity: 1)) }) else { - debugPrint("Failed to get the address and port from the socket address received from recvfrom") + //debugPrint("Failed to get the address and port from the socket address received from recvfrom") self.closeConnection() return } - debugPrint("UDP connection received \(bytesRead) bytes from \(endpoint.host):\(endpoint.port)") + //debugPrint("UDP connection received \(bytesRead) bytes from \(endpoint.host):\(endpoint.port)") let responseBytes = Data(response[0.. 0 else { if let errorString = String(validatingUTF8: strerror(errno)) { - debugPrint("UDP connection failed to send data: \(errorString)") + //debugPrint("UDP connection failed to send data: \(errorString)") } closeConnection() throw ConnectionError.sendingMessageFailed(code: errno) @@ -221,7 +221,7 @@ open class UDPBroadcastConnection { if sent == broadcastMessageLength { // Success - debugPrint("UDP connection sent \(broadcastMessageLength) bytes") + //debugPrint("UDP connection sent \(broadcastMessageLength) bytes") } } } diff --git a/Shared/Typings/Typings.swift b/Shared/Typings/Typings.swift index fa220d70..99f9374c 100644 --- a/Shared/Typings/Typings.swift +++ b/Shared/Typings/Typings.swift @@ -18,7 +18,6 @@ struct LibraryFilters: Codable, Hashable { } public enum SortBy: String, Codable, CaseIterable { - case productionYear = "ProductionYear" case premiereDate = "PremiereDate" case name = "SortName" case dateAdded = "DateCreated" @@ -27,14 +26,12 @@ public enum SortBy: String, Codable, CaseIterable { extension SortBy { var localized: String { switch self { - case .productionYear: - return "Release Year" case .premiereDate: return "Premiere date" case .name: - return "Title" + return "Name" case .dateAdded: - return "Date Added" + return "Date added" } } } diff --git a/Shared/ViewModels/LibraryFilterViewModel.swift b/Shared/ViewModels/LibraryFilterViewModel.swift index d15dd52f..8b38372c 100644 --- a/Shared/ViewModels/LibraryFilterViewModel.swift +++ b/Shared/ViewModels/LibraryFilterViewModel.swift @@ -35,10 +35,26 @@ final class LibraryFilterViewModel: ViewModel { var possibleItemFilters = ItemFilter.supportedTypes @Published var enabledFilterType: [FilterType] + @Published + var selectedSortOrder: APISortOrder = .descending + @Published + var selectedSortBy: SortBy = .name + + func updateModifiedFilter() { + modifiedFilters.sortOrder = [selectedSortOrder] + modifiedFilters.sortBy = [selectedSortBy] + } + + func resetFilters() { + modifiedFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name]) + } init(filters: LibraryFilters? = nil, enabledFilterType: [FilterType] = [.tag, .genre, .sortBy, .sortOrder, .filter]) { self.enabledFilterType = enabledFilterType + self.selectedSortBy = filters!.sortBy.first! + self.selectedSortOrder = filters!.sortOrder.first! + super.init() if let filters = filters { self.modifiedFilters = filters