440 lines
15 KiB
Swift
440 lines
15 KiB
Swift
//
|
||
// SOSPracticeView.swift
|
||
// QuickLocation
|
||
//
|
||
// Created by 八条 on 2026/6/18.
|
||
//
|
||
|
||
import UIKit
|
||
import RxSwift
|
||
import RxCocoa
|
||
import RxGesture
|
||
import Lottie
|
||
import SwiftyUserDefaults
|
||
|
||
class SOSPracticeView: UIView {
|
||
|
||
var disposeBag = DisposeBag()
|
||
|
||
var countDownFinish: Bool = false
|
||
|
||
private func setupRx() {
|
||
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和位置发送到你的圈子和紧急联系人。"
|
||
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 {
|
||
UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut]) {
|
||
self.finishView.alpha = 1
|
||
}
|
||
}
|
||
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(titleView)
|
||
addSubview(exclamationLottieView)
|
||
addSubview(tipsLab)
|
||
addSubview(bottomTipsLab)
|
||
addSubview(scrollView)
|
||
addSubview(pageControl)
|
||
addSubview(countDownView)
|
||
addSubview(finishView)
|
||
|
||
titleView.layoutChain
|
||
.top(15)
|
||
.centerX()
|
||
|
||
exclamationLottieView.layoutChain
|
||
.topToBottomOfView(titleView, offset: 13)
|
||
.edgesHorzontal(28)
|
||
.heightToWidth(1)
|
||
|
||
tipsLab.layoutChain
|
||
.topToBottomOfView(exclamationLottieView, offset: 60)
|
||
.centerX()
|
||
|
||
bottomTipsLab.layoutChain
|
||
.centerX()
|
||
.bottom(kSafeBottomMargin + 40)
|
||
|
||
scrollView.layoutChain
|
||
.top(15)
|
||
.edges(excludingEdge: .top)
|
||
|
||
pageControl.layoutChain
|
||
.centerX()
|
||
.bottom(kSafeBottomMargin + 80)
|
||
|
||
countDownView.layoutChain
|
||
.edges()
|
||
|
||
finishView.layoutChain.edges()
|
||
}
|
||
|
||
lazy var titleView: UIView = {
|
||
let view = UIView()
|
||
view.backgroundColor = .clear
|
||
|
||
view.addSubview(titleLab)
|
||
titleLab.layoutChain
|
||
.edgesVertical(9)
|
||
.edgesHorzontal(38)
|
||
|
||
return view
|
||
}()
|
||
|
||
lazy var titleLab: UILabel = {
|
||
let label = UILabel()
|
||
label.text = "开始练习"
|
||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||
label.textColor = .white
|
||
label.textAlignment = .center
|
||
return label
|
||
}()
|
||
|
||
lazy var exclamationLottieView: LottieAnimationView = {
|
||
let view = LottieAnimationView(name: "yellow-exclamation")
|
||
view.loopMode = .loop
|
||
return view
|
||
}()
|
||
|
||
lazy var tipsLab: UILabel = {
|
||
let label = UILabel()
|
||
label.text = "当您感到紧张或不安全时,请按住此按钮。"
|
||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||
label.textColor = UIColor(hexStr: "#333333")
|
||
return label
|
||
}()
|
||
|
||
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
|
||
}()
|
||
|
||
lazy var scrollView: UIScrollView = {
|
||
let view = UIScrollView()
|
||
view.backgroundColor = .white
|
||
view.isPagingEnabled = true
|
||
view.showsHorizontalScrollIndicator = false
|
||
view.bounces = false
|
||
view.delegate = self
|
||
|
||
view.addSubview(scrollContentView)
|
||
scrollContentView.layoutChain.edges().heightToView(view)
|
||
|
||
let view1 = UIView()
|
||
view1.backgroundColor = .clear
|
||
let img1 = UIImageView(image: UIImage(named: "SOS/1"))
|
||
let nextBtn = UIButton(type: .custom)
|
||
nextBtn.setTitle("开始设置", for: .normal)
|
||
nextBtn.setTitleColor(.white, for: .normal)
|
||
nextBtn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||
nextBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||
nextBtn.cornerRadius = 25
|
||
nextBtn.rx.tap.subscribe(onNext: { _ in
|
||
view.setContentOffset(CGPointMake(kScreenWidth, 0), animated: true)
|
||
self.pageControl.currentPage = 1
|
||
}).disposed(by: disposeBag)
|
||
|
||
view1.addSubview(img1)
|
||
view1.addSubview(nextBtn)
|
||
scrollContentView.addSubview(view1)
|
||
view1.layoutChain.edges(excludingEdge: .right).width(kScreenWidth)
|
||
img1.layoutChain
|
||
.top()
|
||
.edgesHorzontal(39)
|
||
.heightToWidth(471/297)
|
||
|
||
nextBtn.layoutChain
|
||
.height(50)
|
||
.edgesHorzontal(30)
|
||
.bottom(kSafeBottomMargin + 20)
|
||
|
||
let view2 = UIView()
|
||
view2.backgroundColor = .clear
|
||
let img2 = UIImageView(image: UIImage(named: "SOS/2"))
|
||
view2.addSubview(img2)
|
||
scrollContentView.addSubview(view2)
|
||
view2.layoutChain.top().bottom().leftToRightOfView(view1).widthToView(view1)
|
||
img2.layoutChain
|
||
.top()
|
||
.edgesHorzontal(39)
|
||
.heightToWidth(471/297)
|
||
|
||
let view3 = UIView()
|
||
view3.backgroundColor = .clear
|
||
let img3 = UIImageView(image: UIImage(named: "SOS/3"))
|
||
let doneBtn = UIButton(type: .custom)
|
||
doneBtn.setTitle("练习SOS触发", for: .normal)
|
||
doneBtn.setTitleColor(.white, for: .normal)
|
||
doneBtn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||
doneBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||
doneBtn.cornerRadius = 25
|
||
doneBtn.rx.tap.subscribe(onNext: { _ in
|
||
self.scrollView.isHidden = true
|
||
self.pageControl.isHidden = true
|
||
}).disposed(by: disposeBag)
|
||
|
||
view3.addSubview(img3)
|
||
view3.addSubview(doneBtn)
|
||
scrollContentView.addSubview(view3)
|
||
view3.layoutChain.top().bottom().right().leftToRightOfView(view2).widthToView(view1)
|
||
img3.layoutChain
|
||
.top()
|
||
.edgesHorzontal(39)
|
||
.heightToWidth(471/297)
|
||
|
||
doneBtn.layoutChain
|
||
.height(50)
|
||
.edgesHorzontal(30)
|
||
.bottom(kSafeBottomMargin + 20)
|
||
|
||
return view
|
||
}()
|
||
|
||
lazy var scrollContentView: UIView = {
|
||
let view = UIView()
|
||
view.backgroundColor = .clear
|
||
return view
|
||
}()
|
||
|
||
lazy var pageControl: UIPageControl = {
|
||
let pc = UIPageControl()
|
||
pc.numberOfPages = 3
|
||
pc.currentPageIndicatorTintColor = UIColor(hexStr: "#57C7FF")
|
||
pc.pageIndicatorTintColor = UIColor(hexStr: "#D9D9D9", alpha: 1)
|
||
return pc
|
||
}()
|
||
|
||
// 倒计时
|
||
lazy var countDownView: UIView = {
|
||
let view = UIView()
|
||
view.backgroundColor = .white
|
||
view.alpha = 0
|
||
|
||
view.addSubview(countDownTitleView)
|
||
countDownTitleView.layoutChain
|
||
.top(15)
|
||
.centerX()
|
||
|
||
view.addSubview(countDownLottieView)
|
||
countDownLottieView.layoutChain
|
||
.topToBottomOfView(countDownTitleView, offset: 13)
|
||
.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 countDownTitleView: UIView = {
|
||
let view = UIView()
|
||
view.backgroundColor = .clear
|
||
|
||
view.addSubview(countDownTitleLab)
|
||
countDownTitleLab.layoutChain
|
||
.edgesVertical(9)
|
||
.edgesHorzontal(38)
|
||
|
||
return view
|
||
}()
|
||
|
||
lazy var countDownTitleLab: UILabel = {
|
||
let label = UILabel()
|
||
label.text = "练习模式"
|
||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||
label.textColor = .white
|
||
label.textAlignment = .center
|
||
return label
|
||
}()
|
||
|
||
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 finishView: UIView = {
|
||
let view = UIView()
|
||
view.backgroundColor = .white
|
||
view.alpha = 0
|
||
|
||
let imgView = UIImageView(image: UIImage(named: "SOS/finish"))
|
||
imgView.contentMode = .scaleAspectFill
|
||
view.addSubview(imgView)
|
||
imgView.layoutChain
|
||
.top(50)
|
||
.edgesHorzontal(70)
|
||
.heightToWidth(356/235)
|
||
|
||
let doneBtn = UIButton(type: .custom)
|
||
doneBtn.setTitle("收到了", for: .normal)
|
||
doneBtn.setTitleColor(.white, for: .normal)
|
||
doneBtn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||
doneBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||
doneBtn.cornerRadius = 25
|
||
doneBtn.rx.tap.subscribe(onNext: { _ in
|
||
var sosIsPracticeList = Defaults[\.sosIsPracticeList]
|
||
sosIsPracticeList.append(AppContextManager.shared.userId)
|
||
Defaults[\.sosIsPracticeList] = sosIsPracticeList
|
||
UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut], animations: {
|
||
self.alpha = 0
|
||
}, completion: { _ in
|
||
self.isHidden = true
|
||
})
|
||
}).disposed(by: disposeBag)
|
||
view.addSubview(doneBtn)
|
||
doneBtn.layoutChain
|
||
.height(50)
|
||
.edgesHorzontal(30)
|
||
.bottom(kSafeBottomMargin + 20)
|
||
|
||
return view
|
||
}()
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: .zero)
|
||
self.isHidden = true
|
||
backgroundColor = .white
|
||
setupUI()
|
||
setupRx()
|
||
|
||
exclamationLottieView.play()
|
||
}
|
||
|
||
required init?(coder aDecoder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
override func layoutSubviews() {
|
||
super.layoutSubviews()
|
||
self.layoutIfNeeded()
|
||
|
||
titleView.setGradientLayer(frame: titleView.bounds,
|
||
startPoint: CGPoint(x: 0, y: 0.5),
|
||
endPoint: CGPoint(x: 1, y: 0.5),
|
||
colors: [UIColor(hexStr: "#5CBBFF", alpha: 0), UIColor(hexStr: "#5CBBFF"), UIColor(hexStr: "#5CBBFF", alpha: 0)],
|
||
locations: [0, 0.5, 1])
|
||
|
||
countDownView.layoutIfNeeded()
|
||
countDownTitleView.setGradientLayer(frame: countDownTitleView.bounds,
|
||
startPoint: CGPoint(x: 0, y: 0.5),
|
||
endPoint: CGPoint(x: 1, y: 0.5),
|
||
colors: [UIColor(hexStr: "#5CBBFF", alpha: 0), UIColor(hexStr: "#5CBBFF"), UIColor(hexStr: "#5CBBFF", alpha: 0)],
|
||
locations: [0, 0.5, 1])
|
||
}
|
||
}
|
||
|
||
// MARK: - UIScrollViewDelegate (page control)
|
||
extension SOSPracticeView: UIScrollViewDelegate {
|
||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||
guard scrollView == self.scrollView else { return }
|
||
let page = Int(scrollView.contentOffset.x / scrollView.bounds.width)
|
||
pageControl.currentPage = page
|
||
}
|
||
}
|