jsdw_ios/QuickLocation/Section/Home/HomeViewController.swift

282 lines
10 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// HomeViewController.swift
// QuickLocation
//
// Created by on 2026/5/27.
//
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
import CoreLocation
#if !targetEnvironment(simulator)
import MAMapKit
#endif
class HomeViewController: BaseViewController {
override var isNavigationBarHidden: Bool { true }
override var preferredStatusBarStyle: UIStatusBarStyle { .default }
// MARK: - Properties
fileprivate var rootView: HomeView!
private var tableView: UITableView {
rootView.groupMemberView.tableView
}
private var viewModel = HomeViewModel()
private let locationManager = CLLocationManager()
private var currentHeading: Double = 0
private var members: [CircleMember] = []
private var currentUserMember: CircleMember?
private var currentUserAnnotation: MemberAnnotation?
override func loadView() {
#if !targetEnvironment(simulator)
MAMapView.updatePrivacyAgree(.didAgree)
MAMapView.updatePrivacyShow(.didShow, privacyInfo: .didContain)
#endif
rootView = HomeView(frame: UIScreen.main.bounds)
view = rootView
}
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
setupMap()
setupHeading()
reactiveAction()
requestUserInfo()
requestGroupInfo()
}
private func reactiveAction() {
rootView.groupView.rx.tapGesture.subscribe { _ in
guard let groupModel = self.viewModel.groupModel else { return }
let groupViewFrame = self.view.convert(self.rootView.groupView.frame, from: self.rootView)
let startPointY = groupViewFrame.origin.y + groupViewFrame.height
GroupListPopView.show(start: CGPoint(x: 0, y: startPointY + 20),
groupModel: groupModel) { groupKey in
guard let key = groupKey else { return }
self.requestOperateGroup(groupKey: key)
}
}.disposed(by: disposeBag)
rootView.groupMemberView.refreshBtn.rx.tap.subscribe(onNext: { _ in
self.requestGroupInfo()
}).disposed(by: disposeBag)
rootView.groupMemberView.inviteJoinBtn.rx.tap.subscribe(onNext: { _ in
AppRouter.push(Route.inviteJoin, userInfo: ["groupInfo": self.viewModel.groupInfo])
}).disposed(by: disposeBag)
rootView.locationView.rx.tapGesture.subscribe { _ in
if let ann = self.currentUserAnnotation {
self.rootView.mapView.setCenter(ann.coordinate, animated: true)
}
}.disposed(by: disposeBag)
NotificationCenter.default.rx.notification(.RefreshGroupInfoNotification, object: nil)
.subscribe { [weak self] notification in
self?.requestGroupInfo()
}.disposed(by: disposeBag)
}
private func bindViewModel() {
viewModel.output.sectionedItems
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
tableView.rx.modelSelected(GroupMemberModel.self)
.subscribe(viewModel.cellAction.inputs)
.disposed(by: disposeBag)
}
// MARK: - UITableViewDataSource
lazy private var dataSource: RxTableViewSectionedReloadDataSource<GroupMemberListSectionModel> = {
return RxTableViewSectionedReloadDataSource<GroupMemberListSectionModel>(
configureCell: { (_, tableView, indexPath, model) in
let cell: GroupMemberCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(model: model,
isCurrentUser: self.viewModel.isCurrentUser(id: model.user_id),
isOwn: self.viewModel.isGroupOwn(id: model.user_id))
return cell
})
}()
// MARK: - API
private func requestUserInfo() {
UserService.userInfo().subscribe { response in
guard let model = response.model else { return }
AppContextManager.shared.saveAccount(model)
self.rootView.avatarImgView.image = model.userIcon
}.disposed(by: disposeBag)
}
private func requestGroupInfo() {
GroupService.groupInfo().subscribe { response in
guard let model = response.model else { return }
self.viewModel.groupModel = model
self.rootView.groupMemberView.setupCountData(self.viewModel.memberCount, self.viewModel.memberOnlineCount)
self.rootView.groupNameLab.text = self.viewModel.groupName
self.syncMemberAnnotations(model.select_group_employee)
}.disposed(by: disposeBag)
}
private func requestOperateGroup(groupKey: String) {
GroupService.operate(opType: "setdefault", requestData: ["group_key" : groupKey]).subscribe { response in
self.requestGroupInfo()
}.disposed(by: disposeBag)
}
// MARK: - Map Setup
private func setupMap() {
#if !targetEnvironment(simulator)
rootView.mapView.delegate = self
let r = MAUserLocationRepresentation()
r.showsAccuracyRing = false
r.showsHeadingIndicator = false
r.enablePulseAnnimation = false
r.lineWidth = 0
r.image = transparentImage()
rootView.mapView.update(r)
rootView.mapView.showsUserLocation = true
rootView.mapView.userTrackingMode = .none
#endif
}
private func transparentImage() -> UIImage {
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), false, 0)
let img = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
UIGraphicsEndImageContext()
return img
}
// MARK: - Heading (CLLocationManager)
private func setupHeading() {
locationManager.delegate = self
locationManager.startUpdatingHeading()
}
private func updateCurrentUserHeading() {
#if !targetEnvironment(simulator)
guard let ann = currentUserAnnotation,
let view = rootView.mapView.view(for: ann) as? MemberAnnotationView else { return }
view.updateHeading(currentHeading)
#endif
}
// MARK: - Map Annotations from API data
private func syncMemberAnnotations(_ list: [GroupMemberModel]) {
#if !targetEnvironment(simulator)
let isGroupOwner: (String) -> Bool = { [weak self] id in
self?.viewModel.isGroupOwn(id: id) ?? false
}
let currentUserId = AppContextManager.shared.userId
// GPS
let me = CircleMember(
id: "current",
name: AppContextManager.shared.name,
avatar: AppContextManager.shared.account?.head_pic ?? "1",
isOnline: true,
isOwner: false,
coordinate: kCLLocationCoordinate2DInvalid,
address: "",
heading: 0,
lastUpdateText: "在线"
)
// select_group_employee
var others: [CircleMember] = []
for model in list where model.user_id != currentUserId || currentUserId.isEmpty {
let m = CircleMember(member: model, isOwner: isGroupOwner(model.user_id))
guard CLLocationCoordinate2DIsValid(m.coordinate) else { continue }
others.append(m)
}
let newMembers = others + [me]
members = newMembers
currentUserMember = me
let mapView = rootView.mapView
let existing = mapView.annotations?.compactMap { $0 as? MemberAnnotation } ?? []
let existingIDs = Set(existing.map { $0.member.id })
let newIDs = Set(newMembers.map { $0.id })
let toRemove = existing.filter { !newIDs.contains($0.member.id) }
mapView.removeAnnotations(toRemove)
let toAdd = newMembers.filter { !existingIDs.contains($0.id) }
let annotations = toAdd.map { MemberAnnotation(member: $0) }
mapView.addAnnotations(annotations)
#endif
}
}
#if !targetEnvironment(simulator)
// MARK: - MAMapViewDelegate
extension HomeViewController: MAMapViewDelegate {
func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! {
if annotation is MAUserLocation {
return nil
}
guard let memberAnnotation = annotation as? MemberAnnotation else { return nil }
let identifier = "MemberAnnotation"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MemberAnnotationView
if annotationView == nil {
annotationView = MemberAnnotationView(annotation: memberAnnotation, reuseIdentifier: identifier)
} else {
annotationView?.annotation = memberAnnotation
}
annotationView?.configure(with: memberAnnotation.member)
if memberAnnotation.member.isCurrentUser {
currentUserAnnotation = memberAnnotation
annotationView?.updateHeading(currentHeading)
}
return annotationView
}
func mapView(_ mapView: MAMapView!, didUpdate userLocation: MAUserLocation!, updatingLocation: Bool) {
guard updatingLocation, let location = userLocation.location else { return }
let coordinate = location.coordinate
guard CLLocationCoordinate2DIsValid(coordinate) else { return }
if let ann = currentUserAnnotation {
ann.coordinate = coordinate
}
mapView.setCenter(coordinate, animated: true)
mapView.setUserTrackingMode(.follow, animated: true)
}
func mapViewRequireLocationAuth(_ locationManager: CLLocationManager!) {
locationManager.requestAlwaysAuthorization()
}
}
#endif
// MARK: - CLLocationManagerDelegate (heading only)
extension HomeViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
let h = newHeading.trueHeading
guard h >= 0 else { return }
currentHeading = h
updateCurrentUserHeading()
}
}