bug修改

-  微信登录首次没反应
      -  UI显示问题修改
This commit is contained in:
linshujie 2026-07-07 18:49:11 +08:00
parent 6163dd1ca8
commit 6eba31963b
27 changed files with 653 additions and 227 deletions

View File

@ -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

View File

@ -20,7 +20,7 @@ extension DefaultsKeys {
/// IMid /// IMid
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")
} }

View File

@ -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: -

View File

@ -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)
]) ])
} }

View File

@ -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"]
}
}

View File

@ -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 ?? "登录失效,请重新登录!") {

View File

@ -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 }

View File

@ -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?()
} }

View File

@ -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
} }
} }
} }

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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 登录失败")
}
}

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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()

View File

@ -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
}
} }
} }

View File

@ -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? {

View File

@ -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)
} }

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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 }