// // CreateBubbleSetupView.swift // QuickLocation // import UIKit import RxSwift import RxCocoa import SwiftDate /// 设置页:时长 picker + 同页底部通知 sheet final class CreateBubbleSetupView: UIView { var disposeBag = DisposeBag() let selectedHour = BehaviorRelay(value: 1) var messageText: String = "" var onConfirmCreate: (() -> Void)? var onSheetVisibilityChanged: ((Bool) -> Void)? private let hours = Array(1...6) let heroView = BubbleHeroView() let confirmBtn = UIButton(type: .custom) let sheetConfirmBtn = UIButton(type: .custom) private let subtitleLab = UILabel() private let titleLab = UILabel() private let pickerView = UIPickerView() private let hourLab = UILabel() private let sheetOverlay = UIView() private let sheetPanel = UIView() private let pinIcon = UIImageView() private let sheetTitleLab = UILabel() private let messageView = UIView() private let messageLab = UILabel() private let previewAvatar = UIImageView() private let tipStack = UIStackView() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .white setupUI() setupPicker() setupSheet() pickerView.selectRow(0, inComponent: 0, animated: false) selectedHour.accept(hours[0]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func showNotifySheet(message: String) { messageText = message messageLab.text = message sheetOverlay.isHidden = false sheetOverlay.alpha = 0 sheetPanel.transform = CGAffineTransform(translationX: 0, y: 40) onSheetVisibilityChanged?(true) UIView.animate(withDuration: 0.28, delay: 0, options: .curveEaseOut) { self.sheetOverlay.alpha = 1 self.sheetPanel.transform = .identity } } func dismissNotifySheet() { onSheetVisibilityChanged?(false) UIView.animate(withDuration: 0.22, delay: 0, options: .curveEaseIn) { self.sheetOverlay.alpha = 0 self.sheetPanel.transform = CGAffineTransform(translationX: 0, y: 40) } completion: { _ in self.sheetOverlay.isHidden = true } } var isSheetVisible: Bool { !sheetOverlay.isHidden && sheetOverlay.alpha > 0.01 } private func setupUI() { addSubview(heroView) addSubview(titleLab) addSubview(subtitleLab) addSubview(pickerView) addSubview(hourLab) addSubview(confirmBtn) heroView.layoutChain .top(33) .leading(30) .trailing(30) .heightToWidth(1) titleLab.text = "您会在气泡待多久?" titleLab.font = FontManager.boboBold(30) titleLab.textColor = UIColor(hexStr: "#293445") titleLab.textAlignment = .center titleLab.layoutChain.topToBottomOfView(heroView, offset: 25).centerX() subtitleLab.text = "设置气泡时间" subtitleLab.font = .systemFont(ofSize: 18, weight: .bold) subtitleLab.textColor = UIColor(hexStr: "#9CA3AF") subtitleLab.layoutChain.topToBottomOfView(titleLab, offset: 4).centerX() pickerView.layoutChain .topToBottomOfView(subtitleLab, offset: 12) .edgesHorzontal(15) .height(180) hourLab.text = "小时" hourLab.font = .systemFont(ofSize: 16, weight: .medium) hourLab.textColor = UIColor(hexStr: "#16B3FF") hourLab.layoutChain.centerX(self, offset: 100).centerY(pickerView) confirmBtn.setTitle("确定", for: .normal) confirmBtn.setTitleColor(.white, for: .normal) confirmBtn.titleLabel?.font = FontManager.boboBold(18) confirmBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal) confirmBtn.cornerRadius = 20 confirmBtn.layoutChain.left(30).right(30).bottom(34).height(56) } private func setupPicker() { pickerView.delegate = self pickerView.dataSource = self pickerView.backgroundColor = .clear } override func layoutSubviews() { super.layoutSubviews() clearPickerSelectionBackground() } private func clearPickerSelectionBackground() { pickerView.subviews.forEach { sub in sub.backgroundColor = .clear if sub.frame.height <= 1 { sub.isHidden = true } } } private func setupSheet() { sheetOverlay.backgroundColor = UIColor.black.withAlphaComponent(0.45) sheetOverlay.isHidden = true addSubview(sheetOverlay) sheetOverlay.layoutChain.edges() sheetPanel.backgroundColor = .white sheetPanel.layer.cornerRadius = 24 sheetPanel.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] sheetOverlay.addSubview(sheetPanel) sheetPanel.layoutChain.edgesHorzontal().bottom() pinIcon.image = UIImage(named: "Bubble/pin") pinIcon.contentMode = .scaleAspectFit sheetPanel.addSubview(pinIcon) pinIcon.layoutChain.top(-22).centerX().width(44).height(44) sheetTitleLab.text = "通知您的圈子" sheetTitleLab.font = .systemFont(ofSize: 18, weight: .bold) sheetTitleLab.textColor = UIColor(hexStr: "#293445") sheetTitleLab.textAlignment = .center sheetPanel.addSubview(sheetTitleLab) sheetTitleLab.layoutChain.top(28).centerX() messageView.backgroundColor = UIColor(hexStr: "#EBF6F9") messageView.layer.cornerRadius = 12 sheetPanel.addSubview(messageView) messageView.addSubview(messageLab) messageLab.font = .systemFont(ofSize: 15, weight: .medium) messageLab.textColor = UIColor(hexStr: "#293445") messageLab.numberOfLines = 0 messageLab.layoutChain.edgesHorzontal(16).edgesVertical(14) previewAvatar.contentMode = .scaleAspectFill previewAvatar.clipsToBounds = true previewAvatar.layer.cornerRadius = 18 previewAvatar.layer.borderWidth = 2 previewAvatar.layer.borderColor = UIColor.white.cgColor let avatar = AppContextManager.shared.avaterIcon previewAvatar.image = avatar.size.width > 0 ? avatar : UIImage(named: "Common/default_avatar") sheetPanel.addSubview(previewAvatar) messageView.layoutChain .topToBottomOfView(sheetTitleLab, offset: 20) .left(24) .right(72) previewAvatar.layoutChain .right(24) .bottomToView(messageView) .width(40) .height(40) tipStack.axis = .vertical tipStack.spacing = 12 sheetPanel.addSubview(tipStack) tipStack.layoutChain .topToBottomOfView(messageView, offset: 16) .edgesHorzontal(24) [ "您的圈子成员只会看到您在气泡中。", "当您在气泡中,将不会共享您的确切位置。" ].forEach { tipStack.addArrangedSubview(makeTipRow($0)) } sheetConfirmBtn.setTitle("确定", for: .normal) sheetConfirmBtn.setTitleColor(.white, for: .normal) sheetConfirmBtn.titleLabel?.font = FontManager.boboBold(18) sheetConfirmBtn.backgroundColor = UIColor(hexStr: "#16B3FF") sheetConfirmBtn.layer.cornerRadius = 16 sheetPanel.addSubview(sheetConfirmBtn) sheetConfirmBtn.layoutChain .topToBottomOfView(tipStack, offset: 20) .edgesHorzontal(20) .height(56) .bottom(24 + kSafeBottomMargin) let tap = UITapGestureRecognizer(target: self, action: #selector(tapMask)) sheetOverlay.addGestureRecognizer(tap) } private func makeTipRow(_ text: String) -> UIView { let row = UIView() let star = UIImageView(image: UIImage(systemName: "sparkle")) star.tintColor = UIColor(hexStr: "#16B3FF") star.contentMode = .scaleAspectFit let lab = UILabel() lab.text = text lab.font = .systemFont(ofSize: 14, weight: .medium) lab.textColor = UIColor(hexStr: "#767676") lab.numberOfLines = 0 row.addSubview(star) row.addSubview(lab) star.layoutChain.left().top(2).width(14).height(14) lab.layoutChain.leftToRightOfView(star, offset: 8).right().top().bottom() return row } @objc private func tapMask(_ gesture: UITapGestureRecognizer) { let point = gesture.location(in: sheetPanel) if sheetPanel.bounds.contains(point) { return } dismissNotifySheet() } func buildMessage(for hours: Int) -> String { let endDate = Calendar.current.date(byAdding: .hour, value: hours, to: Date()) ?? Date() let timeStr = endDate.toFormat("HH:mm") return "\(timeStr)之前,我会一直在这个区域。如果需要我帮忙,请给我发消息。" } } extension CreateBubbleSetupView: UIPickerViewDataSource, UIPickerViewDelegate { func numberOfComponents(in pickerView: UIPickerView) -> Int { 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { hours.count } func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { 44 } func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { let container = view as? UIView ?? UIView() container.subviews.forEach { $0.removeFromSuperview() } let label = UILabel() label.textAlignment = .center label.font = .systemFont(ofSize: 20, weight: .medium) let selected = row == pickerView.selectedRow(inComponent: component) label.textColor = selected ? UIColor(hexStr: "#16B3FF") : UIColor(hexStr: "#999999") label.text = "\(hours[row])" container.addSubview(label) label.layoutChain.center().width(60).height(36) if selected { let capsule = UIView() capsule.backgroundColor = UIColor(hexStr: "#E3F6FF") capsule.layer.cornerRadius = 14 container.insertSubview(capsule, at: 0) capsule.layoutChain.center().width(130).height(36) } return container } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { selectedHour.accept(hours[row]) pickerView.reloadAllComponents() clearPickerSelectionBackground() } }