diff --git a/data/src/main/java/com/gdg/data/datasource/CrowdZeroDataSource.kt b/data/src/main/java/com/gdg/data/datasource/CrowdZeroDataSource.kt index 440e05a..84f3ff5 100644 --- a/data/src/main/java/com/gdg/data/datasource/CrowdZeroDataSource.kt +++ b/data/src/main/java/com/gdg/data/datasource/CrowdZeroDataSource.kt @@ -3,10 +3,12 @@ package com.gdg.data.datasource import com.gdg.data.dto.BaseResponse import com.gdg.data.dto.response.AssemblyResponseDto import com.gdg.data.dto.response.CongestionResponseDto +import com.gdg.data.dto.response.RoadResponseDto import com.gdg.data.dto.response.WeatherResponseDto interface CrowdZeroDataSource { suspend fun getWeather(areaId: Int): BaseResponse suspend fun getCongestion(areaId: Int): BaseResponse + suspend fun getRoad(areaId: Int): BaseResponse> suspend fun getAssembly(date: String): BaseResponse> } \ No newline at end of file diff --git a/data/src/main/java/com/gdg/data/datasourceimpl/CrowdZeroDataSourceImpl.kt b/data/src/main/java/com/gdg/data/datasourceimpl/CrowdZeroDataSourceImpl.kt index e94d6cf..378c38c 100644 --- a/data/src/main/java/com/gdg/data/datasourceimpl/CrowdZeroDataSourceImpl.kt +++ b/data/src/main/java/com/gdg/data/datasourceimpl/CrowdZeroDataSourceImpl.kt @@ -4,6 +4,7 @@ import com.gdg.data.datasource.CrowdZeroDataSource import com.gdg.data.dto.BaseResponse import com.gdg.data.dto.response.AssemblyResponseDto import com.gdg.data.dto.response.CongestionResponseDto +import com.gdg.data.dto.response.RoadResponseDto import com.gdg.data.dto.response.WeatherResponseDto import com.gdg.data.service.CrowdZeroService import javax.inject.Inject @@ -19,6 +20,10 @@ class CrowdZeroDataSourceImpl @Inject constructor( return crowdZeroApiService.getCongestion(areaId) } + override suspend fun getRoad(areaId: Int): BaseResponse> { + return crowdZeroApiService.getRoad(areaId) + } + override suspend fun getAssembly(date: String): BaseResponse> { return crowdZeroApiService.getAssembly(date) } diff --git a/data/src/main/java/com/gdg/data/dto/response/RoadResponseDto.kt b/data/src/main/java/com/gdg/data/dto/response/RoadResponseDto.kt new file mode 100644 index 0000000..081a7e8 --- /dev/null +++ b/data/src/main/java/com/gdg/data/dto/response/RoadResponseDto.kt @@ -0,0 +1,16 @@ +package com.gdg.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RoadResponseDto( + @SerialName("id") val id: Long, + @SerialName("acdntOccrDt") val acdntOccrDt: String, + @SerialName("expClrDt") val expClrDt: String, + @SerialName("acdntInfo") val acdntInfo: String, + @SerialName("acdntX") val acdntX: Double, + @SerialName("acdntY") val acdntY: Double, + @SerialName("acdntTime") val acdntTime: String, + @SerialName("areaId") val areaId: Int +) \ No newline at end of file diff --git a/data/src/main/java/com/gdg/data/mapper/ResponseRoadDtoMapper.kt b/data/src/main/java/com/gdg/data/mapper/ResponseRoadDtoMapper.kt new file mode 100644 index 0000000..bfec87e --- /dev/null +++ b/data/src/main/java/com/gdg/data/mapper/ResponseRoadDtoMapper.kt @@ -0,0 +1,14 @@ +package com.gdg.data.mapper + +import com.gdg.data.dto.response.RoadResponseDto +import com.gdg.domain.entity.RoadEntity + +fun RoadResponseDto.toRoadEntity() = RoadEntity( + areaId = id.toInt(), + acdntOccrDt = acdntOccrDt, + expClrDt = expClrDt, + acdntInfo = acdntInfo, + acdntX = acdntX, + acdntY = acdntY, + acdntTime = acdntTime +) diff --git a/data/src/main/java/com/gdg/data/repositoryimpl/CrowdZeroRepositoryImpl.kt b/data/src/main/java/com/gdg/data/repositoryimpl/CrowdZeroRepositoryImpl.kt index 41bcf9f..19a53b5 100644 --- a/data/src/main/java/com/gdg/data/repositoryimpl/CrowdZeroRepositoryImpl.kt +++ b/data/src/main/java/com/gdg/data/repositoryimpl/CrowdZeroRepositoryImpl.kt @@ -2,9 +2,12 @@ package com.gdg.data.repositoryimpl import com.gdg.data.datasource.CrowdZeroDataSource import com.gdg.data.mapper.toCongestionEntity +import com.gdg.data.mapper.toExampleEntity import com.gdg.data.mapper.toScheduleEntity import com.gdg.data.mapper.toWeatherEntity +import com.gdg.data.mapper.toRoadEntity import com.gdg.domain.entity.CongestionEntity +import com.gdg.domain.entity.RoadEntity import com.gdg.domain.entity.ScheduleEntity import com.gdg.domain.entity.WeatherEntity import com.gdg.domain.repository.CrowdZeroRepository @@ -27,9 +30,16 @@ class CrowdZeroRepositoryImpl @Inject constructor( } } + override suspend fun getRoad(areaId: Int): Result> { + return runCatching { + crowdZeroDataSource.getRoad(areaId).data?.map { it.toRoadEntity() } ?: emptyList() + } + } + +} override suspend fun getAssembly(date: String): Result> { return runCatching { crowdZeroDataSource.getAssembly(date).data?.map { it.toScheduleEntity() } ?: emptyList() } } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/gdg/data/service/CrowdZeroService.kt b/data/src/main/java/com/gdg/data/service/CrowdZeroService.kt index eb1ba9a..4c4370f 100644 --- a/data/src/main/java/com/gdg/data/service/CrowdZeroService.kt +++ b/data/src/main/java/com/gdg/data/service/CrowdZeroService.kt @@ -3,7 +3,9 @@ package com.gdg.data.service import com.gdg.data.dto.BaseResponse import com.gdg.data.dto.response.AssemblyResponseDto import com.gdg.data.dto.response.CongestionResponseDto +import com.gdg.data.dto.response.RoadResponseDto import com.gdg.data.dto.response.WeatherResponseDto +import com.gdg.data.service.ApiKeyStorage.ACDNT import com.gdg.data.service.ApiKeyStorage.API import com.gdg.data.service.ApiKeyStorage.AREA_ID import com.gdg.data.service.ApiKeyStorage.ASSEMBLY @@ -24,6 +26,11 @@ interface CrowdZeroService { @Path(AREA_ID) areaId: Int ): BaseResponse + @GET("/$API/$ACDNT/{$AREA_ID}") + suspend fun getRoad( + @Path(AREA_ID) areaId: Int + ): BaseResponse> + @GET("/$API/$ASSEMBLY/{$DATE}") suspend fun getAssembly( @Path(DATE) date: String diff --git a/domain/src/main/java/com/gdg/domain/repository/CrowdZeroRepository.kt b/domain/src/main/java/com/gdg/domain/repository/CrowdZeroRepository.kt index 51aab01..7cc58c4 100644 --- a/domain/src/main/java/com/gdg/domain/repository/CrowdZeroRepository.kt +++ b/domain/src/main/java/com/gdg/domain/repository/CrowdZeroRepository.kt @@ -1,11 +1,13 @@ package com.gdg.domain.repository import com.gdg.domain.entity.CongestionEntity +import com.gdg.domain.entity.RoadEntity import com.gdg.domain.entity.ScheduleEntity import com.gdg.domain.entity.WeatherEntity interface CrowdZeroRepository { suspend fun getWeather(areaId: Int): Result suspend fun getCongestion(areaId: Int): Result + suspend fun getRoad(areaId: Int): Result> suspend fun getAssembly(date: String): Result> } \ No newline at end of file diff --git a/feature/src/main/java/com/gdg/feature/map/MapRoute.kt b/feature/src/main/java/com/gdg/feature/map/MapRoute.kt index a07b379..df3538a 100644 --- a/feature/src/main/java/com/gdg/feature/map/MapRoute.kt +++ b/feature/src/main/java/com/gdg/feature/map/MapRoute.kt @@ -59,8 +59,7 @@ import timber.log.Timber @Composable fun MapRoute( - mapViewModel: MapViewModel = hiltViewModel(), - navigateToDetail: (Long) -> Unit + mapViewModel: MapViewModel = hiltViewModel(), navigateToDetail: (Long) -> Unit ) { val mapProperties by remember { mutableStateOf( @@ -81,6 +80,11 @@ fun MapRoute( } val getCongestionState by mapViewModel.getCongestionState.collectAsStateWithLifecycle() val context = LocalContext.current + val roads by mapViewModel.roads.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + mapViewModel.getRoads() + } LaunchedEffect(key1 = mapViewModel.sideEffects) { mapViewModel.sideEffects.collect { sideEffect -> @@ -96,7 +100,7 @@ fun MapRoute( mapUiSettings = mapUiSettings, cameraPositionState = cameraPositionState, locations = mapViewModel.locations, - roads = mapViewModel.mockRoadEntity, + roads = roads, congestionState = getCongestionState, getPlaceEntity = { id -> mapViewModel.getCongestion(id.toInt()) }, onButtonClick = mapViewModel::navigateToDetail @@ -150,8 +154,7 @@ fun MapScreen( } if (roads.isNotEmpty()) { roads.forEach { road -> - Marker( - width = 20.dp, + Marker(width = 20.dp, height = 20.dp, state = MarkerState(position = LatLng(road.acdntY, road.acdntX)), icon = OverlayImage.fromResource(R.drawable.ic_ban_marker), @@ -162,8 +165,7 @@ fun MapScreen( sheetState.show() } true - } - ) + }) } } } @@ -171,29 +173,24 @@ fun MapScreen( modifier = Modifier .fillMaxWidth() .align(Alignment.TopCenter) - .padding(top = 110.dp, start = 10.dp, end = 10.dp), - state = listState + .padding(top = 110.dp, start = 10.dp, end = 10.dp), state = listState ) { itemsIndexed(locations) { index, location -> - MapChip( - title = location, - isSelected = selectedLocation == location, - onClick = { - if (selectedLocation == location) { - selectedLocation = null - } else { - selectedLocation = location - getPlaceEntity(location.id) - cameraPositionState.move( - CameraUpdate.scrollAndZoomTo(location.latLng, 17.0) - .animate(CameraAnimation.Easing) - ) - } - coroutineScope.launch { - listState.animateScrollToItem(index) - } + MapChip(title = location, isSelected = selectedLocation == location, onClick = { + if (selectedLocation == location) { + selectedLocation = null + } else { + selectedLocation = location + getPlaceEntity(location.id) + cameraPositionState.move( + CameraUpdate.scrollAndZoomTo(location.latLng, 17.0) + .animate(CameraAnimation.Easing) + ) } - ) + coroutineScope.launch { + listState.animateScrollToItem(index) + } + }) } } selectedLocation?.let { location -> diff --git a/feature/src/main/java/com/gdg/feature/map/MapViewModel.kt b/feature/src/main/java/com/gdg/feature/map/MapViewModel.kt index a0fe872..242c00e 100644 --- a/feature/src/main/java/com/gdg/feature/map/MapViewModel.kt +++ b/feature/src/main/java/com/gdg/feature/map/MapViewModel.kt @@ -26,9 +26,16 @@ class MapViewModel @Inject constructor( MutableStateFlow(UiState.Empty) val getCongestionState: StateFlow> get() = _getCongestionState + private val _getRoadState: MutableStateFlow>> = + MutableStateFlow(UiState.Empty) + val getRoadState: StateFlow>> get() = _getRoadState + private val _sideEffects: MutableSharedFlow = MutableSharedFlow() val sideEffects: SharedFlow get() = _sideEffects + private val _roads = MutableStateFlow>(emptyList()) + val roads: StateFlow> get() = _roads + fun getCongestion(areaId: Int) { viewModelScope.launch { _getCongestionState.emit(UiState.Loading) @@ -51,6 +58,29 @@ class MapViewModel @Inject constructor( } } + fun getRoads() { + viewModelScope.launch { + _getRoadState.emit(UiState.Loading) + _roads.emit(emptyList()) + (1..5).forEach { areaId -> + crowdZeroRepository.getRoad(areaId).fold( + onSuccess = { + _roads.emit(_roads.value + it) + }, + onFailure = { + _getRoadState.emit(UiState.Failure(it.message.toString())) + } + ) + } + + if (_roads.value.isNotEmpty()) { + _getRoadState.emit(UiState.Success(_roads.value)) + } else { + _getRoadState.emit(UiState.Failure("도로 정보를 가져오는 데 실패했습니다.")) + } + } + } + @Stable val locations = immutableListOf( LocationType.GANGNAM_STATION, @@ -60,51 +90,10 @@ class MapViewModel @Inject constructor( LocationType.YEOUIDO ) - fun getPlaceEntity(id: Long): PlaceEntity? { - return mockPlaces.find { it.id == id } - } - fun navigateToDetail(id: Long) { viewModelScope.launch { _sideEffects.emit(MapSideEffect.NavigateToDetail(id)) } } - private val mockPlaces = listOf( - PlaceEntity(1, "강남역", "보통", 100, 200), - PlaceEntity(2, "광화문 광장", "보통", 100, 200), - PlaceEntity(3, "삼각지역", "보통", 100, 200), - PlaceEntity(4, "서울역", "보통", 100, 200), - PlaceEntity(5, "여의도", "보통", 100, 200) - ) - - val mockRoadEntity = listOf( - RoadEntity( - 1, - "2021-09-01 13:00", - "2021-09-01 13:00", - "사고1", - 126.97719959199067, - 37.57473917460146, - "2021-09-01 13:00" - ), - RoadEntity( - 2, - "2021-09-01 13:00", - "2021-09-01 13:00", - "사고2", - 126.97724062015716, - 37.57196573522649, - "2021-09-01 13:00" - ), - RoadEntity( - 3, - "2021-09-01 13:00", - "2021-09-01 13:00", - "사고3", - 126.97681755577895, - 37.56963658011575, - "2021-09-01 13:00" - ) - ) }