350 lines
12 KiB
Swift
350 lines
12 KiB
Swift
//
|
|
// AddMessagePopView.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import UIKit
|
|
|
|
final class AddMessagePopView: UIView {
|
|
|
|
private static let shared = AddMessagePopView(frame: CGRect(origin: .zero, size: kScreenSize))
|
|
private static let primaryBlue = UIColor(hexStr: "#16B3FF")
|
|
|
|
private let messages = QuickMessageView.messageList
|
|
private var selectedIndex: Int = 0
|
|
private var completion: ((Int?) -> Void)?
|
|
|
|
private let bgView: UIView = {
|
|
let view = UIView()
|
|
view.backgroundColor = .black.withAlphaComponent(0.5)
|
|
return view
|
|
}()
|
|
|
|
private let infoView: UIView = {
|
|
let view = UIView()
|
|
view.backgroundColor = .white
|
|
view.layer.cornerRadius = 28
|
|
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
|
view.clipsToBounds = true
|
|
return view
|
|
}()
|
|
|
|
private let titleLab: UILabel = {
|
|
let label = UILabel()
|
|
label.text = "实时消息"
|
|
label.textColor = UIColor(hexStr: "#293445")
|
|
label.font = .systemFont(ofSize: 18, weight: .bold)
|
|
label.textAlignment = .center
|
|
return label
|
|
}()
|
|
|
|
private let stackView: UIStackView = {
|
|
let stack = UIStackView()
|
|
stack.axis = .vertical
|
|
stack.alignment = .leading
|
|
stack.spacing = 12
|
|
return stack
|
|
}()
|
|
|
|
private lazy var cancelBtn: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setTitle("取消", for: .normal)
|
|
btn.setTitleColor(Self.primaryBlue, for: .normal)
|
|
btn.titleLabel?.font = FontManager.boboBold(18)
|
|
btn.backgroundColor = .white
|
|
btn.cornerRadius = 16
|
|
btn.layer.borderWidth = 1
|
|
btn.layer.borderColor = Self.primaryBlue.cgColor
|
|
btn.addTarget(self, action: #selector(cancelAction), for: .touchUpInside)
|
|
return btn
|
|
}()
|
|
|
|
private lazy var confirmBtn: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setTitle("确定", for: .normal)
|
|
btn.setTitleColor(.white, for: .normal)
|
|
btn.titleLabel?.font = FontManager.boboBold(18)
|
|
btn.backgroundColor = Self.primaryBlue
|
|
btn.cornerRadius = 16
|
|
btn.addTarget(self, action: #selector(confirmAction), for: .touchUpInside)
|
|
return btn
|
|
}()
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
backgroundColor = .clear
|
|
setupUI()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private func setupUI() {
|
|
addSubview(bgView)
|
|
addSubview(infoView)
|
|
bgView.layoutChain.edges()
|
|
|
|
infoView.addSubview(titleLab)
|
|
infoView.addSubview(stackView)
|
|
infoView.addSubview(cancelBtn)
|
|
infoView.addSubview(confirmBtn)
|
|
|
|
infoView.layoutChain
|
|
.bottom()
|
|
.edgesHorzontal()
|
|
|
|
titleLab.layoutChain
|
|
.top(22)
|
|
.edgesHorzontal(20)
|
|
.height(24)
|
|
|
|
cancelBtn.layoutChain
|
|
.left(20)
|
|
.bottom(20 + kSafeBottomMargin)
|
|
.height(44)
|
|
.widthToView(confirmBtn)
|
|
|
|
confirmBtn.layoutChain
|
|
.leftToRightOfView(cancelBtn, offset: 12)
|
|
.right(20)
|
|
.bottomToView(cancelBtn)
|
|
.height(44)
|
|
|
|
stackView.layoutChain
|
|
.topToBottomOfView(titleLab, offset: 20)
|
|
.left(20)
|
|
.right(20)
|
|
.bottomToTopOfView(cancelBtn, offset: -20)
|
|
|
|
for (index, text) in messages.enumerated() {
|
|
let button = UIButton(type: .custom)
|
|
button.tag = index
|
|
var configuration = UIButton.Configuration.plain()
|
|
configuration.title = text
|
|
configuration.baseForegroundColor = UIColor(hexStr: "#293445")
|
|
configuration.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 16, bottom: 10, trailing: 16)
|
|
configuration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
|
|
var outgoing = incoming
|
|
outgoing.font = .systemFont(ofSize: 15, weight: .medium)
|
|
return outgoing
|
|
}
|
|
button.configuration = configuration
|
|
button.layer.cornerRadius = 10
|
|
button.layer.borderWidth = 1
|
|
button.addTarget(self, action: #selector(selectMessage(_:)), for: .touchUpInside)
|
|
stackView.addArrangedSubview(button)
|
|
}
|
|
|
|
let tap = UITapGestureRecognizer(target: self, action: #selector(cancelAction))
|
|
tap.delegate = self
|
|
addGestureRecognizer(tap)
|
|
}
|
|
|
|
private func resetSelection() {
|
|
selectedIndex = 0
|
|
refreshButtons()
|
|
updatePreview()
|
|
}
|
|
|
|
private func refreshButtons() {
|
|
for (index, view) in stackView.arrangedSubviews.enumerated() {
|
|
guard let button = view as? UIButton else { continue }
|
|
let selected = index == selectedIndex
|
|
var configuration = button.configuration
|
|
configuration?.baseForegroundColor = selected ? Self.primaryBlue : UIColor(hexStr: "#293445")
|
|
button.configuration = configuration
|
|
button.backgroundColor = selected ? UIColor(hexStr: "#F3FBFF") : UIColor(hexStr: "#F5F6F8")
|
|
button.layer.borderColor = (selected ? Self.primaryBlue : UIColor.clear).cgColor
|
|
}
|
|
}
|
|
|
|
private func slideOffTransform() -> CGAffineTransform {
|
|
layoutIfNeeded()
|
|
return CGAffineTransform(translationX: 0, y: max(infoView.bounds.height, kScreenHeight * 0.4))
|
|
}
|
|
|
|
private func updatePreview() {
|
|
guard messages.indices.contains(selectedIndex) else { return }
|
|
ReceivedMessagePopView.showPreview(text: messages[selectedIndex])
|
|
}
|
|
|
|
@objc private func selectMessage(_ sender: UIButton) {
|
|
selectedIndex = sender.tag
|
|
refreshButtons()
|
|
updatePreview()
|
|
}
|
|
|
|
@objc private func cancelAction() {
|
|
completion?(nil)
|
|
}
|
|
|
|
@objc private func confirmAction() {
|
|
completion?(selectedIndex)
|
|
}
|
|
}
|
|
|
|
extension AddMessagePopView {
|
|
static func show(completion: @escaping (Int?) -> Void) {
|
|
guard let superView = kKeyWindow else { return }
|
|
let pop = AddMessagePopView.shared
|
|
if pop.superview != nil {
|
|
pop.removeFromSuperview()
|
|
}
|
|
pop.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
|
|
pop.completion = { index in
|
|
completion(index)
|
|
AddMessagePopView.dismiss()
|
|
}
|
|
pop.bgView.alpha = 0
|
|
superView.addSubview(pop)
|
|
superView.bringSubviewToFront(pop)
|
|
pop.layoutIfNeeded()
|
|
pop.resetSelection()
|
|
pop.infoView.transform = pop.slideOffTransform()
|
|
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut) {
|
|
pop.bgView.alpha = 1
|
|
pop.infoView.transform = .identity
|
|
}
|
|
}
|
|
|
|
static func dismiss() {
|
|
let pop = AddMessagePopView.shared
|
|
guard pop.superview != nil else { return }
|
|
ReceivedMessagePopView.dismissPreview()
|
|
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) {
|
|
pop.bgView.alpha = 0
|
|
pop.infoView.transform = pop.slideOffTransform()
|
|
} completion: { _ in
|
|
pop.infoView.transform = .identity
|
|
pop.removeFromSuperview()
|
|
}
|
|
}
|
|
}
|
|
|
|
extension AddMessagePopView: UIGestureRecognizerDelegate {
|
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
|
touch.view == self || touch.view == bgView
|
|
}
|
|
}
|
|
|
|
|
|
|
|
final class ReceivedMessagePopView {
|
|
|
|
private static let banner: UIView = {
|
|
let view = UIView()
|
|
view.backgroundColor = UIColor.white.withAlphaComponent(0.92)
|
|
view.layer.cornerRadius = 10
|
|
view.isHidden = true
|
|
view.isUserInteractionEnabled = false
|
|
view.addSubview(messageLab)
|
|
messageLab.layoutChain.edges(all: 12)
|
|
return view
|
|
}()
|
|
|
|
private static let messageLab: UILabel = {
|
|
let label = UILabel()
|
|
label.font = .systemFont(ofSize: 12, weight: .medium)
|
|
label.textColor = UIColor(hexStr: "#0F2846")
|
|
label.numberOfLines = 2
|
|
return label
|
|
}()
|
|
|
|
private static var hideWorkItem: DispatchWorkItem?
|
|
private static var isPreview = false
|
|
private static var showToken = 0
|
|
private static var didInstallConstraints = false
|
|
|
|
static func showPreview(text: String) {
|
|
let attr = NSAttributedString(
|
|
string: text,
|
|
attributes: [
|
|
.font: UIFont.systemFont(ofSize: 12, weight: .medium),
|
|
.foregroundColor: UIColor(hexStr: "#0F2846")
|
|
]
|
|
)
|
|
isPreview = true
|
|
show(attributed: attr, autoHide: false)
|
|
}
|
|
|
|
static func showIncoming(nickName: String, message: String) {
|
|
let name = nickName.isEmpty ? "成员" : nickName
|
|
let prefix = "对你说:"
|
|
let fullText = name + prefix + message
|
|
let attr = NSMutableAttributedString(string: fullText)
|
|
let fullRange = NSRange(location: 0, length: (fullText as NSString).length)
|
|
let nameRange = NSRange(location: 0, length: (name as NSString).length)
|
|
attr.addAttribute(.font, value: UIFont.systemFont(ofSize: 12, weight: .medium), range: fullRange)
|
|
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#333333"), range: fullRange)
|
|
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"), range: nameRange)
|
|
isPreview = false
|
|
show(attributed: attr, autoHide: true)
|
|
}
|
|
|
|
static func dismissPreview() {
|
|
guard isPreview else { return }
|
|
dismiss()
|
|
}
|
|
|
|
static func dismiss() {
|
|
hideWorkItem?.cancel()
|
|
hideWorkItem = nil
|
|
isPreview = false
|
|
guard banner.superview != nil, !banner.isHidden else { return }
|
|
showToken += 1
|
|
let token = showToken
|
|
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) {
|
|
banner.transform = CGAffineTransform(translationX: 0, y: -90)
|
|
banner.alpha = 0
|
|
} completion: { finished in
|
|
guard finished, token == showToken else { return }
|
|
banner.isHidden = true
|
|
banner.alpha = 1
|
|
banner.transform = .identity
|
|
}
|
|
}
|
|
|
|
private static func show(attributed: NSAttributedString, autoHide: Bool) {
|
|
guard let window = kKeyWindow else { return }
|
|
hideWorkItem?.cancel()
|
|
hideWorkItem = nil
|
|
showToken += 1
|
|
let token = showToken
|
|
let view = banner
|
|
if view.superview != window {
|
|
view.removeFromSuperview()
|
|
didInstallConstraints = false
|
|
window.addSubview(view)
|
|
}
|
|
if !didInstallConstraints {
|
|
view.layoutChain
|
|
.top(kStatusBarHeight)
|
|
.centerX()
|
|
view.widthAnchor.constraint(lessThanOrEqualToConstant: kScreenWidth - 30).isActive = true
|
|
didInstallConstraints = true
|
|
}
|
|
window.bringSubviewToFront(view)
|
|
messageLab.attributedText = attributed
|
|
messageLab.preferredMaxLayoutWidth = kScreenWidth - 54
|
|
let shouldAnimateIn = view.isHidden || view.alpha < 1
|
|
view.isHidden = false
|
|
view.alpha = 1
|
|
if shouldAnimateIn {
|
|
view.transform = CGAffineTransform(translationX: 0, y: -90)
|
|
UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseOut]) {
|
|
view.transform = .identity
|
|
}
|
|
} else {
|
|
view.transform = .identity
|
|
}
|
|
guard autoHide else { return }
|
|
let work = DispatchWorkItem {
|
|
guard token == showToken else { return }
|
|
dismiss()
|
|
}
|
|
hideWorkItem = work
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: work)
|
|
}
|
|
}
|