Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ dependencies {

}

configurations.all {
exclude(group = "io.github.fornewid", module = "naver-map-location")
}

kapt {
correctErrorTypes = true
}
205 changes: 165 additions & 40 deletions app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,19 @@ import android.content.pm.PackageManager
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
Expand All @@ -30,11 +38,12 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
Expand All @@ -43,6 +52,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.eatssu.android.R
import com.eatssu.android.data.MySharedPreferences
import com.eatssu.android.domain.model.Partnership
import com.eatssu.android.domain.model.RestaurantType
import com.eatssu.android.presentation.MainState
import com.eatssu.android.presentation.MainViewModel
Expand All @@ -57,28 +67,28 @@ import com.eatssu.android.presentation.mypage.userinfo.UserInfoActivity
import com.eatssu.android.presentation.util.TrackScreenViewEvent
import com.eatssu.common.EventLogger
import com.eatssu.common.enums.ScreenId
import com.eatssu.design_system.theme.Black
import com.eatssu.design_system.theme.EatssuTheme
import com.eatssu.design_system.theme.Gray300
import com.eatssu.design_system.theme.Primary
import com.naver.maps.geometry.LatLng
import com.naver.maps.map.CameraPosition
import com.naver.maps.map.compose.Align
import com.naver.maps.map.CameraUpdate
import com.naver.maps.map.clustering.ClusteringKey
import com.naver.maps.map.compose.Clustering
import com.naver.maps.map.compose.ExperimentalNaverMapApi
import com.naver.maps.map.compose.LocationTrackingMode
import com.naver.maps.map.compose.MapProperties
import com.naver.maps.map.compose.MapUiSettings
import com.naver.maps.map.compose.Marker
import com.naver.maps.map.compose.NaverMap
import com.naver.maps.map.compose.rememberCameraPositionState
import com.naver.maps.map.compose.rememberMarkerState
import com.naver.maps.map.overlay.OverlayImage
import com.naver.maps.map.util.FusedLocationSource
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import timber.log.Timber

private const val DEFAULT_LATITUDE = 37.49517278813046
private const val DEFAULT_LONGITUDE = 126.95661313346206
private const val DEFAULT_ZOOM = 17.5
private const val DEFAULT_ZOOM = 17.0
private const val PERMISSION_REQUEST_CODE = 1001

@OptIn(ExperimentalNaverMapApi::class, ExperimentalMaterial3Api::class)
Expand Down Expand Up @@ -124,7 +134,11 @@ fun MapFragmentComposeView(
) { permissions ->
val granted = permissions.values.all { it }
if (!granted) {
Toast.makeText(context, "내 위치를 바로 확인하며 제휴 식당을 찾아볼 수 있도록 위치 권한을 허용해 주세요.", Toast.LENGTH_SHORT).show()
Toast.makeText(
context,
"내 위치를 바로 확인하며 제휴 식당을 찾아볼 수 있도록 위치 권한을 허용해 주세요.",
Toast.LENGTH_SHORT
).show()
}
}

Expand All @@ -136,21 +150,25 @@ fun MapFragmentComposeView(
else -> "" to false
}
}

else -> "" to false
}

LaunchedEffect(Unit) {
viewModel.uiEvent.collectLatest { event ->
when (event) {
is UiEvent.ShowToast -> Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
is UiEvent.ShowToast -> Toast.makeText(context, event.message, Toast.LENGTH_SHORT)
.show()
}
}
}

// 최초 실행 시 위치 권한 요청
LaunchedEffect(Unit) {
val fine = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
val coarse = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
val fine =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
val coarse =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)

if (fine != PackageManager.PERMISSION_GRANTED || coarse != PackageManager.PERMISSION_GRANTED) {
permissionLauncher.launch(
Expand All @@ -173,7 +191,10 @@ fun MapFragmentComposeView(

var hasLocationPermission by remember {
mutableStateOf(
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
)
}

Expand All @@ -184,6 +205,7 @@ fun MapFragmentComposeView(
viewModel.loadPartnerships()
EventLogger.clickMap()
}

FilterType.Mine -> {
viewModel.loadUserCollegePartnerships()

Expand Down Expand Up @@ -279,49 +301,148 @@ fun MapFragmentComposeView(
NaverMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState,
uiSettings = MapUiSettings(isZoomControlEnabled = false, isLocationButtonEnabled = true),
uiSettings = MapUiSettings(
isZoomControlEnabled = false,
isLocationButtonEnabled = true
),
locationSource = locationSource,
contentPadding = PaddingValues(bottom = dimensionResource(R.dimen.bottom_nav_height)),
properties = MapProperties(
locationTrackingMode = LocationTrackingMode.Follow,
locationTrackingMode = LocationTrackingMode.NoFollow,
),
onLocationChange = { location ->
// 위치가 업데이트되면 위치 권한 있다고 간주
hasLocationPermission = true
},
) {
mapState.partnerships.forEach { partnership ->
val markerState = rememberMarkerState(position = LatLng(partnership.latitude, partnership.longitude))
val clusterItems = mapState.partnerships.associateBy {
ItemKey(
it.storeName,
LatLng(it.latitude, it.longitude)
)
}

Marker(
icon = OverlayImage.fromResource(
when (partnership.restaurantType) {
Clustering(
items = clusterItems,
thresholdStrategy = { zoom ->
25.0
},

clusterContent = {
Box(
modifier = Modifier
.size(32.dp)
.background(Primary, CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = "${it.size}",
color = Color.White,
style = EatssuTheme.typography.body2
)
}
},
leafContent = { info ->
val partnership = info.tag as? Partnership ?: return@Clustering

Row(
modifier = Modifier
.background(Color.White, RoundedCornerShape(13.dp))
.border(1.dp, Gray300, RoundedCornerShape(13.dp))
.padding(start = 3.dp, end = 7.dp, top = 2.5.dp, bottom = 2.5.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = spacedBy(
3.dp
)
) {
val iconRes = when (partnership.restaurantType) {
RestaurantType.CAFE -> R.drawable.ic_map_marker_cafe
RestaurantType.RESTAURANT -> R.drawable.ic_map_marker_restaurant
RestaurantType.PUB -> R.drawable.ic_map_marker_pub
else -> R.drawable.ic_map_marker_restaurant
}
),
width = 20.dp,
height = 20.dp,
captionAligns = arrayOf(Align.Bottom),
state = markerState,
captionText = partnership.storeName,
captionColor = Black,
captionTextSize = 10.sp,
onClick = {
if (partnership.partnershipInfos.isEmpty()) {
// 제휴 정보가 없을 때는 토스트만 띄우고 바텀시트는 안 띄움
Toast.makeText(context, "제휴 정보가 없습니다.", Toast.LENGTH_SHORT).show()
true
} else {
// 제휴 정보가 있을 때만 바텀시트 띄움
viewModel.selectPartnershipByStoreName(partnership.storeName)
true
}

Image(
painter = painterResource(id = iconRes),
contentDescription = null,
modifier = Modifier.size(20.dp)
)

Text(
text = partnership.storeName,
style = EatssuTheme.typography.caption3,
color = Color.Black
)
}
)
},
onClickCluster = { info, _ ->
scope.launch {
cameraPositionState.animate(
CameraUpdate.toCameraPosition(
CameraPosition(
info.position,
cameraPositionState.position.zoom + 2.0
)
)
)
}
true
},
onClickLeaf = { info, _ ->
val partnership = info.tag as? Partnership ?: return@Clustering true

if (partnership.partnershipInfos.isEmpty()) {
// 제휴 정보가 없을 때는 토스트만 띄우고 바텀시트는 안 띄움
Toast.makeText(context, "제휴 정보가 없습니다.", Toast.LENGTH_SHORT).show()
true
} else {
// 제휴 정보가 있을 때만 바텀시트 띄움
viewModel.selectPartnershipByStoreName(partnership.storeName)
true
}
true
}

}
)
// mapState.partnerships.forEach { partnership ->
// val markerState = rememberSaveable(saver = MarkerState.Saver) {
// MarkerState(
// position = LatLng(
// partnership.latitude,
// partnership.longitude
// )
// )
// }
//
// Marker(
// icon = OverlayImage.fromResource(
// when (partnership.restaurantType) {
// RestaurantType.CAFE -> R.drawable.ic_map_marker_cafe
// RestaurantType.RESTAURANT -> R.drawable.ic_map_marker_restaurant
// RestaurantType.PUB -> R.drawable.ic_map_marker_pub
// }
// ),
// width = 20.dp,
// height = 20.dp,
// captionAligns = arrayOf(Align.Bottom),
// state = markerState,
// captionText = partnership.storeName,
// captionColor = Black,
// captionTextSize = 10.sp,
//
// onClick = {
// if (partnership.partnershipInfos.isEmpty()) {
// // 제휴 정보가 없을 때는 토스트만 띄우고 바텀시트는 안 띄움
// Toast.makeText(context, "제휴 정보가 없습니다.", Toast.LENGTH_SHORT).show()
// true
// } else {
// // 제휴 정보가 있을 때만 바텀시트 띄움
// viewModel.selectPartnershipByStoreName(partnership.storeName)
// true
// }
// }
// )
//
// }
}

// 학과 정보를 입력하지 않은 상태에서 제휴 필터를 변경하려고 할 때 BottomSheet 표시
Expand Down Expand Up @@ -386,3 +507,7 @@ fun MapFragmentComposeViewPreview() {
MapFragmentComposeView()
}
}

data class ItemKey(val id: String, private val latLng: LatLng) : ClusteringKey {
override fun getPosition() = latLng
}
5 changes: 3 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ lifecycleViewmodelCompose = "2.8.7"
mapSdk = "3.21.0"
material = "1.8.0"
constraintlayout = "2.1.4"
naverMapCompose = "1.8.2"
naverMapCompose = "fc~cluster-SNAPSHOT"
naverMapLocation = "21.0.2"
naverMapClustering = "1.0.2"
navigationUi = "2.8.9"
playServicesLocation = "21.3.0"
navigationFragment = "2.9.3"
Expand Down Expand Up @@ -142,7 +143,7 @@ timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "tim
oss-licenses = { group = "com.google.android.gms", name = "play-services-oss-licenses", version.ref = "ossLicenses" }
oss-licenses-plugin = { group = "com.google.android.gms", name = "oss-licenses-plugin", version.ref = "ossLicensesPlugin" }
map-sdk = { module = "com.naver.maps:map-sdk", version.ref = "mapSdk" }
naver-map-compose = { module = "io.github.fornewid:naver-map-compose", version.ref = "naverMapCompose" }
naver-map-compose = { module = "com.github.ho8278:naver-map-compose", version.ref = "naverMapCompose" }
naver-map-location = { module = "io.github.fornewid:naver-map-location", version.ref = "naverMapLocation" }
play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" }

Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencyResolutionManagement {
mavenCentral()
maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }
maven { url 'https://repository.map.naver.com/archive/maven' }
maven { url 'https://jitpack.io' }
}
}
rootProject.name = "EatSSU-Android"
Expand Down