333 lines
11 KiB
Swift
333 lines
11 KiB
Swift
//
|
||
// 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")
|
||
}
|
||
|
||
}
|