diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e52a3959..ee5adf20 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -59,6 +59,7 @@ dependencies { implementation(projects.data.search) implementation(projects.local.main) implementation(projects.local.oauth) + implementation(projects.local.mypage) implementation(projects.local.place) implementation(projects.remote.oauth) implementation(projects.remote.onboarding) diff --git a/core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/button/SolplyButton.kt b/core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/button/SolplyButton.kt index 1e0a9bac..0825a733 100644 --- a/core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/button/SolplyButton.kt +++ b/core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/button/SolplyButton.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon import androidx.compose.material3.Text diff --git a/data/mypage/build.gradle.kts b/data/mypage/build.gradle.kts index 0c25eb39..2f2b9695 100644 --- a/data/mypage/build.gradle.kts +++ b/data/mypage/build.gradle.kts @@ -7,5 +7,6 @@ android { } dependencies { + implementation(projects.core.datastore) implementation(projects.domain.mypage) } diff --git a/data/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageLocalDataSource.kt b/data/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageLocalDataSource.kt new file mode 100644 index 00000000..e1dcd777 --- /dev/null +++ b/data/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageLocalDataSource.kt @@ -0,0 +1,5 @@ +package com.teamsolply.solply.mypage.datasource + +interface MypageLocalDataSource { + suspend fun saveAutoSignIn(autoSignIn: Boolean) +} diff --git a/data/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSource.kt b/data/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSource.kt index 3d18a557..f957db0e 100644 --- a/data/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSource.kt +++ b/data/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSource.kt @@ -1,13 +1,19 @@ package com.teamsolply.solply.mypage.datasource +import com.teamsolply.solply.mypage.dto.request.DeleteUserRequestDto import com.teamsolply.solply.mypage.dto.response.GetPersonaListResponseDto import com.teamsolply.solply.mypage.dto.response.GetUserInfoResponseDto +import com.teamsolply.solply.mypage.dto.response.GetWithdrawListResponseDto import com.teamsolply.solply.mypage.dto.response.NicknameDuplicateResponseDto import com.teamsolply.solply.mypage.dto.response.PlaceListResponseDto +import com.teamsolply.solply.network.model.NullableBaseResponse interface MypageRemoteDataSource { suspend fun getUserInfo(): GetUserInfoResponseDto suspend fun getPlaceList(townId: Long): PlaceListResponseDto suspend fun getPersonaList(): GetPersonaListResponseDto suspend fun checkNicknameDuplicate(nickname: String): NicknameDuplicateResponseDto + suspend fun getWithdrawList(): GetWithdrawListResponseDto + suspend fun deleteUser(deleteUserRequestDto: DeleteUserRequestDto): NullableBaseResponse + suspend fun getReportPlaceList(userId: Long): PlaceListResponseDto } diff --git a/data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/request/DeleteUserRequestDto.kt b/data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/request/DeleteUserRequestDto.kt new file mode 100644 index 00000000..6ab57e71 --- /dev/null +++ b/data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/request/DeleteUserRequestDto.kt @@ -0,0 +1,14 @@ +package com.teamsolply.solply.mypage.dto.request + +import com.teamsolply.solply.mypage.model.WithdrawType +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class DeleteUserRequestDto( + @SerialName("withdrawReason") + val withdrawType: WithdrawType, + + @SerialName("reasonText") + val reasonText: String +) diff --git a/data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/GetWithdrawListResponseDto.kt b/data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/GetWithdrawListResponseDto.kt new file mode 100644 index 00000000..33ef986b --- /dev/null +++ b/data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/GetWithdrawListResponseDto.kt @@ -0,0 +1,20 @@ +package com.teamsolply.solply.mypage.dto.response + +import com.teamsolply.solply.mypage.model.WithdrawType +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetWithdrawListResponseDto( + @SerialName("description") + val withdrawList: List +) + +@Serializable +data class WithdrawResponseDto( + @SerialName("withdrawType") + val withdrawType: WithdrawType, + + @SerialName("description") + val description: String +) diff --git a/data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/PlaceListResponseDto.kt b/data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/PlaceListResponseDto.kt index a9d1cf92..df91aa80 100644 --- a/data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/PlaceListResponseDto.kt +++ b/data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/PlaceListResponseDto.kt @@ -19,6 +19,9 @@ data class PlaceResponseDto( @SerialName("placeName") val placeName: String, + @SerialName("townId") + val townId: Long, + @SerialName("thumbnailImageUrl") val imageUrl: String, diff --git a/data/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepositoryImpl.kt b/data/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepositoryImpl.kt index b828e137..a5397b55 100644 --- a/data/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepositoryImpl.kt +++ b/data/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepositoryImpl.kt @@ -1,15 +1,19 @@ package com.teamsolply.solply.mypage.repository -import android.util.Log +import com.teamsolply.solply.mypage.datasource.MypageLocalDataSource import com.teamsolply.solply.mypage.datasource.MypageRemoteDataSource +import com.teamsolply.solply.mypage.dto.request.DeleteUserRequestDto import com.teamsolply.solply.mypage.dto.response.toDomain import com.teamsolply.solply.mypage.model.PersonaEntity import com.teamsolply.solply.mypage.model.PlaceInfoEntity import com.teamsolply.solply.mypage.model.UserInfo +import com.teamsolply.solply.mypage.model.WithdrawEntity +import com.teamsolply.solply.mypage.model.WithdrawType import javax.inject.Inject class MypageRepositoryImpl @Inject constructor( - private val mypageRemoteDataSource: MypageRemoteDataSource + private val mypageRemoteDataSource: MypageRemoteDataSource, + private val mypageLocalDataSource: MypageLocalDataSource ) : MypageRepository { override suspend fun getUserInfo(): Result = runCatching { mypageRemoteDataSource.getUserInfo() @@ -26,15 +30,31 @@ class MypageRepositoryImpl @Inject constructor( placeName = place.placeName, placeType = place.tag, imageUrls = place.imageUrl, - isSaved = place.isSaved + isSaved = place.isSaved, + townId = place.townId ) } } + override suspend fun getReportPlaceList(userId: Long): Result> = + runCatching { + mypageRemoteDataSource.getReportPlaceList(userId = userId).placeList + }.mapCatching { placeList -> + placeList.map { place -> + PlaceInfoEntity( + placeId = place.placeId, + placeName = place.placeName, + placeType = place.tag, + imageUrls = place.imageUrl, + isSaved = place.isSaved, + townId = place.townId + ) + } + } + override suspend fun getPersonaList(): Result> = runCatching { mypageRemoteDataSource.getPersonaList().personaDtoList }.mapCatching { personaList -> - Log.d("persona: ", "repo impl start") personaList.map { persona -> PersonaEntity( personaType = persona.personaType, @@ -46,4 +66,30 @@ class MypageRepositoryImpl @Inject constructor( override suspend fun checkNicknameDuplicate(nickname: String): Result = runCatching { mypageRemoteDataSource.checkNicknameDuplicate(nickname = nickname).isDuplicated } + + override suspend fun getWithdrawList(): Result> = runCatching { + mypageRemoteDataSource.getWithdrawList().withdrawList + }.mapCatching { list -> + list.map { reason -> + WithdrawEntity( + withdrawType = reason.withdrawType, + description = reason.description + ) + } + } + + override suspend fun deleteUser(withdrawType: WithdrawType, reason: String): Result = + runCatching { + mypageRemoteDataSource.deleteUser( + DeleteUserRequestDto( + withdrawType = withdrawType, + reasonText = reason + ) + ) + } + + override suspend fun saveAutoSignIn(autoSignIn: Boolean): Result = + runCatching { + mypageLocalDataSource.saveAutoSignIn(autoSignIn) + } } diff --git a/domain/mypage/build.gradle.kts b/domain/mypage/build.gradle.kts index 7f643ef7..82bd86b1 100644 --- a/domain/mypage/build.gradle.kts +++ b/domain/mypage/build.gradle.kts @@ -1,8 +1,10 @@ plugins { alias(libs.plugins.solply.java.library) + alias(libs.plugins.kotlin.serialization) } dependencies { implementation(projects.core.model) implementation(libs.bundles.coroutine) + implementation(libs.kotlinx.serialization.json) } diff --git a/domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/PlaceInfoEntity.kt b/domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/PlaceInfoEntity.kt index d391c0cc..5c65b931 100644 --- a/domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/PlaceInfoEntity.kt +++ b/domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/PlaceInfoEntity.kt @@ -6,6 +6,7 @@ data class PlaceInfoEntity( val placeId: Long, val placeName: String, val placeType: PlaceType, + val townId: Long, val imageUrls: String, val isSaved: Boolean, val isSelected: Boolean = false diff --git a/domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/WithdrawEntity.kt b/domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/WithdrawEntity.kt new file mode 100644 index 00000000..4358a847 --- /dev/null +++ b/domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/WithdrawEntity.kt @@ -0,0 +1,6 @@ +package com.teamsolply.solply.mypage.model + +data class WithdrawEntity( + val withdrawType: WithdrawType, + val description: String +) diff --git a/domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/WithdrawType.kt b/domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/WithdrawType.kt new file mode 100644 index 00000000..fbb7ad64 --- /dev/null +++ b/domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/WithdrawType.kt @@ -0,0 +1,15 @@ +package com.teamsolply.solply.mypage.model + +import kotlinx.serialization.Serializable + +@Serializable +enum class WithdrawType( + val description: String +) { + NOT_USE("자주 사용하지 않아서"), + DEFICIENT_INFO("원하는 지역과 장소가 부족해서"), + INCONVENIENT("앱 기능이 불편해서"), + HATE_RECOMMEND("추천 콘텐츠가 나와 맞지 않아서"), + USE_OTHER_SERVICE("다른 서비스를 사용하고 있습니다."), + OTHERS("기타") +} diff --git a/domain/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepository.kt b/domain/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepository.kt index 86b7648a..78dc5cbf 100644 --- a/domain/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepository.kt +++ b/domain/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepository.kt @@ -3,10 +3,16 @@ package com.teamsolply.solply.mypage.repository import com.teamsolply.solply.mypage.model.PersonaEntity import com.teamsolply.solply.mypage.model.PlaceInfoEntity import com.teamsolply.solply.mypage.model.UserInfo +import com.teamsolply.solply.mypage.model.WithdrawEntity +import com.teamsolply.solply.mypage.model.WithdrawType interface MypageRepository { suspend fun getUserInfo(): Result suspend fun getPlaceList(townId: Long): Result> + suspend fun getReportPlaceList(userId: Long): Result> suspend fun getPersonaList(): Result> suspend fun checkNicknameDuplicate(nickname: String): Result + suspend fun getWithdrawList(): Result> + suspend fun saveAutoSignIn(autoSignIn: Boolean): Result + suspend fun deleteUser(withdrawType: WithdrawType, reason: String): Result } diff --git a/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/component/CollectionScreen.kt b/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/component/CollectionScreen.kt index 4ec0e60f..c2b6115f 100644 --- a/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/component/CollectionScreen.kt +++ b/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/component/CollectionScreen.kt @@ -6,12 +6,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyGridScope import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.teamsolply.solply.collection.R import com.teamsolply.solply.designsystem.component.dialog.SolplyConfirmDialog @@ -24,10 +26,6 @@ fun CollectionScreen( onBackButtonClick: () -> Unit, onDialogConfirmClick: () -> Unit, onDialogDismissClick: () -> Unit, - selectMode: Boolean, - onSelectButtonClick: () -> Unit, - onDeleteButtonClick: () -> Unit, - onCancelButtonClick: () -> Unit, dialogState: Boolean, content: LazyGridScope.() -> Unit, modifier: Modifier = Modifier @@ -52,20 +50,38 @@ fun CollectionScreen( barText = town, onBackButtonClick = { onBackButtonClick() } ) - SelectModeBar( - selectMode = selectMode, - onSelectButtonClick = onSelectButtonClick, - onDeleteButtonClick = onDeleteButtonClick, - onCancelButtonClick = onCancelButtonClick - ) LazyVerticalGrid( - modifier = modifier + modifier = Modifier .fillMaxSize(), columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(10.dp), horizontalArrangement = Arrangement.Center, - contentPadding = PaddingValues(top = 16.dp, start = 16.dp, end = 16.dp), + contentPadding = PaddingValues(start = 16.dp, end = 16.dp), content = content ) } } + +@Preview +@Composable +private fun CollectionScreenPreview() { + SolplyTheme { + CollectionScreen( + town = "", + onBackButtonClick = {}, + onDialogConfirmClick = {}, + onDialogDismissClick = {}, + content = { + item(span = { GridItemSpan(maxLineSpan) }) { + SelectModeBar( + selectMode = false, + onSelectButtonClick = { }, + onDeleteButtonClick = { }, + onCancelButtonClick = { } + ) + } + }, + dialogState = false + ) + } +} diff --git a/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/course/CourseCollectionScreen.kt b/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/course/CourseCollectionScreen.kt index 3cc1385c..96d291cc 100644 --- a/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/course/CourseCollectionScreen.kt +++ b/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/course/CourseCollectionScreen.kt @@ -19,6 +19,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.teamsolply.solply.collection.R import com.teamsolply.solply.collection.collection.component.CollectionScreen +import com.teamsolply.solply.collection.collection.component.SelectModeBar import com.teamsolply.solply.designsystem.component.card.SolplyCourseCard import com.teamsolply.solply.model.MapsType import com.teamsolply.solply.ui.extension.customClickable @@ -61,12 +62,16 @@ fun CourseCollectionRoute( onBackButtonClick = { viewModel.sendIntent(CourseCollectionIntent.BackButtonClick) }, onDialogConfirmClick = { viewModel.sendIntent(CourseCollectionIntent.DialogConfirmClick) }, onDialogDismissClick = { viewModel.sendIntent(CourseCollectionIntent.DialogDismissClick) }, - selectMode = uiState.selectMode, - onSelectButtonClick = { viewModel.sendIntent(CourseCollectionIntent.SelectButtonClick) }, - onDeleteButtonClick = { viewModel.sendIntent(CourseCollectionIntent.DeleteButtonClick) }, - onCancelButtonClick = { viewModel.sendIntent(CourseCollectionIntent.CancelButtonClick) }, dialogState = uiState.dialogState, content = { + item(span = { GridItemSpan(maxLineSpan) }) { + SelectModeBar( + selectMode = uiState.selectMode, + onSelectButtonClick = { viewModel.sendIntent(CourseCollectionIntent.SelectButtonClick) }, + onDeleteButtonClick = { viewModel.sendIntent(CourseCollectionIntent.DeleteButtonClick) }, + onCancelButtonClick = { viewModel.sendIntent(CourseCollectionIntent.CancelButtonClick) } + ) + } itemsIndexed(uiState.courses) { index, it -> Box( modifier = Modifier diff --git a/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/place/PlaceCollectionScreen.kt b/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/place/PlaceCollectionScreen.kt index 0d2cff64..a3c9130c 100644 --- a/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/place/PlaceCollectionScreen.kt +++ b/feature/collection/src/main/java/com/teamsolply/solply/collection/collection/place/PlaceCollectionScreen.kt @@ -19,6 +19,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.teamsolply.solply.collection.R import com.teamsolply.solply.collection.collection.component.CollectionScreen +import com.teamsolply.solply.collection.collection.component.SelectModeBar import com.teamsolply.solply.designsystem.component.card.SolplyPlaceCard import com.teamsolply.solply.model.MapsType import com.teamsolply.solply.ui.extension.customClickable @@ -61,12 +62,16 @@ fun PlaceCollectionRoute( onBackButtonClick = { viewModel.sendIntent(PlaceCollectionIntent.BackButtonClick) }, onDialogConfirmClick = { viewModel.sendIntent(PlaceCollectionIntent.DialogConfirmClick) }, onDialogDismissClick = { viewModel.sendIntent(PlaceCollectionIntent.DialogDismissClick) }, - selectMode = uiState.selectMode, - onSelectButtonClick = { viewModel.sendIntent(PlaceCollectionIntent.SelectButtonClick) }, - onDeleteButtonClick = { viewModel.sendIntent(PlaceCollectionIntent.DeleteButtonClick) }, - onCancelButtonClick = { viewModel.sendIntent(PlaceCollectionIntent.CancelButtonClick) }, dialogState = uiState.dialogState, content = { + item(span = { GridItemSpan(maxLineSpan) }) { + SelectModeBar( + selectMode = uiState.selectMode, + onSelectButtonClick = { viewModel.sendIntent(PlaceCollectionIntent.SelectButtonClick) }, + onDeleteButtonClick = { viewModel.sendIntent(PlaceCollectionIntent.DeleteButtonClick) }, + onCancelButtonClick = { viewModel.sendIntent(PlaceCollectionIntent.CancelButtonClick) } + ) + } itemsIndexed(uiState.places) { index, it -> Box( modifier = Modifier diff --git a/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt b/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt index 33085e7f..a739dff2 100644 --- a/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt +++ b/feature/main/src/main/java/com/teamsolply/solply/main/MainNavigator.kt @@ -18,6 +18,7 @@ import com.teamsolply.solply.main.splash.Splash import com.teamsolply.solply.maps.navigation.navigateMaps import com.teamsolply.solply.mypage.navigation.navigateMypage import com.teamsolply.solply.mypage.profile.navigation.navigateProfile +import com.teamsolply.solply.mypage.withdraw.navigation.navigateWithdraw import com.teamsolply.solply.oauth.navigation.navigateOauth import com.teamsolply.solply.onboarding.navigation.navigateOnBoarding import com.teamsolply.solply.place.navigation.navigatePlace @@ -154,6 +155,12 @@ internal class MainNavigator( ) } + fun navigateToWithdraw( + navOptions: NavOptions + ) { + navController.navigateWithdraw(navOptions) + } + fun navigateToFavoriteTown(navOptions: NavOptions = navOptions {}) { navController.navigateFavoriteTown(navOptions) } diff --git a/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt b/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt index 3c1522d7..fe19db56 100644 --- a/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt +++ b/feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt @@ -42,6 +42,7 @@ import com.teamsolply.solply.model.SnackBarType import com.teamsolply.solply.mypage.navigation.Mypage import com.teamsolply.solply.mypage.navigation.mypageNavGraph import com.teamsolply.solply.mypage.profile.navigation.profileNavGraph +import com.teamsolply.solply.mypage.withdraw.navigation.withdrawNavGraph import com.teamsolply.solply.oauth.navigation.oauthNavGraph import com.teamsolply.solply.onboarding.navigation.onBoardingNavGraph import com.teamsolply.solply.place.navigation.placeNavGraph @@ -339,6 +340,23 @@ internal fun MainScreen( navigateToProfile = { val navOptions = navOptions { } navigator.navigateToProfile(navOptions) + }, + navigateToWithdraw = { + val navOptions = navOptions { } + navigator.navigateToWithdraw(navOptions) + }, + navigateToOauth = { + val navOptions = navOptions { } + navigator.navigateToOauth(navOptions) + }, + navigateToMaps = { mapsType, townId, placeId -> + val navOptions = navOptions {} + navigator.navigateToMaps( + mapsType = mapsType, + townId = townId, + placeId = placeId, + navOptions = navOptions + ) } ) profileNavGraph( @@ -352,6 +370,14 @@ internal fun MainScreen( navigator.navigateToMypage(navOptions = navOptions) } ) + withdrawNavGraph( + paddingValues = innerPadding, + navigateToBack = navigator::navigateToBack, + navigateToOauth = { + val navOptions = navOptions { } + navigator.navigateToOauth(navOptions) + } + ) favoriteTownNavGraph( paddingValues = innerPadding, navigateToBack = navigator::navigateToBack diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageContract.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageContract.kt index bdeca22e..9e44934c 100644 --- a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageContract.kt +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageContract.kt @@ -17,22 +17,31 @@ data class MypageState( profileImageUrl = "" ), val placeList: List = emptyList(), + val placeAllState: Boolean = false, val dialogState: Boolean = false ) : UiState sealed interface MypageIntent : UiIntent { data object Init : MypageIntent + data object MypageBackButtonClick : MypageIntent + data object PlaceAllBackButtonClick : MypageIntent + data class PlaceCardClick(val placeId: Long, val townId: Long) : MypageIntent + data object LogOutButtonClick : MypageIntent - data object WithdrawButtonClick : MypageIntent - data object DialogConfirmClick : MypageIntent - data object DialogDismissClick : MypageIntent + data object LogOutDialogConfirmClick : MypageIntent + data object LogOutDialogDismissClick : MypageIntent + data object PlaceAllClick : MypageIntent data object ProfileEditClick : MypageIntent + data object WithdrawClick : MypageIntent } sealed interface MypageSideEffect : SideEffect { data object NavigateToBack : MypageSideEffect data object NavigateToProfile : MypageSideEffect + data object NavigateToWithdraw : MypageSideEffect + data object NavigateToOauth : MypageSideEffect + data class NavigateToMap(val placeId: Long, val townId: Long) : MypageSideEffect } diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageScreen.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageScreen.kt index c6cbd419..bdbca4cc 100644 --- a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageScreen.kt +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageScreen.kt @@ -32,7 +32,9 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.teamsolply.solply.designsystem.component.dialog.SolplyConfirmDialog import com.teamsolply.solply.designsystem.theme.SolplyTheme +import com.teamsolply.solply.model.MapsType import com.teamsolply.solply.mypage.component.EmptyPlaceContainer +import com.teamsolply.solply.mypage.component.MypagePlaceAllScreen import com.teamsolply.solply.mypage.component.MypageSettingItem import com.teamsolply.solply.mypage.component.SavedPlaceListContainer import com.teamsolply.solply.mypage.model.PlaceInfoEntity @@ -45,6 +47,9 @@ fun MypageRoute( paddingValues: PaddingValues, navigateToBack: () -> Unit, navigateToProfile: () -> Unit, + navigateToWithdraw: () -> Unit, + navigateToOauth: () -> Unit, + navigateToMaps: (String, Long, Long) -> Unit, viewModel: MypageViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() @@ -58,21 +63,40 @@ fun MypageRoute( when (sideEffect) { MypageSideEffect.NavigateToBack -> navigateToBack() MypageSideEffect.NavigateToProfile -> navigateToProfile() + MypageSideEffect.NavigateToWithdraw -> navigateToWithdraw() + MypageSideEffect.NavigateToOauth -> navigateToOauth() + is MypageSideEffect.NavigateToMap -> navigateToMaps( + MapsType.PLACE_DETAIL.name, + sideEffect.placeId, + sideEffect.townId + ) } } } - - MypageScreen( - nickname = uiState.userInfo.nickname, - savedPlaceList = uiState.placeList, - dialogState = uiState.dialogState, - onBackButtonClick = navigateToBack, - onProfileEditClick = { viewModel.sendIntent(MypageIntent.ProfileEditClick) }, - onLogOutClick = { viewModel.sendIntent(MypageIntent.LogOutButtonClick) }, - onDialogConfirmClick = { viewModel.sendIntent(MypageIntent.DialogConfirmClick) }, - onDialogDismissClick = { viewModel.sendIntent(MypageIntent.DialogDismissClick) }, - modifier = Modifier.padding(paddingValues) - ) + if (uiState.placeAllState) { + MypagePlaceAllScreen( + placeList = uiState.placeList, + onBackButtonClick = { viewModel.sendIntent(MypageIntent.PlaceAllBackButtonClick) }, + onPlaceCardClick = { placeId, townId -> + viewModel.sendIntent(MypageIntent.PlaceCardClick(placeId, townId)) + }, + modifier = Modifier.padding(paddingValues) + ) + } else { + MypageScreen( + nickname = uiState.userInfo.nickname, + savedPlaceList = uiState.placeList.take(3), + dialogState = uiState.dialogState, + onBackButtonClick = { viewModel.sendIntent(MypageIntent.MypageBackButtonClick) }, + onProfileEditClick = { viewModel.sendIntent(MypageIntent.ProfileEditClick) }, + onAllClick = { viewModel.sendIntent(MypageIntent.PlaceAllClick) }, + onLogOutClick = { viewModel.sendIntent(MypageIntent.LogOutButtonClick) }, + onWithdrawClick = { viewModel.sendIntent(MypageIntent.WithdrawClick) }, + onDialogConfirmClick = { viewModel.sendIntent(MypageIntent.LogOutDialogConfirmClick) }, + onDialogDismissClick = { viewModel.sendIntent(MypageIntent.LogOutDialogDismissClick) }, + modifier = Modifier.padding(paddingValues) + ) + } } @Composable @@ -82,7 +106,9 @@ fun MypageScreen( dialogState: Boolean, onBackButtonClick: () -> Unit, onProfileEditClick: () -> Unit, + onAllClick: () -> Unit, onLogOutClick: () -> Unit, + onWithdrawClick: () -> Unit, onDialogConfirmClick: () -> Unit, onDialogDismissClick: () -> Unit, modifier: Modifier = Modifier @@ -91,7 +117,7 @@ fun MypageScreen( SolplyConfirmDialog( text = stringResource(R.string.logout_dialog), confirmButtonText = stringResource(R.string.logout_dialog_confirm), - dismissButtonText = stringResource(R.string.mypage_dialog_cancel), + dismissButtonText = stringResource(R.string.dialog_cancel), onClickConfirm = onDialogConfirmClick, onClickDismiss = onDialogDismissClick ) @@ -171,13 +197,36 @@ fun MypageScreen( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 20.dp, vertical = 16.dp) + .padding(horizontal = 20.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { Text( - text = "내가 등록한 장소", + text = stringResource(R.string.mypage_place), color = SolplyTheme.colors.black, style = SolplyTheme.typography.body16M ) + if (savedPlaceList.isNotEmpty()) { + Row( + modifier = Modifier + .customClickable( + rippleEnabled = false, + onClick = onAllClick + ), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.mypage_place_all), + style = SolplyTheme.typography.body14R, + color = SolplyTheme.colors.gray600 + ) + Icon( + painter = painterResource(com.teamsolply.solply.designsystem.R.drawable.ic_next_arrow), + contentDescription = "", + tint = SolplyTheme.colors.gray600 + ) + } + } } if (savedPlaceList.isEmpty()) { EmptyPlaceContainer( @@ -235,8 +284,8 @@ fun MypageScreen( isBorderEnabled = true ) MypageSettingItem( - text = "탈퇴하기", - onClick = { /* TODO */ }, + text = stringResource(R.string.mypage_withdraw), + onClick = onWithdrawClick, isBorderEnabled = false ) Spacer(modifier = Modifier.height(8.dp)) @@ -254,7 +303,9 @@ private fun MypageScreenPreview() { dialogState = false, onBackButtonClick = {}, onProfileEditClick = {}, + onAllClick = {}, onLogOutClick = {}, + onWithdrawClick = {}, onDialogConfirmClick = {}, onDialogDismissClick = {} ) diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageViewModel.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageViewModel.kt index 024b3409..22060318 100644 --- a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageViewModel.kt +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageViewModel.kt @@ -17,6 +17,10 @@ class MypageViewModel @Inject constructor( when (intent) { MypageIntent.Init -> getInitInfo() + MypageIntent.MypageBackButtonClick -> { + postSideEffect(MypageSideEffect.NavigateToBack) + } + MypageIntent.LogOutButtonClick -> { reduce { copy( @@ -25,25 +29,46 @@ class MypageViewModel @Inject constructor( } } - MypageIntent.WithdrawButtonClick -> { + MypageIntent.LogOutDialogConfirmClick -> { + viewModelScope.launch { + mypageRepository.saveAutoSignIn(false).onSuccess { + reduce { + copy(dialogState = false) + } + postSideEffect(MypageSideEffect.NavigateToOauth) + } + } } - MypageIntent.DialogConfirmClick -> { - // TODO 로그아웃 api + MypageIntent.LogOutDialogDismissClick -> { reduce { copy(dialogState = false) } } - MypageIntent.DialogDismissClick -> { + MypageIntent.PlaceAllClick -> { reduce { - copy(dialogState = false) + copy(placeAllState = true) } } + MypageIntent.PlaceAllBackButtonClick -> { + reduce { + copy(placeAllState = false) + } + } + + is MypageIntent.PlaceCardClick -> { + postSideEffect(MypageSideEffect.NavigateToMap(intent.placeId, intent.townId)) + } + MypageIntent.ProfileEditClick -> { postSideEffect(MypageSideEffect.NavigateToProfile) } + + MypageIntent.WithdrawClick -> { + postSideEffect(MypageSideEffect.NavigateToWithdraw) + } } } @@ -71,4 +96,16 @@ class MypageViewModel @Inject constructor( } } } + + private fun getReportPlaceList(userId: Long) { + viewModelScope.launch { + mypageRepository.getReportPlaceList(userId).onSuccess { + reduce { + copy( + placeList = it.toPersistentList() + ) + } + } + } + } } diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/MypagePlaceAllScreen.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/MypagePlaceAllScreen.kt new file mode 100644 index 00000000..83ae9ca3 --- /dev/null +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/MypagePlaceAllScreen.kt @@ -0,0 +1,91 @@ +package com.teamsolply.solply.mypage.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.teamsolply.solply.designsystem.component.card.SolplyPlaceCard +import com.teamsolply.solply.designsystem.component.topbar.SolplyTopBar +import com.teamsolply.solply.designsystem.theme.SolplyTheme +import com.teamsolply.solply.mypage.R +import com.teamsolply.solply.mypage.model.PlaceInfoEntity +import com.teamsolply.solply.ui.extension.customClickable + +@Composable +fun MypagePlaceAllScreen( + placeList: List, + onBackButtonClick: () -> Unit, + onPlaceCardClick: (Long, Long) -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier + .fillMaxSize() + .background(color = SolplyTheme.colors.white), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally + ) { + SolplyTopBar( + barText = stringResource(R.string.mypage_place), + onBackButtonClick = { onBackButtonClick() } + ) + LazyVerticalGrid( + modifier = Modifier + .fillMaxSize(), + columns = GridCells.Fixed(2), + verticalArrangement = Arrangement.spacedBy(10.dp), + horizontalArrangement = Arrangement.Center, + contentPadding = PaddingValues(start = 16.dp, end = 16.dp, top = 16.dp), + content = { + itemsIndexed(placeList) { index, it -> + Box( + modifier = Modifier + .fillMaxSize() + .customClickable( + rippleEnabled = false + ) { + onPlaceCardClick(it.townId, it.placeId) + }, + contentAlignment = if (index % 2 == 0) { + Alignment.CenterEnd + } else { + Alignment.CenterStart + } + ) { + SolplyPlaceCard( + name = it.placeName, + placeType = it.placeType, + imgRes = it.imageUrls, + selected = it.isSelected, + saved = it.isSaved, + touchable = false, + modifier = + if (index % 2 == 0) { + Modifier.padding(end = 5.dp) + } else { + Modifier.padding(start = 5.dp) + } + ) + } + } + item(span = { GridItemSpan(2) }) { + Spacer(modifier = Modifier.height(60.dp)) + } + } + ) + } +} diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SavedPlaceListContainer.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SavedPlaceListContainer.kt index 1ecfdcbd..839d9bbf 100644 --- a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SavedPlaceListContainer.kt +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SavedPlaceListContainer.kt @@ -1,5 +1,6 @@ package com.teamsolply.solply.mypage.component +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyRow @@ -19,7 +20,8 @@ fun SavedPlaceListContainer( ) { LazyRow( modifier = modifier - .fillMaxWidth() + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) ) { items(savedPlaceList) { SolplyPlaceCard( diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/navigation/MypageNavigation.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/navigation/MypageNavigation.kt index b272060d..0f565cb7 100644 --- a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/navigation/MypageNavigation.kt +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/navigation/MypageNavigation.kt @@ -20,6 +20,9 @@ fun NavController.navigateMypage( fun NavGraphBuilder.mypageNavGraph( navigateToBack: () -> Unit, navigateToProfile: () -> Unit, + navigateToWithdraw: () -> Unit, + navigateToOauth: () -> Unit, + navigateToMaps: (String, Long, Long) -> Unit, paddingValues: PaddingValues ) { composable { backStackEntry -> @@ -28,6 +31,9 @@ fun NavGraphBuilder.mypageNavGraph( paddingValues = paddingValues, navigateToBack = navigateToBack, navigateToProfile = navigateToProfile, + navigateToWithdraw = navigateToWithdraw, + navigateToOauth = navigateToOauth, + navigateToMaps = navigateToMaps, viewModel = viewModel ) } diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/WithdrawContract.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/WithdrawContract.kt new file mode 100644 index 00000000..51a74a72 --- /dev/null +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/WithdrawContract.kt @@ -0,0 +1,32 @@ +package com.teamsolply.solply.mypage.withdraw + +import com.teamsolply.solply.mypage.model.WithdrawEntity +import com.teamsolply.solply.ui.base.SideEffect +import com.teamsolply.solply.ui.base.UiIntent +import com.teamsolply.solply.ui.base.UiState + +data class WithdrawState( + val withdrawList: List = emptyList(), + val selectedIndex: Int = -1, + val withdrawReason: String = "", + val buttonEnabled: Boolean = false, + val dialogState: Boolean = false +) : UiState + +sealed interface WithdrawIntent : UiIntent { + data object Init : WithdrawIntent + + data object BackButtonClick : WithdrawIntent + + data class WithdrawItemClick(val index: Int) : WithdrawIntent + data object WithdrawButtonClick : WithdrawIntent + data class WithdrawReasonInput(val reason: String) : WithdrawIntent + + data object DialogConfirmClick : WithdrawIntent + data object DialogDismissClick : WithdrawIntent +} + +sealed interface WithdrawSideEffect : SideEffect { + data object NavigateToBack : WithdrawSideEffect + data object NavigateToOauth : WithdrawSideEffect +} diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/WithdrawScreen.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/WithdrawScreen.kt new file mode 100644 index 00000000..3f242120 --- /dev/null +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/WithdrawScreen.kt @@ -0,0 +1,215 @@ +package com.teamsolply.solply.mypage.withdraw + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.teamsolply.solply.designsystem.component.button.SolplyBasicButton +import com.teamsolply.solply.designsystem.component.dialog.SolplyConfirmDialog +import com.teamsolply.solply.designsystem.component.textfield.SolplyFixedReportTextField +import com.teamsolply.solply.designsystem.component.topbar.SolplyTopBar +import com.teamsolply.solply.designsystem.theme.SolplyTheme +import com.teamsolply.solply.mypage.R +import com.teamsolply.solply.mypage.model.WithdrawEntity +import com.teamsolply.solply.mypage.model.WithdrawType +import com.teamsolply.solply.ui.extension.customClickable +import com.teamsolply.solply.ui.lifecycle.LaunchedEffectWithLifecycle +import kotlinx.coroutines.flow.collectLatest + +@Composable +fun WithdrawRoute( + paddingValues: PaddingValues, + navigateToBack: () -> Unit, + navigateToOauth: () -> Unit, + viewModel: WithdrawViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + viewModel.sendIntent(WithdrawIntent.Init) + } + + LaunchedEffectWithLifecycle { + viewModel.sideEffect.collectLatest { sideEffect -> + when (sideEffect) { + WithdrawSideEffect.NavigateToBack -> navigateToBack() + WithdrawSideEffect.NavigateToOauth -> navigateToOauth() + } + } + } + + WithdrawScreen( + withdrawList = uiState.withdrawList, + withdrawReason = uiState.withdrawReason, + selectedIndex = uiState.selectedIndex, + buttonEnabled = uiState.buttonEnabled, + dialogState = uiState.dialogState, + onWithdrawItemClick = { viewModel.sendIntent(WithdrawIntent.WithdrawItemClick(it)) }, + onTextFieldValueChange = { viewModel.sendIntent(WithdrawIntent.WithdrawReasonInput(it)) }, + onWithdrawButtonClick = { viewModel.sendIntent(WithdrawIntent.WithdrawButtonClick) }, + onDialogConfirmClick = { viewModel.sendIntent(WithdrawIntent.DialogConfirmClick) }, + onDialogDismissClick = { viewModel.sendIntent(WithdrawIntent.DialogDismissClick) }, + onBackButtonClick = { viewModel.sendIntent(WithdrawIntent.BackButtonClick) }, + modifier = Modifier.padding(paddingValues) + ) +} + +@Composable +fun WithdrawScreen( + withdrawList: List, + withdrawReason: String, + selectedIndex: Int, + buttonEnabled: Boolean, + dialogState: Boolean, + onWithdrawItemClick: (Int) -> Unit, + onTextFieldValueChange: (String) -> Unit, + onWithdrawButtonClick: () -> Unit, + onDialogConfirmClick: () -> Unit, + onDialogDismissClick: () -> Unit, + onBackButtonClick: () -> Unit, + modifier: Modifier = Modifier +) { + if (dialogState) { + SolplyConfirmDialog( + text = stringResource(R.string.mypage_withdraw), + confirmButtonText = stringResource(R.string.withdraw_dialog_confirm), + dismissButtonText = stringResource(R.string.dialog_cancel), + onClickConfirm = onDialogConfirmClick, + onClickDismiss = onDialogDismissClick + ) + } + Column( + modifier = modifier + .fillMaxSize() + .background(SolplyTheme.colors.white), + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally + ) { + LazyColumn { + item { + SolplyTopBar( + barText = stringResource(R.string.mypage_withdraw), + onBackButtonClick = onBackButtonClick + ) + } + itemsIndexed(withdrawList) { index, item -> + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .customClickable( + rippleEnabled = false, + onClick = { onWithdrawItemClick(index) } + ), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = item.description, + style = if (index == selectedIndex) SolplyTheme.typography.body16M else SolplyTheme.typography.body16R, + color = SolplyTheme.colors.black, + modifier = Modifier.padding(vertical = 11.dp) + ) + if (index == selectedIndex) { + Icon( + painter = painterResource(com.teamsolply.solply.designsystem.R.drawable.ic_filter_selected), + contentDescription = "", + tint = Color.Unspecified + ) + } + } + if (index == withdrawList.lastIndex && selectedIndex == withdrawList.lastIndex) { + SolplyFixedReportTextField( + value = withdrawReason, + onValueChange = { onTextFieldValueChange(it) } + ) + } else { + HorizontalDivider( + thickness = 1.dp, + color = SolplyTheme.colors.gray200 + ) + } + } + } + } + SolplyBasicButton( + text = stringResource(R.string.mypage_withdraw), + onClick = { + if (buttonEnabled) { + onWithdrawButtonClick() + } + }, + enabledBackgroundColor = if (buttonEnabled) { + SolplyTheme.colors.gray900 + } else { + SolplyTheme.colors.gray300 + }, + textColor = if (buttonEnabled) { + SolplyTheme.colors.white + } else { + SolplyTheme.colors.gray800 + }, + modifier = Modifier.padding(bottom = 24.dp, start = 20.dp, end = 20.dp) + ) + } +} + +@Preview +@Composable +private fun WithdrawScreenPreview() { + SolplyTheme { + WithdrawScreen( + withdrawList = listOf( + WithdrawEntity( + withdrawType = WithdrawType.NOT_USE, + description = "자주 사용하지 않아서" + ), + WithdrawEntity( + withdrawType = WithdrawType.NOT_USE, + description = "원하는 지역과 장소가 부족해서" + ), + WithdrawEntity( + withdrawType = WithdrawType.OTHERS, + description = "기타 (직접 입력)" + ) + ), + withdrawReason = "", + selectedIndex = 2, + buttonEnabled = false, + dialogState = false, + onWithdrawItemClick = {}, + onWithdrawButtonClick = {}, + onDialogConfirmClick = {}, + onTextFieldValueChange = {}, + onDialogDismissClick = {}, + onBackButtonClick = {}, + modifier = Modifier.fillMaxSize() + ) + } +} diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/WithdrawViewModel.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/WithdrawViewModel.kt new file mode 100644 index 00000000..5e7737cb --- /dev/null +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/WithdrawViewModel.kt @@ -0,0 +1,87 @@ +package com.teamsolply.solply.mypage.withdraw + +import android.util.Log +import androidx.lifecycle.viewModelScope +import com.teamsolply.solply.mypage.repository.MypageRepository +import com.teamsolply.solply.ui.base.BaseViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class WithdrawViewModel @Inject constructor( + private val mypageRepository: MypageRepository +) : + BaseViewModel(WithdrawState()) { + override fun handleIntent(intent: WithdrawIntent) { + when (intent) { + WithdrawIntent.Init -> { + getWithdrawList() + } + + WithdrawIntent.BackButtonClick -> { + postSideEffect(WithdrawSideEffect.NavigateToBack) + } + + is WithdrawIntent.WithdrawReasonInput -> { + reduce { + copy( + withdrawReason = intent.reason, + buttonEnabled = intent.reason.isNotEmpty() + ) + } + } + + is WithdrawIntent.WithdrawItemClick -> { + reduce { + copy( + selectedIndex = intent.index, + buttonEnabled = intent.index != withdrawList.lastIndex + ) + } + } + + WithdrawIntent.WithdrawButtonClick -> { + reduce { + copy( + dialogState = true + ) + } + } + + WithdrawIntent.DialogConfirmClick -> { + val current = uiState.value + viewModelScope.launch { + Log.d("withdraw:", "success") + mypageRepository.deleteUser( + withdrawType = current.withdrawList[current.selectedIndex].withdrawType, + reason = current.withdrawReason + ).onSuccess { + mypageRepository.saveAutoSignIn(false).onSuccess { + reduce { + copy(dialogState = false) + } + postSideEffect(WithdrawSideEffect.NavigateToOauth) + } + } + } + } + + WithdrawIntent.DialogDismissClick -> { + reduce { + copy(dialogState = false) + } + } + } + } + + private fun getWithdrawList() { + viewModelScope.launch { + mypageRepository.getWithdrawList().onSuccess { + reduce { + copy(withdrawList = it) + } + } + } + } +} diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/navigation/WithdrawNavigation.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/navigation/WithdrawNavigation.kt new file mode 100644 index 00000000..4f10c38a --- /dev/null +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/withdraw/navigation/WithdrawNavigation.kt @@ -0,0 +1,37 @@ +package com.teamsolply.solply.mypage.withdraw.navigation + +import androidx.compose.foundation.layout.PaddingValues +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.teamsolply.solply.mypage.withdraw.WithdrawRoute +import com.teamsolply.solply.mypage.withdraw.WithdrawViewModel +import com.teamsolply.solply.navigation.Route +import kotlinx.serialization.Serializable + +fun NavController.navigateWithdraw( + navOptions: NavOptions +) { + navigate(Withdraw, navOptions) +} + +fun NavGraphBuilder.withdrawNavGraph( + navigateToBack: () -> Unit, + navigateToOauth: () -> Unit, + paddingValues: PaddingValues +) { + composable { backStackEntry -> + val viewModel: WithdrawViewModel = hiltViewModel(backStackEntry) + WithdrawRoute( + paddingValues = paddingValues, + navigateToBack = navigateToBack, + navigateToOauth = navigateToOauth, + viewModel = viewModel + ) + } +} + +@Serializable +data object Withdraw : Route diff --git a/feature/mypage/src/main/res/values/strings.xml b/feature/mypage/src/main/res/values/strings.xml index 7725f99c..126a6477 100644 --- a/feature/mypage/src/main/res/values/strings.xml +++ b/feature/mypage/src/main/res/values/strings.xml @@ -2,7 +2,10 @@ 계정 설정 고객센터 + 내가 등록한 장소 + 전체보기 로그아웃 + 탈퇴하기 프로필 수정 기본 프로필 @@ -15,6 +18,10 @@ 취소 로그아웃하시겠습니까? - 취소 + 취소 로그아웃 + + 탈퇴하시겠습니까? + 탈퇴 + \ No newline at end of file diff --git a/local/mypage/.gitignore b/local/mypage/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/local/mypage/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/local/mypage/build.gradle.kts b/local/mypage/build.gradle.kts new file mode 100644 index 00000000..6318d78b --- /dev/null +++ b/local/mypage/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + alias(libs.plugins.solply.local) +} + +android { + namespace = "com.teamsolply.solply.mypage" +} + +dependencies { + implementation(projects.core.model) + implementation(projects.core.datastore) + implementation(projects.data.mypage) + implementation(libs.bundles.datastore) +} diff --git a/local/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageLocalDataSourceImpl.kt b/local/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageLocalDataSourceImpl.kt new file mode 100644 index 00000000..2ee7c1cc --- /dev/null +++ b/local/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageLocalDataSourceImpl.kt @@ -0,0 +1,17 @@ +package com.teamsolply.solply.mypage.datasource + +import androidx.datastore.core.DataStore +import com.teamsolply.solply.datastore.SolplyTokenData +import javax.inject.Inject + +class MypageLocalDataSourceImpl @Inject constructor( + private val mypageLocalDataSource: DataStore +) : MypageLocalDataSource { + override suspend fun saveAutoSignIn(autoSignIn: Boolean) { + mypageLocalDataSource.updateData { old -> + old.copy( + autoSignIn = autoSignIn + ) + } + } +} diff --git a/local/mypage/src/main/java/com/teamsolply/solply/mypage/di/MypageLocalDataModule.kt b/local/mypage/src/main/java/com/teamsolply/solply/mypage/di/MypageLocalDataModule.kt new file mode 100644 index 00000000..02c880b0 --- /dev/null +++ b/local/mypage/src/main/java/com/teamsolply/solply/mypage/di/MypageLocalDataModule.kt @@ -0,0 +1,17 @@ +package com.teamsolply.solply.mypage.di + +import com.teamsolply.solply.mypage.datasource.MypageLocalDataSource +import com.teamsolply.solply.mypage.datasource.MypageLocalDataSourceImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class MypageLocalDataModule { + @Binds + @Singleton + abstract fun bindsMypageLocalDataSource(mypageLocalDataSource: MypageLocalDataSourceImpl): MypageLocalDataSource +} diff --git a/remote/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSourceImpl.kt b/remote/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSourceImpl.kt index 7cec758e..6cf675b0 100644 --- a/remote/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSourceImpl.kt +++ b/remote/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSourceImpl.kt @@ -1,8 +1,9 @@ package com.teamsolply.solply.mypage.datasource -import android.util.Log +import com.teamsolply.solply.mypage.dto.request.DeleteUserRequestDto import com.teamsolply.solply.mypage.dto.response.GetPersonaListResponseDto import com.teamsolply.solply.mypage.dto.response.GetUserInfoResponseDto +import com.teamsolply.solply.mypage.dto.response.GetWithdrawListResponseDto import com.teamsolply.solply.mypage.dto.response.NicknameDuplicateResponseDto import com.teamsolply.solply.mypage.service.MypageService import javax.inject.Inject @@ -12,16 +13,24 @@ class MypageRemoteDataSourceImpl @Inject constructor( ) : MypageRemoteDataSource { override suspend fun getUserInfo(): GetUserInfoResponseDto { val user = mypageService.getUserInfo().data - Log.d("getUser ", user.nickname) return user } override suspend fun getPlaceList(townId: Long) = mypageService.getPlaceList(townId = townId).data + override suspend fun getReportPlaceList(userId: Long) = + mypageService.getMyReportPlaceList(userId = userId).data + override suspend fun getPersonaList(): GetPersonaListResponseDto = mypageService.getPersonaList().data override suspend fun checkNicknameDuplicate(nickname: String): NicknameDuplicateResponseDto = mypageService.checkNicknameDuplicate(nickname = nickname).data + + override suspend fun getWithdrawList(): GetWithdrawListResponseDto = + mypageService.getWithdrawList().data + + override suspend fun deleteUser(deleteUserRequestDto: DeleteUserRequestDto) = + mypageService.deleteUser(deleteUserRequestDto) } diff --git a/remote/mypage/src/main/java/com/teamsolply/solply/mypage/service/MypageService.kt b/remote/mypage/src/main/java/com/teamsolply/solply/mypage/service/MypageService.kt index ce98be63..036df224 100644 --- a/remote/mypage/src/main/java/com/teamsolply/solply/mypage/service/MypageService.kt +++ b/remote/mypage/src/main/java/com/teamsolply/solply/mypage/service/MypageService.kt @@ -1,11 +1,17 @@ package com.teamsolply.solply.mypage.service +import com.teamsolply.solply.mypage.dto.request.DeleteUserRequestDto import com.teamsolply.solply.mypage.dto.response.GetPersonaListResponseDto import com.teamsolply.solply.mypage.dto.response.GetUserInfoResponseDto +import com.teamsolply.solply.mypage.dto.response.GetWithdrawListResponseDto import com.teamsolply.solply.mypage.dto.response.NicknameDuplicateResponseDto import com.teamsolply.solply.mypage.dto.response.PlaceListResponseDto import com.teamsolply.solply.network.model.BaseResponse +import com.teamsolply.solply.network.model.NullableBaseResponse +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.HTTP +import retrofit2.http.Path import retrofit2.http.Query interface MypageService { @@ -18,6 +24,13 @@ interface MypageService { @Query("isBookmarkSearch") isBookmarkedSearch: Boolean = true ): BaseResponse + @GET("api/users/{userId}/places") + suspend fun getMyReportPlaceList( + @Path("userId") userId: Long, + @Query("page") page: Int = 0, + @Query("size") size: Int = 20 + ): BaseResponse + @GET("api/users/persona") suspend fun getPersonaList(): BaseResponse @@ -25,4 +38,12 @@ interface MypageService { suspend fun checkNicknameDuplicate( @Query("nickname") nickname: String ): BaseResponse + + @GET("api/users/withdraw/reasons") + suspend fun getWithdrawList(): BaseResponse + + @HTTP(method = "DELETE", path = "api/users/withdraw", hasBody = true) + suspend fun deleteUser( + @Body deleteUserRequestDto: DeleteUserRequestDto + ): NullableBaseResponse } diff --git a/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/service/OnBoardingService.kt b/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/service/OnBoardingService.kt index 5516a3b1..96487473 100644 --- a/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/service/OnBoardingService.kt +++ b/remote/onboarding/src/main/java/com/teamsolply/solply/onboarding/service/OnBoardingService.kt @@ -13,7 +13,7 @@ import retrofit2.http.Query interface OnBoardingService { - @GET("api/onboarding/questions/persona") + @GET("api/users/persona") suspend fun getPersonaQuestions(): BaseResponse @GET("api/towns") @@ -24,7 +24,7 @@ interface OnBoardingService { @Query("nickname") nickname: String ): BaseResponse - @PATCH("api/onboarding/users") + @PATCH("api/users/onboarding") suspend fun patchUserInfo( @Body patchUserInfoRequestDto: PatchUserInfoRequestDto ): BaseResponse diff --git a/settings.gradle.kts b/settings.gradle.kts index 9cbb5d51..edaae962 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -53,6 +53,7 @@ include(":data:mypage") include(":data:search") include(":local:main") include(":local:oauth") +include(":local:mypage") include(":local:place") include(":remote:oauth") include(":remote:onboarding")