Library: Add searching and cleanup
Add a searchbar to filter through various library entries so it's easier to find items. Also add fixes for < iOS 16 devices and fix up searchbar constraints. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
2258036f7b
commit
e8f62e3cdc
13 changed files with 214 additions and 122 deletions
|
|
@ -32,4 +32,6 @@ public class Application {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let osVersion: OperatingSystemVersion = ProcessInfo().operatingSystemVersion
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,9 @@ public class DebridManager: ObservableObject {
|
||||||
} else {
|
} else {
|
||||||
throw RealDebrid.RDError.FailedRequest(description: "Could not fetch your file from RealDebrid's cache or API")
|
throw RealDebrid.RDError.FailedRequest(description: "Could not fetch your file from RealDebrid's cache or API")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch one more time to add updated data into the RD cloud cache
|
||||||
|
await fetchRdCloud(bypassTTL: true)
|
||||||
} catch {
|
} catch {
|
||||||
switch error {
|
switch error {
|
||||||
case RealDebrid.RDError.EmptyTorrents:
|
case RealDebrid.RDError.EmptyTorrents:
|
||||||
|
|
@ -640,6 +643,9 @@ public class DebridManager: ObservableObject {
|
||||||
} else {
|
} else {
|
||||||
throw AllDebrid.ADError.FailedRequest(description: "Could not fetch your file from AllDebrid's cache or API")
|
throw AllDebrid.ADError.FailedRequest(description: "Could not fetch your file from AllDebrid's cache or API")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch one more time to add updated data into the AD cloud cache
|
||||||
|
await fetchAdCloud(bypassTTL: true)
|
||||||
} catch {
|
} catch {
|
||||||
await sendDebridError(error, prefix: "AllDebrid download error", cancelString: "Download cancelled")
|
await sendDebridError(error, prefix: "AllDebrid download error", cancelString: "Download cancelled")
|
||||||
}
|
}
|
||||||
|
|
@ -650,7 +656,6 @@ public class DebridManager: ObservableObject {
|
||||||
if bypassTTL || Date().timeIntervalSince1970 > allDebridCloudTTL {
|
if bypassTTL || Date().timeIntervalSince1970 > allDebridCloudTTL {
|
||||||
do {
|
do {
|
||||||
allDebridCloudMagnets = try await allDebrid.userMagnets()
|
allDebridCloudMagnets = try await allDebrid.userMagnets()
|
||||||
realDebridCloudDownloads = try await realDebrid.userDownloads()
|
|
||||||
|
|
||||||
// 5 minutes
|
// 5 minutes
|
||||||
allDebridCloudTTL = Date().timeIntervalSince1970 + 300
|
allDebridCloudTTL = Date().timeIntervalSince1970 + 300
|
||||||
|
|
@ -685,6 +690,9 @@ public class DebridManager: ObservableObject {
|
||||||
throw Premiumize.PMError.FailedRequest(description: "There were no items or files found!")
|
throw Premiumize.PMError.FailedRequest(description: "There were no items or files found!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch one more time to add updated data into the PM cloud cache
|
||||||
|
await fetchPmCloud(bypassTTL: true)
|
||||||
|
|
||||||
// Add a PM transfer if the item exists
|
// Add a PM transfer if the item exists
|
||||||
if let premiumizeItem = selectedPremiumizeItem {
|
if let premiumizeItem = selectedPremiumizeItem {
|
||||||
try await premiumize.createTransfer(magnet: premiumizeItem.magnet)
|
try await premiumize.createTransfer(magnet: premiumizeItem.magnet)
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,15 @@ struct BookmarksView: View {
|
||||||
|
|
||||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
var bookmarks: FetchedResults<Bookmark>
|
@Binding var searchText: String
|
||||||
|
|
||||||
@State private var viewTask: Task<Void, Never>?
|
@State private var viewTask: Task<Void, Never>?
|
||||||
|
@State private var bookmarkPredicate: NSPredicate?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
DynamicFetchRequest(predicate: bookmarkPredicate) { (bookmarks: FetchedResults<Bookmark>) in
|
||||||
if !bookmarks.isEmpty {
|
List {
|
||||||
List {
|
if !bookmarks.isEmpty {
|
||||||
ForEach(bookmarks, id: \.self) { bookmark in
|
ForEach(bookmarks, id: \.self) { bookmark in
|
||||||
SearchResultButtonView(result: bookmark.toSearchResult(), existingBookmark: bookmark)
|
SearchResultButtonView(result: bookmark.toSearchResult(), existingBookmark: bookmark)
|
||||||
}
|
}
|
||||||
|
|
@ -30,43 +31,53 @@ struct BookmarksView: View {
|
||||||
for index in offsets {
|
for index in offsets {
|
||||||
if let bookmark = bookmarks[safe: index] {
|
if let bookmark = bookmarks[safe: index] {
|
||||||
PersistenceController.shared.delete(bookmark, context: backgroundContext)
|
PersistenceController.shared.delete(bookmark, context: backgroundContext)
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .didDeleteBookmark, object: bookmark)
|
NotificationCenter.default.post(name: .didDeleteBookmark, object: bookmark)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onMove { source, destination in
|
.onMove { source, destination in
|
||||||
var changedBookmarks = bookmarks.map { $0 }
|
var changedBookmarks = bookmarks.map { $0 }
|
||||||
|
|
||||||
changedBookmarks.move(fromOffsets: source, toOffset: destination)
|
changedBookmarks.move(fromOffsets: source, toOffset: destination)
|
||||||
|
|
||||||
for reverseIndex in stride(from: changedBookmarks.count - 1, through: 0, by: -1) {
|
for reverseIndex in stride(from: changedBookmarks.count - 1, through: 0, by: -1) {
|
||||||
changedBookmarks[reverseIndex].orderNum = Int16(reverseIndex)
|
changedBookmarks[reverseIndex].orderNum = Int16(reverseIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
PersistenceController.shared.save()
|
PersistenceController.shared.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.inlinedList()
|
}
|
||||||
.listStyle(.insetGrouped)
|
.inlinedList()
|
||||||
|
.listStyle(.insetGrouped)
|
||||||
|
.onAppear {
|
||||||
|
if debridManager.enabledDebrids.count > 0 {
|
||||||
|
viewTask = Task {
|
||||||
|
let magnets = bookmarks.compactMap {
|
||||||
|
if let magnetHash = $0.magnetHash {
|
||||||
|
return Magnet(hash: magnetHash, link: $0.magnetLink)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await debridManager.populateDebridIA(magnets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
viewTask?.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if debridManager.enabledDebrids.count > 0 {
|
applyPredicate()
|
||||||
viewTask = Task {
|
|
||||||
let magnets = bookmarks.compactMap {
|
|
||||||
if let magnetHash = $0.magnetHash {
|
|
||||||
return Magnet(hash: magnetHash, link: $0.magnetLink)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await debridManager.populateDebridIA(magnets)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onChange(of: searchText) { _ in
|
||||||
viewTask?.cancel()
|
applyPredicate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyPredicate() {
|
||||||
|
bookmarkPredicate = searchText.isEmpty ? nil : NSPredicate(format: "title CONTAINS[cd] %@", searchText)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,15 @@ struct AllDebridCloudView: View {
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
@EnvironmentObject var navModel: NavigationViewModel
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
|
|
||||||
|
@Binding var searchText: String
|
||||||
|
|
||||||
@State private var viewTask: Task<Void, Never>?
|
@State private var viewTask: Task<Void, Never>?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
DisclosureGroup("Magnets") {
|
DisclosureGroup("Magnets") {
|
||||||
ForEach(debridManager.allDebridCloudMagnets, id: \.id) { magnet in
|
ForEach(debridManager.allDebridCloudMagnets.filter {
|
||||||
|
searchText.isEmpty ? true : $0.filename.lowercased().contains(searchText.lowercased())
|
||||||
|
}, id: \.id) { magnet in
|
||||||
Button {
|
Button {
|
||||||
if magnet.status == "Ready" && !magnet.links.isEmpty {
|
if magnet.status == "Ready" && !magnet.links.isEmpty {
|
||||||
navModel.resultFromCloud = true
|
navModel.resultFromCloud = true
|
||||||
|
|
@ -38,8 +42,9 @@ struct AllDebridCloudView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debridManager.clearIAValues()
|
|
||||||
let magnet = Magnet(hash: magnet.hash, link: nil)
|
let magnet = Magnet(hash: magnet.hash, link: nil)
|
||||||
|
|
||||||
|
// Do not clear old IA values
|
||||||
await debridManager.populateDebridIA([magnet])
|
await debridManager.populateDebridIA([magnet])
|
||||||
|
|
||||||
if debridManager.selectDebridResult(magnet: magnet) {
|
if debridManager.selectDebridResult(magnet: magnet) {
|
||||||
|
|
@ -85,9 +90,3 @@ struct AllDebridCloudView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AllDebridCloudView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
AllDebridCloudView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,15 @@ struct PremiumizeCloudView: View {
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
@EnvironmentObject var navModel: NavigationViewModel
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
|
|
||||||
|
@Binding var searchText: String
|
||||||
|
|
||||||
@State private var viewTask: Task<Void, Never>?
|
@State private var viewTask: Task<Void, Never>?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
DisclosureGroup("Items") {
|
DisclosureGroup("Items") {
|
||||||
ForEach(debridManager.premiumizeCloudItems, id: \.id) { item in
|
ForEach(debridManager.premiumizeCloudItems.filter {
|
||||||
|
searchText.isEmpty ? true : $0.name.lowercased().contains(searchText.lowercased())
|
||||||
|
}, id: \.id) { item in
|
||||||
Button(item.name) {
|
Button(item.name) {
|
||||||
Task {
|
Task {
|
||||||
navModel.resultFromCloud = true
|
navModel.resultFromCloud = true
|
||||||
|
|
@ -60,9 +64,3 @@ struct PremiumizeCloudView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PremiumizeCloudView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
PremiumizeCloudView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,16 @@ struct RealDebridCloudView: View {
|
||||||
@EnvironmentObject var navModel: NavigationViewModel
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
|
|
||||||
|
@Binding var searchText: String
|
||||||
|
|
||||||
@State private var viewTask: Task<Void, Never>?
|
@State private var viewTask: Task<Void, Never>?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
DisclosureGroup("Downloads") {
|
DisclosureGroup("Downloads") {
|
||||||
ForEach(debridManager.realDebridCloudDownloads, id: \.self) { downloadResponse in
|
ForEach(debridManager.realDebridCloudDownloads.filter {
|
||||||
|
searchText.isEmpty ? true : $0.filename.lowercased().contains(searchText.lowercased())
|
||||||
|
}, id: \.self) { downloadResponse in
|
||||||
Button(downloadResponse.filename) {
|
Button(downloadResponse.filename) {
|
||||||
navModel.resultFromCloud = true
|
navModel.resultFromCloud = true
|
||||||
navModel.selectedTitle = downloadResponse.filename
|
navModel.selectedTitle = downloadResponse.filename
|
||||||
|
|
@ -46,7 +50,9 @@ struct RealDebridCloudView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
DisclosureGroup("Torrents") {
|
DisclosureGroup("Torrents") {
|
||||||
ForEach(debridManager.realDebridCloudTorrents, id: \.self) { torrentResponse in
|
ForEach(debridManager.realDebridCloudTorrents.filter {
|
||||||
|
searchText.isEmpty ? true : $0.filename.lowercased().contains(searchText.lowercased())
|
||||||
|
}, id: \.self) { torrentResponse in
|
||||||
Button {
|
Button {
|
||||||
if torrentResponse.status == "downloaded" && !torrentResponse.links.isEmpty {
|
if torrentResponse.status == "downloaded" && !torrentResponse.links.isEmpty {
|
||||||
navModel.resultFromCloud = true
|
navModel.resultFromCloud = true
|
||||||
|
|
@ -69,8 +75,9 @@ struct RealDebridCloudView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debridManager.clearIAValues()
|
|
||||||
let magnet = Magnet(hash: torrentResponse.hash, link: nil)
|
let magnet = Magnet(hash: torrentResponse.hash, link: nil)
|
||||||
|
|
||||||
|
// Do not clear old IA values
|
||||||
await debridManager.populateDebridIA([magnet])
|
await debridManager.populateDebridIA([magnet])
|
||||||
|
|
||||||
if debridManager.selectDebridResult(magnet: magnet) {
|
if debridManager.selectDebridResult(magnet: magnet) {
|
||||||
|
|
@ -119,9 +126,3 @@ struct RealDebridCloudView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RealDebridCloudView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
RealDebridCloudView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -10,30 +10,21 @@ import SwiftUI
|
||||||
struct DebridCloudView: View {
|
struct DebridCloudView: View {
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
|
|
||||||
|
@Binding var searchText: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavView {
|
List {
|
||||||
VStack {
|
switch debridManager.selectedDebridType {
|
||||||
List {
|
case .realDebrid:
|
||||||
switch debridManager.selectedDebridType {
|
RealDebridCloudView(searchText: $searchText)
|
||||||
case .realDebrid:
|
case .premiumize:
|
||||||
RealDebridCloudView()
|
PremiumizeCloudView(searchText: $searchText)
|
||||||
case .premiumize:
|
case .allDebrid:
|
||||||
PremiumizeCloudView()
|
AllDebridCloudView(searchText: $searchText)
|
||||||
case .allDebrid:
|
case .none:
|
||||||
AllDebridCloudView()
|
EmptyView()
|
||||||
case .none:
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.inlinedList()
|
|
||||||
.listStyle(.grouped)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.listStyle(.plain)
|
||||||
}
|
|
||||||
|
|
||||||
struct DebridCloudView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
DebridCloudView()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ struct HistoryButtonView: View {
|
||||||
toastModel.updateToastDescription("URL invalid. Cannot load this history entry. Please delete it.")
|
toastModel.updateToastDescription("URL invalid. Cannot load this history entry. Please delete it.")
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(entry.name ?? "Unknown title")
|
Text(entry.name ?? "Unknown title")
|
||||||
.font(entry.subName == nil ? .body : .subheadline)
|
.font(entry.subName == nil ? .body : .subheadline)
|
||||||
|
|
|
||||||
|
|
@ -10,44 +10,89 @@ import SwiftUI
|
||||||
struct HistoryView: View {
|
struct HistoryView: View {
|
||||||
@EnvironmentObject var navModel: NavigationViewModel
|
@EnvironmentObject var navModel: NavigationViewModel
|
||||||
|
|
||||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
|
||||||
|
|
||||||
var history: FetchedResults<History>
|
var history: FetchedResults<History>
|
||||||
var formatter: DateFormatter = .init()
|
|
||||||
|
|
||||||
@State private var historyIndex = 0
|
@Binding var searchText: String
|
||||||
|
|
||||||
init(history: FetchedResults<History>) {
|
@State private var historyPredicate: NSPredicate?
|
||||||
self.history = history
|
|
||||||
|
|
||||||
formatter.dateStyle = .medium
|
|
||||||
formatter.timeStyle = .none
|
|
||||||
}
|
|
||||||
|
|
||||||
func groupedEntries(_ result: FetchedResults<History>) -> [[History]] {
|
|
||||||
Dictionary(grouping: result) { (element: History) in
|
|
||||||
element.dateString ?? ""
|
|
||||||
}.values.sorted { $0[0].date ?? Date() > $1[0].date ?? Date() }
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if !history.isEmpty {
|
DynamicFetchRequest(predicate: historyPredicate) { (allEntries: FetchedResults<HistoryEntry>) in
|
||||||
List {
|
List {
|
||||||
ForEach(groupedEntries(history), id: \.self) { (section: [History]) in
|
if !history.isEmpty {
|
||||||
Section(header: Text(formatter.string(from: section[0].date ?? Date()))) {
|
ForEach(groupedHistory(history), id: \.self) { historyGroup in
|
||||||
ForEach(section, id: \.self) { history in
|
HistorySectionView(allEntries: allEntries, historyGroup: historyGroup)
|
||||||
ForEach(history.entryArray) { entry in
|
|
||||||
HistoryButtonView(entry: entry)
|
|
||||||
}
|
|
||||||
.onDelete { offsets in
|
|
||||||
removeEntry(at: offsets, from: history)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listStyle(.insetGrouped)
|
.listStyle(.insetGrouped)
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
applyPredicate()
|
||||||
|
}
|
||||||
|
.onChange(of: searchText) { _ in
|
||||||
|
applyPredicate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyPredicate() {
|
||||||
|
if searchText.isEmpty {
|
||||||
|
historyPredicate = nil
|
||||||
|
} else {
|
||||||
|
let namePredicate = NSPredicate(format: "name CONTAINS[cd] %@", searchText.lowercased())
|
||||||
|
let subNamePredicate = NSPredicate(format: "subName CONTAINS[cd] %@", searchText.lowercased())
|
||||||
|
historyPredicate = NSCompoundPredicate(type: .or, subpredicates: [namePredicate, subNamePredicate])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupedHistory(_ result: FetchedResults<History>) -> [[History]] {
|
||||||
|
return Dictionary(grouping: result) { (element: History) in
|
||||||
|
element.dateString ?? ""
|
||||||
|
}
|
||||||
|
.values
|
||||||
|
.sorted { $0[0].date ?? Date() > $1[0].date ?? Date() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HistorySectionView: View {
|
||||||
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
|
var formatter: DateFormatter = .init()
|
||||||
|
var allEntries: FetchedResults<HistoryEntry>
|
||||||
|
var historyGroup: [History]
|
||||||
|
|
||||||
|
init(allEntries: FetchedResults<HistoryEntry>, historyGroup: [History]) {
|
||||||
|
self.allEntries = allEntries
|
||||||
|
self.historyGroup = historyGroup
|
||||||
|
|
||||||
|
formatter.dateStyle = .medium
|
||||||
|
formatter.timeStyle = .none
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if compareGroup(historyGroup) > 0 {
|
||||||
|
Section(header: Text(formatter.string(from: historyGroup[0].date ?? Date()))) {
|
||||||
|
ForEach(historyGroup, id: \.self) { history in
|
||||||
|
ForEach(history.entryArray.filter { allEntries.contains($0) }, id: \.self) { entry in
|
||||||
|
HistoryButtonView(entry: entry)
|
||||||
|
}
|
||||||
|
.onDelete { offsets in
|
||||||
|
removeEntry(at: offsets, from: history)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareGroup(_ group: [History]) -> Int {
|
||||||
|
var totalCount = 0
|
||||||
|
for history in group {
|
||||||
|
totalCount += history.entryArray.reduce(0, { result, item in
|
||||||
|
result + (allEntries.contains { $0.name == item.name || (item.subName.map { return !$0.isEmpty } ?? false && $0.subName == item.subName) } ? 1 : 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeEntry(at offsets: IndexSet, from history: History) {
|
func removeEntry(at offsets: IndexSet, from history: History) {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ struct ContentView: View {
|
||||||
sortDescriptors: []
|
sortDescriptors: []
|
||||||
) var sources: FetchedResults<Source>
|
) var sources: FetchedResults<Source>
|
||||||
|
|
||||||
|
@AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true
|
||||||
|
|
||||||
@State private var selectedSource: Source? {
|
@State private var selectedSource: Source? {
|
||||||
didSet {
|
didSet {
|
||||||
scrapingModel.filteredSource = selectedSource
|
scrapingModel.filteredSource = selectedSource
|
||||||
|
|
@ -72,7 +74,9 @@ struct ContentView: View {
|
||||||
SearchResultsView()
|
SearchResultsView()
|
||||||
}
|
}
|
||||||
.navigationTitle("Search")
|
.navigationTitle("Search")
|
||||||
.navigationBarTitleDisplayMode(navModel.isSearching ? .inline : .large)
|
.navigationBarTitleDisplayMode(
|
||||||
|
navModel.isSearching && Application.shared.osVersion.majorVersion > 14 ? .inline : .large
|
||||||
|
)
|
||||||
.navigationSearchBar {
|
.navigationSearchBar {
|
||||||
SearchBar("Search",
|
SearchBar("Search",
|
||||||
text: $scrapingModel.searchText,
|
text: $scrapingModel.searchText,
|
||||||
|
|
@ -114,8 +118,8 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
.introspectSearchController { searchController in
|
.introspectSearchController { searchController in
|
||||||
searchController.hidesNavigationBarDuringPresentation = false
|
searchController.hidesNavigationBarDuringPresentation = false
|
||||||
searchController.searchBar.autocorrectionType = .no
|
searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no
|
||||||
searchController.searchBar.autocapitalizationType = .none
|
searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import SwiftUIX
|
||||||
|
|
||||||
struct LibraryView: View {
|
struct LibraryView: View {
|
||||||
enum LibraryPickerSegment {
|
enum LibraryPickerSegment {
|
||||||
|
|
@ -19,9 +20,7 @@ struct LibraryView: View {
|
||||||
|
|
||||||
@FetchRequest(
|
@FetchRequest(
|
||||||
entity: Bookmark.entity(),
|
entity: Bookmark.entity(),
|
||||||
sortDescriptors: [
|
sortDescriptors: []
|
||||||
NSSortDescriptor(keyPath: \Bookmark.orderNum, ascending: true)
|
|
||||||
]
|
|
||||||
) var bookmarks: FetchedResults<Bookmark>
|
) var bookmarks: FetchedResults<Bookmark>
|
||||||
|
|
||||||
@FetchRequest(
|
@FetchRequest(
|
||||||
|
|
@ -31,11 +30,15 @@ struct LibraryView: View {
|
||||||
]
|
]
|
||||||
) var history: FetchedResults<History>
|
) var history: FetchedResults<History>
|
||||||
|
|
||||||
@State private var historyEmpty = true
|
@AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true
|
||||||
|
|
||||||
@State private var selectedSegment: LibraryPickerSegment = .bookmarks
|
@State private var selectedSegment: LibraryPickerSegment = .bookmarks
|
||||||
@State private var editMode: EditMode = .inactive
|
@State private var editMode: EditMode = .inactive
|
||||||
|
|
||||||
|
@State private var searchText: String = ""
|
||||||
|
@State private var isEditingSearch = false
|
||||||
|
@State private var isSearching = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavView {
|
NavView {
|
||||||
VStack {
|
VStack {
|
||||||
|
|
@ -48,19 +51,34 @@ struct LibraryView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.segmented)
|
.pickerStyle(.segmented)
|
||||||
.padding()
|
.padding(.horizontal)
|
||||||
|
.padding(.vertical, 5)
|
||||||
|
|
||||||
switch selectedSegment {
|
switch selectedSegment {
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
BookmarksView(bookmarks: bookmarks)
|
BookmarksView(searchText: $searchText)
|
||||||
case .history:
|
case .history:
|
||||||
HistoryView(history: history)
|
HistoryView(history: history, searchText: $searchText)
|
||||||
case .debridCloud:
|
case .debridCloud:
|
||||||
DebridCloudView()
|
DebridCloudView(searchText: $searchText)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
.navigationSearchBar {
|
||||||
|
SearchBar("Search", text: $searchText, isEditing: $isEditingSearch, onCommit: {
|
||||||
|
isSearching = true
|
||||||
|
})
|
||||||
|
.showsCancelButton(isEditingSearch || isSearching)
|
||||||
|
.onCancel {
|
||||||
|
searchText = ""
|
||||||
|
isSearching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.introspectSearchController { searchController in
|
||||||
|
searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no
|
||||||
|
searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none
|
||||||
|
}
|
||||||
.overlay {
|
.overlay {
|
||||||
switch selectedSegment {
|
switch selectedSegment {
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
|
|
@ -80,7 +98,8 @@ struct LibraryView: View {
|
||||||
.navigationTitle("Library")
|
.navigationTitle("Library")
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
HStack {
|
HStack(spacing: Application.shared.osVersion.majorVersion > 14 ? 10 : 18) {
|
||||||
|
Spacer()
|
||||||
EditButton()
|
EditButton()
|
||||||
|
|
||||||
switch selectedSegment {
|
switch selectedSegment {
|
||||||
|
|
@ -90,6 +109,7 @@ struct LibraryView: View {
|
||||||
HistoryActionsView()
|
HistoryActionsView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.animation(.none)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environment(\.editMode, $editMode)
|
.environment(\.editMode, $editMode)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ struct SettingsView: View {
|
||||||
|
|
||||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
|
@AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true
|
||||||
|
|
||||||
@AppStorage("Updates.AutomaticNotifs") var autoUpdateNotifs = true
|
@AppStorage("Updates.AutomaticNotifs") var autoUpdateNotifs = true
|
||||||
|
|
||||||
@AppStorage("Actions.DefaultDebrid") var defaultDebridAction: DefaultDebridActionType = .none
|
@AppStorage("Actions.DefaultDebrid") var defaultDebridAction: DefaultDebridActionType = .none
|
||||||
|
|
@ -76,6 +78,12 @@ struct SettingsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section(header: Text("Behavior")) {
|
||||||
|
Toggle(isOn: $autocorrectSearch) {
|
||||||
|
Text("Autocorrect search")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Section(header: Text("Source management")) {
|
Section(header: Text("Source management")) {
|
||||||
NavigationLink("Source lists", destination: SettingsSourceListView())
|
NavigationLink("Source lists", destination: SettingsSourceListView())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,11 @@ struct SourcesView: View {
|
||||||
|
|
||||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
@FetchRequest(
|
@AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true
|
||||||
entity: Source.entity(),
|
|
||||||
sortDescriptors: []
|
|
||||||
) var sources: FetchedResults<Source>
|
|
||||||
|
|
||||||
@State private var checkedForSources = false
|
@State private var checkedForSources = false
|
||||||
@State private var isEditing = false
|
@State private var isEditingSearch = false
|
||||||
|
@State private var isSearching = false
|
||||||
|
|
||||||
@State private var viewTask: Task<Void, Never>? = nil
|
@State private var viewTask: Task<Void, Never>? = nil
|
||||||
@State private var searchText: String = ""
|
@State private var searchText: String = ""
|
||||||
|
|
@ -35,7 +33,7 @@ struct SourcesView: View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if !checkedForSources {
|
if !checkedForSources {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
} else if sources.isEmpty, sourceManager.availableSources.isEmpty {
|
} else if installedSources.isEmpty, sourceManager.availableSources.isEmpty {
|
||||||
EmptyInstructionView(title: "No Sources", message: "Add a source list in Settings")
|
EmptyInstructionView(title: "No Sources", message: "Add a source list in Settings")
|
||||||
} else {
|
} else {
|
||||||
List {
|
List {
|
||||||
|
|
@ -119,11 +117,18 @@ struct SourcesView: View {
|
||||||
}
|
}
|
||||||
.navigationTitle("Sources")
|
.navigationTitle("Sources")
|
||||||
.navigationSearchBar {
|
.navigationSearchBar {
|
||||||
SearchBar("Search", text: $searchText, isEditing: $isEditing)
|
SearchBar("Search", text: $searchText, isEditing: $isEditingSearch, onCommit: {
|
||||||
.showsCancelButton(isEditing)
|
isSearching = true
|
||||||
.onCancel {
|
})
|
||||||
searchText = ""
|
.showsCancelButton(isEditingSearch || isSearching)
|
||||||
}
|
.onCancel {
|
||||||
|
searchText = ""
|
||||||
|
isSearching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.introspectSearchController { searchController in
|
||||||
|
searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no
|
||||||
|
searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue