diff --git a/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/modal/AcceptModal.kt b/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/modal/AcceptModal.kt index e4e3cc39..224e5098 100644 --- a/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/modal/AcceptModal.kt +++ b/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/modal/AcceptModal.kt @@ -18,6 +18,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -33,17 +36,20 @@ import com.umcspot.spot.ui.extension.screenWidthDp @Composable fun AcceptModal( + painter: Painter, + painterTint: Color, modalTitle : String, - modalDes : String, + modalDes : String?, okButtonText : String, + noButtonText: String?, modifier: Modifier = Modifier, onClick: () -> Unit = {}, - onDismiss:() -> Unit= {} + onDismiss:() -> Unit = {} ) { Card( modifier = modifier .width(screenWidthDp(326.dp)) - .height(screenHeightDp(249.dp)), + .wrapContentHeight(), shape = SpotShapes.Round, colors = CardDefaults.elevatedCardColors( containerColor = SpotTheme.colors.white @@ -70,10 +76,11 @@ fun AcceptModal( } Image( - painter = painterResource(R.drawable.ic_check), + painter = painter, contentDescription = null, modifier = Modifier - .size(screenWidthDp(33.dp)) + .size(screenWidthDp(33.dp)), + colorFilter = ColorFilter.tint(painterTint) ) Spacer(Modifier.height(screenHeightDp(7.dp))) @@ -88,25 +95,41 @@ fun AcceptModal( Spacer(Modifier.height(screenHeightDp(20.dp))) - Text( - text = modalDes, - style = SpotTheme.typography.regular_500, - textAlign = TextAlign.Center - ) + if(modalDes != null) { + Text( + text = modalDes, + style = SpotTheme.typography.regular_500, + textAlign = TextAlign.Center + ) - Spacer(Modifier.height(screenHeightDp(20.dp))) + Spacer(Modifier.height(screenHeightDp(20.dp))) + } - TextButton( - modifier = Modifier - .width(screenWidthDp(156.dp)) - .height(screenHeightDp(39.dp)), - text = okButtonText, - style = SpotTheme.typography.h5, - onClick = onClick, - shape = SpotShapes.Soft, - state = TextButtonState.B500State, - ) + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + TextButton( + modifier = Modifier + .width(screenWidthDp(141.dp)) + .height(screenHeightDp(39.dp)), + text = okButtonText, + style = SpotTheme.typography.h5, + onClick = onClick, + shape = SpotShapes.Soft, + state = TextButtonState.B500State, + ) + if(noButtonText != null) { + TextButton( + modifier = Modifier + .width(screenWidthDp(141.dp)) + .height(screenHeightDp(39.dp)), + text = noButtonText, + style = SpotTheme.typography.h5, + onClick = onDismiss, + shape = SpotShapes.Soft, + state = TextButtonState.G500State, + ) + } + } } } } @@ -114,20 +137,26 @@ fun AcceptModal( @Composable fun AcceptDialog( visible: Boolean, + painter: Painter = painterResource(R.drawable.ic_check), + painterTint: Color = Color.Unspecified, modalTitle : String, - modalDes : String, + modalDes : String?, okButtonText : String, + noButtonText: String?, onClick: () -> Unit, onDismiss: () -> Unit, ) { if (!visible) return Dialog(onDismissRequest = onDismiss) { AcceptModal( + painter = painter, + painterTint = painterTint, modalTitle = modalTitle, modalDes = modalDes, okButtonText = okButtonText, + noButtonText = noButtonText, onClick = onClick, - onDismiss = onDismiss + onDismiss = onDismiss, ) } } @@ -140,6 +169,7 @@ private fun AcceptDialog_Preview() { visible = true, modalTitle = "스터디원 신고 완료", modalDes = "스터디원 신고가 완료되었어요.\n쾌적한 서비스 이용을 위해 항상 노력하겠습니다.", + noButtonText = "취소", okButtonText = "확인", onClick = {}, onDismiss = {} diff --git a/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/modal/RejectModal.kt b/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/modal/RejectModal.kt index bdb56ba1..58214489 100644 --- a/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/modal/RejectModal.kt +++ b/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/modal/RejectModal.kt @@ -18,6 +18,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -27,6 +30,7 @@ import com.umcspot.spot.designsystem.R import com.umcspot.spot.designsystem.component.button.TextButton import com.umcspot.spot.designsystem.component.button.TextButtonState import com.umcspot.spot.designsystem.shapes.SpotShapes +import com.umcspot.spot.designsystem.theme.R500 import com.umcspot.spot.designsystem.theme.SpotTheme import com.umcspot.spot.ui.extension.screenHeightDp import com.umcspot.spot.ui.extension.screenWidthDp @@ -34,10 +38,12 @@ import com.umcspot.spot.ui.extension.screenWidthDp @Composable fun RejectModal( + painter : Painter, + painterTint : Color, modalTitle : String, - modalDes : String, + modalDes : String?, okButtonText : String, - noButtonText : String, + noButtonText : String?, modifier: Modifier = Modifier, onClick: () -> Unit = {}, onCancel:() -> Unit= {}, @@ -46,7 +52,7 @@ fun RejectModal( Card( modifier = modifier .width(screenWidthDp(326.dp)) - .height(screenHeightDp(227.dp)), + .wrapContentHeight(), shape = SpotShapes.Round, colors = CardDefaults.elevatedCardColors( containerColor = SpotTheme.colors.white @@ -73,10 +79,11 @@ fun RejectModal( } Image( - painter = painterResource(R.drawable.emoji_sad), + painter = painter, contentDescription = null, modifier = Modifier - .size(screenWidthDp(33.dp)) + .size(screenWidthDp(33.dp)), + colorFilter = ColorFilter.tint(painterTint) ) Spacer(Modifier.height(screenHeightDp(7.dp))) @@ -90,13 +97,15 @@ fun RejectModal( Spacer(Modifier.height(screenHeightDp(20.dp))) - Text( - text = modalDes, - style = SpotTheme.typography.regular_500, - textAlign = TextAlign.Center - ) + if(modalDes != null) { + Text( + text = modalDes, + style = SpotTheme.typography.regular_500, + textAlign = TextAlign.Center + ) - Spacer(Modifier.height(screenHeightDp(20.dp))) + Spacer(Modifier.height(screenHeightDp(20.dp))) + } Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { TextButton( @@ -110,16 +119,18 @@ fun RejectModal( state = TextButtonState.R500State, ) - TextButton( - modifier = Modifier - .width(screenWidthDp(141.dp)) - .height(screenHeightDp(39.dp)), - text = noButtonText, - style = SpotTheme.typography.h5, - onClick = onCancel, - shape = SpotShapes.Soft, - state = TextButtonState.G500State, - ) + if(noButtonText != null) { + TextButton( + modifier = Modifier + .width(screenWidthDp(141.dp)) + .height(screenHeightDp(39.dp)), + text = noButtonText, + style = SpotTheme.typography.h5, + onClick = onCancel, + shape = SpotShapes.Soft, + state = TextButtonState.G500State, + ) + } } } } @@ -128,17 +139,21 @@ fun RejectModal( @Composable fun RejectDialog( visible: Boolean, + painter : Painter = painterResource(R.drawable.emoji_sad), + painterTint : Color = Color.Unspecified, modalTitle : String, - modalDes : String, + modalDes : String?, okButtonText : String, - noButtonText : String, - onDismiss: () -> Unit, + noButtonText : String?, + onDismiss: () -> Unit = {}, onClick: () -> Unit, - onCancel: () -> Unit + onCancel: () -> Unit = {} ) { if (!visible) return Dialog(onDismissRequest = onDismiss) { RejectModal( + painter = painter, + painterTint = painterTint, modalTitle = modalTitle, modalDes = modalDes, okButtonText = okButtonText, @@ -156,6 +171,8 @@ private fun RejectDialog_Preview() { SpotTheme { RejectDialog( visible = true, + painter = painterResource(R.drawable.emoji_sad), + painterTint = SpotTheme.colors.R500, modalTitle = "나가시겠어요?", modalDes = "지금 나가면, 쓰던 글은 저장되지 않아요.", okButtonText = "네", diff --git a/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/StudyItem.kt b/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/StudyItem.kt index 8705c5d9..af59e3ba 100644 --- a/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/StudyItem.kt +++ b/core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/StudyItem.kt @@ -206,7 +206,8 @@ private fun StudyListItemPreview() { isLiked = false, hitCount = 1200, profileImageUrl = ImageRef.Name("spot_logo"), - isOwner = false + isOwner = false, + isAlone = false ), modifier = Modifier.padding(10.dp), onClick = {}, diff --git a/core/designsystem/src/main/res/drawable/error.xml b/core/designsystem/src/main/res/drawable/error.xml new file mode 100644 index 00000000..19080513 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/error.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/model/src/main/java/com/umcspot/spot/model/Global.kt b/core/model/src/main/java/com/umcspot/spot/model/Global.kt index 7c65eb78..a032f4a1 100644 --- a/core/model/src/main/java/com/umcspot/spot/model/Global.kt +++ b/core/model/src/main/java/com/umcspot/spot/model/Global.kt @@ -63,8 +63,8 @@ enum class StudyTheme( OTHER("기타"); companion object { - fun from(value: String): StudyTheme? = - values().firstOrNull { it.name == value } + fun from(value: String): StudyTheme = + StudyTheme.entries.first { it.name == value } } } diff --git a/data/study/src/main/java/com/umcspot/spot/study/dto/response/StudyResponseDto.kt b/data/study/src/main/java/com/umcspot/spot/study/dto/response/StudyResponseDto.kt index 0cba9d94..5acc50b8 100644 --- a/data/study/src/main/java/com/umcspot/spot/study/dto/response/StudyResponseDto.kt +++ b/data/study/src/main/java/com/umcspot/spot/study/dto/response/StudyResponseDto.kt @@ -45,6 +45,9 @@ data class Study ( @SerialName("isOwner") val isOwner: Boolean, + @SerialName("isAlone") + val isAlone: Boolean, + @SerialName("hitCount") val hitCount: Int = 0, diff --git a/data/study/src/main/java/com/umcspot/spot/study/mapper/StudyMapper.kt b/data/study/src/main/java/com/umcspot/spot/study/mapper/StudyMapper.kt index 8d05342e..c76f615d 100644 --- a/data/study/src/main/java/com/umcspot/spot/study/mapper/StudyMapper.kt +++ b/data/study/src/main/java/com/umcspot/spot/study/mapper/StudyMapper.kt @@ -29,6 +29,7 @@ fun Study.toDomain() : StudyResult = likeCount = this.likeCount, isLiked = this.isLiked, isOwner = this.isOwner, + isAlone = this.isAlone, hitCount = this.hitCount, profileImageUrl = this.profileImageUrl.toImageRef() ) diff --git a/data/user/src/main/java/com/umcspot/spot/user/datasource/UserDataSource.kt b/data/user/src/main/java/com/umcspot/spot/user/datasource/UserDataSource.kt index 512286d7..dc3370b8 100644 --- a/data/user/src/main/java/com/umcspot/spot/user/datasource/UserDataSource.kt +++ b/data/user/src/main/java/com/umcspot/spot/user/datasource/UserDataSource.kt @@ -18,4 +18,6 @@ interface UserDataSource { suspend fun getMyPageInfo() : BaseResponse suspend fun getUserPreferredCategory() : BaseResponse + + suspend fun leaveSpot() : NullResultResponse } \ No newline at end of file diff --git a/data/user/src/main/java/com/umcspot/spot/user/datasourceimpl/UserDataSourceImpl.kt b/data/user/src/main/java/com/umcspot/spot/user/datasourceimpl/UserDataSourceImpl.kt index 5f3bec5d..0bb7b78c 100644 --- a/data/user/src/main/java/com/umcspot/spot/user/datasourceimpl/UserDataSourceImpl.kt +++ b/data/user/src/main/java/com/umcspot/spot/user/datasourceimpl/UserDataSourceImpl.kt @@ -51,4 +51,7 @@ class UserDataSourceImpl @Inject constructor( override suspend fun getUserPreferredCategory(): BaseResponse = userService.getUserPreferredCategory() + + override suspend fun leaveSpot(): NullResultResponse = + userService.leaveSpot() } \ No newline at end of file diff --git a/data/user/src/main/java/com/umcspot/spot/user/repositoryimpl/UserRepositoryImpl.kt b/data/user/src/main/java/com/umcspot/spot/user/repositoryimpl/UserRepositoryImpl.kt index dbb2c9c3..5aae05f6 100644 --- a/data/user/src/main/java/com/umcspot/spot/user/repositoryimpl/UserRepositoryImpl.kt +++ b/data/user/src/main/java/com/umcspot/spot/user/repositoryimpl/UserRepositoryImpl.kt @@ -3,7 +3,6 @@ package com.umcspot.spot.user.repositoryimpl import android.content.Context import android.util.Log import com.umcspot.spot.common.location.LocationStore -import com.umcspot.spot.common.location.mapRegionCodesToFullNames import com.umcspot.spot.model.StudyTheme import com.umcspot.spot.user.datasource.UserDataSource import com.umcspot.spot.user.mapper.toDomain @@ -18,7 +17,7 @@ import javax.inject.Inject class UserRepositoryImpl @Inject constructor( private val userDataSource: UserDataSource, - @ApplicationContext private val appContext: Context, // @ApplicationContext 로 주입 추천 + @ApplicationContext private val appContext: Context, ) : UserRepository { override suspend fun getUserName(): Result = runCatching { @@ -87,4 +86,11 @@ class UserRepositoryImpl @Inject constructor( }.onFailure { Log.e("UserRepository", "getUserPreferredCategory failed", it) } + + override suspend fun leaveSpot() : Result = + runCatching { + userDataSource.leaveSpot().code + }.onFailure { + Log.e("UserRepository", "leaveSpot failed", it) + } } \ No newline at end of file diff --git a/data/user/src/main/java/com/umcspot/spot/user/service/UserService.kt b/data/user/src/main/java/com/umcspot/spot/user/service/UserService.kt index a2f90d61..e79d5a33 100644 --- a/data/user/src/main/java/com/umcspot/spot/user/service/UserService.kt +++ b/data/user/src/main/java/com/umcspot/spot/user/service/UserService.kt @@ -10,6 +10,7 @@ import com.umcspot.spot.user.dto.response.UserPreferredCategoryResponseDto import com.umcspot.spot.user.dto.response.UserPreferredRegionResponseDto import com.umcspot.spot.user.dto.response.UserResponseDto import retrofit2.http.Body +import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.POST @@ -44,4 +45,8 @@ interface UserService { @GET("/api/members/prefer-categories") suspend fun getUserPreferredCategory( ): BaseResponse + + @DELETE("/api/members/me") + suspend fun leaveSpot( + ): NullResultResponse } \ No newline at end of file diff --git a/domain/study/src/main/java/com/umcspot/spot/study/model/StudyResult.kt b/domain/study/src/main/java/com/umcspot/spot/study/model/StudyResult.kt index 926b0039..8b9248c8 100644 --- a/domain/study/src/main/java/com/umcspot/spot/study/model/StudyResult.kt +++ b/domain/study/src/main/java/com/umcspot/spot/study/model/StudyResult.kt @@ -28,6 +28,7 @@ data class StudyResult( val likeCount: Int = 0, val isLiked : Boolean, val isOwner : Boolean, + val isAlone : Boolean, val hitCount: Int = 0, val profileImageUrl: ImageRef ) { @@ -53,6 +54,7 @@ data class StudyResult( likeCount = 10 + index * 2, isLiked = index%2 == 0, isOwner = index%2 == 1, + isAlone = index%2 == 1, hitCount = 150 + index * 20, profileImageUrl = ImageRef.Name("ic_study_default") ) diff --git a/domain/user/src/main/java/com/umcspot/spot/user/model/UserPreferredCategoryResult.kt b/domain/user/src/main/java/com/umcspot/spot/user/model/UserPreferredCategoryResult.kt index 06316941..78adc3a2 100644 --- a/domain/user/src/main/java/com/umcspot/spot/user/model/UserPreferredCategoryResult.kt +++ b/domain/user/src/main/java/com/umcspot/spot/user/model/UserPreferredCategoryResult.kt @@ -3,6 +3,6 @@ package com.umcspot.spot.user.model import com.umcspot.spot.model.StudyTheme data class UserPreferredCategoryResult( - val categories : List, + val categories : List, val totalCount: Int ) \ No newline at end of file diff --git a/domain/user/src/main/java/com/umcspot/spot/user/repository/UserRepository.kt b/domain/user/src/main/java/com/umcspot/spot/user/repository/UserRepository.kt index e5553565..1bce239b 100644 --- a/domain/user/src/main/java/com/umcspot/spot/user/repository/UserRepository.kt +++ b/domain/user/src/main/java/com/umcspot/spot/user/repository/UserRepository.kt @@ -17,4 +17,6 @@ interface UserRepository { suspend fun getMyPageInfo() : Result suspend fun getUserPreferredCategory() : Result + + suspend fun leaveSpot() : Result } \ No newline at end of file diff --git a/feature/board/src/main/java/com/umcspot/spot/feature/board/post/content/PostContentScreen.kt b/feature/board/src/main/java/com/umcspot/spot/feature/board/post/content/PostContentScreen.kt index 93ff71d8..dc791c37 100644 --- a/feature/board/src/main/java/com/umcspot/spot/feature/board/post/content/PostContentScreen.kt +++ b/feature/board/src/main/java/com/umcspot/spot/feature/board/post/content/PostContentScreen.kt @@ -259,6 +259,7 @@ fun PostContentScreen( modalTitle = "신고 완료", modalDes = "게시글 신고가 완료되었어요.\n쾌적한 서비스 이용을 위해 항상 노력하겠습니다.", okButtonText = "확인", + noButtonText = null, onDismiss = { showAcceptRequestDialog = false }, onClick = { showAcceptRequestDialog = false } ) diff --git a/feature/main/src/main/java/com/umcspot/spot/main/MainActivity.kt b/feature/main/src/main/java/com/umcspot/spot/main/MainActivity.kt index 817018a8..ee3fbcd1 100644 --- a/feature/main/src/main/java/com/umcspot/spot/main/MainActivity.kt +++ b/feature/main/src/main/java/com/umcspot/spot/main/MainActivity.kt @@ -23,7 +23,7 @@ class MainActivity : ComponentActivity() { setContent { SpotTheme { - MainScreen() + MainScreen( ) } } } diff --git a/feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt b/feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt index 520e64eb..c7ff125d 100644 --- a/feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt +++ b/feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt @@ -23,6 +23,11 @@ import com.umcspot.spot.feature.board.post.posting.navigation.postingGraph import com.umcspot.spot.home.navigation.homeGraph import com.umcspot.spot.jjim.navigation.jjimGraph import com.umcspot.spot.model.QuickMenuType +import com.umcspot.spot.mypage.cancelMemberShip.navigation.cancelMemberShipGraph +import com.umcspot.spot.mypage.cancelMemberShip.navigation.navigateToCancelMembership +import com.umcspot.spot.mypage.editInterestStudy.navigation.interestStudyGraph +import com.umcspot.spot.mypage.editInterestStudy.navigation.navigateToEditInterestStudy +import com.umcspot.spot.mypage.main.navigation.MyPage import com.umcspot.spot.mypage.main.navigation.mypageGraph import com.umcspot.spot.mypage.participating.navigation.navigateToParticipatingStudy import com.umcspot.spot.mypage.participating.navigation.participatingGraph @@ -30,6 +35,7 @@ import com.umcspot.spot.mypage.recruiting.navigation.myRecruitingStudyGraph import com.umcspot.spot.mypage.recruiting.navigation.navigateToMyRecruitingStudy import com.umcspot.spot.mypage.waiting.navigation.navigateToWaitingStudy import com.umcspot.spot.mypage.waiting.navigation.waitingStudyGraph +import com.umcspot.spot.signup.navigation.navigateToLanding import com.umcspot.spot.signup.navigation.signupGraph import com.umcspot.spot.study.detail.navigation.navigateToStudyDetail import com.umcspot.spot.study.detail.navigation.studyDetailGraph @@ -51,7 +57,7 @@ fun MainNavHost( modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(0.dp), onRegisterScrollToTop: ((() -> Unit)?) -> Unit, - onBackRequest : () -> Unit + onBackRequest : () -> Unit, ) { val clearStackNavOptions = navOptions { popUpTo(0) { inclusive = true } @@ -113,13 +119,22 @@ fun MainNavHost( onItemClick = { navigator.navigateToStudyDetail(it) } ) + + /** MyPage **/ mypageGraph( contentPadding = contentPadding, onParticipatingClick = { navigator.navController.navigateToParticipatingStudy() }, onMyRecruitingClick = { navigator.navController.navigateToMyRecruitingStudy() }, onMyAppliedClick = { navigator.navController.navigateToWaitingStudy() }, - onEditInterestClick = { /*navigator.navigateToCheckList*/ }, - onEditInterestLocationClick = { } + onEditInterestClick = { navigator.navController.navigateToEditInterestStudy() }, + onEditInterestLocationClick = { }, + onCancelMemberShipClick = { navigator.navController.navigateToCancelMembership() }, + onLogoutClick = { + // 1) 로그아웃 처리(데이터 삭제) 트리거 + + // 2) Landing으로 이동하면서 스택 클리어 + navigator.navController.navigateToLanding(clearStackNavOptions) + } ) participatingGraph( @@ -144,6 +159,32 @@ fun MainNavHost( moveToRecruitingStudy = { navigator.navigateToRecruitingStudy() }, ) + interestStudyGraph( + contentPadding = contentPadding, + moveToMyInterestStudy = { navigator.navController.navigateToPreferCategoryStudy( + navOptions { + popUpTo { inclusive = false } + launchSingleTop = true + restoreState = false + } + ) }, + moveToMyPage = { navigator.navController.popBackStack() } + ) + + cancelMemberShipGraph( + contentPadding = contentPadding, + successCancelMemberShip = { navigator.navController.navigateToLanding(clearStackNavOptions) }, + moveToParticipatingStudy = { navigator.navController.navigateToParticipatingStudy( + navOptions { + popUpTo { inclusive = false } + launchSingleTop = true + restoreState = false + } + ) } + ) + /************/ + + recruitingStudyGraph( contentPadding = contentPadding, onRegisterScrollToTop = onRegisterScrollToTop, diff --git a/feature/main/src/main/java/com/umcspot/spot/main/MainNavigator.kt b/feature/main/src/main/java/com/umcspot/spot/main/MainNavigator.kt index 0153a8ed..6df57ed0 100644 --- a/feature/main/src/main/java/com/umcspot/spot/main/MainNavigator.kt +++ b/feature/main/src/main/java/com/umcspot/spot/main/MainNavigator.kt @@ -22,6 +22,8 @@ import com.umcspot.spot.home.navigation.Home import com.umcspot.spot.home.navigation.navigateToHome import com.umcspot.spot.jjim.navigation.JJim import com.umcspot.spot.jjim.navigation.navigateToJJim +import com.umcspot.spot.mypage.cancelMemberShip.navigation.CancelMemberShip +import com.umcspot.spot.mypage.editInterestStudy.navigation.EditInterest import com.umcspot.spot.mypage.main.navigation.MyPage import com.umcspot.spot.mypage.main.navigation.navigateToMyPage import com.umcspot.spot.mypage.participating.navigation.ParticipatingStudy @@ -103,7 +105,7 @@ class MainNavigator( @Composable fun showBackTopBar(): Boolean = inAnyGraph(Alert::class, RecruitingFilter::class, PreferLocationFilter::class, PreferCategoryFilter::class, SignUp::class, CheckList::class, Posting::class, BoardList::class, JJim::class, MyPage::class, - ParticipatingStudy::class, MyRecruitingStudy::class, WaitingStudy::class + ParticipatingStudy::class, MyRecruitingStudy::class, WaitingStudy::class, EditInterest::class, CancelMemberShip::class ) || inAnyGraphRoutes(POST_CONTENT_ROUTE) @Composable diff --git a/feature/main/src/main/java/com/umcspot/spot/main/MainScreen.kt b/feature/main/src/main/java/com/umcspot/spot/main/MainScreen.kt index 055fd6a0..55e1846e 100644 --- a/feature/main/src/main/java/com/umcspot/spot/main/MainScreen.kt +++ b/feature/main/src/main/java/com/umcspot/spot/main/MainScreen.kt @@ -35,6 +35,9 @@ import com.umcspot.spot.feature.board.post.posting.navigation.navigateToPostingN import com.umcspot.spot.home.navigation.Home import com.umcspot.spot.jjim.navigation.JJim import com.umcspot.spot.main.component.MainBottomBar +import com.umcspot.spot.mypage.cancelMemberShip.CancelMemberShipScreen +import com.umcspot.spot.mypage.cancelMemberShip.navigation.CancelMemberShip +import com.umcspot.spot.mypage.editInterestStudy.navigation.EditInterest import com.umcspot.spot.mypage.main.navigation.MyPage import com.umcspot.spot.mypage.participating.navigation.ParticipatingStudy import com.umcspot.spot.mypage.recruiting.navigation.MyRecruitingStudy @@ -50,7 +53,7 @@ import kotlinx.collections.immutable.toImmutableList @Composable fun MainScreen( - navigator: MainNavigator = rememberMainNavigator() + navigator: MainNavigator = rememberMainNavigator(), ) { val navController = navigator.navController val backStackEntry by navController.currentBackStackEntryAsState() @@ -81,6 +84,8 @@ fun MainScreen( dest?.hasRoute(ParticipatingStudy::class) == true -> "참여 중인 스터디" dest?.hasRoute(MyRecruitingStudy::class) == true -> "모집 중인 스터디" dest?.hasRoute(WaitingStudy::class) == true -> "대기 중인 스터디" + dest?.hasRoute(EditInterest::class) == true -> "관심 분야" + dest?.hasRoute(CancelMemberShip::class) == true -> "회원 탈퇴" dest?.routeMatches(POST_CONTENT_ROUTE) == true -> "스터디 파트너들의 이야기" else -> "" } @@ -146,7 +151,7 @@ fun MainScreen( .consumeWindowInsets(innerPadding), contentPadding = innerPadding, onRegisterScrollToTop = { handler -> scrollToTop = handler }, - onBackRequest = { showBackRequestDialog = true } + onBackRequest = { showBackRequestDialog = true }, ) } diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/cancelMemberShip/CancelMemberShipScreen.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/cancelMemberShip/CancelMemberShipScreen.kt new file mode 100644 index 00000000..dfe41695 --- /dev/null +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/cancelMemberShip/CancelMemberShipScreen.kt @@ -0,0 +1,196 @@ +package com.umcspot.spot.mypage.cancelMemberShip + +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.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +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.umcspot.spot.designsystem.R +import com.umcspot.spot.designsystem.component.button.TextButton +import com.umcspot.spot.designsystem.component.button.TextButtonState +import com.umcspot.spot.designsystem.component.modal.RejectDialog +import com.umcspot.spot.designsystem.shapes.SpotShapes +import com.umcspot.spot.designsystem.theme.G300 +import com.umcspot.spot.designsystem.theme.G400 +import com.umcspot.spot.designsystem.theme.R500 +import com.umcspot.spot.designsystem.theme.SpotTheme +import com.umcspot.spot.ui.extension.screenHeightDp +import com.umcspot.spot.ui.extension.screenWidthDp + +@Composable +fun CancelMemberShipScreen( + contentPadding : PaddingValues, + successCancelMemberShip: () -> Unit, + moveToParticipatingStudy: () -> Unit, + viewmodel : CancelMemberShipViewModel = hiltViewModel() +) { + var showDialog by remember { mutableStateOf(false) } + var showFailDialog by remember { mutableStateOf(false) } + var showSuccessDialog by remember { mutableStateOf(false) } + val status by viewmodel.leaveStatus.collectAsStateWithLifecycle() + + LaunchedEffect(status) { + when(status) { + "MEMBER4006" -> { showFailDialog = true } + "MEMBER200" -> { showSuccessDialog = true } + } + } + + Column( + modifier = Modifier + .background(SpotTheme.colors.white) + .padding( + top = contentPadding.calculateTopPadding(), + bottom = contentPadding.calculateBottomPadding() + ) + .padding(horizontal = screenWidthDp(17.dp), vertical = screenHeightDp(18.dp)), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top + ) { + Text(text = "SPOT 탈퇴 전 확인하세요.", style = SpotTheme.typography.h3) + + Spacer(modifier = Modifier.height(screenHeightDp(10.dp))) + + Text(text = "탈퇴하시면 아래 정보들은 모두 파기되며 모든 데이터들은", style = SpotTheme.typography.regular_500) + + Spacer(modifier = Modifier.height(screenHeightDp(4.dp))) + + Row { + Text(text = "복구가", style = SpotTheme.typography.regular_500) + + Spacer(modifier = Modifier.width(screenWidthDp(8.dp))) + + Text( + text = "불가능", + style = SpotTheme.typography.regular_500, + color = SpotTheme.colors.R500 + ) + Text(text = "합니다.", style = SpotTheme.typography.regular_500) + } + + Spacer(modifier = Modifier.height(screenHeightDp(40.dp))) + + HorizontalDivider( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = screenWidthDp(4.dp)), + color = SpotTheme.colors.G300, + thickness = 1.dp, + ) + + Spacer(modifier = Modifier.height(screenHeightDp(40.dp))) + + Text(text = "회원 정보", style = SpotTheme.typography.large_500) + + Spacer(modifier = Modifier.height(screenHeightDp(7.dp))) + + Text(text = "로그인 이메일, 성명, 생년월일 정보가 모두 삭제됩니다.", style = SpotTheme.typography.regular_500) + + Spacer(modifier = Modifier.height(screenHeightDp(40.dp))) + + Text(text = "스터디 정보", style = SpotTheme.typography.large_500) + + Spacer(modifier = Modifier.height(screenHeightDp(7.dp))) + + Text(text = "진행 중 스터디, 모집 중 스터디, 스터디 찜 정보, 모든 게시글, 댓글, 사진,", style = SpotTheme.typography.regular_500) + + Spacer(modifier = Modifier.height(screenHeightDp(4.dp))) + + Text(text = "개인 관심사/관심지역 정보 등이 모두 삭제됩니다.", style = SpotTheme.typography.regular_500) + + Spacer(modifier = Modifier.weight(1f)) + + TextButton( + modifier = Modifier + .width(screenWidthDp(326.dp)) + .height(screenHeightDp(47.dp)), + text = "탈퇴하기", + style = SpotTheme.typography.h3, + state = TextButtonState.R500State, + shape = SpotShapes.Soft, + onClick = { showDialog = true } + ) + + RejectDialog( + visible = showDialog, + painter = painterResource(R.drawable.cancel_membership), + painterTint = SpotTheme.colors.G400, + modalTitle = "정말 탈퇴할까요?", + modalDes = null, + okButtonText = "탈퇴", + noButtonText = "취소", + onDismiss = { showDialog = false }, + onClick = { + viewmodel.leaveSpot() + showDialog = false + }, + onCancel = { showDialog = false } + ) + + RejectDialog( + visible = showFailDialog, + painter = painterResource(R.drawable.error), + painterTint = SpotTheme.colors.R500, + modalTitle = "호스트로 운영중인 스터디가 있어요.", + modalDes = "호스트로 운영중인 스터디를 먼저 나가야\n스팟 서비스를 탈퇴할 수 있어요.", + okButtonText = "스터디 나가기", + noButtonText = "취소", + onDismiss = { showFailDialog = false }, + onClick = { + showFailDialog = false + moveToParticipatingStudy() + }, + onCancel = { showFailDialog = false } + ) + + RejectDialog( + visible = showSuccessDialog, + painter = painterResource(R.drawable.success_default), + painterTint = SpotTheme.colors.R500, + modalTitle = "탈퇴 처리 완료", + modalDes = null, + okButtonText = "확인", + noButtonText = null, + onDismiss = { + showSuccessDialog = false + successCancelMemberShip() + }, + onClick = { + showSuccessDialog = false + successCancelMemberShip() + }, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun CancelMemberShipScreenPreview() { + SpotTheme { + CancelMemberShipScreen( + contentPadding = PaddingValues(0.dp), + successCancelMemberShip = {}, + moveToParticipatingStudy = {} + ) + } +} \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/cancelMemberShip/CancelMemberShipViewModel.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/cancelMemberShip/CancelMemberShipViewModel.kt new file mode 100644 index 00000000..906cbfd3 --- /dev/null +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/cancelMemberShip/CancelMemberShipViewModel.kt @@ -0,0 +1,34 @@ +package com.umcspot.spot.mypage.cancelMemberShip + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.umcspot.spot.user.repository.UserRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class CancelMemberShipViewModel @Inject constructor( + private val userRepository: UserRepository +) : ViewModel() { + private val _leaveStatus = MutableStateFlow("") + val leaveStatus : StateFlow = _leaveStatus + + fun leaveSpot() { + Log.d("CancelMemberShipVM", "leaveSpot CALLED") + viewModelScope.launch { + userRepository.leaveSpot() + .onSuccess { status -> + Log.d("CancelMemberShipVM", "leaveSpot success status=$status") + _leaveStatus.value = status + } + .onFailure { e -> + Log.e("CancelMemberShipVM", "leaveSpot failed", e) + } + } + } +} + diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/cancelMemberShip/navigation/CancelMemberShipNavigation.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/cancelMemberShip/navigation/CancelMemberShipNavigation.kt new file mode 100644 index 00000000..e605e8b8 --- /dev/null +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/cancelMemberShip/navigation/CancelMemberShipNavigation.kt @@ -0,0 +1,31 @@ +package com.umcspot.spot.mypage.cancelMemberShip.navigation + +import androidx.compose.foundation.layout.PaddingValues +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.umcspot.spot.mypage.cancelMemberShip.CancelMemberShipScreen +import com.umcspot.spot.navigation.Route +import kotlinx.serialization.Serializable + +fun NavController.navigateToCancelMembership(navOptions: NavOptions? = null) { + navigate(CancelMemberShip, navOptions) +} + +fun NavGraphBuilder.cancelMemberShipGraph( + contentPadding : PaddingValues, + successCancelMemberShip: () -> Unit, + moveToParticipatingStudy: () -> Unit, +) { + composable { + CancelMemberShipScreen( + contentPadding = contentPadding, + successCancelMemberShip = successCancelMemberShip, + moveToParticipatingStudy = moveToParticipatingStudy + ) + } +} + +@Serializable +data object CancelMemberShip : Route \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/editInterestStudy/EditInterestStudyScreen.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/editInterestStudy/EditInterestStudyScreen.kt new file mode 100644 index 00000000..23fdcdbe --- /dev/null +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/editInterestStudy/EditInterestStudyScreen.kt @@ -0,0 +1,156 @@ +package com.umcspot.spot.mypage.editInterestStudy + +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.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +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.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.umcspot.spot.designsystem.component.button.TextButton +import com.umcspot.spot.designsystem.component.button.TextButtonState +import com.umcspot.spot.designsystem.component.modal.AcceptDialog +import com.umcspot.spot.designsystem.component.study.section.ActivityThemeSection +import com.umcspot.spot.designsystem.shapes.SpotShapes +import com.umcspot.spot.designsystem.theme.SpotTheme +import com.umcspot.spot.ui.extension.screenHeightDp +import com.umcspot.spot.ui.extension.screenWidthDp + +@Composable +fun EditInterestStudyScreen( + contentPadding : PaddingValues, + moveToMyInterestStudy: () -> Unit, + moveToMyPage: () -> Unit, + viewmodel : EditInterestStudyViewModel = hiltViewModel() +) { + var showDialog by remember { mutableStateOf(false) } + var editMode by remember { mutableStateOf(false) } + val selectedThemes by viewmodel.preferCategories.collectAsStateWithLifecycle() + var draftThemes by remember { mutableStateOf(selectedThemes) } + + val topPad = contentPadding.calculateTopPadding() + val bottomPad = contentPadding.calculateBottomPadding() + + LaunchedEffect(Unit) { + viewmodel.loadPreferCategories() + } + + LaunchedEffect(selectedThemes) { + if (!editMode) draftThemes = selectedThemes + } + + Column( + modifier = Modifier + .fillMaxSize() + .background(SpotTheme.colors.white) + .padding(top = topPad, bottom = bottomPad) + .padding(horizontal = screenWidthDp(17.dp), vertical = screenHeightDp(30.dp)) + ) { + Spacer(Modifier.height(screenHeightDp(68.dp))) + + Text( + text = "내가 원하는 스터디를 선택해주세요!", + style = SpotTheme.typography.h3, + color = SpotTheme.colors.black + ) + + Spacer(Modifier.height(screenHeightDp(40.dp))) + + Column( + modifier = Modifier + .weight(1f) + ) { + ActivityThemeSection( + selectedThemes = if (editMode) draftThemes else selectedThemes, + onSelect = { theme -> + if (!editMode) return@ActivityThemeSection + + draftThemes = + if (draftThemes.contains(theme)) draftThemes - theme else draftThemes + theme + }, + modifier = Modifier.fillMaxWidth(), + maxSelection = 10, + ) + } + + if(editMode) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + modifier = Modifier + .width(screenWidthDp(156.dp)) + .height(screenHeightDp(47.dp)), + text = "취소", + style = SpotTheme.typography.h3, + onClick = { + draftThemes = selectedThemes + editMode = false + }, + shape = SpotShapes.Soft, + state = TextButtonState.G500State, + ) + + TextButton( + modifier = Modifier + .width(screenWidthDp(156.dp)) + .height(screenHeightDp(47.dp)), + text = "완료", + style = SpotTheme.typography.h3, + onClick = { + viewmodel.updatePreferCategories(draftThemes) + showDialog = true + }, + shape = SpotShapes.Soft, + state = TextButtonState.B500State, + ) + } + } else { + TextButton( + modifier = Modifier + .width(screenWidthDp(326.dp)) + .height(screenHeightDp(47.dp)), + text = "수정", + style = SpotTheme.typography.h3, + onClick = { + draftThemes = selectedThemes + editMode = true + }, + shape = SpotShapes.Soft, + state = TextButtonState.B500State, + ) + } + + AcceptDialog( + visible = showDialog, + modalTitle = "", + modalDes = "수정이 완료되었어요.\n새로운 관심 분야에 맞는 스터디를 확인해보세요.", + okButtonText = "내 관심 스터디 보기", + noButtonText = null, + onClick = { + showDialog = false + moveToMyInterestStudy() + }, + onDismiss = { + showDialog = false + moveToMyPage() + } + ) + } +} \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/editInterestStudy/EditInterestStudyViewModel.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/editInterestStudy/EditInterestStudyViewModel.kt new file mode 100644 index 00000000..9726f818 --- /dev/null +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/editInterestStudy/EditInterestStudyViewModel.kt @@ -0,0 +1,47 @@ +package com.umcspot.spot.mypage.editInterestStudy + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.umcspot.spot.model.StudyTheme +import com.umcspot.spot.study.model.Study +import com.umcspot.spot.user.repository.UserRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class EditInterestStudyViewModel @Inject constructor( + private val userRepository: UserRepository +) : ViewModel() { + private val _preferCategories = MutableStateFlow>(emptyList()) + val preferCategories : StateFlow> = _preferCategories.asStateFlow() + + fun loadPreferCategories() { + viewModelScope.launch { + userRepository.getUserPreferredCategory() + .onSuccess { + _preferCategories.value = it.categories + } + .onFailure { + Log.e("EditInterestStudyViewModel", "loadPreferCategories: ${it.message}") + } + } + } + + fun updatePreferCategories(categories: List) { + viewModelScope.launch { + userRepository.setUserTheme(categories) + .onSuccess { + _preferCategories.value = categories + } + .onFailure { + Log.e("EditInterestStudyViewModel", "updatePreferCategories: ${it.message}") + } + } + } +} + diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/editInterestStudy/navigation/EditInterestStudyNavigation.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/editInterestStudy/navigation/EditInterestStudyNavigation.kt new file mode 100644 index 00000000..d236c75e --- /dev/null +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/editInterestStudy/navigation/EditInterestStudyNavigation.kt @@ -0,0 +1,31 @@ +package com.umcspot.spot.mypage.editInterestStudy.navigation + +import androidx.compose.foundation.layout.PaddingValues +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.umcspot.spot.mypage.editInterestStudy.EditInterestStudyScreen +import com.umcspot.spot.navigation.MainTabRoute +import kotlinx.serialization.Serializable + +fun NavController.navigateToEditInterestStudy(navOptions: NavOptions? = null) { + navigate(EditInterest, navOptions) +} + +fun NavGraphBuilder.interestStudyGraph( + contentPadding : PaddingValues, + moveToMyInterestStudy: () -> Unit, + moveToMyPage: () -> Unit +) { + composable { + EditInterestStudyScreen( + contentPadding = contentPadding, + moveToMyInterestStudy = moveToMyInterestStudy, + moveToMyPage = moveToMyPage + ) + } +} + +@Serializable +data object EditInterest : MainTabRoute \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt index 6c1ff461..4cebc772 100644 --- a/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt @@ -1,6 +1,5 @@ package com.umcspot.spot.mypage.main -import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -23,7 +22,9 @@ import androidx.compose.material3.VerticalDivider 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -40,6 +41,7 @@ import com.umcspot.spot.designsystem.component.ProfileImage import com.umcspot.spot.designsystem.component.SpotSpinner import com.umcspot.spot.designsystem.component.button.BlankButton import com.umcspot.spot.designsystem.component.button.ImageButtonState +import com.umcspot.spot.designsystem.component.modal.AcceptDialog import com.umcspot.spot.designsystem.shapes.ShapeBox import com.umcspot.spot.designsystem.shapes.SpotShapes import com.umcspot.spot.designsystem.theme.B100 @@ -63,11 +65,12 @@ fun MyPageScreen( onMyAppliedClick : () -> Unit, onEditInterestClick : () -> Unit, onEditInterestLocationClick : () -> Unit, + onCancelMemberShipClick: () -> Unit, + onLogoutClick:() -> Unit, viewmodel : MyPageViewModel = hiltViewModel() ) { val uiState by viewmodel.uiState.collectAsStateWithLifecycle() - LaunchedEffect(Unit) { viewmodel.load() } @@ -85,7 +88,9 @@ fun MyPageScreen( onMyRecruitingClick = onMyRecruitingClick, onMyAppliedClick = onMyAppliedClick, onEditInterestClick = onEditInterestClick, - onEditInterestLocationClick = onEditInterestLocationClick + onEditInterestLocationClick = onEditInterestLocationClick, + onCancelMemberShipClick = onCancelMemberShipClick, + onLogoutClick = onLogoutClick ) } @@ -101,282 +106,326 @@ fun MyPageScreenContent( onMyAppliedClick: () -> Unit, onEditInterestClick: () -> Unit, onEditInterestLocationClick: () -> Unit, + onCancelMemberShipClick: () -> Unit, + onLogoutClick: () -> Unit ) { - LazyColumn( - modifier = modifier - .padding(top = screenHeightDp(18.dp)) - .padding(horizontal = screenWidthDp(17.dp)), - contentPadding = PaddingValues(bottom = screenHeightDp(24.dp)) + var logout by remember { mutableStateOf(false) } + var successLogout by remember { mutableStateOf(false) } + + Box( + modifier = Modifier.fillMaxSize() ) { - item { - when (memberInfo) { - is UiState.Loading, is UiState.Empty, is UiState.Failure -> { - Box( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - contentAlignment = Alignment.Center - ) { - SpotSpinner() + LazyColumn( + modifier = modifier + .padding(top = screenHeightDp(18.dp)) + .padding(horizontal = screenWidthDp(17.dp)), + contentPadding = PaddingValues(bottom = screenHeightDp(24.dp)) + ) { + item { + when (memberInfo) { + is UiState.Loading, is UiState.Empty, is UiState.Failure -> { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + contentAlignment = Alignment.Center + ) { + SpotSpinner() + } + + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) } - Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) - } - - is UiState.Success -> { - val data = memberInfo.data + is UiState.Success -> { + val data = memberInfo.data - UserProfile( - nickName = data.nickname, - profileImageUrl = data.profileImageUrl - ) + UserProfile( + nickName = data.nickname, + profileImageUrl = data.profileImageUrl + ) - Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) + } } } - } - item { - when(memberInfo) { - is UiState.Loading, is UiState.Empty, is UiState.Failure -> { - Box( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - contentAlignment = Alignment.Center - ) { - SpotSpinner() + item { + when(memberInfo) { + is UiState.Loading, is UiState.Empty, is UiState.Failure -> { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + contentAlignment = Alignment.Center + ) { + SpotSpinner() + } + + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) } - Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) - } - - is UiState.Success -> { - val data = memberInfo.data + is UiState.Success -> { + val data = memberInfo.data - StudyInfoFrame( - participatingStudyCount = data.participateCount, - recruitingStudyCount = data.recruitingCount, - appliedStudyCount = data.appliedCount, - onParticipatingClick = onParticipatingClick, - onRecruitingClick = onMyRecruitingClick, - onAppliedClick = onMyAppliedClick - ) + StudyInfoFrame( + participatingStudyCount = data.participateCount, + recruitingStudyCount = data.recruitingCount, + appliedStudyCount = data.appliedCount, + onParticipatingClick = onParticipatingClick, + onRecruitingClick = onMyRecruitingClick, + onAppliedClick = onMyAppliedClick + ) - Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) + } } } - } - item { - when(preferCategory) { - is UiState.Loading, is UiState.Empty, is UiState.Failure -> { - Box( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - contentAlignment = Alignment.Center - ) { - SpotSpinner() - } - - Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) - } - is UiState.Success -> { - val data = preferCategory.data - - InterestedInfo( - title = "관심 분야", - icon = painterResource(id = R.drawable.search_prefer), - interest = data, - onClick = {} - ) - - Spacer(modifier = Modifier.height(screenHeightDp(7.dp))) + item { + when(preferCategory) { + is UiState.Loading, is UiState.Empty, is UiState.Failure -> { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + contentAlignment = Alignment.Center + ) { + SpotSpinner() + } - HorizontalDivider( - color = SpotTheme.colors.G300, - thickness = 1.dp, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = screenWidthDp(4.dp)) - ) + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) + } + is UiState.Success -> { + val data = preferCategory.data + + InterestedInfo( + title = "관심 분야", + icon = painterResource(id = R.drawable.search_prefer), + interest = data, + onClick = onEditInterestClick + ) + + Spacer(modifier = Modifier.height(screenHeightDp(7.dp))) + + HorizontalDivider( + color = SpotTheme.colors.G300, + thickness = 1.dp, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = screenWidthDp(4.dp)) + ) + } } } - } - item { - when(preferRegion) { - is UiState.Loading, is UiState.Empty, is UiState.Failure -> { - Box( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - contentAlignment = Alignment.Center - ) { - SpotSpinner() + item { + when(preferRegion) { + is UiState.Loading, is UiState.Empty, is UiState.Failure -> { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + contentAlignment = Alignment.Center + ) { + SpotSpinner() + } + + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) } - Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) - } + is UiState.Success -> { + val data = preferRegion.data - is UiState.Success -> { - val data = preferRegion.data + Spacer(modifier = Modifier.height(screenHeightDp(7.dp))) - Spacer(modifier = Modifier.height(screenHeightDp(7.dp))) - - InterestedInfo( - title = "관심 지역", - icon = painterResource(id = R.drawable.location_outline), - interest = data, - onClick = {} - ) + InterestedInfo( + title = "관심 지역", + icon = painterResource(id = R.drawable.location_outline), + interest = data, + onClick = onEditInterestLocationClick + ) - Spacer(modifier = Modifier.height(screenHeightDp(7.dp))) + Spacer(modifier = Modifier.height(screenHeightDp(7.dp))) - HorizontalDivider( - color = SpotTheme.colors.G300, - thickness = 1.dp, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = screenWidthDp(4.dp)) - ) + HorizontalDivider( + color = SpotTheme.colors.G300, + thickness = 1.dp, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = screenWidthDp(4.dp)) + ) + } } } - } - item { - when(memberInfo) { - is UiState.Loading, is UiState.Empty, is UiState.Failure -> { - Box( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - contentAlignment = Alignment.Center - ) { - SpotSpinner() - } + item { + when(memberInfo) { + is UiState.Loading, is UiState.Empty, is UiState.Failure -> { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + contentAlignment = Alignment.Center + ) { + SpotSpinner() + } - Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) - } + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) + } - is UiState.Success -> { - val data = memberInfo.data + is UiState.Success -> { + val data = memberInfo.data - Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) + Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) - LoginType(type = data.loginType) + LoginType(type = data.loginType) - EmailInfo(email = data.email) + EmailInfo(email = data.email) - Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) + Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) - HorizontalDivider( - color = SpotTheme.colors.G300, - thickness = 1.dp, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = screenWidthDp(4.dp)) - ) + HorizontalDivider( + color = SpotTheme.colors.G300, + thickness = 1.dp, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = screenWidthDp(4.dp)) + ) + } } } - } - item { - Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) + item { + Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) - TermSection( - onCommunityRuleClick = {}, - onRestrictionHistoryClick = {}, - onPrivacyPolicyClick = {}, - onTermsOfServiceClick = {} - ) + TermSection( + onCommunityRuleClick = {}, + onRestrictionHistoryClick = {}, + onPrivacyPolicyClick = {}, + onTermsOfServiceClick = {} + ) - Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) - HorizontalDivider( - color = SpotTheme.colors.G300, - thickness = 1.dp, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = screenWidthDp(4.dp)) - ) - } + Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) + HorizontalDivider( + color = SpotTheme.colors.G300, + thickness = 1.dp, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = screenWidthDp(4.dp)) + ) + } - item { - when(appVersion) { - is UiState.Loading, is UiState.Empty, is UiState.Failure -> { - Box( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - contentAlignment = Alignment.Center - ) { - SpotSpinner() + item { + when(appVersion) { + is UiState.Loading, is UiState.Empty, is UiState.Failure -> { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + contentAlignment = Alignment.Center + ) { + SpotSpinner() + } + + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) } - Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) - } + is UiState.Success -> { + val version = appVersion.data - is UiState.Success -> { - val version = appVersion.data + Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) - Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) + AppVersion(appVersion = version) - AppVersion(appVersion = version) - - Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) - HorizontalDivider( - color = SpotTheme.colors.G300, - thickness = 1.dp, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = screenWidthDp(4.dp)) - ) + Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) + HorizontalDivider( + color = SpotTheme.colors.G300, + thickness = 1.dp, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = screenWidthDp(4.dp)) + ) + } } } - } - item { - Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) + item { + Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) - Logout( - onLogOutClick = {} - ) + Logout( + onLogOutClick = { logout = true } + ) - Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) + Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) - HorizontalDivider( - color = SpotTheme.colors.G300, - thickness = 1.dp, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = screenWidthDp(4.dp)) - ) - } + HorizontalDivider( + color = SpotTheme.colors.G300, + thickness = 1.dp, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = screenWidthDp(4.dp)) + ) + } - item { - Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) + item { + Spacer(modifier = Modifier.height(screenHeightDp(13.dp))) - BlankButton( - modifier = Modifier - .width(screenWidthDp(60.dp)) - .height(screenHeightDp(26.dp)), - state = ImageButtonState.XOUTLINEB100State, - shape = SpotShapes.Hard, - onClick = {} - ) { - Text( - text = "회원 탈퇴", - style = SpotTheme.typography.regular_500, - color = SpotTheme.colors.G400, - textDecoration = TextDecoration.Underline, + BlankButton( modifier = Modifier - .padding(horizontal = screenWidthDp(8.dp), vertical = screenHeightDp(4.dp)) - ) + .width(screenWidthDp(60.dp)) + .height(screenHeightDp(26.dp)), + state = ImageButtonState.XOUTLINEB100State, + shape = SpotShapes.Hard, + onClick = onCancelMemberShipClick + ) { + Text( + text = "회원 탈퇴", + style = SpotTheme.typography.regular_500, + color = SpotTheme.colors.G400, + textDecoration = TextDecoration.Underline, + modifier = Modifier + .padding(horizontal = screenWidthDp(8.dp), vertical = screenHeightDp(4.dp)) + ) + } } } + + AcceptDialog( + visible = logout, + painter = painterResource(R.drawable.logout), + painterTint = SpotTheme.colors.G400, + modalTitle = "로그아웃 할까요?", + modalDes = null, + okButtonText = "로그아웃", + noButtonText = "취소", + onClick = { + successLogout = true + logout = false + }, + onDismiss = { + logout = false + } + ) + + AcceptDialog( + visible = successLogout, + painter = painterResource(R.drawable.ic_check), + painterTint = SpotTheme.colors.B500, + modalTitle = "로그아웃 처리 완료", + modalDes = null, + okButtonText = "확인", + noButtonText = null, + onClick = { + successLogout = false + onLogoutClick() + }, + onDismiss = { + successLogout = false + onLogoutClick() + } + ) } } diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/navigation/MypageNavigation.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/navigation/MypageNavigation.kt index ff3f349a..62064d84 100644 --- a/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/navigation/MypageNavigation.kt +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/main/navigation/MypageNavigation.kt @@ -20,6 +20,8 @@ fun NavGraphBuilder.mypageGraph( onMyAppliedClick : () -> Unit, onEditInterestClick : () -> Unit, onEditInterestLocationClick : () -> Unit, + onCancelMemberShipClick: () -> Unit, + onLogoutClick: () -> Unit ) { composable { MyPageScreen( @@ -28,7 +30,9 @@ fun NavGraphBuilder.mypageGraph( onMyRecruitingClick = onMyRecruitingClick, onMyAppliedClick = onMyAppliedClick, onEditInterestClick = onEditInterestClick, - onEditInterestLocationClick = onEditInterestLocationClick + onEditInterestLocationClick = onEditInterestLocationClick, + onCancelMemberShipClick = onCancelMemberShipClick, + onLogoutClick = onLogoutClick ) } } diff --git a/feature/mypage/src/main/java/com/umcspot/spot/mypage/participating/ParticipatingStudyScreen.kt b/feature/mypage/src/main/java/com/umcspot/spot/mypage/participating/ParticipatingStudyScreen.kt index bed134e9..e4a07bb8 100644 --- a/feature/mypage/src/main/java/com/umcspot/spot/mypage/participating/ParticipatingStudyScreen.kt +++ b/feature/mypage/src/main/java/com/umcspot/spot/mypage/participating/ParticipatingStudyScreen.kt @@ -137,7 +137,8 @@ fun ParticipatingScreen( onStudyClick = onStudyClick, onEditClick = {}, onReportClick = {}, - onLeaveClick = {} + onLeaveClick = {}, + onDeleteClick = {} ) } } @@ -151,7 +152,8 @@ fun ParticipatingStudyScreenContent( onStudyClick: (Long) -> Unit, onEditClick: (Long) -> Unit, onReportClick: (Long) -> Unit, - onLeaveClick: (Long) -> Unit + onLeaveClick: (Long) -> Unit, + onDeleteClick: (Long) -> Unit ) { var expandedForId by remember { mutableStateOf(null) } @@ -193,11 +195,13 @@ fun ParticipatingStudyScreenContent( MeetballMenu( isOwner = item.isOwner , + isAlone = item.isAlone, expanded = expandedForId == item.id, onDismiss = { expandedForId = null }, onEdit = { expandedForId = null; onEditClick(item.id) }, onReport = { expandedForId = null; onReportClick(item.id) }, onLeave = { expandedForId = null; onLeaveClick(item.id) }, + onDelete = { expandedForId = null; onDeleteClick(item.id) } ) } } @@ -221,11 +225,13 @@ fun ParticipatingStudyScreenContent( @Composable fun MeetballMenu( isOwner: Boolean, + isAlone: Boolean, expanded: Boolean, onDismiss: () -> Unit, onEdit: () -> Unit, onReport: () -> Unit, - onLeave: () -> Unit + onLeave: () -> Unit, + onDelete:() -> Unit ) { DropdownMenu( modifier = Modifier @@ -251,47 +257,56 @@ fun MeetballMenu( onEdit() } ) - HorizontalDivider( - modifier = Modifier.fillMaxWidth(), - thickness = 1.dp, - color = SpotTheme.colors.gray200 - ) } - DropdownMenuItem( - modifier = Modifier - .height(screenHeightDp(30.dp)) - .wrapContentWidth(), - text = { - Text( - text = "스터디원 신고", - style = SpotTheme.typography.regular_500, - color = SpotTheme.colors.black + + if(!isAlone) { + if(isOwner) { + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + thickness = 1.dp, + color = SpotTheme.colors.gray200 ) - }, - onClick = { - onDismiss() - onReport() } - ) + + DropdownMenuItem( + modifier = Modifier + .height(screenHeightDp(30.dp)) + .wrapContentWidth(), + text = { + Text( + text = "스터디원 신고", + style = SpotTheme.typography.regular_500, + color = SpotTheme.colors.black + ) + }, + onClick = { + onDismiss() + onReport() + } + ) + } + HorizontalDivider( modifier = Modifier.fillMaxWidth(), thickness = 1.dp, color = SpotTheme.colors.gray200 ) + DropdownMenuItem( modifier = Modifier .height(screenHeightDp(30.dp)) .wrapContentWidth(), text = { Text( - text = "스터디 나가기", + text = if(isAlone) "스터디 삭제하기" else "스터디 나가기", style = SpotTheme.typography.regular_500, color = SpotTheme.colors.R500 ) }, onClick = { onDismiss() - onLeave() + if(isAlone) onDelete() + else onLeave() } ) } diff --git a/feature/signup/src/main/java/com/umcspot/spot/signup/navigation/SignUpNavigation.kt b/feature/signup/src/main/java/com/umcspot/spot/signup/navigation/SignUpNavigation.kt index 157775c9..8bdb398d 100644 --- a/feature/signup/src/main/java/com/umcspot/spot/signup/navigation/SignUpNavigation.kt +++ b/feature/signup/src/main/java/com/umcspot/spot/signup/navigation/SignUpNavigation.kt @@ -24,6 +24,10 @@ fun NavController.navigateToSaving(navOptions: NavOptions? = null) { navigate(Saving, navOptions) } +fun NavController.navigateToLanding(navOptions: NavOptions? = null) { + navigate(Landing, navOptions) +} + fun NavGraphBuilder.signupGraph( navigateToSignUp: () -> Unit, navigateToCheckList: () -> Unit,