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 */; };
|
0C1A3E5629C9488C00DA9730 /* CodableWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */; };
|
||||||
0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */; };
|
0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */; };
|
||||||
0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D62960C50900D6FC16 /* RealDebridCloudView.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 */; };
|
0C2D9653299316CC00A504B6 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2D9652299316CC00A504B6 /* Tag.swift */; };
|
||||||
0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceJsonParser+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||||
|
|
@ -510,6 +512,7 @@
|
||||||
0C84FCE429E4B43200B0DFE4 /* SelectedDebridFilterView.swift */,
|
0C84FCE429E4B43200B0DFE4 /* SelectedDebridFilterView.swift */,
|
||||||
0C871BDE29994D9D005279AC /* FilterLabelView.swift */,
|
0C871BDE29994D9D005279AC /* FilterLabelView.swift */,
|
||||||
0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */,
|
0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */,
|
||||||
|
0C2B028E29E9E61E00DCF127 /* SortFilterView.swift */,
|
||||||
);
|
);
|
||||||
path = Filters;
|
path = Filters;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -899,6 +902,7 @@
|
||||||
0C0167DC29293FA900B65783 /* RealDebridModels.swift in Sources */,
|
0C0167DC29293FA900B65783 /* RealDebridModels.swift in Sources */,
|
||||||
0C57D4CC289032ED008534E8 /* SearchResultInfoView.swift in Sources */,
|
0C57D4CC289032ED008534E8 /* SearchResultInfoView.swift in Sources */,
|
||||||
0C42B5982932F6DD008057A0 /* Set.swift in Sources */,
|
0C42B5982932F6DD008057A0 /* Set.swift in Sources */,
|
||||||
|
0C2B028F29E9E61E00DCF127 /* SortFilterView.swift in Sources */,
|
||||||
0C41BC6328C2AD0F00B47DD6 /* SearchResultButtonView.swift in Sources */,
|
0C41BC6328C2AD0F00B47DD6 /* SearchResultButtonView.swift in Sources */,
|
||||||
0CA05459288EE9E600850554 /* PluginManager.swift in Sources */,
|
0CA05459288EE9E600850554 /* PluginManager.swift in Sources */,
|
||||||
0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */,
|
0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,9 @@ enum FilterType {
|
||||||
case IA
|
case IA
|
||||||
case sort
|
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 magnet: Magnet
|
||||||
let seeders: String?
|
let seeders: String?
|
||||||
let leechers: 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 {
|
extension ScrapingViewModel {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,33 @@ public class NavigationViewModel: ObservableObject {
|
||||||
|
|
||||||
// For filters
|
// For filters
|
||||||
@Published var enabledFilters: Set<FilterType> = []
|
@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
|
@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 {
|
if !debridManager.enabledDebrids.isEmpty {
|
||||||
IAFilterView()
|
IAFilterView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Sort filter picker
|
||||||
|
|
||||||
|
SortFilterView()
|
||||||
}
|
}
|
||||||
.padding(.horizontal, verticalSizeClass == .compact ? 65 : 18)
|
.padding(.horizontal, verticalSizeClass == .compact ? 65 : 18)
|
||||||
.animation(.easeInOut, value: navModel.enabledFilters)
|
.animation(.easeInOut, value: navModel.enabledFilters)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,12 @@ struct SearchResultsView: View {
|
||||||
@Binding var searchText: String
|
@Binding var searchText: String
|
||||||
|
|
||||||
var body: some View {
|
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)
|
let debridIAStatus = debridManager.matchMagnetHash(result.magnet)
|
||||||
if
|
if
|
||||||
(pluginManager.filteredInstalledSources.isEmpty ||
|
(pluginManager.filteredInstalledSources.isEmpty ||
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue