Skip to content

Commit 13a767a

Browse files
committed
Implement search result sorting
1 parent 2348b63 commit 13a767a

5 files changed

Lines changed: 122 additions & 2 deletions

File tree

qBitControl.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@
7474
9082FA2A29080E55006C3960 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9082FA2929080E55006C3960 /* Preview Assets.xcassets */; };
7575
9082FA4D29082208006C3960 /* qBittorrentClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9082FA4C29082208006C3960 /* qBittorrentClass.swift */; };
7676
909EE5A22CDD3B51002403DF /* RSSNodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 909EE5A12CDD3B4E002403DF /* RSSNodeViewModel.swift */; };
77+
90AB036C2DB16AE6009870BB /* SearchFiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90AB036B2DB16ADD009870BB /* SearchFiltersView.swift */; };
78+
90AB036E2DB16D5E009870BB /* SearchSortOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90AB036D2DB16D51009870BB /* SearchSortOptions.swift */; };
7779
90AE89322CDB6F00000E0276 /* RSSViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90AE89312CDB6EFC000E0276 /* RSSViewModel.swift */; };
7880
90AE89342CDB731A000E0276 /* RSSNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90AE89332CDB7316000E0276 /* RSSNode.swift */; };
7981
90AE89392CDBEC4D000E0276 /* RSSNodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90AE89382CDBEC47000E0276 /* RSSNodeView.swift */; };
@@ -177,6 +179,8 @@
177179
9082FA3929080E55006C3960 /* qBitControlUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = qBitControlUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
178180
9082FA4C29082208006C3960 /* qBittorrentClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = qBittorrentClass.swift; sourceTree = "<group>"; };
179181
909EE5A12CDD3B4E002403DF /* RSSNodeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSNodeViewModel.swift; sourceTree = "<group>"; };
182+
90AB036B2DB16ADD009870BB /* SearchFiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFiltersView.swift; sourceTree = "<group>"; };
183+
90AB036D2DB16D51009870BB /* SearchSortOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSortOptions.swift; sourceTree = "<group>"; };
180184
90AE89312CDB6EFC000E0276 /* RSSViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSViewModel.swift; sourceTree = "<group>"; };
181185
90AE89332CDB7316000E0276 /* RSSNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSNode.swift; sourceTree = "<group>"; };
182186
90AE89382CDBEC47000E0276 /* RSSNodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSNodeView.swift; sourceTree = "<group>"; };
@@ -264,6 +268,7 @@
264268
90726B1E2CDA22160032993E /* Models */ = {
265269
isa = PBXGroup;
266270
children = (
271+
90AB036D2DB16D51009870BB /* SearchSortOptions.swift */,
267272
901230532DB02EAA0022FD4C /* SearchResult.swift */,
268273
901230512DB02E870022FD4C /* SearchResponse.swift */,
269274
907C63292DAEDAC7004B1F41 /* SearchStartResult.swift */,
@@ -392,6 +397,7 @@
392397
907C63272DAED94A004B1F41 /* SearchViews */ = {
393398
isa = PBXGroup;
394399
children = (
400+
90AB036B2DB16ADD009870BB /* SearchFiltersView.swift */,
395401
901230552DB0370F0022FD4C /* SearchRowView.swift */,
396402
907C63262DAED94A004B1F41 /* SearchView.swift */,
397403
);
@@ -650,9 +656,11 @@
650656
901230522DB02E8B0022FD4C /* SearchResponse.swift in Sources */,
651657
90726B222CDA22160032993E /* File.swift in Sources */,
652658
90726B752CDA221B0032993E /* ServerAddViewModel.swift in Sources */,
659+
90AB036C2DB16AE6009870BB /* SearchFiltersView.swift in Sources */,
653660
90726B762CDA221B0032993E /* TorrentAddViewModel.swift in Sources */,
654661
90726B772CDA221B0032993E /* TorrentListHelperViewModel.swift in Sources */,
655662
907023342CDA657A007B2199 /* ChangeCategoryView.swift in Sources */,
663+
90AB036E2DB16D5E009870BB /* SearchSortOptions.swift in Sources */,
656664
905284982D457618000EB12C /* ChangeTagsView.swift in Sources */,
657665
90726B782CDA221B0032993E /* MainViewModel.swift in Sources */,
658666
90726B7A2CDA24960032993E /* TrackersViewModel.swift in Sources */,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
enum SearchSortOptions: String, CaseIterable {
2+
case name = "name"
3+
case size = "size"
4+
case seeders = "seeders"
5+
case leechers = "leechers"
6+
}

qBitControl/ViewModels/SearchView/SearchViewModel.swift

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,36 @@ import SwiftUI
33
class SearchViewModel: ObservableObject {
44
@Published var query: String = ""
55
@Published var category: String = "all"
6+
@Published var sortBy: SearchSortOptions = .seeders
7+
@Published var isDescending: Bool = true
68

79
@Published var searchId: Int?
810

11+
@Published var isFilterSheet: Bool = false
12+
913
@Published var latestResponse: SearchResponse?
1014

15+
init() {
16+
self.loadFilters()
17+
}
18+
1119
var latestResults: [SearchResult] {
12-
self.latestResponse?.results ?? []
20+
if let latestResponse = self.latestResponse {
21+
var results = latestResponse.results
22+
results = results.sorted(by: sorter)
23+
results = isDescending ? results.reversed() : results
24+
return results
25+
}
26+
27+
return []
1328
}
1429

1530
var lastestTotal: Int {
1631
self.latestResponse?.total ?? 0
1732
}
1833

1934
var isResponse: Bool {
20-
self.latestResponse != nil
35+
self.lastestTotal > 0
2136
}
2237

2338
var isRunning: Bool {
@@ -28,6 +43,7 @@ class SearchViewModel: ObservableObject {
2843

2944
func startSearch() {
3045
if(isRunning) { return }
46+
if(query.isEmpty) { return }
3147

3248
qBittorrent.getSearchStart(pattern: self.query, category: self.category, completionHandler: { result in
3349
DispatchQueue.main.async {
@@ -67,4 +83,48 @@ class SearchViewModel: ObservableObject {
6783
self.timer?.invalidate()
6884
}
6985
}
86+
87+
private func sorter(res1: SearchResult, res2: SearchResult) -> Bool {
88+
switch(sortBy) {
89+
case .name:
90+
return self.compareValues(res1.fileName, res2.fileName)
91+
case .size:
92+
return self.compareValues(res1.fileSize, res2.fileSize)
93+
case .seeders:
94+
return self.compareValues(res1.nbSeeders, res2.nbSeeders)
95+
case .leechers:
96+
return self.compareValues(res1.nbLeechers, res2.nbLeechers)
97+
}
98+
}
99+
100+
private func compareValues<T: Comparable>(_ a: T?, _ b: T?) -> Bool {
101+
if let a = a, let b = b {
102+
return a < b
103+
}
104+
105+
return false
106+
}
107+
108+
private func prepareKey(_ name: String) -> String {
109+
return "searchViewModel-\(name)"
110+
}
111+
112+
func saveFilters() {
113+
let defaults = UserDefaults.standard
114+
115+
defaults.set(sortBy.rawValue, forKey: self.prepareKey("sortBy"))
116+
defaults.set(isDescending, forKey: self.prepareKey("isDescending"))
117+
print(isDescending)
118+
}
119+
120+
private func loadFilters() {
121+
let defaults = UserDefaults.standard
122+
123+
if let sortBy = defaults.string(forKey: self.prepareKey("sortBy")) {
124+
self.sortBy = SearchSortOptions(rawValue: sortBy) ?? self.sortBy
125+
}
126+
127+
self.isDescending = defaults.bool(forKey: self.prepareKey("isDescending"))
128+
print(self.isDescending)
129+
}
70130
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import SwiftUI
2+
3+
struct SearchFiltersView: View {
4+
@Environment(\.dismiss) var dismiss
5+
@ObservedObject var viewModel: SearchViewModel
6+
7+
var body: some View {
8+
NavigationStack {
9+
List {
10+
Section(header: Text("Descending")) {
11+
Toggle(isOn: $viewModel.isDescending, label: { Text("Descending") })
12+
.onChange(of: viewModel.isDescending) { _ in
13+
viewModel.saveFilters()
14+
}
15+
}
16+
17+
Picker("Sort By", selection: $viewModel.sortBy) {
18+
ForEach(SearchSortOptions.allCases, id: \.self) { option in
19+
Text(LocalizedStringResource(stringLiteral: option.rawValue.capitalized)).tag(option)
20+
}
21+
}.pickerStyle(.inline)
22+
.onChange(of: viewModel.sortBy) { _ in
23+
viewModel.saveFilters()
24+
}
25+
}.toolbar {
26+
ToolbarItem(placement: .topBarTrailing) {
27+
Button {
28+
self.dismiss()
29+
} label: {
30+
Text("Done")
31+
}
32+
}
33+
}
34+
}
35+
}
36+
}

qBitControl/Views/SearchViews/SearchView.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ struct SearchView: View {
4040
Text("No results")
4141
}.foregroundStyle(.gray)
4242
}
43+
}.toolbar {
44+
ToolbarItem(placement: .topBarTrailing) {
45+
Button {
46+
self.viewModel.isFilterSheet.toggle()
47+
} label: {
48+
Image(systemName: "line.3.horizontal.decrease.circle")
49+
}
50+
}
51+
}.sheet(isPresented: $viewModel.isFilterSheet) {
52+
SearchFiltersView(viewModel: viewModel)
4353
}
4454
}
4555
}

0 commit comments

Comments
 (0)