diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d7138990..3c78fc2e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(project(":feature:club")) implementation(project(":feature:email")) implementation(project(":feature:main")) + implementation(project(":feature:certification")) implementation(libs.junit) androidTestImplementation(libs.androidx.test.ext) diff --git a/app/src/main/java/com/msg/bitgoeul_android/navigation/BitgoeulNavHost.kt b/app/src/main/java/com/msg/bitgoeul_android/navigation/BitgoeulNavHost.kt index 93f9dd6e..10d0ab39 100644 --- a/app/src/main/java/com/msg/bitgoeul_android/navigation/BitgoeulNavHost.kt +++ b/app/src/main/java/com/msg/bitgoeul_android/navigation/BitgoeulNavHost.kt @@ -19,6 +19,9 @@ import com.example.my_page.navigation.myPageScreen import com.example.my_page.navigation.navigateToMyPage import com.example.my_page.navigation.navigateToPasswordChange import com.msg.bitgoeul_android.ui.BitgoeulAppState +import com.msg.certification.navigation.addCertificationScreen +import com.msg.certification.navigation.certificationScreen +import com.msg.certification.navigation.navigateToAddCertificationPage import com.msg.club.navigation.clubDetailScreen import com.msg.club.navigation.clubScreen import com.msg.club.navigation.navigateToClubDetailPage @@ -152,5 +155,12 @@ fun BitgoeulNavHost( mainPageScreen( onLoginClicked = navController::navigateToLogin ) + certificationScreen( + onHumanClicked = navController::navigateToMyPage, + onEditClicked = navController::navigateToAddCertificationPage + ) + addCertificationScreen( + onBackClicked = navController::popBackStack + ) } } \ No newline at end of file diff --git a/core/data/src/main/java/com/msg/data/repository/certification/CertificationRepository.kt b/core/data/src/main/java/com/msg/data/repository/certification/CertificationRepository.kt index a977e61b..84bd05ef 100644 --- a/core/data/src/main/java/com/msg/data/repository/certification/CertificationRepository.kt +++ b/core/data/src/main/java/com/msg/data/repository/certification/CertificationRepository.kt @@ -9,5 +9,5 @@ interface CertificationRepository { suspend fun getCertificationListForTeacher(studentId: UUID): Flow> suspend fun getCertificationListForStudent(): Flow> suspend fun writeCertification(body: WriteCertificationRequest): Flow - suspend fun editCertification(studentId: UUID, id: UUID, body: WriteCertificationRequest): Flow + suspend fun editCertification(id: UUID, body: WriteCertificationRequest): Flow } \ No newline at end of file diff --git a/core/data/src/main/java/com/msg/data/repository/certification/CertificationRepositoryImpl.kt b/core/data/src/main/java/com/msg/data/repository/certification/CertificationRepositoryImpl.kt index 2d5eafdc..6883c8ed 100644 --- a/core/data/src/main/java/com/msg/data/repository/certification/CertificationRepositoryImpl.kt +++ b/core/data/src/main/java/com/msg/data/repository/certification/CertificationRepositoryImpl.kt @@ -25,12 +25,10 @@ class CertificationRepositoryImpl @Inject constructor( } override suspend fun editCertification( - studentId: UUID, id: UUID, body: WriteCertificationRequest, ): Flow { return certificationDataSource.editCertification( - studentId = studentId, id = id, body = body ) diff --git a/core/design-system/src/main/java/com/msg/design_system/component/icon/BitgoeulIcon.kt b/core/design-system/src/main/java/com/msg/design_system/component/icon/BitgoeulIcon.kt index 7bcfcca1..dd62cf3f 100644 --- a/core/design-system/src/main/java/com/msg/design_system/component/icon/BitgoeulIcon.kt +++ b/core/design-system/src/main/java/com/msg/design_system/component/icon/BitgoeulIcon.kt @@ -315,4 +315,12 @@ fun BigAlertIcon() { painter = painterResource(id = R.drawable.ic_big_alert), contentDescription = "Big alert icon" ) +} + +@Composable +fun HumanIcon() { + Image( + painter = painterResource(id = R.drawable.ic_human), + contentDescription = "Human upper body icon" + ) } \ No newline at end of file diff --git a/core/design-system/src/main/java/com/msg/design_system/component/topbar/DetailSettingTopBar.kt b/core/design-system/src/main/java/com/msg/design_system/component/topbar/DetailSettingTopBar.kt new file mode 100644 index 00000000..8c907017 --- /dev/null +++ b/core/design-system/src/main/java/com/msg/design_system/component/topbar/DetailSettingTopBar.kt @@ -0,0 +1,39 @@ +package com.msg.design_system.component.topbar + +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.size +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.msg.design_system.component.icon.CloseIcon +import com.msg.design_system.theme.BitgoeulAndroidTheme + +@Composable +fun DetailSettingTopBar( + modifier: Modifier = Modifier, + text: String, + onBackClicked: () -> Unit +) { + BitgoeulAndroidTheme { colors, typography -> + Row( + modifier = modifier.fillMaxWidth() + ) { + Text( + text = text, + style = typography.titleSmall, + color = colors.BLACK + ) + Spacer(modifier = modifier.weight(1f)) + IconButton( + modifier = modifier.size(24.dp), + content = { CloseIcon() }, + onClick = onBackClicked + ) + } + } +} \ No newline at end of file diff --git a/core/design-system/src/main/res/drawable/ic_human.xml b/core/design-system/src/main/res/drawable/ic_human.xml new file mode 100644 index 00000000..e184a218 --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_human.xml @@ -0,0 +1,12 @@ + + + + diff --git a/core/domain/src/main/java/com/msg/domain/certification/EditCertificationUseCase.kt b/core/domain/src/main/java/com/msg/domain/certification/EditCertificationUseCase.kt index 87289a23..c25dd005 100644 --- a/core/domain/src/main/java/com/msg/domain/certification/EditCertificationUseCase.kt +++ b/core/domain/src/main/java/com/msg/domain/certification/EditCertificationUseCase.kt @@ -8,7 +8,7 @@ import javax.inject.Inject class EditCertificationUseCase @Inject constructor( private val certificationRepository: CertificationRepository ) { - suspend operator fun invoke(studentId: UUID, id: UUID, body: WriteCertificationRequest) = runCatching { - certificationRepository.editCertification(studentId = studentId, id = id, body = body) + suspend operator fun invoke(id: UUID, body: WriteCertificationRequest) = runCatching { + certificationRepository.editCertification(id = id, body = body) } } \ No newline at end of file diff --git a/core/model/src/main/java/com/msg/model/remote/request/certification/WriteCertificationRequest.kt b/core/model/src/main/java/com/msg/model/remote/request/certification/WriteCertificationRequest.kt index fbb3e6a2..b276802e 100644 --- a/core/model/src/main/java/com/msg/model/remote/request/certification/WriteCertificationRequest.kt +++ b/core/model/src/main/java/com/msg/model/remote/request/certification/WriteCertificationRequest.kt @@ -4,5 +4,5 @@ import java.time.LocalDate data class WriteCertificationRequest( val name: String, - val acquisitionData: LocalDate + val acquisitionDate: LocalDate ) diff --git a/core/model/src/main/java/com/msg/model/remote/response/club/StudentBelongClubResponse.kt b/core/model/src/main/java/com/msg/model/remote/response/club/StudentBelongClubResponse.kt index 390eef1a..010b620a 100644 --- a/core/model/src/main/java/com/msg/model/remote/response/club/StudentBelongClubResponse.kt +++ b/core/model/src/main/java/com/msg/model/remote/response/club/StudentBelongClubResponse.kt @@ -2,7 +2,7 @@ package com.msg.model.remote.response.club data class StudentBelongClubResponse( val name: String, - val phoneNumber: Int, + val phoneNumber: String, val email: String, val credit: Int ) diff --git a/core/network/src/main/java/com/msg/network/api/CertificationAPI.kt b/core/network/src/main/java/com/msg/network/api/CertificationAPI.kt index eb8692ed..5c887f60 100644 --- a/core/network/src/main/java/com/msg/network/api/CertificationAPI.kt +++ b/core/network/src/main/java/com/msg/network/api/CertificationAPI.kt @@ -23,9 +23,8 @@ interface CertificationAPI { @Body body: WriteCertificationRequest, ) - @PATCH("certification/{student_id}/{id}") + @PATCH("certification/{id}") suspend fun editCertification( - @Path("student_id") studentId: UUID, @Path("id") id: UUID, @Body body: WriteCertificationRequest ) diff --git a/core/network/src/main/java/com/msg/network/datasource/certification/CertificationDataSource.kt b/core/network/src/main/java/com/msg/network/datasource/certification/CertificationDataSource.kt index e99e83cd..b618c859 100644 --- a/core/network/src/main/java/com/msg/network/datasource/certification/CertificationDataSource.kt +++ b/core/network/src/main/java/com/msg/network/datasource/certification/CertificationDataSource.kt @@ -9,5 +9,5 @@ interface CertificationDataSource { suspend fun getCertificationListForTeacher(studentId: UUID): Flow> suspend fun getCertificationListForStudent(): Flow> suspend fun writeCertification(body: WriteCertificationRequest): Flow - suspend fun editCertification(studentId: UUID, id: UUID, body: WriteCertificationRequest): Flow + suspend fun editCertification(id: UUID, body: WriteCertificationRequest): Flow } \ No newline at end of file diff --git a/core/network/src/main/java/com/msg/network/datasource/certification/CertificationDataSourceImpl.kt b/core/network/src/main/java/com/msg/network/datasource/certification/CertificationDataSourceImpl.kt index 6180fe7f..5c89f72c 100644 --- a/core/network/src/main/java/com/msg/network/datasource/certification/CertificationDataSourceImpl.kt +++ b/core/network/src/main/java/com/msg/network/datasource/certification/CertificationDataSourceImpl.kt @@ -41,7 +41,6 @@ class CertificationDataSourceImpl @Inject constructor( }.flowOn(Dispatchers.IO) override suspend fun editCertification( - studentId: UUID, id: UUID, body: WriteCertificationRequest, ): Flow = flow { @@ -49,7 +48,6 @@ class CertificationDataSourceImpl @Inject constructor( BitgoeulApiHandler() .httpRequest { certificationAPI.editCertification( - studentId = studentId, id = id, body = body ) diff --git a/feature/certification/.gitignore b/feature/certification/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/certification/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/certification/build.gradle.kts b/feature/certification/build.gradle.kts new file mode 100644 index 00000000..caaf9cf4 --- /dev/null +++ b/feature/certification/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("bitgoeul.android.feature") + id("bitgoeul.android.hilt") +} + +android { + namespace = "com.msg.certification" +} diff --git a/feature/certification/src/androidTest/java/com/msg/certification/ExampleInstrumentedTest.kt b/feature/certification/src/androidTest/java/com/msg/certification/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..3ff9ff09 --- /dev/null +++ b/feature/certification/src/androidTest/java/com/msg/certification/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.msg.certification + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.msg.certification", appContext.packageName) + } +} \ No newline at end of file diff --git a/feature/certification/src/main/AndroidManifest.xml b/feature/certification/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5918e68 --- /dev/null +++ b/feature/certification/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/AddCertificationScreen.kt b/feature/certification/src/main/java/com/msg/certification/AddCertificationScreen.kt new file mode 100644 index 00000000..96350907 --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/AddCertificationScreen.kt @@ -0,0 +1,126 @@ +package com.msg.certification + +import android.widget.Toast +import androidx.activity.ComponentActivity +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.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.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.msg.certification.component.AddAcquisitionDateSection +import com.msg.certification.component.AddCertificationSection +import com.msg.design_system.component.button.BitgoeulButton +import com.msg.design_system.component.topbar.DetailSettingTopBar +import com.msg.design_system.theme.BitgoeulAndroidTheme +import com.msg.design_system.theme.color.BitgoeulColor +import com.msg.ui.DevicePreviews +import com.msg.ui.util.toKoreanFormat +import java.time.LocalDate + +@Composable +fun AddCertificationScreenRoute( + viewModel: CertificationViewModel = hiltViewModel(LocalContext.current as ComponentActivity), + onBackClicked: () -> Unit, + onAddClicked: () -> Unit +) { + AddCertificationScreen( + selectedName = viewModel.selectedTitle.value, + selectedDate = viewModel.selectedDate.value, + onBackClicked = { + onBackClicked() + }, + onAddClicked = { name, acquisitionDate -> + viewModel.selectedCertificationId.value?.let { + viewModel.editCertification(name = name, acquisitionDate = acquisitionDate) + } ?: viewModel.writeCertification(name = name, acquisitionDate = acquisitionDate) + onAddClicked() + } + ) +} + +@Composable +fun AddCertificationScreen( + modifier: Modifier = Modifier, + selectedName: String, + selectedDate: LocalDate?, + onBackClicked: () -> Unit, + onAddClicked: (name: String, acquisitionDate: LocalDate) -> Unit +) { + val name = remember { mutableStateOf(selectedName) } + val date = remember { mutableStateOf(selectedDate) } + + Box( + modifier = modifier + .fillMaxSize() + .background(color = BitgoeulColor.WHITE) + ) { + val context = LocalContext.current + Column( + modifier = modifier + .fillMaxSize() + .padding( + top = 24.dp, + bottom = 14.dp, + start = 28.dp, + end = 28.dp + ), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + DetailSettingTopBar( + text = "자격증 세부 설정", + onBackClicked = onBackClicked + ) + AddCertificationSection( + onValueChange = { + name.value = it + }, + onClickButton = { + name.value = "" + } + ) + AddAcquisitionDateSection( + onDatePickerQuit = { + date.value = it + }, + acquisitionDate = date.value?.toKoreanFormat() ?: "" + ) + Spacer(modifier = modifier.weight(1f)) + BitgoeulButton( + modifier = modifier.fillMaxWidth(), + text = "자격증 등록", + onClick = { + if (name.value.isBlank()) { + Toast.makeText(context, "자격증 이름을 입력해주세요", Toast.LENGTH_SHORT).show() + } else if (date.value == null) { + Toast.makeText(context, "취득일을 입력해주세요", Toast.LENGTH_SHORT).show() + } else { + onAddClicked(name.value, date.value!!) + } + } + ) + } + } +} + + +@DevicePreviews +@Composable +fun AddCertificationScreenPre() { + AddCertificationScreen( + onBackClicked = {}, + onAddClicked = {_,_->}, + selectedName = "", + selectedDate = null + ) +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/CertificationScreen.kt b/feature/certification/src/main/java/com/msg/certification/CertificationScreen.kt new file mode 100644 index 00000000..59da4365 --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/CertificationScreen.kt @@ -0,0 +1,234 @@ +package com.msg.certification + +import androidx.activity.ComponentActivity +import androidx.compose.foundation.background +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.HorizontalDivider +import androidx.compose.material3.IconButton +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.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.msg.certification.component.CertificationSection +import com.msg.certification.component.FinishedLectureSection +import com.msg.certification.component.StudentInfoSection +import com.msg.certification.util.Event +import com.msg.design_system.component.icon.HumanIcon +import com.msg.design_system.theme.BitgoeulAndroidTheme +import com.msg.model.remote.response.certification.CertificationListResponse +import com.msg.model.remote.response.club.StudentBelongClubResponse +import com.msg.model.remote.response.lecture.GetLectureSignUpHistoryResponse +import com.msg.model.remote.response.lecture.SignUpLectures +import com.msg.ui.DevicePreviews +import java.time.LocalDate +import java.util.UUID + +@Composable +fun CertificationScreenRoute( + viewModel: CertificationViewModel = hiltViewModel(LocalContext.current as ComponentActivity), + onHumanIconClicked: () -> Unit, + onEditClicked: () -> Unit +) { + viewModel.getCertificationList() + viewModel.getStudentBelong() + viewModel.getLectureSignUpHistory() + + LaunchedEffect(true) { + getCertificationList( + viewModel = viewModel, + onSuccess = { + viewModel.certificationList.addAll(it) + }, + onFailure = {} + ) + getStudentData( + viewModel = viewModel, + onSuccess = { + viewModel.studentData.value = it + }, + onFailure = {} + ) + getLectureData( + viewModel = viewModel, + onSuccess = { + viewModel.lectureData.value = it + }, + onFailure = {} + ) + } + + CertificationScreen( + onHumanIconClicked = onHumanIconClicked, + onEditClicked = { id, title, date -> + viewModel.selectedCertificationId.value = id + viewModel.selectedTitle.value = title + viewModel.selectedDate.value = date + onEditClicked() + }, + onPlusClicked = onEditClicked, + studentData = viewModel.studentData.value, + certificationData = viewModel.certificationList, + lectureData = viewModel.lectureData.value + ) +} + +suspend fun getCertificationList( + viewModel: CertificationViewModel, + onSuccess: (data: List) -> Unit, + onFailure: () -> Unit +) { + viewModel.getCertificationListResponse.collect { response -> + when(response) { + is Event.Success -> { + onSuccess(response.data!!) + } + else -> onFailure() + } + } +} + +suspend fun getStudentData( + viewModel: CertificationViewModel, + onSuccess: (data: StudentBelongClubResponse) -> Unit, + onFailure: () -> Unit +) { + viewModel.getStudentBelongResponse.collect { response -> + when(response) { + is Event.Success -> { + onSuccess(response.data!!) + } + else -> onFailure() + } + } +} + +suspend fun getLectureData( + viewModel: CertificationViewModel, + onSuccess: (data: GetLectureSignUpHistoryResponse) -> Unit, + onFailure: () -> Unit +) { + viewModel.getLectureSignUpHistoryResponse.collect { response -> + when(response) { + is Event.Success -> { + onSuccess(response.data!!) + } + else -> onFailure() + } + } +} + +@Composable +fun CertificationScreen( + modifier: Modifier = Modifier, + onHumanIconClicked: () -> Unit, + onEditClicked: (id: UUID, title: String, date: LocalDate) -> Unit, + onPlusClicked: () -> Unit, + studentData: StudentBelongClubResponse, + certificationData: List, + lectureData: GetLectureSignUpHistoryResponse +) { + BitgoeulAndroidTheme { colors, typography -> + Box( + modifier = modifier + .fillMaxSize() + .background(color = colors.WHITE) + ) { + Column( + modifier = modifier + .fillMaxSize() + .padding(horizontal = 28.dp) + ) { + Spacer(modifier = modifier.height(24.dp)) + Row( + modifier = modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "학생정보", + style = typography.titleMedium, + color = colors.BLACK + ) + Spacer(modifier = modifier.weight(1f)) + IconButton( + modifier = modifier.size(24.dp), + content = { HumanIcon() }, + onClick = onHumanIconClicked + ) + } + Spacer(modifier = modifier.height(24.dp)) + StudentInfoSection(data = studentData) + Spacer(modifier = modifier.height(24.dp)) + HorizontalDivider( + modifier = modifier.fillMaxWidth(), + thickness = 1.dp, + color = colors.G9 + ) + Spacer(modifier = modifier.height(24.dp)) + CertificationSection( + onPlusClicked = onPlusClicked, + onEditClicked = { id, title, date -> + onEditClicked(id, title, date) + }, + data = certificationData + ) + Spacer(modifier = modifier.height(12.dp)) + FinishedLectureSection(data = lectureData) + } + } + } +} + +@DevicePreviews +@Composable +fun CertificationScreenPre() { + CertificationScreen( + onHumanIconClicked = {}, + onEditClicked = {_,_,_->}, + onPlusClicked = {}, + studentData = StudentBelongClubResponse( + name = "채종인", + phoneNumber = "010-1234-5678", + email = "s22055@gsm.hs.kr", + credit = 300 + ), + certificationData = listOf( + CertificationListResponse( + certificationId = UUID.randomUUID(), + name = "정보처리산업기사", + acquisitionDate = LocalDate.now() + ) + ), + lectureData = GetLectureSignUpHistoryResponse( + lectures = listOf( + SignUpLectures( + id = UUID.randomUUID(), + name = "개쩌는 강의이름", + lectureType = "상호학점인정교육과정", + currentCompletedDate = LocalDate.now(), + lecturer = "채종인", + isComplete = true + ), + SignUpLectures( + id = UUID.randomUUID(), + name = "덜쩌는 강의이름", + lectureType = "상호학점인정교육과정", + currentCompletedDate = LocalDate.now(), + lecturer = "채종인", + isComplete = true + ) + ) + ) + ) +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/CertificationViewModel.kt b/feature/certification/src/main/java/com/msg/certification/CertificationViewModel.kt new file mode 100644 index 00000000..d7dc4bf6 --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/CertificationViewModel.kt @@ -0,0 +1,196 @@ +package com.msg.certification + +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.msg.certification.util.Event +import com.msg.certification.util.errorHandling +import com.msg.datastore.AuthTokenDataSource +import com.msg.domain.certification.EditCertificationUseCase +import com.msg.domain.certification.GetCertificationListForStudentUseCase +import com.msg.domain.certification.GetCertificationListForTeacherUseCase +import com.msg.domain.certification.WriteCertificationUseCase +import com.msg.domain.club.GetStudentBelongClubUseCase +import com.msg.domain.lecture.GetLectureSignUpHistoryUseCase +import com.msg.model.remote.enumdatatype.Authority +import com.msg.model.remote.request.certification.WriteCertificationRequest +import com.msg.model.remote.response.certification.CertificationListResponse +import com.msg.model.remote.response.club.StudentBelongClubResponse +import com.msg.model.remote.response.lecture.GetLectureSignUpHistoryResponse +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import java.time.LocalDate +import java.util.UUID +import javax.inject.Inject + +class CertificationViewModel @Inject constructor( + private val getCertificationListForTeacherUseCase: GetCertificationListForTeacherUseCase, + private val getCertificationListForStudentUseCase: GetCertificationListForStudentUseCase, + private val writeCertificationUseCase: WriteCertificationUseCase, + private val editCertificationUseCase: EditCertificationUseCase, + private val getStudentBelongClubUseCase: GetStudentBelongClubUseCase, + private val getLectureSignUpHistoryUseCase: GetLectureSignUpHistoryUseCase, + private val authTokenDataSource: AuthTokenDataSource, + private val savedStateHandle: SavedStateHandle +) : ViewModel() { + + private val _getCertificationListResponse = MutableStateFlow>>(Event.Loading) + val getCertificationListResponse = _getCertificationListResponse.asStateFlow() + + private val _writeCertificationResponse = MutableStateFlow>(Event.Loading) + val writeCertificationResponse = _writeCertificationResponse.asStateFlow() + + private val _editCertificationResponse = MutableStateFlow>(Event.Loading) + val editCertificationResponse = _editCertificationResponse.asStateFlow() + + private val _getStudentBelongResponse = MutableStateFlow>(Event.Loading) + val getStudentBelongResponse = _getStudentBelongResponse.asStateFlow() + + private val _getLectureSignUpHistoryResponse = MutableStateFlow>(Event.Loading) + val getLectureSignUpHistoryResponse = _getLectureSignUpHistoryResponse.asStateFlow() + + private fun getRole(): Authority = runBlocking { + return@runBlocking authTokenDataSource.getAuthority().first() + } + + private val studentId = UUID.fromString(savedStateHandle.get("studentId")) + + private val clubId: Long? = savedStateHandle.get("clubId") + + var selectedCertificationId = mutableStateOf(null) + private set + + var selectedTitle = mutableStateOf("") + private set + + var selectedDate = mutableStateOf(null) + private set + + var certificationList = mutableStateListOf() + private set + + var studentData = mutableStateOf( + StudentBelongClubResponse( + name = "", + phoneNumber = "", + email = "", + credit = 0 + ) + ) + private set + + var lectureData = mutableStateOf( + GetLectureSignUpHistoryResponse( + lectures = listOf() + ) + ) + private set + + fun getCertificationList() = viewModelScope.launch { + if (getRole() == Authority.ROLE_STUDENT) { + getCertificationListForStudentUseCase().onSuccess { + it.catch { remoteError -> + _getCertificationListResponse.value = remoteError.errorHandling() + }.collect { response -> + _getCertificationListResponse.value = Event.Success(data = response) + } + }.onFailure { error -> + _getCertificationListResponse.value = error.errorHandling() + } + } else { + if (studentId != null) { + getCertificationListForTeacherUseCase(studentId).onSuccess { + it.catch { remoteError -> + _getCertificationListResponse.value = remoteError.errorHandling() + }.collect { response -> + _getCertificationListResponse.value = Event.Success(data = response) + } + }.onFailure { error -> + _getCertificationListResponse.value = error.errorHandling() + } + } + } + } + + fun writeCertification( + name: String, + acquisitionDate: LocalDate + ) = viewModelScope.launch { + writeCertificationUseCase( + body = WriteCertificationRequest( + name = name, + acquisitionDate = acquisitionDate + ) + ).onSuccess { + it.catch { remoteError -> + _writeCertificationResponse.value = remoteError.errorHandling() + }.collect { + _writeCertificationResponse.value = Event.Success() + } + }.onFailure { error -> + _writeCertificationResponse.value = error.errorHandling() + } + } + + fun editCertification( + name: String, + acquisitionDate: LocalDate + ) = viewModelScope.launch { + editCertificationUseCase( + id = selectedCertificationId.value!!, + body = WriteCertificationRequest( + name = name, + acquisitionDate = acquisitionDate + ) + ).onSuccess { + it.catch { remoteError -> + _editCertificationResponse.value = remoteError.errorHandling() + }.collect { + _editCertificationResponse.value = Event.Success() + } + }.onFailure { error -> + _editCertificationResponse.value = error.errorHandling() + } + } + + fun getStudentBelong() = viewModelScope.launch { + if (clubId != null && studentId != null) { + getStudentBelongClubUseCase( + id = clubId, + studentId = studentId + ).onSuccess { + it.catch { remoteError -> + _getStudentBelongResponse.value = remoteError.errorHandling() + }.collect { response -> + _getStudentBelongResponse.value = Event.Success(data = response) + } + }.onFailure { error -> + _getStudentBelongResponse.value = error.errorHandling() + } + } + } + + fun getLectureSignUpHistory() = viewModelScope.launch { + if (studentId != null) { + getLectureSignUpHistoryUseCase( + studentId = studentId + ).onSuccess { + it.catch { remoteError -> + _getLectureSignUpHistoryResponse.value = remoteError.errorHandling() + }.collect { response -> + _getLectureSignUpHistoryResponse.value = Event.Success(data = response) + } + }.onFailure { error -> + _getLectureSignUpHistoryResponse.value = error.errorHandling() + } + } + } +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/component/AddAcquisitionDateSection.kt b/feature/certification/src/main/java/com/msg/certification/component/AddAcquisitionDateSection.kt new file mode 100644 index 00000000..e17f75e6 --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/component/AddAcquisitionDateSection.kt @@ -0,0 +1,42 @@ +package com.msg.certification.component + +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.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.msg.design_system.component.textfield.PickerTextField +import com.msg.design_system.theme.BitgoeulAndroidTheme +import java.time.LocalDate + +@Composable +fun AddAcquisitionDateSection( + modifier: Modifier = Modifier, + onDatePickerQuit: (LocalDate?) -> Unit, + acquisitionDate: String +) { + BitgoeulAndroidTheme { colors, typography -> + Column( + modifier = modifier.fillMaxWidth() + ) { + Text( + text = "취득일", + style = typography.titleSmall, + color = colors.BLACK + ) + Spacer(modifier = modifier.height(8.dp)) + PickerTextField( + modifier = modifier.fillMaxWidth(), + text = acquisitionDate.ifEmpty { "취득일" }, + list = listOf(), + selectedItem = "", + onItemChange = {}, + isDatePicker = true, + onDatePickerQuit = onDatePickerQuit + ) + } + } +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/component/AddCertificationSection.kt b/feature/certification/src/main/java/com/msg/certification/component/AddCertificationSection.kt new file mode 100644 index 00000000..297b3beb --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/component/AddCertificationSection.kt @@ -0,0 +1,43 @@ +package com.msg.certification.component + +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.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.msg.design_system.component.textfield.DefaultTextField +import com.msg.design_system.theme.BitgoeulAndroidTheme + +@Composable +fun AddCertificationSection( + modifier: Modifier = Modifier, + onValueChange: (String) -> Unit, + onClickButton: () -> Unit +) { + BitgoeulAndroidTheme { colors, typography -> + Column( + modifier = modifier.fillMaxWidth() + ) { + Text( + text = "자격증", + style = typography.titleSmall, + color = colors.BLACK + ) + Spacer(modifier = modifier.height(8.dp)) + DefaultTextField( + modifier = modifier.fillMaxWidth(), + placeholder = "자격증 이름 입력", + isError = false, + isLinked = false, + isDisabled = false, + isReverseTrailingIcon = false, + errorText = "", + onValueChange = onValueChange, + onClickButton = onClickButton + ) + } + } +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/component/CertificationItem.kt b/feature/certification/src/main/java/com/msg/certification/component/CertificationItem.kt new file mode 100644 index 00000000..cb322f52 --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/component/CertificationItem.kt @@ -0,0 +1,72 @@ +package com.msg.certification.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.msg.design_system.theme.BitgoeulAndroidTheme +import com.msg.model.remote.response.certification.CertificationListResponse +import com.msg.design_system.R +import com.msg.ui.util.toDotFormat +import java.time.LocalDate +import java.util.UUID + +@Composable +fun CertificationItem( + modifier: Modifier = Modifier, + data: CertificationListResponse, + onEditClicked: (id: UUID, title: String, date: LocalDate) -> Unit +) { + BitgoeulAndroidTheme { colors, typography -> + Column( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = data.name, + style = typography.bodyMedium, + color = colors.BLACK + ) + Spacer(modifier = modifier.weight(1f)) + Text( + modifier = modifier.clickable { + onEditClicked(data.certificationId, data.name, data.acquisitionDate) + }, + text = "수정", + style = TextStyle( + fontSize = 14.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.pretendard_regular)), + fontWeight = FontWeight(400), + color = colors.G1, + textDecoration = TextDecoration.Underline, + ) + ) + } + Spacer(modifier = modifier.height(12.dp)) + Text( + text = data.acquisitionDate.toDotFormat(), + style = typography.labelMedium, + color = colors.G1 + ) + } + } +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/component/CertificationSection.kt b/feature/certification/src/main/java/com/msg/certification/component/CertificationSection.kt new file mode 100644 index 00000000..1235a03c --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/component/CertificationSection.kt @@ -0,0 +1,83 @@ +package com.msg.certification.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.msg.design_system.component.icon.PlusIcon +import com.msg.design_system.theme.BitgoeulAndroidTheme +import com.msg.model.remote.response.certification.CertificationListResponse +import java.time.LocalDate +import java.util.UUID + +@Composable +fun CertificationSection( + modifier: Modifier = Modifier, + onPlusClicked: () -> Unit, + onEditClicked: (id: UUID, title: String, date: LocalDate) -> Unit, + data: List +) { + BitgoeulAndroidTheme { colors, typography -> + Column( + modifier = modifier.fillMaxWidth() + ) { + Row { + Text( + text = "자격증", + style = typography.titleSmall, + color = colors.BLACK + ) + Spacer(modifier = modifier.weight(1f)) + IconButton( + modifier = modifier.size(24.dp), + content = { PlusIcon() }, + onClick = onPlusClicked + ) + } + Spacer(modifier = modifier.height(24.dp)) + LazyColumn { + items(data) { + CertificationItem( + data = it, + onEditClicked = onEditClicked + ) + Spacer(modifier = modifier.height(12.dp)) + HorizontalDivider( + modifier = modifier.fillMaxWidth(), + thickness = 1.dp, + color = colors.G9 + ) + Spacer(modifier = modifier.height(12.dp)) + } + } + Spacer(modifier = modifier.height(12.dp)) + } + } +} + +@Preview +@Composable +fun CertificationSectionPre() { + CertificationSection( + onPlusClicked = {}, + onEditClicked = {_,_,_->}, + data = listOf( + CertificationListResponse( + certificationId = UUID.randomUUID(), + name = "정보처리산업기사", + acquisitionDate = LocalDate.now() + ) + ) + ) +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/component/FinishedLectureItem.kt b/feature/certification/src/main/java/com/msg/certification/component/FinishedLectureItem.kt new file mode 100644 index 00000000..adb956e2 --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/component/FinishedLectureItem.kt @@ -0,0 +1,65 @@ +package com.msg.certification.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.msg.design_system.theme.BitgoeulAndroidTheme +import com.msg.model.remote.enumdatatype.LectureType +import com.msg.model.remote.response.lecture.SignUpLectures +import com.msg.ui.util.toKoreanFormat + +@Composable +fun FinishedLectureItem( + modifier: Modifier = Modifier, + data: SignUpLectures +) { + BitgoeulAndroidTheme { colors, typography -> + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "# ${data.lectureType}", + style = typography.labelMedium, + color = colors.P3 + ) + Text( + text = data.name, + style = typography.bodyMedium, + color = colors.BLACK + ) + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${data.lecturer}교수님", + style = typography.labelMedium, + color = colors.G1 + ) + VerticalDivider( + modifier = modifier.height(14.dp), + thickness = 1.dp, + color = colors.G1 + ) + Text( + text = if (data.isComplete) "${data.currentCompletedDate.toKoreanFormat()} 이수" else "미이수", + style = typography.labelMedium, + color = colors.G1 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/component/FinishedLectureSection.kt b/feature/certification/src/main/java/com/msg/certification/component/FinishedLectureSection.kt new file mode 100644 index 00000000..159c72c5 --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/component/FinishedLectureSection.kt @@ -0,0 +1,75 @@ +package com.msg.certification.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.msg.design_system.theme.BitgoeulAndroidTheme +import com.msg.model.remote.response.lecture.GetLectureSignUpHistoryResponse +import com.msg.model.remote.response.lecture.SignUpLectures +import java.time.LocalDate +import java.util.UUID + +@Composable +fun FinishedLectureSection( + modifier: Modifier = Modifier, + data: GetLectureSignUpHistoryResponse +) { + BitgoeulAndroidTheme { colors, typography -> + Column { + Text( + text = "이수한 강의 목록", + style = typography.titleSmall, + color = colors.BLACK + ) + Spacer(modifier = modifier.height(24.dp)) + LazyColumn( + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + items(data.lectures) { + FinishedLectureItem(data = it) + Spacer(modifier = modifier.height(24.dp)) + HorizontalDivider( + thickness = 1.dp, + color = colors.G9 + ) + } + } + } + } +} + +@Preview +@Composable +fun FinishedLectureSectionPre() { + FinishedLectureSection( + data = GetLectureSignUpHistoryResponse( + lectures = listOf( + SignUpLectures( + id = UUID.randomUUID(), + name = "개쩌는 강의이름", + lectureType = "상호학점인정교육과정", + currentCompletedDate = LocalDate.now(), + lecturer = "채종인", + isComplete = true + ), + SignUpLectures( + id = UUID.randomUUID(), + name = "덜쩌는 강의이름", + lectureType = "상호학점인정교육과정", + currentCompletedDate = LocalDate.now(), + lecturer = "채종인", + isComplete = true + ) + ) + ) + ) +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/component/StudentInfoSection.kt b/feature/certification/src/main/java/com/msg/certification/component/StudentInfoSection.kt new file mode 100644 index 00000000..ee52db40 --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/component/StudentInfoSection.kt @@ -0,0 +1,87 @@ +package com.msg.certification.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.msg.design_system.theme.BitgoeulAndroidTheme +import com.msg.model.remote.response.club.StudentBelongClubResponse + +@Composable +fun StudentInfoSection( + modifier: Modifier = Modifier, + data: StudentBelongClubResponse +) { + BitgoeulAndroidTheme { colors, typography -> + Column( + modifier = modifier.fillMaxWidth() + ) { + Text( + text = data.name, + style = typography.titleMedium, + color = colors.BLACK + ) + Spacer(modifier = modifier.height(4.dp)) + Row { + Text( + text = "총학점", + style = typography.bodySmall, + color = colors.P5 + ) + Spacer(modifier = modifier.weight(1f)) + Text( + text = data.credit.toString(), + style = typography.bodySmall, + color = colors.P5 + ) + } + Spacer(modifier = modifier.height(5.dp)) + Row { + Text( + text = "이메일", + style = typography.labelMedium, + color = colors.G2 + ) + Spacer(modifier = modifier.weight(1f)) + Text( + text = data.email, + style = typography.labelMedium, + color = colors.G2 + ) + } + Spacer(modifier = modifier.height(5.dp)) + Row { + Text( + text = "전화번호", + style = typography.labelMedium, + color = colors.G2 + ) + Spacer(modifier = modifier.weight(1f)) + Text( + text = data.phoneNumber, + style = typography.labelMedium, + color = colors.G2 + ) + } + } + } +} + +@Preview +@Composable +fun StudentInfoSectionPre() { + StudentInfoSection( + data = StudentBelongClubResponse( + name = "채종인", + phoneNumber = "010-1234-5678", + email = "s22055@gsm.hs.kr", + credit = 300 + ) + ) +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/navigation/CertificationNavigation.kt b/feature/certification/src/main/java/com/msg/certification/navigation/CertificationNavigation.kt new file mode 100644 index 00000000..fd5f322f --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/navigation/CertificationNavigation.kt @@ -0,0 +1,50 @@ +package com.msg.certification.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.NavType +import androidx.navigation.compose.composable +import androidx.navigation.navArgument +import com.msg.certification.AddCertificationScreenRoute +import com.msg.certification.CertificationScreenRoute +import java.util.UUID + +const val certificationRoute = "certification_route" +const val addCertificationRoute = "add_certification_route" + +fun NavController.navigateToCertificationPage(navOptions: NavOptions? = null, clubId: Long, studentId: String) { + this.navigate("$certificationRoute/$clubId/$studentId", navOptions) +} + +fun NavGraphBuilder.certificationScreen(onHumanClicked: () -> Unit, onEditClicked: () -> Unit) { + composable( + route = "certificationRoute/{clubId}/{studentId}", + arguments = listOf( + navArgument("clubId") { + type = NavType.LongType + }, + navArgument("studentId") { + type = NavType.StringType + } + ) + ) { + CertificationScreenRoute( + onHumanIconClicked = onHumanClicked, + onEditClicked = onEditClicked + ) + } +} + +fun NavController.navigateToAddCertificationPage(navOptions: NavOptions? = null) { + this.navigate(addCertificationRoute, navOptions) +} + +fun NavGraphBuilder.addCertificationScreen(onBackClicked: () -> Unit) { + composable(route = addCertificationRoute) { + AddCertificationScreenRoute( + onBackClicked = onBackClicked, + onAddClicked = onBackClicked + ) + } +} \ No newline at end of file diff --git a/feature/certification/src/main/java/com/msg/certification/util/Event.kt b/feature/certification/src/main/java/com/msg/certification/util/Event.kt new file mode 100644 index 00000000..91dd5fe8 --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/util/Event.kt @@ -0,0 +1,60 @@ +package com.msg.certification.util + +//추후 리팩토링을 통해 다른 모듈? 혹은 패키지로 이동 예정 +sealed class Event( + val data: T? = null +) { + + object Loading : Event() + + /** + * 성공 + */ + class Success(data: T? = null) : Event(data = data) + + /** + * 400번 요청이 올바르지 않은 경우 + */ + object BadRequest : Event() + + /** + * 401번 비인증 요청 + */ + object Unauthorized : Event() + + /** + * 403번 권한이 없음 + */ + object ForBidden : Event() + + /** + * 404 찾을 수 없는 경우 + */ + object NotFound : Event() + + /** + * 406 맞는 규격이 없는 경우 + */ + object NotAcceptable : Event() + + /** + * 408 요청이 너무 오래 걸리는 경우 + */ + object TimeOut : Event() + + /** + * 409 권한이 없을 때 + */ + object Conflict : Event() + + /** + * 50X 서버에러 + */ + object Server : Event() + + /** + * 예상치 못한 에러 + */ + object UnKnown : Event() + +} diff --git a/feature/certification/src/main/java/com/msg/certification/util/errorHandling.kt b/feature/certification/src/main/java/com/msg/certification/util/errorHandling.kt new file mode 100644 index 00000000..12c1fb0b --- /dev/null +++ b/feature/certification/src/main/java/com/msg/certification/util/errorHandling.kt @@ -0,0 +1,76 @@ +package com.msg.certification.util + +import android.util.Log +import com.msg.domain.exception.BadRequestException +import com.msg.domain.exception.ConflictException +import com.msg.domain.exception.ForBiddenException +import com.msg.domain.exception.NeedLoginException +import com.msg.domain.exception.NotAcceptableException +import com.msg.domain.exception.NotFoundException +import com.msg.domain.exception.ServerException +import com.msg.domain.exception.TimeOutException +import com.msg.domain.exception.UnauthorizedException + +//추후 리팩토링을 통해 다른 모듈? 패키지로 이동 예정 +suspend fun Throwable.errorHandling( + badRequestAction: suspend () -> Unit = {}, + unauthorizedAction: suspend () -> Unit = {}, + forBiddenAction: suspend () -> Unit = {}, + notFoundAction: suspend () -> Unit = {}, + notAcceptableAction: suspend () -> Unit = {}, + timeOutAction: suspend () -> Unit = {}, + conflictAction: suspend () -> Unit = {}, + serverAction: suspend () -> Unit = {}, + unknownAction: suspend () -> Unit = {}, +): Event = + when (this) { + is BadRequestException -> { + errorLog("BadRequestException", message) + badRequestAction() + Event.BadRequest + } + is UnauthorizedException, is NeedLoginException -> { + errorLog("UnauthorizedException", message) + unauthorizedAction() + Event.Unauthorized + } + is ForBiddenException -> { + errorLog("ForBiddenException", message) + forBiddenAction() + Event.ForBidden + } + is NotFoundException -> { + errorLog("NotFoundException", message) + notFoundAction() + Event.NotFound + } + is NotAcceptableException -> { + errorLog("NotAcceptableException", message) + notAcceptableAction() + Event.NotAcceptable + } + is TimeOutException -> { + errorLog("TimeOutException", message) + timeOutAction() + Event.TimeOut + } + is ConflictException -> { + errorLog("ConflictException", message) + conflictAction() + Event.Conflict + } + is ServerException -> { + errorLog("ServerException", message) + serverAction() + Event.Server + } + else -> { + errorLog("UnKnownException", message) + unknownAction() + Event.UnKnown + } + } + +private fun errorLog(tag: String, msg: String?) { + Log.d("ErrorHandling-$tag", msg ?: "알 수 없는 오류") +} \ No newline at end of file diff --git a/feature/certification/src/test/java/com/msg/certification/ExampleUnitTest.kt b/feature/certification/src/test/java/com/msg/certification/ExampleUnitTest.kt new file mode 100644 index 00000000..43163abf --- /dev/null +++ b/feature/certification/src/test/java/com/msg/certification/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.msg.certification + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/feature/club/src/main/java/com/msg/club/ClubViewModel.kt b/feature/club/src/main/java/com/msg/club/ClubViewModel.kt index 960449b2..839b143b 100644 --- a/feature/club/src/main/java/com/msg/club/ClubViewModel.kt +++ b/feature/club/src/main/java/com/msg/club/ClubViewModel.kt @@ -67,7 +67,7 @@ class ClubViewModel @Inject constructor( var studentBelongClub = mutableStateOf( StudentBelongClubResponse( name = "", - phoneNumber = 0, + phoneNumber = "", email = "", credit = 0 ) diff --git a/feature/student-activity/src/main/java/com/msg/student_activity/ActivityDetailSettingScreen.kt b/feature/student-activity/src/main/java/com/msg/student_activity/ActivityDetailSettingScreen.kt index c12a6f42..59c06d30 100644 --- a/feature/student-activity/src/main/java/com/msg/student_activity/ActivityDetailSettingScreen.kt +++ b/feature/student-activity/src/main/java/com/msg/student_activity/ActivityDetailSettingScreen.kt @@ -55,7 +55,7 @@ fun ActivityDetailSettingScreen( ) { val creditList = listOf("1점", "2점") - val creditPointForShow = remember { mutableStateOf("{$savedCreditPoint}점") } + val creditPointForShow = remember { mutableStateOf("${savedCreditPoint}점") } val creditPoint = remember { mutableIntStateOf(if (creditPointForShow.value == "1점") 1 else if (creditPointForShow.value == "2점") 2 else 0) } val activityDateForShow = remember { mutableStateOf(savedActivityDate?.toKoreanFormat() ?: "") } val activityDate = remember { mutableStateOf(savedActivityDate) } diff --git a/settings.gradle.kts b/settings.gradle.kts index dcc15ff9..7d7c9f56 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,3 +37,4 @@ include(":feature:club") gradle.startParameter.excludedTaskNames.addAll(listOf(":build-logic:convention:testClasses")) include(":feature:email") +include(":feature:certification")