diff --git a/app/src/main/java/com/runnect/runnect/presentation/coursemain/CourseMainFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/coursemain/CourseMainFragment.kt index e87a35111..12b398503 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/coursemain/CourseMainFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/coursemain/CourseMainFragment.kt @@ -2,12 +2,12 @@ package com.runnect.runnect.presentation.coursemain import android.Manifest import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle import android.view.View +import androidx.core.content.ContextCompat import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices -import com.gun0912.tedpermission.PermissionListener -import com.gun0912.tedpermission.normal.TedPermission import com.naver.maps.geometry.LatLng import com.naver.maps.map.CameraAnimation import com.naver.maps.map.CameraUpdate @@ -21,6 +21,8 @@ import com.runnect.runnect.R import com.runnect.runnect.binding.BindingFragment import com.runnect.runnect.databinding.FragmentCourseMainBinding import com.runnect.runnect.presentation.search.SearchActivity +import com.runnect.runnect.util.extension.PermissionUtil +import com.runnect.runnect.util.extension.showToast class CourseMainFragment : @@ -35,18 +37,17 @@ class CourseMainFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) init() - getCurrentLocation() - drawCourseButton() + checkAndRequestLocationPermission() } private fun init() { fusedLocation = LocationServices.getFusedLocationProviderClient(requireActivity()) - requestPermission() + initView() + initCurrentLocationButtonClickListener() + initDrawCourseButtonClickListener() } private fun initView() { - - //MapFragment 추가 val fm = childFragmentManager val mapFragment = fm.findFragmentById(R.id.mapView) as MapFragment? ?: MapFragment.newInstance().also { @@ -57,13 +58,42 @@ class CourseMainFragment : } - private fun getCurrentLocation() { + private fun checkAndRequestLocationPermission() { + if (isLocationPermissionGranted()) { + if (!::naverMap.isInitialized) { + initView() + } + } else { + context?.let { + PermissionUtil.requestLocationPermission( + context = it, + onPermissionGranted = { updateCamera(currentLocation) }, + onPermissionDenied = { showPermissionDeniedToast() }, + permissionType = PermissionUtil.PermissionType.LOCATION + ) + } + } + } + + + private fun initCurrentLocationButtonClickListener() { binding.btnCurrentLocation.setOnClickListener { - cameraUpdate(currentLocation) + if (isLocationPermissionGranted()) { + updateCamera(currentLocation) + } else { + context?.let { + PermissionUtil.requestLocationPermission( + context = it, + onPermissionGranted = { updateCamera(currentLocation) }, + onPermissionDenied = { showPermissionDeniedToast() }, + permissionType = PermissionUtil.PermissionType.LOCATION + ) + } + } } } - private fun drawCourseButton() { + private fun initDrawCourseButtonClickListener() { binding.btnDraw.setOnClickListener { val intent = Intent(activity, SearchActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) @@ -77,8 +107,10 @@ class CourseMainFragment : naverMap.minZoom = 10.0 map.locationSource = locationSource - map.locationTrackingMode = LocationTrackingMode.Follow //위치추적 모드 Follow + if (isLocationPermissionGranted()) { + map.locationTrackingMode = LocationTrackingMode.Follow //위치추적 모드 Follow + } //네이버 맵 sdk에 위치 정보 제공 locationSource = FusedLocationSource( @@ -103,40 +135,27 @@ class CourseMainFragment : locationOverlay.icon = OverlayImage.fromResource(R.drawable.current_location) } - private fun requestPermission() { - TedPermission.create() - .setPermissionListener(object : PermissionListener { - override fun onPermissionGranted() { //요청 승인 시 - initView() //지도 뷰 표시 - } - - override fun onPermissionDenied(deniedPermissions: MutableList?) { //요청 거부 시 - naverMap.locationTrackingMode = LocationTrackingMode.None - onDestroy() //앱 종료 - } - }) - .setRationaleTitle(PERMISSION_TITLE) - .setRationaleMessage(PERMISSION_CONTENT) - .setDeniedMessage(PERMISSION_GUIDE) - .setPermissions( - Manifest.permission.POST_NOTIFICATIONS, // 러닝 시 notification icon 띄우기 - Manifest.permission.ACCESS_COARSE_LOCATION, + private fun isLocationPermissionGranted(): Boolean { + return context?.let { + ContextCompat.checkSelfPermission( + it, Manifest.permission.ACCESS_FINE_LOCATION ) - .check() + } == PackageManager.PERMISSION_GRANTED } - private fun cameraUpdate(location: LatLng) { + private fun updateCamera(location: LatLng) { val cameraUpdate = CameraUpdate.scrollTo(LatLng(location.latitude, location.longitude)) .animate(CameraAnimation.Easing) naverMap.moveCamera(cameraUpdate) } + private fun showPermissionDeniedToast() { + showToast(getString(R.string.location_permission_denied)) + } + companion object { private const val LOCATION_PERMISSION_REQUEST_CODE = 1000 - const val PERMISSION_TITLE = "위치권한 요청" - const val PERMISSION_CONTENT = "코스의 출발지 설정과 러닝 트래킹을 위해 현재 위치 정보를 사용하도록 허용합니다." - const val PERMISSION_GUIDE = "권한을 허용해주세요. [설정] > [앱 및 알림] > [고급] > [앱 권한]" } } \ 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 5af92a7c6..64742a410 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 @@ -20,7 +20,6 @@ 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.google.android.material.internal.ViewUtils.hideKeyboard import com.naver.maps.geometry.LatLng import com.naver.maps.geometry.LatLngBounds import com.naver.maps.map.CameraAnimation @@ -47,8 +46,10 @@ import com.runnect.runnect.presentation.countdown.CountDownActivity import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.DepartureSetMode import com.runnect.runnect.util.custom.dialog.RequireLoginDialogFragment +import com.runnect.runnect.util.extension.PermissionUtil import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.setActivityDialog +import com.runnect.runnect.util.extension.showToast import com.runnect.runnect.util.multipart.ContentUriRequestBody import dagger.hilt.android.AndroidEntryPoint import kotlinx.android.synthetic.main.custom_dialog_make_course.view.btn_run @@ -186,7 +187,6 @@ class DrawActivity : BindingActivity(R.layout.activity_draw private fun initCustomLocationMode() { isCustomLocationMode = true - with(binding) { customDepartureMarker.isVisible = true customDepartureInfoWindow.isVisible = true @@ -197,7 +197,9 @@ class DrawActivity : BindingActivity(R.layout.activity_draw showDrawGuide() hideDeparture() showDrawCourse() - drawCourse(departureLatLng = getCenterPosition()) + getCenterPosition().apply { + departureLatLng = this + }.let(::drawCourse) hideFloatedDeparture() } } @@ -308,14 +310,27 @@ class DrawActivity : BindingActivity(R.layout.activity_draw bottomSheetDialog.setContentView(bottomSheetView) btnCreateCourse.setOnClickListener { - hideKeyboard(etCourseName) - bottomSheetDialog.dismiss() - createMBR() + this.let { + PermissionUtil.requestLocationPermission( + context = it, + onPermissionGranted = { + hideKeyboard(etCourseName) + bottomSheetDialog.dismiss() + createMBR() + }, + onPermissionDenied = { showPermissionDeniedToast() }, + permissionType = PermissionUtil.PermissionType.LOCATION + ) + } } return bottomSheetDialog } + private fun showPermissionDeniedToast() { + showToast(getString(R.string.location_permission_denied)) + } + private fun activateDrawCourse() { binding.btnPreStart.setOnClickListener { isMarkerAvailable = true diff --git a/app/src/main/java/com/runnect/runnect/presentation/mydrawdetail/MyDrawDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/mydrawdetail/MyDrawDetailActivity.kt index 24c2eac70..16344e6d2 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/mydrawdetail/MyDrawDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/mydrawdetail/MyDrawDetailActivity.kt @@ -15,8 +15,10 @@ import com.runnect.runnect.data.dto.response.ResponseGetMyDrawDetail import com.runnect.runnect.databinding.ActivityMyDrawDetailBinding import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.countdown.CountDownActivity +import com.runnect.runnect.util.extension.PermissionUtil import com.runnect.runnect.util.extension.navigateToPreviousScreenWithAnimation import com.runnect.runnect.util.extension.setActivityDialog +import com.runnect.runnect.util.extension.showToast import dagger.hilt.android.AndroidEntryPoint import kotlinx.android.synthetic.main.custom_dialog_delete.view.btn_delete_no import kotlinx.android.synthetic.main.custom_dialog_delete.view.btn_delete_yes @@ -40,7 +42,7 @@ class MyDrawDetailActivity : getMyDrawDetail() backButton() addObserver() - toCountDownButton() + initDrawButtonClickListener() deleteButton() } @@ -88,14 +90,30 @@ class MyDrawDetailActivity : viewModel.getMyDrawDetail(courseId = courseId) } - fun toCountDownButton() { + + private fun initDrawButtonClickListener() { binding.btnMyDrawDetailRun.setOnClickListener { - startActivity(Intent(this, CountDownActivity::class.java).apply { - putExtra(EXTRA_COURSE_DATA, viewModel.myDrawToRunData.value) - }) + this.let { + PermissionUtil.requestLocationPermission( + context = it, + onPermissionGranted = { navigateToCountDown() }, + onPermissionDenied = { showPermissionDeniedToast() }, + permissionType = PermissionUtil.PermissionType.LOCATION + ) + } } } + private fun showPermissionDeniedToast() { + showToast(getString(R.string.location_permission_denied)) + } + + private fun navigateToCountDown() { + startActivity(Intent(this, CountDownActivity::class.java).apply { + putExtra(EXTRA_COURSE_DATA, viewModel.myDrawToRunData.value) + }) + } + fun addObserver() { observeGetResult() registerBackPressedCallback() @@ -158,7 +176,7 @@ class MyDrawDetailActivity : } } - fun deleteCourse() { + private fun deleteCourse() { viewModel.deleteMyDrawCourse(selectList) } 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 0dea2a8ca..7b00996db 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 @@ -7,7 +7,6 @@ import android.os.Bundle import android.view.KeyEvent import android.view.MotionEvent import android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH -import android.view.inputmethod.InputMethodManager import android.widget.TextView import androidx.activity.viewModels import androidx.core.content.ContextCompat @@ -22,13 +21,15 @@ import com.runnect.runnect.presentation.draw.DrawActivity import com.runnect.runnect.presentation.search.adapter.SearchAdapter import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.callback.listener.OnSearchItemClick +import com.runnect.runnect.util.extension.PermissionUtil import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.showKeyboard +import com.runnect.runnect.util.extension.showToast import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber @AndroidEntryPoint -class SearchActivity: BindingActivity(R.layout.activity_search), +class SearchActivity : BindingActivity(R.layout.activity_search), OnSearchItemClick { val viewModel: SearchViewModel by viewModels() private lateinit var searchAdapter: SearchAdapter @@ -159,7 +160,7 @@ class SearchActivity: BindingActivity(R.layout.activity_s override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { if (actionId == IME_ACTION_SEARCH) { val keyword = binding.etSearch.text - if(!keyword.isNullOrBlank()){ + if (!keyword.isNullOrBlank()) { searchKeyword(keyword.toString()) hideKeyboard(binding.etSearch) } @@ -170,7 +171,14 @@ class SearchActivity: BindingActivity(R.layout.activity_s }) binding.cvStartCurrentLocation.setOnClickListener { - startCurrentLocation() + this.let { + PermissionUtil.requestLocationPermission( + context = it, + onPermissionGranted = { startCurrentLocation() }, + onPermissionDenied = { showPermissionDeniedToast() }, + permissionType = PermissionUtil.PermissionType.LOCATION + ) + } } binding.cvStartCustomLocation.setOnClickListener { @@ -178,13 +186,22 @@ class SearchActivity: BindingActivity(R.layout.activity_s } } + private fun showPermissionDeniedToast() { + showToast(getString(R.string.location_permission_denied)) + } + private 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") + SearchResultEntity( + fullAddress = "", + name = "", + locationLatLng = null, + mode = "currentLocation" + ) ) } ) @@ -196,7 +213,12 @@ class SearchActivity: BindingActivity(R.layout.activity_s addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) putExtra( EXTRA_SEARCH_RESULT, - SearchResultEntity(fullAddress = "", name = "", locationLatLng = null, mode = "customLocation") + SearchResultEntity( + fullAddress = "", + name = "", + locationLatLng = null, + mode = "customLocation" + ) ) } ) diff --git a/app/src/main/java/com/runnect/runnect/util/extension/PermissionExt.kt b/app/src/main/java/com/runnect/runnect/util/extension/PermissionExt.kt new file mode 100644 index 000000000..069465d03 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/util/extension/PermissionExt.kt @@ -0,0 +1,68 @@ +package com.runnect.runnect.util.extension + +import android.Manifest +import android.content.Context +import android.widget.Toast +import com.gun0912.tedpermission.PermissionListener +import com.gun0912.tedpermission.normal.TedPermission +import com.runnect.runnect.R + +object PermissionUtil { + fun requestLocationPermission( + context: Context, + onPermissionGranted: () -> Unit, + onPermissionDenied: () -> Unit = { + Toast.makeText( + context, + R.string.common_permission_denied, + Toast.LENGTH_SHORT + ).show() + }, + permissionType: PermissionType + ) { + val permission = setUpPermissionByType(permissionType) + TedPermission.create() + .setPermissionListener(object : PermissionListener { + override fun onPermissionGranted() { + onPermissionGranted() + } + + override fun onPermissionDenied(deniedPermissions: MutableList?) { + onPermissionDenied() + } + }) + .setRationaleTitle(permission.title) + .setRationaleMessage(permission.content) + .setDeniedMessage(permission.guide) + .setPermissions(*permission.permissions.toTypedArray()) + .check() + } + + enum class PermissionType { + LOCATION, + // 필요한 권한 있을 시 추가 + } + + data class PermissionInfo( + val permissions: List, + val title: Int, + val content: Int, + val guide: Int + ) + + private fun setUpPermissionByType(permissionType: PermissionType): PermissionInfo { + return when (permissionType) { + PermissionType.LOCATION -> { + PermissionInfo( + listOf( + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION + ), + R.string.location_permission_title, + R.string.location_permission_content, + R.string.location_permission_guide + ) + } + } + } +} \ 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 8564f27dd..1db596b9e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -166,4 +166,11 @@ 나에게 최적화된 코스를 찾아보세요 최신순 스크랩순 + + 권한이 거부되었습니다. + + 위치 권한이 거부되었습니다. + 위치권한 요청 + 코스의 출발지 설정과 러닝 트래킹을 위해 현재 위치 정보를 사용하도록 허용합니다. + 권한을 허용해주세요. [설정] > [앱 및 알림] > [고급] > [앱 권한] \ No newline at end of file