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

373 lines
12 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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 {}