parent
d88176a8bc
commit
15e4c3df17
|
|
@ -2166,7 +2166,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = cn.zuomeng.jisuloca;
|
PRODUCT_BUNDLE_IDENTIFIER = cn.zuomeng.jisuloca;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|
@ -2218,7 +2218,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = cn.zuomeng.jisuloca;
|
PRODUCT_BUNDLE_IDENTIFIER = cn.zuomeng.jisuloca;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||||
|
|
||||||
|
UIFont.loadFontName("douyu.otf")
|
||||||
|
UIFont.loadFontName("zihunbiantaoti-r.ttf")
|
||||||
setupLocation()
|
setupLocation()
|
||||||
ApiManager.shared.setup()
|
ApiManager.shared.setup()
|
||||||
AppRouter.shared.setup()
|
AppRouter.shared.setup()
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ public extension UIFont {
|
||||||
|
|
||||||
/// 加载本地自定义字体
|
/// 加载本地自定义字体
|
||||||
/// - Parameter name: 字体文件名称
|
/// - Parameter name: 字体文件名称
|
||||||
private static func loadFontName(_ fileName: String) -> String? {
|
public static func loadFontName(_ fileName: String) -> String? {
|
||||||
// 字体文件资源路径
|
// 字体文件资源路径
|
||||||
guard let fontURL = Bundle.main.url(forResource: fileName, withExtension: nil),
|
guard let fontURL = Bundle.main.url(forResource: fileName, withExtension: nil),
|
||||||
let data = try? Data(contentsOf: fontURL),
|
let data = try? Data(contentsOf: fontURL),
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>NSContactsUsageDescription</key>
|
||||||
|
<string>需要访问您的通讯录以导入联系人号码</string>
|
||||||
<key>UIAppFonts</key>
|
<key>UIAppFonts</key>
|
||||||
<array>
|
<array>
|
||||||
<string>douyu.otf</string>
|
<string>douyu.otf</string>
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ struct SystemConfigModel: Mappable {
|
||||||
var timeConfig: PopupWindowTimeModel?
|
var timeConfig: PopupWindowTimeModel?
|
||||||
/// 微信分享配置
|
/// 微信分享配置
|
||||||
var shareConfig: WxShareConfigModel?
|
var shareConfig: WxShareConfigModel?
|
||||||
|
/// 版本更新
|
||||||
|
var versionUpgradeModel: VersionUpgradeModel?
|
||||||
|
|
||||||
/// 聊天界面的警告说明
|
/// 聊天界面的警告说明
|
||||||
var chatWarning: String = ""
|
var chatWarning: String = ""
|
||||||
|
|
@ -100,6 +102,7 @@ struct SystemConfigModel: Mappable {
|
||||||
shareConfig <- map["client.weixin.share", nested: false]
|
shareConfig <- map["client.weixin.share", nested: false]
|
||||||
guideDisplay <- map["client.guide_display", nested: false]
|
guideDisplay <- map["client.guide_display", nested: false]
|
||||||
globalDisplay <- map["client.global_display", nested: false]
|
globalDisplay <- map["client.global_display", nested: false]
|
||||||
|
versionUpgradeModel <- map["client.version.upgrade", nested: false]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,3 +176,32 @@ struct WxShareConfigModel: Mappable {
|
||||||
image <- map["image"]
|
image <- map["image"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct VersionUpgradeModel: Mappable {
|
||||||
|
/// 下载地址
|
||||||
|
var url: String = ""
|
||||||
|
|
||||||
|
/// 标题
|
||||||
|
var title: String = ""
|
||||||
|
|
||||||
|
/// 内容
|
||||||
|
var description: String = ""
|
||||||
|
|
||||||
|
/// 版本
|
||||||
|
var version: String = ""
|
||||||
|
|
||||||
|
/// 强制更新
|
||||||
|
var force: Bool = false
|
||||||
|
|
||||||
|
init?(map: Map) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
url <- map["url"]
|
||||||
|
title <- map["title"]
|
||||||
|
description <- map["description"]
|
||||||
|
force <- map["force"]
|
||||||
|
version <- map["version"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ extension ApiManager {
|
||||||
case GatewayStatusCode.userLoginExpair.rawValue: // token失效
|
case GatewayStatusCode.userLoginExpair.rawValue: // token失效
|
||||||
handleTokenExpired(msg: message)
|
handleTokenExpired(msg: message)
|
||||||
case GatewayStatusCode.failure.rawValue, GatewayStatusCode.noAuthority.rawValue: // 退出当前界面
|
case GatewayStatusCode.failure.rawValue, GatewayStatusCode.noAuthority.rawValue: // 退出当前界面
|
||||||
handlePopView(message)
|
handlePopView(message, handle)
|
||||||
default:
|
default:
|
||||||
// 接口没返回code的时候取success字段
|
// 接口没返回code的时候取success字段
|
||||||
if success != nil {
|
if success != nil {
|
||||||
|
|
@ -171,8 +171,9 @@ extension ApiManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 业务中断(需退出当前页面)
|
// MARK: - 业务中断(需退出当前页面)
|
||||||
private func handlePopView(_ msg: String?) {
|
private func handlePopView(_ msg: String?, _ handle: Bool) {
|
||||||
MainAsync {
|
MainAsync {
|
||||||
|
guard handle else { return }
|
||||||
DLToast.showInfo(text: msg ?? "Error...") {
|
DLToast.showInfo(text: msg ?? "Error...") {
|
||||||
guard let viewController = UIViewController.currentViewController() else { return }
|
guard let viewController = UIViewController.currentViewController() else { return }
|
||||||
viewController.navigationController?.popViewController(animated: true)
|
viewController.navigationController?.popViewController(animated: true)
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,8 @@ final class AppCacheManager {
|
||||||
let fullPath = path + "/" + subPath
|
let fullPath = path + "/" + subPath
|
||||||
var isDir: ObjCBool = false
|
var isDir: ObjCBool = false
|
||||||
if manager.fileExists(atPath: fullPath, isDirectory: &isDir) {
|
if manager.fileExists(atPath: fullPath, isDirectory: &isDir) {
|
||||||
if isDir.boolValue {
|
if isDir.boolValue { continue }
|
||||||
totalSize += folderSize(at: fullPath)
|
totalSize += (try? manager.attributesOfItem(atPath: fullPath))?[.size] as? Int64 ?? 0
|
||||||
} else {
|
|
||||||
// 读取文件属性,失败则跳过
|
|
||||||
if let attrs = try? manager.attributesOfItem(atPath: fullPath) {
|
|
||||||
totalSize += attrs[.size] as? Int64 ?? 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return totalSize
|
return totalSize
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import UserNotifications
|
||||||
import AdSupport
|
import AdSupport
|
||||||
import AppTrackingTransparency
|
import AppTrackingTransparency
|
||||||
import URLNavigator
|
import URLNavigator
|
||||||
|
import Contacts
|
||||||
|
|
||||||
|
|
||||||
// MARK: - AuthorizeType
|
// MARK: - AuthorizeType
|
||||||
|
|
@ -112,6 +113,8 @@ public protocol AuthorizeProtocol {
|
||||||
return AuthorizeMicrophone()
|
return AuthorizeMicrophone()
|
||||||
case .tracking:
|
case .tracking:
|
||||||
return AuthorizeTracking()
|
return AuthorizeTracking()
|
||||||
|
case .contacts:
|
||||||
|
return AuthorizeContacts()
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -402,4 +405,26 @@ class AuthorizeTracking: NSObject, AuthorizeProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AuthorizeContacts
|
||||||
|
private class AuthorizeContacts: NSObject, AuthorizeProtocol {
|
||||||
|
func authorizeStatus() -> AuthorizeStatus {
|
||||||
|
switch CNContactStore.authorizationStatus(for: .contacts) {
|
||||||
|
case .restricted:
|
||||||
|
return .restricted
|
||||||
|
case .denied:
|
||||||
|
return .denied
|
||||||
|
case .authorized:
|
||||||
|
return .authorized
|
||||||
|
default:
|
||||||
|
return .notDetermined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorize(_ completion: ((AuthorizeStatus) -> Void)?) {
|
||||||
|
CNContactStore().requestAccess(for: .contacts) { _, _ in
|
||||||
|
DispatchQueue.main.async { completion?(self.authorizeStatus()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ class ItineraryTraceVC: BaseViewController {
|
||||||
private let memberModel: GroupMemberModel
|
private let memberModel: GroupMemberModel
|
||||||
private var routeOverlays: [MAPolyline] = []
|
private var routeOverlays: [MAPolyline] = []
|
||||||
private let routeSearch = AMapSearchAPI()
|
private let routeSearch = AMapSearchAPI()
|
||||||
private var drivingSegmentIndexes: [ObjectIdentifier: Int] = [:]
|
|
||||||
private var playbackSegments: [[CLLocationCoordinate2D]?] = []
|
private var playbackSegments: [[CLLocationCoordinate2D]?] = []
|
||||||
private var playbackPath: [(coordinate: CLLocationCoordinate2D, distance: Double)] = []
|
private var playbackPath: [(coordinate: CLLocationCoordinate2D, distance: Double)] = []
|
||||||
private var playbackTotalDistance: Double = 0
|
private var playbackTotalDistance: Double = 0
|
||||||
|
|
@ -41,10 +40,6 @@ class ItineraryTraceVC: BaseViewController {
|
||||||
private var playbackTargetCoordinate: CLLocationCoordinate2D?
|
private var playbackTargetCoordinate: CLLocationCoordinate2D?
|
||||||
|
|
||||||
private let minPointDistance: CLLocationDistance = 10
|
private let minPointDistance: CLLocationDistance = 10
|
||||||
private let directSegmentDistance: CLLocationDistance = 80
|
|
||||||
private let maxDrivingSegmentDistance: CLLocationDistance = 1500
|
|
||||||
private let maxDrivingSegmentTimeGap: Int64 = 5 * 60 * 1000
|
|
||||||
private let maxDrivingSegmentRequests = 40
|
|
||||||
private let eventCalloutTag = 91301
|
private let eventCalloutTag = 91301
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
|
|
@ -146,32 +141,38 @@ class ItineraryTraceVC: BaseViewController {
|
||||||
let points = cleanedTracePoints(model.trajectory_path)
|
let points = cleanedTracePoints(model.trajectory_path)
|
||||||
guard points.count >= 2 else { print("[trace] insufficient points: \(points.count)"); return }
|
guard points.count >= 2 else { print("[trace] insufficient points: \(points.count)"); return }
|
||||||
|
|
||||||
playbackSegments = Array(repeating: nil, count: points.count - 1)
|
playbackSegments = []
|
||||||
|
|
||||||
var drivingRequestCount = 0
|
var sampled: [(timestamp: Int64, coordinate: CLLocationCoordinate2D)] = []
|
||||||
for i in 0..<(points.count - 1) {
|
for pt in points {
|
||||||
let from = points[i]
|
if let last = sampled.last {
|
||||||
let to = points[i + 1]
|
if pt.timestamp - last.timestamp >= 5_000 {
|
||||||
let fallback = [from.coordinate, to.coordinate]
|
sampled.append(pt)
|
||||||
let distance = from.coordinate.distance(to: to.coordinate)
|
}
|
||||||
let timeGap = abs(to.timestamp - from.timestamp)
|
|
||||||
|
|
||||||
if distance < directSegmentDistance {
|
|
||||||
addPolyline(fallback)
|
|
||||||
playbackSegments[i] = fallback
|
|
||||||
} else if distance <= maxDrivingSegmentDistance,
|
|
||||||
timeGap <= maxDrivingSegmentTimeGap,
|
|
||||||
drivingRequestCount < maxDrivingSegmentRequests {
|
|
||||||
requestDrivingSegment(from: from.coordinate, to: to.coordinate, segmentIndex: i)
|
|
||||||
drivingRequestCount += 1
|
|
||||||
} else {
|
} else {
|
||||||
// 断档或距离异常的轨迹段不绘制,避免把事件点/异常点直接连到终点。
|
sampled.append(pt)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let coords = sampled.map { $0.coordinate }
|
||||||
|
addPolyline(coords)
|
||||||
|
playbackSegments = [coords]
|
||||||
buildPlaybackPath()
|
buildPlaybackPath()
|
||||||
updatePlaybackAnnotation(progress: playbackProgress)
|
updatePlaybackAnnotation(progress: playbackProgress)
|
||||||
print("[trace] cleaned=\(points.count) drivingRequests=\(drivingRequestCount)")
|
|
||||||
|
// let middle = sampled.dropFirst().dropLast().prefix(16)
|
||||||
|
// let waypoints = middle.map { $0.coordinate }
|
||||||
|
//
|
||||||
|
// let request = AMapDrivingRouteSearchRequest()
|
||||||
|
// request.origin = AMapGeoPoint.location(withLatitude: CGFloat(points.first!.coordinate.latitude), longitude: CGFloat(points.first!.coordinate.longitude))
|
||||||
|
// request.destination = AMapGeoPoint.location(withLatitude: CGFloat(points.last!.coordinate.latitude), longitude: CGFloat(points.last!.coordinate.longitude))
|
||||||
|
// request.strategy = 0
|
||||||
|
// if !waypoints.isEmpty {
|
||||||
|
// request.waypoints = waypoints.map { AMapGeoPoint.location(withLatitude: CGFloat($0.latitude), longitude: CGFloat($0.longitude)) }
|
||||||
|
// }
|
||||||
|
// routeSearch?.aMapDrivingRouteSearch(request)
|
||||||
|
|
||||||
|
print("[trace] cleaned=\(points.count) sampled=\(sampled.count)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildPlaybackPath() {
|
private func buildPlaybackPath() {
|
||||||
|
|
@ -198,7 +199,6 @@ class ItineraryTraceVC: BaseViewController {
|
||||||
rootView.mapView.remove(overlay)
|
rootView.mapView.remove(overlay)
|
||||||
}
|
}
|
||||||
routeOverlays.removeAll()
|
routeOverlays.removeAll()
|
||||||
drivingSegmentIndexes.removeAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func cleanedTracePoints(_ points: [TrackPoint]) -> [(timestamp: Int64, coordinate: CLLocationCoordinate2D)] {
|
private func cleanedTracePoints(_ points: [TrackPoint]) -> [(timestamp: Int64, coordinate: CLLocationCoordinate2D)] {
|
||||||
|
|
@ -222,15 +222,6 @@ class ItineraryTraceVC: BaseViewController {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestDrivingSegment(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D, segmentIndex: Int) {
|
|
||||||
let request = AMapDrivingRouteSearchRequest()
|
|
||||||
request.origin = AMapGeoPoint.location(withLatitude: CGFloat(from.latitude), longitude: CGFloat(from.longitude))
|
|
||||||
request.destination = AMapGeoPoint.location(withLatitude: CGFloat(to.latitude), longitude: CGFloat(to.longitude))
|
|
||||||
request.strategy = 0
|
|
||||||
drivingSegmentIndexes[ObjectIdentifier(request)] = segmentIndex
|
|
||||||
routeSearch?.aMapDrivingRouteSearch(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupPlaybackControls() {
|
private func setupPlaybackControls() {
|
||||||
rootView.playBtn.addTarget(self, action: #selector(playButtonTapped), for: .touchUpInside)
|
rootView.playBtn.addTarget(self, action: #selector(playButtonTapped), for: .touchUpInside)
|
||||||
rootView.progressSlider.addTarget(self, action: #selector(progressSliderTouchDown), for: .touchDown)
|
rootView.progressSlider.addTarget(self, action: #selector(progressSliderTouchDown), for: .touchDown)
|
||||||
|
|
@ -567,29 +558,18 @@ extension ItineraryTraceVC: MAMapViewDelegate {
|
||||||
// MARK: - AMapSearchDelegate
|
// MARK: - AMapSearchDelegate
|
||||||
extension ItineraryTraceVC: AMapSearchDelegate {
|
extension ItineraryTraceVC: AMapSearchDelegate {
|
||||||
func onRouteSearchDone(_ request: AMapRouteSearchBaseRequest!, response: AMapRouteSearchResponse!) {
|
func onRouteSearchDone(_ request: AMapRouteSearchBaseRequest!, response: AMapRouteSearchResponse!) {
|
||||||
let requestId = ObjectIdentifier(request)
|
guard let path = response.route?.paths?.first as? AMapPath else { return }
|
||||||
let segmentIndex = drivingSegmentIndexes.removeValue(forKey: requestId)
|
|
||||||
|
|
||||||
guard let path = response.route?.paths?.first as? AMapPath else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let coords = coordinates(from: path)
|
let coords = coordinates(from: path)
|
||||||
if coords.count > 1 {
|
guard coords.count > 1 else { return }
|
||||||
addPolyline(coords)
|
|
||||||
if let segmentIndex, playbackSegments.indices.contains(segmentIndex) {
|
addPolyline(coords)
|
||||||
playbackSegments[segmentIndex] = coords
|
playbackSegments = [coords]
|
||||||
buildPlaybackPath()
|
buildPlaybackPath()
|
||||||
updatePlaybackAnnotation(progress: playbackProgress)
|
updatePlaybackAnnotation(progress: playbackProgress)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func aMapSearchRequest(_ request: Any!, didFailWithError error: Error!) {
|
func aMapSearchRequest(_ request: Any!, didFailWithError error: Error!) {
|
||||||
if let request = request as? AMapRouteSearchBaseRequest {
|
|
||||||
let requestId = ObjectIdentifier(request)
|
|
||||||
drivingSegmentIndexes.removeValue(forKey: requestId)
|
|
||||||
}
|
|
||||||
print("Trace route search error: \(error.localizedDescription)")
|
print("Trace route search error: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,7 @@ class GroupView: UIView {
|
||||||
lazy var scanBtn: UIButton = {
|
lazy var scanBtn: UIButton = {
|
||||||
let btn = UIButton(type: .custom)
|
let btn = UIButton(type: .custom)
|
||||||
btn.setImage(UIImage(named: "Group/scan"), for: .normal)
|
btn.setImage(UIImage(named: "Group/scan"), for: .normal)
|
||||||
|
btn.isHidden = true
|
||||||
return btn
|
return btn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,14 @@ class SearchLocationResultVC: BaseViewController {
|
||||||
GroupChooseView.show(groupModel: model) { _ in }
|
GroupChooseView.show(groupModel: model) { _ in }
|
||||||
}
|
}
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
rootView.inviteBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
guard let model = self.groupModel else {
|
||||||
|
self.requestGroupInfo(showPicker: true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
GroupChooseView.show(groupModel: model) { _ in }
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleCode() {
|
private func handleCode() {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ class SearchLocationVC: BaseViewController {
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
fd_interactivePopDisabled = true
|
||||||
|
|
||||||
searchBtn.rx.tap.subscribe(onNext: { _ in
|
searchBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
guard let phone = self.rootView.phoneInputTF.text, phone.isPhoneNumber else {
|
guard let phone = self.rootView.phoneInputTF.text, phone.isPhoneNumber else {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ import RxSwift
|
||||||
import RxCocoa
|
import RxCocoa
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import Lottie
|
import Lottie
|
||||||
|
import Contacts
|
||||||
|
import ContactsUI
|
||||||
|
import URLNavigator
|
||||||
|
|
||||||
class SearchLocationView: UIView {
|
class SearchLocationView: UIView {
|
||||||
|
|
||||||
|
|
@ -365,8 +368,22 @@ class SearchLocationView: UIView {
|
||||||
contactsBtn.titleLabel?.font = .systemFont(ofSize: 12, weight: .regular)
|
contactsBtn.titleLabel?.font = .systemFont(ofSize: 12, weight: .regular)
|
||||||
contactsBtn.setImage(UIImage(named: "SearchLocation/contacts"), for: .normal)
|
contactsBtn.setImage(UIImage(named: "SearchLocation/contacts"), for: .normal)
|
||||||
contactsBtn.extendEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 15)
|
contactsBtn.extendEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 15)
|
||||||
contactsBtn.rx.tap.subscribe(onNext: { _ in
|
contactsBtn.rx.tap.subscribe(onNext: { [weak self] in
|
||||||
//TODO: - 通讯录
|
guard let self = self else { return }
|
||||||
|
self.phoneInputTF.resignFirstResponder()
|
||||||
|
let status = CNContactStore.authorizationStatus(for: .contacts)
|
||||||
|
switch status {
|
||||||
|
case .notDetermined:
|
||||||
|
CNContactStore().requestAccess(for: .contacts) { granted, _ in
|
||||||
|
DispatchQueue.main.async { if granted { self.presentContactPicker() } }
|
||||||
|
}
|
||||||
|
case .authorized:
|
||||||
|
self.presentContactPicker()
|
||||||
|
case .denied, .restricted:
|
||||||
|
DLToast.show(text: "请在设置中开启通讯录权限")
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
inputView.addSubview(contactsBtn)
|
inputView.addSubview(contactsBtn)
|
||||||
contactsBtn.layoutChain
|
contactsBtn.layoutChain
|
||||||
|
|
@ -540,4 +557,20 @@ class SearchLocationView: UIView {
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentContactPicker() {
|
||||||
|
let picker = CNContactPickerViewController()
|
||||||
|
picker.delegate = self
|
||||||
|
picker.displayedPropertyKeys = [CNContactPhoneNumbersKey]
|
||||||
|
UIViewController.topMost?.present(picker, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SearchLocationView: CNContactPickerDelegate {
|
||||||
|
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
|
||||||
|
if let phone = contact.phoneNumbers.first?.value.stringValue {
|
||||||
|
phoneInputTF.text = phone.filter { $0.isNumber }
|
||||||
|
phoneInputTF.sendActions(for: .editingChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,14 @@ class LaunchViewController: BaseViewController {
|
||||||
guard let model = response.model else { return }
|
guard let model = response.model else { return }
|
||||||
MQTTService.shared.connect()
|
MQTTService.shared.connect()
|
||||||
Defaults[\.loginToken] = model.token
|
Defaults[\.loginToken] = model.token
|
||||||
|
|
||||||
AppContextManager.shared.systemConfig = model.config
|
AppContextManager.shared.systemConfig = model.config
|
||||||
self.getUserIMToken()
|
|
||||||
|
if let upgrade = model.config?.versionUpgradeModel {
|
||||||
|
self.showVersionUpgrade(upgrade, model: model)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.continueAfterConfig(model: model)
|
||||||
}, onError: { [weak self] (error) in
|
}, onError: { [weak self] (error) in
|
||||||
DLAlert.show(title: error.localizedDescription,
|
DLAlert.show(title: error.localizedDescription,
|
||||||
defaultTitle: "重试") { [weak self] in
|
defaultTitle: "重试") { [weak self] in
|
||||||
|
|
@ -70,6 +76,31 @@ class LaunchViewController: BaseViewController {
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func continueAfterConfig(model: UserConfigModel) {
|
||||||
|
model.temp ? self.navigateAfterDelay() : self.getUserIMToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showVersionUpgrade(_ upgrade: VersionUpgradeModel, model: UserConfigModel) {
|
||||||
|
self.showConfirmPop(
|
||||||
|
showCloseBtn: !upgrade.force,
|
||||||
|
title: upgrade.title + upgrade.version,
|
||||||
|
message: upgrade.description,
|
||||||
|
confirmText: "立即更新",
|
||||||
|
confirmBlock: {
|
||||||
|
if let url = URL(string: upgrade.url) {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
if !upgrade.force {
|
||||||
|
self.continueAfterConfig(model: model)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancelText: upgrade.force ? nil : "稍后再说",
|
||||||
|
cancelBlock: upgrade.force ? nil : { [weak self] in
|
||||||
|
self?.continueAfterConfig(model: model)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// 获取用户IM Token
|
/// 获取用户IM Token
|
||||||
func getUserIMToken() {
|
func getUserIMToken() {
|
||||||
UserService.imToken().subscribe(onNext: { response in
|
UserService.imToken().subscribe(onNext: { response in
|
||||||
|
|
@ -77,6 +108,8 @@ class LaunchViewController: BaseViewController {
|
||||||
AppContextManager.shared.imToken = token
|
AppContextManager.shared.imToken = token
|
||||||
GroupIMService.shared.login { _ in }
|
GroupIMService.shared.login { _ in }
|
||||||
self.navigateAfterDelay()
|
self.navigateAfterDelay()
|
||||||
|
}, onError: { [weak self] (error) in
|
||||||
|
self?.navigateAfterDelay()
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ class LoginView: UIView {
|
||||||
lazy var welcomeLabel: UILabel = {
|
lazy var welcomeLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = "欢迎登录"
|
label.text = "欢迎登录"
|
||||||
label.font = UIFont(name: "DOUYU Font", size: 24)
|
label.font = UIFont(name: "DOUYU Font", size: 24) ?? .systemFont(ofSize: 24, weight: .medium)
|
||||||
label.textColor = UIColor(hexStr: "#030303")
|
label.textColor = UIColor(hexStr: "#030303")
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
return label
|
return label
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ class OneTapLoginView: UIView {
|
||||||
lazy var welcomeLabel: UILabel = {
|
lazy var welcomeLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = "欢迎登录"
|
label.text = "欢迎登录"
|
||||||
label.font = UIFont(name: "DOUYU Font", size: 24)
|
label.font = UIFont(name: "DOUYU Font", size: 24) ?? .systemFont(ofSize: 24, weight: .medium)
|
||||||
label.textColor = UIColor(hexStr: "#030303")
|
label.textColor = UIColor(hexStr: "#030303")
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
return label
|
return label
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@ import UIKit
|
||||||
import RxSwift
|
import RxSwift
|
||||||
import RxCocoa
|
import RxCocoa
|
||||||
import RxDataSources
|
import RxDataSources
|
||||||
|
import Kingfisher
|
||||||
#if !targetEnvironment(simulator)
|
#if !targetEnvironment(simulator)
|
||||||
import GeYanSdk
|
import GeYanSdk
|
||||||
|
import RxGesture
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class AccountVC: BaseViewController {
|
class AccountVC: BaseViewController {
|
||||||
|
|
@ -55,6 +57,12 @@ class AccountVC: BaseViewController {
|
||||||
self.deleteAccount()
|
self.deleteAccount()
|
||||||
}
|
}
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
let tap = UITapGestureRecognizer(target: self, action: #selector(handleClearCacheTap))
|
||||||
|
rootView.clearView.addGestureRecognizer(tap)
|
||||||
|
|
||||||
|
let versionTap = UITapGestureRecognizer(target: self, action: #selector(handleVersionViewTap))
|
||||||
|
rootView.versionView.addGestureRecognizer(versionTap)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
|
|
@ -76,4 +84,42 @@ class AccountVC: BaseViewController {
|
||||||
DLToast.show(text: "申请成功")
|
DLToast.show(text: "申请成功")
|
||||||
}, onError: { _ in }).disposed(by: disposeBag)
|
}, onError: { _ in }).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func handleClearCacheTap() {
|
||||||
|
clearCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleVersionViewTap() {
|
||||||
|
guard let upgrade = AppContextManager.shared.systemConfig?.versionUpgradeModel else {
|
||||||
|
DLToast.show(text: "已是最新版本")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.showConfirmPop(
|
||||||
|
showCloseBtn: !upgrade.force,
|
||||||
|
title: upgrade.title,
|
||||||
|
message: upgrade.description,
|
||||||
|
confirmText: "立即更新",
|
||||||
|
confirmBlock: {
|
||||||
|
if let url = URL(string: upgrade.url) {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancelText: upgrade.force ? nil : "稍后再说",
|
||||||
|
cancelBlock: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func clearCache() {
|
||||||
|
DLToast.showLoading()
|
||||||
|
KingfisherManager.shared.cache.clearMemoryCache()
|
||||||
|
KingfisherManager.shared.cache.clearDiskCache {
|
||||||
|
AppCacheManager.clearCache {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
DLToast.dismiss()
|
||||||
|
DLToast.show(text: "缓存已清除")
|
||||||
|
self.rootView.cacheLab.text = "0 B"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ class AccountView: UIView {
|
||||||
let titleLab = UILabel()
|
let titleLab = UILabel()
|
||||||
titleLab.text = "当前版本"
|
titleLab.text = "当前版本"
|
||||||
titleLab.font = .systemFont(ofSize: 14, weight: .medium)
|
titleLab.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
titleLab.textColor = UIColor(hexStr: "#1A1A1A")
|
titleLab.textColor = UIColor(hexStr: "#1A1A1A ")
|
||||||
view.addSubview(titleLab)
|
view.addSubview(titleLab)
|
||||||
titleLab.layoutChain
|
titleLab.layoutChain
|
||||||
.left(15)
|
.left(15)
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,7 @@ class ScheduleViewedListCell: UITableViewCell {
|
||||||
.top(18)
|
.top(18)
|
||||||
.height(50)
|
.height(50)
|
||||||
.widthToHeight(1)
|
.widthToHeight(1)
|
||||||
|
.bottom(18, relation: .greaterThanOrEqual)
|
||||||
|
|
||||||
nameLab.layoutChain
|
nameLab.layoutChain
|
||||||
.topToView(iconView)
|
.topToView(iconView)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ struct UserService {
|
||||||
|
|
||||||
static func imToken() -> Observable<ResponseModel> {
|
static func imToken() -> Observable<ResponseModel> {
|
||||||
let api = UserAPI.imToken.multiTarget
|
let api = UserAPI.imToken.multiTarget
|
||||||
return APIProvider.request(token: api)
|
return APIProvider.request(token: api, handle: false)
|
||||||
.map(ResponseModel.self)
|
.map(ResponseModel.self)
|
||||||
.asObservable()
|
.asObservable()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue