diff --git a/QuickLocation/Assets.xcassets/Common/logo108.imageset/Contents.json b/QuickLocation/Assets.xcassets/Common/logo108.imageset/Contents.json new file mode 100644 index 0000000..e2c7866 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Common/logo108.imageset/Contents.json @@ -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 + } +} diff --git a/QuickLocation/Assets.xcassets/Common/logo108.imageset/logo108@2x.png b/QuickLocation/Assets.xcassets/Common/logo108.imageset/logo108@2x.png new file mode 100644 index 0000000..2422b18 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/logo108.imageset/logo108@2x.png differ diff --git a/QuickLocation/Common/Constant.swift b/QuickLocation/Common/Constant.swift index 5eb6867..c3e9b98 100644 --- a/QuickLocation/Common/Constant.swift +++ b/QuickLocation/Common/Constant.swift @@ -20,7 +20,7 @@ extension DefaultsKeys { /// 重新登录(IM需要获取id和签名) var isReLogin: DefaultsKey { .init("isReLogin", defaultValue: false) } /// 显示引导页 - var guideShowVersion: DefaultsKey { .init("guideShowVersion") } + var isShowGuide: DefaultsKey { .init("isShowGuide", defaultValue: true) } } /// 通知常量 @@ -35,6 +35,12 @@ extension Notification.Name { static let ShowMemberLocationNotification = Notification.Name("ShowMemberLocationNotification") /// 支付宝/微信支付结果回调 static let RequestOrderPayStatusNotification = Notification.Name("RequestOrderPayStatusNotification") + /// 暂停弹窗队列(第三方登录/注册跳转时) + static let pausePopupQueue = Notification.Name("pausePopupQueue") + /// 恢复弹窗队列(第三方登录取消/失败时) + static let resumePopupQueue = Notification.Name("resumePopupQueue") + /// 终止弹窗队列(登录成功后) + static let invalidatePopupQueue = Notification.Name("invalidatePopupQueue") } diff --git a/QuickLocation/Main/BaseViewController/BaseViewController.swift b/QuickLocation/Main/BaseViewController/BaseViewController.swift index 929ce95..0aa6d36 100644 --- a/QuickLocation/Main/BaseViewController/BaseViewController.swift +++ b/QuickLocation/Main/BaseViewController/BaseViewController.swift @@ -33,8 +33,7 @@ class BaseViewController: UIViewController { /// 导航栏标题 var navTitle: String = "" { didSet { - backButton.setTitle(" \(navTitle)", for: .normal) -// navigationItem.title = navTitle + navigationItem.title = navTitle } } @@ -102,32 +101,13 @@ class BaseViewController: UIViewController { dismissItem.tintColor = .white navigationItem.leftBarButtonItem = dismissItem } else if let count = navigationController?.viewControllers.count, count > 1 { - let leftBarBtn = UIBarButtonItem (customView: backButton) - - //用于消除左边空隙,要不然按钮顶不到最前面 - let spacer = UIBarButtonItem (barButtonSystemItem: .fixedSpace, target: nil, action: nil) - spacer.width = -10 - 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 + let leftBackItem = UIBarButtonItem(image: UIImage(named: "Common/back"), + style: .plain, + target: self, + action: #selector(leftBackClicked(_:))) + 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: - 验证码 diff --git a/QuickLocation/Main/Tabbar/MainTabBarController.swift b/QuickLocation/Main/Tabbar/MainTabBarController.swift index e145772..03ed688 100644 --- a/QuickLocation/Main/Tabbar/MainTabBarController.swift +++ b/QuickLocation/Main/Tabbar/MainTabBarController.swift @@ -50,6 +50,7 @@ final class MainTabBarController: UITabBarController { viewControllers = navControllers tabBar.isHidden = true + tabBar.removeFromSuperview() for nav in navControllers { nav.delegate = self @@ -71,7 +72,7 @@ final class MainTabBarController: UITabBarController { NSLayoutConstraint.activate([ customTabBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), 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) ]) } diff --git a/QuickLocation/Manager/Account/UserConfigModel.swift b/QuickLocation/Manager/Account/UserConfigModel.swift index 9934cde..5ef2208 100644 --- a/QuickLocation/Manager/Account/UserConfigModel.swift +++ b/QuickLocation/Manager/Account/UserConfigModel.swift @@ -68,6 +68,8 @@ struct SystemConfigModel: Mappable { var popupConfig: PopupWindowConfigModel? /// 弹窗时间配置 var timeConfig: PopupWindowTimeModel? + /// 微信分享配置 + var shareConfig: WxShareConfigModel? /// 聊天界面的警告说明 var chatWarning: String = "" @@ -84,6 +86,7 @@ struct SystemConfigModel: Mappable { groupBannerList <- map["client.team.ad", nested: false] popupConfig <- map["client.popup.display", 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"] } } + +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"] + } +} diff --git a/QuickLocation/Manager/App/ApiManager.swift b/QuickLocation/Manager/App/ApiManager.swift index 6bd0096..27871e1 100644 --- a/QuickLocation/Manager/App/ApiManager.swift +++ b/QuickLocation/Manager/App/ApiManager.swift @@ -157,6 +157,7 @@ extension ApiManager { // MARK: - 处理Token过期 private func handleTokenExpired(msg: String?) { MainAsync { + NotificationCenter.default.post(name: .invalidatePopupQueue, object: nil) AppContextManager.shared.deleteAccount() GroupIMService.shared.logout() DLToast.showError(text: msg ?? "登录失效,请重新登录!") { diff --git a/QuickLocation/Section/Group/InviteJoin/InviteJoinVC.swift b/QuickLocation/Section/Group/InviteJoin/InviteJoinVC.swift index 9ecf88c..37b96de 100644 --- a/QuickLocation/Section/Group/InviteJoin/InviteJoinVC.swift +++ b/QuickLocation/Section/Group/InviteJoin/InviteJoinVC.swift @@ -28,8 +28,7 @@ class InviteJoinVC: BaseViewController { // 分享 rootView.shareAppBtn.rx.tap.subscribe(onNext: { _ in - let url = "https://smartdrive.zuom8.cn/jisu/share/user/" - SharePopView.show(shareURL: url) + SharePopView.show() }).disposed(by: disposeBag) guard let model = groupInModel else { return } diff --git a/QuickLocation/Section/Home/HomeView.swift b/QuickLocation/Section/Home/HomeView.swift index 2cb6980..678306b 100644 --- a/QuickLocation/Section/Home/HomeView.swift +++ b/QuickLocation/Section/Home/HomeView.swift @@ -236,6 +236,7 @@ class HomeView: UIView { quickMessageView.isHidden = false interactionView.isHidden = false toolsView.isHidden = true + searchLottieView.isHidden = true quickMessageView.frame.origin.y = kScreenHeight - 384 - 93 interactionView.frame.origin.y = kScreenHeight - 384 } @@ -246,6 +247,7 @@ class HomeView: UIView { quickMessageView.isHidden = true interactionView.isHidden = true toolsView.isHidden = false + searchLottieView.isHidden = false onDismissPanel?() } diff --git a/QuickLocation/Section/Home/HomeViewController.swift b/QuickLocation/Section/Home/HomeViewController.swift index eab8424..0909c91 100644 --- a/QuickLocation/Section/Home/HomeViewController.swift +++ b/QuickLocation/Section/Home/HomeViewController.swift @@ -46,6 +46,7 @@ class HomeViewController: BaseViewController { private var subscribedMemberIds: [String] = [] /// 底部成员面板是否显示 private var isMemberPanelShown = false + private var isSOSPlaying = false /// 记录每个成员最后一次收到位置的时间(MQTT userId → Date) private var lastUpdateTimes: [String: Date] = [:] /// 离线检测定时器 @@ -73,8 +74,6 @@ class HomeViewController: BaseViewController { setupHeading() reactiveAction() - requestUserConfig() - rootView.quickMessageView.tagListView.delegate = self // MQTT 位置上报 UIDevice.current.isBatteryMonitoringEnabled = true @@ -189,9 +188,26 @@ class HomeViewController: BaseViewController { // User Config刷新 NotificationCenter.default.rx.notification(.RefreshUserConfigNotification, object: nil) - .subscribe { [weak self] notification in - self?.requestUserConfig() - }.disposed(by: disposeBag) + .startWith(Notification(name: .RefreshUserConfigNotification)) + .do(onNext: { [weak self] _ in + 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) @@ -248,10 +264,8 @@ class HomeViewController: BaseViewController { } // 分享 - rootView.interactionView.onShare = { [weak self] in - guard let self = self, let member = self.rootView.interactionView.currentMember else { return } - let url = "https://smartdrive.zuom8.cn/jisu/share/user/\(member.id)" - SharePopView.show(shareURL: url) + rootView.interactionView.onShare = { + SharePopView.show() } } @@ -290,25 +304,6 @@ class HomeViewController: BaseViewController { // 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 func getUserIMToken() { DLToast.showLoading() @@ -466,8 +461,8 @@ class HomeViewController: BaseViewController { let newMembers = others + [me] members = newMembers currentUserMember = me - // 从 members 中过滤出在线成员用于地图标注 - let onlineMembers = newMembers.filter { $0.isOnline } + // 从 members 中过滤出在线成员用于地图标注(当前用户坐标无效,由 GPS 回调单独添加) + let onlineMembers = newMembers.filter { $0.isOnline && !$0.isCurrentUser } guard let mapView = rootView.mapView else { return } @@ -477,10 +472,33 @@ class HomeViewController: BaseViewController { let toRemove = existing.filter { !onlineIDs.contains($0.member.id) } mapView.removeAnnotations(toRemove) + if toRemove.contains(where: { $0 === currentUserAnnotation }) { + currentUserAnnotation = nil + } let toAdd = onlineMembers.filter { !existingIDs.contains($0.id) } let annotations = toAdd.map { MemberAnnotation(member: $0) } 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 } @@ -639,15 +657,21 @@ extension HomeViewController { } } case "sos": // 求助 - self.requestGroupInfo(isDefaultGroup: false) guard let userId = msg.data?.user_id, userId != AppContextManager.shared.userId else { return } - playSOSAlarm() - rootView.sosPopView.isHidden = false - rootView.sosPopView.play(toFrame: 30.0) { completed in - guard completed else { return } - self.rootView.sosPopView.stop() - self.rootView.sosPopView.isHidden = true + DispatchQueue.main.async { [weak self] in + guard let self = self, !self.isSOSPlaying else { return } + self.isSOSPlaying = true + self.playSOSAlarm() + self.rootView.sosPopView.isHidden = false + 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": // 圈子有成员加入 guard let gk = msg.data?.group_key, @@ -798,6 +822,22 @@ extension HomeViewController: MAMapViewDelegate { if let ann = currentUserAnnotation { 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 { @@ -835,13 +875,14 @@ extension HomeViewController { return } print("✅ SOS: playing alarm") - player.numberOfLoops = 0 + player.numberOfLoops = -1 player.volume = 1.0 player.play() self.sosPlayer = player DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in self?.sosPlayer?.stop() self?.sosPlayer = nil + self?.isSOSPlaying = false } } } diff --git a/QuickLocation/Section/Home/InteractionView.swift b/QuickLocation/Section/Home/InteractionView.swift index 92a927e..dfabac3 100644 --- a/QuickLocation/Section/Home/InteractionView.swift +++ b/QuickLocation/Section/Home/InteractionView.swift @@ -108,7 +108,10 @@ class InteractionView: UIView { let pages = (items.count + InteractionView.emojiPerPage - 1) / InteractionView.emojiPerPage self.emojiPageControl.numberOfPages = max(pages, 1) 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) } @@ -517,6 +520,11 @@ extension InteractionView: UICollectionViewDelegate { 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) { guard collectionView == emojiCollectionView else { return } (cell as? EmojiPanelCell)?.stopAnimation() @@ -528,29 +536,46 @@ final class EmojiPanelCell: UICollectionViewCell { private static var animationCache: [String: LottieAnimation] = [:] 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) { animationQueue.async { - if let cached = animationCache[name] { + Self.cacheLock.lock() + if let cached = Self.animationCache[name] { + Self.cacheLock.unlock() DispatchQueue.main.async { completion(cached) } return } - if loadingCompletions[name] != nil { - loadingCompletions[name]?.append(completion) + if Self.loadingCompletions[name] != nil { + Self.loadingCompletions[name]?.append(completion) + Self.cacheLock.unlock() return } - loadingCompletions[name] = [completion] + Self.loadingCompletions[name] = [completion] + Self.cacheLock.unlock() + let animation: LottieAnimation? - if let path = Bundle.main.path(forResource: name, ofType: "json") { + if let path = Self.emojiPathMap[name] { animation = LottieAnimation.filepath(path) } else { 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 { completions.forEach { $0(animation) } } @@ -558,7 +583,6 @@ final class EmojiPanelCell: UICollectionViewCell { } private var animationName: String? - private var shouldPlayAnimation = false private let lottieView: LottieAnimationView = { let v = LottieAnimationView() @@ -598,36 +622,28 @@ final class EmojiPanelCell: UICollectionViewCell { func configure(name: String) { animationName = name - shouldPlayAnimation = false lottieView.stop() - lottieView.animation = nil - lottieView.currentProgress = 0 Self.loadAnimation(for: name) { [weak self] animation in guard let self = self, self.animationName == name else { return } self.lottieView.animation = animation self.lottieView.currentProgress = 0 - if self.shouldPlayAnimation { - self.lottieView.play() - } + self.lottieView.play() } lockView.isHidden = AppContextManager.shared.vip > 1 } func playAnimation() { - shouldPlayAnimation = true lottieView.play() } func stopAnimation() { - shouldPlayAnimation = false lottieView.stop() } override func prepareForReuse() { super.prepareForReuse() animationName = nil - shouldPlayAnimation = false lottieView.stop() lottieView.animation = nil } diff --git a/QuickLocation/Section/Launch/LaunchViewController.swift b/QuickLocation/Section/Launch/LaunchViewController.swift index 131f6a2..e92bb5a 100644 --- a/QuickLocation/Section/Launch/LaunchViewController.swift +++ b/QuickLocation/Section/Launch/LaunchViewController.swift @@ -34,9 +34,7 @@ class LaunchViewController: BaseViewController { AppDelegate.shared.showMainViewController() }).disposed(by: disposeBag) - getUserConfig() - navigateAfterDelay() } @@ -44,24 +42,16 @@ class LaunchViewController: BaseViewController { Observable.just(()) .delay(.milliseconds(2500), scheduler: MainScheduler.instance) .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) } - - 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() { @@ -82,7 +72,6 @@ class LaunchViewController: BaseViewController { /// 获取用户IM Token func getUserIMToken() { - DLToast.showLoading() UserService.imToken().subscribe(onNext: { response in guard let data = response.data, let token = data["token"] as? String else { return } AppContextManager.shared.imToken = token diff --git a/QuickLocation/Section/Login/LoginView.swift b/QuickLocation/Section/Login/LoginView.swift index 76fcae9..4c80781 100644 --- a/QuickLocation/Section/Login/LoginView.swift +++ b/QuickLocation/Section/Login/LoginView.swift @@ -68,17 +68,18 @@ class LoginView: UIView { addSubview(bgMaskImage) addSubview(backBtn) addSubview(guestLoginButton) - addSubview(welcomeLabel) - addSubview(phoneNumberLabel) - addSubview(carrierLabel) - addSubview(otherPhoneView) + addSubview(inputContainerView) + inputContainerView.addSubview(welcomeLabel) + inputContainerView.addSubview(phoneNumberLabel) + inputContainerView.addSubview(carrierLabel) + inputContainerView.addSubview(otherPhoneView) otherPhoneView.addSubview(phoneInputView) otherPhoneView.addSubview(smsCodeInputView) - addSubview(loginButton) + inputContainerView.addSubview(loginButton) addSubview(appleLoginBtn) addSubview(wechatLoginBtn) addSubview(phoneLoginBtn) - + addSubview(agreementView) agreementView.addSubview(checkBox) agreementView.addSubview(agreementTV) @@ -98,8 +99,14 @@ class LoginView: UIView { .width(85) .height(29) + inputContainerView.layoutChain + .centerX() + .centerY(self, offset: -17) + .edgesHorzontal() + .height(306) + welcomeLabel.layoutChain - .top(202 - kNaviHeight) + .top(0) .centerX() .edgesHorzontal() @@ -161,7 +168,7 @@ class LoginView: UIView { agreementView.layoutChain .leftToView(loginButton) .rightToView(loginButton) - .bottom(55) + .bottom(kSafeBottomMargin + 20) .height(30) agreementLabel.layoutChain.edges() @@ -178,7 +185,7 @@ class LoginView: UIView { .top() phoneLoginBtn.layoutChain - .bottomToTopOfView(agreementView, offset: -81) + .bottomToTopOfView(agreementView, offset: -30) .centerX() .width(38) .heightToWidth(1) @@ -258,6 +265,13 @@ class LoginView: UIView { return label }() + // 输入区域容器 + lazy var inputContainerView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + // 其他手机号登录 lazy var otherPhoneView: UIView = { let view = UIView() @@ -394,7 +408,6 @@ class LoginView: UIView { textView.backgroundColor = .clear textView.isEditable = false textView.isScrollEnabled = false - textView.isSelectable = false textView.linkTextAttributes = [:] return textView diff --git a/QuickLocation/Section/Login/LoginViewController.swift b/QuickLocation/Section/Login/LoginViewController.swift index 2ce1409..e24de4e 100644 --- a/QuickLocation/Section/Login/LoginViewController.swift +++ b/QuickLocation/Section/Login/LoginViewController.swift @@ -9,6 +9,7 @@ import UIKit import SnapKit import RxSwift import RxCocoa +import AuthenticationServices #if !targetEnvironment(simulator) import GeYanSdk #endif @@ -39,14 +40,14 @@ class LoginViewController: BaseViewController { bindViewModel() reactiveAction() } - + private func reactiveAction() { rootView.smsCodeBtn.rx.controlEvent(.touchUpInside).subscribe { [weak self] _ in guard let self = self else { return } self.requestSmsCodeAPI() }.disposed(by: rootView.disposeBag) } - + // MARK: - Bindings private func bindViewModel() { rootView.checkBox.rx.tap @@ -100,17 +101,40 @@ class LoginViewController: BaseViewController { }) .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 .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) - viewModel.loginSuccess - .subscribe(onNext: { [weak self] in - - }) - .disposed(by: disposeBag) +// viewModel.loginSuccess +// .subscribe(onNext: { [weak self] in +// +// }) +// .disposed(by: disposeBag) } // MARK: - API 验证码 @@ -150,3 +174,40 @@ extension LoginViewController: UITextViewDelegate { 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 登录失败") + } +} diff --git a/QuickLocation/Section/Login/LoginViewModel.swift b/QuickLocation/Section/Login/LoginViewModel.swift index e23fe43..e808d8c 100644 --- a/QuickLocation/Section/Login/LoginViewModel.swift +++ b/QuickLocation/Section/Login/LoginViewModel.swift @@ -6,10 +6,43 @@ import Foundation import RxSwift import SwiftyUserDefaults +import AuthenticationServices #if !targetEnvironment(simulator) import GeYanSdk #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 { /// 验证码接口返回值 @@ -18,47 +51,78 @@ final class LoginViewModel: BaseViewModel { let oneClickLoginResult = PublishSubject() let loginSuccess = PublishSubject() - var gyAuthVM: GyAuthViewModel { + var onAppleLoginRequest: ((ASAuthorizationController) -> Void)? + + #if !targetEnvironment(simulator) + lazy var gyAuthVM: GyAuthViewModel = { let viewModel = GyAuthViewModel() viewModel.statusBarStyle = .default viewModel.userInterfaceStyle = 0 - viewModel.pullAuthVCStyle = .push - viewModel.viewLifeCycleBlock = { (str, bool) in - - } + viewModel.pullAuthVCStyle = .modal viewModel.clickAuthButtonBlock = { return true } return viewModel - } + }() + #endif /// 登录 func loginAction(type: String, data: [String : Any]) { - DLToast.showLoading() - 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) + LoginSessionHandler.login(type: type, data: data) } func performGuestLogin() { 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() { - // 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() { - // 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) } } diff --git a/QuickLocation/Section/Login/OneTapLoginView.swift b/QuickLocation/Section/Login/OneTapLoginView.swift index 0d8bc1f..a90740f 100644 --- a/QuickLocation/Section/Login/OneTapLoginView.swift +++ b/QuickLocation/Section/Login/OneTapLoginView.swift @@ -19,7 +19,9 @@ class OneTapLoginView: UIView { private func setupRx() { backBtn.rx.tap.subscribe(onNext: { _ in - AppRouter.shared.popOrDismiss() + #if !targetEnvironment(simulator) + GeYanSdk.closeAuthVC(true, completion: nil) + #endif }).disposed(by: disposeBag) checkBox.rx.tap.subscribe(onNext: { [weak self] in @@ -55,11 +57,12 @@ class OneTapLoginView: UIView { private func setupUI() { addSubview(bgMaskImage) addSubview(backBtn) - addSubview(welcomeLabel) - addSubview(phoneNumberLabel) - addSubview(carrierLabel) - addSubview(loginButton) - + addSubview(inputContainerView) + inputContainerView.addSubview(welcomeLabel) + inputContainerView.addSubview(phoneNumberLabel) + inputContainerView.addSubview(carrierLabel) + inputContainerView.addSubview(loginButton) + addSubview(agreementView) agreementView.addSubview(checkBox) agreementView.addSubview(agreementTV) @@ -73,8 +76,14 @@ class OneTapLoginView: UIView { .width(24) .height(24) + inputContainerView.layoutChain + .centerX() + .centerY() + .edgesHorzontal() + .height(301) + welcomeLabel.layoutChain - .top(202 - kNaviHeight) + .top(0) .centerX() .edgesHorzontal() @@ -130,6 +139,12 @@ class OneTapLoginView: UIView { } // MARK: - UI Components + lazy var inputContainerView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + lazy var bgMaskImage: UIImageView = { let iv = UIImageView() iv.image = UIImage(named: "Login/bg") @@ -207,7 +222,6 @@ class OneTapLoginView: UIView { textView.backgroundColor = .clear textView.isEditable = false textView.isScrollEnabled = false - textView.isSelectable = false textView.linkTextAttributes = [:] return textView diff --git a/QuickLocation/Section/Mine/Account/AccountVC.swift b/QuickLocation/Section/Mine/Account/AccountVC.swift index 77d2e6f..154faf4 100644 --- a/QuickLocation/Section/Mine/Account/AccountVC.swift +++ b/QuickLocation/Section/Mine/Account/AccountVC.swift @@ -59,6 +59,7 @@ class AccountVC: BaseViewController { DLToast.showLoading() UserService.logout().subscribe(onNext: { response in DLToast.dismiss() + NotificationCenter.default.post(name: .invalidatePopupQueue, object: nil) AppContextManager.shared.deleteAccount() GroupIMService.shared.logout() AppDelegate.shared.showMainViewController() diff --git a/QuickLocation/Section/Mine/MineView.swift b/QuickLocation/Section/Mine/MineView.swift index 7637134..69f3b19 100644 --- a/QuickLocation/Section/Mine/MineView.swift +++ b/QuickLocation/Section/Mine/MineView.swift @@ -188,6 +188,10 @@ final class MineView: UIView { // MARK: - Callbacks var onMenuTap: ((Int) -> Void)? + // MARK: - Dynamic Height + private var settingsCardHeightConstraint: NSLayoutConstraint? + private var contentSizeObservation: NSKeyValueObservation? + // MARK: - Init override init(frame: CGRect) { super.init(frame: frame) @@ -289,14 +293,42 @@ final class MineView: UIView { .topToBottomOfView(profileBgImageView) .left().right().bottom() - // Settings Card (fixed height, not stretched) + // Settings Card (dynamic height, scrolls when exceeds area above tabbar) settingsCardView.layoutChain .topToBottomOfView(vipCardView, offset: 20) .left(16).right(16) - .height(312) + + settingsCardHeightConstraint = settingsCardView.heightAnchor.constraint(equalToConstant: 312) + settingsCardHeightConstraint?.isActive = true settingsTableView.layoutChain .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 + } } } diff --git a/QuickLocation/Section/PopupWindow/PopupQueueManager.swift b/QuickLocation/Section/PopupWindow/PopupQueueManager.swift index 4f23b2c..86dfffb 100644 --- a/QuickLocation/Section/PopupWindow/PopupQueueManager.swift +++ b/QuickLocation/Section/PopupWindow/PopupQueueManager.swift @@ -11,7 +11,28 @@ class PopupQueueManager { private weak var sourceVC: UIViewController? private var currentStep = 0 + private var stepGeneration = 0 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) { sourceVC = vc @@ -19,6 +40,25 @@ class PopupQueueManager { 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() { switch currentStep { case 0: @@ -45,12 +85,14 @@ class PopupQueueManager { skipToNext() return } + let gen = stepGeneration startCountdown(seconds: timeConfig.pay_pop_time) { [weak self] in + guard let self = self, self.currentStep == 0, self.stepGeneration == gen else { return } let vc = PromotionalActivitiesVC() vc.onDismiss = { [weak self] in self?.skipToNext() } - self?.push(vc) + self.push(vc) } } @@ -66,9 +108,11 @@ class PopupQueueManager { skipToNext() 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) { - self?.skipToNext() + self.skipToNext() } } } @@ -82,13 +126,17 @@ class PopupQueueManager { } 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) { // done } } } 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) { // done } @@ -99,6 +147,7 @@ class PopupQueueManager { // MARK: - Helpers private func skipToNext() { + stepGeneration += 1 currentStep += 1 executeNextStep() } @@ -122,7 +171,11 @@ class PopupQueueManager { private func push(_ vc: UIViewController) { 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? { diff --git a/QuickLocation/Section/PopupWindow/PopupWindow.swift b/QuickLocation/Section/PopupWindow/PopupWindow.swift index 7495636..dc2722d 100644 --- a/QuickLocation/Section/PopupWindow/PopupWindow.swift +++ b/QuickLocation/Section/PopupWindow/PopupWindow.swift @@ -151,8 +151,12 @@ class PopupWindow: UIView { registerBtn.rx.tap .subscribe(onNext: { [weak self] _ in + NotificationCenter.default.post(name: .pausePopupQueue, object: nil) 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) @@ -164,8 +168,12 @@ class PopupWindow: UIView { register2Btn.rx.tap .subscribe(onNext: { [weak self] _ in + NotificationCenter.default.post(name: .pausePopupQueue, object: nil) self?.slideOutRegister2Popup() - AppRouter.push(Route.login) + let topVC = UIViewController.currentViewController() + if !(topVC is LoginViewController) { + AppRouter.push(Route.login) + } }) .disposed(by: disposeBag) @@ -178,6 +186,10 @@ class PopupWindow: UIView { vip1UnlockBtn.rx.tap .subscribe(onNext: { [weak self] _ in self?.slideOutUnlockVip1Popup() + let topVC = UIViewController.currentViewController() + if !(topVC is VipRechargeVC) { + AppRouter.push(Route.vipRecharge) + } }) .disposed(by: disposeBag) @@ -190,6 +202,10 @@ class PopupWindow: UIView { unlockVip2Btn.rx.tap .subscribe(onNext: { [weak self] _ in self?.slideOutUnlockVip2Popup() + let topVC = UIViewController.currentViewController() + if !(topVC is VipRechargeVC) { + AppRouter.push(Route.vipRecharge) + } }) .disposed(by: disposeBag) @@ -202,6 +218,10 @@ class PopupWindow: UIView { unlockVip3Btn.rx.tap .subscribe(onNext: { [weak self] _ in self?.slideOutUnlockVip3Popup() + let topVC = UIViewController.currentViewController() + if !(topVC is VipRechargeVC) { + AppRouter.push(Route.vipRecharge) + } }) .disposed(by: disposeBag) @@ -214,6 +234,10 @@ class PopupWindow: UIView { unlockVip4Btn.rx.tap .subscribe(onNext: { [weak self] _ in self?.slideOutUnlockVip4Popup() + let topVC = UIViewController.currentViewController() + if !(topVC is VipRechargeVC) { + AppRouter.push(Route.vipRecharge) + } }) .disposed(by: disposeBag) } diff --git a/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift b/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift index ed672b2..e0c734e 100644 --- a/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift +++ b/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift @@ -49,7 +49,7 @@ class PromotionalActivitiesVC: BaseViewController { override func viewDidLoad() { super.viewDidLoad() - + fd_interactivePopDisabled = true setupTableView() requestRechargeInfo() @@ -103,6 +103,11 @@ class PromotionalActivitiesVC: BaseViewController { } } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + navigationController?.setNavigationBarHidden(true, animated: false) + } + // MARK: - API private func requestRechargeInfo() { DLToast.showLoading() @@ -126,29 +131,29 @@ class PromotionalActivitiesVC: BaseViewController { return } - if timeConfig.pay_pop_type == 3 { - IAPManager.shared.purchase(goodsId: selectedGoodsId) { [weak self] success, msg in - guard let self = self else { return } - if success { - self.showPayResultPop(showCloseBtn: false, - status: true, - title: "支付成功", - message: "恭喜您!成功开通!", - confirmText: "确定", confirmBlock: { - AppRouter.shared.popOrDismiss() - }, cancelBlock: { }) - } else { - self.showPayResultPop(status: false, - title: "支付失败", - message: msg ?? "很抱歉,请您重新支付!", - confirmText: "重新支付", confirmBlock: { }, - cancelText: "取消", cancelBlock: { - AppRouter.shared.popOrDismiss() - }) - } - } - return - } +// if timeConfig.pay_pop_type == 3 { +// IAPManager.shared.purchase(goodsId: selectedGoodsId) { [weak self] success, msg in +// guard let self = self else { return } +// if success { +// self.showPayResultPop(showCloseBtn: false, +// status: true, +// title: "支付成功", +// message: "恭喜您!成功开通!", +// confirmText: "确定", confirmBlock: { +// AppRouter.shared.popOrDismiss() +// }, cancelBlock: { }) +// } else { +// self.showPayResultPop(status: false, +// title: "支付失败", +// message: msg ?? "很抱歉,请您重新支付!", +// confirmText: "重新支付", confirmBlock: { }, +// cancelText: "取消", cancelBlock: { +// AppRouter.shared.popOrDismiss() +// }) +// } +// } +// return +// } let payType = timeConfig.pay_pop_type == 1 ? "alipay" : "weixin" dl.showLoading() diff --git a/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift b/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift index 0be5959..aef7aa7 100644 --- a/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift +++ b/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift @@ -533,7 +533,7 @@ class PromotionalRecommendCell: UITableViewCell { tipsTitleLab.layoutChain .centerX() - .top(5) + .top(7) priceLab.layoutChain .leftToRightOfView(unitLab, offset: 0) diff --git a/QuickLocation/Section/Schedule/ScheduleVC.swift b/QuickLocation/Section/Schedule/ScheduleVC.swift index 867f6fe..b74cb08 100644 --- a/QuickLocation/Section/Schedule/ScheduleVC.swift +++ b/QuickLocation/Section/Schedule/ScheduleVC.swift @@ -55,6 +55,16 @@ class ScheduleVC: BaseViewController { self.rootView.tableView.refresh(status: status, isEmpty: isEmpty) }).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) .subscribe(viewModel.viewedCellAction.inputs) .disposed(by: disposeBag) diff --git a/QuickLocation/Section/Schedule/ScheduleView.swift b/QuickLocation/Section/Schedule/ScheduleView.swift index 27ad4c7..4134c20 100644 --- a/QuickLocation/Section/Schedule/ScheduleView.swift +++ b/QuickLocation/Section/Schedule/ScheduleView.swift @@ -128,6 +128,12 @@ class ScheduleView: UIView { .edgesHorzontal() .height(80) .bottom(15) + + // 暂无访客 + view.addSubview(emptyVisitorsLabel) + emptyVisitorsLabel.layoutChain + .centerY(collectionView) + .centerX() return view }() @@ -145,6 +151,16 @@ class ScheduleView: UIView { 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 = { let view = UIView() @@ -213,9 +229,20 @@ class ScheduleView: UIView { tableView.showsVerticalScrollIndicator = false tableView.register(ScheduleListPopCell.self) tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0) + tableView.backgroundView = emptyScheduleLabel 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 = { let btn = UIButton() btn.setImage(UIImage(named: "Schedule/create"), for: .normal) diff --git a/QuickLocation/Section/Share/SharePopView.swift b/QuickLocation/Section/Share/SharePopView.swift index bd770b8..a6916f7 100644 --- a/QuickLocation/Section/Share/SharePopView.swift +++ b/QuickLocation/Section/Share/SharePopView.swift @@ -6,22 +6,22 @@ // import UIKit +import Kingfisher class SharePopView: UIView { private static let shared = SharePopView(frame: CGRect(origin: .zero, size: kScreenSize)) private var shareURL: String = "" - + private let contentHeight: CGFloat = 191 + kSafeBottomMargin - static func show(shareURL: String) { + static func show() { guard let superView = kKeyWindow else { return } if SharePopView.shared.superview != nil { SharePopView.shared.dismiss(animated: false) } - SharePopView.shared.shareURL = shareURL SharePopView.shared.bgView.alpha = 0 superView.addSubview(SharePopView.shared) superView.bringSubviewToFront(SharePopView.shared) @@ -65,19 +65,52 @@ class SharePopView: UIView { } private func shareToWeChat(scene: Int32) { - let message = WXMediaMessage() - message.title = kAppName - let webpage = WXWebpageObject() - webpage.webpageUrl = shareURL - message.mediaObject = webpage + guard let config = AppContextManager.shared.systemConfig, + let shareConfig = config.shareConfig else { return } - let req = SendMessageToWXReq() - req.bText = false - req.message = message - req.scene = scene - WXApi.send(req) + DLToast.showLoading() - 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 diff --git a/QuickLocation/Section/VipRecharge/VipRechargeVC.swift b/QuickLocation/Section/VipRecharge/VipRechargeVC.swift index 9081cb0..f83c9dc 100644 --- a/QuickLocation/Section/VipRecharge/VipRechargeVC.swift +++ b/QuickLocation/Section/VipRecharge/VipRechargeVC.swift @@ -29,6 +29,11 @@ class VipRechargeVC: BaseViewController { requestRechargeInfo() } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + navigationController?.setNavigationBarHidden(true, animated: false) + } + private func bindViewModel() { viewModel.output.sectionedItems .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 payType = types[rootView.selectedPayTypeTag] - if payType == "apple" { - IAPManager.shared.purchase(goodsId: viewModel.goodsId) { [weak self] success, msg in - guard let self = self else { return } - if success { - self.showPayResultPop(showCloseBtn: false, - status: true, - title: "支付成功", - message: "恭喜您!成功开通\(self.viewModel.goodsName)", - confirmText: "确定", confirmBlock: { - AppRouter.shared.popOrDismiss() - }, cancelBlock: { }) - } else { - self.showPayResultPop(status: false, - title: "支付失败", - message: msg ?? "很抱歉,请您重新支付!", - confirmText: "重新支付", confirmBlock: { }, - cancelText: "取消", cancelBlock: { - AppRouter.shared.popOrDismiss() - }) - } - } - return - } +// if payType == "apple" { +// IAPManager.shared.purchase(goodsId: viewModel.goodsId) { [weak self] success, msg in +// guard let self = self else { return } +// if success { +// self.showPayResultPop(showCloseBtn: false, +// status: true, +// title: "支付成功", +// message: "恭喜您!成功开通\(self.viewModel.goodsName)", +// confirmText: "确定", confirmBlock: { +// AppRouter.shared.popOrDismiss() +// }, cancelBlock: { }) +// } else { +// self.showPayResultPop(status: false, +// title: "支付失败", +// message: msg ?? "很抱歉,请您重新支付!", +// confirmText: "重新支付", confirmBlock: { }, +// cancelText: "取消", cancelBlock: { +// AppRouter.shared.popOrDismiss() +// }) +// } +// } +// return +// } DLToast.showLoading() OrderService.orderPayParams(goodsId: viewModel.goodsId, diff --git a/QuickLocation/Section/VipRecharge/VipRechargeView.swift b/QuickLocation/Section/VipRecharge/VipRechargeView.swift index ebc3ae5..e2209a3 100644 --- a/QuickLocation/Section/VipRecharge/VipRechargeView.swift +++ b/QuickLocation/Section/VipRecharge/VipRechargeView.swift @@ -99,7 +99,7 @@ class VipRechargeView: UIView { .heightToWidth(267/375) vipRightsDetailView.layoutChain - .top(36) + .top(36 + kStatusBarHeight) .right() .height(24) @@ -562,7 +562,7 @@ class VipRechargeView: UIView { let payTypeMap: [(String, String)] = types.compactMap { if $0 == "weixin" { return ("wechat", "微信支付") } if $0 == "alipay" { return ("alipay", "支付宝支付") } - if $0 == "apple" { return ("apple", "苹果支付") } +// if $0 == "apple" { return ("apple", "苹果支付") } return nil } guard !payTypeMap.isEmpty else { return }