From 33bcffa94dca8b46b1971e6c502dfd39ea7ca5d3 Mon Sep 17 00:00:00 2001 From: Programistich Date: Wed, 20 Mar 2024 11:47:28 +0200 Subject: [PATCH] Cancellation install or update app in hub --- .../Sources/Applications/Applications.swift | 71 ++++++++++++++----- .../Hub/Apps/AppView/AppView+Buttons.swift | 13 +++- .../Sources/Hub/Apps/Components/AppRow.swift | 35 ++++++--- .../Sources/Hub/Apps/Components/Buttons.swift | 23 +++++- .../Apps/AppCancel.imageset/AppCancel.svg | 4 ++ .../Apps/AppCancel.imageset/Contents.json | 21 ++++++ 6 files changed, 136 insertions(+), 31 deletions(-) create mode 100644 Flipper/Shared/Assets.xcassets/Apps/AppCancel.imageset/AppCancel.svg create mode 100644 Flipper/Shared/Assets.xcassets/Apps/AppCancel.imageset/Contents.json diff --git a/Flipper/Packages/Core/Sources/Applications/Applications.swift b/Flipper/Packages/Core/Sources/Applications/Applications.swift index 1236d2787..d4841db19 100644 --- a/Flipper/Packages/Core/Sources/Applications/Applications.swift +++ b/Flipper/Packages/Core/Sources/Applications/Applications.swift @@ -12,6 +12,7 @@ public class Applications: ObservableObject { public typealias Application = Catalog.Application private var categories: [Category] = [] + private var taskStorage: [Application.ID: Task] = [:] public enum InstalledStatus { case loading @@ -117,30 +118,40 @@ public class Applications: ObservableObject { } public func install(_ application: Application) async { - do { - statuses[application.id] = .installing(0) - try await _install(application) { progress in - Task { - statuses[application.id] = .installing(progress) + taskStorage[application.id]?.cancel() + + taskStorage[application.id] = Task { + do { + statuses[application.id] = .installing(0) + try await _install(application) { progress in + Task { + statuses[application.id] = .installing(progress) + } } + statuses[application.id] = .installed + } catch { + logger.error("install app: \(error)") } - statuses[application.id] = .installed - } catch { - logger.error("install app: \(error)") + taskStorage[application.id] = nil } } public func update(_ application: Application) async { - do { - statuses[application.id] = .updating(0) - try await _install(application) { progress in - Task { - statuses[application.id] = .updating(progress) + taskStorage[application.id]?.cancel() + + taskStorage[application.id] = Task { + do { + statuses[application.id] = .updating(0) + try await _install(application) { progress in + Task { + statuses[application.id] = .updating(progress) + } } + statuses[application.id] = .installed + } catch { + logger.error("update app: \(error)") } - statuses[application.id] = .installed - } catch { - logger.error("update app: \(error)") + taskStorage[application.id] = nil } } @@ -163,6 +174,26 @@ public class Applications: ObservableObject { } } + public func cancel(_ id: Application.ID) async { + guard + let status = statuses[id], + let task = taskStorage[id] + else { return } + + guard status.hasCancelOpportunity else { return } + task.cancel() + + switch status { + case .updating: + statuses[id] = .outdated + case .installing: + installed.removeAll { $0.id == id } + statuses[id] = .notInstalled + default: + return + } + } + public enum OpenAppStatus { case success case busy @@ -402,6 +433,14 @@ public class Applications: ObservableObject { case .checking: return 8 } } + + public var hasCancelOpportunity: Bool { + switch self { + case .installing: return true + case .updating: return true + default: return false + } + } } public var hasOpenAppSupport: Bool { diff --git a/Flipper/Packages/UI/Sources/Hub/Apps/AppView/AppView+Buttons.swift b/Flipper/Packages/UI/Sources/Hub/Apps/AppView/AppView+Buttons.swift index 65b9a7dba..4c837f254 100644 --- a/Flipper/Packages/UI/Sources/Hub/Apps/AppView/AppView+Buttons.swift +++ b/Flipper/Packages/UI/Sources/Hub/Apps/AppView/AppView+Buttons.swift @@ -25,7 +25,12 @@ extension AppView { var body: some View { HStack(alignment: .center, spacing: 12) { - if canDelete { + if status.hasCancelOpportunity { + CancelProgressAppButton { + cancel() + } + .frame(width: 46, height: 46) + } else if canDelete { DeleteAppButton { confirmDelete = true } @@ -122,6 +127,12 @@ extension AppView { } } + func cancel() { + Task { + await model.cancel(application.id) + } + } + func openApp() { Task { await model.openApp(by: application.id) { result in diff --git a/Flipper/Packages/UI/Sources/Hub/Apps/Components/AppRow.swift b/Flipper/Packages/UI/Sources/Hub/Apps/Components/AppRow.swift index d79b2135e..2b047d478 100644 --- a/Flipper/Packages/UI/Sources/Hub/Apps/Components/AppRow.swift +++ b/Flipper/Packages/UI/Sources/Hub/Apps/Components/AppRow.swift @@ -33,17 +33,24 @@ struct AppRow: View { .disabled(!isBuildReady) if isInstalled { - DeleteAppButton { - showConfirmDelete = true - } - .frame(width: 34, height: 34) - .alert(isPresented: $showConfirmDelete) { - ConfirmDeleteAppAlert( - isPresented: $showConfirmDelete, - application: application, - category: model.category(for: application) - ) { - delete() + if status.hasCancelOpportunity { + CancelProgressAppButton { + cancel() + } + .frame(width: 34, height: 34) + } else { + DeleteAppButton { + showConfirmDelete = true + } + .frame(width: 34, height: 34) + .alert(isPresented: $showConfirmDelete) { + ConfirmDeleteAppAlert( + isPresented: $showConfirmDelete, + application: application, + category: model.category(for: application) + ) { + delete() + } } } } @@ -69,6 +76,12 @@ struct AppRow: View { } } + func cancel() { + Task { + await model.cancel(application.id) + } + } + struct AppRowActionButton: View { @EnvironmentObject var model: Applications @EnvironmentObject var device: Device diff --git a/Flipper/Packages/UI/Sources/Hub/Apps/Components/Buttons.swift b/Flipper/Packages/UI/Sources/Hub/Apps/Components/Buttons.swift index 0bab27404..c29c4b5e5 100644 --- a/Flipper/Packages/UI/Sources/Hub/Apps/Components/Buttons.swift +++ b/Flipper/Packages/UI/Sources/Hub/Apps/Components/Buttons.swift @@ -52,15 +52,16 @@ struct UpdateAllAppButton: View { } } -struct DeleteAppButton: View { - var action: () -> Void +private struct ActionAppButton: View { + let image: String + let action: () -> Void var body: some View { Button { action() } label: { GeometryReader { proxy in - Image("AppDelete") + Image(image) .resizable() .frame( width: proxy.size.width, @@ -70,6 +71,22 @@ struct DeleteAppButton: View { } } +struct DeleteAppButton: View { + var action: () -> Void + + var body: some View { + ActionAppButton(image: "AppDelete", action: action) + } +} + +struct CancelProgressAppButton: View { + var action: () -> Void + + var body: some View { + ActionAppButton(image: "AppCancel", action: action) + } +} + struct InstallAppButton: View { var action: () -> Void diff --git a/Flipper/Shared/Assets.xcassets/Apps/AppCancel.imageset/AppCancel.svg b/Flipper/Shared/Assets.xcassets/Apps/AppCancel.imageset/AppCancel.svg new file mode 100644 index 000000000..8543b887d --- /dev/null +++ b/Flipper/Shared/Assets.xcassets/Apps/AppCancel.imageset/AppCancel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Flipper/Shared/Assets.xcassets/Apps/AppCancel.imageset/Contents.json b/Flipper/Shared/Assets.xcassets/Apps/AppCancel.imageset/Contents.json new file mode 100644 index 000000000..a942fdcb0 --- /dev/null +++ b/Flipper/Shared/Assets.xcassets/Apps/AppCancel.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "AppCancel.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +}