Skip to content

Commit

Permalink
Merge pull request #28 from TeamPINGLE/feat-main-map-default-view
Browse files Browse the repository at this point in the history
[feat] 메인 지도뷰 디폴트 화면 구현
  • Loading branch information
jihyunniiii authored Jan 5, 2024
2 parents ddc22d0 + 395b948 commit 812b6d9
Show file tree
Hide file tree
Showing 19 changed files with 424 additions and 16 deletions.
7 changes: 7 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "BASE_URL", properties["base.url"].toString())
buildConfigField("String", "ACCESS_TOKEN", properties["access.token"].toString())
buildConfigField("String", "NAVER_MAP_CLIENT_ID", properties["naver.map.client.id"].toString())
manifestPlaceholders["IO_SENTRY_DSN"] = properties["io.sentry.dsn"] as String
}

Expand Down Expand Up @@ -89,6 +90,12 @@ dependencies {
implementation(libs.bundles.okhttp)
implementation(libs.bundles.retrofit)
implementation(libs.kotlin.serialization.json)

// Naver Map
implementation(libs.naver.maps)

// Location
implementation(libs.play.services.location)
}

ktlint {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<application
android:name=".PingleApp"
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/org/sopt/pingle/PingleApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package org.sopt.pingle

import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import com.naver.maps.map.NaverMapSdk
import dagger.hilt.android.HiltAndroidApp
import org.sopt.pingle.BuildConfig.NAVER_MAP_CLIENT_ID
import org.sopt.pingle.util.PingleDebugTree
import timber.log.Timber

Expand All @@ -13,6 +15,7 @@ class PingleApp : Application() {

setTimber()
setDarkMode()
setNaverMap()
}

private fun setTimber() {
Expand All @@ -22,4 +25,9 @@ class PingleApp : Application() {
private fun setDarkMode() {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}

private fun setNaverMap() {
NaverMapSdk.getInstance(this).client =
NaverMapSdk.NaverCloudPlatformClient(NAVER_MAP_CLIENT_ID)
}
}
9 changes: 9 additions & 0 deletions app/src/main/java/org/sopt/pingle/domain/model/PinEntity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.sopt.pingle.domain.model

data class PinEntity(
val id: Long,
val x: Double,
val y: Double,
val category: String,
val meetingCount: Int
)
21 changes: 21 additions & 0 deletions app/src/main/java/org/sopt/pingle/presentation/mapper/PinMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.sopt.pingle.presentation.mapper

import com.naver.maps.geometry.LatLng
import com.naver.maps.map.overlay.Marker
import com.naver.maps.map.overlay.OverlayImage
import org.sopt.pingle.R
import org.sopt.pingle.domain.model.PinEntity
import org.sopt.pingle.presentation.type.CategoryType

fun PinEntity.toMarker(): Marker = Marker().apply {
position = LatLng(y, x)
isHideCollidedMarkers = true
icon = OverlayImage.fromResource(
when (category) {
CategoryType.PLAY.toString() -> R.drawable.ic_map_marker_play_small
CategoryType.STUDY.toString() -> R.drawable.ic_map_marker_study_small
CategoryType.MULTI.toString() -> R.drawable.ic_map_marker_multi_small
else -> R.drawable.ic_map_marker_other_small
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ class MainActivity : BindingActivity<ActivityMainBinding>(R.layout.activity_main
}

private inline fun <reified T : Fragment> navigateToFragment() {
supportFragmentManager.commit {
replace<T>(R.id.fcv_main_all_navi, T::class.java.canonicalName)
if (supportFragmentManager.findFragmentById(R.id.fcv_main_all_navi) !is T) {
supportFragmentManager.commit {
replace<T>(R.id.fcv_main_all_navi, T::class.java.canonicalName)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,179 @@
package org.sopt.pingle.presentation.ui.main.home.map

import android.Manifest
import android.content.pm.PackageManager
import android.location.Location
import android.os.Bundle
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.fragment.app.add
import androidx.fragment.app.commit
import androidx.fragment.app.viewModels
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.location.LocationServices
import com.naver.maps.geometry.LatLng
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.OverlayImage
import com.naver.maps.map.util.FusedLocationSource
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.sopt.pingle.R
import org.sopt.pingle.databinding.FragmentMapBinding
import org.sopt.pingle.presentation.mapper.toMarker
import org.sopt.pingle.presentation.type.CategoryType
import org.sopt.pingle.util.base.BindingFragment
import org.sopt.pingle.util.component.PingleChip
import org.sopt.pingle.util.fragment.showToast

class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map), OnMapReadyCallback {
private val mapViewModel by viewModels<MapViewModel>()
private lateinit var naverMap: NaverMap
private lateinit var locationSource: FusedLocationSource

class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

initMap()
initLayout()
addListeners()
collectData()
setLocationTrackingMode()
}

override fun onMapReady(naverMap: NaverMap) {
this.naverMap = naverMap.apply {
isNightModeEnabled = true
mapType = NaverMap.MapType.Navi

with(uiSettings) {
isZoomControlEnabled = false
isScaleBarEnabled = false
}
}

makeMarkers()
}

private fun initMap() {
val mapFragment =
childFragmentManager.findFragmentById(R.id.fragment_map_naver_map) as MapFragment?
?: MapFragment.newInstance().also {
childFragmentManager.commit {
add<MapFragment>(R.id.fragment_map_naver_map)
setReorderingAllowed(true)
}
}

mapFragment.getMapAsync(this@MapFragment)
}

private fun initLayout() {
with(binding) {
chipMapCategoryPlay.setChipCategoryType(CategoryType.PLAY)
chipMapCategoryStudy.setChipCategoryType(CategoryType.STUDY)
chipMapCategoryMulti.setChipCategoryType(CategoryType.MULTI)
chipMapCategoryOthers.setChipCategoryType(CategoryType.OTHER)
}
}

private fun addListeners() {
binding.fabMapHere.setOnClickListener {
if (::locationSource.isInitialized) {
locationSource.lastLocation?.let { location -> moveMapCamera(location) }
}
}

binding.cgMapCategory.setOnCheckedStateChangeListener { group, checkedIds ->
mapViewModel.setCategory(
category = checkedIds.getOrNull(SINGLE_SELECTION)
?.let { group.findViewById<PingleChip>(it).categoryType }
)
}
}

private fun collectData() {
mapViewModel.category.flowWithLifecycle(lifecycle).onEach {
// TODO 서버 통신 구현 시 삭제 예정
showToast(it?.name ?: "null")
}.launchIn(lifecycleScope)
}

private fun setLocationTrackingMode() {
if (LOCATION_PERMISSIONS.any { permission ->
ContextCompat.checkSelfPermission(
requireContext(),
permission
) == PackageManager.PERMISSION_GRANTED
}
) {
locationSource = FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE)
} else {
requestLocationPermission()
}

LocationServices.getFusedLocationProviderClient(requireContext()).lastLocation.addOnSuccessListener { location ->
if (::naverMap.isInitialized) {
with(naverMap) {
locationSource = this@MapFragment.locationSource
locationTrackingMode = LocationTrackingMode.NoFollow

locationOverlay.apply {
isVisible = true
icon = OverlayImage.fromResource(R.drawable.ic_map_location_overlay)
}
}
}

moveMapCamera(location)
}
}

private fun requestLocationPermission() {
val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions[LOCATION_PERMISSIONS[0]] == true || permissions[LOCATION_PERMISSIONS[1]] == true -> {
setLocationTrackingMode()
}
}
}

locationPermissionRequest.launch(LOCATION_PERMISSIONS)
}

private fun moveMapCamera(location: Location) {
if (::naverMap.isInitialized) {
naverMap.moveCamera(
CameraUpdate.scrollTo(
LatLng(
location.latitude,
location.longitude
)
).animate(CameraAnimation.Linear)
)
}
}

private fun makeMarkers() {
mapViewModel.dummyPinList.map { pinEntity ->
pinEntity.toMarker().map = naverMap
}
}

companion object {
private const val LOCATION_PERMISSION_REQUEST_CODE = 1000
private val LOCATION_PERMISSIONS = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
private const val SINGLE_SELECTION = 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.sopt.pingle.presentation.ui.main.home.map

import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.sopt.pingle.domain.model.PinEntity
import org.sopt.pingle.presentation.type.CategoryType

class MapViewModel() : ViewModel() {
val dummyPinList = listOf(
PinEntity(
id = 1,
x = 126.9275108,
y = 37.5262935,
category = "PLAY",
meetingCount = 1
),
PinEntity(
id = 2,
x = 126.9283122,
y = 37.5259168,
category = "STUDY",
meetingCount = 2
),
PinEntity(
id = 3,
x = 126.9276423,
y = 37.5258711,
category = "MULTI",
meetingCount = 1
),
PinEntity(
id = 4,
x = 126.9286719,
y = 37.5253629,
category = "OTHERS",
meetingCount = 2
)
)

private val _category = MutableStateFlow<CategoryType?>(null)
val category get() = _category.asStateFlow()

fun setCategory(category: CategoryType?) {
_category.value = category
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class PingleChip @JvmOverloads constructor(
attrs: AttributeSet? = null,
defStyleAttr: Int = R.style.Theme_Pingle_Chip_All
) : Chip(context, attrs, defStyleAttr) {
private lateinit var _categoryType: CategoryType
val categoryType get() = _categoryType

private fun setColorStateList(
context: Context,
Expand All @@ -33,7 +35,9 @@ class PingleChip @JvmOverloads constructor(
)

fun setChipCategoryType(categoryType: CategoryType) {
val inactivatedOutlinedColor = R.color.g_03
this@PingleChip._categoryType = categoryType

val inactivatedOutlinedColor = R.color.g_09
val inactivatedTextColor = R.color.g_03
val inactivatedChipColor = R.color.g_11

Expand Down
13 changes: 13 additions & 0 deletions app/src/main/res/drawable/ic_map_location_overlay.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M10,10m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:fillColor="#FF3B3B"
android:fillAlpha="0.5"/>
<path
android:pathData="M10,10m-6.923,0a6.923,6.923 0,1 1,13.846 0a6.923,6.923 0,1 1,-13.846 0"
android:fillColor="#FF3B3B"/>
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_map_marker_multi_small.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:pathData="M16,0L16,0A16,16 0,0 1,32 16L32,16A16,16 0,0 1,16 32L16,32A16,16 0,0 1,0 16L0,16A16,16 0,0 1,16 0z"
android:fillColor="#F2FF5E"/>
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_map_marker_other_small.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:pathData="M16,0L16,0A16,16 0,0 1,32 16L32,16A16,16 0,0 1,16 32L16,32A16,16 0,0 1,0 16L0,16A16,16 0,0 1,16 0z"
android:fillColor="#EDF0F4"/>
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_map_marker_play_small.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:pathData="M16,0L16,0A16,16 0,0 1,32 16L32,16A16,16 0,0 1,16 32L16,32A16,16 0,0 1,0 16L0,16A16,16 0,0 1,16 0z"
android:fillColor="#B7FF1D"/>
</vector>
Loading

0 comments on commit 812b6d9

Please sign in to comment.