-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Šimon Šesták
committed
Nov 5, 2024
1 parent
a682316
commit 6f672bd
Showing
3 changed files
with
203 additions
and
0 deletions.
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
Sources/FuturedArchitecture/Architecture/QueueAction.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// | ||
// QueueAction.swift | ||
// | ||
// | ||
// Created by Simon Sestak on 11/10/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
public protocol QueueAction: Identifiable { | ||
/// Priority, which defines the order of actions addition to the queue | ||
var priority: QueueActionPriority { get } | ||
} | ||
|
||
public enum QueueActionPriority { | ||
/// Appended to the end of the queue | ||
case normal | ||
/// Added to the start of the queue, but after `highest` | ||
/// If there is already a `high` priority action in the queue, new action will be added after it | ||
case high | ||
/// Added to the start of the queue | ||
/// If there is already a `highest` priority action in the queue, new action will be added after it | ||
case highest | ||
/// Queue will replace all its elements when receives an action with `replaceAll` priority | ||
case replaceAll | ||
|
||
public var higher: Self? { | ||
switch self { | ||
case .normal: | ||
return .high | ||
case .high: | ||
return .highest | ||
case .highest: | ||
return .replaceAll | ||
case .replaceAll: | ||
return nil | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// | ||
// ActionsCache.swift | ||
// | ||
// | ||
// Created by Simon Sestak on 11/10/2024. | ||
// | ||
|
||
import FuturedArchitecture | ||
import Combine | ||
|
||
actor ActionsCache<A: QueueAction> { | ||
let subject: CurrentValueSubject<A?, Never> = CurrentValueSubject(nil) | ||
|
||
private var actions = [A]() { | ||
didSet { | ||
subject.send(actions.first) | ||
} | ||
} | ||
|
||
var currentAction: A? { | ||
actions.first | ||
} | ||
|
||
func add(action: A) { | ||
// If there is already an action with the same id, just replace it with new one | ||
if let index = actions.firstIndex(where: { $0.id == action.id }) { | ||
actions[index] = action | ||
|
||
// Else, append it regarding to its priority | ||
} else { | ||
switch action.priority { | ||
case .normal: | ||
actions.append(action) | ||
case .high, .highest: | ||
if let index = indexForAction(withPriority: action.priority) { | ||
actions.insert(action, at: index) | ||
} else { | ||
actions.insert(action, at: 0) | ||
} | ||
case .replaceAll: | ||
actions = [action] | ||
} | ||
} | ||
} | ||
|
||
func replaceFirstAction(where oldAction: (A) -> Bool, with newAction: A) { | ||
if let index = actions.firstIndex(where: oldAction) { | ||
actions[index] = newAction | ||
} else { | ||
add(action: newAction) | ||
} | ||
} | ||
|
||
func finishCurrent() { | ||
if !actions.isEmpty { | ||
actions.removeFirst() | ||
} | ||
} | ||
|
||
func finishAll(where condition: (A) -> Bool) { | ||
actions.removeAll(where: condition) | ||
} | ||
|
||
func finishAll() { | ||
actions.removeAll() | ||
} | ||
|
||
private func indexForAction(withPriority priority: QueueActionPriority) -> Int? { | ||
guard let lastIndex = actions.lastIndex(where: { $0.priority == priority }) else { | ||
if let higherPriority = priority.higher { | ||
return indexForAction(withPriority: higherPriority) | ||
} else { | ||
return nil | ||
} | ||
} | ||
|
||
return actions.index(after: lastIndex) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// | ||
// ActionsQueue.swift | ||
// | ||
// | ||
// Created by Simon Sestak on 11/10/2024. | ||
// | ||
|
||
import Combine | ||
import FuturedArchitecture | ||
import Foundation | ||
|
||
/// Actions queue which emits new first action using publisher property after current first action is removed | ||
public class ActionsQueue<A: QueueAction> { | ||
/** | ||
- Subscribe on change of the first element in queue | ||
- Will emit new action after the current first action in queue was removed | ||
- Ignores duplicates of the same element | ||
*/ | ||
public let publisher: AnyPublisher<A?, Never> | ||
|
||
private var actionsCache: ActionsCache<A> | ||
private var cancellable: AnyCancellable? | ||
|
||
/** | ||
Init the queue to assign its changes to @Published property, which triggers ui updates automatically | ||
|
||
- Parameters: | ||
- published: @Published.Publisher property which is assigned to action queue publisher. Typically @Published property, that is responsible for presenting alerts, sheets, covers, toasts etc. | ||
*/ | ||
public init(publisher: inout Published<A?>.Publisher) { | ||
self.actionsCache = ActionsCache() | ||
self.publisher = actionsCache.subject | ||
.removeDuplicates { $0?.id == $1?.id } | ||
.eraseToAnyPublisher() | ||
|
||
// Send actions to @Published property | ||
self.publisher | ||
.receive(on: DispatchQueue.main) | ||
.assign(to: &publisher) | ||
|
||
// If @Published property is set to nil, remove first action from queue if present | ||
self.cancellable = | ||
publisher | ||
.map { $0 == nil } | ||
.removeDuplicates() | ||
.sink(receiveValue: removeFirstIfNecessary) | ||
} | ||
|
||
/// Current action in queue (the first one in the array of actions) | ||
public func currentAction() async -> A? { | ||
await actionsCache.currentAction | ||
} | ||
|
||
/// Adds actions to queue | ||
/// If there is already an action with the same id, just replace it with new one | ||
public func add(action: A) async { | ||
await actionsCache.add(action: action) | ||
} | ||
|
||
/// Removes first action in queue | ||
public func finishCurrent() async { | ||
await actionsCache.finishCurrent() | ||
} | ||
|
||
/// Removes all actions, which matches given condition | ||
public func finishAll(where condition: (A) -> Bool) async { | ||
await actionsCache.finishAll(where: condition) | ||
} | ||
|
||
public func finishAll() async { | ||
await actionsCache.finishAll() | ||
} | ||
|
||
/// Replaces first found action in array, that matches given condition, with new one | ||
public func replaceFirstAction(where oldAction: (A) -> Bool, with newAction: A) async { | ||
await actionsCache.replaceFirstAction(where: oldAction, with: newAction) | ||
} | ||
|
||
private func removeFirstIfNecessary(isNil: Bool) { | ||
if isNil { | ||
Task { await finishCurrent() } | ||
} | ||
} | ||
} |