diff --git a/app/src/main/java/com/spoony/spoony/data/di/RepositoryModule.kt b/app/src/main/java/com/spoony/spoony/data/di/RepositoryModule.kt index 11329e6a..e268d869 100644 --- a/app/src/main/java/com/spoony/spoony/data/di/RepositoryModule.kt +++ b/app/src/main/java/com/spoony/spoony/data/di/RepositoryModule.kt @@ -1,7 +1,9 @@ package com.spoony.spoony.data.di import com.spoony.spoony.data.repositoryimpl.DummyRepositoryImpl +import com.spoony.spoony.data.repositoryimpl.PostRepositoryImpl import com.spoony.spoony.domain.repository.DummyRepository +import com.spoony.spoony.domain.repository.PostRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -14,4 +16,8 @@ abstract class RepositoryModule { @Binds @Singleton abstract fun bindDummyRepository(dummyRepositoryImpl: DummyRepositoryImpl): DummyRepository + + @Binds + @Singleton + abstract fun bindPostRepository(postRepositoryImpl: PostRepositoryImpl): PostRepository } diff --git a/app/src/main/java/com/spoony/spoony/data/repositoryimpl/PostRepositoryImpl.kt b/app/src/main/java/com/spoony/spoony/data/repositoryimpl/PostRepositoryImpl.kt new file mode 100644 index 00000000..64093bac --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/data/repositoryimpl/PostRepositoryImpl.kt @@ -0,0 +1,59 @@ +package com.spoony.spoony.data.repositoryimpl + +import com.spoony.spoony.domain.entity.CategoryEntity +import com.spoony.spoony.domain.entity.PostEntity +import com.spoony.spoony.domain.repository.PostRepository +import javax.inject.Inject +import kotlinx.collections.immutable.persistentListOf + +class PostRepositoryImpl @Inject constructor() : PostRepository { + override suspend fun getPost(postId: Int): Result = + Result.success( + PostEntity( + postId = postId, + userId = 1, + photoUrlList = persistentListOf( + "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/post/%2Fc4c71962-10df-4b79-999d-f17737bfa5a6starbucks_1.jpg", + "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/post/%2Fe14dc4d1-f11c-4187-a120-54ab0c2493b2starbucks_2.jpg", + "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/post/%2Fd25a3ca9-f107-47e2-8ca7-b00ed9a1704cstarbucks_3.jpg" + ), + title = "스타벅스 강남R점 후기", + date = "2025년 1월", + menuList = persistentListOf( + "아메리카노", + "카페라떼" + ), + description = "강남R점에서 커피를 마셨습니다. 분위기가 좋았어요!", + placeName = "스타벅스 강남R점", + placeAddress = "서울특별시 강남구 역삼동 825", + latitude = 37.497711, + longitude = 127.028439, + addMapCount = 1, + isAddMap = false, + isScooped = false, + category = CategoryEntity( + categoryId = 1, + categoryName = "카페", + iconUrl = "https://spoony-storage.s3.ap-northeast-2.amazonaws.com/category/icons/cafe_color.png", + unSelectedIconUrl = "unSelectedIconUrl", + textColor = "FF7E84", + backgroundColor = "FFE4E5" + ) + ) + ) + + override suspend fun postScoopPost(postId: Int, userId: Int): Result = + Result.success( + true + ) + + override suspend fun postAddMap(postId: Int, userId: Int): Result = + Result.success( + true + ) + + override suspend fun deletePinMap(postId: Int, userId: Int): Result = + Result.success( + true + ) +} diff --git a/app/src/main/java/com/spoony/spoony/domain/entity/UserEntity.kt b/app/src/main/java/com/spoony/spoony/domain/entity/UserEntity.kt index 1a9759b4..59878bca 100644 --- a/app/src/main/java/com/spoony/spoony/domain/entity/UserEntity.kt +++ b/app/src/main/java/com/spoony/spoony/domain/entity/UserEntity.kt @@ -1,7 +1,9 @@ package com.spoony.spoony.domain.entity data class UserEntity( - val userProfileUrl: String, + val userId: Int, + val userEmail: String, val userName: String, + val userProfileUrl: String, val userRegion: String ) diff --git a/app/src/main/java/com/spoony/spoony/domain/repository/PostRepository.kt b/app/src/main/java/com/spoony/spoony/domain/repository/PostRepository.kt new file mode 100644 index 00000000..8546705d --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/domain/repository/PostRepository.kt @@ -0,0 +1,13 @@ +package com.spoony.spoony.domain.repository + +import com.spoony.spoony.domain.entity.PostEntity + +interface PostRepository { + suspend fun getPost(postId: Int): Result + + suspend fun postScoopPost(postId: Int, userId: Int): Result + + suspend fun postAddMap(postId: Int, userId: Int): Result + + suspend fun deletePinMap(postId: Int, userId: Int): Result +} diff --git a/app/src/main/java/com/spoony/spoony/presentation/main/MainNavigator.kt b/app/src/main/java/com/spoony/spoony/presentation/main/MainNavigator.kt index 2af7e752..0d12e464 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/main/MainNavigator.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/main/MainNavigator.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.remember import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavHostController +import androidx.navigation.NavOptions import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions @@ -12,6 +13,7 @@ import com.spoony.spoony.presentation.explore.navigation.navigateToExplore import com.spoony.spoony.presentation.map.navigaion.Map import com.spoony.spoony.presentation.map.navigaion.navigateToMap import com.spoony.spoony.presentation.register.navigation.navigateToRegister +import com.spoony.spoony.presentation.report.navigation.navigateToReport class MainNavigator( val navController: NavHostController @@ -51,6 +53,14 @@ class MainNavigator( currentDestination?.hasRoute(it::class) == true } + fun navigateToReport(navOptions: NavOptions? = null) { + navController.navigateToReport(navOptions) + } + + fun navigateUp() { + navController.navigateUp() + } + inline fun isCurrentDestination(destination: NavDestination): Boolean { return navController.currentDestination == destination } diff --git a/app/src/main/java/com/spoony/spoony/presentation/main/MainScreen.kt b/app/src/main/java/com/spoony/spoony/presentation/main/MainScreen.kt index e911c5a0..88d1145c 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/main/MainScreen.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/main/MainScreen.kt @@ -53,7 +53,9 @@ fun MainScreen( ) placeDetailNavGraph( - paddingValues = innerPadding + paddingValues = innerPadding, + navigateUp = navigator::navigateUp, + navigateToReport = navigator::navigateToReport ) reportNavGraph() diff --git a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt index 4162bbd9..e2ffa6cd 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt @@ -8,9 +8,11 @@ import android.os.Build import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -56,11 +58,14 @@ import com.spoony.spoony.presentation.placeDetail.component.PlaceDetailImageLazy import com.spoony.spoony.presentation.placeDetail.component.ScoopDialog import com.spoony.spoony.presentation.placeDetail.component.StoreInfo import com.spoony.spoony.presentation.placeDetail.component.UserProfileInfo +import com.spoony.spoony.presentation.placeDetail.type.DropdownOption import kotlinx.collections.immutable.ImmutableList @Composable fun PlaceDetailRoute( paddingValues: PaddingValues, + navigateToReport: () -> Unit, + navigateUp: () -> Unit, viewModel: PlaceDetailViewModel = hiltViewModel() ) { val lifecycleOwner = LocalLifecycleOwner.current @@ -69,24 +74,27 @@ fun PlaceDetailRoute( val spoonAmount = when (state.spoonAmountEntity) { is UiState.Success -> (state.spoonAmountEntity as UiState.Success).data - else -> 0 + else -> 99 } val userProfile = when (state.userEntity) { is UiState.Success -> (state.userEntity as UiState.Success).data else -> UserEntity( - userProfileUrl = "", - userName = "", - userRegion = "" + userId = -1, + userEmail = "test@email.com", + userProfileUrl = "https://avatars.githubusercontent.com/u/93641814?v=4", + userName = "안세홍", + userRegion = "성북구" ) } - when (state.postEntity) { is UiState.Empty -> {} is UiState.Loading -> {} is UiState.Failure -> {} is UiState.Success -> { with(state.postEntity as UiState.Success) { + val postId = (state.postId as? UiState.Success)?.data ?: return + val userId = (state.userId as? UiState.Success)?.data ?: return PlaceDetailScreen( paddingValues = paddingValues, menuList = data.menuList, @@ -106,11 +114,12 @@ fun PlaceDetailRoute( isAddMap = data.isAddMap, latitude = data.latitude, longitude = data.longitude, - onScoopButtonClick = viewModel::useSpoon, - onAddMapButtonClick = viewModel::updateAddMap, + onScoopButtonClick = { viewModel.useSpoon(postId, userId) }, + onAddMapButtonClick = { viewModel.addMyMap(postId, userId) }, + onDeletePinMapButtonClick = { viewModel.deletePinMap(postId, userId) }, dropdownMenuList = state.dropDownMenuList, - onBackButtonClick = {}, - onReportButtonClick = {} + onBackButtonClick = navigateUp, + onReportButtonClick = navigateToReport ) } } @@ -138,10 +147,11 @@ private fun PlaceDetailScreen( latitude: Double, longitude: Double, onScoopButtonClick: () -> Unit, - onAddMapButtonClick: (Boolean) -> Unit, + onAddMapButtonClick: () -> Unit, + onDeletePinMapButtonClick: () -> Unit, onBackButtonClick: () -> Unit, dropdownMenuList: ImmutableList, - onReportButtonClick: (String) -> Unit + onReportButtonClick: () -> Unit ) { val scrollState = rememberScrollState() val context = LocalContext.current @@ -163,7 +173,7 @@ private fun PlaceDetailScreen( Column( modifier = Modifier .fillMaxSize() - .padding(paddingValues) + .padding(bottom = paddingValues.calculateBottomPadding()) ) { TagTopAppBar( count = spoonAmount, @@ -193,7 +203,13 @@ private fun PlaceDetailScreen( IconDropdownMenu( menuItems = dropdownMenuList, - onMenuItemClick = onReportButtonClick + onMenuItemClick = { menu -> + when (menu) { + DropdownOption.REPORT.string -> { + onReportButtonClick() + } + } + } ) } @@ -264,7 +280,8 @@ private fun PlaceDetailScreen( context = context ) }, - onAddMapButtonClick = onAddMapButtonClick + onAddMapButtonClick = onAddMapButtonClick, + onDeletePinMapButtonClick = onDeletePinMapButtonClick ) } } @@ -306,7 +323,8 @@ private fun PlaceDetailBottomBar( addMapCount: Int, onScoopButtonClick: () -> Unit, onSearchMapClick: () -> Unit, - onAddMapButtonClick: (Boolean) -> Unit, + onAddMapButtonClick: () -> Unit, + onDeletePinMapButtonClick: () -> Unit, modifier: Modifier = Modifier, isScooped: Boolean = false, isAddMap: Boolean = false @@ -318,7 +336,8 @@ private fun PlaceDetailBottomBar( .padding( horizontal = 20.dp, vertical = 10.dp - ), + ) + .height(IntrinsicSize.Max), verticalAlignment = Alignment.CenterVertically ) { if (isScooped) { @@ -334,8 +353,15 @@ private fun PlaceDetailBottomBar( Column( modifier = Modifier - .sizeIn(minWidth = 56.dp, minHeight = 56.dp) - .noRippleClickable { onAddMapButtonClick(isAddMap) }, + .fillMaxHeight() + .sizeIn(minWidth = 56.dp) + .noRippleClickable( + if (isAddMap) { + onDeletePinMapButtonClick + } else { + onAddMapButtonClick + } + ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { diff --git a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailState.kt b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailState.kt index f8ba5463..6c506694 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailState.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailState.kt @@ -7,6 +7,8 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf data class PlaceDetailState( + val postId: UiState = UiState.Loading, + val userId: UiState = UiState.Loading, val postEntity: UiState = UiState.Loading, val userEntity: UiState = UiState.Loading, val spoonAmountEntity: UiState = UiState.Loading, diff --git a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailViewModel.kt b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailViewModel.kt index c3b7c610..eaad3b81 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailViewModel.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailViewModel.kt @@ -1,20 +1,113 @@ package com.spoony.spoony.presentation.placeDetail +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import com.spoony.spoony.core.state.UiState +import com.spoony.spoony.domain.repository.PostRepository +import com.spoony.spoony.presentation.placeDetail.navigation.PlaceDetail import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch @HiltViewModel -class PlaceDetailViewModel @Inject constructor() : ViewModel() { - var _state: MutableStateFlow = MutableStateFlow(PlaceDetailState()) +class PlaceDetailViewModel @Inject constructor( + private val postRepository: PostRepository, + savedStateHandle: SavedStateHandle +) : ViewModel() { + private var _state: MutableStateFlow = MutableStateFlow(PlaceDetailState()) val state: StateFlow get() = _state - fun useSpoon() { + init { + val postArgs = savedStateHandle.toRoute() + _state.value = _state.value.copy( + postId = UiState.Success(data = postArgs.postId), + userId = UiState.Success(data = postArgs.userId) + ) + getPost(postArgs.postId) } - fun updateAddMap(isAddMap: Boolean) { + fun getPost(postId: Int) { + viewModelScope.launch { + postRepository.getPost(postId = postId) + .onSuccess { response -> + _state.value = _state.value.copy( + postEntity = UiState.Success(response) + ) + } + .onFailure { + // 실패 했을 경우 + } + } + } + + fun useSpoon(userId: Int, postId: Int) { + viewModelScope.launch { + postRepository.postScoopPost(userId = userId, postId = postId) + .onSuccess { response -> + (_state.value.postEntity as? UiState.Success)?.data?.let { currentPostEntity -> + with(currentPostEntity) { + _state.value = _state.value.copy( + postEntity = UiState.Success( + copy(isScooped = true) + ) + ) + } + } + } + .onFailure { + // 실패 했을 경우 + } + } + } + + fun addMyMap(userId: Int, postId: Int) { + viewModelScope.launch { + postRepository.postAddMap(userId = userId, postId = postId) + .onSuccess { response -> + (_state.value.postEntity as? UiState.Success)?.data?.let { currentPostEntity -> + with(currentPostEntity) { + _state.value = _state.value.copy( + postEntity = UiState.Success( + copy( + isAddMap = true, + addMapCount = currentPostEntity.addMapCount + 1 + ) + ) + ) + } + } + } + .onFailure { + // 실패 했을 경우 + } + } + } + + fun deletePinMap(userId: Int, postId: Int) { + viewModelScope.launch { + postRepository.deletePinMap(userId = userId, postId = postId) + .onSuccess { response -> + (_state.value.postEntity as? UiState.Success)?.data?.let { currentPostEntity -> + with(currentPostEntity) { + _state.value = _state.value.copy( + postEntity = UiState.Success( + copy( + isAddMap = false, + addMapCount = currentPostEntity.addMapCount - 1 + ) + ) + ) + } + } + } + .onFailure { + // 실패 했을 경우 + } + } } } diff --git a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/navigation/PlaceDetailNavigation.kt b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/navigation/PlaceDetailNavigation.kt index 9481b8c3..24a2efee 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/navigation/PlaceDetailNavigation.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/navigation/PlaceDetailNavigation.kt @@ -10,20 +10,26 @@ import com.spoony.spoony.presentation.placeDetail.PlaceDetailRoute import kotlinx.serialization.Serializable fun NavController.navigateToPlaceDetail( + postId: Int, + userId: Int, navOptions: NavOptions? = null ) { - navigate(PlaceDetail, navOptions) + navigate(PlaceDetail(postId, userId), navOptions) } fun NavGraphBuilder.placeDetailNavGraph( - paddingValues: PaddingValues + paddingValues: PaddingValues, + navigateToReport: () -> Unit, + navigateUp: () -> Unit ) { composable { PlaceDetailRoute( - paddingValues = paddingValues + paddingValues = paddingValues, + navigateToReport = navigateToReport, + navigateUp = navigateUp ) } } @Serializable -data object PlaceDetail : Route +data class PlaceDetail(val postId: Int, val userId: Int) : Route diff --git a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/type/DropdownOption.kt b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/type/DropdownOption.kt new file mode 100644 index 00000000..bb0d95a4 --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/type/DropdownOption.kt @@ -0,0 +1,7 @@ +package com.spoony.spoony.presentation.placeDetail.type + +enum class DropdownOption( + val string: String +) { + REPORT("신고하기") +}