Filters: Add result sorting
Sort by seeders, leechers, and size. Also supports ascending and descending options. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
dc3014095c
commit
f7d2f1ce60
7 changed files with 153 additions and 1 deletions
|
|
@ -21,6 +21,7 @@
|
|||
0C1A3E5629C9488C00DA9730 /* CodableWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */; };
|
||||
0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */; };
|
||||
0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */; };
|
||||
0C2B028F29E9E61E00DCF127 /* SortFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2B028E29E9E61E00DCF127 /* SortFilterView.swift */; };
|
||||
0C2D9653299316CC00A504B6 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2D9652299316CC00A504B6 /* Tag.swift */; };
|
||||
0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */; };
|
||||
0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */; };
|
||||
|
|
@ -170,6 +171,7 @@
|
|||
0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableWrapper.swift; sourceTree = "<group>"; };
|
||||
0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridCloudView.swift; sourceTree = "<group>"; };
|
||||
0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealDebridCloudView.swift; sourceTree = "<group>"; };
|
||||
0C2B028E29E9E61E00DCF127 /* SortFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortFilterView.swift; sourceTree = "<group>"; };
|
||||
0C2D9652299316CC00A504B6 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
||||
0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceJsonParser+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceJsonParser+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
|
|
@ -510,6 +512,7 @@
|
|||
0C84FCE429E4B43200B0DFE4 /* SelectedDebridFilterView.swift */,
|
||||
0C871BDE29994D9D005279AC /* FilterLabelView.swift */,
|
||||
0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */,
|
||||
0C2B028E29E9E61E00DCF127 /* SortFilterView.swift */,
|
||||
);
|
||||
path = Filters;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -899,6 +902,7 @@
|
|||
0C0167DC29293FA900B65783 /* RealDebridModels.swift in Sources */,
|
||||
0C57D4CC289032ED008534E8 /* SearchResultInfoView.swift in Sources */,
|
||||
0C42B5982932F6DD008057A0 /* Set.swift in Sources */,
|
||||
0C2B028F29E9E61E00DCF127 /* SortFilterView.swift in Sources */,
|
||||
0C41BC6328C2AD0F00B47DD6 /* SearchResultButtonView.swift in Sources */,
|
||||
0CA05459288EE9E600850554 /* PluginManager.swift in Sources */,
|
||||
0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */,
|
||||
|
|
|
|||
|
|
@ -12,3 +12,9 @@ enum FilterType {
|
|||
case IA
|
||||
case sort
|
||||
}
|
||||
|
||||
enum SortFilter: String, Hashable, CaseIterable {
|
||||
case seeders = "Seeders"
|
||||
case leechers = "Leechers"
|
||||
case size = "Size"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,40 @@ public struct SearchResult: Codable, Hashable, Sendable {
|
|||
let magnet: Magnet
|
||||
let seeders: String?
|
||||
let leechers: String?
|
||||
|
||||
// Converts size to a double
|
||||
func rawSize() -> Double? {
|
||||
guard let size else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let splitSize = size.split(separator: " ")
|
||||
|
||||
guard
|
||||
let bytesString = splitSize.first,
|
||||
let multipliedBytes = Double(bytesString),
|
||||
let units = splitSize.last
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch units.lowercased() {
|
||||
case "gb":
|
||||
return multipliedBytes * 1e9
|
||||
case "gib":
|
||||
return multipliedBytes * pow(1024, 3)
|
||||
case "mb":
|
||||
return multipliedBytes * 1e6
|
||||
case "mib":
|
||||
return multipliedBytes * pow(1024, 2)
|
||||
case "kb":
|
||||
return multipliedBytes * 1e3
|
||||
case "kib":
|
||||
return multipliedBytes * 1024
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ScrapingViewModel {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,33 @@ public class NavigationViewModel: ObservableObject {
|
|||
|
||||
// For filters
|
||||
@Published var enabledFilters: Set<FilterType> = []
|
||||
@Published var currentSortFilter: SortFilter?
|
||||
@Published var currentSortOrder: SortOrder = .forward
|
||||
|
||||
public func compareSearchResult(lhs: SearchResult, rhs: SearchResult) -> Bool {
|
||||
switch currentSortFilter {
|
||||
case .leechers:
|
||||
guard let lhsLeechers = lhs.leechers, let rhsLeechers = rhs.leechers else {
|
||||
return false
|
||||
}
|
||||
|
||||
return currentSortOrder == .forward ? lhsLeechers > rhsLeechers : lhsLeechers < rhsLeechers
|
||||
case .seeders:
|
||||
guard let lhsSeeders = lhs.seeders, let rhsSeeders = rhs.seeders else {
|
||||
return false
|
||||
}
|
||||
|
||||
return currentSortOrder == .forward ? lhsSeeders > rhsSeeders : lhsSeeders < rhsSeeders
|
||||
case .size:
|
||||
guard let lhsSize = lhs.rawSize(), let rhsSize = rhs.rawSize() else {
|
||||
return false
|
||||
}
|
||||
|
||||
return currentSortOrder == .forward ? lhsSize > rhsSize : lhsSize < rhsSize
|
||||
case .none:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Published var kodiExpanded: Bool = false
|
||||
|
||||
|
|
|
|||
72
Ferrite/Views/ComponentViews/Filters/SortFilterView.swift
Normal file
72
Ferrite/Views/ComponentViews/Filters/SortFilterView.swift
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// SortFilterView.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 4/14/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SortFilterView: View {
|
||||
@EnvironmentObject var navModel: NavigationViewModel
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
Button {
|
||||
navModel.currentSortFilter = nil
|
||||
navModel.currentSortOrder = .forward
|
||||
} label: {
|
||||
HStack {
|
||||
Text("None")
|
||||
|
||||
if navModel.currentSortFilter == nil {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ForEach(SortFilter.allCases, id: \.self) { sortFilter in
|
||||
Button {
|
||||
navModel.currentSortFilter = sortFilter
|
||||
navModel.currentSortOrder = navModel.currentSortOrder == .forward ? .reverse : .forward
|
||||
} label: {
|
||||
HStack {
|
||||
Text(sortFilter.rawValue)
|
||||
|
||||
if navModel.currentSortFilter == sortFilter {
|
||||
Image(systemName: navModel.currentSortOrder == .forward ? "chevron.down" : "chevron.up")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
FilterLabelView(
|
||||
name: "Sort\(navModel.currentSortFilter.map { ": \($0.rawValue)" } ?? "")",
|
||||
count: navModel.currentSortFilter == nil ? 0 : 1
|
||||
)
|
||||
}
|
||||
.id(navModel.currentSortFilter)
|
||||
.onChange(of: navModel.currentSortFilter) { newFilter in
|
||||
navModel.currentSortOrder = .forward
|
||||
if newFilter == nil {
|
||||
navModel.enabledFilters.remove(.sort)
|
||||
} else {
|
||||
navModel.enabledFilters.insert(.sort)
|
||||
}
|
||||
}
|
||||
.onChange(of: navModel.enabledFilters) { newFilters in
|
||||
if newFilters.isEmpty {
|
||||
Task {
|
||||
try? await Task.sleep(seconds: 0.25)
|
||||
navModel.currentSortFilter = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SortFilterView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SortFilterView()
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +60,10 @@ struct SearchFilterHeaderView: View {
|
|||
if !debridManager.enabledDebrids.isEmpty {
|
||||
IAFilterView()
|
||||
}
|
||||
|
||||
// MARK: - Sort filter picker
|
||||
|
||||
SortFilterView()
|
||||
}
|
||||
.padding(.horizontal, verticalSizeClass == .compact ? 65 : 18)
|
||||
.animation(.easeInOut, value: navModel.enabledFilters)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,12 @@ struct SearchResultsView: View {
|
|||
@Binding var searchText: String
|
||||
|
||||
var body: some View {
|
||||
ForEach(scrapingModel.searchResults, id: \.self) { result in
|
||||
ForEach(
|
||||
scrapingModel.searchResults.sorted {
|
||||
navModel.compareSearchResult(lhs: $0, rhs: $1)
|
||||
}
|
||||
, id: \.self
|
||||
) { result in
|
||||
let debridIAStatus = debridManager.matchMagnetHash(result.magnet)
|
||||
if
|
||||
(pluginManager.filteredInstalledSources.isEmpty ||
|
||||
|
|
|
|||
Loading…
Reference in a new issue