Skip to content

Commit

Permalink
Merge pull request #348 from boostcampwm2023/iOS/task/Gradient
Browse files Browse the repository at this point in the history
[iOS] ์ง€๋„ ๊ทธ๋ผ๋””์–ธํŠธ
  • Loading branch information
SwiftyJunnos authored Jan 12, 2024
2 parents c5fe526 + 20e7f51 commit a29b108
Show file tree
Hide file tree
Showing 40 changed files with 771 additions and 459 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ import MSDomain
public protocol HomeNavigationDelegate: AnyObject {

func navigateToSpot(spotCoordinate coordinate: Coordinate)
func navigateToSelectSong(lastCoordinate: Coordinate)
func navigateToSaveJourneyFlow(lastCoordinate: Coordinate)

}
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,35 @@ 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(receiveCompletion: { [weak self] completion in
if case .failure = completion {
self?.contentViewController.clearOverlays()
self?.contentViewController.recordingDidStop()
}
}, receiveValue: { [weak self] cancelledJourney in
self?.contentViewController.clearOverlays()
self?.contentViewController.recordingDidStop(cancelledJourney)
})
.store(in: &self.cancellables)

self.viewModel.state.visibleJourneys
.sink { [weak self] visibleJourneys in
self?.contentViewController.visibleJourneysDidUpdated(visibleJourneys)
Expand All @@ -155,7 +177,6 @@ public final class HomeViewController: HomeBottomSheetViewController {
self?.hideBottomSheet()
} else {
self?.showBottomSheet()
self?.contentViewController.recordingShouldStop(isCancelling: false)
}
self?.updateButtonMode(isRecording: isRecording)
}
Expand All @@ -169,37 +190,39 @@ public final class HomeViewController: HomeBottomSheetViewController {
.store(in: &self.cancellables)

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

self.viewModel.state.overlaysShouldBeCleared
.combineLatest(self.viewModel.state.isRecording)
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.contentViewController.clearOverlays()
self?.contentViewController.clearAnnotations()
.sink { [weak self] isHidden, isRecording in
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.34,
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
})
}

public func spotDidAdded(_ spot: Spot, photoData: Data) {
self.contentViewController.spotDidAdded(spot, photoData: photoData)
}

public func journeyDidEnded(endedJourney: Journey) {
self.contentViewController.recordingDidStop(RecordingJourney(endedJourney))
}

}

// MARK: - Buttons
Expand Down Expand Up @@ -231,14 +254,13 @@ extension HomeViewController: RecordJourneyButtonViewDelegate {
self.viewModel.trigger(.refreshButtonDidTap(visibleCoordinates: coordinates))
}

public func backButtonDidTap(_ button: MSRectButton) {
func backButtonDidTap(_ button: MSRectButton) {
guard self.viewModel.state.isRecording.value == true else { return }

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

public func spotButtonDidTap(_ button: MSRectButton) {
func spotButtonDidTap(_ button: MSRectButton) {
guard self.viewModel.state.isRecording.value == true else { return }

guard let currentUserCoordiante = self.contentViewController.currentUserCoordinate else {
Expand All @@ -248,14 +270,14 @@ extension HomeViewController: RecordJourneyButtonViewDelegate {
self.navigationDelegate?.navigateToSpot(spotCoordinate: currentUserCoordiante)
}

public func nextButtonDidTap(_ button: MSRectButton) {
func nextButtonDidTap(_ button: MSRectButton) {
guard self.viewModel.state.isRecording.value == true else { return }

guard let currentUserCoordiante = self.contentViewController.currentUserCoordinate else {
return
}

self.navigationDelegate?.navigateToSelectSong(lastCoordinate: currentUserCoordiante)
self.navigationDelegate?.navigateToSaveJourneyFlow(lastCoordinate: currentUserCoordiante)
}

}
Expand Down
82 changes: 64 additions & 18 deletions iOS/Features/Home/Sources/Home/Presentation/HomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@ public final class HomeViewModel {
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, Error>()
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,22 +70,25 @@ public final class HomeViewModel {
#endif

self.createNewUserWhenFirstLaunch()

self.resumeJourneyIfNeeded()
case .viewNeedsReloaded:
let isRecording = self.journeyRepository.fetchIsRecording()
self.state.isRecording.send(isRecording)
self.syncRecordingState()
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 @@ -118,8 +123,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 @@ -128,28 +151,51 @@ 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)")
self.state.journeyDidCancelled.send(completion: .failure(error))
}
}
}

func syncRecordingState() {
let isRecording = self.journeyRepository.isRecording
#if DEBUG
MSLogger.make(category: .home)
.debug("์—ฌ์ • ๊ธฐ๋ก ์—ฌ๋ถ€๋ฅผ ์‹ฑํฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ๊ธฐ๋ก ์ƒํƒœ: \(isRecording ? "๊ธฐ๋ก ์ค‘" : "๊ธฐ๋ก ์ค‘์ด์ง€ ์•Š์Œ")")
#endif
self.state.isRecording.send(isRecording)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import UIKit
import MSDesignSystem
import MSUIKit

public protocol RecordJourneyButtonViewDelegate: AnyObject {
internal protocol RecordJourneyButtonViewDelegate: AnyObject {

func backButtonDidTap(_ button: MSRectButton)
func spotButtonDidTap(_ button: MSRectButton)
Expand Down Expand Up @@ -64,7 +64,7 @@ public final class RecordJourneyButtonStackView: UIView {

// MARK: - Properties

public var delegate: RecordJourneyButtonViewDelegate?
internal var delegate: RecordJourneyButtonViewDelegate?

// MARK: - Initializer

Expand Down
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,18 +45,36 @@ extension MapViewController {
self.locationManager.allowsBackgroundLocationUpdates = true

#if DEBUG
MSLogger.make(category: .home).debug("์—ฌ์ • ๊ธฐ๋ก์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
MSLogger.make(category: .home).debug("์—ฌ์ • ๊ธฐ๋ก์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค: \(startedJourney)")
#endif
}

public func recordingShouldStop(isCancelling: Bool) {
guard let viewModel = self.viewModel as? RecordJourneyViewModel else {
MSLogger.make(category: .home).error("์—ฌ์ •์ด ์ข…๋ฃŒ๋˜์–ด์•ผ ํ•˜์ง€๋งŒ ์ด๋ฏธ Map์—์„œ NavigateMapViewModel์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.")
return
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 isCancelling {
viewModel.trigger(.recordingDidCancelled)
#if DEBUG
MSLogger.make(category: .home).debug("์—ฌ์ • ๊ธฐ๋ก์ด ์žฌ๊ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค: \(recordedJourney)")
#endif
}

public func recordingDidStop(_ stoppedJourney: RecordingJourney? = nil) {
guard self.viewModel is RecordJourneyViewModel else {
MSLogger.make(category: .home).error("์—ฌ์ •์ด ์ข…๋ฃŒ๋˜์–ด์•ผ ํ•˜์ง€๋งŒ ์ด๋ฏธ Map์—์„œ NavigateMapViewModel์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.")
return
}

let journeyRepository = JourneyRepositoryImplementation()
Expand All @@ -66,8 +85,25 @@ extension MapViewController {
self.locationManager.allowsBackgroundLocationUpdates = false

#if DEBUG
MSLogger.make(category: .home).debug("์—ฌ์ • ๊ธฐ๋ก์ด ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
if let stoppedJourney {
MSLogger.make(category: .home).debug("์—ฌ์ • ๊ธฐ๋ก์ด ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค: \(stoppedJourney)")
}
#endif
}

}

// MARK: - Spot

extension MapViewController {

public func spotDidAdded(_ spot: Spot, photoData: Data) {
let coordinate = CLLocationCoordinate2D(latitude: spot.coordinate.latitude,
longitude: spot.coordinate.longitude)

self.addAnnotation(title: spot.timestamp.description,
coordinate: coordinate,
photoData: photoData)
}

}
Loading

0 comments on commit a29b108

Please sign in to comment.