diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapFragment.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapFragment.kt index 643a3c00..717cd283 100644 --- a/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapFragment.kt +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapFragment.kt @@ -15,23 +15,18 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices -import com.kakao.vectormap.KakaoMap -import com.kakao.vectormap.KakaoMapReadyCallback import com.kakao.vectormap.LatLng -import com.kakao.vectormap.MapLifeCycleCallback -import com.kakao.vectormap.camera.CameraPosition -import com.kakao.vectormap.label.Label import com.project200.common.constants.RuleConstants.SEOUL_CITY_HALL_LATITUDE import com.project200.common.constants.RuleConstants.SEOUL_CITY_HALL_LONGITUDE import com.project200.common.constants.RuleConstants.ZOOM_LEVEL -import com.project200.domain.model.MapPosition -import com.project200.domain.model.MatchingMember -import com.project200.feature.matching.map.cluster.ClusterCalculator import com.project200.feature.matching.map.cluster.MapClusterItem +import com.project200.feature.matching.map.compose.MatchingMapController +import com.project200.feature.matching.map.compose.MatchingMapScreen import com.project200.feature.matching.map.filter.FilterBottomSheetDialog import com.project200.feature.matching.map.filter.MatchingFilterRVAdapter import com.project200.feature.matching.utils.MatchingFilterType import com.project200.presentation.base.BindingFragment +import com.project200.presentation.compose.applyAppTheme import com.project200.undabang.feature.matching.R import com.project200.undabang.feature.matching.databinding.FragmentMatchingMapBinding import dagger.hilt.android.AndroidEntryPoint @@ -41,35 +36,23 @@ import timber.log.Timber @AndroidEntryPoint class MatchingMapFragment : BindingFragment(R.layout.fragment_matching_map) { - // MapViewManager로 지도 관련 로직 위임 - private var mapViewManager: MapViewManager? = null + private val viewModel: MatchingMapViewModel by viewModels() + // 지도 본체(MatchingMapScreen) 내부 카메라를 제어하기 위한 통로 + private val mapController = MatchingMapController() + private var isMapInitialized = false private lateinit var fusedLocationClient: FusedLocationProviderClient - private val viewModel: MatchingMapViewModel by viewModels() private val filterAdapter by lazy { MatchingFilterRVAdapter( - onFilterClick = { type -> - viewModel.onFilterTypeClicked(type) - }, - onClearClick = { - viewModel.clearFilters() - }, + onFilterClick = { type -> viewModel.onFilterTypeClicked(type) }, + onClearClick = { viewModel.clearFilters() }, ) } - private var isMapInitialized: Boolean = false - - // 클러스터링 계산을 위한 헬퍼 클래스 - private lateinit var clusterCalculator: ClusterCalculator - - // 위치 권한 요청을 위한 ActivityResultLauncher 정의 private val locationPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> - if (isGranted) { - // 권한이 허용되면 현재 위치로 이동 - moveToCurrentLocation() - } + if (isGranted) moveToCurrentLocation() } override fun getViewBinding(view: View): FragmentMatchingMapBinding { @@ -77,43 +60,29 @@ class MatchingMapFragment : } override fun setupViews() { - // 클러스터 계산기 초기화 - clusterCalculator = ClusterCalculator() isMapInitialized = false fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity()) - initMapView() + // 지도 본체는 Compose(MatchingMapScreen)로 호스팅. 마커/클러스터/카메라 idle은 Screen 내부 처리. + binding.mapComposeView.applyAppTheme { + MatchingMapScreen( + viewModel = viewModel, + controller = mapController, + onMapReady = { restoreInitialCamera() }, + onClusterClick = { items -> showMembersBottomSheet(items) }, + onPlaceMarkerClick = { + findNavController().navigate( + MatchingMapFragmentDirections.actionMatchingMapFragmentToExercisePlaceFragment(), + ) + }, + ) + } + initListeners() binding.matchingFilterRv.adapter = filterAdapter filterAdapter.submitFilterList(MatchingFilterType.entries) } - private fun initMapView() { - binding.mapView.start( - object : MapLifeCycleCallback() { - override fun onMapDestroy() {} - - override fun onMapError(error: Exception) { - Timber.d("$error") - } - }, - object : KakaoMapReadyCallback() { - override fun onMapReady(map: KakaoMap) { - // MapViewManager를 생성하여 지도 로직을 위임 - mapViewManager = - MapViewManager( - context = requireContext(), - kakaoMap = map, - onCameraIdle = { cameraPosition -> handleCamera(cameraPosition) }, - onLabelClick = { label -> handleLabelClick(label) }, - ) - - setupMapRelatedObservers() - } - }, - ) - } - private fun initListeners() { binding.currentLocationBtn.setOnClickListener { checkPermissionAndMove() @@ -127,45 +96,77 @@ class MatchingMapFragment : } /** - * MapViewManager로부터 카메라 이동 완료 이벤트를 받아 처리합니다. + * 지도가 준비되면 1회 호출되어 초기 카메라 위치를 복원한다. + * 저장된 위치가 있으면 그곳으로, 없으면 현재 위치(권한 시) 또는 기본 위치(서울시청)로 이동한다. */ - private fun handleCamera(cameraPosition: CameraPosition) { - // 마지막 위치 저장 - viewModel.saveLastLocation( - cameraPosition.position.latitude, - cameraPosition.position.longitude, - cameraPosition.zoomLevel, - ) + private fun restoreInitialCamera() { + if (isMapInitialized) return - // 현재 화면 영역(Bounds) 조회 - val currentBounds = mapViewManager?.getCurrentBounds() ?: return + val savedPosition = viewModel.initialMapPosition.value + if (isLocationPermissionGranted()) { + if (savedPosition != null) { + mapController.moveCamera( + LatLng.from(savedPosition.latitude, savedPosition.longitude), + savedPosition.zoomLevel, + ) + } else { + moveToCurrentLocation() + } + } else { + mapController.moveCamera( + LatLng.from(SEOUL_CITY_HALL_LATITUDE, SEOUL_CITY_HALL_LONGITUDE), + ZOOM_LEVEL, + ) + locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) + } - // 화면 이동에 따라 매칭 멤버 데이터 갱신 - viewModel.fetchMatchingMembersIfMoved( - currentBounds = currentBounds, - currentCenter = cameraPosition.position, - currentZoom = cameraPosition.zoomLevel, - ) + isMapInitialized = true } - /** - * MapViewManager로부터 라벨 클릭 이벤트를 받아 처리합니다. - */ - private fun handleLabelClick(label: Label) { - val cameraPosition = mapViewManager?.getCurrentCameraPosition() ?: return - - // 클릭한 라벨이 클러스터인지 확인 - clusterCalculator.getClusters(cameraPosition).find { cluster -> - cluster.position.latitude == label.position.latitude && - cluster.position.longitude == label.position.longitude - }?.let { foundCluster -> - // 클러스터에 있는 운동 장소 리스트 표시 - showMembersBottomSheet(foundCluster.items.toList()) - return + private fun checkPermissionAndMove() { + if (isLocationPermissionGranted()) { + moveToCurrentLocation() + } else { + locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) } + } + + @SuppressLint("MissingPermission") // 권한은 isLocationPermissionGranted()로 이미 확인됨 + private fun moveToCurrentLocation() { + fusedLocationClient.lastLocation + .addOnSuccessListener { location -> + if (location != null) { + mapController.moveCamera( + LatLng.from(location.latitude, location.longitude), + ZOOM_LEVEL, + ) + } else { + fallbackToDefaultLocation(cause = null) + } + } + .addOnFailureListener { e -> + fallbackToDefaultLocation(cause = e) + } + } - // 클러스터를 찾지 못한 경우 == 내 장소 마커를 클릭한 경우 - findNavController().navigate(MatchingMapFragmentDirections.actionMatchingMapFragmentToExercisePlaceFragment()) + private fun fallbackToDefaultLocation(cause: Throwable?) { + cause?.let { Timber.e(it, "getLastLocation failed") } + Toast.makeText( + requireContext(), + R.string.error_cannot_find_current_location, + Toast.LENGTH_SHORT, + ).show() + mapController.moveCamera( + LatLng.from(SEOUL_CITY_HALL_LATITUDE, SEOUL_CITY_HALL_LONGITUDE), + ZOOM_LEVEL, + ) + } + + private fun isLocationPermissionGranted(): Boolean { + return ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.ACCESS_FINE_LOCATION, + ) == PackageManager.PERMISSION_GRANTED } override fun setupObservers() { @@ -216,74 +217,6 @@ class MatchingMapFragment : } } - /** - * 지도와 직접적으로 관련된 옵저버들을 설정합니다. - */ - private fun setupMapRelatedObservers() { - // 초기 지도 위치 관찰 - viewModel.initialMapPosition.observe(viewLifecycleOwner) { savedPosition -> - // 지도의 초기 위치 설정이 아직 완료되지 않았을 때만 카메라를 이동시킵니다. - if (isMapInitialized) return@observe - - if (isLocationPermissionGranted()) { // 권한이 있는 경우 - if (savedPosition != null) { - mapViewManager?.moveCamera( - LatLng.from(savedPosition.latitude, savedPosition.longitude), - savedPosition.zoomLevel, - ) - } else { - // 저장된 위치가 없으면, 현재 위치로 이동 - moveToCurrentLocation() - } - } else { - // 위치 권한이 없는 경우 - // 기본위치(서울시청)로 이동 - val defaultPosition = - MapPosition( - latitude = SEOUL_CITY_HALL_LATITUDE, - longitude = SEOUL_CITY_HALL_LONGITUDE, - zoomLevel = ZOOM_LEVEL, - ) - - mapViewManager?.moveCamera( - LatLng.from(defaultPosition.latitude, defaultPosition.longitude), - defaultPosition.zoomLevel, - ) - locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) - } - - isMapInitialized = true - } - - // 회원 및 장소 데이터 통합 관찰 - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.combinedMapData.collect { (members, places) -> - // 클러스터 계산기에 최신 회원 데이터 업데이트 - updateClusterData(members) - - // 데이터가 변경되었으므로 마커 redraw - mapViewManager?.redrawMarkers(places, clusterCalculator) - } - } - } - } - - /** - * ViewModel의 최신 데이터를 ClusterCalculator에 업데이트합니다. - */ - private fun updateClusterData(members: List) { - val clusterItems = mutableListOf() - members.forEach { member -> - member.locations.forEach { location -> - clusterItems.add(MapClusterItem(member, location)) - } - } - - clusterCalculator.clearItems() - clusterCalculator.addItems(clusterItems) - } - private fun showPlaceGuideDialog() { val dialog = MatchingPlaceGuideDialog( @@ -293,7 +226,7 @@ class MatchingMapFragment : ) }, ) - dialog.isCancelable = false // 바깥 영역 터치 시 다이얼로그가 닫히지 않도록 설정 + dialog.isCancelable = false dialog.show(parentFragmentManager, this::class.java.simpleName) } @@ -321,50 +254,8 @@ class MatchingMapFragment : bottomSheet.show(childFragmentManager, FilterBottomSheetDialog::class.java.simpleName) } - private fun checkPermissionAndMove() { - if (isLocationPermissionGranted()) { - moveToCurrentLocation() - } else { - locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) - } - } - - /** - * 현재 위치로 카메라를 이동시키는 함수 - */ - @SuppressLint("MissingPermission") // 권한 체크는 isLocationPermissionGranted()로 이미 수행됨 - private fun moveToCurrentLocation() { - fusedLocationClient.lastLocation.addOnSuccessListener { location -> - val latLng: LatLng - if (location != null) { - latLng = LatLng.from(location.latitude, location.longitude) - } else { - latLng = LatLng.from(SEOUL_CITY_HALL_LATITUDE, SEOUL_CITY_HALL_LONGITUDE) - Toast.makeText( - requireContext(), - R.string.error_cannot_find_current_location, - Toast.LENGTH_SHORT, - ).show() - } - mapViewManager?.moveCamera(latLng, ZOOM_LEVEL) - } - } - - private fun isLocationPermissionGranted(): Boolean { - return ContextCompat.checkSelfPermission( - requireContext(), - Manifest.permission.ACCESS_FINE_LOCATION, - ) == PackageManager.PERMISSION_GRANTED - } - override fun onResume() { super.onResume() - binding.mapView.resume() viewModel.refreshExercisePlaces() } - - override fun onPause() { - super.onPause() - binding.mapView.pause() - } } diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapViewModel.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapViewModel.kt index 8b6c5fd0..0b525586 100644 --- a/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapViewModel.kt +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapViewModel.kt @@ -1,8 +1,6 @@ package com.project200.feature.matching.map import android.content.SharedPreferences -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.kakao.vectormap.LatLng @@ -126,8 +124,8 @@ class MatchingMapViewModel ) // 지도 초기 위치 - private val _initialMapPosition = MutableLiveData() - val initialMapPosition: LiveData = _initialMapPosition + private val _initialMapPosition = MutableStateFlow(null) + val initialMapPosition: StateFlow = _initialMapPosition.asStateFlow() // 운동 장소 다이얼로그 표시 알림 private val _shouldShowPlaceGuideDialog = MutableSharedFlow() diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/compose/KakaoMapView.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/compose/KakaoMapView.kt new file mode 100644 index 00000000..8b64358d --- /dev/null +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/compose/KakaoMapView.kt @@ -0,0 +1,105 @@ +package com.project200.feature.matching.map.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import com.kakao.vectormap.KakaoMap +import com.kakao.vectormap.KakaoMapReadyCallback +import com.kakao.vectormap.MapLifeCycleCallback +import com.kakao.vectormap.MapView +import com.project200.presentation.compose.theme.ColorGray100 +import com.project200.presentation.compose.theme.ColorGray300 +import timber.log.Timber + +/** + * + * MapView 를 AndroidView 로 호스팅하고, + * 생명주기(resume/pause/finish)를 DisposableEffect 로 연결한다. + * MapView 단일 인스턴스 생성, start()/MapReadyCallback 캡슐화, 준비된 KakaoMap 을 + * [onMapReady] 로 노출한다. + * + * @param onMapReady 지도 준비 완료 시 KakaoMap 핸들 전달(여기서 MapViewManager 생성 등) + * @param onMapError 지도 로딩 에러 + */ +@Composable +fun KakaoMapView( + modifier: Modifier = Modifier, + onMapReady: (KakaoMap) -> Unit = {}, + onMapError: (Exception) -> Unit = {}, +) { + // Preview 에서는 실제 지도 대신 modifier 크기만큼의 placeholder 를 그린다 + if (LocalInspectionMode.current) { + Box( + modifier = modifier.background(ColorGray300), + contentAlignment = Alignment.Center, + ) { + Text(text = "지도 미리보기", color = ColorGray100) + } + return + } + + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + + // recomposition 마다 재생성 방지하기 위해 remember 사용 (context 변경 시에는 재생성) + val mapView = remember(context) { MapView(context) } + + // factory/콜백은 최초 1회만 생성되므로, 상위가 새 람다를 넘겨도 항상 최신 콜백을 참조하도록 한다 + val currentOnMapReady by rememberUpdatedState(onMapReady) + val currentOnMapError by rememberUpdatedState(onMapError) + + // 호스트 생명주기를 MapView 의 resume/pause/finish 로 전달 + DisposableEffect(lifecycleOwner, mapView) { + val observer = + LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> mapView.resume() + Lifecycle.Event.ON_PAUSE -> mapView.pause() + else -> Unit + } + } + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + mapView.finish() + } + } + + AndroidView( + modifier = modifier, + factory = { + mapView.apply { + start( + object : MapLifeCycleCallback() { + override fun onMapDestroy() { + Timber.d("KakaoMap destroyed") + } + + override fun onMapError(error: Exception) { + Timber.e(error, "KakaoMap error") + currentOnMapError(error) + } + }, + object : KakaoMapReadyCallback() { + override fun onMapReady(map: KakaoMap) { + currentOnMapReady(map) + } + }, + ) + } + }, + ) +} diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/compose/MatchingMapController.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/compose/MatchingMapController.kt new file mode 100644 index 00000000..26ea5a33 --- /dev/null +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/compose/MatchingMapController.kt @@ -0,0 +1,22 @@ +package com.project200.feature.matching.map.compose + +import com.kakao.vectormap.LatLng +import com.project200.feature.matching.map.MapViewManager + +/** + * 지도 카메라를 화면 밖(Fragment)에서 제어하기 위한 통로. + * + * MapViewManager 는 MatchingMapScreen 내부에서 비동기로 생성되므로, + * 권한/현재위치/초기 복원처럼 Fragment 가 담당하는 로직이 카메라를 만지려면 이 컨트롤러로 다리를 놓는다. + * 준비 전(manager == null)에는 카메라 명령이 무시된다. + */ +class MatchingMapController { + internal var manager: MapViewManager? = null + + fun moveCamera( + latLng: LatLng, + zoomLevel: Int, + ) { + manager?.moveCamera(latLng, zoomLevel) + } +} diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/compose/MatchingMapScreen.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/compose/MatchingMapScreen.kt new file mode 100644 index 00000000..91778055 --- /dev/null +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/compose/MatchingMapScreen.kt @@ -0,0 +1,122 @@ +package com.project200.feature.matching.map.compose + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.kakao.vectormap.label.Label +import com.project200.domain.model.MatchingMember +import com.project200.feature.matching.map.MapViewManager +import com.project200.feature.matching.map.MatchingMapViewModel +import com.project200.feature.matching.map.cluster.ClusterCalculator +import com.project200.feature.matching.map.cluster.MapClusterItem + +/** + * 매칭 지도 화면 + * + * KakaoMapView 로 지도를 호스팅하고, ViewModel 의 combinedMapData 를 구독해 마커/클러스터를 그린다. + * + * @param onClusterClick 클러스터 라벨 클릭 시, 묶인 멤버 목록 전달 + * @param onPlaceMarkerClick 내 장소 마커 클릭 시 + */ +@Composable +fun MatchingMapScreen( + viewModel: MatchingMapViewModel, + modifier: Modifier = Modifier, + controller: MatchingMapController? = null, + onMapReady: () -> Unit = {}, + onClusterClick: (List) -> Unit = {}, + onPlaceMarkerClick: () -> Unit = {}, +) { + val context = LocalContext.current + // 멤버 데이터 구독 + val mapData by viewModel.combinedMapData.collectAsStateWithLifecycle() + + // 클러스터 계산기 + val clusterCalculator = remember { ClusterCalculator() } + var manager by remember { mutableStateOf(null) } + + Box(modifier = modifier.fillMaxSize()) { + KakaoMapView( + modifier = Modifier.fillMaxSize(), + onMapReady = { map -> + manager = + MapViewManager( + context = context, + kakaoMap = map, + onCameraIdle = { cameraPosition -> + // 카메라 이동 종료 → 위치 저장 + 영역 이동 시 멤버 재조회 + viewModel.saveLastLocation( + cameraPosition.position.latitude, + cameraPosition.position.longitude, + cameraPosition.zoomLevel, + ) + manager?.getCurrentBounds()?.let { bounds -> + viewModel.fetchMatchingMembersIfMoved( + currentBounds = bounds, + currentCenter = cameraPosition.position, + currentZoom = cameraPosition.zoomLevel, + ) + } + }, + onLabelClick = { label -> + handleLabelClick(label, manager, clusterCalculator, onClusterClick, onPlaceMarkerClick) + }, + ).also { mgr -> + controller?.manager = mgr // 카메라 제어 통로 연결 + onMapReady() // Fragment가 초기 복원 등을 시작하도록 통지 + } + }, + ) + // 현재위치 버튼 등 오버레이는 Fragment XML 유지(추후 Compose 전환) + } + + // 데이터(또는 지도 준비) 변경 시 마커/클러스터 다시 그리기 + LaunchedEffect(mapData, manager) { + val mgr = manager ?: return@LaunchedEffect + val (members, places) = mapData + updateClusterData(members, clusterCalculator) + mgr.redrawMarkers(places, clusterCalculator) + } +} + +/** ViewModel 의 최신 멤버를 ClusterCalculator 에 반영. */ +private fun updateClusterData( + members: List, + clusterCalculator: ClusterCalculator, +) { + val clusterItems = + members.flatMap { member -> + member.locations.map { location -> MapClusterItem(member, location) } + } + clusterCalculator.clearItems() + clusterCalculator.addItems(clusterItems) +} + +/** 클릭한 라벨이 클러스터면 멤버 목록 콜백, 아니면 내 장소 마커 콜백. */ +private fun handleLabelClick( + label: Label, + manager: MapViewManager?, + clusterCalculator: ClusterCalculator, + onClusterClick: (List) -> Unit, + onPlaceMarkerClick: () -> Unit, +) { + val cameraPosition = manager?.getCurrentCameraPosition() ?: return + val cluster = + clusterCalculator.getClusters(cameraPosition).find { cluster -> + cluster.position.latitude == label.position.latitude && + cluster.position.longitude == label.position.longitude + } + if (cluster != null) { + onClusterClick(cluster.items.toList()) + } else { + onPlaceMarkerClick() + } +} diff --git a/feature/matching/src/main/res/layout/fragment_matching_map.xml b/feature/matching/src/main/res/layout/fragment_matching_map.xml index ebf7568e..a722942d 100644 --- a/feature/matching/src/main/res/layout/fragment_matching_map.xml +++ b/feature/matching/src/main/res/layout/fragment_matching_map.xml @@ -35,8 +35,8 @@ android:paddingStart="@dimen/base_horizontal_margin" android:paddingEnd="10dp"/> -