diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7fd25940..3392eed9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,13 @@ ## 요약 ## 작업내용 +- [ ] 기능개발 +- [ ] 버그개선 +- [ ] 리팩토링 +- [ ] 핫픽스 +- [ ] 빌드 파일 수정 +- [ ] 기타 + +## 스크린샷 ## 기타 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1e6d1bc9..7aa5d118 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,7 +19,7 @@ android { applicationId = "com.moneymong.moneymong" minSdk = 24 targetSdk = 34 - versionCode = 27 + versionCode = 28 versionName = "1.2.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/com/moneymong/moneymong/MainViewModel.kt b/app/src/main/java/com/moneymong/moneymong/MainViewModel.kt index 98c163c6..191ab18b 100644 --- a/app/src/main/java/com/moneymong/moneymong/MainViewModel.kt +++ b/app/src/main/java/com/moneymong/moneymong/MainViewModel.kt @@ -13,10 +13,8 @@ class MainViewModel @Inject constructor( ) : BaseViewModel(MainState()) { fun checkShouldUpdate(version: String) = intent { - val shouldUpdate = - checkVersionUpdateUseCase(version = version).isFailure - reduce { - state.copy(shouldUpdate = shouldUpdate) - } + checkVersionUpdateUseCase(version = version) + .onSuccess { reduce { state.copy(shouldUpdate = false) } } + .onFailure { reduce { state.copy(shouldUpdate = it.message?.contains("업데이트") == true) } } } } \ No newline at end of file diff --git a/app/src/main/java/com/moneymong/moneymong/navigation/MoneyMongNavHost.kt b/app/src/main/java/com/moneymong/moneymong/navigation/MoneyMongNavHost.kt deleted file mode 100644 index e69de29b..00000000 diff --git a/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/button/Button.kt b/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/button/Button.kt index 732bd0f1..b9a158ed 100644 --- a/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/button/Button.kt +++ b/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/button/Button.kt @@ -30,6 +30,7 @@ fun MDSButton( @DrawableRes iconResource: Int? = null, enabled: Boolean = true, contentHorizontalPadding: Dp = 0.dp, + cornerShape : Dp = 10.dp, ) { val backgroundColor = if (enabled) type.backgroundColor else disabledBackgroundColor val contentColor = if (enabled) type.contentColor else disabledContentColor @@ -38,10 +39,10 @@ fun MDSButton( modifier = modifier .background( color = backgroundColor, - shape = RoundedCornerShape(10.dp) + shape = RoundedCornerShape(cornerShape) ) .clip( - RoundedCornerShape(10.dp) + RoundedCornerShape(cornerShape) ) .clickable( onClick = onClick, diff --git a/core/model/src/main/java/com/moneymong/moneymong/model/sign/UnivResponse.kt b/core/model/src/main/java/com/moneymong/moneymong/model/sign/UnivResponse.kt index 46dbf79f..6fb08df1 100644 --- a/core/model/src/main/java/com/moneymong/moneymong/model/sign/UnivResponse.kt +++ b/core/model/src/main/java/com/moneymong/moneymong/model/sign/UnivResponse.kt @@ -1,6 +1,6 @@ package com.moneymong.moneymong.model.sign data class UnivResponse( - val universityName: String, - val grade: Int + val universityName: String?, + val grade: Int? ) \ No newline at end of file diff --git a/core/network/src/main/java/com/moneymong/moneymong/network/api/AgencyApi.kt b/core/network/src/main/java/com/moneymong/moneymong/network/api/AgencyApi.kt index c03d0c1f..c664b584 100644 --- a/core/network/src/main/java/com/moneymong/moneymong/network/api/AgencyApi.kt +++ b/core/network/src/main/java/com/moneymong/moneymong/network/api/AgencyApi.kt @@ -1,16 +1,16 @@ package com.moneymong.moneymong.network.api -import com.moneymong.moneymong.model.agency.AgencyJoinRequest -import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.AgenciesGetResponse +import com.moneymong.moneymong.model.agency.AgencyGetResponse +import com.moneymong.moneymong.model.agency.AgencyJoinRequest import com.moneymong.moneymong.model.agency.AgencyJoinResponse +import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.agency.RegisterAgencyResponse import com.moneymong.moneymong.model.member.InvitationCodeResponse import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET -import retrofit2.http.Header import retrofit2.http.PATCH import retrofit2.http.POST import retrofit2.http.Path @@ -33,6 +33,11 @@ interface AgencyApi { @GET("api/v1/agencies/me") suspend fun fetchMyAgencyList(): Result> + @GET("api/v1/agencies/search") + suspend fun fetchAgencyByName( + @Query("keyword") name: String + ): Result> + // POST @POST("/api/v1/agencies/{agencyId}/invitation-code") suspend fun agencyCodeNumbers( @@ -55,5 +60,5 @@ interface AgencyApi { @DELETE("api/v1/agencies/{agencyId}") suspend fun deleteAgency( @Path("agencyId") agencyId: Int - ) : Result + ): Result } \ No newline at end of file diff --git a/core/network/src/main/java/com/moneymong/moneymong/network/util/MoneyMongTokenAuthenticator.kt b/core/network/src/main/java/com/moneymong/moneymong/network/util/MoneyMongTokenAuthenticator.kt index 24123b6e..1a599a89 100644 --- a/core/network/src/main/java/com/moneymong/moneymong/network/util/MoneyMongTokenAuthenticator.kt +++ b/core/network/src/main/java/com/moneymong/moneymong/network/util/MoneyMongTokenAuthenticator.kt @@ -37,14 +37,10 @@ class MoneyMongTokenAuthenticator @Inject constructor( } } .onFailure { - runBlocking { - tokenRepository.notifyTokenUpdateFailed(true) - } + tokenRepository.notifyTokenUpdateFailed(true) } }.onFailure { - runBlocking { - tokenRepository.notifyTokenUpdateFailed(true) - } + tokenRepository.notifyTokenUpdateFailed(true) } newRequest } diff --git a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSource.kt b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSource.kt index 5ce29b85..10832478 100644 --- a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSource.kt +++ b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSource.kt @@ -1,9 +1,10 @@ package com.moneymong.moneymong.data.datasource.agency +import com.moneymong.moneymong.model.agency.AgenciesGetResponse +import com.moneymong.moneymong.model.agency.AgencyGetResponse import com.moneymong.moneymong.model.agency.AgencyJoinRequest -import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.AgencyJoinResponse -import com.moneymong.moneymong.model.agency.AgenciesGetResponse +import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.agency.RegisterAgencyResponse @@ -11,5 +12,6 @@ interface AgencyRemoteDataSource { suspend fun registerAgency(request: AgencyRegisterRequest): Result suspend fun getAgencies(page: Int, size: Int): Result suspend fun fetchMyAgencyList(): Result> + suspend fun fetchAgencyByName(agencyName: String): Result> suspend fun agencyCodeNumbers(agencyId : Long, data: AgencyJoinRequest) : Result } \ No newline at end of file diff --git a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceImpl.kt b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceImpl.kt index 68c8a6a6..6643d480 100644 --- a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceImpl.kt +++ b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceImpl.kt @@ -1,12 +1,13 @@ package com.moneymong.moneymong.data.datasource.agency -import com.moneymong.moneymong.network.api.AgencyApi -import com.moneymong.moneymong.model.agency.AgencyJoinRequest -import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.AgenciesGetResponse +import com.moneymong.moneymong.model.agency.AgencyGetResponse +import com.moneymong.moneymong.model.agency.AgencyJoinRequest import com.moneymong.moneymong.model.agency.AgencyJoinResponse +import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.agency.RegisterAgencyResponse +import com.moneymong.moneymong.network.api.AgencyApi import javax.inject.Inject class AgencyRemoteDataSourceImpl @Inject constructor( @@ -25,6 +26,10 @@ class AgencyRemoteDataSourceImpl @Inject constructor( return agencyApi.fetchMyAgencyList() } + override suspend fun fetchAgencyByName(agencyName: String): Result> { + return agencyApi.fetchAgencyByName(name = agencyName) + } + override suspend fun agencyCodeNumbers( agencyId: Long, data: AgencyJoinRequest diff --git a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceMock.kt b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceMock.kt index fe6be889..1c14125b 100644 --- a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceMock.kt +++ b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceMock.kt @@ -1,10 +1,10 @@ package com.moneymong.moneymong.data.datasource.agency -import com.moneymong.moneymong.model.agency.AgencyJoinRequest -import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.AgenciesGetResponse import com.moneymong.moneymong.model.agency.AgencyGetResponse +import com.moneymong.moneymong.model.agency.AgencyJoinRequest import com.moneymong.moneymong.model.agency.AgencyJoinResponse +import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.agency.RegisterAgencyResponse import kotlinx.coroutines.delay @@ -24,6 +24,10 @@ class AgencyRemoteDataSourceMock : AgencyRemoteDataSource { return Result.success(emptyList()) } + override suspend fun fetchAgencyByName(agencyName: String): Result> { + return Result.success(emptyList()) + } + override suspend fun agencyCodeNumbers( agencyId: Long, data: AgencyJoinRequest diff --git a/data/src/main/java/com/moneymong/moneymong/data/repository/agency/AgencyRepositoryImpl.kt b/data/src/main/java/com/moneymong/moneymong/data/repository/agency/AgencyRepositoryImpl.kt index 225ea34b..3d8445a3 100644 --- a/data/src/main/java/com/moneymong/moneymong/data/repository/agency/AgencyRepositoryImpl.kt +++ b/data/src/main/java/com/moneymong/moneymong/data/repository/agency/AgencyRepositoryImpl.kt @@ -35,6 +35,9 @@ class AgencyRepositoryImpl @Inject constructor( override suspend fun fetchMyAgencyList(): Result> = agencyRemoteDataSource.fetchMyAgencyList() + override suspend fun fetchAgencyByName(agencyName: String): Result> = + agencyRemoteDataSource.fetchAgencyByName(agencyName) + override suspend fun agencyCodeNumbers( agencyId: Long, data: AgencyJoinRequest diff --git a/data/src/main/java/com/moneymong/moneymong/data/repository/token/TokenRepositoryImpl.kt b/data/src/main/java/com/moneymong/moneymong/data/repository/token/TokenRepositoryImpl.kt index 56f222ff..a6784c7c 100644 --- a/data/src/main/java/com/moneymong/moneymong/data/repository/token/TokenRepositoryImpl.kt +++ b/data/src/main/java/com/moneymong/moneymong/data/repository/token/TokenRepositoryImpl.kt @@ -25,15 +25,19 @@ class TokenRepositoryImpl @Inject constructor( return loginLocalDataSource.getRefreshToken() } - override suspend fun postAccessToken(type: LoginType, accessToken: String): Result { - return tokenRemoteDataSource.postAccessToken(type = type, accessToken = accessToken).onSuccess { - loginLocalDataSource.setDataStore( - it.accessToken, - it.refreshToken, - it.loginSuccess, - it.schoolInfoProvided - ) - } + override suspend fun postAccessToken( + type: LoginType, + accessToken: String + ): Result { + return tokenRemoteDataSource.postAccessToken(type = type, accessToken = accessToken) + .onSuccess { + loginLocalDataSource.setDataStore( + it.accessToken, + it.refreshToken, + it.loginSuccess, + it.schoolInfoProvided + ) + } } override suspend fun getAccessToken(): Result { diff --git a/domain/src/main/java/com/moneymong/moneymong/domain/repository/agency/AgencyRepository.kt b/domain/src/main/java/com/moneymong/moneymong/domain/repository/agency/AgencyRepository.kt index 1515676b..a8cabcc1 100644 --- a/domain/src/main/java/com/moneymong/moneymong/domain/repository/agency/AgencyRepository.kt +++ b/domain/src/main/java/com/moneymong/moneymong/domain/repository/agency/AgencyRepository.kt @@ -13,6 +13,7 @@ interface AgencyRepository { suspend fun registerAgency(request: AgencyRegisterRequest): Result fun getAgencies(): Flow> suspend fun fetchMyAgencyList(): Result> + suspend fun fetchAgencyByName(agencyName: String): Result> suspend fun agencyCodeNumbers(agencyId: Long, data: AgencyJoinRequest): Result suspend fun saveAgencyId(agencyId: Int) diff --git a/domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/GetAgenciesUseCase.kt b/domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/FetchAgenciesUseCase.kt similarity index 90% rename from domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/GetAgenciesUseCase.kt rename to domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/FetchAgenciesUseCase.kt index ef420be5..07ca5c99 100644 --- a/domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/GetAgenciesUseCase.kt +++ b/domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/FetchAgenciesUseCase.kt @@ -6,7 +6,7 @@ import com.moneymong.moneymong.model.agency.AgencyGetResponse import kotlinx.coroutines.flow.Flow import javax.inject.Inject -class GetAgenciesUseCase @Inject constructor( +class FetchAgenciesUseCase @Inject constructor( private val agencyRepository: AgencyRepository ) { operator fun invoke(): Flow> { diff --git a/domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/FetchAgencyByNameUseCase.kt b/domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/FetchAgencyByNameUseCase.kt new file mode 100644 index 00000000..0555ec0d --- /dev/null +++ b/domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/FetchAgencyByNameUseCase.kt @@ -0,0 +1,14 @@ +package com.moneymong.moneymong.domain.usecase.agency + +import com.moneymong.moneymong.domain.repository.agency.AgencyRepository +import com.moneymong.moneymong.model.agency.AgencyGetResponse +import javax.inject.Inject + +class FetchAgencyByNameUseCase @Inject constructor( + private val agencyRepository: AgencyRepository, +) { + + suspend operator fun invoke(agencyName: String): Result> { + return agencyRepository.fetchAgencyByName(agencyName = agencyName) + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/moneymong/moneymong/domain/usecase/user/GetMyInfoUseCase.kt b/domain/src/main/java/com/moneymong/moneymong/domain/usecase/user/FetchMyInfoUseCase.kt similarity index 88% rename from domain/src/main/java/com/moneymong/moneymong/domain/usecase/user/GetMyInfoUseCase.kt rename to domain/src/main/java/com/moneymong/moneymong/domain/usecase/user/FetchMyInfoUseCase.kt index 1a0c6d80..0ec28b5d 100644 --- a/domain/src/main/java/com/moneymong/moneymong/domain/usecase/user/GetMyInfoUseCase.kt +++ b/domain/src/main/java/com/moneymong/moneymong/domain/usecase/user/FetchMyInfoUseCase.kt @@ -4,7 +4,7 @@ import com.moneymong.moneymong.domain.repository.user.UserRepository import com.moneymong.moneymong.model.user.UserResponse import javax.inject.Inject -class GetMyInfoUseCase @Inject constructor( +class FetchMyInfoUseCase @Inject constructor( private val userRepository: UserRepository ) { suspend operator fun invoke(): Result { diff --git a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchContentView.kt b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchContentView.kt new file mode 100644 index 00000000..e7381280 --- /dev/null +++ b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchContentView.kt @@ -0,0 +1,221 @@ +package com.moneymong.moneymong.feature.agency.search + +import androidx.compose.foundation.Image +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.itemKey +import com.moneymong.moneymong.common.error.MoneyMongError +import com.moneymong.moneymong.design_system.R +import com.moneymong.moneymong.design_system.component.indicator.LoadingItem +import com.moneymong.moneymong.design_system.component.indicator.LoadingScreen +import com.moneymong.moneymong.design_system.error.ErrorItem +import com.moneymong.moneymong.design_system.error.ErrorScreen +import com.moneymong.moneymong.design_system.theme.Body3 +import com.moneymong.moneymong.design_system.theme.Gray07 +import com.moneymong.moneymong.feature.agency.search.item.AgencyFeedbackItem +import com.moneymong.moneymong.feature.agency.search.item.AgencyItem + +@Composable +internal fun AgencySearchContentView( + modifier: Modifier = Modifier, + pagingItems: LazyPagingItems, + searchedAgencies: List, + onClickItem: (agencyId: Long) -> Unit, + onClickFeedbackItem: () -> Unit, + isSearched: Boolean, + isLoading: Boolean, + isError: Boolean, + errorMessage: String, + onRetry: () -> Unit, +) { + val contentLoading = pagingItems.loadState.refresh is LoadState.Loading || isLoading + val contentError = pagingItems.loadState.refresh is LoadState.Error || isError + val contentErrorMessage = errorMessage.ifEmpty { + (pagingItems.loadState.refresh as? LoadState.Error)?.error?.message + ?: MoneyMongError.UnExpectedError.message + } + + when { + contentLoading -> LoadingScreen(modifier = modifier.fillMaxSize()) + + contentError -> + ErrorScreen( + modifier = modifier.fillMaxSize(), + message = contentErrorMessage, + onRetry = { + pagingItems.retry() + onRetry() + }, + ) + + isSearched -> + ContentViewWithAgencies( + modifier = modifier, + agencies = searchedAgencies, + onClickItem = onClickItem, + onClickFeedbackItem = onClickFeedbackItem + ) + + pagingItems.itemCount == 0 -> + ContentViewWithoutAgencies( + modifier = modifier, + pagingItems = pagingItems, + onClickFeedbackItem = onClickFeedbackItem + ) + + + else -> + ContentViewWithAgencies( + modifier = modifier, + pagingItems = pagingItems, + onClickItem = onClickItem, + onClickFeedbackItem = onClickFeedbackItem + ) + } +} + + +@Composable +private fun ContentViewWithAgencies( + modifier: Modifier = Modifier, + agencies: List, + onClickItem: (agencyId: Long) -> Unit, + onClickFeedbackItem: () -> Unit +) { + LazyColumn( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(vertical = 6.dp) + ) { + item { + AgencyFeedbackItem(onClick = onClickFeedbackItem) + } + items(count = agencies.size, key = { agencies[it].id }) { + AgencyItem( + agency = agencies[it], + onClick = { onClickItem(agencies[it].id) } + ) + } + } +} + +@Composable +private fun ContentViewWithAgencies( + modifier: Modifier = Modifier, + pagingItems: LazyPagingItems, + onClickItem: (agencyId: Long) -> Unit, + onClickFeedbackItem: () -> Unit +) { + LazyColumn( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(vertical = 6.dp) + ) { + item { + AgencyFeedbackItem( + onClick = onClickFeedbackItem + ) + } + items( + count = pagingItems.itemCount, key = pagingItems.itemKey { it.id } + ) { + pagingItems[it]?.let { agency -> + AgencyItem( + agency = agency, + onClick = { onClickItem(agency.id) } + ) + } + } + + when (pagingItems.loadState.source.append) { + is LoadState.Loading -> { + item { + LoadingItem(modifier = Modifier.fillMaxWidth()) + } + } + + is LoadState.Error -> { + val e = pagingItems.loadState.source.append as LoadState.Error + item { + ErrorItem( + modifier = Modifier.fillMaxWidth(), + message = "${e.error.message}", + onRetry = pagingItems::retry + ) + } + } + + is LoadState.NotLoading -> Unit + } + } +} + +@Composable +private fun ContentViewWithoutAgencies( + modifier: Modifier = Modifier, + pagingItems: LazyPagingItems, + onClickFeedbackItem: () -> Unit +) { + + when (pagingItems.loadState.refresh) { + is LoadState.Loading -> { + LoadingScreen(modifier = modifier.fillMaxSize()) + } + + is LoadState.Error -> { + val e = pagingItems.loadState.refresh as LoadState.Error + ErrorScreen( + modifier = modifier.fillMaxSize(), + message = "${e.error.message}", + onRetry = pagingItems::retry + ) + } + + is LoadState.NotLoading -> { + Box(modifier = Modifier.fillMaxSize()) { + AgencyFeedbackItem( + modifier = Modifier + .align(Alignment.TopCenter) + .padding(top = 10.dp), + onClick = onClickFeedbackItem + ) + Column( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy( + space = 4.dp, + alignment = Alignment.CenterVertically + ), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + modifier = Modifier.size(size = 80.dp), + painter = painterResource(id = R.drawable.img_agency), + contentDescription = "agency image", + ) + Text( + text = "아직 등록된 소속이 없어요\n하단 버튼을 통해 등록해보세요", + textAlign = TextAlign.Center, + color = Gray07, + style = Body3 + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchScreen.kt b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchScreen.kt index 8e3592d9..25c84600 100644 --- a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchScreen.kt +++ b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchScreen.kt @@ -2,51 +2,43 @@ package com.moneymong.moneymong.feature.agency.search import android.content.Intent import android.net.Uri -import androidx.compose.foundation.Image +import androidx.activity.compose.BackHandler +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateIntAsState +import androidx.compose.animation.core.tween 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.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.paging.LoadState -import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems -import androidx.paging.compose.itemKey -import com.moneymong.moneymong.common.error.MoneyMongError import com.moneymong.moneymong.design_system.R import com.moneymong.moneymong.design_system.component.button.MDSFloatingActionButton -import com.moneymong.moneymong.design_system.component.indicator.LoadingItem -import com.moneymong.moneymong.design_system.component.indicator.LoadingScreen import com.moneymong.moneymong.design_system.component.tooltip.MDSToolTip import com.moneymong.moneymong.design_system.component.tooltip.MDSToolTipPosition import com.moneymong.moneymong.design_system.error.ErrorDialog -import com.moneymong.moneymong.design_system.error.ErrorItem -import com.moneymong.moneymong.design_system.error.ErrorScreen -import com.moneymong.moneymong.design_system.theme.Body3 import com.moneymong.moneymong.design_system.theme.Gray01 -import com.moneymong.moneymong.design_system.theme.Gray07 import com.moneymong.moneymong.design_system.theme.MMHorizontalSpacing import com.moneymong.moneymong.design_system.theme.Red03 import com.moneymong.moneymong.feature.agency.search.component.AgencySearchTopBar -import com.moneymong.moneymong.feature.agency.search.item.AgencyFeedbackItem -import com.moneymong.moneymong.feature.agency.search.item.AgencyItem +import com.moneymong.moneymong.feature.agency.search.component.searchbar.AgencySearchBar +import com.moneymong.moneymong.ui.pxToDp import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect @@ -61,6 +53,10 @@ fun AgencySearchScreen( val context = LocalContext.current val pagingItems = viewModel.agencies.collectAsLazyPagingItems() + BackHandler(enabled = state.visibleSearchBar) { + viewModel.toggleVisibilitySearchBar() + } + viewModel.collectSideEffect { when (it) { is AgencySearchSideEffect.NavigateToRegister -> { @@ -88,6 +84,16 @@ fun AgencySearchScreen( ) } + var searchBarHeight by remember { mutableIntStateOf(0) } + val offsetY by animateIntAsState( + targetValue = if (state.visibleSearchBar) searchBarHeight else 0, + animationSpec = tween( + durationMillis = 300, + easing = LinearEasing + ), + label = "Content Offset Y" + ) + Box( modifier = modifier .fillMaxSize() @@ -98,23 +104,40 @@ fun AgencySearchScreen( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { - AgencySearchTopBar() - AgencySearchContentView( - modifier = Modifier.weight(1f), - pagingItems = pagingItems, - onClickItem = { agencyId -> - if (agencyId in state.joinedAgenciesIds) { - viewModel.changeVisibleWarningDialog(true) - } else { - viewModel.navigateToJoin(agencyId) - } - }, - onClickFeedbackItem = viewModel::onClickAskFeedback, - isLoading = state.isLoading, - isError = state.isError, - errorMessage = state.errorMessage, - onRetry = viewModel::getInitialData, + AgencySearchTopBar( + onSearchIconClick = viewModel::toggleVisibilitySearchBar, + visibleSearchIcon = state.visibleSearchBar.not() ) + Box(modifier = Modifier.fillMaxSize()) { + AgencySearchBar( + modifier = Modifier.onSizeChanged { searchBarHeight = it.height }, + state = state.searchTextFieldState, + visible = state.visibleSearchBar, + onSearch = viewModel::searchAgency, + onClear = viewModel::clearSearchTextField, + onCancel = viewModel::toggleVisibilitySearchBar, + ) + AgencySearchContentView( + modifier = Modifier + .offset { IntOffset(x = 0, y = offsetY) } + .padding(bottom = offsetY.pxToDp), + pagingItems = pagingItems, + searchedAgencies = state.searchedAgencies, + onClickItem = { agencyId -> + if (agencyId in state.joinedAgenciesIds) { + viewModel.changeVisibleWarningDialog(true) + } else { + viewModel.navigateToJoin(agencyId) + } + }, + onClickFeedbackItem = viewModel::onClickAskFeedback, + isSearched = state.isSearched, + isLoading = state.isLoading, + isError = state.isError, + errorMessage = state.errorMessage, + onRetry = viewModel::getInitialData, + ) + } } Column( modifier = Modifier @@ -137,156 +160,3 @@ fun AgencySearchScreen( } } } - -@Composable -private fun AgencySearchContentView( - modifier: Modifier = Modifier, - pagingItems: LazyPagingItems, - onClickItem: (agencyId: Long) -> Unit, - onClickFeedbackItem: () -> Unit, - isLoading: Boolean, - isError: Boolean, - errorMessage: String, - onRetry: () -> Unit, -) { - val contentLoading = pagingItems.loadState.refresh is LoadState.Loading || isLoading - val contentError = pagingItems.loadState.refresh is LoadState.Error || isError - val contentErrorMessage = errorMessage.ifEmpty { - (pagingItems.loadState.refresh as? LoadState.Error)?.error?.message - ?: MoneyMongError.UnExpectedError.message - } - - if (contentLoading) { - LoadingScreen(modifier = modifier.fillMaxSize()) - } else if (contentError) { - ErrorScreen( - modifier = modifier.fillMaxSize(), - message = contentErrorMessage, - onRetry = { - pagingItems.retry() - onRetry() - }, - ) - } else { - if (pagingItems.itemCount == 0) { - ContentViewWithoutAgencies( - modifier = modifier, - pagingItems = pagingItems, - onClickFeedbackItem = onClickFeedbackItem - ) - } else { - ContentViewWithAgencies( - modifier = modifier, - pagingItems = pagingItems, - onClickItem = onClickItem, - onClickFeedbackItem = onClickFeedbackItem - ) - } - } -} - - -@Composable -private fun ContentViewWithAgencies( - modifier: Modifier = Modifier, - pagingItems: LazyPagingItems, - onClickItem: (agencyId: Long) -> Unit, - onClickFeedbackItem: () -> Unit -) { - LazyColumn( - modifier = modifier, - verticalArrangement = Arrangement.spacedBy(12.dp), - contentPadding = PaddingValues(vertical = 4.dp) - ) { - item { - AgencyFeedbackItem( - onClick = onClickFeedbackItem - ) - } - items( - count = pagingItems.itemCount, key = pagingItems.itemKey { it.id } - ) { - pagingItems[it]?.let { agency -> - AgencyItem( - agency = agency, - onClick = { onClickItem(agency.id) } - ) - } - } - - when (pagingItems.loadState.source.append) { - is LoadState.Loading -> { - item { - LoadingItem(modifier = Modifier.fillMaxWidth()) - } - } - - is LoadState.Error -> { - val e = pagingItems.loadState.source.append as LoadState.Error - item { - ErrorItem( - modifier = Modifier.fillMaxWidth(), - message = "${e.error.message}", - onRetry = pagingItems::retry - ) - } - } - - is LoadState.NotLoading -> Unit - } - } -} - -@Composable -private fun ContentViewWithoutAgencies( - modifier: Modifier = Modifier, - pagingItems: LazyPagingItems, - onClickFeedbackItem: () -> Unit -) { - - when (pagingItems.loadState.refresh) { - is LoadState.Loading -> { - LoadingScreen(modifier = modifier.fillMaxSize()) - } - - is LoadState.Error -> { - val e = pagingItems.loadState.refresh as LoadState.Error - ErrorScreen( - modifier = modifier.fillMaxSize(), - message = "${e.error.message}", - onRetry = pagingItems::retry - ) - } - - is LoadState.NotLoading -> { - Box(modifier = Modifier.fillMaxSize()) { - AgencyFeedbackItem( - modifier = Modifier - .align(Alignment.TopCenter) - .padding(top = 10.dp), - onClick = onClickFeedbackItem - ) - Column( - modifier = modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy( - space = 4.dp, - alignment = Alignment.CenterVertically - ), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - modifier = Modifier.size(size = 80.dp), - painter = painterResource(id = R.drawable.img_agency), - contentDescription = "agency image", - ) - Text( - text = "아직 등록된 소속이 없어요\n하단 버튼을 통해 등록해보세요", - textAlign = TextAlign.Center, - color = Gray07, - style = Body3 - ) - } - } - } - } -} \ No newline at end of file diff --git a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchState.kt b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchState.kt index 2feee998..ecb9cdf7 100644 --- a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchState.kt +++ b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchState.kt @@ -1,14 +1,19 @@ package com.moneymong.moneymong.feature.agency.search +import androidx.compose.foundation.text.input.TextFieldState import com.moneymong.moneymong.common.base.State data class AgencySearchState( val joinedAgencies: List = emptyList(), + val searchedAgencies: List = emptyList(), + val isSearched: Boolean = false, val isLoading: Boolean = false, val isError: Boolean = false, val errorMessage: String = "", val visibleWarningDialog: Boolean = false, - val isUniversityStudent: Boolean = false + val isUniversityStudent: Boolean = false, + val visibleSearchBar: Boolean = false, + val searchTextFieldState: TextFieldState = TextFieldState(), ) : State { val joinedAgenciesIds: List diff --git a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchViewModel.kt b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchViewModel.kt index 323d3b74..7682e377 100644 --- a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchViewModel.kt +++ b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/AgencySearchViewModel.kt @@ -1,12 +1,14 @@ package com.moneymong.moneymong.feature.agency.search +import androidx.compose.foundation.text.input.clearText import androidx.lifecycle.viewModelScope import androidx.paging.cachedIn import androidx.paging.map import com.moneymong.moneymong.common.base.BaseViewModel import com.moneymong.moneymong.common.error.MoneyMongError +import com.moneymong.moneymong.domain.usecase.agency.FetchAgenciesUseCase +import com.moneymong.moneymong.domain.usecase.agency.FetchAgencyByNameUseCase import com.moneymong.moneymong.domain.usecase.agency.FetchMyAgencyListUseCase -import com.moneymong.moneymong.domain.usecase.agency.GetAgenciesUseCase import com.moneymong.moneymong.domain.usecase.university.FetchMyUniversityUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async @@ -19,9 +21,10 @@ import javax.inject.Inject @HiltViewModel class AgencySearchViewModel @Inject constructor( - getAgenciesUseCase: GetAgenciesUseCase, + fetchAgenciesUseCase: FetchAgenciesUseCase, private val fetchMyAgencyListUseCase: FetchMyAgencyListUseCase, - private val fetchMyUniversityUseCase: FetchMyUniversityUseCase + private val fetchMyUniversityUseCase: FetchMyUniversityUseCase, + private val fetchAgencyByNameUseCase: FetchAgencyByNameUseCase, ) : BaseViewModel(AgencySearchState()) { fun navigateToRegister() = intent { @@ -31,7 +34,7 @@ class AgencySearchViewModel @Inject constructor( fun navigateToJoin(agencyId: Long) = eventEmit(sideEffect = AgencySearchSideEffect.NavigateToAgencyJoin(agencyId)) - val agencies = getAgenciesUseCase().map { pagingData -> + val agencies = fetchAgenciesUseCase().map { pagingData -> pagingData.map { it.toAgency() } @@ -65,7 +68,7 @@ class AgencySearchViewModel @Inject constructor( isLoading = false, joinedAgencies = fetchMyAgenciesResult.getOrThrow() .map { myAgencyResponse -> myAgencyResponse.toAgency() }, - isUniversityStudent = fetchMyUniversityResult.getOrThrow().universityName.isNotBlank(), + isUniversityStudent = fetchMyUniversityResult.getOrThrow().universityName?.isNotBlank() ?: false , ) } } else { @@ -82,6 +85,58 @@ class AgencySearchViewModel @Inject constructor( } } + fun searchAgency() = intent { + reduce { + state.copy( + isLoading = true, + isError = false + ) + } + fetchAgencyByNameUseCase(agencyName = state.searchTextFieldState.text.toString()) + .onSuccess { agencies -> + reduce { + state.copy( + isLoading = false, + searchedAgencies = agencies.map { agencyResponse -> agencyResponse.toAgency() } + ) + } + }.onFailure { + reduce { + state.copy( + isLoading = false, + isError = true, + errorMessage = it.message ?: MoneyMongError.UnExpectedError.message + ) + } + }.also { + reduce { + state.copy(isSearched = true) + } + } + } + + fun toggleVisibilitySearchBar() = intent { + if (state.visibleSearchBar) { + reduce { + state.copy( + visibleSearchBar = false, + searchedAgencies = emptyList(), + isSearched = false + ).also { + clearSearchTextField() + } + } + } else { + reduce { + state.copy(visibleSearchBar = true) + } + } + } + + fun clearSearchTextField() = intent { + state.searchTextFieldState.clearText() + } + fun changeVisibleWarningDialog(visible: Boolean) = intent { reduce { state.copy(visibleWarningDialog = visible) diff --git a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/component/AgencySearchTopBar.kt b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/component/AgencySearchTopBar.kt index 10aa1c30..7ef1ac89 100644 --- a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/component/AgencySearchTopBar.kt +++ b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/component/AgencySearchTopBar.kt @@ -1,21 +1,70 @@ package com.moneymong.moneymong.feature.agency.search.component +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon 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.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.moneymong.moneymong.common.ui.noRippleClickable import com.moneymong.moneymong.design_system.theme.Gray10 import com.moneymong.moneymong.design_system.theme.Heading1 +import com.moneymong.moneymong.design_system.R as MDSR @Composable -fun AgencySearchTopBar( +internal fun AgencySearchTopBar( modifier: Modifier = Modifier, + onSearchIconClick: () -> Unit, + visibleSearchIcon: Boolean, ) { - Text( - modifier = modifier.padding(vertical = 16.dp), - text = "소속 찾기", - color = Gray10, - style = Heading1 + val animationSpec = tween( + durationMillis = 300, easing = FastOutSlowInEasing + ) + Box( + modifier = modifier.fillMaxWidth() + ) { + Text( + modifier = Modifier + .align(Alignment.Center) + .padding(vertical = 16.dp), + text = "소속 찾기", + color = Gray10, + style = Heading1 + ) + AnimatedVisibility( + modifier = Modifier.align(Alignment.CenterEnd), + visible = visibleSearchIcon, + enter = fadeIn(animationSpec = animationSpec), + exit = fadeOut(animationSpec = animationSpec) + ) { + Icon( + modifier = Modifier + .size(24.dp) + .noRippleClickable { onSearchIconClick() }, + imageVector = ImageVector.vectorResource(id = MDSR.drawable.ic_search), + contentDescription = "검색", + ) + } + } +} + +@Preview +@Composable +private fun AgencySearchTopBarPreview() { + AgencySearchTopBar( + onSearchIconClick = {}, + visibleSearchIcon = true ) } \ No newline at end of file diff --git a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/component/searchbar/AgencySearchBar.kt b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/component/searchbar/AgencySearchBar.kt new file mode 100644 index 00000000..9fbb230d --- /dev/null +++ b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/component/searchbar/AgencySearchBar.kt @@ -0,0 +1,99 @@ +package com.moneymong.moneymong.feature.agency.search.component.searchbar + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.moneymong.moneymong.common.ui.noRippleClickable +import com.moneymong.moneymong.design_system.theme.Body2 +import com.moneymong.moneymong.design_system.theme.Gray08 +import com.moneymong.moneymong.design_system.theme.MMTheme + +@Composable +internal fun AgencySearchBar( + modifier: Modifier = Modifier, + state: TextFieldState, + visible: Boolean, + onSearch: () -> Unit, + onCancel: () -> Unit, + onClear: () -> Unit +) { + val focusRequester = remember { FocusRequester() } + val focusManager = LocalFocusManager.current + val animationSpec = tween( + durationMillis = 300, + easing = FastOutSlowInEasing + ) + + LaunchedEffect(key1 = visible) { + if (visible) { + focusRequester.requestFocus() + } + } + + AnimatedVisibility( + modifier = modifier, + visible = visible, + enter = fadeIn(animationSpec = animationSpec), + exit = fadeOut(animationSpec = animationSpec) + ) { + Row( + modifier = Modifier.padding(vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AgencySearchTextField( + modifier = Modifier + .weight(1f) + .focusRequester(focusRequester), + state = state, + onSearch = { + onSearch() + focusManager.clearFocus() + }, + onClear = { + onClear() + focusRequester.requestFocus() + } + ) + Spacer(modifier = Modifier.width(10.dp)) + Text( + modifier = Modifier.noRippleClickable { onCancel() }, + text = "취소", + style = Body2, + color = Gray08 + ) + } + } +} + +@Preview +@Composable +private fun SearchBarPreview() { + MMTheme { + AgencySearchBar( + state = rememberTextFieldState(), + visible = true, + onSearch = {}, + onClear = {}, + onCancel = {}, + ) + } +} \ No newline at end of file diff --git a/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/component/searchbar/AgencySearchTextField.kt b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/component/searchbar/AgencySearchTextField.kt new file mode 100644 index 00000000..d35e352b --- /dev/null +++ b/feature/agency/src/main/java/com/moneymong/moneymong/feature/agency/search/component/searchbar/AgencySearchTextField.kt @@ -0,0 +1,93 @@ +package com.moneymong.moneymong.feature.agency.search.component.searchbar + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.moneymong.moneymong.common.ui.noRippleClickable +import com.moneymong.moneymong.design_system.theme.Blue04 +import com.moneymong.moneymong.design_system.theme.Body2 +import com.moneymong.moneymong.design_system.theme.Gray04 +import com.moneymong.moneymong.design_system.theme.Gray05 +import com.moneymong.moneymong.design_system.theme.Gray09 +import com.moneymong.moneymong.design_system.theme.MMTheme +import com.moneymong.moneymong.design_system.theme.White +import com.moneymong.moneymong.design_system.R as MDSR + +@Composable +internal fun AgencySearchTextField( + modifier: Modifier = Modifier, + state: TextFieldState, + onSearch: () -> Unit, + onClear: () -> Unit, +) { + + BasicTextField( + modifier = modifier, + state = state, + keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Search), + onKeyboardAction = { onSearch() }, + decorator = { innerTextField -> + Row( + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(color = White) + .border(width = 1.dp, color = Blue04, shape = RoundedCornerShape(8.dp)) + .padding(vertical = 10.dp, horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box { + innerTextField() + if (state.text.isEmpty()) { + Text( + text = "소속을 검색해 보세요", + color = Gray05, + style = Body2 + ) + } + } + Spacer(modifier = Modifier.weight(1f)) + Icon( + modifier = Modifier + .size(20.dp) + .noRippleClickable { onClear() }, + imageVector = ImageVector.vectorResource(id = MDSR.drawable.ic_close_default), + contentDescription = "Clear Search Text", + tint = Gray04, + ) + } + }, + textStyle = Body2.copy(color = Gray09) + ) +} + +@Preview +@Composable +private fun SearchTextFieldPreview() { + MMTheme { + AgencySearchTextField( + state = rememberTextFieldState(), + onSearch = { }, + onClear = { } + ) + } +} \ No newline at end of file diff --git a/feature/home/src/main/java/com/moneymong/moneymong/home/HomeScreen.kt b/feature/home/src/main/java/com/moneymong/moneymong/home/HomeScreen.kt index a0752ac0..2f690615 100644 --- a/feature/home/src/main/java/com/moneymong/moneymong/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/moneymong/moneymong/home/HomeScreen.kt @@ -29,8 +29,10 @@ import com.moneymong.moneymong.feature.sign.navigation.loginScreen import com.moneymong.moneymong.feature.sign.navigation.navigateLogin import com.moneymong.moneymong.feature.sign.navigation.navigateSignComplete import com.moneymong.moneymong.feature.sign.navigation.navigateSignUp +import com.moneymong.moneymong.feature.sign.navigation.navigateSignUpUniversity import com.moneymong.moneymong.feature.sign.navigation.signCompleteScreen import com.moneymong.moneymong.feature.sign.navigation.signUpScreen +import com.moneymong.moneymong.feature.sign.navigation.signUpUniversity import com.moneymong.moneymong.feature.sign.navigation.splashRoute import com.moneymong.moneymong.feature.sign.navigation.splashScreen import com.moneymong.moneymong.home.navigation.rememberHomeNavigator @@ -99,7 +101,15 @@ fun HomeScreen( ) signUpScreen( - navigateToSignComplete = homeNavController::navigateSignComplete, + navigateToLedger = homeNavController::navigateLedger, + navigateToSignUniversity = homeNavController::navigateSignUpUniversity, + navigateToAgency = homeNavController::navigateAgency, + navigateUp = homeNavController::navigateUp + ) + + signUpUniversity( + navigateToLedger = homeNavController::navigateLedger, + navigateToAgency = homeNavController::navigateAgency, navigateUp = homeNavController::navigateUp ) diff --git a/feature/member/src/main/java/com/example/member/MemberViewModel.kt b/feature/member/src/main/java/com/example/member/MemberViewModel.kt index 460f31b7..49ab1ec2 100644 --- a/feature/member/src/main/java/com/example/member/MemberViewModel.kt +++ b/feature/member/src/main/java/com/example/member/MemberViewModel.kt @@ -9,7 +9,7 @@ import com.moneymong.moneymong.domain.usecase.member.MemberInvitationCodeUseCase import com.moneymong.moneymong.domain.usecase.member.MemberListUseCase import com.moneymong.moneymong.domain.usecase.member.MemberReInvitationCodeUseCase import com.moneymong.moneymong.domain.usecase.member.UpdateMemberAuthorUseCase -import com.moneymong.moneymong.domain.usecase.user.GetMyInfoUseCase +import com.moneymong.moneymong.domain.usecase.user.FetchMyInfoUseCase import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.member.AgencyUser import com.moneymong.moneymong.model.member.MemberBlockRequest @@ -26,7 +26,7 @@ class MemberViewModel @Inject constructor( private val memberInvitationCodeUseCase: MemberInvitationCodeUseCase, private val memberReInvitationCodeUseCase: MemberReInvitationCodeUseCase, private val memberListUseCase: MemberListUseCase, - private val getMyInfoUseCase: GetMyInfoUseCase, + private val fetchMyInfoUseCase: FetchMyInfoUseCase, private val updateMemberAuthorUseCase: UpdateMemberAuthorUseCase, private val memberBlockUseCase: MemberBlockUseCase, private val fetchAgencyIdUseCase: FetchAgencyIdUseCase, @@ -224,7 +224,7 @@ class MemberViewModel @Inject constructor( } fun getMyInfo() = intent { - getMyInfoUseCase.invoke() + fetchMyInfoUseCase.invoke() .onSuccess { reduce { state.copy( diff --git a/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongScreen.kt b/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongScreen.kt index 74d1a373..7eecaeb5 100644 --- a/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongScreen.kt +++ b/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongScreen.kt @@ -101,7 +101,6 @@ fun MyMongScreen( name = state.name, email = state.email, university = state.university, - grade = state.grade, getInfo = viewModel::getInfo ) Spacer(modifier = Modifier.height(16.dp)) diff --git a/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongState.kt b/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongState.kt index 8973dcb4..5c0e37e1 100644 --- a/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongState.kt +++ b/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongState.kt @@ -6,7 +6,6 @@ data class MyMongState( val name: String = "", val email: String = "", val university: String = "", - val grade: Int = 0, val infoErrorMessage: String = "", val logoutErrorMessage: String = "", val isInfoLoading: Boolean = true, diff --git a/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongViewModel.kt b/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongViewModel.kt index a93db28e..3d10ad2a 100644 --- a/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongViewModel.kt +++ b/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/MyMongViewModel.kt @@ -3,7 +3,7 @@ package com.moneymong.moneymong.feature.mymong.main import com.moneymong.moneymong.common.base.BaseViewModel import com.moneymong.moneymong.common.error.MoneyMongError import com.moneymong.moneymong.domain.usecase.agency.SaveAgencyIdUseCase -import com.moneymong.moneymong.domain.usecase.user.GetMyInfoUseCase +import com.moneymong.moneymong.domain.usecase.user.FetchMyInfoUseCase import com.moneymong.moneymong.domain.usecase.user.LogoutUseCase import com.moneymong.moneymong.domain.usecase.user.SaveDeniedCameraPermissionUseCase import com.moneymong.moneymong.domain.usecase.user.SaveUserIdUseCase @@ -15,7 +15,7 @@ import javax.inject.Inject @HiltViewModel class MyMongViewModel @Inject constructor( - private val getMyInfoUseCase: GetMyInfoUseCase, + private val fetchMyInfoUseCase: FetchMyInfoUseCase, private val logoutUseCase: LogoutUseCase, private val saveAgencyIdUseCase: SaveAgencyIdUseCase, private val saveUserIdUseCase: SaveUserIdUseCase, @@ -42,7 +42,7 @@ class MyMongViewModel @Inject constructor( isInfoError = false, ) } - getMyInfoUseCase() + fetchMyInfoUseCase() .also { reduce { state.copy(isInfoLoading = false) @@ -53,7 +53,6 @@ class MyMongViewModel @Inject constructor( name = it.name, email = it.email, university = it.university.orEmpty(), - grade = it.grade ) } }.onFailure { diff --git a/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/view/MyMongInfoView.kt b/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/view/MyMongInfoView.kt index e0e4e7c2..01721d10 100644 --- a/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/view/MyMongInfoView.kt +++ b/feature/mymong/src/main/java/com/moneymong/moneymong/feature/mymong/main/view/MyMongInfoView.kt @@ -43,7 +43,6 @@ internal fun MyMongInfoView( name: String, email: String, university: String, - grade: Int, getInfo: () -> Unit ) { Box( @@ -66,10 +65,7 @@ internal fun MyMongInfoView( email = email ) Spacer(modifier = Modifier.height(20.dp)) - UniversityInfo( - university = university, - grade = grade - ) + UniversityInfo(university = university) } } } @@ -123,14 +119,10 @@ private fun Profile( @Composable fun UniversityInfo( - university: String, - grade: Int + university: String ) { - val universityInfoText = when { - university.isEmpty() -> "정보 없음" - grade == 5 -> "$university ${grade}학년 이상" - else -> "$university ${grade}학년" - } + + val universityInfoText = university.ifEmpty { "정보 없음" } Box( modifier = Modifier diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/SignUpScreen.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/SignUpScreen.kt index b3e98d26..c6b97c11 100644 --- a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/SignUpScreen.kt +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/SignUpScreen.kt @@ -1,47 +1,65 @@ package com.moneymong.moneymong.feature.sign +import android.annotation.SuppressLint import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility 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.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.size +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.moneymong.moneymong.common.ui.noRippleClickable import com.moneymong.moneymong.design_system.R +import com.moneymong.moneymong.design_system.component.selection.MDSSelection +import com.moneymong.moneymong.design_system.component.textfield.MDSTextField +import com.moneymong.moneymong.design_system.component.textfield.util.MDSTextFieldIcons import com.moneymong.moneymong.design_system.error.ErrorDialog import com.moneymong.moneymong.design_system.error.ErrorScreen +import com.moneymong.moneymong.design_system.theme.Blue04 +import com.moneymong.moneymong.design_system.theme.Body2 +import com.moneymong.moneymong.design_system.theme.Body3 import com.moneymong.moneymong.design_system.theme.Gray07 import com.moneymong.moneymong.design_system.theme.MMHorizontalSpacing import com.moneymong.moneymong.design_system.theme.White import com.moneymong.moneymong.feature.sign.sideeffect.SignUpSideEffect import com.moneymong.moneymong.feature.sign.state.SignUpState -import com.moneymong.moneymong.feature.sign.view.SearchUnivView -import com.moneymong.moneymong.feature.sign.view.SignCompleteCheckedView +import com.moneymong.moneymong.feature.sign.util.AgencyType import com.moneymong.moneymong.feature.sign.view.SignUpButtonView -import com.moneymong.moneymong.feature.sign.view.SignUpGradeView import com.moneymong.moneymong.feature.sign.view.SignUpTitleView import com.moneymong.moneymong.feature.sign.viewmodel.SignUpViewModel import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect +@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable fun SignUpScreen( - navigateToSignComplete: () -> Unit, + navigateToLedger: () -> Unit, + navigateToSignUpUniversity: (String, AgencyType?) -> Unit, + navigateToAgency: () -> Unit, navigateUp: () -> Unit, viewModel: SignUpViewModel = hiltViewModel() ) { @@ -51,7 +69,7 @@ fun SignUpScreen( navigateUp() } - if(state.visibleError){ + if (state.visibleError) { ErrorScreen( modifier = Modifier.fillMaxSize(), message = state.errorMessage, @@ -59,27 +77,25 @@ fun SignUpScreen( viewModel.visibleErrorChanged(false) } ) - } - else if(state.visiblePopUpError){ + } else if (state.visiblePopUpError) { ErrorDialog( message = state.popUpErrorMessage, onConfirm = { viewModel.visiblePopUpErrorChanged(false) } ) - } - else{ + } else { Scaffold( modifier = Modifier .fillMaxSize() - .background(White) - .padding(horizontal = MMHorizontalSpacing), + .background(White), topBar = { Row( modifier = Modifier .fillMaxWidth() .height(44.dp) - .background(White), + .background(White) + .padding(horizontal = MMHorizontalSpacing), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start ) { @@ -96,10 +112,12 @@ fun SignUpScreen( ) } }, - content = { innerPadding -> + content = { paddingValues -> SignUpContent( - modifier = Modifier.padding(innerPadding), - navigateToSignComplete = navigateToSignComplete, + modifier = Modifier.padding(paddingValues), + navigateToLedger = navigateToLedger, + navigateToSignUpUniversity = navigateToSignUpUniversity, + navigateToAgency = navigateToAgency, viewModel = viewModel, state = state ) @@ -111,24 +129,38 @@ fun SignUpScreen( @Composable fun SignUpContent( - modifier: Modifier = Modifier, - navigateToSignComplete: () -> Unit, + modifier: Modifier, + navigateToLedger: () -> Unit, + navigateToSignUpUniversity: (String, AgencyType?) -> Unit, + navigateToAgency: () -> Unit, viewModel: SignUpViewModel, state: SignUpState ) { + val focusManager = LocalFocusManager.current + LaunchedEffect(key1 = state.isUnivCreated) { if (state.isUnivCreated) { - navigateToSignComplete() + if (state.isInvited) { + navigateToAgency() + } else { + viewModel.registerAgency() + } + } + } + + LaunchedEffect(key1 = state.isAgencyCreated) { + if (state.isAgencyCreated) { + navigateToLedger() } } + LaunchedEffect(key1 = state.isUnivCreated) { + + } + viewModel.collectSideEffect { when (it) { - is SignUpSideEffect.UniversitiesApi -> { - viewModel.searchUniv(it.univ) - } - is SignUpSideEffect.CreateUniversityApi -> { viewModel.createUniv(it.universityName, it.grade) } @@ -141,107 +173,164 @@ fun SignUpContent( .background(color = White) ) { Column( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .padding(horizontal = MMHorizontalSpacing), horizontalAlignment = Alignment.Start ) { SignUpTitleView( modifier = Modifier .fillMaxWidth() - .padding(top = 8.dp), - subTitleState = state.subTitleState + .padding(vertical = 12.dp), ) - Box( + Column( modifier = Modifier - .padding(top = 40.dp) + .padding(vertical = 28.dp) +// .padding(top = 28.dp, end = 28.dp) .fillMaxWidth() ) { - if (!state.isSelected) { - SearchUnivView( - isFilled = state.isFilled, - isFilledChanged = { isFilled -> viewModel.isFilledChanged(isFilled) }, - isListVisible = state.isListVisible, - isListVisibleChanged = { isListVisible -> - viewModel.isListVisibleChanged( - isListVisible - ) - }, - isItemSelectedChanged = { isItemSelected -> - viewModel.isItemSelectedChanged( - isItemSelected - ) - }, - isItemSelected = state.isItemSelected, - textValue = state.textValue, - universityResponse = state.universityResponse, - onClick = { - viewModel.isSelectedChanged(true) - viewModel.selectedUnivChanged(it) - }, - onChange = { viewModel.textValueChanged(it) }, - onSearchIconClicked = { - viewModel.eventEmit(SignUpSideEffect.UniversitiesApi(it)) - }, - value = state.textValue, - isButtonVisibleChanged = { isButtonVisible -> viewModel.isButtonVisibleChanged(isButtonVisible)} + Text( + text = "소속 유형", + style = Body2, + color = Color.Black + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + MDSSelection( + modifier = Modifier.weight(1f), + text = "기타 모임", + enabled = true, + isSelected = state.agencyType == AgencyType.GENERAL, + onClick = { viewModel.onChangeAgencyType(AgencyType.GENERAL) } + ) + MDSSelection( + modifier = Modifier.weight(1f), + text = "동아리", + enabled = true, + isSelected = state.agencyType == AgencyType.CLUB, + onClick = { viewModel.onChangeAgencyType(AgencyType.CLUB) } + ) + MDSSelection( + modifier = Modifier.weight(1f), + text = "학생회", + enabled = true, + isSelected = state.agencyType == AgencyType.STUDENT_COUNCIL, + onClick = { viewModel.onChangeAgencyType(AgencyType.STUDENT_COUNCIL) } ) - } else { - Column(modifier = Modifier.fillMaxSize()) { - SignCompleteCheckedView( - modifier = Modifier.fillMaxWidth(), - text = state.selectedUniv, - onChanged = { - viewModel.isSelectedChanged(false) + } + } + AnimatedVisibility(visible = state.MDSSelected) { + Column { + Text( + text = "소속 이름", + style = Body2, + color = Blue04 + ) + MDSTextField( + modifier = Modifier + .fillMaxWidth() + .onFocusChanged { focusState -> + if (focusState.isFocused) { + viewModel.updateEdittextFocused(true) + viewModel.changeButtonCornerShape(0.dp) + } else { + viewModel.updateEdittextFocused(false) + } }, - onSelectedGrade = { viewModel.selectedGradeChange(null) }, - onItemSelectedChanged = { viewModel.isItemSelectedChanged(it) }, - isEnableChanged = { viewModel.isEnabledChanged(it) } - ) - SignUpGradeView( - modifier = Modifier.fillMaxWidth(), - selectedGrade = state.selectedGrade, - selectedGradeChange = {selectedGrade -> viewModel.selectedGradeChange(selectedGrade)}, - onClick = { viewModel.isEnabledChanged(true) }, - changeGradeInfor = { viewModel.gradeInforChanged(it) } - ) - } + value = state.agencyName, + onValueChange = viewModel::updateAgencyName, + title = "", + isFilled = false, + singleLine = true, + maxCount = 50, + placeholder = "", + icon = MDSTextFieldIcons.Clear, + onIconClick = { viewModel.updateAgencyName(TextFieldValue()) }, + keyboardActions = KeyboardActions( + onDone = { + focusManager.clearFocus() + viewModel.changeButtonCornerShape(10.dp) + } + ), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) + ) } } } - Box( + Column( modifier = Modifier .fillMaxWidth() .align(Alignment.BottomCenter) ) { - if(state.isButtonVisible){ - SignUpButtonView( - modifier = Modifier.fillMaxWidth(), - isEnabled = state.isEnabled, - visiblePopUpError = state.visiblePopUpError, - popUpErrorMessage = state.popUpErrorMessage, - visiblePopUpErrorChanged = { visiblePopUpError -> - viewModel.visiblePopUpErrorChanged(visiblePopUpError) - }, - onCreateUniversity = { - viewModel.eventEmit( - SignUpSideEffect.CreateUniversityApi( - state.selectedUniv, - state.gradeInfor - ) + SignUpButtonView( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = if (state.editTextFocused) 0.dp else MMHorizontalSpacing), + isEnabled = state.isButtonVisible, + visiblePopUpError = state.visiblePopUpError, + popUpErrorMessage = state.popUpErrorMessage, + visiblePopUpErrorChanged = { visiblePopUpError -> + viewModel.visiblePopUpErrorChanged(visiblePopUpError) + }, + onCreateUniversity = { + viewModel.eventEmit( + SignUpSideEffect.CreateUniversityApi( + state.selectedUniv, + state.gradeInfor ) - }, + ) + }, + navigateToSignUpUniversity = { agencyName, agencyType -> + navigateToSignUpUniversity(agencyName, agencyType) + }, + agencyName = state.agencyName.text, + agencyType = state.agencyType, + pageType = 1, + cornerShape = state.buttonCornerShape + ) + + if (!state.editTextFocused) { + Spacer(modifier = Modifier.height(60.dp)) + } + + } + + if (!state.editTextFocused) { + Column( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + ) { + Spacer(modifier = Modifier.height(16.dp)) + + Text( + modifier = Modifier + .fillMaxWidth() + .noRippleClickable { + viewModel.eventEmit( + SignUpSideEffect.CreateUniversityApi( + state.selectedUniv, + state.gradeInfor + ) + ) + viewModel.changeInvitedType(true) +// navigateToAgency() + }, + textAlign = TextAlign.Center, + text = "총무에게 초대받았어요", + style = Body3, + color = Blue04 ) + + Spacer(modifier = Modifier.height(24.dp)) + } } } } - -@Preview -@Composable -fun Preview(){ - SignUpScreen( - navigateUp = {}, - navigateToSignComplete = {} - ) -} \ No newline at end of file diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/item/UnivItem.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/item/UnivItem.kt index 80dfb41a..2a59d3b6 100644 --- a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/item/UnivItem.kt +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/item/UnivItem.kt @@ -29,14 +29,15 @@ fun UnivItem( isItemSelectedChanged: (Boolean) -> Unit, univs: University, onClick: (String) -> Unit, - isButtonVisibleChanged : (Boolean) -> Unit + isButtonVisibleChanged : (Boolean) -> Unit, + selectedUniv : String ) { Row( modifier = Modifier .background(White) .fillMaxWidth() .noRippleClickable { - isItemSelectedChanged(!isItemSelected) + isItemSelectedChanged(true) onClick(univs.schoolName) isButtonVisibleChanged(true) } @@ -50,7 +51,7 @@ fun UnivItem( ) Text( text = univs.schoolName, - color = if (isItemSelected) Blue04 else Black, + color = if (selectedUniv == univs.schoolName) Blue04 else Black, style = Body4, modifier = Modifier .weight(1f) @@ -62,7 +63,7 @@ fun UnivItem( painter = painterResource(id = R.drawable.ic_check), contentDescription = null, modifier = Modifier.size(24.dp), - tint = if (isItemSelected) Blue04 else Gray03 + tint = if (selectedUniv == univs.schoolName) Blue04 else Gray03 ) } } \ No newline at end of file diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/navigation/SignUpNavigation.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/navigation/SignUpNavigation.kt index 82bf301a..f7fed170 100644 --- a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/navigation/SignUpNavigation.kt +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/navigation/SignUpNavigation.kt @@ -5,6 +5,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.moneymong.moneymong.feature.sign.SignUpScreen +import com.moneymong.moneymong.feature.sign.util.AgencyType const val signUpRoute = "signup_route" @@ -13,12 +14,16 @@ fun NavController.navigateSignUp(navOptions: NavOptions? = null) { } fun NavGraphBuilder.signUpScreen( - navigateToSignComplete: () -> Unit, + navigateToLedger: () -> Unit, + navigateToSignUniversity : (String, AgencyType?) -> Unit, + navigateToAgency : () -> Unit, navigateUp: () -> Unit ) { composable(route = signUpRoute) { SignUpScreen( - navigateToSignComplete = navigateToSignComplete, + navigateToLedger = navigateToLedger, + navigateToSignUpUniversity = navigateToSignUniversity, + navigateToAgency = navigateToAgency, navigateUp = navigateUp ) } diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/navigation/SignUpUniversityNavigation.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/navigation/SignUpUniversityNavigation.kt new file mode 100644 index 00000000..65f8df38 --- /dev/null +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/navigation/SignUpUniversityNavigation.kt @@ -0,0 +1,35 @@ +package com.moneymong.moneymong.feature.sign.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.moneymong.moneymong.feature.sign.util.AgencyType +import com.moneymong.moneymong.feature.sign.view.SignUpUniversity + +const val signUpUniversityRoute = "signup_university_route" + +fun NavController.navigateSignUpUniversity(agencyName: String, agencyType: AgencyType?, navOptions: NavOptions? = null) { + val routeWithParams = "signup_university_route?agencyName=$agencyName&agencyType=${agencyType?.name}" + navigate(route = routeWithParams, navOptions = navOptions) +} + +fun NavGraphBuilder.signUpUniversity( + navigateToLedger: () -> Unit, + navigateToAgency : () -> Unit, + navigateUp: () -> Unit, +) { + + composable(route = "$signUpUniversityRoute?agencyName={agencyName}&agencyType={agencyType}") { navBackStackEntry -> + val agencyName = navBackStackEntry.arguments?.getString("agencyName") ?: "" + val agencyType = navBackStackEntry.arguments?.getString("agencyType")?.let { AgencyType.valueOf(it) }.run { AgencyType.CLUB } + + SignUpUniversity( + navigateToLedger = navigateToLedger, + navigateToAgency = navigateToAgency, + navigateUp = navigateUp, + agencyName = agencyName, + agencyType = agencyType + ) + } +} diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/sideeffect/SignUpSideEffect.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/sideeffect/SignUpSideEffect.kt index e553a26e..30ef0232 100644 --- a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/sideeffect/SignUpSideEffect.kt +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/sideeffect/SignUpSideEffect.kt @@ -3,7 +3,6 @@ package com.moneymong.moneymong.feature.sign.sideeffect import com.moneymong.moneymong.common.base.SideEffect sealed class SignUpSideEffect : SideEffect { - data class UniversitiesApi(val univ: String) : SignUpSideEffect() data class CreateUniversityApi( val universityName : String, val grade: Int diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/sideeffect/SignUpUniversitySideEffect.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/sideeffect/SignUpUniversitySideEffect.kt new file mode 100644 index 00000000..4de12bf2 --- /dev/null +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/sideeffect/SignUpUniversitySideEffect.kt @@ -0,0 +1,6 @@ +package com.moneymong.moneymong.feature.sign.sideeffect + +import com.moneymong.moneymong.common.base.SideEffect + +sealed class SignUpUniversitySideEffect : SideEffect { +} \ No newline at end of file diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/state/SignUpState.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/state/SignUpState.kt index d2dd3200..1145e682 100644 --- a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/state/SignUpState.kt +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/state/SignUpState.kt @@ -1,32 +1,42 @@ package com.moneymong.moneymong.feature.sign.state import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import com.moneymong.moneymong.common.base.State +import com.moneymong.moneymong.feature.sign.util.AgencyType import com.moneymong.moneymong.feature.sign.util.Grade import com.moneymong.moneymong.model.sign.UniversitiesResponse data class SignUpState( //screen - val isSelected: Boolean = false, + val isSelected: Boolean? = null, val selectedUniv: String = "", val textValue: TextFieldValue = TextFieldValue(), val isEnabled: Boolean = false, val subTitleState: Boolean = false, val gradeInfor: Int = 0, + val isInvited : Boolean = false, + val buttonCornerShape : Dp = 10.dp, //view val isListVisible: Boolean = false, val isFilled: Boolean = false, val universityResponse: UniversitiesResponse? = null, val isUnivCreated : Boolean = false, + val isAgencyCreated : Boolean = false, + val editTextFocused : Boolean = false, + val MDSSelected : Boolean = false, //item val isItemSelected : Boolean = false, val selectedGrade : Grade? = null, + val agencyType : AgencyType? = null, + val agencyName : TextFieldValue = TextFieldValue(), //error val visibleError : Boolean = false, val errorMessage : String = "", val visiblePopUpError : Boolean = false, val popUpErrorMessage : String = "", - val isButtonVisible : Boolean = true, + val isButtonVisible : Boolean = false, ) : State \ No newline at end of file diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/state/SignUpUniversityState.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/state/SignUpUniversityState.kt new file mode 100644 index 00000000..d5f2053c --- /dev/null +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/state/SignUpUniversityState.kt @@ -0,0 +1,35 @@ +package com.moneymong.moneymong.feature.sign.state + +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.moneymong.moneymong.common.base.State +import com.moneymong.moneymong.feature.sign.util.AgencyType +import com.moneymong.moneymong.model.sign.UniversitiesResponse + +data class SignUpUniversityState ( + //screen + val isSelected: Boolean? = null, + val selectedUniv: String = "", + val textValue: TextFieldValue = TextFieldValue(), + val isEnabled: Boolean = false, + val gradeInfor: Int = 0, + val buttonCornerShape : Dp = 10.dp, + val editTextFocused : Boolean = false, + //view + val isListVisible: Boolean = false, + val isFilled: Boolean = false, + val universityResponse: UniversitiesResponse? = null, + //item + val isItemSelected : Boolean = false, + val agencyType : AgencyType? = null, + //error + val visibleError : Boolean = false, + val errorMessage : String = "", + val visiblePopUpError : Boolean = false, + val popUpErrorMessage : String = "", + val isButtonVisible : Boolean = true, + + val isUnivCreated : Boolean = false, + val isAgencyCreated : Boolean = false, +): State \ No newline at end of file diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/util/AgencyType.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/util/AgencyType.kt new file mode 100644 index 00000000..78b9ae70 --- /dev/null +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/util/AgencyType.kt @@ -0,0 +1,8 @@ +package com.moneymong.moneymong.feature.sign.util + +enum class AgencyType(val text: String) { + GENERAL("GENERAL"), + CLUB("IN_SCHOOL_CLUB"), + STUDENT_COUNCIL("STUDENT_COUNCIL"), + NONE("NONE") +} diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SearchUnivView.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SearchUnivView.kt index f08c941c..95f56a6e 100644 --- a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SearchUnivView.kt +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SearchUnivView.kt @@ -13,26 +13,26 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.moneymong.moneymong.design_system.component.textfield.MDSTextField import com.moneymong.moneymong.design_system.component.textfield.util.MDSTextFieldIcons import com.moneymong.moneymong.design_system.theme.Body4 import com.moneymong.moneymong.design_system.theme.Gray05 import com.moneymong.moneymong.design_system.theme.White -import com.moneymong.moneymong.model.sign.University import com.moneymong.moneymong.feature.sign.item.UnivItem import com.moneymong.moneymong.model.sign.UniversitiesResponse +import com.moneymong.moneymong.model.sign.University import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.debounce -@OptIn(ExperimentalComposeUiApi::class, FlowPreview::class) +@OptIn(FlowPreview::class) @Composable fun SearchUnivView( modifier: Modifier = Modifier, @@ -49,9 +49,12 @@ fun SearchUnivView( universityResponse: UniversitiesResponse?, value: TextFieldValue, isButtonVisibleChanged: (Boolean) -> Unit, + selectedUniv: String, + changeButtonCornerShape: (Dp) -> Unit, + changeEditTextFocus: (Boolean) -> Unit ) { - val keyboardController = LocalSoftwareKeyboardController.current + val focusManager = LocalFocusManager.current val debouncePeriod = 300L val queryState = remember { MutableStateFlow("") } @@ -61,10 +64,9 @@ fun SearchUnivView( .debounce(debouncePeriod) .collect { query -> Log.d("query", query) - if(query.isEmpty() && value.text.isNotEmpty()){ + if (query.isEmpty() && value.text.isNotEmpty()) { onSearchIconClicked(value.text) - } - else{ + } else { onSearchIconClicked(query) } isFilledChanged(false) @@ -79,8 +81,10 @@ fun SearchUnivView( modifier = Modifier .fillMaxWidth() .onFocusChanged { focusState -> + changeEditTextFocus(focusState.isFocused) if (focusState.isFocused) { isButtonVisibleChanged(false) + changeButtonCornerShape(0.dp) } }, value = value, @@ -100,7 +104,7 @@ fun SearchUnivView( if (value.text.isEmpty()) { isListVisibleChanged(false) } else { - onSearchIconClicked(textValue.toString()) + onSearchIconClicked(textValue.text) isFilledChanged(true) isListVisibleChanged(true) } @@ -109,7 +113,9 @@ fun SearchUnivView( keyboardActions = KeyboardActions( onDone = { isFilledChanged(true) - keyboardController?.hide() + changeButtonCornerShape(10.dp) + focusManager.clearFocus() + changeEditTextFocus(false) } ) ) @@ -122,7 +128,8 @@ fun SearchUnivView( isItemSelectedChanged = isItemSelectedChanged, univs = universityResponse.universities, onClick = onClick, - isButtonVisibleChanged = isButtonVisibleChanged + isButtonVisibleChanged = isButtonVisibleChanged, + selectedUniv = selectedUniv ) } else { Column( @@ -148,7 +155,8 @@ fun UnivList( isItemSelectedChanged: (Boolean) -> Unit, univs: List, onClick: (String) -> Unit, - isButtonVisibleChanged: (Boolean) -> Unit + isButtonVisibleChanged: (Boolean) -> Unit, + selectedUniv: String ) { LazyColumn { items(univs) { univ -> @@ -157,7 +165,8 @@ fun UnivList( isItemSelectedChanged = isItemSelectedChanged, univs = univ, onClick = onClick, - isButtonVisibleChanged = isButtonVisibleChanged + isButtonVisibleChanged = isButtonVisibleChanged, + selectedUniv = selectedUniv ) } } diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpButtonView.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpButtonView.kt index 6b83058b..b53eba20 100644 --- a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpButtonView.kt +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpButtonView.kt @@ -1,21 +1,16 @@ package com.moneymong.moneymong.feature.sign.view import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.moneymong.moneymong.common.ui.noRippleClickable import com.moneymong.moneymong.design_system.component.button.MDSButton import com.moneymong.moneymong.design_system.component.button.MDSButtonSize import com.moneymong.moneymong.design_system.component.button.MDSButtonType import com.moneymong.moneymong.design_system.error.ErrorDialog -import com.moneymong.moneymong.design_system.theme.Blue04 -import com.moneymong.moneymong.design_system.theme.Body3 +import com.moneymong.moneymong.feature.sign.util.AgencyType @Composable fun SignUpButtonView( @@ -25,6 +20,11 @@ fun SignUpButtonView( popUpErrorMessage: String, visiblePopUpErrorChanged: (Boolean) -> Unit, onCreateUniversity: () -> Unit, + navigateToSignUpUniversity: (String, AgencyType?) -> Unit, + agencyName: String, + agencyType: AgencyType?, + pageType: Int, + cornerShape: Dp = 10.dp ) { if (visiblePopUpError) { ErrorDialog( @@ -39,29 +39,19 @@ fun SignUpButtonView( ) { MDSButton( modifier = Modifier.fillMaxWidth(), +// .height(56.dp), onClick = { - onCreateUniversity() + if (agencyType == AgencyType.GENERAL || pageType == 2) onCreateUniversity() else if (agencyType != AgencyType.GENERAL && pageType == 1) navigateToSignUpUniversity( + agencyName, + agencyType + ) }, - text = "가입하기", + text = if (agencyType == AgencyType.GENERAL || pageType == 2) "등록하기" else "다음으로", type = MDSButtonType.PRIMARY, size = MDSButtonSize.LARGE, - enabled = isEnabled + enabled = isEnabled, + cornerShape = cornerShape ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - modifier = Modifier - .fillMaxWidth() - .noRippleClickable { - onCreateUniversity() - }, - textAlign = TextAlign.Center, - text = "입력할 대학 정보가 없어요", - color = Blue04, - style = Body3 - ) - - Spacer(modifier = Modifier.height(28.dp)) - } } diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpTitleView.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpTitleView.kt index dad5d7e5..476e3f85 100644 --- a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpTitleView.kt +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpTitleView.kt @@ -15,23 +15,16 @@ import com.moneymong.moneymong.design_system.theme.Heading2 import com.moneymong.moneymong.design_system.theme.White @Composable -fun SignUpTitleView(modifier: Modifier = Modifier, subTitleState: Boolean) { +fun SignUpTitleView(modifier: Modifier = Modifier, /*subTitleState: Boolean*/) { Column( modifier = modifier.background(White), horizontalAlignment = Alignment.Start ) { Text( - text = "대학정보를 알려주세요!", + text = "회비관리가 필요한\n소속정보를 알려주세요!", style = Heading2, color = Black ) - Text( - modifier = Modifier.padding(top= 8.dp), - text = "학교 이름과 학년을 선택해주세요.", - style = Body3, - color = if (!subTitleState) Gray06.copy(alpha = 0.4f) else Gray06 - - ) } } \ No newline at end of file diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpUniversity.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpUniversity.kt new file mode 100644 index 00000000..3e6ea7c9 --- /dev/null +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/view/SignUpUniversity.kt @@ -0,0 +1,252 @@ +package com.moneymong.moneymong.feature.sign.view + +import android.annotation.SuppressLint +import androidx.activity.compose.BackHandler +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.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.size +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.moneymong.moneymong.common.ui.noRippleClickable +import com.moneymong.moneymong.design_system.R +import com.moneymong.moneymong.design_system.error.ErrorDialog +import com.moneymong.moneymong.design_system.error.ErrorScreen +import com.moneymong.moneymong.design_system.theme.Black +import com.moneymong.moneymong.design_system.theme.Blue04 +import com.moneymong.moneymong.design_system.theme.Body3 +import com.moneymong.moneymong.design_system.theme.Gray06 +import com.moneymong.moneymong.design_system.theme.Gray07 +import com.moneymong.moneymong.design_system.theme.Heading2 +import com.moneymong.moneymong.design_system.theme.MMHorizontalSpacing +import com.moneymong.moneymong.design_system.theme.White +import com.moneymong.moneymong.feature.sign.state.SignUpUniversityState +import com.moneymong.moneymong.feature.sign.util.AgencyType +import com.moneymong.moneymong.feature.sign.viewmodel.SignUpUniversityViewModel +import org.orbitmvi.orbit.compose.collectAsState + +@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") +@Composable +fun SignUpUniversity( + navigateToLedger: () -> Unit, + navigateToAgency: () -> Unit, + navigateUp: () -> Unit, + agencyName: String, + agencyType: AgencyType, + viewModel: SignUpUniversityViewModel = hiltViewModel() +) { + val state = viewModel.collectAsState().value + + BackHandler { + navigateUp() + } + + if (state.visibleError) { + ErrorScreen( + modifier = Modifier.fillMaxSize(), + message = state.errorMessage, + onRetry = { + viewModel.visibleErrorChanged(false) + } + ) + } else if (state.visiblePopUpError) { + ErrorDialog( + message = state.popUpErrorMessage, + onConfirm = { + viewModel.visiblePopUpErrorChanged(false) + } + ) + } else { + Scaffold( + modifier = Modifier + .fillMaxSize() + .background(White), + topBar = { + Row( + modifier = Modifier + .fillMaxWidth() + .height(44.dp) + .background(White) + .padding(horizontal = MMHorizontalSpacing), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start + ) { + Icon( + painter = painterResource(id = R.drawable.ic_chevron_left), + contentDescription = null, + modifier = Modifier + .size(24.dp) + .background(White) + .noRippleClickable { + navigateUp() + }, + tint = Gray07 + ) + } + }, + content = { paddingValues -> + SignUpUniversityContent( + modifier = Modifier.padding(paddingValues), + navigateToLedger = navigateToLedger, + agencyName = agencyName, + agencyType = agencyType, + viewModel = viewModel, + state = state + ) + } + ) + } +} + +@Composable +fun SignUpUniversityContent( + modifier: Modifier, + navigateToLedger: () -> Unit, + agencyName: String, + agencyType: AgencyType, + viewModel: SignUpUniversityViewModel, + state: SignUpUniversityState + +) { + + LaunchedEffect(key1 = state.isUnivCreated) { + if (state.isUnivCreated) { + viewModel.registerAgency(agencyName, agencyType) + } + } + + LaunchedEffect(key1 = state.isAgencyCreated) { + if (state.isAgencyCreated) { + navigateToLedger() + } + } + + Box(modifier = Modifier.fillMaxSize()) { + + Column( + modifier = modifier + .background(White) + .padding(horizontal = MMHorizontalSpacing), + ) { + Text( + modifier = Modifier.padding(top = 12.dp, bottom = 8.dp), + text = "어디 학교 교내 동아리인가요?", + style = Heading2, + color = Black + ) + Text( + modifier = Modifier.padding(bottom = 12.dp), + text = "소속 대학교를 알려주세요", + style = Body3, + color = Gray06 + ) + + SearchUnivView( + modifier = Modifier + .fillMaxWidth() + .padding(top = 28.dp, bottom = 28.dp) + .weight(1.0F), + isFilled = state.isFilled, + isFilledChanged = { isFilled -> viewModel.isFilledChanged(isFilled) }, + isListVisible = state.isListVisible, + isListVisibleChanged = { isListVisible -> + viewModel.isListVisibleChanged( + isListVisible + ) + }, + isItemSelectedChanged = { isItemSelected -> + viewModel.isItemSelectedChanged( + isItemSelected + ) + }, + isItemSelected = state.isItemSelected, + textValue = state.textValue, + universityResponse = state.universityResponse, + onClick = { + viewModel.isSelectedChanged(true) + viewModel.selectedUnivChanged(it) + }, + onChange = { viewModel.textValueChanged(it) }, + onSearchIconClicked = { + viewModel.searchUniv(it) + }, + value = state.textValue, + isButtonVisibleChanged = { isButtonVisible -> + viewModel.isButtonVisibleChanged( + isButtonVisible + ) + }, + selectedUniv = state.selectedUniv, + changeButtonCornerShape = { cornerShape -> + viewModel.changeButtonCornerShape( + cornerShape + ) + }, + changeEditTextFocus = { editTextFocused -> + viewModel.changeEditTextFocus( + editTextFocused + ) + } + ) + + } + + Column( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter), + verticalArrangement = Arrangement.Bottom + ) { + SignUpButtonView( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = if (state.editTextFocused) 0.dp else MMHorizontalSpacing), + isEnabled = state.isItemSelected, + visiblePopUpError = state.visiblePopUpError, + popUpErrorMessage = state.popUpErrorMessage, + visiblePopUpErrorChanged = { visiblePopUpError -> + viewModel.visiblePopUpErrorChanged(visiblePopUpError) + }, + onCreateUniversity = { + viewModel.createUniv(state.selectedUniv, state.gradeInfor) + }, + navigateToSignUpUniversity = { agencyName, agencyType -> }, + agencyName = agencyName, + agencyType = agencyType, + pageType = 2, + cornerShape = state.buttonCornerShape + ) + if (!state.editTextFocused) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + modifier = Modifier + .fillMaxWidth() + .noRippleClickable { + viewModel.createUniv(state.selectedUniv, state.gradeInfor) + }, + textAlign = TextAlign.Center, + text = "총무에게 초대받았어요", + style = Body3, + color = Blue04 + ) + Spacer(modifier = Modifier.height(24.dp)) + } + } + } +} \ No newline at end of file diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/viewmodel/SignUpUniversityViewModel.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/viewmodel/SignUpUniversityViewModel.kt new file mode 100644 index 00000000..fe034780 --- /dev/null +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/viewmodel/SignUpUniversityViewModel.kt @@ -0,0 +1,188 @@ +package com.moneymong.moneymong.feature.sign.viewmodel + +import android.view.View +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.Dp +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.moneymong.moneymong.common.base.BaseViewModel +import com.moneymong.moneymong.domain.usecase.agency.RegisterAgencyUseCase +import com.moneymong.moneymong.domain.usecase.signup.SchoolInfoUseCase +import com.moneymong.moneymong.domain.usecase.university.CreateUniversityUseCase +import com.moneymong.moneymong.domain.usecase.university.SearchUniversityUseCase +import com.moneymong.moneymong.feature.sign.sideeffect.SignUpUniversitySideEffect +import com.moneymong.moneymong.feature.sign.state.SignUpUniversityState +import com.moneymong.moneymong.feature.sign.util.AgencyType +import com.moneymong.moneymong.model.agency.AgencyRegisterRequest +import com.moneymong.moneymong.model.sign.UnivRequest +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import org.orbitmvi.orbit.annotation.OrbitExperimental +import org.orbitmvi.orbit.syntax.simple.blockingIntent +import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.reduce +import javax.inject.Inject + +@HiltViewModel +class SignUpUniversityViewModel @Inject constructor( + private val createUniversityUseCase: CreateUniversityUseCase, + private val registerAgencyUseCase : RegisterAgencyUseCase, + private val schoolInfoUseCase : SchoolInfoUseCase, + private val searchUniversityUseCase: SearchUniversityUseCase +) : BaseViewModel(SignUpUniversityState()){ + + fun searchUniv(searchQuery: String) = intent { + searchUniversityUseCase(searchQuery) + .onSuccess { + reduce { + state.copy( + universityResponse = it + ) + } + }.onFailure { + reduce { + state.copy( + visibleError = true, + errorMessage = it.message.toString() + ) + } + } + } + + + fun createUniv(universityName: String?, grade: Int?) = intent { + val body = UnivRequest(universityName, grade) + createUniversityUseCase(body) + .onSuccess { + storeSchoolInfoProvided(true) + reduce { + state.copy( + isUnivCreated = true + ) + } + } + .onFailure { + reduce { + state.copy( + visiblePopUpError = true, + popUpErrorMessage = it.message.toString() + ) + } + } + } + + fun registerAgency(agencyName: String, agencyType: AgencyType) = intent{ + registerAgencyUseCase(AgencyRegisterRequest(agencyName, agencyType.text)) + .onSuccess { + reduce { + state.copy( + isAgencyCreated = true + ) + } + } + .onFailure { + reduce { + state.copy( + visiblePopUpError = true, + popUpErrorMessage = it.message.toString() + ) + } + } + + } + + private fun storeSchoolInfoProvided(infoExist : Boolean ){ + viewModelScope.launch { + schoolInfoUseCase.invoke(infoExist) + } + } + + fun isSelectedChanged(isSelected: Boolean) = intent { + reduce { + state.copy( + isSelected = isSelected + ) + } + } + + fun selectedUnivChanged(selectedUniv: String) = intent { + reduce { + state.copy( + selectedUniv = selectedUniv + ) + } + } + + @OptIn(OrbitExperimental::class) + fun textValueChanged(textValue: TextFieldValue) = blockingIntent { + reduce { + state.copy( + textValue = textValue + ) + } + } + + fun isFilledChanged(isFilled: Boolean) = intent { + reduce { + state.copy( + isFilled = isFilled + ) + } + } + + fun isListVisibleChanged(isListVisible: Boolean) = intent { + reduce { + state.copy( + isListVisible = isListVisible + ) + } + } + + fun isItemSelectedChanged(isItemSelected: Boolean) = intent { + reduce { + state.copy( + isItemSelected = isItemSelected + ) + } + } + + fun isButtonVisibleChanged(isButtonVisible: Boolean) = intent { + reduce { + state.copy( + isButtonVisible = isButtonVisible + ) + } + } + + fun visiblePopUpErrorChanged(visiblePopUpError: Boolean) = intent { + reduce { + state.copy( + visiblePopUpError = visiblePopUpError, + ) + } + } + + fun visibleErrorChanged(visibleError: Boolean) = intent { + reduce { + state.copy( + visibleError = visibleError, + ) + } + } + + fun changeButtonCornerShape(cornerShape : Dp) = intent{ + reduce{ + state.copy( + buttonCornerShape = cornerShape + ) + } + } + + fun changeEditTextFocus(isFocused : Boolean) = intent { + reduce { + state.copy( + editTextFocused = isFocused + ) + } + } +} \ No newline at end of file diff --git a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/viewmodel/SignUpViewModel.kt b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/viewmodel/SignUpViewModel.kt index 85fe8a43..45f4a460 100644 --- a/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/viewmodel/SignUpViewModel.kt +++ b/feature/sign/src/main/java/com/moneymong/moneymong/feature/sign/viewmodel/SignUpViewModel.kt @@ -1,21 +1,19 @@ package com.moneymong.moneymong.feature.sign.viewmodel import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.Dp import androidx.lifecycle.viewModelScope import com.moneymong.moneymong.common.base.BaseViewModel +import com.moneymong.moneymong.domain.usecase.agency.RegisterAgencyUseCase import com.moneymong.moneymong.domain.usecase.signup.SchoolInfoUseCase import com.moneymong.moneymong.domain.usecase.university.CreateUniversityUseCase -import com.moneymong.moneymong.domain.usecase.university.SearchUniversityUseCase import com.moneymong.moneymong.feature.sign.sideeffect.SignUpSideEffect import com.moneymong.moneymong.feature.sign.state.SignUpState -import com.moneymong.moneymong.feature.sign.util.Grade +import com.moneymong.moneymong.feature.sign.util.AgencyType +import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.sign.UnivRequest import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.orbitmvi.orbit.annotation.OrbitExperimental -import org.orbitmvi.orbit.syntax.simple.blockingIntent import org.orbitmvi.orbit.syntax.simple.intent import org.orbitmvi.orbit.syntax.simple.reduce import javax.inject.Inject @@ -23,10 +21,10 @@ import javax.inject.Inject @HiltViewModel class SignUpViewModel @Inject constructor( private val createUniversityUseCase: CreateUniversityUseCase, - private val searchUniversityUseCase: SearchUniversityUseCase, + private val registerAgencyUseCase: RegisterAgencyUseCase, private val schoolInfoUseCase: SchoolInfoUseCase, ) : BaseViewModel(SignUpState()) { - fun createUniv(universityName: String?, grade: Int?) = intent { + fun createUniv(universityName: String, grade: Int) = intent { val body = UnivRequest(universityName, grade) createUniversityUseCase(body) .onSuccess { @@ -47,135 +45,88 @@ class SignUpViewModel @Inject constructor( } } - fun searchUniv(searchQuery: String) = intent { - searchUniversityUseCase(searchQuery) + fun registerAgency() = intent { + registerAgencyUseCase(AgencyRegisterRequest(state.agencyName.text, AgencyType.GENERAL.text)) .onSuccess { reduce { state.copy( - universityResponse = it + isAgencyCreated = true ) } - }.onFailure { + } + .onFailure { reduce { state.copy( - visibleError = true, - errorMessage = it.message.toString() + visiblePopUpError = true, + popUpErrorMessage = it.message.toString() ) } } - } - fun storeSchoolInfoProvided(infoExist : Boolean ){ - viewModelScope.launch { - schoolInfoUseCase.invoke(infoExist) - } } - fun isSelectedChanged(isSelected: Boolean) = intent { - reduce { - state.copy( - isSelected = isSelected - ) - } - } - fun selectedUnivChanged(selectedUniv: String) = intent { - reduce { - state.copy( - selectedUniv = selectedUniv - ) - } - } - - @OptIn(OrbitExperimental::class) - fun textValueChanged(textValue: TextFieldValue) = blockingIntent { - reduce { - state.copy( - textValue = textValue - ) - } - } - - fun isEnabledChanged(isEnable: Boolean) = intent { - reduce { - state.copy( - isEnabled = isEnable - ) - } - } - - fun subTitleStateChanged(subTitleState: Boolean) = intent { - reduce { - state.copy( - subTitleState = subTitleState - ) - } - } - - fun gradeInforChanged(gradeInfor: Int) = intent { - reduce { - state.copy( - gradeInfor = gradeInfor - ) + private fun storeSchoolInfoProvided(infoExist: Boolean) { + viewModelScope.launch { + schoolInfoUseCase.invoke(infoExist) } } - - fun isFilledChanged(isFilled: Boolean) = intent { + fun visiblePopUpErrorChanged(visiblePopUpError: Boolean) = intent { reduce { state.copy( - isFilled = isFilled + visiblePopUpError = visiblePopUpError, ) } } - fun isListVisibleChanged(isListVisible: Boolean) = intent { + fun visibleErrorChanged(visibleError: Boolean) = intent { reduce { state.copy( - isListVisible = isListVisible + visibleError = visibleError, ) } } - fun isItemSelectedChanged(isItemSelected: Boolean) = intent { + fun onChangeAgencyType(agencyType: AgencyType) = intent { reduce { state.copy( - isItemSelected = isItemSelected + agencyType = agencyType, + MDSSelected = true, ) } } - fun selectedGradeChange(selectedGrade: Grade?) = intent { + fun updateAgencyName(agencyName: TextFieldValue) = intent { reduce { state.copy( - selectedGrade = selectedGrade + agencyName = agencyName, + isButtonVisible = agencyName.text != "" && state.agencyType != null ) } } - fun visiblePopUpErrorChanged(visiblePopUpError: Boolean) = intent { + fun updateEdittextFocused(focusState: Boolean) = intent { reduce { state.copy( - visiblePopUpError = visiblePopUpError, + editTextFocused = focusState ) } } - fun visibleErrorChanged(visibleError: Boolean) = intent { + fun changeInvitedType(invited: Boolean) = intent { reduce { state.copy( - visibleError = visibleError, + isInvited = invited ) } } - - fun isButtonVisibleChanged(isButtonVisible: Boolean) = intent { + fun changeButtonCornerShape(cornerShape: Dp) = intent { reduce { state.copy( - isButtonVisible = isButtonVisible + buttonCornerShape = cornerShape ) } } - } \ No newline at end of file