diff --git a/app/src/debug/res/drawable/draw_require_coure_name_border.xml b/app/src/debug/res/drawable/draw_require_coure_name_border.xml
new file mode 100644
index 000000000..b85d056a6
--- /dev/null
+++ b/app/src/debug/res/drawable/draw_require_coure_name_border.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/debug/res/drawable/ic_current_location.xml b/app/src/debug/res/drawable/ic_current_location.xml
new file mode 100644
index 000000000..b46678518
--- /dev/null
+++ b/app/src/debug/res/drawable/ic_current_location.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/app/src/debug/res/drawable/ic_custom_location.xml b/app/src/debug/res/drawable/ic_custom_location.xml
new file mode 100644
index 000000000..ab8e2741b
--- /dev/null
+++ b/app/src/debug/res/drawable/ic_custom_location.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/debug/res/drawable/ic_drag.xml b/app/src/debug/res/drawable/ic_drag.xml
new file mode 100644
index 000000000..ce23336c5
--- /dev/null
+++ b/app/src/debug/res/drawable/ic_drag.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/app/src/debug/res/drawable/ic_info_window.xml b/app/src/debug/res/drawable/ic_info_window.xml
new file mode 100644
index 000000000..742a04813
--- /dev/null
+++ b/app/src/debug/res/drawable/ic_info_window.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/app/src/main/java/com/runnect/runnect/data/dto/LocationData.kt b/app/src/main/java/com/runnect/runnect/data/dto/LocationData.kt
new file mode 100644
index 000000000..95b566cce
--- /dev/null
+++ b/app/src/main/java/com/runnect/runnect/data/dto/LocationData.kt
@@ -0,0 +1,11 @@
+package com.runnect.runnect.data.dto
+
+
+import android.os.Parcelable
+import kotlinx.android.parcel.Parcelize
+
+@Parcelize
+data class LocationData(
+ val buildingName: String,
+ val fullAddress: String,
+) : Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/com/runnect/runnect/data/dto/SearchResultEntity.kt b/app/src/main/java/com/runnect/runnect/data/dto/SearchResultEntity.kt
index 512f87bbe..76279cc67 100644
--- a/app/src/main/java/com/runnect/runnect/data/dto/SearchResultEntity.kt
+++ b/app/src/main/java/com/runnect/runnect/data/dto/SearchResultEntity.kt
@@ -8,5 +8,6 @@ import kotlinx.android.parcel.Parcelize
data class SearchResultEntity(
val fullAddress: String,
val name: String,
- val locationLatLng: LatLng,
+ val locationLatLng: LatLng?,
+ val mode: String
) : Parcelable
diff --git a/app/src/main/java/com/runnect/runnect/data/dto/tmap/geocoding/ResponseReverseGeocodingDto.kt b/app/src/main/java/com/runnect/runnect/data/dto/tmap/geocoding/ResponseReverseGeocodingDto.kt
new file mode 100644
index 000000000..928e1d5f8
--- /dev/null
+++ b/app/src/main/java/com/runnect/runnect/data/dto/tmap/geocoding/ResponseReverseGeocodingDto.kt
@@ -0,0 +1,94 @@
+package com.runnect.runnect.data.dto.tmap.geocoding
+
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ResponseReverseGeocodingDto(
+ @SerialName("addressInfo")
+ val addressInfo: AddressInfo
+) {
+ @Serializable
+ data class AddressInfo(
+ @SerialName("addressKey")
+ val addressKey: String? = null,
+ @SerialName("addressType")
+ val addressType: String,
+ @SerialName("adminDong")
+ val adminDong: String,
+ @SerialName("adminDongCode")
+ val adminDongCode: String,
+ @SerialName("adminDongCoord")
+ val adminDongCoord: AdminDongCoord? = null,
+ @SerialName("buildingIndex")
+ val buildingIndex: String,
+ @SerialName("buildingName")
+ val buildingName: String,
+ @SerialName("bunji")
+ val bunji: String,
+ @SerialName("city_do")
+ val cityDo: String,
+ @SerialName("eup_myun")
+ val eupMyun: String,
+ @SerialName("fullAddress")
+ val fullAddress: String,
+ @SerialName("gu_gun")
+ val guGun: String,
+ @SerialName("legalDong")
+ val legalDong: String,
+ @SerialName("legalDongCode")
+ val legalDongCode: String,
+ @SerialName("legalDongCoord")
+ val legalDongCoord: LegalDongCoord? = null,
+ @SerialName("mappingDistance")
+ val mappingDistance: String,
+ @SerialName("ri")
+ val ri: String,
+ @SerialName("roadAddressKey")
+ val roadAddressKey: String? = null,
+ @SerialName("roadCode")
+ val roadCode: String,
+ @SerialName("roadCoord")
+ val roadCoord: RoadCoord? = null,
+ @SerialName("roadName")
+ val roadName: String
+ ) {
+
+ @Serializable
+ data class AdminDongCoord(
+ @SerialName("lat")
+ val lat: String,
+ @SerialName("latEntr")
+ val latEntr: String,
+ @SerialName("lon")
+ val lon: String,
+ @SerialName("lonEntr")
+ val lonEntr: String
+ )
+
+ @Serializable
+ data class RoadCoord(
+ @SerialName("lat")
+ val lat: String,
+ @SerialName("latEntr")
+ val latEntr: String,
+ @SerialName("lon")
+ val lon: String,
+ @SerialName("lonEntr")
+ val lonEntr: String
+ )
+
+ @Serializable
+ data class LegalDongCoord(
+ @SerialName("lat")
+ val lat: String,
+ @SerialName("latEntr")
+ val latEntr: String,
+ @SerialName("lon")
+ val lon: String,
+ @SerialName("lonEntr")
+ val lonEntr: String
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/runnect/runnect/data/repository/DepartureSearchRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/DepartureSearchRepositoryImpl.kt
index d24027cb4..b96ab33f5 100644
--- a/app/src/main/java/com/runnect/runnect/data/repository/DepartureSearchRepositoryImpl.kt
+++ b/app/src/main/java/com/runnect/runnect/data/repository/DepartureSearchRepositoryImpl.kt
@@ -24,7 +24,8 @@ class DepartureSearchRepositoryImpl @Inject constructor(private val departureSou
SearchResultEntity(
fullAddress = makeMainAdress(it),
name = it.name ?: "",
- locationLatLng = LatLng(it.noorLat.toDouble(), it.noorLon.toDouble())
+ locationLatLng = LatLng(it.noorLat.toDouble(), it.noorLon.toDouble()),
+ mode = "searchLocation" //현위치, 지도에서 출발과 구분하기 위한 식별자
)
}
return changedData
diff --git a/app/src/main/java/com/runnect/runnect/data/repository/ReverseGeocodingRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/ReverseGeocodingRepositoryImpl.kt
new file mode 100644
index 000000000..8fd425c0a
--- /dev/null
+++ b/app/src/main/java/com/runnect/runnect/data/repository/ReverseGeocodingRepositoryImpl.kt
@@ -0,0 +1,21 @@
+package com.runnect.runnect.data.repository
+
+import com.runnect.runnect.data.dto.LocationData
+import com.runnect.runnect.data.source.remote.RemoteReverseGeocodingDataSource
+import com.runnect.runnect.domain.ReverseGeocodingRepository
+import javax.inject.Inject
+
+class ReverseGeocodingRepositoryImpl @Inject constructor(private val reverseGeocodingDataSource: RemoteReverseGeocodingDataSource) :
+ ReverseGeocodingRepository {
+ override suspend fun getLocationInfoUsingLatLng(
+ lat: Double,
+ lon: Double
+ ): LocationData {
+ val response =
+ reverseGeocodingDataSource.getLocationInfoUsingLatLng(lat = lat, lon = lon).body()
+ return LocationData(
+ buildingName = response?.addressInfo?.buildingName ?: "buildingName fail",
+ fullAddress = response?.addressInfo?.fullAddress ?: "fullAddress fail"
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/runnect/runnect/data/service/ReverseGeocodingService.kt b/app/src/main/java/com/runnect/runnect/data/service/ReverseGeocodingService.kt
new file mode 100644
index 000000000..706c3a77a
--- /dev/null
+++ b/app/src/main/java/com/runnect/runnect/data/service/ReverseGeocodingService.kt
@@ -0,0 +1,23 @@
+package com.runnect.runnect.data.service
+
+import com.runnect.runnect.BuildConfig
+import com.runnect.runnect.data.dto.tmap.SearchResponseTmapDto
+import com.runnect.runnect.data.dto.tmap.geocoding.ResponseReverseGeocodingDto
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Header
+import retrofit2.http.Query
+
+interface ReverseGeocodingService {
+
+ @GET("/tmap/geo/reversegeocoding?")
+ suspend fun getLocationUsingLatLng(
+ @Header("appKey") appKey: String = BuildConfig.TMAP_API_KEY,
+ @Query("version") version: Int = 1,
+ @Query("callback") callback: String? = null,
+ @Query("lat") lat: Double,
+ @Query("lon") lon: Double,
+ @Query("coordType") coordType: String? = "WGS84GEO",
+ @Query("addressType") addresstType: String? = "A04",
+ ): Response
+}
diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteReverseGeocodingDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteReverseGeocodingDataSource.kt
new file mode 100644
index 000000000..a4f29514a
--- /dev/null
+++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteReverseGeocodingDataSource.kt
@@ -0,0 +1,14 @@
+package com.runnect.runnect.data.source.remote
+
+import com.runnect.runnect.data.dto.tmap.geocoding.ResponseReverseGeocodingDto
+import com.runnect.runnect.data.service.ReverseGeocodingService
+import retrofit2.Response
+import javax.inject.Inject
+
+class RemoteReverseGeocodingDataSource @Inject constructor(private val reverseGeocodingService: ReverseGeocodingService) {
+ suspend fun getLocationInfoUsingLatLng(
+ lat: Double,
+ lon: Double
+ ): Response =
+ reverseGeocodingService.getLocationUsingLatLng(lat = lat, lon = lon)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/runnect/runnect/di/RepositoryModule.kt b/app/src/main/java/com/runnect/runnect/di/RepositoryModule.kt
index 7511b7be2..f45ad8e50 100644
--- a/app/src/main/java/com/runnect/runnect/di/RepositoryModule.kt
+++ b/app/src/main/java/com/runnect/runnect/di/RepositoryModule.kt
@@ -1,12 +1,11 @@
package com.runnect.runnect.di
-import com.runnect.runnect.data.service.*
import com.runnect.runnect.data.repository.*
+import com.runnect.runnect.data.service.*
import com.runnect.runnect.data.source.remote.*
import com.runnect.runnect.domain.*
import dagger.Binds
import dagger.Module
-import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@@ -34,6 +33,10 @@ interface RepositoryModule {
@Binds
fun bindLoginRepository(loginRepositoryImpl: LoginRepositoryImpl): LoginRepository
+ @Singleton
+ @Binds
+ fun bindReverseGeocodingRepository(reverseGeocodingRepositoryImpl: ReverseGeocodingRepositoryImpl): ReverseGeocodingRepository
+
@Singleton
@Binds
fun bindBannerRepository(bannerRepositoryImpl: BannerRepositoryImpl): BannerRepository
diff --git a/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt b/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt
index d302d0fa1..bcee7268b 100644
--- a/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt
+++ b/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt
@@ -36,6 +36,11 @@ object ServiceModule {
fun provideKSearchService(@RetrofitModule.Tmap tmapRetrofit: Retrofit) =
tmapRetrofit.create(SearchService::class.java)
+ @Singleton
+ @Provides
+ fun provideReverseGeocodingService(@RetrofitModule.Tmap tmapRetrofit: Retrofit) =
+ tmapRetrofit.create(ReverseGeocodingService::class.java)
+
@Singleton
@Provides
fun provideBannerService() = Firebase.firestore
diff --git a/app/src/main/java/com/runnect/runnect/domain/ReverseGeocodingRepository.kt b/app/src/main/java/com/runnect/runnect/domain/ReverseGeocodingRepository.kt
new file mode 100644
index 000000000..ee968dbac
--- /dev/null
+++ b/app/src/main/java/com/runnect/runnect/domain/ReverseGeocodingRepository.kt
@@ -0,0 +1,7 @@
+package com.runnect.runnect.domain
+
+import com.runnect.runnect.data.dto.LocationData
+
+interface ReverseGeocodingRepository {
+ suspend fun getLocationInfoUsingLatLng(lat: Double, lon: Double): LocationData
+}
\ No newline at end of file
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 ca4d331d5..1fa25bfe5 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
@@ -1,25 +1,34 @@
package com.runnect.runnect.presentation.draw
-import android.app.AlertDialog
-import android.content.ContentValues
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.PointF
import android.net.Uri
import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.Toast
import androidx.activity.viewModels
import androidx.core.content.FileProvider
import androidx.core.view.isVisible
+import androidx.core.widget.addTextChangedListener
+import androidx.lifecycle.lifecycleScope
+import com.google.android.gms.location.FusedLocationProviderClient
+import com.google.android.gms.location.LocationServices
+import com.google.android.material.bottomsheet.BottomSheetDialog
import com.naver.maps.geometry.LatLng
import com.naver.maps.geometry.LatLngBounds
+import com.naver.maps.map.CameraAnimation
import com.naver.maps.map.CameraUpdate
+import com.naver.maps.map.LocationTrackingMode
import com.naver.maps.map.MapFragment
import com.naver.maps.map.NaverMap
import com.naver.maps.map.OnMapReadyCallback
+import com.naver.maps.map.overlay.InfoWindow
import com.naver.maps.map.overlay.Marker
import com.naver.maps.map.overlay.OverlayImage
import com.naver.maps.map.overlay.PathOverlay
@@ -30,6 +39,7 @@ import com.runnect.runnect.data.dto.CourseData
import com.runnect.runnect.data.dto.SearchResultEntity
import com.runnect.runnect.data.dto.UploadLatLng
import com.runnect.runnect.databinding.ActivityDrawBinding
+import com.runnect.runnect.databinding.BottomsheetRequireCourseNameBinding
import com.runnect.runnect.presentation.MainActivity
import com.runnect.runnect.presentation.countdown.CountDownActivity
import com.runnect.runnect.presentation.login.LoginActivity
@@ -41,6 +51,8 @@ import kotlinx.android.synthetic.main.custom_dialog_make_course.view.btn_run
import kotlinx.android.synthetic.main.custom_dialog_make_course.view.btn_storage
import kotlinx.android.synthetic.main.custom_dialog_require_login.view.btn_cancel
import kotlinx.android.synthetic.main.custom_dialog_require_login.view.btn_login
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.ByteArrayOutputStream
import java.io.File
@@ -52,8 +64,15 @@ import java.math.RoundingMode
class DrawActivity :
com.runnect.runnect.binding.BindingActivity(R.layout.activity_draw),
OnMapReadyCallback {
- private lateinit var naverMap: NaverMap
private lateinit var locationSource: FusedLocationSource
+ private lateinit var currentLocation: LatLng
+ private lateinit var fusedLocation: FusedLocationProviderClient//현재 위치 반환 객체 변수)
+
+ var isCustomLocationMode: Boolean = false
+ var isSearchLocationMode: Boolean = false
+ var isCurrentLocationMode: Boolean = false
+
+ private lateinit var naverMap: NaverMap
private lateinit var departureLatLng: LatLng
private lateinit var animDown: Animation
private lateinit var animUp: Animation
@@ -71,40 +90,37 @@ class DrawActivity :
private var isMarkerAvailable: Boolean = false
var isVisitorMode: Boolean = MainActivity.isVisitorMode
+ var isFirstInit: Boolean = true
+
+ private lateinit var bottomSheetBinding: BottomsheetRequireCourseNameBinding // Bottom Sheet 바인딩
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.model = viewModel
binding.lifecycleOwner = this
- if (::searchResult.isInitialized.not()) {
- intent?.let {
- searchResult =
- intent.getParcelableExtra(EXTRA_SEARCH_RESULT) ?: throw Exception("데이터가 존재하지 않습니다.")
-
- Timber.tag(ContentValues.TAG).d("searchResult : $searchResult")
- viewModel.searchResult.value = searchResult
- initView()
- courseFinish()
- addObserver()
- backButton()
- activateDrawCourse()
- }
- }
+ initMapView()
+ getSearchIntent()
+ addObserver()
+ backButton()
+ courseFinish()
}
- override fun onBackPressed() {
- finish()
- overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right)
+ private fun getSearchIntent() {
+ searchResult =
+ intent.getParcelableExtra(EXTRA_SEARCH_RESULT) ?: throw Exception("데이터가 존재하지 않습니다.")
}
- private fun initView() {
+ private fun initMapView() {
+ fusedLocation = LocationServices.getFusedLocationProviderClient(this) // 현위치 정보 제공
+
val fm = supportFragmentManager
val mapFragment =
fm.findFragmentById(R.id.mapView) as MapFragment? ?: MapFragment.newInstance().also {
fm.beginTransaction().add(R.id.mapView, it).commit()
}
mapFragment.getMapAsync(this)
+
locationSource = FusedLocationSource(
this, LOCATION_PERMISSION_REQUEST_CODE
)
@@ -112,28 +128,188 @@ class DrawActivity :
override fun onMapReady(map: NaverMap) {
naverMap = map
- naverMap.maxZoom = 18.0
- naverMap.minZoom = 10.0
+ initMode()
+ setLocationTrackingMode()
+ setCurrentLocationIcon()
+ setZoomControl()
+ setLocationChangedListener()
+ setCameraFinishedListener()
+ }
+ private fun initMode() {
+ when (searchResult.mode) {
+ "searchLocation" -> initSearchLocationMode()
+ "currentLocation" -> initCurrentLocationMode()
+ "customLocation" -> initCustomLocationMode()
+ }
+ }
+ private fun initSearchLocationMode() {
+ isSearchLocationMode = true
- //네이버 맵 sdk에 위치 정보 제공
- locationSource = FusedLocationSource(this@DrawActivity, LOCATION_PERMISSION_REQUEST_CODE)
- naverMap.locationSource = locationSource
+ with(binding){
+ tvGuide.isVisible = false
+ btnDraw.text = CREATE_COURSE
+ }
+
+ viewModel.searchResult.value = searchResult
+ viewModel.departureName.value =
+ searchResult.name //searchLocationMode일 땐 departureName 값 세팅해주는 부분이 따로 없어서 여기에 작성해놓음
+
+ setDepartureLatLng(
+ latLng = LatLng(
+ searchResult.locationLatLng!!.latitude, searchResult.locationLatLng!!.longitude
+ )
+ )
+ activateDrawCourse()
- drawCourse()
+ lifecycleScope.launch {
+ delay(500) //인위적으로 늦춰줌
+ if (::departureLatLng.isInitialized) {
+ drawCourse(departureLatLng = departureLatLng)
+ }
+ }
+ }
+ private fun initCurrentLocationMode() {
+ isCurrentLocationMode = true
+ isMarkerAvailable = true
+
+ with(binding) {
+ customDepartureMarker.isVisible = false
+ tvGuide.isVisible = false
+ }
+ showDrawGuide()
+ hideDeparture()
+ showDrawCourse()
+ }
+ private fun initCustomLocationMode() {
+ isCustomLocationMode = true
+
+ with(binding) {
+ customDepartureMarker.isVisible = true
+ customDepartureInfoWindow.isVisible = true
+ tvCustomDepartureGuideFrame.isVisible = true
+
+ btnPreStart.setOnClickListener {
+ isMarkerAvailable = true
+ showDrawGuide()
+ hideDeparture()
+ showDrawCourse()
+ drawCourse(departureLatLng = getCenterPosition())
+ hideFloatedDeparture()
+ }
+ }
+ }
+
+ private fun hideFloatedDeparture() {
+ with(binding) {
+ customDepartureMarker.isVisible = false
+ customDepartureInfoWindow.isVisible = false
+ }
+ }
+
+ private fun setDepartureLatLng(latLng: LatLng) {
+ departureLatLng = LatLng(
+ latLng.latitude, latLng.longitude
+ )
+ }
+
+ private fun getLocationInfoUsingLatLng(lat: Double, lon: Double) {
+ viewModel.getLocationInfoUsingLatLng(lat = lat, lon = lon)
+ }
+
+ fun setZoomControl() {
+ naverMap.maxZoom = 18.0
+ naverMap.minZoom = 10.0
val uiSettings = naverMap.uiSettings
uiSettings.isZoomControlEnabled = false
}
+ 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( //코스를 다 그린 후에도 계속 통신이 돌아서 리소스 낭비를 막기 위한 조치 필요
+ lat = centerLatLng.latitude, lon = centerLatLng.longitude
+ )
+ }
+ Timber.tag("카메라-끝").d("$centerLatLng") //위에 통신이 비동기로 돌아서 이게 먼저 찍힘.
+ }
+ }
+
+ fun getCenterPosition(): LatLng {
+ val cameraPosition = naverMap.cameraPosition
+ 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))
+
+ //같은 scope 안에 넣었으니 setDepartureLatLng 다음에 drawCourse가 실행되는 것이 보장됨
+ //이때 isFirstInit의 초기값을 true로 줘서 최초 1회는 실행되게 하고 이후 drawCourse 내에서 isFirstInit 값을 false로 바꿔줌
+ //뒤의 조건을 안 달아주면 다른 mode에서는 버튼을 클릭하기도 전에 drawCourse()가 돌 거라 안 됨.
+ if(isFirstInit && isCurrentLocationMode){
+ drawCourse(departureLatLng = departureLatLng)
+ }
+ }
+ }
+
+ private fun setLocationTrackingMode() {
+ naverMap.locationSource = locationSource
+
+ if (isCurrentLocationMode || isCustomLocationMode) {
+ naverMap.locationTrackingMode =
+ LocationTrackingMode.Follow //위치추적 모드 Follow - 자동으로 camera 이동
+ }
+ }
+
private fun courseFinish() {
binding.btnDraw.setOnClickListener {
-
if (isVisitorMode) {
requireVisitorLogin()
+ return@setOnClickListener
+ }
+ when {
+ isSearchLocationMode -> createMbr()
+ isCurrentLocationMode || isCustomLocationMode -> requireCourseNameDialog().show()
+ }
+ }
+ }
+
+ private fun requireCourseNameDialog(): BottomSheetDialog {
+ bottomSheetBinding = BottomsheetRequireCourseNameBinding.inflate(layoutInflater)
+ val bottomSheetView = bottomSheetBinding.root
+ val etCourseName = bottomSheetBinding.etCourseName
+ val btnCreateCourse = bottomSheetBinding.btnCreateCourse
+
+ etCourseName.addTextChangedListener {
+ if (!etCourseName.text.isNullOrEmpty()) {
+ btnCreateCourse.setBackgroundResource(R.drawable.radius_10_m1_button)
+ btnCreateCourse.isEnabled = true
+ viewModel.departureName.value = etCourseName.text.toString()
} else {
- createMbr()
+ btnCreateCourse.setBackgroundResource(R.drawable.radius_10_g3_button)
+ btnCreateCourse.isEnabled = false
}
+ Timber.tag("EditTextValue").d("${viewModel.departureName.value}")
}
+
+ btnCreateCourse.setOnClickListener {
+ createMbr()
+ }
+
+ val bottomSheetDialog = BottomSheetDialog(this)
+ bottomSheetDialog.setContentView(bottomSheetView)
+
+ return bottomSheetDialog
}
private fun activateDrawCourse() {
@@ -173,6 +349,8 @@ class DrawActivity :
tvDeparture.startAnimation(animUp)
viewTopFrame.isVisible = false
tvDeparture.isVisible = false
+ tvGuide.isVisible = false
+ tvCustomDepartureGuideFrame.isVisible = false
//Bottom invisible
viewBottomSheetFrame.startAnimation(animDown)
@@ -190,6 +368,17 @@ class DrawActivity :
private fun addObserver() {
observeIsBtnAvailable()
observeDrawState()
+
+ viewModel.reverseGeocodingResult.observe(this) {
+ val buildingName = viewModel.reverseGeocodingResult.value!!.buildingName
+
+ if (buildingName.isEmpty()) {
+ binding.tvPlaceName.text = CUSTOM_DEPARTURE
+ } else binding.tvPlaceName.text = buildingName
+
+ binding.tvPlaceAddress.text =
+ viewModel.reverseGeocodingResult.value?.fullAddress ?: "fail"
+ }
}
private fun activateMarkerBackBtn() {
@@ -229,7 +418,7 @@ class DrawActivity :
binding.indeterminateBar.isVisible = false
}
- private fun observeDrawState() {
+ private fun observeDrawState() { //분기 처리를 더 해줘야 함. 서버에서 400 날아오는데 이게 success로 빠져서 '코스 생성 완료' 팝업이 뜨고 있음.
viewModel.drawState.observe(this) {
when (it) {
UiState.Empty -> hideLoadingBar()
@@ -263,7 +452,7 @@ class DrawActivity :
publicCourseId = null,
touchList = touchList,
startLatLng = departureLatLng,
- departure = searchResult.name,
+ departure = viewModel.departureName.value!!,
distance = viewModel.distanceSum.value!!,
image = captureUri.toString(),
dataFrom = "fromDrawCourse"
@@ -315,56 +504,63 @@ class DrawActivity :
}
}
-
- //카메라 위치 변경 함수
private fun cameraUpdate(location: Any) {
- //이건 맨 처음 지도 켤 때 startLocation으로 위치 옮길 때 사용
+ //맨 처음 지도 켤 때 startLocation으로 위치 옮길 때 사용
if (location is LatLng) {
- val cameraUpdate = CameraUpdate.scrollTo(location)
+ val cameraUpdate = CameraUpdate.scrollTo(location).animate(CameraAnimation.Easing)
naverMap.moveCamera(cameraUpdate)
}
- //이건 카메라 이동해서 캡쳐할 때 사용
+ //카메라 이동해서 캡쳐할 때 사용
else if (location is LatLngBounds) {
val cameraUpdate = CameraUpdate.fitBounds(location)
naverMap.moveCamera(cameraUpdate)
}
}
- private fun drawCourse() {
- createDepartureMarker()
+ private fun drawCourse(departureLatLng: LatLng) {
+ isFirstInit = false
+ createDepartureMarker(departureLatLng = departureLatLng)
createRouteMarker()
deleteRouteMarker()
}
- private fun createDepartureMarker() {
- setDepartureLatLng()
- setDepartureMarker()
- addDepartureToCoords()
- addDepartureToCalcDistanceList()
+ private fun createDepartureMarker(departureLatLng: LatLng) {
+ setDepartureMarker(departureLatLng = departureLatLng)
+ addDepartureToCoords(departureLatLng = departureLatLng)
+ addDepartureToCalcDistanceList(departureLatLng = departureLatLng)
}
- private fun setDepartureLatLng() {
- departureLatLng = LatLng(
- searchResult.locationLatLng.latitude, searchResult.locationLatLng.longitude
- )
- }
-
- private fun setDepartureMarker() {
+ private fun setDepartureMarker(departureLatLng: LatLng) {
val departureMarker = Marker()
departureMarker.position = LatLng(departureLatLng.latitude, departureLatLng.longitude)
- departureMarker.anchor = PointF(0.5f, 0.7f)
- departureMarker.icon = OverlayImage.fromResource(R.drawable.marker_departure)
+ departureMarker.anchor = PointF(0.5f, 0.5f)
+ departureMarker.icon = OverlayImage.fromResource(R.drawable.runnect_marker)
departureMarker.map = naverMap
- cameraUpdate(
- LatLng(departureLatLng.latitude, departureLatLng.longitude)
- )
+
+ if (isSearchLocationMode) {
+ cameraUpdate(
+ LatLng(departureLatLng.latitude, departureLatLng.longitude)
+ ) // 현위치에서 출발할 때 이것 때문에 트랙킹 모드 활성화 시 카메라 이동하는 게 묻혔음
+ }
+ setCustomInfoWindow(marker = departureMarker)
+ }
+
+ private fun setCustomInfoWindow(marker: Marker) {
+ val infoWindow = InfoWindow()
+ infoWindow.adapter = object : InfoWindow.ViewAdapter() {
+ override fun getView(p0: InfoWindow): View {
+ return LayoutInflater.from(this@DrawActivity)
+ .inflate(R.layout.custom_info_window, binding.root as ViewGroup, false)
+ }
+ }
+ infoWindow.open(marker)
}
- private fun addDepartureToCoords() {
+ private fun addDepartureToCoords(departureLatLng: LatLng) {
coords.add(LatLng(departureLatLng.latitude, departureLatLng.longitude))
}
- private fun addDepartureToCalcDistanceList() {
+ private fun addDepartureToCalcDistanceList(departureLatLng: LatLng) {
calcDistanceList.add(
LatLng(
departureLatLng.latitude, departureLatLng.longitude
@@ -374,16 +570,17 @@ class DrawActivity :
private fun createRouteMarker() {
naverMap.setOnMapClickListener { _, coord ->
- if (isMarkerAvailable) {
- viewModel.isBtnAvailable.value = true
- if (touchList.size < MAX_MARKER_NUM) {
- addCoordsToTouchList(coord)
- setRouteMarker(coord)
- generateRouteLine(coord)
- calcDistance()
- } else {
- Toast.makeText(this, "마커는 20개까지 생성 가능합니다", Toast.LENGTH_SHORT).show()
- }
+ if (!isMarkerAvailable) return@setOnMapClickListener
+
+ viewModel.isBtnAvailable.value = true
+
+ if (touchList.size < MAX_MARKER_NUM) {
+ addCoordsToTouchList(coord)
+ setRouteMarker(coord)
+ generateRouteLine(coord)
+ calcDistance()
+ } else {
+ Toast.makeText(this, NOTIFY_LIMIT_MARKER_NUM, Toast.LENGTH_SHORT).show()
}
}
}
@@ -461,7 +658,6 @@ class DrawActivity :
}
private fun calcDistance() {
-
for (i in 0 until touchList.size) {
if (!calcDistanceList.contains(touchList[i])) {
calcDistanceList.add(touchList[i])
@@ -512,14 +708,24 @@ class DrawActivity :
UploadLatLng(latLng.latitude, latLng.longitude)
}
viewModel.path.value = uploadLatLngList
- viewModel.departureAddress.value = searchResult.fullAddress
- viewModel.departureName.value = searchResult.name
+
+ when {
+ isSearchLocationMode -> {
+ viewModel.departureAddress.value = searchResult.fullAddress
+ viewModel.departureName.value = searchResult.name
+ }
+
+ isCurrentLocationMode || isCustomLocationMode -> {
+ viewModel.departureAddress.value =
+ viewModel.reverseGeocodingResult.value?.fullAddress
+ }
+ }
+
}
// Get uri of images from camera function
private fun getImageUri(inImage: Bitmap): Uri {
-
- val tempFile = File.createTempFile("temprentpk", ".png")
+ val tempFile = File.createTempFile("CreatedCourse", ".png")
val bytes = ByteArrayOutputStream()
inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes)
val bitmapData = bytes.toByteArray()
@@ -535,6 +741,11 @@ class DrawActivity :
return uri
}
+ override fun onBackPressed() {
+ finish()
+ overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right)
+ }
+
companion object {
private const val LOCATION_PERMISSION_REQUEST_CODE = 1000
const val MAX_MARKER_NUM = 20
@@ -543,5 +754,8 @@ class DrawActivity :
const val EXTRA_SEARCH_RESULT = "searchResult"
const val EXTRA_COURSE_DATA = "CourseData"
const val EXTRA_FRAGMENT_REPLACEMENT_DIRECTION = "fragmentReplacementDirection"
+ const val CUSTOM_DEPARTURE = "내가 설정한 출발지"
+ const val NOTIFY_LIMIT_MARKER_NUM = "마커는 20개까지 생성 가능합니다"
+ const val CREATE_COURSE = "완성하기"
}
}
\ No newline at end of file
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 905d88c63..8149dc892 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
@@ -5,10 +5,12 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.runnect.runnect.data.dto.LocationData
import com.runnect.runnect.data.dto.SearchResultEntity
import com.runnect.runnect.data.dto.UploadLatLng
import com.runnect.runnect.data.dto.response.ResponsePostCourseDto
import com.runnect.runnect.domain.CourseRepository
+import com.runnect.runnect.domain.ReverseGeocodingRepository
import com.runnect.runnect.presentation.state.UiState
import com.runnect.runnect.util.ContentUriRequestBody
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -23,7 +25,12 @@ import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
-class DrawViewModel @Inject constructor(val courseRepository: CourseRepository) : ViewModel() {
+class DrawViewModel @Inject constructor(
+ val courseRepository: CourseRepository,
+ val reverseGeocodingRepository: ReverseGeocodingRepository
+) : ViewModel() {
+
+ val editTextValue = MutableLiveData()
private var _drawState = MutableLiveData(UiState.Empty)
val drawState: LiveData
@@ -37,6 +44,8 @@ class DrawViewModel @Inject constructor(val courseRepository: CourseRepository)
val departureName = MutableLiveData()
val isBtnAvailable = MutableLiveData(false)
+ val reverseGeocodingResult = MutableLiveData()
+
private val _image = MutableLiveData()
val image: LiveData
@@ -90,7 +99,7 @@ class DrawViewModel @Inject constructor(val courseRepository: CourseRepository)
data = RequestBody(
path = path.value!!,
distance = distanceSum.value!!,
- departureAddress = departureAddress.value!!,
+ departureAddress = departureAddress.value!!, //커스텀의 경우 지금 여기에 들어가는 게 아무것도 없음.
departureName = departureName.value!!
)
)
@@ -106,6 +115,22 @@ class DrawViewModel @Inject constructor(val courseRepository: CourseRepository)
}
}
+ fun getLocationInfoUsingLatLng(lat: Double, lon: Double) {
+ viewModelScope.launch {
+ runCatching {
+ reverseGeocodingRepository.getLocationInfoUsingLatLng(
+ lat = lat, lon = lon
+ )
+ }.onSuccess {
+ Timber.tag(ContentValues.TAG).d("통신success")
+ reverseGeocodingResult.value = it
+ }.onFailure {
+ Timber.tag(ContentValues.TAG).d("통신failure : ${it}")
+ errorMessage.value = it.message
+ }
+ }
+ }
+
private fun RequestBody(
path: List,
distance: Float,
diff --git a/app/src/main/java/com/runnect/runnect/presentation/search/SearchActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/search/SearchActivity.kt
index a4dd3f7ae..5282a2878 100644
--- a/app/src/main/java/com/runnect/runnect/presentation/search/SearchActivity.kt
+++ b/app/src/main/java/com/runnect/runnect/presentation/search/SearchActivity.kt
@@ -85,12 +85,11 @@ class SearchActivity :
startActivity(
Intent(this, DrawActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
- putExtra(EXTRA_SEARCH_RESULT, item)
+ putExtra(EXTRA_SEARCH_RESULT, item) //mode == "searchLocation"
}
)
}
-
private fun showEmptyView() {
with(binding) {
ivNoSearchResult.isVisible = true
@@ -179,6 +178,38 @@ class SearchActivity :
return false
}
})
+
+ binding.cvStartCurrentLocation.setOnClickListener {
+ startCurrentLocation()
+ }
+
+ binding.cvStartCustomLocation.setOnClickListener {
+ startCustomLocation()
+ }
+ }
+
+ fun startCurrentLocation() {
+ startActivity(
+ Intent(this, DrawActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ putExtra(
+ EXTRA_SEARCH_RESULT,
+ SearchResultEntity(fullAddress = "", name = "", locationLatLng = null, mode = "currentLocation")
+ )
+ }
+ )
+ }
+
+ fun startCustomLocation() {
+ startActivity(
+ Intent(this, DrawActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ putExtra(
+ EXTRA_SEARCH_RESULT,
+ SearchResultEntity(fullAddress = "", name = "", locationLatLng = null, mode = "customLocation")
+ )
+ }
+ )
}
//키보드 밖 터치 시, 키보드 내림
diff --git a/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt b/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt
index 1b961fec6..5b503855e 100644
--- a/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt
+++ b/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt
@@ -11,6 +11,7 @@ import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.appcompat.widget.AppCompatButton
+import androidx.appcompat.widget.AppCompatEditText
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.fragment.app.Fragment
import com.google.android.material.bottomsheet.BottomSheetDialog
@@ -133,6 +134,11 @@ fun BottomSheetDialog.setEditBottomSheetClickListener(listener: (which: LinearLa
}
}
+fun BottomSheetDialog.handleEditTextValue(){
+ this.setOnShowListener {
+ val editText = this.layout_edit_frame
+ }
+}
fun Context.getStampResId(
stampId: String?,
diff --git a/app/src/main/res/drawable/draw_custom_departure_tv_guide.xml b/app/src/main/res/drawable/draw_custom_departure_tv_guide.xml
new file mode 100644
index 000000000..954cbfe81
--- /dev/null
+++ b/app/src/main/res/drawable/draw_custom_departure_tv_guide.xml
@@ -0,0 +1,18 @@
+
+
+ -
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_draw.xml b/app/src/main/res/layout/activity_draw.xml
index 7e6c9ed88..c6903bc28 100644
--- a/app/src/main/res/layout/activity_draw.xml
+++ b/app/src/main/res/layout/activity_draw.xml
@@ -15,6 +15,32 @@
android:layout_height="match_parent"
tools:context=".views.location_enroll.LocationEnrollFragment">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/bottomsheet_require_course_name.xml b/app/src/main/res/layout/bottomsheet_require_course_name.xml
new file mode 100644
index 000000000..d09df3281
--- /dev/null
+++ b/app/src/main/res/layout/bottomsheet_require_course_name.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/custom_info_window.xml b/app/src/main/res/layout/custom_info_window.xml
new file mode 100644
index 000000000..1b13a92f0
--- /dev/null
+++ b/app/src/main/res/layout/custom_info_window.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cc7fe168e..028311d2a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -54,7 +54,7 @@
km
시작하기
시작하기
- 지역과 키워드 중심으로 검색해보세요
+ 출발지를 설정해주세요
검색결과가 없습니다\n검색어를 다시 확인해주세요
수고하셨습니다! 러닝을 완료했어요!
기록 보러 가기
@@ -115,4 +115,7 @@
닉네임을 입력해주세요
시작하기
장기간 미접속으로 인해 재로그인이 필요합니다.
+ 지도를 움직여 출발지를 설정해주세요
+ 지도에서 선택
+ 다음으로
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index d4aee40fa..2b0e7c8ce 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -14,6 +14,8 @@
- @color/transparent_00
- false
+
+ - @style/AppBottomSheetDialogTheme
-
+
@@ -31,4 +33,17 @@
- @android:color/transparent
+
+
+
+
\ No newline at end of file