Skip to content

Commit

Permalink
♻️ 여정 기록 컨트롤 파편화 개선
Browse files Browse the repository at this point in the history
여러 곳에 파편화되어 있던 여정 시작 / 중단 / 재개 로직을 Home 부분에 통합
  • Loading branch information
SwiftyJunnos committed Jan 10, 2024
1 parent 0390082 commit 787b2cb
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,6 @@ public final class HomeViewController: HomeBottomSheetViewController {
self.navigationController?.isNavigationBarHidden = true
}

public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

self.viewModel.trigger(.viewNeedsReloaded)
}

public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

Expand All @@ -132,10 +126,27 @@ public final class HomeViewController: HomeBottomSheetViewController {
// MARK: - Combine Binding

private func bind() {
self.viewModel.state.startedJourney
self.viewModel.state.journeyDidStarted
.receive(on: DispatchQueue.main)
.sink { [weak self] startedJourney in
self?.contentViewController.recordingShouldStart(startedJourney)
self?.contentViewController.clearOverlays()
self?.contentViewController.recordingDidStart(startedJourney)
}
.store(in: &self.cancellables)

self.viewModel.state.journeyDidResumed
.receive(on: DispatchQueue.main)
.sink { [weak self] resumedJourney in
self?.contentViewController.clearOverlays()
self?.contentViewController.recordingDidResume(resumedJourney)
}
.store(in: &self.cancellables)

self.viewModel.state.journeyDidCancelled
.receive(on: DispatchQueue.main)
.sink { [weak self] cancelledJourney in
self?.contentViewController.clearOverlays()
self?.contentViewController.recordingDidStop(cancelledJourney)
}
.store(in: &self.cancellables)

Expand All @@ -155,7 +166,6 @@ public final class HomeViewController: HomeBottomSheetViewController {
self?.hideBottomSheet()
} else {
self?.showBottomSheet()
self?.contentViewController.recordingShouldStop(isCancelling: false)
}
self?.updateButtonMode(isRecording: isRecording)
}
Expand All @@ -169,34 +179,27 @@ public final class HomeViewController: HomeBottomSheetViewController {
.store(in: &self.cancellables)

self.viewModel.state.isRefreshButtonHidden
.removeDuplicates(by: { $0 == $1 })
.combineLatest(self.viewModel.state.isRecording)
.receive(on: DispatchQueue.main)
.sink { [weak self] isHidden, isRecording in
self?.refreshButton.isHidden = (isHidden && !isRecording)
}
.store(in: &self.cancellables)

self.viewModel.state.overlaysShouldBeCleared
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.contentViewController.clearOverlays()
self?.contentViewController.clearAnnotations()
guard let self = self else { return }
UIView.transition(with: self.refreshButton,
duration: 0.2,
options: .transitionCrossDissolve) { [weak self] in
self?.refreshButton.isHidden = (isHidden || isRecording)
}
}
.store(in: &self.cancellables)
}

// MARK: - Functions

private func updateButtonMode(isRecording: Bool) {
UIView.transition(with: startButton, duration: 0.5,
UIView.transition(with: self.view,
duration: 0.5,
options: .transitionCrossDissolve,
animations: { [weak self] in
self?.startButton.isHidden = isRecording
})
UIView.transition(with: recordJourneyButtonStackView, duration: 0.5,
options: .transitionCrossDissolve,
animations: { [weak self] in
self?.recordJourneyButtonStackView.isHidden = !isRecording
})
}
Expand Down Expand Up @@ -236,7 +239,6 @@ extension HomeViewController: RecordJourneyButtonViewDelegate {
guard self.viewModel.state.isRecording.value == true else { return }

self.viewModel.trigger(.backButtonDidTap)
self.contentViewController.recordingShouldStop(isCancelling: true)
}

public func spotButtonDidTap(_ button: MSRectButton) {
Expand Down
88 changes: 56 additions & 32 deletions iOS/Features/Home/Sources/Home/Presentation/HomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,19 @@ public final class HomeViewModel {

public enum Action {
case viewNeedsLoaded
case viewNeedsReloaded
case startButtonDidTap(Coordinate)
case refreshButtonDidTap(visibleCoordinates: (minCoordinate: Coordinate, maxCoordinate: Coordinate))
case backButtonDidTap
case recordingStateDidChange(Bool)
case mapViewDidChange
}

public struct State {
// Passthrough
public var startedJourney = PassthroughSubject<RecordingJourney, Never>()
public var journeyDidStarted = PassthroughSubject<RecordingJourney, Never>()
public var journeyDidResumed = PassthroughSubject<RecordingJourney, Never>()
public var journeyDidCancelled = PassthroughSubject<RecordingJourney, Never>()
public var visibleJourneys = PassthroughSubject<[Journey], Never>()
public var overlaysShouldBeCleared = PassthroughSubject<Bool, Never>()

// CurrentValue
public var isRecording = CurrentValueSubject<Bool, Never>(false)
Expand Down Expand Up @@ -68,28 +69,23 @@ public final class HomeViewModel {
#endif

self.createNewUserWhenFirstLaunch()
case .viewNeedsReloaded:
let isRecording = self.journeyRepository.isRecording
#if DEBUG
MSLogger.make(category: .home).debug("여정 기록 중 여부: \(isRecording)")
#endif
if isRecording {
self.resumeJourney()
}
self.state.isRecording.send(isRecording)

self.resumeJourneyIfNeeded()
case .startButtonDidTap(let coordinate):
#if DEBUG
MSLogger.make(category: .home).debug("Start 버튼 탭: \(coordinate)")
MSLogger.make(category: .home).debug("시작 버튼이 탭 되었습니다: \(coordinate)")
#endif
self.startJourney(at: coordinate)
self.state.isRefreshButtonHidden.send(true)
case .refreshButtonDidTap(visibleCoordinates: (let minCoordinate, let maxCoordinate)):
self.state.isRefreshButtonHidden.send(true)
self.fetchJourneys(minCoordinate: minCoordinate, maxCoordinate: maxCoordinate)
case .backButtonDidTap:
self.state.isRecording.send(false)
self.state.isRefreshButtonHidden.send(false)
self.state.overlaysShouldBeCleared.send(true)
#if DEBUG
MSLogger.make(category: .home).debug("취소 버튼이 탭 되었습니다.")
#endif
self.cancelJourney()
case .recordingStateDidChange(let isRecording):
self.state.isRecording.send(isRecording)
case .mapViewDidChange:
if self.state.isRecording.value == false {
self.state.isRefreshButtonHidden.send(false)
Expand Down Expand Up @@ -124,8 +120,26 @@ private extension HomeViewModel {
}
}

func fetchJourneys(minCoordinate: Coordinate, maxCoordinate: Coordinate) {
guard let userID = self.userRepository.fetchUUID() else { return }

Task {
let result = await self.journeyRepository.fetchJourneyList(userID: userID,
minCoordinate: minCoordinate,
maxCoordinate: maxCoordinate)
switch result {
case .success(let journeys):
self.state.visibleJourneys.send(journeys)
case .failure(let error):
MSLogger.make(category: .home).error("\(error)")
}
}
}

func startJourney(at coordinate: Coordinate) {
Task {
defer { self.syncRecordingState() }

self.state.isStartButtonLoading.send(true)
defer { self.state.isStartButtonLoading.send(false) }

Expand All @@ -134,36 +148,46 @@ private extension HomeViewModel {
let result = await self.journeyRepository.startJourney(at: coordinate, userID: userID)
switch result {
case .success(let recordingJourney):
self.state.startedJourney.send(recordingJourney)
self.state.isRecording.send(true)
self.state.journeyDidStarted.send(recordingJourney)
self.state.isRefreshButtonHidden.send(true)
case .failure(let error):
MSLogger.make(category: .home).error("\(error)")
}
}
}

func fetchJourneys(minCoordinate: Coordinate, maxCoordinate: Coordinate) {
guard let userID = self.userRepository.fetchUUID() else { return }
func resumeJourneyIfNeeded() {
defer { self.syncRecordingState() }

guard let recordingJourney = self.journeyRepository.fetchRecordingJourney() else {
return
}

self.state.journeyDidResumed.send(recordingJourney)
}

func cancelJourney() {
guard let userID = self.userRepository.fetchUUID(),
let recordingJourney = self.journeyRepository.fetchRecordingJourney() else {
return
}

Task {
let result = await self.journeyRepository.fetchJourneyList(userID: userID,
minCoordinate: minCoordinate,
maxCoordinate: maxCoordinate)
defer { self.syncRecordingState() }

let result = await self.journeyRepository.deleteJourney(recordingJourney, userID: userID)
switch result {
case .success(let journeys):
self.state.visibleJourneys.send(journeys)
case .success(let deletedJourney):
self.state.journeyDidCancelled.send(deletedJourney)
case .failure(let error):
MSLogger.make(category: .home).error("\(error)")
}
}
}

func resumeJourney() {
guard let recordingJourney = self.journeyRepository.fetchRecordingJourney() else {
return
}

MSLogger.make(category: .home).debug("Recording Journey: \(recordingJourney)")
func syncRecordingState() {
let isRecording = self.journeyRepository.isRecording
self.state.isRecording.send(isRecording)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by 이창준 on 2023.12.10.
//

import CoreLocation
import Foundation

import MSData
Expand All @@ -27,7 +28,7 @@ extension MapViewController {

extension MapViewController {

public func recordingShouldStart(_ startedJourney: RecordingJourney) {
public func recordingDidStart(_ startedJourney: RecordingJourney) {
guard self.viewModel is NavigateMapViewModel else {
MSLogger.make(category: .home).error("여정이 시작되어야 하지만 이미 Map에서 RecordJourneyViewModel을 사용하고 있습니다.")
return
Expand All @@ -44,36 +45,38 @@ extension MapViewController {
self.locationManager.allowsBackgroundLocationUpdates = true

#if DEBUG
MSLogger.make(category: .home).debug("여정 기록이 시작되었습니다.")
MSLogger.make(category: .home).debug("여정 기록이 시작되었습니다: \(startedJourney)")
#endif
}

public func recordingShouldResume(_ recordedJourney: RecordingJourney) {
public func recordingDidResume(_ recordedJourney: RecordingJourney) {
let userRepository = UserRepositoryImplementation()
let journeyRepository = JourneyRepositoryImplementation()
let recordJourneyViewModel = RecordJourneyViewModel(startedJourney: recordedJourney,
userRepository: userRepository,
journeyRepository: journeyRepository)
self.swapViewModel(to: recordJourneyViewModel)

let coordinates = recordedJourney.coordinates.map {
CLLocationCoordinate2D(latitude: $0.latitude,
longitude: $0.longitude)
}
self.drawPolyline(using: coordinates)

self.locationManager.startUpdatingLocation()
self.locationManager.allowsBackgroundLocationUpdates = true

#if DEBUG
MSLogger.make(category: .home).debug("여정 기록이 재개되었습니다.")
MSLogger.make(category: .home).debug("여정 기록이 재개되었습니다: \(recordedJourney)")
#endif
}

public func recordingShouldStop(isCancelling: Bool) {
guard let viewModel = self.viewModel as? RecordJourneyViewModel else {
public func recordingDidStop(_ stoppedJourney: RecordingJourney) {
guard self.viewModel is RecordJourneyViewModel else {
MSLogger.make(category: .home).error("여정이 종료되어야 하지만 이미 Map에서 NavigateMapViewModel을 사용하고 있습니다.")
return
}

if isCancelling {
viewModel.trigger(.recordingDidCancelled)
}

let journeyRepository = JourneyRepositoryImplementation()
let navigateMapViewModel = NavigateMapViewModel(repository: journeyRepository)
self.swapViewModel(to: navigateMapViewModel)
Expand All @@ -82,7 +85,7 @@ extension MapViewController {
self.locationManager.allowsBackgroundLocationUpdates = false

#if DEBUG
MSLogger.make(category: .home).debug("여정 기록이 종료되었습니다.")
MSLogger.make(category: .home).debug("여정 기록이 종료되었습니다: \(stoppedJourney)")
#endif
}

Expand Down
Loading

0 comments on commit 787b2cb

Please sign in to comment.