rename folder

This commit is contained in:
Scott Pfeil 2023-05-26 12:38:48 -04:00
parent 9ea5443639
commit 30c6355973
24 changed files with 239 additions and 6 deletions

View File

@ -2033,7 +2033,7 @@
D29DF11921E68467003B2FB9 /* Containers */,
D22D1F582204D2590077CEC0 /* Legacy */,
D29DF10F21E67A7D003B2FB9 /* BaseControllers */,
D29DF11E21E6851E003B2FB9 /* TopAlert */,
D29DF11E21E6851E003B2FB9 /* TopNotification */,
D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */,
D29DF0D021E404D4003B2FB9 /* Info.plist */,
);
@ -2156,9 +2156,10 @@
path = Containers;
sourceTree = "<group>";
};
D29DF11E21E6851E003B2FB9 /* TopAlert */ = {
D29DF11E21E6851E003B2FB9 /* TopNotification */ = {
isa = PBXGroup;
children = (
AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */,
D2ED2814254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h */,
D2ED2805254B0EB700A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h */,
D2ED2809254B0EB700A1C293 /* MVMCoreTopAlertDelegateProtocol.h */,
@ -2180,7 +2181,7 @@
D29DF12721E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h */,
D29DF12121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m */,
);
path = TopAlert;
path = TopNotification;
sourceTree = "<group>";
};
D29DF13321E68604003B2FB9 /* Styles */ = {
@ -2531,7 +2532,6 @@
AF7E509729E477C0009DC2AD /* AlertController.swift */,
AF7E509629E477C0009DC2AD /* AlertHandler.swift */,
AFA4931F29E5CA73001A9663 /* AlertOperation.swift */,
AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */,
);
path = Alerts;
sourceTree = "<group>";

View File

@ -80,7 +80,7 @@ CGFloat const PanelAnimationDuration = 0.2;
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];
[MVMCoreUISession sharedGlobal].splitViewController = splitViewController;
return splitViewController;

View File

@ -10,7 +10,6 @@ import UIKit
import MVMCore
@objcMembers open class CoreUIObject: MVMCoreObject {
public var globalTopAlertDelegate: MVMCoreGlobalTopAlertDelegateProtocol?
public var alertHandler: AlertHandler?
public var topNotificationHandler: TopNotificationHandler?

View File

@ -7,6 +7,233 @@
//
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 {
@ -234,3 +461,10 @@ extension TopNotificationHandler: MVMCorePresentationDelegateProtocol {
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
}
}