From ce01376235baa4312a875b63705d7cd5a1fd43a4 Mon Sep 17 00:00:00 2001 From: Michael-128 Date: Tue, 12 Dec 2023 22:43:00 +0100 Subject: [PATCH] Added 'Select Torrents' Feature & Cleaned Up Code --- qBitControl.xcodeproj/project.pbxproj | 12 ++ qBitControl/Classes/qBittorrentClass.swift | 16 ++ qBitControl/Structures.swift | 4 +- .../TorrentListView/TorrentList.swift | 35 ++++- .../TorrentListDefaultToolbar.swift | 139 +++++++++++++++++ .../TorrentListSelectionToolbar.swift | 141 ++++++++++++++++++ .../TorrentListView/TorrentListToolbar.swift | 138 +++++++++++++++++ .../TorrentListView/TorrentListView.swift | 127 +--------------- 8 files changed, 482 insertions(+), 130 deletions(-) create mode 100644 qBitControl/TorrentView/TorrentListView/TorrentListDefaultToolbar.swift create mode 100644 qBitControl/TorrentView/TorrentListView/TorrentListSelectionToolbar.swift create mode 100644 qBitControl/TorrentView/TorrentListView/TorrentListToolbar.swift diff --git a/qBitControl.xcodeproj/project.pbxproj b/qBitControl.xcodeproj/project.pbxproj index 238dc16..e39f2ae 100644 --- a/qBitControl.xcodeproj/project.pbxproj +++ b/qBitControl.xcodeproj/project.pbxproj @@ -30,6 +30,9 @@ 90A280132B1B9A0E00A8396C /* TorrentFilterDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90A280122B1B9A0E00A8396C /* TorrentFilterDemo.swift */; }; 90A280152B1B9AE400A8396C /* TorrentAddFileViewDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90A280142B1B9AE400A8396C /* TorrentAddFileViewDemo.swift */; }; 90A280172B1B9AFD00A8396C /* TorrentAddOptionsViewDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90A280162B1B9AFD00A8396C /* TorrentAddOptionsViewDemo.swift */; }; + 90AB9B752B2900F4005C3612 /* TorrentListToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90AB9B742B2900F4005C3612 /* TorrentListToolbar.swift */; }; + 90AB9B772B2901D5005C3612 /* TorrentListDefaultToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90AB9B762B2901D5005C3612 /* TorrentListDefaultToolbar.swift */; }; + 90AB9B792B29051B005C3612 /* TorrentListSelectionToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90AB9B782B29051B005C3612 /* TorrentListSelectionToolbar.swift */; }; 90BA88552B2206F100C8A342 /* LocalNetworkPermissionClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90BA88542B2206F100C8A342 /* LocalNetworkPermissionClass.swift */; }; 90BCAD19291A7C91009F1FEC /* RSSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90BCAD18291A7C91009F1FEC /* RSSView.swift */; }; 90BCAD1B291A7EC9009F1FEC /* RSSFeedArticlesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90BCAD1A291A7EC9009F1FEC /* RSSFeedArticlesView.swift */; }; @@ -91,6 +94,9 @@ 90A280122B1B9A0E00A8396C /* TorrentFilterDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentFilterDemo.swift; sourceTree = ""; }; 90A280142B1B9AE400A8396C /* TorrentAddFileViewDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentAddFileViewDemo.swift; sourceTree = ""; }; 90A280162B1B9AFD00A8396C /* TorrentAddOptionsViewDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentAddOptionsViewDemo.swift; sourceTree = ""; }; + 90AB9B742B2900F4005C3612 /* TorrentListToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentListToolbar.swift; sourceTree = ""; }; + 90AB9B762B2901D5005C3612 /* TorrentListDefaultToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentListDefaultToolbar.swift; sourceTree = ""; }; + 90AB9B782B29051B005C3612 /* TorrentListSelectionToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentListSelectionToolbar.swift; sourceTree = ""; }; 90BA88532B22019900C8A342 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 90BA88542B2206F100C8A342 /* LocalNetworkPermissionClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNetworkPermissionClass.swift; sourceTree = ""; }; 90BCAD18291A7C91009F1FEC /* RSSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSView.swift; sourceTree = ""; }; @@ -227,6 +233,9 @@ children = ( 9082FA4E2908248B006C3960 /* TorrentListView.swift */, 908315022B272FB7005B6953 /* TorrentList.swift */, + 90AB9B742B2900F4005C3612 /* TorrentListToolbar.swift */, + 90AB9B762B2901D5005C3612 /* TorrentListDefaultToolbar.swift */, + 90AB9B782B29051B005C3612 /* TorrentListSelectionToolbar.swift */, ); path = TorrentListView; sourceTree = ""; @@ -392,6 +401,7 @@ 9082FA2529080E53006C3960 /* ServersView.swift in Sources */, 903DED2E290AABA800FB36D6 /* TorrentAddView.swift in Sources */, 90A2800E2B1B910E00A8396C /* TorrentListViewDemo.swift in Sources */, + 90AB9B772B2901D5005C3612 /* TorrentListDefaultToolbar.swift in Sources */, 90EF6A5129095ACC001E9E7F /* TorrentRowView.swift in Sources */, 90C6F3982911529100F5A6FD /* TorrentFilterView.swift in Sources */, 909F850E290FFB73003FC051 /* TorrentAddOptionsView.swift in Sources */, @@ -409,8 +419,10 @@ 90EF6A492909267A001E9E7F /* AuthClass.swift in Sources */, 903DED30290AADB300FB36D6 /* TorrentAddMagnetView.swift in Sources */, 9082FA4F2908248B006C3960 /* TorrentListView.swift in Sources */, + 90AB9B792B29051B005C3612 /* TorrentListSelectionToolbar.swift in Sources */, 90A280172B1B9AFD00A8396C /* TorrentAddOptionsViewDemo.swift in Sources */, 90FA253629190D0400DC8F0B /* FileNodeClass.swift in Sources */, + 90AB9B752B2900F4005C3612 /* TorrentListToolbar.swift in Sources */, 9082FA4D29082208006C3960 /* qBittorrentClass.swift in Sources */, 90FA253829190D3100DC8F0B /* TorrentDetailsFilesView.swift in Sources */, 90896E452B13FD26001B3322 /* LoginView.swift in Sources */, diff --git a/qBitControl/Classes/qBittorrentClass.swift b/qBitControl/Classes/qBittorrentClass.swift index f92ba53..5f2aaab 100644 --- a/qBitControl/Classes/qBittorrentClass.swift +++ b/qBitControl/Classes/qBittorrentClass.swift @@ -255,6 +255,14 @@ class qBittorrent { qBitRequest.requestTorrentManagement(request: request) } + static func recheckTorrents(hashes: [String]) { + let path = "/api/v2/torrents/recheck" + + let request = qBitRequest.prepareURLRequest(path: path, queryItems: [URLQueryItem(name: "hashes", value: hashes.joined(separator: "|"))]) + + qBitRequest.requestTorrentManagement(request: request) + } + static func reannounceTorrent(hash: String) { let path = "/api/v2/torrents/reannounce" @@ -263,6 +271,14 @@ class qBittorrent { qBitRequest.requestTorrentManagement(request: request) } + static func reannounceTorrents(hashes: [String]) { + let path = "/api/v2/torrents/reannounce" + + let request = qBitRequest.prepareURLRequest(path: path, queryItems: [URLQueryItem(name: "hashes", value: hashes.joined(separator: "|"))]) + + qBitRequest.requestTorrentManagement(request: request) + } + static func deleteTorrent(hash: String, deleteFiles: Bool) { let path = "/api/v2/torrents/delete" diff --git a/qBitControl/Structures.swift b/qBitControl/Structures.swift index 7160d78..8fa0087 100644 --- a/qBitControl/Structures.swift +++ b/qBitControl/Structures.swift @@ -5,7 +5,7 @@ import Foundation -struct Torrent: Decodable { +struct Torrent: Decodable, Hashable { let added_on: Int let amount_left: Int let auto_tmm: Bool @@ -41,7 +41,7 @@ struct Torrent: Decodable { let seen_complete: Int let seq_dl: Bool let size: Int64 - let state: String + var state: String let super_seeding: Bool let tags: String let time_active: Int diff --git a/qBitControl/TorrentView/TorrentListView/TorrentList.swift b/qBitControl/TorrentView/TorrentListView/TorrentList.swift index aad1dfb..1f23368 100644 --- a/qBitControl/TorrentView/TorrentListView/TorrentList.swift +++ b/qBitControl/TorrentView/TorrentListView/TorrentList.swift @@ -26,18 +26,39 @@ struct TorrentList: View { @State private var hash = "" + @Binding public var isSelectionMode: Bool + @Binding public var selectedTorrents: Set + var body: some View { Section(header: torrentListHeader()) { ForEach(torrents, id: \.hash) { torrent in if searchQuery == "" || torrent.name.lowercased().contains(searchQuery.lowercased()) { - NavigationLink { - TorrentDetailsView(torrent: torrent) - } label: { - TorrentRowView(name: torrent.name, progress: torrent.progress, state: torrent.state, dlspeed: torrent.dlspeed, upspeed: torrent.upspeed, ratio: torrent.ratio) - .contextMenu() { - torrentRowContextMenu(torrent: torrent) + if(!isSelectionMode) { + NavigationLink { + TorrentDetailsView(torrent: torrent) + } label: { + TorrentRowView(name: torrent.name, progress: torrent.progress, state: torrent.state, dlspeed: torrent.dlspeed, upspeed: torrent.upspeed, ratio: torrent.ratio) + .contextMenu() { + torrentRowContextMenu(torrent: torrent) + } + } + } else { + if(selectedTorrents.contains(torrent)) { + HStack { + Image(systemName: "checkmark.circle.fill").scaleEffect(1.25).foregroundStyle(Color(.blue)) + TorrentRowView(name: torrent.name, progress: torrent.progress, state: torrent.state, dlspeed: torrent.dlspeed, upspeed: torrent.upspeed, ratio: torrent.ratio) + }.onTapGesture { + selectedTorrents.remove(torrent) + } + } else { + HStack { + Image(systemName: "circle").scaleEffect(1.25).foregroundStyle(Color(.gray)) + TorrentRowView(name: torrent.name, progress: torrent.progress, state: torrent.state, dlspeed: torrent.dlspeed, upspeed: torrent.upspeed, ratio: torrent.ratio) + }.onTapGesture { + selectedTorrents.insert(torrent) } + } } } } @@ -140,7 +161,7 @@ struct TorrentList: View { func getTorrents() { - if(scenePhase != .active || isTorrentAddView) { + if(scenePhase != .active || isTorrentAddView || isSelectionMode) { return } diff --git a/qBitControl/TorrentView/TorrentListView/TorrentListDefaultToolbar.swift b/qBitControl/TorrentView/TorrentListView/TorrentListDefaultToolbar.swift new file mode 100644 index 0000000..70fba88 --- /dev/null +++ b/qBitControl/TorrentView/TorrentListView/TorrentListDefaultToolbar.swift @@ -0,0 +1,139 @@ +// + +import SwiftUI + +struct TorrentListDefaultToolbar: ToolbarContent { + @Binding public var torrents: [Torrent] + + @Binding public var category: String + + @Binding public var isSelectionMode: Bool + @Binding public var isLoggedIn: Bool + @Binding public var isFilterView: Bool + + @State private var alertIdentifier: AlertIdentifier? + @State private var isAlertClearCompleted: Bool = false + + var body: some ToolbarContent { + ToolbarItem(placement: .topBarLeading) { + Menu { + Section { + Button { + isSelectionMode = true + } label: { + Image(systemName: "checkmark.circle") + Text("Select") + } + } + + if(category != "None") { + Section { + Button { + let torrentsInCategory = torrents.filter { + torrent in + return torrent.category == category + } + + qBittorrent.resumeTorrents(hashes: torrentsInCategory.compactMap { torrent in torrent.hash }) + } label: { + Image(systemName: "play") + .rotationEffect(.degrees(180)) + Text("Resume \(category.capitalized)") + } + + Button { + let torrentsInCategory = torrents.filter { + torrent in + return torrent.category == category + } + + qBittorrent.pauseTorrents(hashes: torrentsInCategory.compactMap { torrent in torrent.hash }) + } label: { + Image(systemName: "pause") + .rotationEffect(.degrees(180)) + Text("Pause \(category.capitalized)") + } + } + } + + Section { + Button { + alertIdentifier = AlertIdentifier(id: .resumeAll) + } label: { + Image(systemName: "play") + .rotationEffect(.degrees(180)) + Text("Resume All Tasks") + } + + Button { + alertIdentifier = AlertIdentifier(id: .pauseAll) + } label: { + Image(systemName: "pause") + .rotationEffect(.degrees(180)) + Text("Pause All Tasks") + } + } + + Section { + Button(role: .destructive) { + isAlertClearCompleted = true + } label: { + Image(systemName: "trash") + .rotationEffect(.degrees(180)) + Text("Clear Completed") + } + } + + Section { + Button(role: .destructive) { + alertIdentifier = AlertIdentifier(id: .logOut) + } label: { + Image(systemName: "rectangle.portrait.and.arrow.forward") + .rotationEffect(.degrees(180)) + Text("Log out") + } + } + } label: { + Image(systemName: "ellipsis.circle") + }.alert(item: $alertIdentifier) { alert in + switch(alert.id) { + case .resumeAll: + return Alert(title: Text("Confirm Resume All"), message: Text("Are you sure you want to resume all tasks?"), primaryButton: .default(Text("Resume")) { + qBittorrent.resumeAllTorrents() + }, secondaryButton: .cancel()) + case .pauseAll: + return Alert(title: Text("Confirm Pause All"), message: Text("Are you sure you want to pause all tasks?"), primaryButton: .default(Text("Pause")) { + qBittorrent.pauseAllTorrents() + }, secondaryButton: .cancel()) + case .logOut: + return Alert(title: Text("Confirm Logout"), message: Text("Are you sure you want to log out?"), primaryButton: .destructive(Text("Log Out")) { + qBittorrent.setCookie(cookie: "") + isLoggedIn = false + }, secondaryButton: .cancel()) + } + }.alert("Confirm Deletion", isPresented: $isAlertClearCompleted, actions: { + Button("Delete Completed Tasks", role: .destructive) { + let completedTorrents = torrents.filter {torrent in torrent.progress == 1} + let completedHashes = completedTorrents.compactMap {torrent in torrent.hash} + + qBittorrent.deleteTorrents(hashes: completedHashes) + } + Button("Delete Completed Tasks with Files", role: .destructive) { + let completedTorrents = torrents.filter {torrent in torrent.progress == 1} + let completedHashes = completedTorrents.compactMap {torrent in torrent.hash} + + + qBittorrent.deleteTorrents(hashes: completedHashes, deleteFiles: true) + } + }) + } + + ToolbarItem(placement: .topBarTrailing) { + Button { + isFilterView.toggle() + } label: { + Image(systemName: "line.3.horizontal.decrease.circle") + } + } + } +} diff --git a/qBitControl/TorrentView/TorrentListView/TorrentListSelectionToolbar.swift b/qBitControl/TorrentView/TorrentListView/TorrentListSelectionToolbar.swift new file mode 100644 index 0000000..a5a4337 --- /dev/null +++ b/qBitControl/TorrentView/TorrentListView/TorrentListSelectionToolbar.swift @@ -0,0 +1,141 @@ +// + +import SwiftUI + +struct TorrentListSelectionToolbar: ToolbarContent { + @Binding public var torrents: [Torrent] + + @Binding public var isSelectionMode: Bool + @State private var isAlertDeleteSelected: Bool = false + + @Binding public var selectedTorrents: Set + + var body: some ToolbarContent { + ToolbarItem(placement: .topBarLeading) { + if(selectedTorrents.count == torrents.count) { + Button { + selectedTorrents.removeAll() + } label: { + Text("Deselect All") + } + } else { + Button { + torrents.forEach { + torrent in + selectedTorrents.insert(torrent) + } + } label: { + Text("Select All") + } + } + } + + ToolbarItem(placement: .topBarTrailing) { + Button { + isSelectionMode = false + + // do something + + selectedTorrents.removeAll() + } label: { + Text("Done") + .fontWeight(.bold) + } + } + + if(selectedTorrents.count > 0) { + ToolbarItemGroup(placement: .bottomBar) { + HStack { + Button { + let selectedHashes = selectedTorrents.compactMap { + torrent in + torrent.hash + } + + qBittorrent.resumeTorrents(hashes: selectedHashes) + isSelectionMode = false + selectedTorrents.removeAll() + } label: { + Image(systemName: "play.fill") + } + + Spacer() + + Button { + let selectedHashes = selectedTorrents.compactMap { + torrent in + torrent.hash + } + + qBittorrent.pauseTorrents(hashes: selectedHashes) + isSelectionMode = false + selectedTorrents.removeAll() + } label: { + Image(systemName: "pause.fill") + } + + Spacer() + + Button { + let selectedHashes = selectedTorrents.compactMap { + torrent in + torrent.hash + } + + qBittorrent.recheckTorrents(hashes: selectedHashes) + isSelectionMode = false + selectedTorrents.removeAll() + } label: { + Image(systemName: "magnifyingglass") + } + + Spacer() + + Button { + let selectedHashes = selectedTorrents.compactMap { + torrent in + torrent.hash + } + + qBittorrent.reannounceTorrents(hashes: selectedHashes) + isSelectionMode = false + selectedTorrents.removeAll() + } label: { + Image(systemName: "circle.dashed") + } + + Spacer() + + Button { + isAlertDeleteSelected = true + } label: { + Image(systemName: "trash.fill").foregroundStyle(Color(.red)) + } + }.alert("Confirm Deletion", isPresented: $isAlertDeleteSelected, actions: { + Button("Delete Selected Tasks", role: .destructive) { + let selectedHashes = selectedTorrents.compactMap { + torrent in + torrent.hash + } + + qBittorrent.deleteTorrents(hashes: selectedHashes) + + isSelectionMode = false + selectedTorrents.removeAll() + } + Button("Delete Selected Tasks with Files", role: .destructive) { + let selectedHashes = selectedTorrents.compactMap { + torrent in + torrent.hash + } + + qBittorrent.deleteTorrents(hashes: selectedHashes, deleteFiles: true) + + isSelectionMode = false + selectedTorrents.removeAll() + } + }) + } + } + } +} diff --git a/qBitControl/TorrentView/TorrentListView/TorrentListToolbar.swift b/qBitControl/TorrentView/TorrentListView/TorrentListToolbar.swift new file mode 100644 index 0000000..6c1fc78 --- /dev/null +++ b/qBitControl/TorrentView/TorrentListView/TorrentListToolbar.swift @@ -0,0 +1,138 @@ +// + +import SwiftUI + +struct TorrentListToolbar: ToolbarContent { + @Binding public var torrents: [Torrent] + + @Binding public var category: String + + @Binding public var isSelectionMode: Bool + @Binding public var isLoggedIn: Bool + @Binding public var isFilterView: Bool + + @Binding public var selectedTorrents: Set + + var body: some ToolbarContent { + if(!isSelectionMode) { + TorrentListDefaultToolbar(torrents: $torrents, category: $category, isSelectionMode: $isSelectionMode, isLoggedIn: $isLoggedIn, isFilterView: $isFilterView) + } else { + TorrentListSelectionToolbar(torrents: $torrents, isSelectionMode: $isSelectionMode, selectedTorrents: $selectedTorrents) + } + } +} + +/* if(isSelectionMode && selectedTorrents.count > 0) { + ToolbarItemGroup(placement: .bottomBar) { + HStack { + Group { + Button { + let selectedHashes = selectedTorrents.compactMap { + torrent in + torrent.hash + } + + qBittorrent.resumeTorrents(hashes: selectedHashes) + isSelectionMode = false + selectedTorrents.removeAll() + } label: { + Image(systemName: "play.circle") + } + + Button { + let selectedHashes = selectedTorrents.compactMap { + torrent in + torrent.hash + } + + qBittorrent.pauseTorrents(hashes: selectedHashes) + isSelectionMode = false + selectedTorrents.removeAll() + } label: { + Image(systemName: "pause.circle") + } + + Button { + let selectedHashes = selectedTorrents.compactMap { + torrent in + torrent.hash + } + + qBittorrent.recheckTorrents(hashes: selectedHashes) + isSelectionMode = false + selectedTorrents.removeAll() + } label: { + Image(systemName: "magnifyingglass.circle") + } + + Button { + let selectedHashes = selectedTorrents.compactMap { + torrent in + torrent.hash + } + + qBittorrent.reannounceTorrents(hashes: selectedHashes) + isSelectionMode = false + selectedTorrents.removeAll() + } label: { + Image(systemName: "circle.dotted.circle") + } + + Button { + // to be implemented + } label: { + Image(systemName: "trash.circle").foregroundStyle(Color(.red)) + } + } + }.scaleEffect(1.20) + } +} + +if(!isSelectionMode) { + ToolbarItem(placement: .navigationBarLeading) { + leftToolbarMenu() + } + ToolbarItem(placement: .navigationBarTrailing) { + Button { + isFilterView.toggle() + } label: { + Image(systemName: "line.3.horizontal.decrease.circle") + } + + } +} else { + ToolbarItem(placement: .navigationBarLeading) { + if(selectedTorrents.count == torrents.count) { + Button { + selectedTorrents.removeAll() + } label: { + Text("Deselect All") + } + } else { + Button { + torrents.forEach { + torrent in + selectedTorrents.insert(torrent) + } + } label: { + Text("Select All") + } + } + } + + ToolbarItem(placement: .navigationBarTrailing) { + Button { + isSelectionMode = false + + // do something + + selectedTorrents.removeAll() + } label: { + Text("Done") + .fontWeight(.bold) + } + } +}*/ + +/*func leftToolbarMenu() -> some View { +*/ diff --git a/qBitControl/TorrentView/TorrentListView/TorrentListView.swift b/qBitControl/TorrentView/TorrentListView/TorrentListView.swift index 8992177..d3d62c6 100644 --- a/qBitControl/TorrentView/TorrentListView/TorrentListView.swift +++ b/qBitControl/TorrentView/TorrentListView/TorrentListView.swift @@ -27,11 +27,11 @@ struct TorrentListView: View { @Binding var isLoggedIn: Bool - @State private var alertIdentifier: AlertIdentifier? - @State private var isAlertClearCompleted: Bool = false - let defaults = UserDefaults.standard + @State private var isSelectionMode: Bool = false + @State private var selectedTorrents = Set() + var body: some View { NavigationView { List { @@ -46,22 +46,11 @@ struct TorrentListView: View { }.searchable(text: $searchQuery) } - TorrentList(torrents: $torrents, searchQuery: $searchQuery, sort: $sort, reverse: $reverse, filter: $filter, category: $category, tag: $tag, isTorrentAddView: $isTorrentAddView) + TorrentList(torrents: $torrents, searchQuery: $searchQuery, sort: $sort, reverse: $reverse, filter: $filter, category: $category, tag: $tag, isTorrentAddView: $isTorrentAddView, isSelectionMode: $isSelectionMode, selectedTorrents: $selectedTorrents) .navigationTitle(category == "None" ? "Tasks" : category.capitalized) - } - .toolbar() { - ToolbarItem(placement: .navigationBarLeading) { - leftToolbarMenu() - } - ToolbarItem(placement: .navigationBarTrailing) { - Button { - isFilterView.toggle() - } label: { - Image(systemName: "line.3.horizontal.decrease.circle") - } - - } + }.toolbar() { + TorrentListToolbar(torrents: $torrents, category: $category, isSelectionMode: $isSelectionMode, isLoggedIn: $isLoggedIn, isFilterView: $isFilterView, selectedTorrents: $selectedTorrents) } .sheet(isPresented: $isFilterView, content: { TorrentFilterView(sort: $sort, reverse: $reverse, filter: $filter, category: $category, tag: $tag) @@ -71,110 +60,6 @@ struct TorrentListView: View { }) } } - - func leftToolbarMenu() -> some View { - Menu { - if(category != "None") { - Section { - Button { - let torrentsInCategory = torrents.filter { - torrent in - return torrent.category == category - } - - qBittorrent.resumeTorrents(hashes: torrentsInCategory.compactMap { torrent in torrent.hash }) - } label: { - Image(systemName: "play") - .rotationEffect(.degrees(180)) - Text("Resume \(category.capitalized)") - } - - Button { - let torrentsInCategory = torrents.filter { - torrent in - return torrent.category == category - } - - qBittorrent.pauseTorrents(hashes: torrentsInCategory.compactMap { torrent in torrent.hash }) - } label: { - Image(systemName: "pause") - .rotationEffect(.degrees(180)) - Text("Pause \(category.capitalized)") - } - } - } - - Section { - Button { - alertIdentifier = AlertIdentifier(id: .resumeAll) - } label: { - Image(systemName: "play") - .rotationEffect(.degrees(180)) - Text("Resume All Tasks") - } - - Button { - alertIdentifier = AlertIdentifier(id: .pauseAll) - } label: { - Image(systemName: "pause") - .rotationEffect(.degrees(180)) - Text("Pause All Tasks") - } - } - - Section { - Button(role: .destructive) { - isAlertClearCompleted = true - } label: { - Image(systemName: "trash") - .rotationEffect(.degrees(180)) - Text("Clear Completed") - } - } - - Section { - Button(role: .destructive) { - alertIdentifier = AlertIdentifier(id: .logOut) - } label: { - Image(systemName: "rectangle.portrait.and.arrow.forward") - .rotationEffect(.degrees(180)) - Text("Log out") - } - } - } label: { - Image(systemName: "ellipsis.circle") - }.alert(item: $alertIdentifier) { alert in - switch(alert.id) { - case .resumeAll: - return Alert(title: Text("Confirm Resume All"), message: Text("Are you sure you want to resume all tasks?"), primaryButton: .default(Text("Resume")) { - qBittorrent.resumeAllTorrents() - }, secondaryButton: .cancel()) - case .pauseAll: - return Alert(title: Text("Confirm Pause All"), message: Text("Are you sure you want to pause all tasks?"), primaryButton: .default(Text("Pause")) { - qBittorrent.pauseAllTorrents() - }, secondaryButton: .cancel()) - case .logOut: - return Alert(title: Text("Confirm Logout"), message: Text("Are you sure you want to log out?"), primaryButton: .destructive(Text("Log Out")) { - qBittorrent.setCookie(cookie: "") - isLoggedIn = false - }, secondaryButton: .cancel()) - } - }.alert("Confirm Deletion", isPresented: $isAlertClearCompleted, actions: { - Button("Delete Completed Tasks", role: .destructive) { - let completedTorrents = torrents.filter {torrent in torrent.progress == 1} - let completedHashes = completedTorrents.compactMap {torrent in torrent.hash} - - qBittorrent.deleteTorrents(hashes: completedHashes) - } - Button("Delete Completed Tasks with Files", role: .destructive) { - let completedTorrents = torrents.filter {torrent in torrent.progress == 1} - let completedHashes = completedTorrents.compactMap {torrent in torrent.hash} - - - qBittorrent.deleteTorrents(hashes: completedHashes, deleteFiles: true) - } - }) - } }