diff --git a/app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostMyDrawCourse.kt b/app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostMyDrawCourse.kt index 1ce0a3a63..8b18758ed 100644 --- a/app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostMyDrawCourse.kt +++ b/app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostMyDrawCourse.kt @@ -5,20 +5,20 @@ import kotlinx.serialization.Serializable @Serializable data class ResponsePostMyDrawCourse( - @SerialName("data") - val data: Data, - @SerialName("message") - val message: String, @SerialName("status") val status: Int, @SerialName("success") val success: Boolean, + @SerialName("message") + val message: String, + @SerialName("data") + val data: Data, ) { @Serializable data class Data( - @SerialName("createdAt") - val createdAt: String, @SerialName("id") val id: Int, + @SerialName("createdAt") + val createdAt: String, ) } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt index 540a03cd1..4fb1cfdad 100644 --- a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt +++ b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt @@ -73,11 +73,11 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc override suspend fun uploadCourse( image: MultipartBody.Part, - courseCreateRequestDto: RequestBody + data: RequestBody ): Response { return remoteCourseDataSource.uploadCourse( image = image, - courseCreateRequestDto = courseCreateRequestDto + data = data ) } diff --git a/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt b/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt index fa4b14046..e61137072 100644 --- a/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt +++ b/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt @@ -83,9 +83,9 @@ interface CourseService { //코스 업로드 @Multipart - @POST("/api/course/v2") + @POST("/api/course") suspend fun uploadCourse( @Part image: MultipartBody.Part, - @Part("courseCreateRequestDto") courseCreateRequestDto: RequestBody, + @Part("data") data: RequestBody, ): Response } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt index ffb83942d..093876699 100644 --- a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt +++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt @@ -54,6 +54,6 @@ class RemoteCourseDataSource @Inject constructor( suspend fun postRecord(request: RequestPostRunningHistory) = courseService.postRecord(request) - suspend fun uploadCourse(image: MultipartBody.Part, courseCreateRequestDto: RequestBody) = - courseService.uploadCourse(image, courseCreateRequestDto) + suspend fun uploadCourse(image: MultipartBody.Part, data: RequestBody) = + courseService.uploadCourse(image, data) } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt index ec56b0e9b..ec62303f2 100644 --- a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt +++ b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt @@ -42,7 +42,7 @@ interface CourseRepository { suspend fun postRecord(request: RequestPostRunningHistory): Response suspend fun uploadCourse( - image: MultipartBody.Part, courseCreateRequestDto: RequestBody + image: MultipartBody.Part, data: RequestBody ): Response suspend fun getCourseDetail(publicCourseId: Int): Result diff --git a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt index 64742a410..c9383650a 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt @@ -74,8 +74,12 @@ class DrawActivity : BindingActivity(R.layout.activity_draw var isSearchLocationMode: Boolean = false var isCurrentLocationMode: Boolean = false + var isBlockUpdateDeparture: Boolean = false + + private lateinit var naverMap: NaverMap private lateinit var departureLatLng: LatLng + private lateinit var customDepartureLatLng: LatLng private lateinit var animDown: Animation private lateinit var animUp: Animation private lateinit var searchResult: SearchResultEntity @@ -137,25 +141,26 @@ class DrawActivity : BindingActivity(R.layout.activity_draw setCameraFinishedListener() } + /** + * 출발지 mode 설정 + * + * 검색/현위치/커스텀 + */ private fun initMode() { when (searchResult.mode) { DepartureSetMode.SEARCH.mode -> initSearchLocationMode() DepartureSetMode.CURRENT.mode -> initCurrentLocationMode() DepartureSetMode.CUSTOM.mode -> initCustomLocationMode() - else -> throw IllegalArgumentException("Unknown mode: ${searchResult.mode}") + else -> throw IllegalArgumentException("Unknown mode: ${searchResult.mode}") //todo 예외 처리 필요 } } private fun initSearchLocationMode() { isSearchLocationMode = true - with(binding) { - tvGuide.isVisible = false - } - + binding.tvGuide.isVisible = false viewModel.searchResult.value = searchResult - viewModel.departureName.value = - searchResult.name //searchLocationMode일 땐 departureName 값 세팅해주는 부분이 따로 없어서 여기에 작성해놓음 + viewModel.departureName.value = searchResult.name setDepartureLatLng( latLng = LatLng( @@ -197,9 +202,15 @@ class DrawActivity : BindingActivity(R.layout.activity_draw showDrawGuide() hideDeparture() showDrawCourse() + customDepartureLatLng = getCenterPosition() + isBlockUpdateDeparture = true getCenterPosition().apply { departureLatLng = this }.let(::drawCourse) + + //커스텀 모드 시 departureLatLng이 계속 현위치 좌표값으로 갱신되어 RunActivity로 path 넘길 때 잘못된 값이 넘어가서 대응 조치 + //위의 방법으로도 한계가 있어서 putExtra 직전에 값을 한 번 더 갱신해주는 식으로 대응 + hideFloatedDeparture() } } @@ -218,10 +229,16 @@ class DrawActivity : BindingActivity(R.layout.activity_draw ) } + /** + * Reverse Geocoding API 호출 + */ private fun getLocationInfoUsingLatLng(lat: Double, lon: Double) { viewModel.getLocationInfoUsingLatLng(lat = lat, lon = lon) } + /** + * 지도 줌 설정 + */ private fun setZoomControl() { naverMap.maxZoom = 18.0 naverMap.minZoom = 10.0 @@ -230,16 +247,22 @@ class DrawActivity : BindingActivity(R.layout.activity_draw uiSettings.isZoomControlEnabled = false } + /** + * 현위치 커스텀 아이콘 설정 + */ private fun setCurrentLocationIcon() { val locationOverlay = naverMap.locationOverlay locationOverlay.icon = OverlayImage.fromResource(R.drawable.current_location) } + /** + * 카메라 이동 종료 시 실행되는 리스너 + */ private fun setCameraFinishedListener() { naverMap.addOnCameraIdleListener { val centerLatLng = getCenterPosition() if (::currentLocation.isInitialized) { - getLocationInfoUsingLatLng( //코스를 다 그린 후에도 계속 통신이 돌아서 리소스 낭비를 막기 위한 조치 필요 + getLocationInfoUsingLatLng( //todo 코스를 다 그린 후에도 계속 통신이 돌아서 리소스 낭비를 막기 위한 조치 필요 lat = centerLatLng.latitude, lon = centerLatLng.longitude ) } @@ -247,19 +270,32 @@ class DrawActivity : BindingActivity(R.layout.activity_draw } } + /** + * 지도 화면의 중앙 위경도 좌표값 반환 + */ private fun getCenterPosition(): LatLng { val cameraPosition = naverMap.cameraPosition - return cameraPosition.target // 중심 좌표 + return cameraPosition.target } + /** + * 현위치 추척, 변동 시 위경도 좌표값 반환 + */ private fun setLocationChangedListener() { naverMap.addOnLocationChangeListener { location -> currentLocation = LatLng(location.latitude, location.longitude) naverMap.locationOverlay.position = currentLocation naverMap.locationOverlay.isVisible = false - setDepartureLatLng(latLng = LatLng(currentLocation.latitude, currentLocation.longitude)) + if (!isBlockUpdateDeparture) { + setDepartureLatLng( + latLng = LatLng( + currentLocation.latitude, + currentLocation.longitude + ) + ) + } //같은 scope 안에 넣었으니 setDepartureLatLng 다음에 drawCourse가 실행되는 것이 보장됨 //이때 isFirstInit의 초기값을 true로 줘서 최초 1회는 실행되게 하고 이후 drawCourse 내에서 isFirstInit 값을 false로 바꿔줌 //뒤의 조건을 안 달아주면 다른 mode에서는 버튼을 클릭하기도 전에 drawCourse()가 돌 거라 안 됨. @@ -269,6 +305,9 @@ class DrawActivity : BindingActivity(R.layout.activity_draw } } + /** + * 지도에 실시간 현위치 아이콘 표시 여부 설정 + */ private fun setLocationTrackingMode() { naverMap.locationSource = locationSource @@ -288,6 +327,9 @@ class DrawActivity : BindingActivity(R.layout.activity_draw } } + /** + * 코스 이름 입력 받는 BottomSheet + */ private fun requireCourseNameDialog(): BottomSheetDialog { val bottomSheetBinding = BottomsheetRequireCourseNameBinding.inflate(layoutInflater) val bottomSheetView = bottomSheetBinding.root @@ -437,7 +479,7 @@ class DrawActivity : BindingActivity(R.layout.activity_draw binding.indeterminateBar.isVisible = false } - private fun observeDrawState() { //분기 처리를 더 해줘야 함. 서버에서 400 날아오는데 이게 success로 빠져서 '코스 생성 완료' 팝업이 뜨고 있음. + private fun observeDrawState() { viewModel.drawState.observe(this) { when (it) { UiState.Empty -> hideLoadingBar() @@ -454,7 +496,10 @@ class DrawActivity : BindingActivity(R.layout.activity_draw } } - private fun notifyCreateFinish() { + /** + * 코스 완성 시 뜨는 팝업 (보관함 가기 / 바로 달리기) + */ + private fun notifyCreateFinish() { //todo dialogFragment로 리팩토링 val (dialog, dialogLayout) = setActivityDialog( layoutInflater = layoutInflater, view = binding.root, @@ -464,9 +509,11 @@ class DrawActivity : BindingActivity(R.layout.activity_draw with(dialogLayout) { this.btn_run.setOnClickListener { + if (isCustomLocationMode) departureLatLng = customDepartureLatLng + val courseData = CourseData( courseId = viewModel.uploadResult.value?.data?.id, - publicCourseId = null, + publicCourseId = null, //직접 생성하는 코스는 publicCourseId가 없지만 코스 발견 -> 러닝 등의 루트로 넘어올 시 기록 업로드에서 requestBody에 필요함 touchList = touchList, startLatLng = departureLatLng, departure = viewModel.departureName.value, @@ -512,7 +559,10 @@ class DrawActivity : BindingActivity(R.layout.activity_draw } } - private fun cameraUpdate(location: Any) { + /** + * 지도 카메라 갱신 + */ + private fun updateCamera(location: Any) { //맨 처음 지도 켤 때 startLocation으로 위치 옮길 때 사용 if (location is LatLng) { val cameraUpdate = CameraUpdate.scrollTo(location).animate(CameraAnimation.Easing) @@ -532,6 +582,9 @@ class DrawActivity : BindingActivity(R.layout.activity_draw deleteRouteMarker() } + /** + * 출발지 마커 생성 + */ private fun createDepartureMarker(departureLatLng: LatLng) { setDepartureMarker(departureLatLng = departureLatLng) addDepartureToCoords(departureLatLng = departureLatLng) @@ -546,13 +599,16 @@ class DrawActivity : BindingActivity(R.layout.activity_draw departureMarker.map = naverMap if (isSearchLocationMode) { - cameraUpdate( + updateCamera( LatLng(departureLatLng.latitude, departureLatLng.longitude) ) // 현위치에서 출발할 때 이것 때문에 트랙킹 모드 활성화 시 카메라 이동하는 게 묻혔음 } setCustomInfoWindow(marker = departureMarker) } + /** + * 출발지 정보창 설정 + */ private fun setCustomInfoWindow(marker: Marker) { val infoWindow = InfoWindow() infoWindow.adapter = object : InfoWindow.ViewAdapter() { @@ -576,6 +632,9 @@ class DrawActivity : BindingActivity(R.layout.activity_draw ) } + /** + * 경로 마커 생성 + */ private fun createRouteMarker() { naverMap.setOnMapClickListener { _, coord -> if (!isMarkerAvailable) return@setOnMapClickListener @@ -612,6 +671,9 @@ class DrawActivity : BindingActivity(R.layout.activity_draw markerList.add(routeMarker) } + /** + * 경로선 그리기 + */ private fun generateRouteLine(coord: LatLng) { coords.add(LatLng(coord.latitude, coord.longitude)) // coords에 터치로 받아온 좌표값 추가 path.coords = coords // 경로선 그리기 @@ -620,6 +682,9 @@ class DrawActivity : BindingActivity(R.layout.activity_draw path.map = naverMap } + /** + * 경로 마커 제거 + */ private fun deleteRouteMarker() { binding.btnMarkerBack.setOnClickListener { updateRouteMarkerData() @@ -664,6 +729,9 @@ class DrawActivity : BindingActivity(R.layout.activity_draw viewModel.distanceSum.value = distanceSum } + /** + * 위경도 좌표 간 거리 합 계산 + */ private fun calcDistance() { for (i in 0 until touchList.size) { if (!calcDistanceList.contains(touchList[i])) { @@ -687,6 +755,8 @@ class DrawActivity : BindingActivity(R.layout.activity_draw } /** + * MBR 생성 및 화면 캡쳐 + * * MBR : 남서쪽과 북동쪽 꼭지점 두 개의 좌표로 만드는 직사각형 영역 */ private fun createMBR() { @@ -694,7 +764,7 @@ class DrawActivity : BindingActivity(R.layout.activity_draw .include(LatLng(departureLatLng.latitude, departureLatLng.longitude)).include(touchList) .build() naverMap.setContentPadding(100, 100, 100, 100) - cameraUpdate(bounds) + updateCamera(bounds) captureMap() } @@ -728,9 +798,10 @@ class DrawActivity : BindingActivity(R.layout.activity_draw isCurrentLocationMode || isCustomLocationMode -> { viewModel.departureAddress.value = viewModel.reverseGeocodingResult.value?.fullAddress + viewModel.departureName.value = + viewModel.reverseGeocodingResult.value?.buildingName ?: "내가 설정한 출발지" } } - } // Get uri of images from camera function diff --git a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawViewModel.kt index 08887f7d0..c4fe4d421 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawViewModel.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawViewModel.kt @@ -24,8 +24,6 @@ class DrawViewModel @Inject constructor( val reverseGeocodingRepository: ReverseGeocodingRepository ) : ViewModel() { - val editTextValue = MutableLiveData() - private var _drawState = MutableLiveData(UiState.Empty) val drawState: LiveData get() = _drawState @@ -36,7 +34,7 @@ class DrawViewModel @Inject constructor( var distanceSum = MutableLiveData(0.0f) val departureAddress = MutableLiveData() var courseTitle = "" - val departureName = MutableLiveData() + val departureName = MutableLiveData("내가 설정한 출발지") val isBtnAvailable = MutableLiveData(false) val reverseGeocodingResult = MutableLiveData() @@ -91,7 +89,7 @@ class DrawViewModel @Inject constructor( _drawState.value = UiState.Loading courseRepository.uploadCourse( image = _image.value!!.toFormData(), - courseCreateRequestDto = CourseCreateRequestDto( + data = CourseCreateRequestDto( path = path.value ?: listOf( UploadLatLng( 37.52901832956373, @@ -100,11 +98,15 @@ class DrawViewModel @Inject constructor( ), title = courseTitle, distance = distanceSum.value!!, - departureAddress = departureAddress.value!!, //커스텀의 경우 지금 여기에 들어가는 게 아무것도 없음. + departureAddress = departureAddress.value!!, departureName = departureName.value!! ).toRequestBody() ) }.onSuccess { + if (it.body() == null) { + _drawState.value = UiState.Failure + return@onSuccess //추가 조치 필요 + } Timber.tag(ContentValues.TAG).d("통신success") uploadResult.value = it.body() _drawState.value = UiState.Success