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:
kingbri 2023-04-20 16:37:26 -04:00
parent dc3014095c
commit f7d2f1ce60
7 changed files with 153 additions and 1 deletions

View file

@ -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 */,

View file

@ -12,3 +12,9 @@ enum FilterType {
case IA
case sort
}
enum SortFilter: String, Hashable, CaseIterable {
case seeders = "Seeders"
case leechers = "Leechers"
case size = "Size"
}

View file

@ -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 {

View file

@ -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

View 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()
}
}

View file

@ -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)

View file

@ -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 ||