Skip to content

Commit

Permalink
Merge branch 'develop' into feat-plan-recruitment
Browse files Browse the repository at this point in the history
# Conflicts:
#	app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanViewModel.kt
#	app/src/main/res/layout/item_plan_location.xml
#	app/src/main/res/values/strings.xml
  • Loading branch information
HAJIEUN02 committed Jan 9, 2024
2 parents 862bf81 + d0d1e99 commit a1d9d2d
Show file tree
Hide file tree
Showing 28 changed files with 605 additions and 125 deletions.
9 changes: 8 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ 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())
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 @@ -96,6 +100,9 @@ dependencies {

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

// progress Bar
implementation(libs.progress.bar)
}

ktlint {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.sopt.pingle.presentation.mapper

import org.sopt.pingle.presentation.type.CategoryType
import org.sopt.pingle.presentation.ui.main.home.map.MapFragment

fun CategoryType.toMarkerIcon(isSelected: Boolean) =
when (this) {
CategoryType.PLAY -> if (isSelected) MapFragment.OVERLAY_IMAGE_PLAY_BIG else MapFragment.OVERLAY_IMAGE_PLAY_SMALL
CategoryType.STUDY -> if (isSelected) MapFragment.OVERLAY_IMAGE_STUDY_BIG else MapFragment.OVERLAY_IMAGE_STUDY_SMALL
CategoryType.MULTI -> if (isSelected) MapFragment.OVERLAY_IMAGE_MULTI_BIG else MapFragment.OVERLAY_IMAGE_MULTI_SMALL
CategoryType.OTHERS -> if (isSelected) MapFragment.OVERLAY_IMAGE_OTHER_BIG else MapFragment.OVERLAY_IMAGE_OTHER_SMALL
}
32 changes: 21 additions & 11 deletions app/src/main/java/org/sopt/pingle/presentation/mapper/PinMapper.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
package org.sopt.pingle.presentation.mapper

import androidx.databinding.Observable
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.model.MarkerModel
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
fun PinEntity.toMarkerModel(): MarkerModel {
val markerModel = MarkerModel(
Marker().apply {
position = LatLng(y, x)
isHideCollidedMarkers = true
icon = CategoryType.fromString(category).toMarkerIcon(false)
}
)

markerModel.isSelected.addOnPropertyChangedCallback(
object :
Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
with(markerModel.marker) {
icon = CategoryType.fromString(category).toMarkerIcon(markerModel.isSelected.get())
}
}
}
)

return markerModel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.sopt.pingle.presentation.model

import androidx.databinding.ObservableBoolean
import com.naver.maps.map.overlay.Marker

data class MarkerModel(
val marker: Marker,
var isSelected: ObservableBoolean = ObservableBoolean(false)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.sopt.pingle.presentation.ui.main.home.mainlist

import android.os.Bundle
import android.view.View
import org.sopt.pingle.R
import org.sopt.pingle.databinding.FragmentMainListBinding
import org.sopt.pingle.presentation.ui.main.home.map.MapFragment
import org.sopt.pingle.util.base.BindingFragment
import org.sopt.pingle.util.fragment.navigateToFragment

class MainListFragment : BindingFragment<FragmentMainListBinding>(R.layout.fragment_main_list) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

addListeners()
}

private fun addListeners() {
binding.fabMainListMap.setOnClickListener {
navigateToFragment<MapFragment>()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ 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
Expand All @@ -26,12 +25,13 @@ 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.presentation.ui.common.AllModalDialogFragment
import org.sopt.pingle.presentation.ui.main.home.mainlist.MainListFragment
import org.sopt.pingle.util.base.BindingFragment
import org.sopt.pingle.util.component.AllModalDialogFragment
import org.sopt.pingle.util.component.OnPingleCardClickListener
import org.sopt.pingle.util.component.PingleChip
import org.sopt.pingle.util.fragment.navigateToFragment
import org.sopt.pingle.util.fragment.navigateToWebView
import org.sopt.pingle.util.fragment.showToast
import org.sopt.pingle.util.fragment.stringOf
Expand All @@ -40,6 +40,15 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
private val mapViewModel by viewModels<MapViewModel>()
private lateinit var naverMap: NaverMap
private lateinit var locationSource: FusedLocationSource
private val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions[LOCATION_PERMISSIONS[0]] == true || permissions[LOCATION_PERMISSIONS[1]] == true -> {
setLocationTrackingMode()
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Expand All @@ -48,7 +57,6 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
initLayout()
addListeners()
collectData()
setLocationTrackingMode()
}

override fun onMapReady(naverMap: NaverMap) {
Expand All @@ -60,9 +68,14 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
isZoomControlEnabled = false
isScaleBarEnabled = false
}

setOnMapClickListener { _, _ ->
mapViewModel.clearSelectedMarkerPosition()
}
}

makeMarkers()
setLocationTrackingMode()
}

private fun initMap() {
Expand All @@ -84,7 +97,6 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
chipMapCategoryStudy.setChipCategoryType(CategoryType.STUDY)
chipMapCategoryMulti.setChipCategoryType(CategoryType.MULTI)
chipMapCategoryOthers.setChipCategoryType(CategoryType.OTHERS)
cardMap.initLayout(mapViewModel.dummyPingle)
cardMap.listener = object : OnPingleCardClickListener {
override fun onPingleCardChatBtnClickListener() {
startActivity(navigateToWebView(mapViewModel.dummyPingle.chatLink))
Expand All @@ -103,7 +115,14 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
private fun addListeners() {
binding.fabMapHere.setOnClickListener {
if (::locationSource.isInitialized) {
locationSource.lastLocation?.let { location -> moveMapCamera(location) }
locationSource.lastLocation?.let { location ->
moveMapCamera(
LatLng(
location.latitude,
location.longitude
)
)
}
}
}

Expand All @@ -113,13 +132,28 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
?.let { group.findViewById<PingleChip>(it).categoryType }
)
}

binding.fabMapList.setOnClickListener {
navigateToFragment<MainListFragment>()
}
}

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

mapViewModel.selectedMarkerPosition.flowWithLifecycle(lifecycle)
.onEach { selectedMarkerPosition ->
(selectedMarkerPosition == MapViewModel.DEFAULT_SELECTED_MARKER_POSITION).run {
with(binding) {
fabMapHere.visibility = if (this@run) View.VISIBLE else View.INVISIBLE
fabMapList.visibility = if (this@run) View.VISIBLE else View.INVISIBLE
cardMap.visibility = if (this@run) View.INVISIBLE else View.VISIBLE
}
}
}.launchIn(lifecycleScope)
}

private fun setLocationTrackingMode() {
Expand All @@ -131,57 +165,47 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
}
) {
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)
}
}
}
with(naverMap) {
locationSource = this@MapFragment.locationSource
locationTrackingMode = LocationTrackingMode.NoFollow

moveMapCamera(location)
}
}

private fun requestLocationPermission() {
val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions[LOCATION_PERMISSIONS[0]] == true || permissions[LOCATION_PERMISSIONS[1]] == true -> {
setLocationTrackingMode()
locationOverlay.apply {
isVisible = true
icon = OverlayImage.fromResource(R.drawable.ic_map_location_overlay)
}
}
} else {
locationPermissionRequest.launch(LOCATION_PERMISSIONS)
}

locationPermissionRequest.launch(LOCATION_PERMISSIONS)
LocationServices.getFusedLocationProviderClient(requireContext()).lastLocation.addOnSuccessListener { location ->
moveMapCamera(LatLng(location.latitude, location.longitude))
}
}

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

private fun makeMarkers() {
mapViewModel.dummyPinList.map { pinEntity ->
pinEntity.toMarker().map = naverMap
mapViewModel.markerList.value.mapIndexed { index, markerModel ->
markerModel.marker.apply {
map = naverMap
setOnClickListener {
with(mapViewModel) {
handleMarkerClick(index)
// TODO 마커 상세 정보 받아오는 로직 추가
binding.cardMap.initLayout(dummyPingle)
moveMapCamera(position)
}
return@setOnClickListener true
}
}
}
}

Expand Down Expand Up @@ -218,5 +242,18 @@ class MapFragment : BindingFragment<FragmentMapBinding>(R.layout.fragment_map),
private const val SINGLE_SELECTION = 0
private const val MAP_CANCEL_MODAL = "mapCancelModal"
private const val MAP_MODAL = "mapModal"

val OVERLAY_IMAGE_PLAY_SMALL =
OverlayImage.fromResource(R.drawable.ic_map_marker_play_small)
val OVERLAY_IMAGE_STUDY_SMALL =
OverlayImage.fromResource(R.drawable.ic_map_marker_study_small)
val OVERLAY_IMAGE_MULTI_SMALL =
OverlayImage.fromResource(R.drawable.ic_map_marker_multi_small)
val OVERLAY_IMAGE_OTHER_SMALL =
OverlayImage.fromResource(R.drawable.ic_map_marker_other_small)
val OVERLAY_IMAGE_PLAY_BIG = OverlayImage.fromResource(R.drawable.ic_map_marker_play_big)
val OVERLAY_IMAGE_STUDY_BIG = OverlayImage.fromResource(R.drawable.ic_map_marker_study_big)
val OVERLAY_IMAGE_MULTI_BIG = OverlayImage.fromResource(R.drawable.ic_map_marker_multi_big)
val OVERLAY_IMAGE_OTHER_BIG = OverlayImage.fromResource(R.drawable.ic_map_marker_other_big)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.sopt.pingle.domain.model.PinEntity
import org.sopt.pingle.domain.model.PingleEntity
import org.sopt.pingle.presentation.mapper.toMarkerModel
import org.sopt.pingle.presentation.model.MarkerModel
import org.sopt.pingle.presentation.type.CategoryType

class MapViewModel() : ViewModel() {
Expand Down Expand Up @@ -57,10 +59,47 @@ class MapViewModel() : ViewModel() {
private val _category = MutableStateFlow<CategoryType?>(null)
val category get() = _category.asStateFlow()

private var _markerList = MutableStateFlow<List<MarkerModel>>(emptyList())
val markerList get() = _markerList.asStateFlow()

private var _selectedMarkerPosition = MutableStateFlow(DEFAULT_SELECTED_MARKER_POSITION)
val selectedMarkerPosition = _selectedMarkerPosition.asStateFlow()

init {
setMarkerList()
}

fun setCategory(category: CategoryType?) {
_category.value = category
}

fun setMarkerList() {
_markerList.value = dummyPinList.map { pinEntity ->
pinEntity.toMarkerModel()
}
}

fun handleMarkerClick(position: Int) {
setMarkerIsSelected(position)
if (_selectedMarkerPosition.value != DEFAULT_SELECTED_MARKER_POSITION) {
setMarkerIsSelected(
_selectedMarkerPosition.value
)
}
_selectedMarkerPosition.value = position
}

fun clearSelectedMarkerPosition() {
if (_selectedMarkerPosition.value != DEFAULT_SELECTED_MARKER_POSITION) {
setMarkerIsSelected(_selectedMarkerPosition.value)
_selectedMarkerPosition.value = DEFAULT_SELECTED_MARKER_POSITION
}
}

private fun setMarkerIsSelected(position: Int) {
markerList.value[position].isSelected.set(!markerList.value[position].isSelected.get())
}

fun cancelPingle() {
dummyPingle = PingleEntity(
id = 1,
Expand Down Expand Up @@ -94,4 +133,8 @@ class MapViewModel() : ViewModel() {
chatLink = "https://github.com/TeamPINGLE/PINGLE-ANDROID"
)
}

companion object {
const val DEFAULT_SELECTED_MARKER_POSITION = -1
}
}
Loading

0 comments on commit a1d9d2d

Please sign in to comment.