diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df5237817..841b2f7fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,10 +25,12 @@ jobs: run: | echo $PROD_BASE_URL >> ./local.properties echo $DEV_BASE_URL >> ./local.properties + echo $TERMS_URL >> ./local.properties cat env: PROD_BASE_URL: ${{secrets.PROD_BASE_URL}} DEV_BASE_URL: ${{secrets.DEV_BASE_URL}} + TERMS_URL: ${{secrets.TERMS_URL}} - name: Grant execute permission for gradlew run: chmod +x gradlew diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e5c9ced23..f9ab46fa4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { minSdk = libs.versions.minSdk.get().toInt() targetSdk = libs.versions.targetSdk.get().toInt() - versionCode = 6 - versionName = "v1.2.0" + versionCode = 8 + versionName = "v1.2.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/data/src/main/java/team/aliens/dms/android/data/meal/repository/MealRepository.kt b/data/src/main/java/team/aliens/dms/android/data/meal/repository/MealRepository.kt index ef4d5b188..326f70ef4 100644 --- a/data/src/main/java/team/aliens/dms/android/data/meal/repository/MealRepository.kt +++ b/data/src/main/java/team/aliens/dms/android/data/meal/repository/MealRepository.kt @@ -6,4 +6,6 @@ import team.aliens.dms.android.data.meal.model.Meal abstract class MealRepository { abstract suspend fun fetchMeal(date: LocalDate): Meal + + abstract suspend fun updateMeal(date: LocalDate): Meal } diff --git a/data/src/main/java/team/aliens/dms/android/data/meal/repository/MealRepositoryImpl.kt b/data/src/main/java/team/aliens/dms/android/data/meal/repository/MealRepositoryImpl.kt index 84e854a3c..7fadced42 100644 --- a/data/src/main/java/team/aliens/dms/android/data/meal/repository/MealRepositoryImpl.kt +++ b/data/src/main/java/team/aliens/dms/android/data/meal/repository/MealRepositoryImpl.kt @@ -17,12 +17,16 @@ internal class MealRepositoryImpl @Inject constructor( databaseMealDataSource.queryMeal(date).toModel() } catch (_: Exception) { try { - networkMealDataSource.fetchMeals(date).toModel().also { meals -> - databaseMealDataSource.saveMeals(meals.toEntity()) - } - .find { it.date == date }!! + this.updateMeal(date = date) } catch (_: Exception) { throw CannotFindMealException() } } + + override suspend fun updateMeal(date: LocalDate): Meal { + return networkMealDataSource.fetchMeals(date).toModel().also { meals -> + databaseMealDataSource.saveMeals(meals.toEntity()) + } + .find { it.date == date }!! + } } diff --git a/feature/src/main/java/team/aliens/dms/android/feature/editpassword/2_SetPasswordScreen.kt b/feature/src/main/java/team/aliens/dms/android/feature/editpassword/2_SetPasswordScreen.kt index 66e77e63a..8fdfa04e0 100644 --- a/feature/src/main/java/team/aliens/dms/android/feature/editpassword/2_SetPasswordScreen.kt +++ b/feature/src/main/java/team/aliens/dms/android/feature/editpassword/2_SetPasswordScreen.kt @@ -99,6 +99,10 @@ internal fun EditPasswordSetPasswordScreen( EditPasswordSideEffect.PasswordFormatError -> isPasswordFormatError = true + EditPasswordSideEffect.PasswordEditFailure -> toast.showErrorToast( + message = context.getString(R.string.edit_password_error_password_edit_failure), + ) + else -> {/* explicit blank */ } } diff --git a/feature/src/main/java/team/aliens/dms/android/feature/editpassword/EditPasswordViewModel.kt b/feature/src/main/java/team/aliens/dms/android/feature/editpassword/EditPasswordViewModel.kt index 164b34d50..c6b6da203 100644 --- a/feature/src/main/java/team/aliens/dms/android/feature/editpassword/EditPasswordViewModel.kt +++ b/feature/src/main/java/team/aliens/dms/android/feature/editpassword/EditPasswordViewModel.kt @@ -57,27 +57,30 @@ class EditPasswordViewModel @Inject constructor( ), ) - private fun editPassword() = viewModelScope.launch(Dispatchers.IO) { + private fun editPassword() = run { val capturedState = stateFlow.value - if (capturedState.newPassword != capturedState.newPasswordRepeat) { + val newPassword = capturedState.newPassword + val newPasswordRepeat = capturedState.newPasswordRepeat + + if (newPassword != newPasswordRepeat) { postSideEffect(EditPasswordSideEffect.PasswordMismatch) - return@launch + return@run } - if (!checkIfPasswordValid(capturedState.newPassword)) { + if (!checkIfPasswordValid(newPassword)) { postSideEffect(EditPasswordSideEffect.PasswordFormatError) - return@launch + return@run } - - runCatching { - userRepository.editPassword( - currentPassword = capturedState.currentPassword, - newPassword = capturedState.newPassword, - ) - }.onSuccess { - postSideEffect(EditPasswordSideEffect.PasswordEdited) - }.onFailure { - it.printStackTrace() - postSideEffect(EditPasswordSideEffect.PasswordFormatError) + viewModelScope.launch(Dispatchers.IO) { + runCatching { + userRepository.editPassword( + currentPassword = capturedState.currentPassword, + newPassword = newPassword, + ) + }.onSuccess { + postSideEffect(EditPasswordSideEffect.PasswordEdited) + }.onFailure { + postSideEffect(EditPasswordSideEffect.PasswordEditFailure) + } } } } @@ -111,4 +114,5 @@ sealed class EditPasswordSideEffect : SideEffect() { data object PasswordEdited : EditPasswordSideEffect() data object PasswordFormatError : EditPasswordSideEffect() data object PasswordMismatch : EditPasswordSideEffect() + data object PasswordEditFailure : EditPasswordSideEffect() } diff --git a/feature/src/main/java/team/aliens/dms/android/feature/main/home/HomeScreen.kt b/feature/src/main/java/team/aliens/dms/android/feature/main/home/HomeScreen.kt index 2419a5f57..8fd2133af 100644 --- a/feature/src/main/java/team/aliens/dms/android/feature/main/home/HomeScreen.kt +++ b/feature/src/main/java/team/aliens/dms/android/feature/main/home/HomeScreen.kt @@ -34,6 +34,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshContainer +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect @@ -49,6 +51,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -67,12 +70,13 @@ import team.aliens.dms.android.core.designsystem.DmsCalendar import team.aliens.dms.android.core.designsystem.DmsScaffold import team.aliens.dms.android.core.designsystem.DmsTheme import team.aliens.dms.android.core.designsystem.DmsTopAppBar +import team.aliens.dms.android.core.designsystem.LocalToast import team.aliens.dms.android.core.designsystem.ModalBottomSheet import team.aliens.dms.android.core.designsystem.OutlinedButton import team.aliens.dms.android.core.designsystem.PrimaryDefault import team.aliens.dms.android.core.designsystem.ShadowDefaults +import team.aliens.dms.android.core.designsystem.TextButton import team.aliens.dms.android.core.designsystem.clickable -import team.aliens.dms.android.core.designsystem.rememberToastState import team.aliens.dms.android.core.ui.DefaultHorizontalSpace import team.aliens.dms.android.core.ui.DefaultVerticalSpace import team.aliens.dms.android.core.ui.PaddingDefaults @@ -100,13 +104,29 @@ internal fun HomeScreen( onChangeBottomAppBarVisibility: (visible: Boolean) -> Unit, onNavigateToAnnouncementList: () -> Unit, ) { - val toast = rememberToastState() + val toast = LocalToast.current val context = LocalContext.current - val uiState by viewModel.stateFlow.collectAsStateWithLifecycle() + val pullToRefreshState = rememberPullToRefreshState() + + val onRefresh = remember { + { + pullToRefreshState.startRefresh() + viewModel.postIntent(HomeIntent.UpdateMeal); Unit + } + } viewModel.sideEffectFlow.collectInLaunchedEffectWithLifecycle { sideEffect -> when (sideEffect) { - HomeSideEffect.CannotFindMeal -> toast.showErrorToast(context.getString(R.string.meal_error_not_found)) // FIXME: toast not showing + HomeSideEffect.CannotFindMeal, HomeSideEffect.MealUpdateFailed -> toast.showErrorToast( + context.getString(R.string.meal_error_not_found), + ) + + HomeSideEffect.MealUpdated -> { + pullToRefreshState.endRefresh() + toast.showSuccessToast( + message = context.getString(R.string.home_success_meal_refreshed), + ) + } } } @@ -119,6 +139,12 @@ internal fun HomeScreen( val (shouldShowCalendar, onShouldShowCalendarChange) = remember { mutableStateOf(false) } + LaunchedEffect(pullToRefreshState.isRefreshing) { + if (pullToRefreshState.isRefreshing) { + onRefresh() + } + } + if (shouldShowCalendar) { ModalBottomSheet( onDismissRequest = { @@ -151,6 +177,7 @@ internal fun HomeScreen( ) { padValues -> Column( modifier = Modifier + .nestedScroll(pullToRefreshState.nestedScrollConnection) .animateContentSize() .background(brush = HomeBackgroundBrush) .fillMaxSize() @@ -192,9 +219,18 @@ internal fun HomeScreen( meal = uiState.currentMeal, onNextDay = { onSelectedDateChange(uiState.selectedDate.plusDays(1)) }, onPreviousDay = { onSelectedDateChange(uiState.selectedDate.minusDays(1)) }, + onRefresh = onRefresh, ) Spacer(modifier = Modifier.height(92.dp)) } + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = padValues.calculateTopPadding()), + contentAlignment = Alignment.TopCenter, + ) { + PullToRefreshContainer(state = pullToRefreshState) + } } } @@ -362,6 +398,7 @@ private fun MealCards( meal: Meal, onNextDay: () -> Unit, onPreviousDay: () -> Unit, + onRefresh: () -> Unit, ) { val pagerState = rememberPagerState( initialPage = getProperMeal(), @@ -444,6 +481,7 @@ private fun MealCards( ) } }, + onRefresh = onRefresh, ) } } @@ -479,6 +517,7 @@ private fun MealCard( kcalOfDinner: String?, onSwipeToLeft: () -> Unit, onSwipeToRight: () -> Unit, + onRefresh: () -> Unit, ) { var dragDirection: DragDirection? by remember { mutableStateOf(null) } @@ -519,14 +558,24 @@ private fun MealCard( ), elevation = CardDefaults.outlinedCardElevation(defaultElevation = ShadowDefaults.SmallElevation), ) { + val dishes = when (currentCardType) { + BREAKFAST -> breakfast + LUNCH -> lunch + DINNER -> dinner + } + if (dishes.isEmpty()) { + TextButton( + modifier = Modifier.fillMaxWidth(), + onClick = onRefresh, + colors = ButtonDefaults.textGrayButtonColors(), + ) { + Text(text = stringResource(id = R.string.refresh)) + } + } Dishes( modifier = Modifier.fillMaxWidth(), iconRes = currentCardType.iconRes, - dishes = when (currentCardType) { - BREAKFAST -> breakfast - LUNCH -> lunch - DINNER -> dinner - }, + dishes = dishes, kcal = when (currentCardType) { BREAKFAST -> kcalOfBreakfast LUNCH -> kcalOfLunch diff --git a/feature/src/main/java/team/aliens/dms/android/feature/main/home/HomeViewModel.kt b/feature/src/main/java/team/aliens/dms/android/feature/main/home/HomeViewModel.kt index 6c4771e2b..45cb05c00 100644 --- a/feature/src/main/java/team/aliens/dms/android/feature/main/home/HomeViewModel.kt +++ b/feature/src/main/java/team/aliens/dms/android/feature/main/home/HomeViewModel.kt @@ -25,12 +25,13 @@ internal class HomeViewModel @Inject constructor( ) { init { fetchWhetherNewNoticeExists() - updateMeal(date = stateFlow.value.selectedDate) + updateDate(date = stateFlow.value.selectedDate) } override fun processIntent(intent: HomeIntent) { when (intent) { - is HomeIntent.UpdateSelectedDate -> updateMeal(intent.selectedDate) + is HomeIntent.UpdateSelectedDate -> updateDate(intent.selectedDate) + HomeIntent.UpdateMeal -> updateMeal() } } @@ -44,7 +45,7 @@ internal class HomeViewModel @Inject constructor( } } - private fun updateMeal(date: LocalDate) { + private fun updateDate(date: LocalDate) { viewModelScope.launch(Dispatchers.IO) { runCatching { mealRepository.fetchMeal(date) @@ -62,6 +63,27 @@ internal class HomeViewModel @Inject constructor( } } } + + private fun updateMeal() = + viewModelScope.launch(Dispatchers.IO) { + println("LOGLOG1") + val capturedDate = stateFlow.value.selectedDate + runCatching { + mealRepository.updateMeal(capturedDate) + }.onSuccess { meal -> + reduce( + newState = stateFlow.value.copy( + selectedDate = capturedDate, + currentMeal = meal, + ), + ) + postSideEffect(HomeSideEffect.MealUpdated) + }.onFailure { + postSideEffect(HomeSideEffect.MealUpdateFailed) + }.also { + println("LOGLOG ${it.isSuccess}") + } + } } internal data class HomeUiState( @@ -94,10 +116,13 @@ internal data class HomeUiState( internal sealed class HomeIntent : Intent() { class UpdateSelectedDate(val selectedDate: LocalDate) : HomeIntent() + data object UpdateMeal : HomeIntent() } internal sealed class HomeSideEffect : SideEffect() { data object CannotFindMeal : HomeSideEffect() + data object MealUpdated : HomeSideEffect() + data object MealUpdateFailed : HomeSideEffect() } /* diff --git a/feature/src/main/java/team/aliens/dms/android/feature/signup/6_SetPasswordScreen.kt b/feature/src/main/java/team/aliens/dms/android/feature/signup/6_SetPasswordScreen.kt index 8841cb804..b0d9fd91f 100644 --- a/feature/src/main/java/team/aliens/dms/android/feature/signup/6_SetPasswordScreen.kt +++ b/feature/src/main/java/team/aliens/dms/android/feature/signup/6_SetPasswordScreen.kt @@ -60,6 +60,14 @@ internal fun SignUpSetPasswordScreen( val (shouldShowQuitSignUpDialog, onShouldShowQuitSignUpDialogChange) = remember { mutableStateOf(false) } + + val (isPasswordInvalid, onChangeIsPasswordInvalid) = remember( + uiState.password, + uiState.passwordRepeat + ) { + mutableStateOf(false) + } + if (shouldShowQuitSignUpDialog) { AlertDialog( title = { Text(text = stringResource(id = R.string.sign_up)) }, @@ -90,6 +98,13 @@ internal fun SignUpSetPasswordScreen( message = context.getString(R.string.sign_up_set_password_error_password_mismatch), ) + SignUpSideEffect.InvalidPassword -> { + toast.showErrorToast( + message = context.getString(R.string.sign_up_set_password_invalid_password), + ) + onChangeIsPasswordInvalid(true) + } + else -> {/* explicit blank */ } } @@ -167,4 +182,4 @@ internal fun SignUpSetPasswordScreen( BackHandler { onShouldShowQuitSignUpDialogChange(true) } -} \ No newline at end of file +} diff --git a/feature/src/main/java/team/aliens/dms/android/feature/signup/SignUpViewModel.kt b/feature/src/main/java/team/aliens/dms/android/feature/signup/SignUpViewModel.kt index 705a0536a..96ba64f22 100644 --- a/feature/src/main/java/team/aliens/dms/android/feature/signup/SignUpViewModel.kt +++ b/feature/src/main/java/team/aliens/dms/android/feature/signup/SignUpViewModel.kt @@ -13,6 +13,8 @@ import team.aliens.dms.android.data.auth.repository.AuthRepository import team.aliens.dms.android.data.school.repository.SchoolRepository import team.aliens.dms.android.data.student.repository.StudentRepository import team.aliens.dms.android.shared.validator.checkIfEmailValid +import team.aliens.dms.android.shared.validator.checkIfIdValid +import team.aliens.dms.android.shared.validator.checkIfPasswordValid import java.util.UUID import javax.inject.Inject @@ -201,13 +203,22 @@ class SignUpViewModel @Inject constructor( ), ) - private fun confirmId() = viewModelScope.launch(Dispatchers.IO) { - runCatching { - studentRepository.checkIdDuplication(id = stateFlow.value.id) - }.onSuccess { - postSideEffect(SignUpSideEffect.IdAvailable) - }.onFailure { - postSideEffect(SignUpSideEffect.IdDuplicated) + private fun confirmId() = run { + val capturedId = stateFlow.value.id + + if (!checkIfIdValid(capturedId)) { + postSideEffect(SignUpSideEffect.InvalidId) + return@run + } + + viewModelScope.launch(Dispatchers.IO) { + runCatching { + studentRepository.checkIdDuplication(id = capturedId) + }.onSuccess { + postSideEffect(SignUpSideEffect.IdAvailable) + }.onFailure { + postSideEffect(SignUpSideEffect.IdDuplicated) + } } } @@ -227,13 +238,18 @@ class SignUpViewModel @Inject constructor( val capturedState = stateFlow.value val password = capturedState.password val passwordRepeat = capturedState.passwordRepeat - postSideEffect( - sideEffect = if (password != passwordRepeat) { - SignUpSideEffect.PasswordMismatch - } else { - SignUpSideEffect.PasswordSet - }, - ) + + if (password != passwordRepeat) { + postSideEffect(SignUpSideEffect.PasswordMismatch) + return + } + + if (!checkIfPasswordValid(password)) { + postSideEffect(SignUpSideEffect.InvalidPassword) + return + } + + postSideEffect(SignUpSideEffect.PasswordSet) } private fun signUp() = viewModelScope.launch(Dispatchers.IO) { @@ -368,10 +384,12 @@ sealed class SignUpSideEffect : SideEffect() { data object UserNotFound : SignUpSideEffect() data object IdAvailable : SignUpSideEffect() data object IdDuplicated : SignUpSideEffect() + data object InvalidId : SignUpSideEffect() // SetPassword data object PasswordSet : SignUpSideEffect() data object PasswordMismatch : SignUpSideEffect() + data object InvalidPassword : SignUpSideEffect() // Terms data object SignedUp : SignUpSideEffect() diff --git a/feature/src/main/java/team/aliens/dms/android/feature/studyroom/details/StudyRoomDetailsScreen.kt b/feature/src/main/java/team/aliens/dms/android/feature/studyroom/details/StudyRoomDetailsScreen.kt index 4eabe0c3a..07188835b 100644 --- a/feature/src/main/java/team/aliens/dms/android/feature/studyroom/details/StudyRoomDetailsScreen.kt +++ b/feature/src/main/java/team/aliens/dms/android/feature/studyroom/details/StudyRoomDetailsScreen.kt @@ -352,17 +352,14 @@ private fun SeatList( onSelectSeat: (seat: StudyRoom.Seat) -> Unit, ) { // FIXME: 리컴포지션 방지하도록 작성하기 - val formedSeats: List> = List( - size = countOfRows - ) { indexOfRows -> - arrayOfNulls(size = countOfColumns) - }.apply { - seats.forEach { seat -> - val rowIndexOfSeat = seat.row - 1 - val columnIndexOfSeat = seat.column - 1 - this[rowIndexOfSeat][columnIndexOfSeat] = seat + val formedSeats: List> = + List(size = countOfRows) { arrayOfNulls(size = countOfColumns).apply {} }.apply { + seats.forEach { seat -> + val rowIndexOfSeat = seat.column - 1 + val columnIndexOfSeat = seat.row - 1 + this[rowIndexOfSeat][columnIndexOfSeat] = seat + } } - } Box( modifier = modifier diff --git a/feature/src/main/res/values/strings.xml b/feature/src/main/res/values/strings.xml index 68643e14b..5e02bc60e 100644 --- a/feature/src/main/res/values/strings.xml +++ b/feature/src/main/res/values/strings.xml @@ -148,9 +148,9 @@ 비밀번호 설정 이용약관 확인 비밀번호를 입력해 주세요 - 비밀번호가 일치하지 않습니다 - 비밀번호를 입력해 주세요 비밀번호를 한 번 더 입력해 주세요 + 비밀번호가 일치하지 않습니다 + 올바르지 않은 비밀번호 형식입니다 비밀번호가 재설정 되었습니다 비밀번호는 영문, 숫자, 기호를 포함한 8~20자이어야 합니다 @@ -212,6 +212,7 @@ 비밀번호를 변경했습니다 비밀번호가 일치하지 않습니다 유효하지 않은 형식입니다 + 비밀번호를 변경할 수 없습니다 기존 비밀번호를 입력해 주세요 새로운 비밀번호를 입력해 주세요 비밀번호를 한 번 더 입력해 주세요 @@ -287,4 +288,6 @@ 번호 %s 님이 맞으신가요? + 새로고침 + 급식을 다시 불러왔습니다 \ No newline at end of file diff --git a/shared/validator/build.gradle.kts b/shared/validator/build.gradle.kts index 773bf8ac8..272d4d4ce 100644 --- a/shared/validator/build.gradle.kts +++ b/shared/validator/build.gradle.kts @@ -6,4 +6,8 @@ plugins { java { sourceCompatibility = Versions.java targetCompatibility = Versions.java + + dependencies { + implementation(libs.junit) + } } \ No newline at end of file diff --git a/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/AsciiValidator.kt b/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/AsciiValidator.kt index 290df3607..4752ec02d 100644 --- a/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/AsciiValidator.kt +++ b/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/AsciiValidator.kt @@ -6,12 +6,7 @@ object AsciiValidator : Validator() { override fun validate(value: String): Boolean = value.matches(regex) } -fun checkIfAscii(value: String): Boolean { - if (value.isEmpty()) { - return false - } - return AsciiValidator.validate(value) -} +fun checkIfAscii(value: String): Boolean = AsciiValidator.validate(value) val String.isAscii: Boolean inline get() = AsciiValidator.validate(this) diff --git a/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/EmailValidator.kt b/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/EmailValidator.kt index c58bae990..adc8a8660 100644 --- a/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/EmailValidator.kt +++ b/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/EmailValidator.kt @@ -7,9 +7,4 @@ object EmailValidator : Validator() { override fun validate(value: String): Boolean = value.matches(regex) } -fun checkIfEmailValid(value: String): Boolean { - if (value.isEmpty()) { - return false - } - return EmailValidator.validate(value) -} +fun checkIfEmailValid(value: String): Boolean = EmailValidator.validate(value) diff --git a/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/IdValidator.kt b/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/IdValidator.kt index fb71c9349..fbb073845 100644 --- a/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/IdValidator.kt +++ b/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/IdValidator.kt @@ -1,14 +1,9 @@ package team.aliens.dms.android.shared.validator object IdValidator : Validator() { - override val regex = Regex("^[A-Za-z0-9]{4,20}\$") + override val regex = Regex("^[A-Za-z0-9._-]{4,20}\$") override fun validate(value: String): Boolean = value.matches(regex) } -fun checkIfIdValid(value: String): Boolean { - if (value.isEmpty()) { - return false - } - return IdValidator.validate(value) -} +fun checkIfIdValid(value: String): Boolean = IdValidator.validate(value) diff --git a/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/PasswordValidator.kt b/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/PasswordValidator.kt index 6eb6be7c4..641666796 100644 --- a/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/PasswordValidator.kt +++ b/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/PasswordValidator.kt @@ -2,14 +2,10 @@ package team.aliens.dms.android.shared.validator object PasswordValidator : Validator() { - override val regex = Regex("^.*(?=^.{8,20}\$)(?=.*\\d)(?=.*[a-zA-Z])(?=.*[!@#\$%^&+=]).*\$") + override val regex = + Regex("(?=.*[a-z])(?=.*[0-9])(?=.*[!#\$%&'()*+,./:;<=>?@\^_`{|}~-])[a-zA-Z0-9!#\$%&'()*+,./:;<=>?@\^_`{|}~-]{8,20}\$") override fun validate(value: String): Boolean = value.matches(regex) } -fun checkIfPasswordValid(value: String): Boolean { - if (value.isEmpty()) { - return false - } - return PasswordValidator.validate(value) -} +fun checkIfPasswordValid(value: String): Boolean = PasswordValidator.validate(value) diff --git a/shared/validator/src/test/java/team/aliens/dms/android/shared/validator/test/IdValidatorTest.kt b/shared/validator/src/test/java/team/aliens/dms/android/shared/validator/test/IdValidatorTest.kt new file mode 100644 index 000000000..98b235134 --- /dev/null +++ b/shared/validator/src/test/java/team/aliens/dms/android/shared/validator/test/IdValidatorTest.kt @@ -0,0 +1,91 @@ +package team.aliens.dms.android.shared.validator.test + +import org.junit.Test +import team.aliens.dms.android.shared.validator.IdValidator +import team.aliens.dms.android.shared.validator.checkIfIdValid + +@Suppress("SimplifyBooleanWithConstants", "LocalVariableName", "ConvertToStringTemplate") +class IdValidatorTest { + + @Test + fun `Test empty id`() { + val `empty id` = "" + + assert(IdValidator.validate(`empty id`) == false) + assert(checkIfIdValid(`empty id`) == false) + } + + @Test + fun `Test id not in English`() { + val `id not in english` = "박준수" + + assert(IdValidator.validate(`id not in english`) == false) + assert(checkIfIdValid(`id not in english`) == false) + } + + @Test + fun `Test id with only english digits`() { + val `english only id` = "abcdefgh" + + assert(IdValidator.validate(`english only id`) == true) + assert(checkIfIdValid(`english only id`) == true) + } + + @Test + fun `Test id with only numbers`() { + val `numbers only id` = "12345678" + + assert(IdValidator.validate(`numbers only id`) == true) + assert(checkIfIdValid(`numbers only id`) == true) + } + + @Test + fun `Test id under length 4`() { + val `id under length 4` = "ab1" + + assert(IdValidator.validate(`id under length 4`) == false) + assert(checkIfIdValid(`id under length 4`) == false) + } + + @Test + fun `Test id over length 20`() { + val `id over length 20` = + "abcd1234" + "abcd1234" + "abcd" + "1" + + assert(IdValidator.validate(`id over length 20`) == false) + assert(checkIfIdValid(`id over length 20`) == false) + } + + @Test + fun `Test id containing blank`() { + val `id with blank` = "ab d1234!" + + assert(IdValidator.validate(`id with blank`) == false) + assert(checkIfIdValid(`id with blank`) == false) + } + + @Test + fun `Test id containing not allowed spacial character`() { + val `id with not allowed special character` = "abcd1234!" + + assert(IdValidator.validate(`id with not allowed special character`) == false) + assert(checkIfIdValid(`id with not allowed special character`) == false) + } + + @Test + fun `Test id containing with allowed special character`() { + val `base id` = "abcd1234" + val case1 = `base id` + '.' + val case2 = `base id` + '_' + val case3 = `base id` + '-' + + assert(IdValidator.validate(case1) == true) + assert(checkIfIdValid(case1) == true) + + assert(IdValidator.validate(case2) == true) + assert(checkIfIdValid(case2) == true) + + assert(IdValidator.validate(case3) == true) + assert(checkIfIdValid(case3) == true) + } +} diff --git a/shared/validator/src/test/java/team/aliens/dms/android/shared/validator/test/PasswordValidatorTest.kt b/shared/validator/src/test/java/team/aliens/dms/android/shared/validator/test/PasswordValidatorTest.kt new file mode 100644 index 000000000..ac79fbbb1 --- /dev/null +++ b/shared/validator/src/test/java/team/aliens/dms/android/shared/validator/test/PasswordValidatorTest.kt @@ -0,0 +1,58 @@ +package team.aliens.dms.android.shared.validator.test + +import org.junit.Test +import team.aliens.dms.android.shared.validator.PasswordValidator +import team.aliens.dms.android.shared.validator.checkIfPasswordValid + +@Suppress("SimplifyBooleanWithConstants", "LocalVariableName") +class PasswordValidatorTest { + + @Test + fun `Test empty password`() { + val `empty password` = "" + + assert(PasswordValidator.validate(`empty password`) == false) + assert(checkIfPasswordValid(`empty password`) == false) + } + + @Test + fun `Test password with only english digits`() { + val `english only password` = "abcdefgh" + + assert(PasswordValidator.validate(`english only password`) == false) + assert(checkIfPasswordValid(`english only password`) == false) + } + + @Test + fun `Test password with only numbers`() { + val `numbers only password` = "12345678" + + assert(PasswordValidator.validate(`numbers only password`) == false) + assert(checkIfPasswordValid(`numbers only password`) == false) + } + + @Test + fun `Test password under length 8`() { + val `password under length 8` = "abc123!" + + assert(PasswordValidator.validate(`password under length 8`) == false) + assert(checkIfPasswordValid(`password under length 8`) == false) + } + + @Test + fun `Test password over length 20`() { + val `password over length 20` = + "abcd1234" + "abcd1234" + "!!.." + "-" + + assert(PasswordValidator.validate(`password over length 20`) == false) + assert(checkIfPasswordValid(`password over length 20`) == false) + } + + @Test + fun `Test password containing blank`() { + val `password with blank` = "ab d1234!" + + assert(PasswordValidator.validate(`password with blank`) == false) + assert(checkIfPasswordValid(`password with blank`) == false) + } +}