rename folder
This commit is contained in:
parent
9ea5443639
commit
30c6355973
@ -2033,7 +2033,7 @@
|
|||||||
D29DF11921E68467003B2FB9 /* Containers */,
|
D29DF11921E68467003B2FB9 /* Containers */,
|
||||||
D22D1F582204D2590077CEC0 /* Legacy */,
|
D22D1F582204D2590077CEC0 /* Legacy */,
|
||||||
D29DF10F21E67A7D003B2FB9 /* BaseControllers */,
|
D29DF10F21E67A7D003B2FB9 /* BaseControllers */,
|
||||||
D29DF11E21E6851E003B2FB9 /* TopAlert */,
|
D29DF11E21E6851E003B2FB9 /* TopNotification */,
|
||||||
D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */,
|
D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */,
|
||||||
D29DF0D021E404D4003B2FB9 /* Info.plist */,
|
D29DF0D021E404D4003B2FB9 /* Info.plist */,
|
||||||
);
|
);
|
||||||
@ -2156,9 +2156,10 @@
|
|||||||
path = Containers;
|
path = Containers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D29DF11E21E6851E003B2FB9 /* TopAlert */ = {
|
D29DF11E21E6851E003B2FB9 /* TopNotification */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */,
|
||||||
D2ED2814254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h */,
|
D2ED2814254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h */,
|
||||||
D2ED2805254B0EB700A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h */,
|
D2ED2805254B0EB700A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h */,
|
||||||
D2ED2809254B0EB700A1C293 /* MVMCoreTopAlertDelegateProtocol.h */,
|
D2ED2809254B0EB700A1C293 /* MVMCoreTopAlertDelegateProtocol.h */,
|
||||||
@ -2180,7 +2181,7 @@
|
|||||||
D29DF12721E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h */,
|
D29DF12721E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h */,
|
||||||
D29DF12121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m */,
|
D29DF12121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m */,
|
||||||
);
|
);
|
||||||
path = TopAlert;
|
path = TopNotification;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D29DF13321E68604003B2FB9 /* Styles */ = {
|
D29DF13321E68604003B2FB9 /* Styles */ = {
|
||||||
@ -2531,7 +2532,6 @@
|
|||||||
AF7E509729E477C0009DC2AD /* AlertController.swift */,
|
AF7E509729E477C0009DC2AD /* AlertController.swift */,
|
||||||
AF7E509629E477C0009DC2AD /* AlertHandler.swift */,
|
AF7E509629E477C0009DC2AD /* AlertHandler.swift */,
|
||||||
AFA4931F29E5CA73001A9663 /* AlertOperation.swift */,
|
AFA4931F29E5CA73001A9663 /* AlertOperation.swift */,
|
||||||
AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */,
|
|
||||||
);
|
);
|
||||||
path = Alerts;
|
path = Alerts;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|||||||
@ -80,7 +80,7 @@ CGFloat const PanelAnimationDuration = 0.2;
|
|||||||
return [MVMCoreActionUtility initializerClassCheck:[MVMCoreUISession sharedGlobal].splitViewController classToVerify:self];
|
return [MVMCoreActionUtility initializerClassCheck:[MVMCoreUISession sharedGlobal].splitViewController classToVerify:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (nullable instancetype)setup:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel {
|
+ (nullable instancetype)setup:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel topAlertView:(nonnull UIView <MVMCoreTopAlertViewProtocol>*)topAlertView {
|
||||||
MVMCoreUISplitViewController *splitViewController = [[self alloc] initWithLeftPanel:leftPanel rightPanel:rightPanel];
|
MVMCoreUISplitViewController *splitViewController = [[self alloc] initWithLeftPanel:leftPanel rightPanel:rightPanel];
|
||||||
[MVMCoreUISession sharedGlobal].splitViewController = splitViewController;
|
[MVMCoreUISession sharedGlobal].splitViewController = splitViewController;
|
||||||
return splitViewController;
|
return splitViewController;
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import UIKit
|
|||||||
import MVMCore
|
import MVMCore
|
||||||
|
|
||||||
@objcMembers open class CoreUIObject: MVMCoreObject {
|
@objcMembers open class CoreUIObject: MVMCoreObject {
|
||||||
public var globalTopAlertDelegate: MVMCoreGlobalTopAlertDelegateProtocol?
|
|
||||||
public var alertHandler: AlertHandler?
|
public var alertHandler: AlertHandler?
|
||||||
public var topNotificationHandler: TopNotificationHandler?
|
public var topNotificationHandler: TopNotificationHandler?
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,233 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import MVMCore
|
import MVMCore
|
||||||
|
import Dispatch
|
||||||
|
|
||||||
|
public protocol NotificationTransitionDelegateProtocol {
|
||||||
|
@MainActor
|
||||||
|
func show(notification: UIView) async
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func hide(notification: UIView) async
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func update(with model: TopNotificationModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NotificationOperation: MVMCoreOperation {
|
||||||
|
|
||||||
|
private let notification: UIView
|
||||||
|
|
||||||
|
private var notificationModel: TopNotificationModel
|
||||||
|
|
||||||
|
/// The delegate that manages transitioning the notification.
|
||||||
|
private let transitionDelegate: NotificationTransitionDelegateProtocol
|
||||||
|
|
||||||
|
/// The notification animation transition operation (show or hide).
|
||||||
|
private var transitionOperation: MVMCoreOperation?
|
||||||
|
|
||||||
|
/// The stop timer for non-persistent notifications.
|
||||||
|
private var timerSource: DispatchSourceTimer?
|
||||||
|
|
||||||
|
/// Determines if the operation is ready. Certain notifications are only meant to be displayed on certain pages.
|
||||||
|
public var isDisplayable: Bool {
|
||||||
|
get {
|
||||||
|
var isDisplayable: Bool = true
|
||||||
|
displayableQueue.sync {
|
||||||
|
isDisplayable = _isDisplayable
|
||||||
|
}
|
||||||
|
return isDisplayable
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
guard newValue != isDisplayable else { return }
|
||||||
|
displayableQueue.async(flags: .barrier) { [weak self] in
|
||||||
|
self?._isDisplayable = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var displayableQueue = DispatchQueue(label: "displayable", attributes: .concurrent)
|
||||||
|
private var _isDisplayable: Bool = true {
|
||||||
|
willSet {
|
||||||
|
guard super.isReady else { return }
|
||||||
|
willChangeValue(for: \.isReady)
|
||||||
|
}
|
||||||
|
didSet {
|
||||||
|
guard super.isReady else { return }
|
||||||
|
didChangeValue(for: \.isReady)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This operation is ready only if this notification is allowed to show.
|
||||||
|
public override var isReady: Bool {
|
||||||
|
get {
|
||||||
|
guard !isCancelled else { return super.isReady }
|
||||||
|
return super.isReady && isDisplayable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private actor Properties {
|
||||||
|
private var isDisplayed: Bool = false
|
||||||
|
private var isAnimating: Bool = false
|
||||||
|
|
||||||
|
func set(displayed: Bool) {
|
||||||
|
isDisplayed = displayed
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIsDisplayed() -> Bool {
|
||||||
|
return isDisplayed
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(animating: Bool) {
|
||||||
|
isAnimating = animating
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIsAnimating() -> Bool {
|
||||||
|
return isAnimating
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var properties = Properties()
|
||||||
|
|
||||||
|
// A flag for tracking if the operation needs to be re-added because it was cancelled for a higher priority notification.
|
||||||
|
public var reAddAfterCancel = false
|
||||||
|
|
||||||
|
public init(with notification: UIView, notificationModel: TopNotificationModel, transitionDelegate: NotificationTransitionDelegateProtocol) {
|
||||||
|
self.notification = notification
|
||||||
|
self.notificationModel = notificationModel
|
||||||
|
self.transitionDelegate = transitionDelegate
|
||||||
|
super.init()
|
||||||
|
queuePriority = notificationModel.priority
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func main() {
|
||||||
|
guard !checkAndHandleForCancellation() else { return }
|
||||||
|
add {
|
||||||
|
await self.showNotification()
|
||||||
|
guard !self.isCancelled else {
|
||||||
|
// Cancelled, dismiss immediately.
|
||||||
|
self.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.updateStopTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func stop() {
|
||||||
|
if let timerSource = timerSource {
|
||||||
|
timerSource.cancel()
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
guard await properties.getIsDisplayed(),
|
||||||
|
await !properties.getIsAnimating() else { return }
|
||||||
|
add {
|
||||||
|
await self.hideNotification()
|
||||||
|
guard !self.isCancelled,
|
||||||
|
!self.notificationModel.persistent else { return }
|
||||||
|
self.markAsFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the transition of the notification to the queue.
|
||||||
|
private func add(transition: @escaping () async -> Void) {
|
||||||
|
Task {
|
||||||
|
guard await properties.getIsDisplayed(),
|
||||||
|
await !properties.getIsAnimating() else { return }
|
||||||
|
transitionOperation = MVMCoreBlockOperation(block: { [weak self] blockOperation in
|
||||||
|
guard !blockOperation.checkAndHandleForCancellation() else { return }
|
||||||
|
guard let self = self else {
|
||||||
|
blockOperation.markAsFinished()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
await transition()
|
||||||
|
blockOperation.markAsFinished()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
transitionOperation?.completionBlock = { [weak self] in
|
||||||
|
self?.transitionOperation = nil
|
||||||
|
}
|
||||||
|
// Add the animation to the navigation queue to avoid animation collisions.
|
||||||
|
await MVMCoreNavigationHandler.shared()?.addNavigationOperation(transitionOperation!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateStopTimer() {
|
||||||
|
if let timerSource = timerSource {
|
||||||
|
timerSource.cancel()
|
||||||
|
}
|
||||||
|
guard !notificationModel.persistent else { return }
|
||||||
|
timerSource = DispatchSource.makeTimerSource()
|
||||||
|
timerSource?.setEventHandler(handler: { [weak self] in
|
||||||
|
print("SSSS TIMER EVENT FIRED FOR: \(String(describing: self?.notificationModel.type))")
|
||||||
|
guard let self = self,
|
||||||
|
!self.isFinished,
|
||||||
|
!self.checkAndHandleForCancellation() else { return }
|
||||||
|
self.stop()
|
||||||
|
})
|
||||||
|
timerSource?.setCancelHandler(handler: { [weak self] in
|
||||||
|
print("SSSS TIMER EVENT CANCELLED FOR: \(String(describing: self?.notificationModel.type))")
|
||||||
|
})
|
||||||
|
timerSource?.schedule(deadline: .now() + .seconds(notificationModel.dismissTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func cancel() {
|
||||||
|
super.cancel()
|
||||||
|
Task {
|
||||||
|
if await !properties.getIsDisplayed() {
|
||||||
|
// Cancel any pending show transitions.
|
||||||
|
transitionOperation?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do nothing if animating.
|
||||||
|
guard await !properties.getIsAnimating() else { return }
|
||||||
|
if await properties.getIsDisplayed() {
|
||||||
|
stop()
|
||||||
|
} else if isExecuting {
|
||||||
|
markAsFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func showNotification() async {
|
||||||
|
await properties.set(animating: true)
|
||||||
|
await transitionDelegate.show(notification: notification)
|
||||||
|
await properties.set(displayed: true)
|
||||||
|
await properties.set(animating: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func hideNotification() async {
|
||||||
|
await properties.set(animating: true)
|
||||||
|
await transitionDelegate.hide(notification: notification)
|
||||||
|
await properties.set(displayed: false)
|
||||||
|
await properties.set(animating: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the notification with the new model.
|
||||||
|
public func update(with model: TopNotificationModel) {
|
||||||
|
self.notificationModel = model
|
||||||
|
queuePriority = model.priority
|
||||||
|
guard isExecuting,
|
||||||
|
!isCancelled else { return }
|
||||||
|
updateStopTimer()
|
||||||
|
Task { @MainActor in
|
||||||
|
transitionDelegate.update(with: notificationModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copy(with zone: NSZone? = nil) -> Any {
|
||||||
|
let operation = NotificationOperation(with: notification, notificationModel: notificationModel, transitionDelegate: transitionDelegate)
|
||||||
|
operation.reAddAfterCancel = reAddAfterCancel
|
||||||
|
operation.isDisplayable = isDisplayable
|
||||||
|
for dependency in dependencies {
|
||||||
|
operation.addDependency(dependency)
|
||||||
|
}
|
||||||
|
operation.name = name
|
||||||
|
operation.qualityOfService = qualityOfService
|
||||||
|
return operation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class TopNotificationHandler {
|
public class TopNotificationHandler {
|
||||||
|
|
||||||
@ -234,3 +461,10 @@ extension TopNotificationHandler: MVMCorePresentationDelegateProtocol {
|
|||||||
reevaluteQueue()
|
reevaluteQueue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NotificationOperation {
|
||||||
|
/// Updates if the operation is displayable based on the page type.
|
||||||
|
func updateDisplayable(by pageType: String) {
|
||||||
|
isDisplayable = notificationModel.pages?.contains(pageType) ?? true
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user