parent
6163dd1ca8
commit
6eba31963b
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "logo108@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -20,7 +20,7 @@ extension DefaultsKeys {
|
||||||
/// 重新登录(IM需要获取id和签名)
|
/// 重新登录(IM需要获取id和签名)
|
||||||
var isReLogin: DefaultsKey<Bool> { .init("isReLogin", defaultValue: false) }
|
var isReLogin: DefaultsKey<Bool> { .init("isReLogin", defaultValue: false) }
|
||||||
/// 显示引导页
|
/// 显示引导页
|
||||||
var guideShowVersion: DefaultsKey<Int?> { .init("guideShowVersion") }
|
var isShowGuide: DefaultsKey<Bool> { .init("isShowGuide", defaultValue: true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 通知常量
|
/// 通知常量
|
||||||
|
|
@ -35,6 +35,12 @@ extension Notification.Name {
|
||||||
static let ShowMemberLocationNotification = Notification.Name("ShowMemberLocationNotification")
|
static let ShowMemberLocationNotification = Notification.Name("ShowMemberLocationNotification")
|
||||||
/// 支付宝/微信支付结果回调
|
/// 支付宝/微信支付结果回调
|
||||||
static let RequestOrderPayStatusNotification = Notification.Name("RequestOrderPayStatusNotification")
|
static let RequestOrderPayStatusNotification = Notification.Name("RequestOrderPayStatusNotification")
|
||||||
|
/// 暂停弹窗队列(第三方登录/注册跳转时)
|
||||||
|
static let pausePopupQueue = Notification.Name("pausePopupQueue")
|
||||||
|
/// 恢复弹窗队列(第三方登录取消/失败时)
|
||||||
|
static let resumePopupQueue = Notification.Name("resumePopupQueue")
|
||||||
|
/// 终止弹窗队列(登录成功后)
|
||||||
|
static let invalidatePopupQueue = Notification.Name("invalidatePopupQueue")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,7 @@ class BaseViewController: UIViewController {
|
||||||
/// 导航栏标题
|
/// 导航栏标题
|
||||||
var navTitle: String = "" {
|
var navTitle: String = "" {
|
||||||
didSet {
|
didSet {
|
||||||
backButton.setTitle(" \(navTitle)", for: .normal)
|
navigationItem.title = navTitle
|
||||||
// navigationItem.title = navTitle
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,32 +101,13 @@ class BaseViewController: UIViewController {
|
||||||
dismissItem.tintColor = .white
|
dismissItem.tintColor = .white
|
||||||
navigationItem.leftBarButtonItem = dismissItem
|
navigationItem.leftBarButtonItem = dismissItem
|
||||||
} else if let count = navigationController?.viewControllers.count, count > 1 {
|
} else if let count = navigationController?.viewControllers.count, count > 1 {
|
||||||
let leftBarBtn = UIBarButtonItem (customView: backButton)
|
let leftBackItem = UIBarButtonItem(image: UIImage(named: "Common/back"),
|
||||||
|
style: .plain,
|
||||||
//用于消除左边空隙,要不然按钮顶不到最前面
|
target: self,
|
||||||
let spacer = UIBarButtonItem (barButtonSystemItem: .fixedSpace, target: nil, action: nil)
|
action: #selector(leftBackClicked(_:)))
|
||||||
spacer.width = -10
|
navigationItem.leftBarButtonItem = leftBackItem
|
||||||
navigationItem.leftBarButtonItem = leftBarBtn
|
|
||||||
// navigationItem.leftBarButtonItems = [spacer, leftBarBtn]
|
|
||||||
// let leftBackItem = UIBarButtonItem(image: UIImage(named: "Common/back"),
|
|
||||||
// style: .plain,
|
|
||||||
// target: self, action: #selector(leftBackClicked(_:)))
|
|
||||||
// leftBackItem.tintColor = .white
|
|
||||||
// navigationItem.leftBarButtonItem = leftBackItem
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy var backButton: UIButton = {
|
|
||||||
let button = UIButton (type: .custom)
|
|
||||||
button.frame = CGRectMake (0, 0, 200, 30)
|
|
||||||
button.setImage(UIImage(named: "Common/back"), for: .normal)
|
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 18, weight: .medium)
|
|
||||||
button.contentHorizontalAlignment = .left
|
|
||||||
button.setTitleColor(ThemeManager.shared.color.titleColor, for: .normal)
|
|
||||||
button.addTarget(self, action: #selector(leftBackClicked(_:)), for: .touchUpInside)
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 验证码
|
// MARK: - 验证码
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ final class MainTabBarController: UITabBarController {
|
||||||
|
|
||||||
viewControllers = navControllers
|
viewControllers = navControllers
|
||||||
tabBar.isHidden = true
|
tabBar.isHidden = true
|
||||||
|
tabBar.removeFromSuperview()
|
||||||
|
|
||||||
for nav in navControllers {
|
for nav in navControllers {
|
||||||
nav.delegate = self
|
nav.delegate = self
|
||||||
|
|
@ -71,7 +72,7 @@ final class MainTabBarController: UITabBarController {
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
customTabBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
customTabBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
customTabBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
customTabBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
customTabBar.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -41),
|
customTabBar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
customTabBar.heightAnchor.constraint(equalToConstant: tabBarHeight)
|
customTabBar.heightAnchor.constraint(equalToConstant: tabBarHeight)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,8 @@ struct SystemConfigModel: Mappable {
|
||||||
var popupConfig: PopupWindowConfigModel?
|
var popupConfig: PopupWindowConfigModel?
|
||||||
/// 弹窗时间配置
|
/// 弹窗时间配置
|
||||||
var timeConfig: PopupWindowTimeModel?
|
var timeConfig: PopupWindowTimeModel?
|
||||||
|
/// 微信分享配置
|
||||||
|
var shareConfig: WxShareConfigModel?
|
||||||
|
|
||||||
/// 聊天界面的警告说明
|
/// 聊天界面的警告说明
|
||||||
var chatWarning: String = ""
|
var chatWarning: String = ""
|
||||||
|
|
@ -84,6 +86,7 @@ struct SystemConfigModel: Mappable {
|
||||||
groupBannerList <- map["client.team.ad", nested: false]
|
groupBannerList <- map["client.team.ad", nested: false]
|
||||||
popupConfig <- map["client.popup.display", nested: false]
|
popupConfig <- map["client.popup.display", nested: false]
|
||||||
timeConfig <- map["client.time.setting", nested: false]
|
timeConfig <- map["client.time.setting", nested: false]
|
||||||
|
shareConfig <- map["client.weixin.share", nested: false]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,3 +135,28 @@ struct PopupWindowTimeModel: Mappable {
|
||||||
pay_pop_type <- map["pay_pop_type"]
|
pay_pop_type <- map["pay_pop_type"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct WxShareConfigModel: Mappable {
|
||||||
|
/// 链接
|
||||||
|
var link: String = ""
|
||||||
|
|
||||||
|
/// 标题
|
||||||
|
var title: String = ""
|
||||||
|
|
||||||
|
/// 内容
|
||||||
|
var content: String = ""
|
||||||
|
|
||||||
|
/// 图标
|
||||||
|
var image: String = ""
|
||||||
|
|
||||||
|
init?(map: Map) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
link <- map["link"]
|
||||||
|
title <- map["title"]
|
||||||
|
content <- map["content"]
|
||||||
|
image <- map["image"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,7 @@ extension ApiManager {
|
||||||
// MARK: - 处理Token过期
|
// MARK: - 处理Token过期
|
||||||
private func handleTokenExpired(msg: String?) {
|
private func handleTokenExpired(msg: String?) {
|
||||||
MainAsync {
|
MainAsync {
|
||||||
|
NotificationCenter.default.post(name: .invalidatePopupQueue, object: nil)
|
||||||
AppContextManager.shared.deleteAccount()
|
AppContextManager.shared.deleteAccount()
|
||||||
GroupIMService.shared.logout()
|
GroupIMService.shared.logout()
|
||||||
DLToast.showError(text: msg ?? "登录失效,请重新登录!") {
|
DLToast.showError(text: msg ?? "登录失效,请重新登录!") {
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,7 @@ class InviteJoinVC: BaseViewController {
|
||||||
|
|
||||||
// 分享
|
// 分享
|
||||||
rootView.shareAppBtn.rx.tap.subscribe(onNext: { _ in
|
rootView.shareAppBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
let url = "https://smartdrive.zuom8.cn/jisu/share/user/"
|
SharePopView.show()
|
||||||
SharePopView.show(shareURL: url)
|
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
guard let model = groupInModel else { return }
|
guard let model = groupInModel else { return }
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@ class HomeView: UIView {
|
||||||
quickMessageView.isHidden = false
|
quickMessageView.isHidden = false
|
||||||
interactionView.isHidden = false
|
interactionView.isHidden = false
|
||||||
toolsView.isHidden = true
|
toolsView.isHidden = true
|
||||||
|
searchLottieView.isHidden = true
|
||||||
quickMessageView.frame.origin.y = kScreenHeight - 384 - 93
|
quickMessageView.frame.origin.y = kScreenHeight - 384 - 93
|
||||||
interactionView.frame.origin.y = kScreenHeight - 384
|
interactionView.frame.origin.y = kScreenHeight - 384
|
||||||
}
|
}
|
||||||
|
|
@ -246,6 +247,7 @@ class HomeView: UIView {
|
||||||
quickMessageView.isHidden = true
|
quickMessageView.isHidden = true
|
||||||
interactionView.isHidden = true
|
interactionView.isHidden = true
|
||||||
toolsView.isHidden = false
|
toolsView.isHidden = false
|
||||||
|
searchLottieView.isHidden = false
|
||||||
onDismissPanel?()
|
onDismissPanel?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class HomeViewController: BaseViewController {
|
||||||
private var subscribedMemberIds: [String] = []
|
private var subscribedMemberIds: [String] = []
|
||||||
/// 底部成员面板是否显示
|
/// 底部成员面板是否显示
|
||||||
private var isMemberPanelShown = false
|
private var isMemberPanelShown = false
|
||||||
|
private var isSOSPlaying = false
|
||||||
/// 记录每个成员最后一次收到位置的时间(MQTT userId → Date)
|
/// 记录每个成员最后一次收到位置的时间(MQTT userId → Date)
|
||||||
private var lastUpdateTimes: [String: Date] = [:]
|
private var lastUpdateTimes: [String: Date] = [:]
|
||||||
/// 离线检测定时器
|
/// 离线检测定时器
|
||||||
|
|
@ -73,8 +74,6 @@ class HomeViewController: BaseViewController {
|
||||||
setupHeading()
|
setupHeading()
|
||||||
reactiveAction()
|
reactiveAction()
|
||||||
|
|
||||||
requestUserConfig()
|
|
||||||
|
|
||||||
rootView.quickMessageView.tagListView.delegate = self
|
rootView.quickMessageView.tagListView.delegate = self
|
||||||
// MQTT 位置上报
|
// MQTT 位置上报
|
||||||
UIDevice.current.isBatteryMonitoringEnabled = true
|
UIDevice.current.isBatteryMonitoringEnabled = true
|
||||||
|
|
@ -189,9 +188,26 @@ class HomeViewController: BaseViewController {
|
||||||
|
|
||||||
// User Config刷新
|
// User Config刷新
|
||||||
NotificationCenter.default.rx.notification(.RefreshUserConfigNotification, object: nil)
|
NotificationCenter.default.rx.notification(.RefreshUserConfigNotification, object: nil)
|
||||||
.subscribe { [weak self] notification in
|
.startWith(Notification(name: .RefreshUserConfigNotification))
|
||||||
self?.requestUserConfig()
|
.do(onNext: { [weak self] _ in
|
||||||
}.disposed(by: disposeBag)
|
self?.popupQueue = nil
|
||||||
|
})
|
||||||
|
.flatMapLatest { _ in
|
||||||
|
SystemService.userConfig()
|
||||||
|
}
|
||||||
|
.subscribe(onNext: { [weak self] response in
|
||||||
|
guard let self = self, let model = response.model else { return }
|
||||||
|
Defaults[\.loginToken] = model.token
|
||||||
|
AppContextManager.shared.systemConfig = model.config
|
||||||
|
self.getUserIMToken()
|
||||||
|
self.requestUserInfo { [weak self] in
|
||||||
|
self?.requestGroupInfo()
|
||||||
|
}
|
||||||
|
self.requestNotice()
|
||||||
|
self.popupQueue = nil
|
||||||
|
self.popupQueue = PopupQueueManager()
|
||||||
|
self.popupQueue?.start(from: self)
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
// 圈子刷新
|
// 圈子刷新
|
||||||
NotificationCenter.default.rx.notification(.RefreshGroupInfoNotification, object: nil)
|
NotificationCenter.default.rx.notification(.RefreshGroupInfoNotification, object: nil)
|
||||||
|
|
@ -248,10 +264,8 @@ class HomeViewController: BaseViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分享
|
// 分享
|
||||||
rootView.interactionView.onShare = { [weak self] in
|
rootView.interactionView.onShare = {
|
||||||
guard let self = self, let member = self.rootView.interactionView.currentMember else { return }
|
SharePopView.show()
|
||||||
let url = "https://smartdrive.zuom8.cn/jisu/share/user/\(member.id)"
|
|
||||||
SharePopView.show(shareURL: url)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,25 +304,6 @@ class HomeViewController: BaseViewController {
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
|
|
||||||
/// 获取用户配置
|
|
||||||
func requestUserConfig() {
|
|
||||||
SystemService.userConfig().subscribe(onNext: { response in
|
|
||||||
guard let model = response.model else { return }
|
|
||||||
Defaults[\.loginToken] = model.token
|
|
||||||
AppContextManager.shared.systemConfig = model.config
|
|
||||||
self.getUserIMToken()
|
|
||||||
// 先更新用户信息(含头像),再拉群列表同步地图标注,避免头像旧
|
|
||||||
self.requestUserInfo { [weak self] in
|
|
||||||
self?.requestGroupInfo()
|
|
||||||
}
|
|
||||||
// 首页公告
|
|
||||||
self.requestNotice()
|
|
||||||
// 弹窗控制器
|
|
||||||
self.popupQueue = PopupQueueManager()
|
|
||||||
self.popupQueue?.start(from: self)
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取用户IM Token
|
/// 获取用户IM Token
|
||||||
func getUserIMToken() {
|
func getUserIMToken() {
|
||||||
DLToast.showLoading()
|
DLToast.showLoading()
|
||||||
|
|
@ -466,8 +461,8 @@ class HomeViewController: BaseViewController {
|
||||||
let newMembers = others + [me]
|
let newMembers = others + [me]
|
||||||
members = newMembers
|
members = newMembers
|
||||||
currentUserMember = me
|
currentUserMember = me
|
||||||
// 从 members 中过滤出在线成员用于地图标注
|
// 从 members 中过滤出在线成员用于地图标注(当前用户坐标无效,由 GPS 回调单独添加)
|
||||||
let onlineMembers = newMembers.filter { $0.isOnline }
|
let onlineMembers = newMembers.filter { $0.isOnline && !$0.isCurrentUser }
|
||||||
|
|
||||||
guard let mapView = rootView.mapView else { return }
|
guard let mapView = rootView.mapView else { return }
|
||||||
|
|
||||||
|
|
@ -477,10 +472,33 @@ class HomeViewController: BaseViewController {
|
||||||
|
|
||||||
let toRemove = existing.filter { !onlineIDs.contains($0.member.id) }
|
let toRemove = existing.filter { !onlineIDs.contains($0.member.id) }
|
||||||
mapView.removeAnnotations(toRemove)
|
mapView.removeAnnotations(toRemove)
|
||||||
|
if toRemove.contains(where: { $0 === currentUserAnnotation }) {
|
||||||
|
currentUserAnnotation = nil
|
||||||
|
}
|
||||||
|
|
||||||
let toAdd = onlineMembers.filter { !existingIDs.contains($0.id) }
|
let toAdd = onlineMembers.filter { !existingIDs.contains($0.id) }
|
||||||
let annotations = toAdd.map { MemberAnnotation(member: $0) }
|
let annotations = toAdd.map { MemberAnnotation(member: $0) }
|
||||||
mapView.addAnnotations(annotations)
|
mapView.addAnnotations(annotations)
|
||||||
|
|
||||||
|
if currentUserAnnotation == nil,
|
||||||
|
let me = currentUserMember,
|
||||||
|
let loc = lastLocation,
|
||||||
|
CLLocationCoordinate2DIsValid(loc.coordinate) {
|
||||||
|
let updatedMe = CircleMember(
|
||||||
|
id: "current",
|
||||||
|
name: me.name,
|
||||||
|
avatar: me.avatar,
|
||||||
|
isOnline: true,
|
||||||
|
isOwner: false,
|
||||||
|
coordinate: loc.coordinate,
|
||||||
|
address: me.address,
|
||||||
|
heading: me.heading,
|
||||||
|
lastUpdateTime: me.lastUpdateTime,
|
||||||
|
battery: me.battery
|
||||||
|
)
|
||||||
|
currentUserMember = updatedMe
|
||||||
|
mapView.addAnnotation(MemberAnnotation(member: updatedMe))
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -639,15 +657,21 @@ extension HomeViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "sos": // 求助
|
case "sos": // 求助
|
||||||
self.requestGroupInfo(isDefaultGroup: false)
|
|
||||||
guard let userId = msg.data?.user_id,
|
guard let userId = msg.data?.user_id,
|
||||||
userId != AppContextManager.shared.userId else { return }
|
userId != AppContextManager.shared.userId else { return }
|
||||||
playSOSAlarm()
|
DispatchQueue.main.async { [weak self] in
|
||||||
rootView.sosPopView.isHidden = false
|
guard let self = self, !self.isSOSPlaying else { return }
|
||||||
rootView.sosPopView.play(toFrame: 30.0) { completed in
|
self.isSOSPlaying = true
|
||||||
guard completed else { return }
|
self.playSOSAlarm()
|
||||||
self.rootView.sosPopView.stop()
|
self.rootView.sosPopView.isHidden = false
|
||||||
self.rootView.sosPopView.isHidden = true
|
self.rootView.sosPopView.play { completed in
|
||||||
|
self.rootView.sosPopView.stop()
|
||||||
|
self.rootView.sosPopView.isHidden = true
|
||||||
|
}
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in
|
||||||
|
self?.rootView.sosPopView.stop()
|
||||||
|
self?.rootView.sosPopView.isHidden = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "join": // 圈子有成员加入
|
case "join": // 圈子有成员加入
|
||||||
guard let gk = msg.data?.group_key,
|
guard let gk = msg.data?.group_key,
|
||||||
|
|
@ -798,6 +822,22 @@ extension HomeViewController: MAMapViewDelegate {
|
||||||
|
|
||||||
if let ann = currentUserAnnotation {
|
if let ann = currentUserAnnotation {
|
||||||
ann.coordinate = coordinate
|
ann.coordinate = coordinate
|
||||||
|
} else if let me = currentUserMember {
|
||||||
|
let updatedMe = CircleMember(
|
||||||
|
id: "current",
|
||||||
|
name: me.name,
|
||||||
|
avatar: me.avatar,
|
||||||
|
isOnline: true,
|
||||||
|
isOwner: false,
|
||||||
|
coordinate: coordinate,
|
||||||
|
address: me.address,
|
||||||
|
heading: me.heading,
|
||||||
|
lastUpdateTime: me.lastUpdateTime,
|
||||||
|
battery: me.battery
|
||||||
|
)
|
||||||
|
currentUserMember = updatedMe
|
||||||
|
let annotation = MemberAnnotation(member: updatedMe)
|
||||||
|
mapView.addAnnotation(annotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isMemberPanelShown {
|
if !isMemberPanelShown {
|
||||||
|
|
@ -835,13 +875,14 @@ extension HomeViewController {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
print("✅ SOS: playing alarm")
|
print("✅ SOS: playing alarm")
|
||||||
player.numberOfLoops = 0
|
player.numberOfLoops = -1
|
||||||
player.volume = 1.0
|
player.volume = 1.0
|
||||||
player.play()
|
player.play()
|
||||||
self.sosPlayer = player
|
self.sosPlayer = player
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in
|
||||||
self?.sosPlayer?.stop()
|
self?.sosPlayer?.stop()
|
||||||
self?.sosPlayer = nil
|
self?.sosPlayer = nil
|
||||||
|
self?.isSOSPlaying = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,10 @@ class InteractionView: UIView {
|
||||||
let pages = (items.count + InteractionView.emojiPerPage - 1) / InteractionView.emojiPerPage
|
let pages = (items.count + InteractionView.emojiPerPage - 1) / InteractionView.emojiPerPage
|
||||||
self.emojiPageControl.numberOfPages = max(pages, 1)
|
self.emojiPageControl.numberOfPages = max(pages, 1)
|
||||||
self.emojiPageControl.currentPage = 0
|
self.emojiPageControl.currentPage = 0
|
||||||
self.emojiCollectionView.setContentOffset(.zero, animated: false)
|
self.emojiCollectionView.layoutIfNeeded()
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.emojiCollectionView.setContentOffset(.zero, animated: false)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
@ -517,6 +520,11 @@ extension InteractionView: UICollectionViewDelegate {
|
||||||
emojiPageControl.currentPage = page
|
emojiPageControl.currentPage = page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||||
|
guard collectionView == emojiCollectionView else { return }
|
||||||
|
(cell as? EmojiPanelCell)?.playAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||||
guard collectionView == emojiCollectionView else { return }
|
guard collectionView == emojiCollectionView else { return }
|
||||||
(cell as? EmojiPanelCell)?.stopAnimation()
|
(cell as? EmojiPanelCell)?.stopAnimation()
|
||||||
|
|
@ -528,29 +536,46 @@ final class EmojiPanelCell: UICollectionViewCell {
|
||||||
|
|
||||||
private static var animationCache: [String: LottieAnimation] = [:]
|
private static var animationCache: [String: LottieAnimation] = [:]
|
||||||
private static var loadingCompletions: [String: [(LottieAnimation?) -> Void]] = [:]
|
private static var loadingCompletions: [String: [(LottieAnimation?) -> Void]] = [:]
|
||||||
private static let animationQueue = DispatchQueue(label: "com.quicklocation.emoji.animation", qos: .userInitiated)
|
private static let cacheLock = NSLock()
|
||||||
|
private static let animationQueue = DispatchQueue(label: "com.quicklocation.emoji.animation", qos: .userInitiated, attributes: .concurrent)
|
||||||
|
|
||||||
|
private static let emojiPathMap: [String: String] = {
|
||||||
|
Bundle.main.paths(forResourcesOfType: "json", inDirectory: nil).reduce(into: [:]) { map, path in
|
||||||
|
let name = (path as NSString).lastPathComponent.replacingOccurrences(of: ".json", with: "")
|
||||||
|
map[name] = path
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
static func loadAnimation(for name: String, completion: @escaping (LottieAnimation?) -> Void) {
|
static func loadAnimation(for name: String, completion: @escaping (LottieAnimation?) -> Void) {
|
||||||
animationQueue.async {
|
animationQueue.async {
|
||||||
if let cached = animationCache[name] {
|
Self.cacheLock.lock()
|
||||||
|
if let cached = Self.animationCache[name] {
|
||||||
|
Self.cacheLock.unlock()
|
||||||
DispatchQueue.main.async { completion(cached) }
|
DispatchQueue.main.async { completion(cached) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if loadingCompletions[name] != nil {
|
if Self.loadingCompletions[name] != nil {
|
||||||
loadingCompletions[name]?.append(completion)
|
Self.loadingCompletions[name]?.append(completion)
|
||||||
|
Self.cacheLock.unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingCompletions[name] = [completion]
|
Self.loadingCompletions[name] = [completion]
|
||||||
|
Self.cacheLock.unlock()
|
||||||
|
|
||||||
let animation: LottieAnimation?
|
let animation: LottieAnimation?
|
||||||
if let path = Bundle.main.path(forResource: name, ofType: "json") {
|
if let path = Self.emojiPathMap[name] {
|
||||||
animation = LottieAnimation.filepath(path)
|
animation = LottieAnimation.filepath(path)
|
||||||
} else {
|
} else {
|
||||||
animation = nil
|
animation = nil
|
||||||
}
|
}
|
||||||
animationCache[name] = animation
|
|
||||||
let completions = loadingCompletions.removeValue(forKey: name) ?? []
|
Self.cacheLock.lock()
|
||||||
|
Self.animationCache[name] = animation
|
||||||
|
let completions = Self.loadingCompletions.removeValue(forKey: name) ?? []
|
||||||
|
Self.cacheLock.unlock()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
completions.forEach { $0(animation) }
|
completions.forEach { $0(animation) }
|
||||||
}
|
}
|
||||||
|
|
@ -558,7 +583,6 @@ final class EmojiPanelCell: UICollectionViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var animationName: String?
|
private var animationName: String?
|
||||||
private var shouldPlayAnimation = false
|
|
||||||
|
|
||||||
private let lottieView: LottieAnimationView = {
|
private let lottieView: LottieAnimationView = {
|
||||||
let v = LottieAnimationView()
|
let v = LottieAnimationView()
|
||||||
|
|
@ -598,36 +622,28 @@ final class EmojiPanelCell: UICollectionViewCell {
|
||||||
|
|
||||||
func configure(name: String) {
|
func configure(name: String) {
|
||||||
animationName = name
|
animationName = name
|
||||||
shouldPlayAnimation = false
|
|
||||||
lottieView.stop()
|
lottieView.stop()
|
||||||
lottieView.animation = nil
|
|
||||||
lottieView.currentProgress = 0
|
|
||||||
Self.loadAnimation(for: name) { [weak self] animation in
|
Self.loadAnimation(for: name) { [weak self] animation in
|
||||||
guard let self = self, self.animationName == name else { return }
|
guard let self = self, self.animationName == name else { return }
|
||||||
self.lottieView.animation = animation
|
self.lottieView.animation = animation
|
||||||
self.lottieView.currentProgress = 0
|
self.lottieView.currentProgress = 0
|
||||||
if self.shouldPlayAnimation {
|
self.lottieView.play()
|
||||||
self.lottieView.play()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lockView.isHidden = AppContextManager.shared.vip > 1
|
lockView.isHidden = AppContextManager.shared.vip > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func playAnimation() {
|
func playAnimation() {
|
||||||
shouldPlayAnimation = true
|
|
||||||
lottieView.play()
|
lottieView.play()
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopAnimation() {
|
func stopAnimation() {
|
||||||
shouldPlayAnimation = false
|
|
||||||
lottieView.stop()
|
lottieView.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
animationName = nil
|
animationName = nil
|
||||||
shouldPlayAnimation = false
|
|
||||||
lottieView.stop()
|
lottieView.stop()
|
||||||
lottieView.animation = nil
|
lottieView.animation = nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,7 @@ class LaunchViewController: BaseViewController {
|
||||||
AppDelegate.shared.showMainViewController()
|
AppDelegate.shared.showMainViewController()
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
||||||
getUserConfig()
|
getUserConfig()
|
||||||
|
|
||||||
navigateAfterDelay()
|
navigateAfterDelay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,24 +42,16 @@ class LaunchViewController: BaseViewController {
|
||||||
Observable.just(())
|
Observable.just(())
|
||||||
.delay(.milliseconds(2500), scheduler: MainScheduler.instance)
|
.delay(.milliseconds(2500), scheduler: MainScheduler.instance)
|
||||||
.subscribe(onNext: { [weak self] in
|
.subscribe(onNext: { [weak self] in
|
||||||
self?.navigateToNext()
|
if Defaults[\.isShowGuide] {
|
||||||
|
self?.collectionView.isHidden = false
|
||||||
|
self?.pageControl.isHidden = false
|
||||||
|
self?.experienceBtn.isHidden = false
|
||||||
|
} else {
|
||||||
|
AppDelegate.shared.showMainViewController()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func navigateToNext() {
|
|
||||||
let hasShownGuide = UserDefaults.standard.bool(forKey: "hasShownGuide")
|
|
||||||
if hasShownGuide {
|
|
||||||
let loginVC = LoginViewController()
|
|
||||||
UIView.transition(with: view.window!, duration: 0.4, options: .transitionCrossDissolve) {
|
|
||||||
self.view.window?.rootViewController = UINavigationController(rootViewController: loginVC)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
collectionView.isHidden = false
|
|
||||||
pageControl.isHidden = false
|
|
||||||
experienceBtn.isHidden = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取用户配置
|
/// 获取用户配置
|
||||||
func getUserConfig() {
|
func getUserConfig() {
|
||||||
|
|
@ -82,7 +72,6 @@ class LaunchViewController: BaseViewController {
|
||||||
|
|
||||||
/// 获取用户IM Token
|
/// 获取用户IM Token
|
||||||
func getUserIMToken() {
|
func getUserIMToken() {
|
||||||
DLToast.showLoading()
|
|
||||||
UserService.imToken().subscribe(onNext: { response in
|
UserService.imToken().subscribe(onNext: { response in
|
||||||
guard let data = response.data, let token = data["token"] as? String else { return }
|
guard let data = response.data, let token = data["token"] as? String else { return }
|
||||||
AppContextManager.shared.imToken = token
|
AppContextManager.shared.imToken = token
|
||||||
|
|
|
||||||
|
|
@ -68,17 +68,18 @@ class LoginView: UIView {
|
||||||
addSubview(bgMaskImage)
|
addSubview(bgMaskImage)
|
||||||
addSubview(backBtn)
|
addSubview(backBtn)
|
||||||
addSubview(guestLoginButton)
|
addSubview(guestLoginButton)
|
||||||
addSubview(welcomeLabel)
|
addSubview(inputContainerView)
|
||||||
addSubview(phoneNumberLabel)
|
inputContainerView.addSubview(welcomeLabel)
|
||||||
addSubview(carrierLabel)
|
inputContainerView.addSubview(phoneNumberLabel)
|
||||||
addSubview(otherPhoneView)
|
inputContainerView.addSubview(carrierLabel)
|
||||||
|
inputContainerView.addSubview(otherPhoneView)
|
||||||
otherPhoneView.addSubview(phoneInputView)
|
otherPhoneView.addSubview(phoneInputView)
|
||||||
otherPhoneView.addSubview(smsCodeInputView)
|
otherPhoneView.addSubview(smsCodeInputView)
|
||||||
addSubview(loginButton)
|
inputContainerView.addSubview(loginButton)
|
||||||
addSubview(appleLoginBtn)
|
addSubview(appleLoginBtn)
|
||||||
addSubview(wechatLoginBtn)
|
addSubview(wechatLoginBtn)
|
||||||
addSubview(phoneLoginBtn)
|
addSubview(phoneLoginBtn)
|
||||||
|
|
||||||
addSubview(agreementView)
|
addSubview(agreementView)
|
||||||
agreementView.addSubview(checkBox)
|
agreementView.addSubview(checkBox)
|
||||||
agreementView.addSubview(agreementTV)
|
agreementView.addSubview(agreementTV)
|
||||||
|
|
@ -98,8 +99,14 @@ class LoginView: UIView {
|
||||||
.width(85)
|
.width(85)
|
||||||
.height(29)
|
.height(29)
|
||||||
|
|
||||||
|
inputContainerView.layoutChain
|
||||||
|
.centerX()
|
||||||
|
.centerY(self, offset: -17)
|
||||||
|
.edgesHorzontal()
|
||||||
|
.height(306)
|
||||||
|
|
||||||
welcomeLabel.layoutChain
|
welcomeLabel.layoutChain
|
||||||
.top(202 - kNaviHeight)
|
.top(0)
|
||||||
.centerX()
|
.centerX()
|
||||||
.edgesHorzontal()
|
.edgesHorzontal()
|
||||||
|
|
||||||
|
|
@ -161,7 +168,7 @@ class LoginView: UIView {
|
||||||
agreementView.layoutChain
|
agreementView.layoutChain
|
||||||
.leftToView(loginButton)
|
.leftToView(loginButton)
|
||||||
.rightToView(loginButton)
|
.rightToView(loginButton)
|
||||||
.bottom(55)
|
.bottom(kSafeBottomMargin + 20)
|
||||||
.height(30)
|
.height(30)
|
||||||
|
|
||||||
agreementLabel.layoutChain.edges()
|
agreementLabel.layoutChain.edges()
|
||||||
|
|
@ -178,7 +185,7 @@ class LoginView: UIView {
|
||||||
.top()
|
.top()
|
||||||
|
|
||||||
phoneLoginBtn.layoutChain
|
phoneLoginBtn.layoutChain
|
||||||
.bottomToTopOfView(agreementView, offset: -81)
|
.bottomToTopOfView(agreementView, offset: -30)
|
||||||
.centerX()
|
.centerX()
|
||||||
.width(38)
|
.width(38)
|
||||||
.heightToWidth(1)
|
.heightToWidth(1)
|
||||||
|
|
@ -258,6 +265,13 @@ class LoginView: UIView {
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// 输入区域容器
|
||||||
|
lazy var inputContainerView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
// 其他手机号登录
|
// 其他手机号登录
|
||||||
lazy var otherPhoneView: UIView = {
|
lazy var otherPhoneView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
|
|
@ -394,7 +408,6 @@ class LoginView: UIView {
|
||||||
textView.backgroundColor = .clear
|
textView.backgroundColor = .clear
|
||||||
textView.isEditable = false
|
textView.isEditable = false
|
||||||
textView.isScrollEnabled = false
|
textView.isScrollEnabled = false
|
||||||
textView.isSelectable = false
|
|
||||||
textView.linkTextAttributes = [:]
|
textView.linkTextAttributes = [:]
|
||||||
|
|
||||||
return textView
|
return textView
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import UIKit
|
||||||
import SnapKit
|
import SnapKit
|
||||||
import RxSwift
|
import RxSwift
|
||||||
import RxCocoa
|
import RxCocoa
|
||||||
|
import AuthenticationServices
|
||||||
#if !targetEnvironment(simulator)
|
#if !targetEnvironment(simulator)
|
||||||
import GeYanSdk
|
import GeYanSdk
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -39,14 +40,14 @@ class LoginViewController: BaseViewController {
|
||||||
bindViewModel()
|
bindViewModel()
|
||||||
reactiveAction()
|
reactiveAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reactiveAction() {
|
private func reactiveAction() {
|
||||||
rootView.smsCodeBtn.rx.controlEvent(.touchUpInside).subscribe { [weak self] _ in
|
rootView.smsCodeBtn.rx.controlEvent(.touchUpInside).subscribe { [weak self] _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.requestSmsCodeAPI()
|
self.requestSmsCodeAPI()
|
||||||
}.disposed(by: rootView.disposeBag)
|
}.disposed(by: rootView.disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Bindings
|
// MARK: - Bindings
|
||||||
private func bindViewModel() {
|
private func bindViewModel() {
|
||||||
rootView.checkBox.rx.tap
|
rootView.checkBox.rx.tap
|
||||||
|
|
@ -100,17 +101,40 @@ class LoginViewController: BaseViewController {
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
viewModel.onAppleLoginRequest = { [weak self] controller in
|
||||||
|
guard let self = self else { return }
|
||||||
|
controller.delegate = self
|
||||||
|
controller.presentationContextProvider = self
|
||||||
|
controller.performRequests()
|
||||||
|
}
|
||||||
|
|
||||||
rootView.phoneLoginBtn.rx.tap
|
rootView.phoneLoginBtn.rx.tap
|
||||||
.subscribe(onNext: { [weak self] in
|
.subscribe(onNext: { [weak self] in
|
||||||
|
#if !targetEnvironment(simulator)
|
||||||
|
if GeYanSdk.getCurrentCarrierCount() == 0 {
|
||||||
|
DLToast.show(text: "请插入SIM卡后重试")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
GeYanSdk.preGetToken { preDic in
|
||||||
|
guard let dict = preDic as? [String: Any],
|
||||||
|
dict["code"] as? Int == 30000,
|
||||||
|
let model = GyContentModel.current(),
|
||||||
|
!model.pn.isEmpty else {
|
||||||
|
DLToast.showError(text: "获取手机号失败,请使用验证码登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let oneTapView = OneTapLoginView(frame: UIScreen.main.bounds)
|
||||||
|
self?.viewModel.performOneTapLogin(from: self!, contentView: oneTapView)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
viewModel.loginSuccess
|
// viewModel.loginSuccess
|
||||||
.subscribe(onNext: { [weak self] in
|
// .subscribe(onNext: { [weak self] in
|
||||||
|
//
|
||||||
})
|
// })
|
||||||
.disposed(by: disposeBag)
|
// .disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - API 验证码
|
// MARK: - API 验证码
|
||||||
|
|
@ -150,3 +174,40 @@ extension LoginViewController: UITextViewDelegate {
|
||||||
|
|
||||||
private func openURL(_ url: String) {}
|
private func openURL(_ url: String) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Apple Sign In
|
||||||
|
extension LoginViewController: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
|
||||||
|
|
||||||
|
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
|
||||||
|
view.window ?? UIWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizationController(controller: ASAuthorizationController,
|
||||||
|
didCompleteWithAuthorization authorization: ASAuthorization) {
|
||||||
|
guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { return }
|
||||||
|
|
||||||
|
let userId = credential.user
|
||||||
|
let userName: String = {
|
||||||
|
if let name = credential.fullName {
|
||||||
|
let parts = [name.givenName, name.familyName].compactMap { $0 }
|
||||||
|
if !parts.isEmpty { return parts.joined(separator: " ") }
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}()
|
||||||
|
let identityToken = credential.identityToken.flatMap { String(data: $0, encoding: .utf8) } ?? ""
|
||||||
|
let authCode = credential.authorizationCode.flatMap { String(data: $0, encoding: .utf8) } ?? ""
|
||||||
|
|
||||||
|
viewModel.loginAction(type: "apple", data: [
|
||||||
|
"user_id": userId,
|
||||||
|
"user_name": userName,
|
||||||
|
"identify_code": authCode,
|
||||||
|
"identify_token": identityToken
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizationController(controller: ASAuthorizationController,
|
||||||
|
didCompleteWithError error: Error) {
|
||||||
|
NotificationCenter.default.post(name: .resumePopupQueue, object: nil)
|
||||||
|
DLToast.showError(text: "Apple 登录失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,43 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import RxSwift
|
import RxSwift
|
||||||
import SwiftyUserDefaults
|
import SwiftyUserDefaults
|
||||||
|
import AuthenticationServices
|
||||||
#if !targetEnvironment(simulator)
|
#if !targetEnvironment(simulator)
|
||||||
import GeYanSdk
|
import GeYanSdk
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
enum LoginSessionHandler {
|
||||||
|
private static let disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
static func login(type: String, data: [String: Any]) {
|
||||||
|
DLToast.showLoading()
|
||||||
|
UserService.login(type: type, data: data)
|
||||||
|
.delaySubscription(.seconds(1), scheduler: MainScheduler.instance)
|
||||||
|
.retry(1)
|
||||||
|
.subscribe(onNext: { response in
|
||||||
|
GroupIMService.shared.logout()
|
||||||
|
DLToast.dismiss()
|
||||||
|
guard let model = response.model else { return }
|
||||||
|
NotificationCenter.default.post(name: .invalidatePopupQueue, object: nil)
|
||||||
|
Defaults[\.loginToken] = model.token
|
||||||
|
DLToast.showSuccess(text: "登录成功") {
|
||||||
|
if let userId = model.uid {
|
||||||
|
MQTTService.shared.updateClientID("smartdrive_\(userId)")
|
||||||
|
}
|
||||||
|
NotificationCenter.default.post(name: .RefreshUserConfigNotification, object: nil)
|
||||||
|
if let nav = AppRouter.shared.navigationController, nav.viewControllers.count > 1 {
|
||||||
|
AppRouter.shared.popOrDismiss()
|
||||||
|
} else {
|
||||||
|
AppDelegate.shared.showMainViewController()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, onError: { error in
|
||||||
|
DLToast.dismiss()
|
||||||
|
DLToast.showError(text: "登录失败,请重试")
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class LoginViewModel: BaseViewModel {
|
final class LoginViewModel: BaseViewModel {
|
||||||
|
|
||||||
/// 验证码接口返回值
|
/// 验证码接口返回值
|
||||||
|
|
@ -18,47 +51,78 @@ final class LoginViewModel: BaseViewModel {
|
||||||
let oneClickLoginResult = PublishSubject<Bool>()
|
let oneClickLoginResult = PublishSubject<Bool>()
|
||||||
let loginSuccess = PublishSubject<Void>()
|
let loginSuccess = PublishSubject<Void>()
|
||||||
|
|
||||||
var gyAuthVM: GyAuthViewModel {
|
var onAppleLoginRequest: ((ASAuthorizationController) -> Void)?
|
||||||
|
|
||||||
|
#if !targetEnvironment(simulator)
|
||||||
|
lazy var gyAuthVM: GyAuthViewModel = {
|
||||||
let viewModel = GyAuthViewModel()
|
let viewModel = GyAuthViewModel()
|
||||||
viewModel.statusBarStyle = .default
|
viewModel.statusBarStyle = .default
|
||||||
viewModel.userInterfaceStyle = 0
|
viewModel.userInterfaceStyle = 0
|
||||||
viewModel.pullAuthVCStyle = .push
|
viewModel.pullAuthVCStyle = .modal
|
||||||
viewModel.viewLifeCycleBlock = { (str, bool) in
|
|
||||||
|
|
||||||
}
|
|
||||||
viewModel.clickAuthButtonBlock = {
|
viewModel.clickAuthButtonBlock = {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return viewModel
|
return viewModel
|
||||||
}
|
}()
|
||||||
|
#endif
|
||||||
|
|
||||||
/// 登录
|
/// 登录
|
||||||
func loginAction(type: String, data: [String : Any]) {
|
func loginAction(type: String, data: [String : Any]) {
|
||||||
DLToast.showLoading()
|
LoginSessionHandler.login(type: type, data: data)
|
||||||
UserService.login(type: type, data: data).subscribe { response in
|
|
||||||
GroupIMService.shared.logout()
|
|
||||||
DLToast.dismiss()
|
|
||||||
guard let model = response.model else { return }
|
|
||||||
Defaults[\.loginToken] = model.token
|
|
||||||
DLToast.showSuccess(text: "登录成功") {
|
|
||||||
if let userId = model.uid {
|
|
||||||
MQTTService.shared.updateClientID("smartdrive_\(userId)")
|
|
||||||
}
|
|
||||||
NotificationCenter.default.post(name: .RefreshUserConfigNotification, object: nil)
|
|
||||||
AppRouter.shared.popOrDismiss()
|
|
||||||
}
|
|
||||||
}.disposed(by: disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func performGuestLogin() {
|
func performGuestLogin() {
|
||||||
loginSuccess.onNext(())
|
loginSuccess.onNext(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !targetEnvironment(simulator)
|
||||||
|
func performOneTapLogin(from vc: UIViewController, contentView: OneTapLoginView) {
|
||||||
|
NotificationCenter.default.post(name: .pausePopupQueue, object: nil)
|
||||||
|
let subViews: [UIView] = [
|
||||||
|
contentView.phoneNumberLabel,
|
||||||
|
contentView.carrierLabel,
|
||||||
|
contentView.loginButton,
|
||||||
|
contentView.checkBox,
|
||||||
|
contentView.agreementTV
|
||||||
|
]
|
||||||
|
GeYanSdk.oneTapLogin(vc, viewModel: gyAuthVM, contentView: contentView,
|
||||||
|
subViewList: subViews) { [weak self] result in
|
||||||
|
guard let dict = result as? [String: Any],
|
||||||
|
dict["code"] as? Int == 30000,
|
||||||
|
let token = dict["token"] as? String else { return }
|
||||||
|
|
||||||
|
let opType: String = {
|
||||||
|
switch dict["operatorType"] as? Int {
|
||||||
|
case 1: return "CM"
|
||||||
|
case 2: return "CU"
|
||||||
|
case 3: return "CT"
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
self?.loginAction(type: "onekey", data: [
|
||||||
|
"auth_code": "",
|
||||||
|
"access_code": token,
|
||||||
|
"operator_type": opType
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
func performWechatLogin() {
|
func performWechatLogin() {
|
||||||
// TODO: Integrate WeChat SDK
|
NotificationCenter.default.post(name: .pausePopupQueue, object: nil)
|
||||||
|
let req = SendAuthReq()
|
||||||
|
req.scope = "snsapi_userinfo"
|
||||||
|
req.state = "login"
|
||||||
|
WXApi.send(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func performAppleLogin() {
|
func performAppleLogin() {
|
||||||
// TODO: Integrate Sign in with Apple
|
NotificationCenter.default.post(name: .pausePopupQueue, object: nil)
|
||||||
|
let provider = ASAuthorizationAppleIDProvider()
|
||||||
|
let request = provider.createRequest()
|
||||||
|
request.requestedScopes = [.fullName, .email]
|
||||||
|
let controller = ASAuthorizationController(authorizationRequests: [request])
|
||||||
|
onAppleLoginRequest?(controller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ class OneTapLoginView: UIView {
|
||||||
|
|
||||||
private func setupRx() {
|
private func setupRx() {
|
||||||
backBtn.rx.tap.subscribe(onNext: { _ in
|
backBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
AppRouter.shared.popOrDismiss()
|
#if !targetEnvironment(simulator)
|
||||||
|
GeYanSdk.closeAuthVC(true, completion: nil)
|
||||||
|
#endif
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
checkBox.rx.tap.subscribe(onNext: { [weak self] in
|
checkBox.rx.tap.subscribe(onNext: { [weak self] in
|
||||||
|
|
@ -55,11 +57,12 @@ class OneTapLoginView: UIView {
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
addSubview(bgMaskImage)
|
addSubview(bgMaskImage)
|
||||||
addSubview(backBtn)
|
addSubview(backBtn)
|
||||||
addSubview(welcomeLabel)
|
addSubview(inputContainerView)
|
||||||
addSubview(phoneNumberLabel)
|
inputContainerView.addSubview(welcomeLabel)
|
||||||
addSubview(carrierLabel)
|
inputContainerView.addSubview(phoneNumberLabel)
|
||||||
addSubview(loginButton)
|
inputContainerView.addSubview(carrierLabel)
|
||||||
|
inputContainerView.addSubview(loginButton)
|
||||||
|
|
||||||
addSubview(agreementView)
|
addSubview(agreementView)
|
||||||
agreementView.addSubview(checkBox)
|
agreementView.addSubview(checkBox)
|
||||||
agreementView.addSubview(agreementTV)
|
agreementView.addSubview(agreementTV)
|
||||||
|
|
@ -73,8 +76,14 @@ class OneTapLoginView: UIView {
|
||||||
.width(24)
|
.width(24)
|
||||||
.height(24)
|
.height(24)
|
||||||
|
|
||||||
|
inputContainerView.layoutChain
|
||||||
|
.centerX()
|
||||||
|
.centerY()
|
||||||
|
.edgesHorzontal()
|
||||||
|
.height(301)
|
||||||
|
|
||||||
welcomeLabel.layoutChain
|
welcomeLabel.layoutChain
|
||||||
.top(202 - kNaviHeight)
|
.top(0)
|
||||||
.centerX()
|
.centerX()
|
||||||
.edgesHorzontal()
|
.edgesHorzontal()
|
||||||
|
|
||||||
|
|
@ -130,6 +139,12 @@ class OneTapLoginView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UI Components
|
// MARK: - UI Components
|
||||||
|
lazy var inputContainerView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
lazy var bgMaskImage: UIImageView = {
|
lazy var bgMaskImage: UIImageView = {
|
||||||
let iv = UIImageView()
|
let iv = UIImageView()
|
||||||
iv.image = UIImage(named: "Login/bg")
|
iv.image = UIImage(named: "Login/bg")
|
||||||
|
|
@ -207,7 +222,6 @@ class OneTapLoginView: UIView {
|
||||||
textView.backgroundColor = .clear
|
textView.backgroundColor = .clear
|
||||||
textView.isEditable = false
|
textView.isEditable = false
|
||||||
textView.isScrollEnabled = false
|
textView.isScrollEnabled = false
|
||||||
textView.isSelectable = false
|
|
||||||
textView.linkTextAttributes = [:]
|
textView.linkTextAttributes = [:]
|
||||||
|
|
||||||
return textView
|
return textView
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ class AccountVC: BaseViewController {
|
||||||
DLToast.showLoading()
|
DLToast.showLoading()
|
||||||
UserService.logout().subscribe(onNext: { response in
|
UserService.logout().subscribe(onNext: { response in
|
||||||
DLToast.dismiss()
|
DLToast.dismiss()
|
||||||
|
NotificationCenter.default.post(name: .invalidatePopupQueue, object: nil)
|
||||||
AppContextManager.shared.deleteAccount()
|
AppContextManager.shared.deleteAccount()
|
||||||
GroupIMService.shared.logout()
|
GroupIMService.shared.logout()
|
||||||
AppDelegate.shared.showMainViewController()
|
AppDelegate.shared.showMainViewController()
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,10 @@ final class MineView: UIView {
|
||||||
// MARK: - Callbacks
|
// MARK: - Callbacks
|
||||||
var onMenuTap: ((Int) -> Void)?
|
var onMenuTap: ((Int) -> Void)?
|
||||||
|
|
||||||
|
// MARK: - Dynamic Height
|
||||||
|
private var settingsCardHeightConstraint: NSLayoutConstraint?
|
||||||
|
private var contentSizeObservation: NSKeyValueObservation?
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
@ -289,14 +293,42 @@ final class MineView: UIView {
|
||||||
.topToBottomOfView(profileBgImageView)
|
.topToBottomOfView(profileBgImageView)
|
||||||
.left().right().bottom()
|
.left().right().bottom()
|
||||||
|
|
||||||
// Settings Card (fixed height, not stretched)
|
// Settings Card (dynamic height, scrolls when exceeds area above tabbar)
|
||||||
settingsCardView.layoutChain
|
settingsCardView.layoutChain
|
||||||
.topToBottomOfView(vipCardView, offset: 20)
|
.topToBottomOfView(vipCardView, offset: 20)
|
||||||
.left(16).right(16)
|
.left(16).right(16)
|
||||||
.height(312)
|
|
||||||
|
settingsCardHeightConstraint = settingsCardView.heightAnchor.constraint(equalToConstant: 312)
|
||||||
|
settingsCardHeightConstraint?.isActive = true
|
||||||
|
|
||||||
settingsTableView.layoutChain
|
settingsTableView.layoutChain
|
||||||
.edges()
|
.edges()
|
||||||
|
|
||||||
|
contentSizeObservation = settingsTableView.observe(\.contentSize, options: [.new, .initial]) { [weak self] _, _ in
|
||||||
|
self?.refreshTableViewHeight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Layout
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
refreshTableViewHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshTableViewHeight() {
|
||||||
|
let contentHeight = settingsTableView.contentSize.height
|
||||||
|
guard contentHeight > 0 else { return }
|
||||||
|
|
||||||
|
let tabBarHeight: CGFloat = 56 + kSafeBottomMargin
|
||||||
|
let maxHeight = bounds.height - tabBarHeight - settingsCardView.frame.minY
|
||||||
|
|
||||||
|
if contentHeight <= maxHeight {
|
||||||
|
settingsCardHeightConstraint?.constant = contentHeight
|
||||||
|
settingsTableView.isScrollEnabled = false
|
||||||
|
} else {
|
||||||
|
settingsCardHeightConstraint?.constant = maxHeight
|
||||||
|
settingsTableView.isScrollEnabled = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,28 @@ class PopupQueueManager {
|
||||||
|
|
||||||
private weak var sourceVC: UIViewController?
|
private weak var sourceVC: UIViewController?
|
||||||
private var currentStep = 0
|
private var currentStep = 0
|
||||||
|
private var stepGeneration = 0
|
||||||
private var countdownTimer: Timer?
|
private var countdownTimer: Timer?
|
||||||
|
private var isPaused = false
|
||||||
|
|
||||||
|
init() {
|
||||||
|
NotificationCenter.default.addObserver(self,
|
||||||
|
selector: #selector(handlePause),
|
||||||
|
name: .pausePopupQueue,
|
||||||
|
object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self,
|
||||||
|
selector: #selector(handleResume),
|
||||||
|
name: .resumePopupQueue,
|
||||||
|
object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self,
|
||||||
|
selector: #selector(handleInvalidate),
|
||||||
|
name: .invalidatePopupQueue,
|
||||||
|
object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
func start(from vc: UIViewController) {
|
func start(from vc: UIViewController) {
|
||||||
sourceVC = vc
|
sourceVC = vc
|
||||||
|
|
@ -19,6 +40,25 @@ class PopupQueueManager {
|
||||||
executeNextStep()
|
executeNextStep()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func handlePause() {
|
||||||
|
countdownTimer?.invalidate()
|
||||||
|
countdownTimer = nil
|
||||||
|
isPaused = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleResume() {
|
||||||
|
guard isPaused else { return }
|
||||||
|
isPaused = false
|
||||||
|
executeNextStep()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleInvalidate() {
|
||||||
|
countdownTimer?.invalidate()
|
||||||
|
countdownTimer = nil
|
||||||
|
isPaused = true
|
||||||
|
currentStep = 3
|
||||||
|
}
|
||||||
|
|
||||||
private func executeNextStep() {
|
private func executeNextStep() {
|
||||||
switch currentStep {
|
switch currentStep {
|
||||||
case 0:
|
case 0:
|
||||||
|
|
@ -45,12 +85,14 @@ class PopupQueueManager {
|
||||||
skipToNext()
|
skipToNext()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let gen = stepGeneration
|
||||||
startCountdown(seconds: timeConfig.pay_pop_time) { [weak self] in
|
startCountdown(seconds: timeConfig.pay_pop_time) { [weak self] in
|
||||||
|
guard let self = self, self.currentStep == 0, self.stepGeneration == gen else { return }
|
||||||
let vc = PromotionalActivitiesVC()
|
let vc = PromotionalActivitiesVC()
|
||||||
vc.onDismiss = { [weak self] in
|
vc.onDismiss = { [weak self] in
|
||||||
self?.skipToNext()
|
self?.skipToNext()
|
||||||
}
|
}
|
||||||
self?.push(vc)
|
self.push(vc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,9 +108,11 @@ class PopupQueueManager {
|
||||||
skipToNext()
|
skipToNext()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
startCountdown(seconds: timeConfig.search_phone) { [weak self] in
|
let gen = stepGeneration
|
||||||
|
startCountdown(seconds: timeConfig.search_phone) {
|
||||||
|
guard self.currentStep == 1, self.stepGeneration == gen else { return }
|
||||||
PopupWindow.show(popupType: .search) {
|
PopupWindow.show(popupType: .search) {
|
||||||
self?.skipToNext()
|
self.skipToNext()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,13 +126,17 @@ class PopupQueueManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if AppContextManager.shared.isGuest {
|
if AppContextManager.shared.isGuest {
|
||||||
startCountdown(seconds: timeConfig.signup_tip) { [weak self] in
|
let gen = stepGeneration
|
||||||
|
startCountdown(seconds: timeConfig.signup_tip) {
|
||||||
|
guard self.currentStep == 2, self.stepGeneration == gen else { return }
|
||||||
PopupWindow.show(popupType: .register) {
|
PopupWindow.show(popupType: .register) {
|
||||||
// done
|
// done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if AppContextManager.shared.vip == 1 {
|
} else if AppContextManager.shared.vip == 1 {
|
||||||
startCountdown(seconds: timeConfig.buy_vip_tip) { [weak self] in
|
let gen = stepGeneration
|
||||||
|
startCountdown(seconds: timeConfig.buy_vip_tip) {
|
||||||
|
guard self.currentStep == 2, self.stepGeneration == gen else { return }
|
||||||
PopupWindow.show(popupType: .vip) {
|
PopupWindow.show(popupType: .vip) {
|
||||||
// done
|
// done
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +147,7 @@ class PopupQueueManager {
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private func skipToNext() {
|
private func skipToNext() {
|
||||||
|
stepGeneration += 1
|
||||||
currentStep += 1
|
currentStep += 1
|
||||||
executeNextStep()
|
executeNextStep()
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +171,11 @@ class PopupQueueManager {
|
||||||
|
|
||||||
private func push(_ vc: UIViewController) {
|
private func push(_ vc: UIViewController) {
|
||||||
let topVC = topViewController()
|
let topVC = topViewController()
|
||||||
topVC?.navigationController?.pushViewController(vc, animated: true)
|
guard let nav = topVC?.navigationController else { return }
|
||||||
|
if nav.viewControllers.contains(where: { type(of: $0) == type(of: vc) }) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nav.pushViewController(vc, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func topViewController() -> UIViewController? {
|
private func topViewController() -> UIViewController? {
|
||||||
|
|
|
||||||
|
|
@ -151,8 +151,12 @@ class PopupWindow: UIView {
|
||||||
|
|
||||||
registerBtn.rx.tap
|
registerBtn.rx.tap
|
||||||
.subscribe(onNext: { [weak self] _ in
|
.subscribe(onNext: { [weak self] _ in
|
||||||
|
NotificationCenter.default.post(name: .pausePopupQueue, object: nil)
|
||||||
self?.activePopupView == 2 ? self?.slideOutRegister2Popup() : self?.slideOutRegisterPopup()
|
self?.activePopupView == 2 ? self?.slideOutRegister2Popup() : self?.slideOutRegisterPopup()
|
||||||
AppRouter.push(Route.login)
|
let topVC = UIViewController.currentViewController()
|
||||||
|
if !(topVC is LoginViewController) {
|
||||||
|
AppRouter.push(Route.login)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
@ -164,8 +168,12 @@ class PopupWindow: UIView {
|
||||||
|
|
||||||
register2Btn.rx.tap
|
register2Btn.rx.tap
|
||||||
.subscribe(onNext: { [weak self] _ in
|
.subscribe(onNext: { [weak self] _ in
|
||||||
|
NotificationCenter.default.post(name: .pausePopupQueue, object: nil)
|
||||||
self?.slideOutRegister2Popup()
|
self?.slideOutRegister2Popup()
|
||||||
AppRouter.push(Route.login)
|
let topVC = UIViewController.currentViewController()
|
||||||
|
if !(topVC is LoginViewController) {
|
||||||
|
AppRouter.push(Route.login)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
@ -178,6 +186,10 @@ class PopupWindow: UIView {
|
||||||
vip1UnlockBtn.rx.tap
|
vip1UnlockBtn.rx.tap
|
||||||
.subscribe(onNext: { [weak self] _ in
|
.subscribe(onNext: { [weak self] _ in
|
||||||
self?.slideOutUnlockVip1Popup()
|
self?.slideOutUnlockVip1Popup()
|
||||||
|
let topVC = UIViewController.currentViewController()
|
||||||
|
if !(topVC is VipRechargeVC) {
|
||||||
|
AppRouter.push(Route.vipRecharge)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
@ -190,6 +202,10 @@ class PopupWindow: UIView {
|
||||||
unlockVip2Btn.rx.tap
|
unlockVip2Btn.rx.tap
|
||||||
.subscribe(onNext: { [weak self] _ in
|
.subscribe(onNext: { [weak self] _ in
|
||||||
self?.slideOutUnlockVip2Popup()
|
self?.slideOutUnlockVip2Popup()
|
||||||
|
let topVC = UIViewController.currentViewController()
|
||||||
|
if !(topVC is VipRechargeVC) {
|
||||||
|
AppRouter.push(Route.vipRecharge)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
@ -202,6 +218,10 @@ class PopupWindow: UIView {
|
||||||
unlockVip3Btn.rx.tap
|
unlockVip3Btn.rx.tap
|
||||||
.subscribe(onNext: { [weak self] _ in
|
.subscribe(onNext: { [weak self] _ in
|
||||||
self?.slideOutUnlockVip3Popup()
|
self?.slideOutUnlockVip3Popup()
|
||||||
|
let topVC = UIViewController.currentViewController()
|
||||||
|
if !(topVC is VipRechargeVC) {
|
||||||
|
AppRouter.push(Route.vipRecharge)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
@ -214,6 +234,10 @@ class PopupWindow: UIView {
|
||||||
unlockVip4Btn.rx.tap
|
unlockVip4Btn.rx.tap
|
||||||
.subscribe(onNext: { [weak self] _ in
|
.subscribe(onNext: { [weak self] _ in
|
||||||
self?.slideOutUnlockVip4Popup()
|
self?.slideOutUnlockVip4Popup()
|
||||||
|
let topVC = UIViewController.currentViewController()
|
||||||
|
if !(topVC is VipRechargeVC) {
|
||||||
|
AppRouter.push(Route.vipRecharge)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ class PromotionalActivitiesVC: BaseViewController {
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
fd_interactivePopDisabled = true
|
||||||
setupTableView()
|
setupTableView()
|
||||||
requestRechargeInfo()
|
requestRechargeInfo()
|
||||||
|
|
||||||
|
|
@ -103,6 +103,11 @@ class PromotionalActivitiesVC: BaseViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
navigationController?.setNavigationBarHidden(true, animated: false)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
private func requestRechargeInfo() {
|
private func requestRechargeInfo() {
|
||||||
DLToast.showLoading()
|
DLToast.showLoading()
|
||||||
|
|
@ -126,29 +131,29 @@ class PromotionalActivitiesVC: BaseViewController {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if timeConfig.pay_pop_type == 3 {
|
// if timeConfig.pay_pop_type == 3 {
|
||||||
IAPManager.shared.purchase(goodsId: selectedGoodsId) { [weak self] success, msg in
|
// IAPManager.shared.purchase(goodsId: selectedGoodsId) { [weak self] success, msg in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
if success {
|
// if success {
|
||||||
self.showPayResultPop(showCloseBtn: false,
|
// self.showPayResultPop(showCloseBtn: false,
|
||||||
status: true,
|
// status: true,
|
||||||
title: "支付成功",
|
// title: "支付成功",
|
||||||
message: "恭喜您!成功开通!",
|
// message: "恭喜您!成功开通!",
|
||||||
confirmText: "确定", confirmBlock: {
|
// confirmText: "确定", confirmBlock: {
|
||||||
AppRouter.shared.popOrDismiss()
|
// AppRouter.shared.popOrDismiss()
|
||||||
}, cancelBlock: { })
|
// }, cancelBlock: { })
|
||||||
} else {
|
// } else {
|
||||||
self.showPayResultPop(status: false,
|
// self.showPayResultPop(status: false,
|
||||||
title: "支付失败",
|
// title: "支付失败",
|
||||||
message: msg ?? "很抱歉,请您重新支付!",
|
// message: msg ?? "很抱歉,请您重新支付!",
|
||||||
confirmText: "重新支付", confirmBlock: { },
|
// confirmText: "重新支付", confirmBlock: { },
|
||||||
cancelText: "取消", cancelBlock: {
|
// cancelText: "取消", cancelBlock: {
|
||||||
AppRouter.shared.popOrDismiss()
|
// AppRouter.shared.popOrDismiss()
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
let payType = timeConfig.pay_pop_type == 1 ? "alipay" : "weixin"
|
let payType = timeConfig.pay_pop_type == 1 ? "alipay" : "weixin"
|
||||||
dl.showLoading()
|
dl.showLoading()
|
||||||
|
|
|
||||||
|
|
@ -533,7 +533,7 @@ class PromotionalRecommendCell: UITableViewCell {
|
||||||
|
|
||||||
tipsTitleLab.layoutChain
|
tipsTitleLab.layoutChain
|
||||||
.centerX()
|
.centerX()
|
||||||
.top(5)
|
.top(7)
|
||||||
|
|
||||||
priceLab.layoutChain
|
priceLab.layoutChain
|
||||||
.leftToRightOfView(unitLab, offset: 0)
|
.leftToRightOfView(unitLab, offset: 0)
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,16 @@ class ScheduleVC: BaseViewController {
|
||||||
self.rootView.tableView.refresh(status: status, isEmpty: isEmpty)
|
self.rootView.tableView.refresh(status: status, isEmpty: isEmpty)
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
viewModel.output.sectionedItems
|
||||||
|
.map { !($0.first?.items.isEmpty ?? true) }
|
||||||
|
.bind(to: rootView.emptyVisitorsLabel.rx.isHidden)
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
viewModel.output.scheduleSectionedItems
|
||||||
|
.map { !($0.first?.items.isEmpty ?? true) }
|
||||||
|
.bind(to: rootView.emptyScheduleLabel.rx.isHidden)
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
rootView.collectionView.rx.modelSelected(ViewedModel.self)
|
rootView.collectionView.rx.modelSelected(ViewedModel.self)
|
||||||
.subscribe(viewModel.viewedCellAction.inputs)
|
.subscribe(viewModel.viewedCellAction.inputs)
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,12 @@ class ScheduleView: UIView {
|
||||||
.edgesHorzontal()
|
.edgesHorzontal()
|
||||||
.height(80)
|
.height(80)
|
||||||
.bottom(15)
|
.bottom(15)
|
||||||
|
|
||||||
|
// 暂无访客
|
||||||
|
view.addSubview(emptyVisitorsLabel)
|
||||||
|
emptyVisitorsLabel.layoutChain
|
||||||
|
.centerY(collectionView)
|
||||||
|
.centerX()
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
@ -145,6 +151,16 @@ class ScheduleView: UIView {
|
||||||
return cv
|
return cv
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
lazy var emptyVisitorsLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = "暂无访客"
|
||||||
|
label.font = .systemFont(ofSize: 14)
|
||||||
|
label.textColor = UIColor(hexStr: "#999999")
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.isHidden = true
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
/// 行程路线
|
/// 行程路线
|
||||||
lazy var travelRouteView: UIView = {
|
lazy var travelRouteView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
|
|
@ -213,9 +229,20 @@ class ScheduleView: UIView {
|
||||||
tableView.showsVerticalScrollIndicator = false
|
tableView.showsVerticalScrollIndicator = false
|
||||||
tableView.register(ScheduleListPopCell.self)
|
tableView.register(ScheduleListPopCell.self)
|
||||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
|
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
|
||||||
|
tableView.backgroundView = emptyScheduleLabel
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
lazy var emptyScheduleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = "暂无行程"
|
||||||
|
label.font = .systemFont(ofSize: 14)
|
||||||
|
label.textColor = UIColor(hexStr: "#999999")
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.isHidden = true
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
lazy var createBtn: UIButton = {
|
lazy var createBtn: UIButton = {
|
||||||
let btn = UIButton()
|
let btn = UIButton()
|
||||||
btn.setImage(UIImage(named: "Schedule/create"), for: .normal)
|
btn.setImage(UIImage(named: "Schedule/create"), for: .normal)
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,22 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
class SharePopView: UIView {
|
class SharePopView: UIView {
|
||||||
|
|
||||||
private static let shared = SharePopView(frame: CGRect(origin: .zero, size: kScreenSize))
|
private static let shared = SharePopView(frame: CGRect(origin: .zero, size: kScreenSize))
|
||||||
private var shareURL: String = ""
|
private var shareURL: String = ""
|
||||||
|
|
||||||
private let contentHeight: CGFloat = 191 + kSafeBottomMargin
|
private let contentHeight: CGFloat = 191 + kSafeBottomMargin
|
||||||
|
|
||||||
static func show(shareURL: String) {
|
static func show() {
|
||||||
guard let superView = kKeyWindow else { return }
|
guard let superView = kKeyWindow else { return }
|
||||||
|
|
||||||
if SharePopView.shared.superview != nil {
|
if SharePopView.shared.superview != nil {
|
||||||
SharePopView.shared.dismiss(animated: false)
|
SharePopView.shared.dismiss(animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
SharePopView.shared.shareURL = shareURL
|
|
||||||
SharePopView.shared.bgView.alpha = 0
|
SharePopView.shared.bgView.alpha = 0
|
||||||
superView.addSubview(SharePopView.shared)
|
superView.addSubview(SharePopView.shared)
|
||||||
superView.bringSubviewToFront(SharePopView.shared)
|
superView.bringSubviewToFront(SharePopView.shared)
|
||||||
|
|
@ -65,19 +65,52 @@ class SharePopView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func shareToWeChat(scene: Int32) {
|
private func shareToWeChat(scene: Int32) {
|
||||||
let message = WXMediaMessage()
|
guard let config = AppContextManager.shared.systemConfig,
|
||||||
message.title = kAppName
|
let shareConfig = config.shareConfig else { return }
|
||||||
let webpage = WXWebpageObject()
|
|
||||||
webpage.webpageUrl = shareURL
|
|
||||||
message.mediaObject = webpage
|
|
||||||
|
|
||||||
let req = SendMessageToWXReq()
|
DLToast.showLoading()
|
||||||
req.bText = false
|
|
||||||
req.message = message
|
|
||||||
req.scene = scene
|
|
||||||
WXApi.send(req)
|
|
||||||
|
|
||||||
dismiss()
|
let fallbackImage = UIImage(named: "Common/logo108")
|
||||||
|
let imageURL: URL? = {
|
||||||
|
if shareConfig.image.isEmpty { return nil }
|
||||||
|
return URL(string: shareConfig.image)
|
||||||
|
}()
|
||||||
|
|
||||||
|
let sendMessage: (UIImage?) -> Void = { [weak self] thumbImage in
|
||||||
|
guard let self = self else { return }
|
||||||
|
DLToast.dismiss()
|
||||||
|
let message = WXMediaMessage()
|
||||||
|
message.title = shareConfig.title
|
||||||
|
message.description = shareConfig.content
|
||||||
|
if let thumb = thumbImage {
|
||||||
|
message.setThumbImage(thumb)
|
||||||
|
}
|
||||||
|
|
||||||
|
let webpage = WXWebpageObject()
|
||||||
|
webpage.webpageUrl = shareConfig.link
|
||||||
|
message.mediaObject = webpage
|
||||||
|
|
||||||
|
let req = SendMessageToWXReq()
|
||||||
|
req.bText = false
|
||||||
|
req.message = message
|
||||||
|
req.scene = scene
|
||||||
|
WXApi.send(req)
|
||||||
|
|
||||||
|
self.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let url = imageURL {
|
||||||
|
KingfisherManager.shared.retrieveImage(with: url) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let value):
|
||||||
|
sendMessage(value.image)
|
||||||
|
case .failure:
|
||||||
|
sendMessage(fallbackImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendMessage(fallbackImage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UI
|
// MARK: - UI
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,11 @@ class VipRechargeVC: BaseViewController {
|
||||||
requestRechargeInfo()
|
requestRechargeInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
navigationController?.setNavigationBarHidden(true, animated: false)
|
||||||
|
}
|
||||||
|
|
||||||
private func bindViewModel() {
|
private func bindViewModel() {
|
||||||
viewModel.output.sectionedItems
|
viewModel.output.sectionedItems
|
||||||
.bind(to: rootView.expenseCollectionView.rx.items(dataSource: dataSource))
|
.bind(to: rootView.expenseCollectionView.rx.items(dataSource: dataSource))
|
||||||
|
|
@ -102,29 +107,29 @@ class VipRechargeVC: BaseViewController {
|
||||||
let types = viewModel.payType.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
|
let types = viewModel.payType.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
|
||||||
let payType = types[rootView.selectedPayTypeTag]
|
let payType = types[rootView.selectedPayTypeTag]
|
||||||
|
|
||||||
if payType == "apple" {
|
// if payType == "apple" {
|
||||||
IAPManager.shared.purchase(goodsId: viewModel.goodsId) { [weak self] success, msg in
|
// IAPManager.shared.purchase(goodsId: viewModel.goodsId) { [weak self] success, msg in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
if success {
|
// if success {
|
||||||
self.showPayResultPop(showCloseBtn: false,
|
// self.showPayResultPop(showCloseBtn: false,
|
||||||
status: true,
|
// status: true,
|
||||||
title: "支付成功",
|
// title: "支付成功",
|
||||||
message: "恭喜您!成功开通\(self.viewModel.goodsName)",
|
// message: "恭喜您!成功开通\(self.viewModel.goodsName)",
|
||||||
confirmText: "确定", confirmBlock: {
|
// confirmText: "确定", confirmBlock: {
|
||||||
AppRouter.shared.popOrDismiss()
|
// AppRouter.shared.popOrDismiss()
|
||||||
}, cancelBlock: { })
|
// }, cancelBlock: { })
|
||||||
} else {
|
// } else {
|
||||||
self.showPayResultPop(status: false,
|
// self.showPayResultPop(status: false,
|
||||||
title: "支付失败",
|
// title: "支付失败",
|
||||||
message: msg ?? "很抱歉,请您重新支付!",
|
// message: msg ?? "很抱歉,请您重新支付!",
|
||||||
confirmText: "重新支付", confirmBlock: { },
|
// confirmText: "重新支付", confirmBlock: { },
|
||||||
cancelText: "取消", cancelBlock: {
|
// cancelText: "取消", cancelBlock: {
|
||||||
AppRouter.shared.popOrDismiss()
|
// AppRouter.shared.popOrDismiss()
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
DLToast.showLoading()
|
DLToast.showLoading()
|
||||||
OrderService.orderPayParams(goodsId: viewModel.goodsId,
|
OrderService.orderPayParams(goodsId: viewModel.goodsId,
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class VipRechargeView: UIView {
|
||||||
.heightToWidth(267/375)
|
.heightToWidth(267/375)
|
||||||
|
|
||||||
vipRightsDetailView.layoutChain
|
vipRightsDetailView.layoutChain
|
||||||
.top(36)
|
.top(36 + kStatusBarHeight)
|
||||||
.right()
|
.right()
|
||||||
.height(24)
|
.height(24)
|
||||||
|
|
||||||
|
|
@ -562,7 +562,7 @@ class VipRechargeView: UIView {
|
||||||
let payTypeMap: [(String, String)] = types.compactMap {
|
let payTypeMap: [(String, String)] = types.compactMap {
|
||||||
if $0 == "weixin" { return ("wechat", "微信支付") }
|
if $0 == "weixin" { return ("wechat", "微信支付") }
|
||||||
if $0 == "alipay" { return ("alipay", "支付宝支付") }
|
if $0 == "alipay" { return ("alipay", "支付宝支付") }
|
||||||
if $0 == "apple" { return ("apple", "苹果支付") }
|
// if $0 == "apple" { return ("apple", "苹果支付") }
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard !payTypeMap.isEmpty else { return }
|
guard !payTypeMap.isEmpty else { return }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue