Visual_Novel_iOS/crush/Crush/Src/Components/UI/Alert/EGNewBaseAlert.swift

373 lines
12 KiB
Swift
Raw Normal View History

2025-10-09 10:29:35 +00:00
//
// EGNewBaseAlert.swift
// Crush
//
// Created by Leon on 2025/8/3.
//
import UIKit
import SnapKit
// MARK: - EGNewAlertAction
enum EGNewAlertActionStyle: Int {
case `default`
case cancel
case confirm
case disabled
case inputSave
case destructive
}
class EGNewAlertAction : Equatable {
var title: String
var attributedTitle: NSAttributedString?
var actionStyle: EGNewAlertActionStyle
var autoDismiss: Bool
var actionBlock: (() -> Void)?
init(title: String, actionStyle: EGNewAlertActionStyle = .default, autoDismiss: Bool = true, block: (() -> Void)? = nil) {
self.title = title
self.attributedTitle = nil
self.actionStyle = actionStyle
self.autoDismiss = autoDismiss
self.actionBlock = block
}
// 使 NSAttributedString
init(attributedTitle: NSAttributedString, actionStyle: EGNewAlertActionStyle = .default, autoDismiss: Bool = true, block: (() -> Void)? = nil) {
self.title = "" // 使title
self.attributedTitle = attributedTitle
self.actionStyle = actionStyle
self.autoDismiss = autoDismiss
self.actionBlock = block
}
static func action(title: String, block: (() -> Void)? = nil) -> EGNewAlertAction {
return EGNewAlertAction(title: title, block: block)
}
static func action(title: String, actionStyle: EGNewAlertActionStyle, block: (() -> Void)? = nil) -> EGNewAlertAction {
return EGNewAlertAction(title: title, actionStyle: actionStyle, block: block)
}
static func action(title: String, actionStyle: EGNewAlertActionStyle, autoDismiss: Bool, block: (() -> Void)? = nil) -> EGNewAlertAction {
return EGNewAlertAction(title: title, actionStyle: actionStyle, autoDismiss: autoDismiss, block: block)
}
// 使 NSAttributedString
static func action(attributedTitle: NSAttributedString, block: (() -> Void)? = nil) -> EGNewAlertAction {
return EGNewAlertAction(attributedTitle: attributedTitle, block: block)
}
static func action(attributedTitle: NSAttributedString, actionStyle: EGNewAlertActionStyle, block: (() -> Void)? = nil) -> EGNewAlertAction {
return EGNewAlertAction(attributedTitle: attributedTitle, actionStyle: actionStyle, block: block)
}
static func action(attributedTitle: NSAttributedString, actionStyle: EGNewAlertActionStyle, autoDismiss: Bool, block: (() -> Void)? = nil) -> EGNewAlertAction {
return EGNewAlertAction(attributedTitle: attributedTitle, actionStyle: actionStyle, autoDismiss: autoDismiss, block: block)
}
// MARK: - Equatable
static func == (lhs: EGNewAlertAction, rhs: EGNewAlertAction) -> Bool {
return lhs === rhs // 🔥 Using identity comparison; adjust if unique identifier exists
}
}
// MARK: - EGNewBaseAlert
enum EGNewAlertPriority: Int {
case `default` = 0
case update = 10
case forceUpdate = 100
}
class EGNewBaseAlert: UIView {
// MARK: - Properties
private(set) var backgroundView: UIView!
private(set) var containerView: UIView!
private(set) var textContentView: UIView!
private(set) var buttonContainer: UIView!
private(set) var buttons: [StyleButton] = []
private(set) var actions: [EGNewAlertAction] = []
var priority: EGNewAlertPriority = .default
private var maxActionCount: Int = 2
var containerWidth: CGFloat {
return UIScreen.main.bounds.width - containerMarginLR() * 2
}
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
baseDataInit()
baseUIInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
baseDataInit()
baseUIInit()
}
private func baseDataInit() {
maxActionCount = 2
buttons = []
actions = []
}
private func baseUIInit() {
backgroundView = UIView().then {
addSubview($0)
$0.backgroundColor = .c.cbs
$0.alpha = 0
$0.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
containerView = UIView().then {
addSubview($0)
$0.backgroundColor = .c.csbn // 🔥 Assuming EPS_Color_Surface_base_normal is a light color
$0.layer.cornerRadius = containerCornerRadius()
$0.clipsToBounds = true
$0.alpha = 0
let width = UIScreen.main.bounds.width * 0.8
$0.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(width)
}
}
textContentView = UIView().then {
containerView.addSubview($0)
$0.backgroundColor = .clear
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
$0.snp.makeConstraints { make in
make.leading.trailing.top.equalToSuperview()
make.height.greaterThanOrEqualTo(textContentMinHeight())
}
}
buttonContainer = UIView().then {
containerView.addSubview($0)
$0.backgroundColor = .clear
$0.snp.makeConstraints { make in
make.top.equalTo(textContentView.snp.bottom)
make.leading.trailing.bottom.equalToSuperview()
}
}
}
// MARK: - Layout Methods (Overridable)
func containerMarginLR() -> CGFloat {
return 40
}
func textContentMaxHeight() -> CGFloat {
return UIScreen.main.bounds.height * 0.65
}
func textContentMinHeight() -> CGFloat {
return 100
}
func containerCornerRadius() -> CGFloat {
return 16
}
func containerAlphaDuration() -> CGFloat {
return 0.35
}
func backgroundAlphaValue() -> CGFloat {
return 0.65
}
func alertInitRatio() -> CGFloat {
return 0.6
}
// MARK: - Actions
func addAction(_ action: EGNewAlertAction) {
addAction(action, propertySetup: nil)
}
func addAction(_ action: EGNewAlertAction, propertySetup: ((UIButton) -> Void)?) {
guard actions.count < maxActionCount else { return }
let button = StyleButton(type: .custom)
button.addTarget(self, action: #selector(baseButtonPressed(_:)), for: .touchUpInside)
//
if let attributedTitle = action.attributedTitle {
button.setAttributedTitle(attributedTitle, for: .normal)
} else {
button.setTitle(action.title, for: .normal)
}
button.tag = actions.count
button.titleLabel?.font = .t.tbsl// EPS_Txt_BodySemibold_l is a bold font
reloadButton(button, style: action.actionStyle)
if let setup = propertySetup {
setup(button)
}
actions.append(action)
buttons.append(button)
}
func reloadAction(_ action: EGNewAlertAction, title: String) {
guard let index = actions.firstIndex(of: action) else {
assertionFailure("Action not found")
return
}
let button = buttons[index]
button.setTitle(title, for: .normal)
reloadButton(button, style: action.actionStyle)
}
// reloadAction
func reloadAction(_ action: EGNewAlertAction, attributedTitle: NSAttributedString) {
guard let index = actions.firstIndex(of: action) else {
assertionFailure("Action not found")
return
}
let button = buttons[index]
button.setAttributedTitle(attributedTitle, for: .normal)
reloadButton(button, style: action.actionStyle)
}
private func reloadButton(_ button: StyleButton, style: EGNewAlertActionStyle) {
button.isEnabled = true
switch style {
case .default, .cancel:
button.tertiary(size: .large)
case .confirm,.inputSave:
button.primary(size: .large)
case .disabled:
button.tertiary(size: .large)
button.isEnabled = false
case .destructive:
button.defaultDestructive(size: .large)
}
}
// MARK: - Events
@objc private func baseButtonPressed(_ button: UIButton) {
endEditing(true)
let action = actions[button.tag]
if action.autoDismiss {
dismiss()
}
action.actionBlock?()
}
func alertDidShow() {
// Overridable by subclasses
}
// MARK: - Animation
func showWithAnimation() {
containerView.transform = CGAffineTransform(scaleX: alertInitRatio(), y: alertInitRatio())
UIView.animate(withDuration: containerAlphaDuration(), delay: 0, options: .curveEaseOut, animations: {
self.backgroundView.alpha = self.backgroundAlphaValue()
self.containerView.alpha = 1
self.containerView.transform = .identity
}) { _ in
self.alertDidShow()
}
}
func dismissWithAnimation() {
UIView.animate(withDuration: containerAlphaDuration(), delay: 0, options: .curveEaseIn, animations: {
self.containerView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
self.backgroundView.alpha = 0
self.containerView.alpha = 0
}) { _ in
self.removeFromSuperview()
}
}
// MARK: - Display
func show() {
// Before is windows's first
//guard let window = UIApplication.shared.windows.first else { return }
//
// var window: UIWindow!
// for per in UIApplication.shared.windows.reversed(){
// if per.isHidden == false{
// window = per
// break
// }
// }
// 3
var window: UIWindow!
window = UIWindow.getTopDisplayWindow()!
if window == nil{
assert(false, "alert window not found")
}
window.endEditing(true)
// Alert
for subview in window.subviews {
if let oldAlert = subview as? EGNewBaseAlert {
if oldAlert.priority != .default && oldAlert.priority.rawValue >= priority.rawValue {
return
} else {
oldAlert.dismiss()
}
}
}
window.addSubview(self)
self.frame = window.bounds
showWithAnimation()
Hud.hideIndicator()
}
func dismiss() {
dismissWithAnimation()
}
static func existingAlert() -> Bool {
return existingAlertObj() != nil
}
static func existingAlertObj() -> EGNewBaseAlert? {
//guard let window = UIApplication.shared.windows.first else { return nil }
guard let window = UIWindow.getTopDisplayWindow() else { return nil }
return window.subviews.first(where: { $0 is EGNewBaseAlert }) as? EGNewBaseAlert
}
static func hideAllAlert() {
guard let window = UIApplication.shared.windows.first else { return }
for subview in window.subviews {
if let alert = subview as? EGNewBaseAlert {
alert.isHidden = true
alert.removeFromSuperview()
}
}
}
func setupMaxActionCount(_ count: Int) {
maxActionCount = count
}
}
// MARK: - Then Protocol for Chaining
protocol Then {}
extension Then where Self: AnyObject {
func then(_ block: (Self) -> Void) -> Self {
block(self)
return self
}
}
extension UIView: Then {}