// // SOSView.swift // QuickLocation // // Created by 八条 on 2026/6/18. // import UIKit import RxSwift import RxCocoa import RxGesture import Lottie import SwiftyUserDefaults class SOSView: UIView { var disposeBag = DisposeBag() var sosPracticeView: SOSPracticeView = SOSPracticeView() var countDownFinish: Bool = false private func setupRx() { backBtn.rx.tap.subscribe(onNext: { _ in AppRouter.shared.popOrDismiss() }).disposed(by: disposeBag) exclamationLottieView.rx.tapGesture.subscribe(onNext: { _ in UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut], animations: { self.countDownView.alpha = 1 }, completion: { _ in self.countDownLottieView.play { completed in guard completed else { return } if let path = Bundle.main.path(forResource: "red-exclamation", ofType: "json") { self.countDownLottieView.animation = LottieAnimation.filepath(path) self.countDownLottieView.loopMode = .loop self.countDownLottieView.play() self.countDownTipsLab.text = "已将你的SOS和位置发送到你的圈子和紧急联系人。\n\n您的 SOS 将发送给5个人" self.countDownFinish = true } } }) }).disposed(by: disposeBag) sliderIcon.rx.panGesture() .subscribe(onNext: { [weak self] gesture in guard let self = self else { return } let translation = gesture.translation(in: self.sliderView) let maxX = self.sliderView.bounds.width - self.sliderIcon.frame.width switch gesture.state { case .began, .changed: var newX = self.sliderIcon.frame.minX + translation.x newX = max(0, min(maxX, newX)) self.sliderIcon.frame.origin.x = newX gesture.setTranslation(.zero, in: self.sliderView) case .ended: if self.sliderIcon.frame.minX >= maxX - 5 { if countDownFinish { } else { self.countDownView.alpha = 0 self.countDownLottieView.stop() } } UIView.animate(withDuration: 0.3) { self.sliderIcon.frame.origin.x = 3 } default: break } }) .disposed(by: disposeBag) } private func setupUI() { addSubview(exclamationLottieView) addSubview(messageLab) addSubview(tipsLab) addSubview(bottomTipsLab) addSubview(addContactView) addSubview(sosPracticeView) addSubview(countDownView) addSubview(navBgView) addSubview(navBarView) navBarView.addSubview(navTitleLabel) navBarView.addSubview(backBtn) navBgView.layoutChain .edges(excludingEdge: .bottom) .heightToWidth(160/375) navBarView.layoutChain .edges(excludingEdge: .bottom) .height(kNaviHeight) navTitleLabel.layoutChain .top(kStatusBarHeight + 12) .centerY(backBtn) .centerX() backBtn.layoutChain .centerY(navTitleLabel) .left(15) .width(24) .height(24) sosPracticeView.layoutChain .topToBottomOfView(navBarView) .edges(excludingEdge: .top) exclamationLottieView.layoutChain .topToBottomOfView(navBarView, offset: 40) .edgesHorzontal(28) .heightToWidth(1) messageLab.layoutChain .topToBottomOfView(exclamationLottieView, offset: 19) .centerX() tipsLab.layoutChain .topToBottomOfView(messageLab, offset: 10) .edgesHorzontal(23) addContactView.layoutChain .topToBottomOfView(tipsLab, offset: 30) .centerX() countDownView.layoutChain .topToBottomOfView(navBarView) .edges(excludingEdge: .top) bottomTipsLab.layoutChain .centerX() .bottom(kSafeBottomMargin + 40) } lazy var navBgView: UIImageView = { let iv = UIImageView() iv.image = UIImage(named: "Common/navBar_bg_2") iv.contentMode = .scaleAspectFill return iv }() lazy var navBarView: UIView = { let view = UIView() view.backgroundColor = .clear return view }() lazy var navTitleLabel: UILabel = { let label = UILabel() label.text = "SOS" label.font = .systemFont(ofSize: 18, weight: .medium) label.textColor = ThemeManager.shared.color.titleAuxColor label.textAlignment = .center return label }() lazy var backBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "Common/back"), for: .normal) btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100) return btn }() lazy var exclamationLottieView: LottieAnimationView = { let view = LottieAnimationView(name: "yellow-exclamation") view.loopMode = .loop return view }() lazy var messageLab: UILabel = { let label = UILabel() label.text = "当您感到紧张或不安全时,请按住此按钮。" label.font = .systemFont(ofSize: 16, weight: .medium) label.textColor = UIColor(hexStr: "#333333") return label }() lazy var tipsLab: UILabel = { let label = UILabel() label.text = "提示:若你在气泡当中时,发送SOS将会自动从气泡中弹出。" label.font = .systemFont(ofSize: 14, weight: .regular) label.textColor = UIColor(hexStr: "#999999") label.numberOfLines = 0 return label }() lazy var addContactView: UIView = { let view = UIView() view.backgroundColor = .clear let icon = UIImageView(image: UIImage(named: "SOS/add")) view.addSubview(icon) icon.layoutChain .left() .edgesVertical() .height(30) .width(30) let label = UILabel() label.text = "添加紧急联系人" label.font = .systemFont(ofSize: 14, weight: .medium) view.addSubview(label) label.layoutChain .leftToRightOfView(icon, offset: 10) .right() .centerY() return view }() // 倒计时 lazy var countDownView: UIView = { let view = UIView() view.backgroundColor = .white view.alpha = 0 view.addSubview(countDownLottieView) countDownLottieView.layoutChain .top(40) .edgesHorzontal(27) .heightToWidth(1) let titleLab = UILabel() titleLab.text = "滑动取消" titleLab.font = .systemFont(ofSize: 26, weight: .semibold) view.addSubview(titleLab) titleLab.layoutChain .topToBottomOfView(countDownLottieView, offset: 15) .centerX() view.addSubview(countDownTipsLab) countDownTipsLab.layoutChain .topToBottomOfView(titleLab, offset: 8) .edgesHorzontal(38) view.addSubview(sliderView) sliderView.layoutChain .edgesHorzontal(30) .bottom(kSafeBottomMargin + 30) .height(50) return view }() lazy var countDownLottieView: LottieAnimationView = { let view = LottieAnimationView(name: "10-second-timer") view.loopMode = .playOnce return view }() lazy var countDownTipsLab: UILabel = { let tipsLab = UILabel() tipsLab.text = "10秒后,会将你的SOS和位置发送到你的圈子和紧急联系人。" tipsLab.font = .systemFont(ofSize: 16, weight: .medium) tipsLab.textAlignment = .center tipsLab.numberOfLines = 0 return tipsLab }() lazy var sliderView: UIView = { let view = UIView() view.backgroundColor = .clear view.cornerRadius = 25 let imgView = UIImageView(image: UIImage(named: "Common/button_bg_2")) imgView.contentMode = .scaleAspectFill view.addSubview(imgView) imgView.layoutChain.edges() let label = UILabel() label.text = "滑动以取消SOS" label.textColor = .white label.font = .systemFont(ofSize: 16, weight: .medium) view.addSubview(label) label.layoutChain.centerX().centerY() view.addSubview(sliderIcon) return view }() lazy var sliderIcon: UIImageView = { let view = UIImageView(frame: CGRectMake(3, 2, 46, 46)) view.image = UIImage(named: "SOS/slider") view.isUserInteractionEnabled = true return view }() lazy var bottomTipsLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#333333") let text = "轻点感叹号发送SOS" let attr = NSMutableAttributedString(string: text) attr.addAttribute(.font, value: UIFont.systemFont(ofSize: 20, weight: .semibold), range: NSRange(location: 0, length: text.count)) attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#FF383C"), range: NSRange(location: "轻点感叹号发送".count, length: "SOS".count)) label.attributedText = attr return label }() override init(frame: CGRect) { super.init(frame: .zero) backgroundColor = .white setupUI() setupRx() exclamationLottieView.play() let isHidden = Defaults[\.sosIsPracticeList].first(where: { $0 == AppContextManager.shared.userId }) != nil sosPracticeView.isHidden = isHidden guard AppContextManager.shared.status == 1 else { return } countDownView.alpha = 1 countDownFinish = true if let path = Bundle.main.path(forResource: "red-exclamation", ofType: "json") { self.countDownLottieView.animation = LottieAnimation.filepath(path) self.countDownLottieView.loopMode = .loop self.countDownLottieView.play() self.countDownTipsLab.text = "已将你的SOS和位置发送到你的圈子和紧急联系人。\n\n您的 SOS 将发送给5个人" } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }