Skip to content

Commit

Permalink
BlockingTask
Browse files Browse the repository at this point in the history
  • Loading branch information
michele-theleftbit committed Jan 13, 2025
1 parent d80a9c9 commit 692ee72
Showing 1 changed file with 364 additions and 0 deletions.
364 changes: 364 additions & 0 deletions Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,364 @@
//
// Created by Michele Restuccia on 13/1/25.
//

import SwiftUI; import UIKit

Check failure on line 5 in Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift

View workflow job for this annotation

GitHub Actions / build

no such module 'UIKit'

Check failure on line 5 in Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift

View workflow job for this annotation

GitHub Actions / build

no such module 'UIKit'

Check failure on line 5 in Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift

View workflow job for this annotation

GitHub Actions / build

no such module 'UIKit'

Check failure on line 5 in Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift

View workflow job for this annotation

GitHub Actions / build

no such module 'UIKit'

Check failure on line 5 in Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift

View workflow job for this annotation

GitHub Actions / build

no such module 'UIKit'

Check failure on line 5 in Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift

View workflow job for this annotation

GitHub Actions / build

no such module 'UIKit'

Check failure on line 5 in Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift

View workflow job for this annotation

GitHub Actions / build

no such module 'UIKit'

Check failure on line 5 in Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift

View workflow job for this annotation

GitHub Actions / build

no such module 'UIKit'

Check failure on line 5 in Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift

View workflow job for this annotation

GitHub Actions / build

no such module 'UIKit'

Check failure on line 5 in Sources/BSWInterfaceKit/SwiftUI/ViewModifiers/BlockingTask.swift

View workflow job for this annotation

GitHub Actions / build

no such module 'UIKit'

public typealias BlockingTask = @MainActor () async throws -> ()
public typealias BlockingReturnTask = @MainActor (String) async throws -> ()
public typealias BlockingTaskWithValue<T: Equatable> = @MainActor (T) async throws -> ()
public typealias BlockingReturnTaskWithValue<T: Equatable> = @MainActor (BlockingTaskReturn<T>) async throws -> ()

public struct BlockingTaskReturn<T: Equatable> {
public let task: T
public let textFieldValue: String
}

public extension View {

func performBlockingTask(readyToPerform: Binding<Bool>, task: @escaping BlockingTask) -> some View {
self.modifier(PerformBlockingModifier(readyToPerform: readyToPerform, task: task))
}

func performBlockingTaskAfterConfirmation(
presentingAlert: Binding<Bool>,
titleAlert: String,
messageAlert: String? = nil,
textFieldTitle: String? = nil,
buttonAlert: String = NSLocalizedString("accept", bundle: Bundle.main, comment: ""),
buttonRole: ButtonRole? = nil,
task: @escaping BlockingReturnTask) -> some View {
self.modifier(PerformBlockingAfterConfirmationModifier(
presentingAlert: presentingAlert,
titleAlert: titleAlert,
messageAlert: messageAlert,
textFieldTitle: textFieldTitle,
buttonAlert: buttonAlert,
buttonRole: buttonRole,
task: task
))
}

func performBlockingTask<T: Equatable>(value: Binding<T?>, task: @escaping BlockingTaskWithValue<T>) -> some View {
self.modifier(PerformEquatableBlockingModifier(value: value, task: task))
}

func performBlockingTaskAfterConfirmation<T: Equatable>(
value: Binding<T?>,
titleAlert: String,
messageAlert: String? = nil,
textFieldTitle: String? = nil,
buttonAlert: String = NSLocalizedString("accept", bundle: Bundle.main, comment: ""),
buttonRole: ButtonRole? = nil,
task: @escaping BlockingReturnTaskWithValue<T>) -> some View {
self.modifier(PerformEquatableBlockingModifierAfterConfirmationModifier(
value: value,
titleAlert: titleAlert,
messageAlert: messageAlert,
textFieldTitle: textFieldTitle,
buttonAlert: buttonAlert,
buttonRole: buttonRole,
task: task
))
}
}

// MARK: PerformBlockingModifier

private struct PerformBlockingModifier: ViewModifier {

@Binding
var readyToPerform: Bool

let task: BlockingTask

private enum Phase {
case idle
case loading
}

@State
private var phase: Phase = .idle

@State
private var hudVC: UIViewController?

@State
private var taskError: Error? = nil

func body(content: Content) -> some View {
content
.task(id: phase) {
guard phase == .loading else { return }
hudVC = await presentHUDViewController()
do {
try await task()
} catch {
taskError = error
}
await dismissHUDViewController(hudVC: hudVC)
readyToPerform = false
self.phase = .idle
}
.onChange(of: readyToPerform) { newValue in
if newValue {
self.phase = .loading
}
}
.errorAlert(error: $taskError)
}
}

// MARK: PerformEquatableBlockingModifier

private struct PerformEquatableBlockingModifier<T: Equatable>: ViewModifier {

@Binding
var value: T?

let task: BlockingTaskWithValue<T>

@State
private var hudVC: UIViewController?

@State
private var taskError: Error? = nil

func body(content: Content) -> some View {
content
.task(id: value) {
guard let value = self.value else { return }
hudVC = await presentHUDViewController()
do {
try await task(value)
} catch {
taskError = error
}
await dismissHUDViewController(hudVC: hudVC)
self.value = nil
}
.errorAlert(error: $taskError)
}
}

// MARK: PerformBlockingAfterConfirmationModifier

private struct PerformBlockingAfterConfirmationModifier: ViewModifier {

@Binding
var presentingAlert: Bool

let titleAlert: String
let messageAlert: String?
let textFieldTitle: String?
let buttonAlert: String
let buttonRole: ButtonRole?
let task: BlockingReturnTask

@State
private var readyToPerform: Bool = false

@State
private var hudVC: UIViewController?

@State
private var textFieldValue: String = ""

@State
private var phase: Phase = .idle
private enum Phase {
case idle, loading
}

@State
private var taskError: Error? = nil

func body(content: Content) -> some View {
content
.task(id: phase) {
guard phase == .loading else { return }
hudVC = await presentHUDViewController()
do {
try await task(textFieldValue)
} catch {
taskError = error
}
await dismissHUDViewController(hudVC: hudVC)
readyToPerform = false
textFieldValue = ""
self.phase = .idle
}
.onChange(of: readyToPerform) { newValue in
if newValue {
self.phase = .loading
}
}
.errorAlert(error: $taskError)
.modifier(BlockingAlertView(
titleAlert: titleAlert,
messageAlert: messageAlert,
textFieldTitle: textFieldTitle,
buttonAlert: buttonAlert,
buttonRole: buttonRole,
presentingAlert: $presentingAlert,
readyToPerform: $readyToPerform,
textFieldValue: $textFieldValue
))
}
}

// MARK: PerformEquatableBlockingModifierAfterConfirmationModifier

private struct PerformEquatableBlockingModifierAfterConfirmationModifier<T: Equatable>: ViewModifier {

@Binding
var value: T?

let titleAlert: String
let messageAlert: String?
let textFieldTitle: String?
let buttonAlert: String
let buttonRole: ButtonRole?
let task: BlockingReturnTaskWithValue<T>

@State
private var hudVC: UIViewController?

@State
private var presentingAlert: Bool = false

@State
private var readyToPerform: Bool = false

@State
private var taskError: Error? = nil

@State
private var textFieldValue: String = ""

func body(content: Content) -> some View {
content
.task(id: value) {
guard let _ = self.value else { return }
presentingAlert = true
}
.task(id: readyToPerform) {
guard let value = self.value else { return }
hudVC = await presentHUDViewController()
do {
try await task(.init(task: value, textFieldValue: textFieldValue))
} catch {
taskError = error
}
await dismissHUDViewController(hudVC: hudVC)
readyToPerform = false
textFieldValue = ""
self.value = nil
}
.errorAlert(error: $taskError)
.modifier(BlockingAlertView(
titleAlert: titleAlert,
messageAlert: messageAlert,
textFieldTitle: textFieldTitle,
buttonAlert: buttonAlert,
buttonRole: buttonRole,
presentingAlert: $presentingAlert,
readyToPerform: $readyToPerform,
textFieldValue: $textFieldValue
))
}
}

// MARK: BlockingAlertView

private struct BlockingAlertView: ViewModifier {

let titleAlert: String
let messageAlert: String?
let textFieldTitle: String?
let buttonAlert: String
let buttonRole: ButtonRole?

@Binding
var presentingAlert: Bool

@Binding
var readyToPerform: Bool

@Binding
var textFieldValue: String

func body(content: Content) -> some View {
content
.alert(
titleAlert,
isPresented: $presentingAlert,
actions: {
if let textFieldTitle {
TextField(textFieldTitle, text: $textFieldValue)
// .naturitasFont(forTextStyle: .footnote)
// .foregroundStyle(Color(.naturitasTextTitleColor))
.textInputAutocapitalization(.sentences)
}

Button("cancel".localized, role: .cancel) { }
Button(buttonAlert, role: buttonRole, action: {
readyToPerform = true
})
.disabled({
if textFieldTitle != nil {
return textFieldValue.isEmpty
} else {
return false
}
}())
},
message: {
if let messageAlert {
Text(messageAlert)
}
}
)
}
}

// MARK: HUDView

private struct HUDView: View {

var body: some View {
VStack(spacing: 8) {
ProgressView()
.frame(width: 60, height: 60)
}
.transition(.scale.combined(with: .opacity))
.padding()
.frame(minWidth: 100, minHeight: 100)
.aspectRatio(1, contentMode: .fit)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8))
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
}
}

@MainActor
private func presentHUDViewController() async -> UIViewController? {
#if canImport(UIKit.UIViewController)
guard let windowScene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
let rootVC = windowScene.keyWindow?.visibleViewController else { return nil }
let ___hudVC = UIHostingController(rootView: HUDView())
___hudVC.modalPresentationStyle = .overCurrentContext
___hudVC.modalTransitionStyle = .crossDissolve
___hudVC.view.backgroundColor = .clear
___hudVC.view.isOpaque = false
await rootVC.present(___hudVC, animated: true)
return ___hudVC
#endif
}

@MainActor
private func dismissHUDViewController(hudVC: UIViewController?) async {
#if canImport(UIKit.UIViewController)
await hudVC?.dismiss(animated: true)
#endif
}

0 comments on commit 692ee72

Please sign in to comment.