diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 02bf502b..2b0b6f49 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,15 @@
+
+
+
+
+
+
+
navController.navigateToExpoModify(id)
},
- onProgramClick = navController::navigateToHomeDetailProgram
+ onProgramClick = { id ->
+ navController.navigateToHomeDetailProgram(id)
+ }
)
homeSendMessageScreen(
@@ -142,11 +146,14 @@ fun ExpoNavHost(
homeDetailProgramScreen(
onBackClick = navController::popBackStack,
- navigateToProgramDetail = navController::navigateToHomeDetailProgramParticipant
+ navigateToProgramDetail = { id ->
+ navController.navigateToHomeDetailProgramParticipant(id)
+ }
)
homeDetailProgramParticipantScreen(
- onBackClick = navController::popBackStack
+ onBackClick = navController::popBackStack,
+ navigateToQrScanner = navController::navigateQrScanner
)
homeDetailParticipantManagementScreen(
@@ -161,5 +168,10 @@ fun ExpoNavHost(
expoCreateScreen(
onErrorToast = makeErrorToast
)
+
+ qrScannerScreen(
+ onBackClick = navController::popBackStack,
+ onPermissionBlock = navController::popBackStack
+ )
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/school_of_company/expo_android/ui/ExpoApp.kt b/app/src/main/java/com/school_of_company/expo_android/ui/ExpoApp.kt
index 67020c9b..09710a9a 100644
--- a/app/src/main/java/com/school_of_company/expo_android/ui/ExpoApp.kt
+++ b/app/src/main/java/com/school_of_company/expo_android/ui/ExpoApp.kt
@@ -69,8 +69,7 @@ fun ExpoApp(
}
) { paddingValues ->
// 네비게이션 호스트
- Box(modifier = Modifier.padding(paddingValues =
- paddingValues)) {
+ Box(modifier = Modifier.padding(paddingValues = paddingValues)) {
ExpoNavHost(appState = appState)
}
}
diff --git a/core/data/src/main/java/com/school_of_company/data/di/RepositoryModule.kt b/core/data/src/main/java/com/school_of_company/data/di/RepositoryModule.kt
index f17cc7a3..71281a7f 100644
--- a/core/data/src/main/java/com/school_of_company/data/di/RepositoryModule.kt
+++ b/core/data/src/main/java/com/school_of_company/data/di/RepositoryModule.kt
@@ -1,5 +1,7 @@
package com.school_of_company.data.di
+import com.school_of_company.data.repository.attendance.AttendanceRepository
+import com.school_of_company.data.repository.attendance.AttendanceRepositoryImpl
import com.school_of_company.data.repository.auth.AuthRepository
import com.school_of_company.data.repository.auth.AuthRepositoryImpl
import com.school_of_company.data.repository.expo.ExpoRepository
@@ -50,4 +52,9 @@ abstract class RepositoryModule {
abstract fun bindStandardRepository(
standardRepositoryImpl: StandardRepositoryImpl
) : StandardRepository
+
+ @Binds
+ abstract fun bindAttendanceRepository(
+ attendanceRepositoryImpl: AttendanceRepositoryImpl
+ ) : AttendanceRepository
}
\ No newline at end of file
diff --git a/core/data/src/main/java/com/school_of_company/data/repository/attendance/AttendanceRepository.kt b/core/data/src/main/java/com/school_of_company/data/repository/attendance/AttendanceRepository.kt
new file mode 100644
index 00000000..480712e0
--- /dev/null
+++ b/core/data/src/main/java/com/school_of_company/data/repository/attendance/AttendanceRepository.kt
@@ -0,0 +1,8 @@
+package com.school_of_company.data.repository.attendance
+
+import com.school_of_company.model.param.attendance.TrainingQrCodeRequestParam
+import kotlinx.coroutines.flow.Flow
+
+interface AttendanceRepository {
+ fun trainingQrCode(trainingId: Long, body: TrainingQrCodeRequestParam) : Flow
+}
\ No newline at end of file
diff --git a/core/data/src/main/java/com/school_of_company/data/repository/attendance/AttendanceRepositoryImpl.kt b/core/data/src/main/java/com/school_of_company/data/repository/attendance/AttendanceRepositoryImpl.kt
new file mode 100644
index 00000000..d9889efa
--- /dev/null
+++ b/core/data/src/main/java/com/school_of_company/data/repository/attendance/AttendanceRepositoryImpl.kt
@@ -0,0 +1,21 @@
+package com.school_of_company.data.repository.attendance
+
+import com.school_of_company.model.param.attendance.TrainingQrCodeRequestParam
+import com.school_of_company.network.datasource.attendance.AttendanceDataSource
+import com.school_of_company.network.mapper.attendance.request.toDto
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+class AttendanceRepositoryImpl @Inject constructor(
+ private val dataSource: AttendanceDataSource
+) : AttendanceRepository {
+ override fun trainingQrCode(
+ trainingId: Long,
+ body: TrainingQrCodeRequestParam
+ ): Flow {
+ return dataSource.trainingQrCode(
+ trainingId = trainingId,
+ body = body.toDto()
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/domain/src/main/java/com/school_of_company/domain/usecase/attendance/TrainingQrCodeRequestUseCase.kt b/core/domain/src/main/java/com/school_of_company/domain/usecase/attendance/TrainingQrCodeRequestUseCase.kt
new file mode 100644
index 00000000..9649fb33
--- /dev/null
+++ b/core/domain/src/main/java/com/school_of_company/domain/usecase/attendance/TrainingQrCodeRequestUseCase.kt
@@ -0,0 +1,19 @@
+package com.school_of_company.domain.usecase.attendance
+
+import com.school_of_company.data.repository.attendance.AttendanceRepository
+import com.school_of_company.model.param.attendance.TrainingQrCodeRequestParam
+import javax.inject.Inject
+
+class TrainingQrCodeRequestUseCase @Inject constructor(
+ private val repository: AttendanceRepository
+) {
+ operator fun invoke(
+ trainingId: Long,
+ body: TrainingQrCodeRequestParam
+ ) = runCatching {
+ repository.trainingQrCode(
+ trainingId = trainingId,
+ body = body
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/model/src/main/java/com/school_of_company/model/entity/training/TeacherTrainingProgramResponseEntity.kt b/core/model/src/main/java/com/school_of_company/model/entity/training/TeacherTrainingProgramResponseEntity.kt
index 56e8ef7d..b8405dbf 100644
--- a/core/model/src/main/java/com/school_of_company/model/entity/training/TeacherTrainingProgramResponseEntity.kt
+++ b/core/model/src/main/java/com/school_of_company/model/entity/training/TeacherTrainingProgramResponseEntity.kt
@@ -1,11 +1,12 @@
package com.school_of_company.model.entity.training
data class TeacherTrainingProgramResponseEntity(
+ val id: Long,
val name: String,
val organization: String,
val position: String,
val programName: String,
val status: Boolean,
- val entryTime: String,
- val leaveTime: String
+ val entryTime: String?,
+ val leaveTime: String?
)
\ No newline at end of file
diff --git a/core/model/src/main/java/com/school_of_company/model/param/attendance/TrainingQrCodeRequestParam.kt b/core/model/src/main/java/com/school_of_company/model/param/attendance/TrainingQrCodeRequestParam.kt
new file mode 100644
index 00000000..26f260cb
--- /dev/null
+++ b/core/model/src/main/java/com/school_of_company/model/param/attendance/TrainingQrCodeRequestParam.kt
@@ -0,0 +1,5 @@
+package com.school_of_company.model.param.attendance
+
+data class TrainingQrCodeRequestParam (
+ val traineeId: Long
+)
\ No newline at end of file
diff --git a/core/network/src/main/java/com/school_of_company/network/api/AttendanceAPI.kt b/core/network/src/main/java/com/school_of_company/network/api/AttendanceAPI.kt
new file mode 100644
index 00000000..c78f01ce
--- /dev/null
+++ b/core/network/src/main/java/com/school_of_company/network/api/AttendanceAPI.kt
@@ -0,0 +1,15 @@
+package com.school_of_company.network.api
+
+import com.school_of_company.network.dto.attendance.request.TrainingQrCodeRequest
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.Path
+
+interface AttendanceAPI {
+
+ @GET("/attendance/training/{trainingPro_id}")
+ suspend fun trainingQrCode(
+ @Path("trainingPro_id") trainingId: Long,
+ @Body body: TrainingQrCodeRequest
+ )
+}
\ No newline at end of file
diff --git a/core/network/src/main/java/com/school_of_company/network/datasource/attendance/AttendanceDataSource.kt b/core/network/src/main/java/com/school_of_company/network/datasource/attendance/AttendanceDataSource.kt
new file mode 100644
index 00000000..ab688288
--- /dev/null
+++ b/core/network/src/main/java/com/school_of_company/network/datasource/attendance/AttendanceDataSource.kt
@@ -0,0 +1,8 @@
+package com.school_of_company.network.datasource.attendance
+
+import com.school_of_company.network.dto.attendance.request.TrainingQrCodeRequest
+import kotlinx.coroutines.flow.Flow
+
+interface AttendanceDataSource {
+ fun trainingQrCode(trainingId: Long, body: TrainingQrCodeRequest) : Flow
+}
\ No newline at end of file
diff --git a/core/network/src/main/java/com/school_of_company/network/datasource/attendance/AttendanceDataSourceImpl.kt b/core/network/src/main/java/com/school_of_company/network/datasource/attendance/AttendanceDataSourceImpl.kt
new file mode 100644
index 00000000..1d4f66a2
--- /dev/null
+++ b/core/network/src/main/java/com/school_of_company/network/datasource/attendance/AttendanceDataSourceImpl.kt
@@ -0,0 +1,20 @@
+package com.school_of_company.network.datasource.attendance
+
+import com.school_of_company.network.api.AttendanceAPI
+import com.school_of_company.network.dto.attendance.request.TrainingQrCodeRequest
+import com.school_of_company.network.util.performApiRequest
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+class AttendanceDataSourceImpl @Inject constructor(
+ private val service: AttendanceAPI
+) : AttendanceDataSource {
+ override fun trainingQrCode(
+ trainingId: Long,
+ body: TrainingQrCodeRequest
+ ): Flow =
+ performApiRequest { service.trainingQrCode(
+ body = body,
+ trainingId = trainingId
+ ) }
+}
\ No newline at end of file
diff --git a/core/network/src/main/java/com/school_of_company/network/di/NetworkModule.kt b/core/network/src/main/java/com/school_of_company/network/di/NetworkModule.kt
index cca88a9b..50c9936b 100644
--- a/core/network/src/main/java/com/school_of_company/network/di/NetworkModule.kt
+++ b/core/network/src/main/java/com/school_of_company/network/di/NetworkModule.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.util.Log
import com.readystatesoftware.chuck.ChuckInterceptor
import com.school_of_company.network.BuildConfig
+import com.school_of_company.network.api.AttendanceAPI
import com.school_of_company.network.api.AuthAPI
import com.school_of_company.network.api.ExpoAPI
import com.school_of_company.network.api.ImageAPI
@@ -114,4 +115,7 @@ object NetworkModule {
fun provideStandardAPI(retrofit: Retrofit) : StandardAPI =
retrofit.create(StandardAPI::class.java)
+ @Provides
+ fun provideAttendanceAPI(retrofit: Retrofit) : AttendanceAPI =
+ retrofit.create(AttendanceAPI::class.java)
}
\ No newline at end of file
diff --git a/core/network/src/main/java/com/school_of_company/network/di/RemoteDataSourceModule.kt b/core/network/src/main/java/com/school_of_company/network/di/RemoteDataSourceModule.kt
index 2fcb291f..aa477e70 100644
--- a/core/network/src/main/java/com/school_of_company/network/di/RemoteDataSourceModule.kt
+++ b/core/network/src/main/java/com/school_of_company/network/di/RemoteDataSourceModule.kt
@@ -1,5 +1,7 @@
package com.school_of_company.network.di
+import com.school_of_company.network.datasource.attendance.AttendanceDataSource
+import com.school_of_company.network.datasource.attendance.AttendanceDataSourceImpl
import com.school_of_company.network.datasource.auth.AuthDataSource
import com.school_of_company.network.datasource.auth.AuthDataSourceImpl
import com.school_of_company.network.datasource.expo.ExpoDataSource
@@ -50,4 +52,9 @@ abstract class RemoteDataSourceModule {
abstract fun bindStandardRemoteDataSource(
standardDataSourceImpl: StandardDataSourceImpl
) : StandardDataSource
+
+ @Binds
+ abstract fun bindAttendanceRemoteDataSource(
+ attendanceDataSourceImpl: AttendanceDataSourceImpl
+ ) : AttendanceDataSource
}
\ No newline at end of file
diff --git a/core/network/src/main/java/com/school_of_company/network/dto/attendance/request/TrainingQrCodeRequest.kt b/core/network/src/main/java/com/school_of_company/network/dto/attendance/request/TrainingQrCodeRequest.kt
new file mode 100644
index 00000000..879afe13
--- /dev/null
+++ b/core/network/src/main/java/com/school_of_company/network/dto/attendance/request/TrainingQrCodeRequest.kt
@@ -0,0 +1,9 @@
+package com.school_of_company.network.dto.attendance.request
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class TrainingQrCodeRequest(
+ @Json(name = "traineeId") val traineeId: Long
+)
\ No newline at end of file
diff --git a/core/network/src/main/java/com/school_of_company/network/dto/training/response/TeacherTrainingProgramResponse.kt b/core/network/src/main/java/com/school_of_company/network/dto/training/response/TeacherTrainingProgramResponse.kt
index d7535e53..849368df 100644
--- a/core/network/src/main/java/com/school_of_company/network/dto/training/response/TeacherTrainingProgramResponse.kt
+++ b/core/network/src/main/java/com/school_of_company/network/dto/training/response/TeacherTrainingProgramResponse.kt
@@ -5,11 +5,12 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class TeacherTrainingProgramResponse(
+ @Json(name = "id") val id: Long,
@Json(name = "name") val name: String,
@Json(name = "organization") val organization: String,
@Json(name = "position") val position: String,
@Json(name = "programName") val programName: String,
@Json(name = "status") val status: Boolean,
- @Json(name = "entryTime") val entryTime: String,
- @Json(name = "leaveTime") val leaveTime: String
+ @Json(name = "entryTime") val entryTime: String?,
+ @Json(name = "leaveTime") val leaveTime: String?
)
diff --git a/core/network/src/main/java/com/school_of_company/network/mapper/attendance/request/TrainingQrCodeRequestMapper.kt b/core/network/src/main/java/com/school_of_company/network/mapper/attendance/request/TrainingQrCodeRequestMapper.kt
new file mode 100644
index 00000000..4fb66ab7
--- /dev/null
+++ b/core/network/src/main/java/com/school_of_company/network/mapper/attendance/request/TrainingQrCodeRequestMapper.kt
@@ -0,0 +1,7 @@
+package com.school_of_company.network.mapper.attendance.request
+
+import com.school_of_company.model.param.attendance.TrainingQrCodeRequestParam
+import com.school_of_company.network.dto.attendance.request.TrainingQrCodeRequest
+
+fun TrainingQrCodeRequestParam.toDto(): TrainingQrCodeRequest =
+ TrainingQrCodeRequest(traineeId = traineeId)
\ No newline at end of file
diff --git a/core/network/src/main/java/com/school_of_company/network/mapper/training/response/TeacherTrainingProgramResponseMapper.kt b/core/network/src/main/java/com/school_of_company/network/mapper/training/response/TeacherTrainingProgramResponseMapper.kt
index 94a54d09..ab66357b 100644
--- a/core/network/src/main/java/com/school_of_company/network/mapper/training/response/TeacherTrainingProgramResponseMapper.kt
+++ b/core/network/src/main/java/com/school_of_company/network/mapper/training/response/TeacherTrainingProgramResponseMapper.kt
@@ -5,6 +5,7 @@ import com.school_of_company.network.dto.training.response.TeacherTrainingProgra
fun TeacherTrainingProgramResponse.toEntity(): TeacherTrainingProgramResponseEntity =
TeacherTrainingProgramResponseEntity(
+ id = this.id,
name = this.name,
organization = this.organization,
position = this.position,
diff --git a/feature/expo/src/main/java/com/school_of_company/expo/navigation/ExpoNavigation.kt b/feature/expo/src/main/java/com/school_of_company/expo/navigation/ExpoNavigation.kt
index e1804203..591a9bb5 100644
--- a/feature/expo/src/main/java/com/school_of_company/expo/navigation/ExpoNavigation.kt
+++ b/feature/expo/src/main/java/com/school_of_company/expo/navigation/ExpoNavigation.kt
@@ -58,7 +58,7 @@ fun NavGraphBuilder.expoDetailScreen(
onCheckClick: () -> Unit,
onQrGenerateClick: () -> Unit,
onModifyClick: (String) -> Unit,
- onProgramClick: () -> Unit
+ onProgramClick: (String) -> Unit
) {
composable(route = "$expoDetailRoute/{id}") { backStackEntry ->
val id = backStackEntry.arguments?.getString("id") ?: ""
diff --git a/feature/expo/src/main/java/com/school_of_company/expo/view/ExpoCreateScreen.kt b/feature/expo/src/main/java/com/school_of_company/expo/view/ExpoCreateScreen.kt
index e411dc83..a9c4a4e1 100644
--- a/feature/expo/src/main/java/com/school_of_company/expo/view/ExpoCreateScreen.kt
+++ b/feature/expo/src/main/java/com/school_of_company/expo/view/ExpoCreateScreen.kt
@@ -135,8 +135,8 @@ internal fun ExpoCreateRoute(
description = viewModel.introduce_title.value,
location = viewModel.location.value,
coverImage = (imageUpLoadUiState as ImageUpLoadUiState.Success).data.imageURL,
- x = 35.14308f,
- y = 126.80043f
+ x = 37.511734f,
+ y = 127.05905f
)
)
}
diff --git a/feature/expo/src/main/java/com/school_of_company/expo/view/ExpoDetailScreen.kt b/feature/expo/src/main/java/com/school_of_company/expo/view/ExpoDetailScreen.kt
index c8d1f1b4..a1945e9a 100644
--- a/feature/expo/src/main/java/com/school_of_company/expo/view/ExpoDetailScreen.kt
+++ b/feature/expo/src/main/java/com/school_of_company/expo/view/ExpoDetailScreen.kt
@@ -58,7 +58,7 @@ internal fun ExpoDetailRoute(
onCheckClick: () -> Unit,
onQrGenerateClick: () -> Unit,
onModifyClick: (String) -> Unit,
- onProgramClick: () -> Unit,
+ onProgramClick: (String) -> Unit,
viewModel: ExpoViewModel = hiltViewModel()
) {
val getExpoInformationUiState by viewModel.getExpoInformationUiState.collectAsStateWithLifecycle()
@@ -92,7 +92,7 @@ internal fun ExpoDetailScreen(
onCheckClick: () -> Unit,
onQrGenerateClick: () -> Unit,
onModifyClick: (String) -> Unit,
- onProgramClick: () -> Unit
+ onProgramClick: (String) -> Unit
) {
val (openDialog, isOpenDialog) = rememberSaveable { mutableStateOf(false) }
val (openQrDialog, isOpenQrDialog) = rememberSaveable { mutableStateOf(false) }
@@ -296,7 +296,7 @@ internal fun ExpoDetailScreen(
ExpoEnableDetailButton(
text = "프로그램",
- onClick = onProgramClick,
+ onClick = { onProgramClick(id) },
modifier = Modifier
.fillMaxWidth()
.border(
diff --git a/feature/expo/src/main/java/com/school_of_company/expo/view/component/ExpoBottomSheet.kt b/feature/expo/src/main/java/com/school_of_company/expo/view/component/ExpoBottomSheet.kt
index 7cc24c34..d2054d4e 100644
--- a/feature/expo/src/main/java/com/school_of_company/expo/view/component/ExpoBottomSheet.kt
+++ b/feature/expo/src/main/java/com/school_of_company/expo/view/component/ExpoBottomSheet.kt
@@ -96,6 +96,7 @@ private fun HomeBottomSheetOptions(
indication = ripple(color = rippleColor),
interactionSource = remember { MutableInteractionSource() }
)
+ .padding(horizontal = 12.dp)
) {
Text(
text = text,
diff --git a/feature/expo/src/main/java/com/school_of_company/expo/viewmodel/ExpoViewModel.kt b/feature/expo/src/main/java/com/school_of_company/expo/viewmodel/ExpoViewModel.kt
index 83381dd6..6b1232ff 100644
--- a/feature/expo/src/main/java/com/school_of_company/expo/viewmodel/ExpoViewModel.kt
+++ b/feature/expo/src/main/java/com/school_of_company/expo/viewmodel/ExpoViewModel.kt
@@ -33,6 +33,7 @@ import com.school_of_company.model.model.expo.ExpoRequestAndResponseModel
import com.school_of_company.model.model.standard.StandardRequestModel
import com.school_of_company.model.model.training.TrainingDtoModel
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts
index f82eb2c2..1fe3c42d 100644
--- a/feature/home/build.gradle.kts
+++ b/feature/home/build.gradle.kts
@@ -13,4 +13,18 @@ dependencies {
implementation(libs.mlkit)
implementation(libs.zxing.core)
+
+ implementation(libs.swiperefresh)
+
+ implementation(libs.camera.core)
+ implementation(libs.camera.view)
+ implementation(libs.camera.camera2)
+ implementation(libs.camera.lifecycle)
+ implementation(libs.camera.extensions)
+
+ implementation(libs.accompanist.permission)
+
+ implementation(libs.kotlinx.coroutines.core)
+ implementation(libs.kotlinx.coroutines.play.services)
+
}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/navigation/HomeNavigation.kt b/feature/home/src/main/java/com/school_of_company/home/navigation/HomeNavigation.kt
index c0d3c2c2..81022cde 100644
--- a/feature/home/src/main/java/com/school_of_company/home/navigation/HomeNavigation.kt
+++ b/feature/home/src/main/java/com/school_of_company/home/navigation/HomeNavigation.kt
@@ -7,23 +7,37 @@ import androidx.navigation.compose.composable
import com.school_of_company.home.view.HomeDetailParticipantManagementRoute
import com.school_of_company.home.view.HomeDetailProgramParticipantRoute
import com.school_of_company.home.view.HomeDetailProgramRoute
+import com.school_of_company.home.view.QrScannerRoute
import com.school_of_company.home.view.SendMessageRoute
const val homeSendMessageRoute = "home_send_message_route"
const val homeDetailProgramRoute = "home_detail_program_route"
const val homeDetailProgramParticipantRoute = "home_detail_program_participant_route"
const val homeDetailParticipantManagementRoute = "home_detail_participant_management_route"
+const val qrScannerRoute = "qr_scanner_route"
fun NavController.navigateToHomeSendMessage(navOptions: NavOptions? = null) {
this.navigate(homeSendMessageRoute, navOptions)
}
-fun NavController.navigateToHomeDetailProgram(navOptions: NavOptions? = null) {
- this.navigate(homeDetailProgramRoute, navOptions)
+fun NavController.navigateToHomeDetailProgram(
+ id: String,
+ navOptions: NavOptions? = null
+) {
+ this.navigate(
+ route = "$homeDetailProgramRoute/${id}",
+ navOptions
+ )
}
-fun NavController.navigateToHomeDetailProgramParticipant(navOptions: NavOptions? = null) {
- this.navigate(homeDetailProgramParticipantRoute, navOptions)
+fun NavController.navigateToHomeDetailProgramParticipant(
+ id: Long,
+ navOptions: NavOptions? = null
+) {
+ this.navigate(
+ route = "$homeDetailProgramParticipantRoute/${id}",
+ navOptions
+ )
}
fun NavController.navigateToHomeDetailParticipantManagement(navOptions: NavOptions? = null) {
@@ -40,12 +54,25 @@ fun NavGraphBuilder.homeSendMessageScreen(
}
}
+fun NavController.navigateQrScanner(
+ id: Long,
+ traineeId: Long,
+ navOptions: NavOptions? = null
+) {
+ this.navigate(
+ route = "$qrScannerRoute/${id}/${traineeId}",
+ navOptions
+ )
+}
+
fun NavGraphBuilder.homeDetailProgramScreen(
onBackClick: () -> Unit,
- navigateToProgramDetail: () -> Unit
+ navigateToProgramDetail: (Long) -> Unit
) {
- composable(route = homeDetailProgramRoute) {
+ composable(route = "$homeDetailProgramRoute/{id}") { backStackEntry ->
+ val id = backStackEntry.arguments?.getString("id") ?: ""
HomeDetailProgramRoute(
+ id = id,
onBackClick = onBackClick,
navigateToProgramDetail = navigateToProgramDetail
)
@@ -53,12 +80,18 @@ fun NavGraphBuilder.homeDetailProgramScreen(
}
fun NavGraphBuilder.homeDetailProgramParticipantScreen(
- onBackClick: () -> Unit
+ onBackClick: () -> Unit,
+ navigateToQrScanner: (Long, Long) -> Unit
) {
- composable(route = homeDetailProgramParticipantRoute) {
- HomeDetailProgramParticipantRoute(
- onBackClick = onBackClick
- )
+ composable(route = "$homeDetailProgramParticipantRoute/{id}") { backStackEntry ->
+ val id = backStackEntry.arguments?.getString("id")?.toLongOrNull()
+ if (id != null) {
+ HomeDetailProgramParticipantRoute(
+ id = id,
+ onBackClick = onBackClick,
+ navigateToQrScanner = navigateToQrScanner
+ )
+ }
}
}
@@ -70,4 +103,22 @@ fun NavGraphBuilder.homeDetailParticipantManagementScreen(
onBackClick = onBackClick
)
}
+}
+
+fun NavGraphBuilder.qrScannerScreen(
+ onBackClick: () -> Unit,
+ onPermissionBlock: () -> Unit
+) {
+ composable(route = "$qrScannerRoute/{id}/{traineeId}") { backStackEntry ->
+ val id = backStackEntry.arguments?.getString("id")?.toLongOrNull()
+ val traineeId = backStackEntry.arguments?.getString("traineeId")?.toLongOrNull()
+ if (id != null && traineeId != null) {
+ QrScannerRoute(
+ id = id,
+ traineeId = traineeId,
+ onBackClick = onBackClick,
+ onPermissionBlock = onPermissionBlock
+ )
+ }
+ }
}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/util/QrcodeScanner.kt b/feature/home/src/main/java/com/school_of_company/home/util/QrcodeScanner.kt
new file mode 100644
index 00000000..f2aabe1a
--- /dev/null
+++ b/feature/home/src/main/java/com/school_of_company/home/util/QrcodeScanner.kt
@@ -0,0 +1,61 @@
+package com.school_of_company.home.util
+
+import android.util.Log
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageProxy
+import com.google.mlkit.vision.barcode.BarcodeScannerOptions
+import com.google.mlkit.vision.barcode.BarcodeScanning
+import com.google.mlkit.vision.barcode.common.Barcode
+import com.google.mlkit.vision.common.InputImage
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.tasks.await
+
+class QrcodeScanner(
+ private val qrcodeData: (Long) -> Unit
+) : ImageAnalysis.Analyzer {
+
+ private val scanner = BarcodeScanning.getClient(
+ BarcodeScannerOptions.Builder()
+ .setBarcodeFormats(
+ Barcode.FORMAT_QR_CODE,
+ Barcode.FORMAT_CODE_128,
+ Barcode.FORMAT_CODE_39
+ )
+ .build()
+ )
+
+ @androidx.annotation.OptIn(androidx.camera.core.ExperimentalGetImage::class)
+ override fun analyze(imageProxy: ImageProxy) {
+ val mediaImage = imageProxy.image
+ if (mediaImage != null) {
+ val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val barcodes = scanner.process(image).await()
+ for (barcode in barcodes) {
+ val rawValue = barcode.displayValue
+ Log.d("qrcode", "Raw QR code value: $rawValue")
+
+ if (rawValue != null) {
+ try {
+ val qrCodeValue = rawValue.toLong()
+ qrcodeData(qrCodeValue)
+ Log.d("qrcode", "QR code scan succeeded: $qrCodeValue")
+ } catch (e: NumberFormatException) {
+ Log.d("qrcode", "QR code contains non-numeric value: $rawValue")
+ }
+ }
+ }
+ } catch (e: Exception) {
+ Log.e("qrcode", "Scan failed: ${e.message}")
+ } finally {
+ imageProxy.close()
+ }
+ }
+ } else {
+ imageProxy.close()
+ }
+ }
+}
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/HomeDetailProgramParticipantScreen.kt b/feature/home/src/main/java/com/school_of_company/home/view/HomeDetailProgramParticipantScreen.kt
index adb7403a..12e00305 100644
--- a/feature/home/src/main/java/com/school_of_company/home/view/HomeDetailProgramParticipantScreen.kt
+++ b/feature/home/src/main/java/com/school_of_company/home/view/HomeDetailProgramParticipantScreen.kt
@@ -17,46 +17,74 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.google.accompanist.swiperefresh.SwipeRefresh
+import com.google.accompanist.swiperefresh.SwipeRefreshIndicator
+import com.google.accompanist.swiperefresh.SwipeRefreshState
+import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.school_of_company.design_system.component.modifier.clickable.expoClickable
import com.school_of_company.design_system.component.topbar.ExpoTopBar
import com.school_of_company.design_system.icon.LeftArrowIcon
import com.school_of_company.design_system.icon.WarnIcon
import com.school_of_company.design_system.theme.ExpoAndroidTheme
-import com.school_of_company.home.view.component.HomeDetailProgramParticipantData
import com.school_of_company.home.view.component.HomeDetailProgramParticipantList
-import kotlinx.collections.immutable.ImmutableList
-import kotlinx.collections.immutable.toPersistentList
-
-fun generateParticipantSampleData(): ImmutableList {
- return List(20) {
- HomeDetailProgramParticipantData(
- name = "이명훈",
- company = "초등학교",
- position = "교사",
- schoolSubject = "컴퓨터공학",
- phone = "010-1234-5678"
- )
- }.toPersistentList()
-}
+import com.school_of_company.home.view.component.QrButton
+import com.school_of_company.home.viewmodel.HomeViewModel
+import com.school_of_company.home.viewmodel.uistate.TeacherTrainingProgramListUiState
+import kotlinx.collections.immutable.toImmutableList
@Composable
internal fun HomeDetailProgramParticipantRoute(
- onBackClick: () -> Unit
+ id: Long,
+ onBackClick: () -> Unit,
+ navigateToQrScanner: (Long, Long) -> Unit,
+ viewModel: HomeViewModel = hiltViewModel()
) {
+ val swipeRefreshLoading by viewModel.swipeRefreshLoading.collectAsStateWithLifecycle()
+ val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = swipeRefreshLoading)
+
+ val teacherTrainingProgramListUiState by viewModel.teacherTrainingProgramListUiState.collectAsStateWithLifecycle()
+
+ val traineeId: Long = when (teacherTrainingProgramListUiState) {
+ is TeacherTrainingProgramListUiState.Success -> {
+ (teacherTrainingProgramListUiState as TeacherTrainingProgramListUiState.Success).data.firstOrNull()?.id
+ ?: -1L
+ }
+
+ else -> -1L
+ }
+
HomeDetailProgramParticipantScreen(
+ id = id,
onBackClick = onBackClick,
- participantData = generateParticipantSampleData()
+ teacherTrainingProgramListUiState = teacherTrainingProgramListUiState,
+ swipeRefreshState = swipeRefreshState,
+ getTeacherTrainingProgramList = { viewModel.teacherTrainingProgramList(id) },
+ navigateToQrScanner = navigateToQrScanner,
+ traineeId = traineeId
)
+
+ LaunchedEffect(Unit) {
+ viewModel.teacherTrainingProgramList(id)
+ }
}
@Composable
internal fun HomeDetailProgramParticipantScreen(
+ id: Long,
modifier: Modifier = Modifier,
- participantData: ImmutableList,
+ swipeRefreshState: SwipeRefreshState,
+ teacherTrainingProgramListUiState: TeacherTrainingProgramListUiState,
+ getTeacherTrainingProgramList: () -> Unit,
+ traineeId: Long,
+ navigateToQrScanner: (Long, Long) -> Unit,
onBackClick: () -> Unit
) {
ExpoAndroidTheme { colors, typography ->
@@ -79,17 +107,28 @@ internal fun HomeDetailProgramParticipantScreen(
Spacer(modifier = Modifier.height(28.dp))
- Text(
- text = "프로그램",
- style = typography.bodyBold2,
- color = colors.black,
- modifier = Modifier.padding(start = 16.dp)
- )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(
+ text = "프로그램",
+ style = typography.bodyBold2,
+ color = colors.black,
+ modifier = Modifier.padding(start = 16.dp)
+ )
+
+ QrButton(
+ onClick = { navigateToQrScanner(id, traineeId) },
+ modifier = Modifier.padding(end = 16.dp)
+ )
+ }
Spacer(modifier = Modifier.height(12.dp))
Row(modifier = Modifier.padding(horizontal = 16.dp)) {
- Row(horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.Start),) {
+ Row(horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.Start)) {
WarnIcon(
tint = colors.gray300,
modifier = Modifier.size(16.dp)
@@ -120,12 +159,19 @@ internal fun HomeDetailProgramParticipantScreen(
style = typography.captionRegular2,
color = colors.gray500
)
+ when (teacherTrainingProgramListUiState) {
+ is TeacherTrainingProgramListUiState.Loading -> Unit
+ is TeacherTrainingProgramListUiState.Success -> {
+ Text(
+ text = "${teacherTrainingProgramListUiState.data.size}명",
+ style = typography.captionRegular2,
+ color = colors.main
+ )
+ }
- Text(
- text = "${participantData.size}명",
- style = typography.captionRegular2,
- color = colors.main
- )
+ is TeacherTrainingProgramListUiState.Error -> Unit
+ is TeacherTrainingProgramListUiState.Empty -> Unit
+ }
}
}
@@ -138,9 +184,7 @@ internal fun HomeDetailProgramParticipantScreen(
color = colors.gray200
)
.fillMaxWidth()
- .padding(
- vertical = 16.dp
- )
+ .padding(vertical = 16.dp)
) {
Row(
modifier = Modifier
@@ -150,7 +194,7 @@ internal fun HomeDetailProgramParticipantScreen(
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
Spacer(modifier = Modifier.width(20.dp))
-
+
Text(
text = "성명",
style = typography.captionBold1,
@@ -184,7 +228,27 @@ internal fun HomeDetailProgramParticipantScreen(
}
}
- HomeDetailProgramParticipantList(item = participantData)
+ SwipeRefresh(
+ state = swipeRefreshState,
+ onRefresh = { getTeacherTrainingProgramList() },
+ indicator = { state, refreshTrigger ->
+ SwipeRefreshIndicator(
+ state = state,
+ refreshTriggerDistance = refreshTrigger,
+ contentColor = colors.main
+ )
+ }
+ ) {
+ when (teacherTrainingProgramListUiState) {
+ is TeacherTrainingProgramListUiState.Loading -> Unit
+ is TeacherTrainingProgramListUiState.Success -> HomeDetailProgramParticipantList(
+ item = teacherTrainingProgramListUiState.data.toImmutableList()
+ )
+
+ is TeacherTrainingProgramListUiState.Error -> Unit
+ is TeacherTrainingProgramListUiState.Empty -> Unit
+ }
+ }
}
}
}
@@ -192,8 +256,5 @@ internal fun HomeDetailProgramParticipantScreen(
@Preview
@Composable
private fun HomeDetailProgramParticipantScreenPreview() {
- HomeDetailProgramParticipantScreen(
- onBackClick = {},
- participantData = generateParticipantSampleData()
- )
+
}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/HomeDetailProgramScreen.kt b/feature/home/src/main/java/com/school_of_company/home/view/HomeDetailProgramScreen.kt
index fce36051..2ebbe5c9 100644
--- a/feature/home/src/main/java/com/school_of_company/home/view/HomeDetailProgramScreen.kt
+++ b/feature/home/src/main/java/com/school_of_company/home/view/HomeDetailProgramScreen.kt
@@ -19,6 +19,8 @@ import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -26,49 +28,71 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.google.accompanist.swiperefresh.SwipeRefresh
+import com.google.accompanist.swiperefresh.SwipeRefreshIndicator
+import com.google.accompanist.swiperefresh.SwipeRefreshState
+import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.school_of_company.design_system.component.modifier.clickable.expoClickable
import com.school_of_company.design_system.component.topbar.ExpoTopBar
import com.school_of_company.design_system.icon.LeftArrowIcon
import com.school_of_company.design_system.theme.ExpoAndroidTheme
import com.school_of_company.home.view.component.ProgramList
import com.school_of_company.home.view.component.ProgramTabRowItem
-import com.school_of_company.home.view.component.ProgramTempList
-import kotlinx.collections.immutable.ImmutableList
+import com.school_of_company.home.view.component.StandardProgramList
+import com.school_of_company.home.viewmodel.HomeViewModel
+import com.school_of_company.home.viewmodel.uistate.StandardProgramListUiState
+import com.school_of_company.home.viewmodel.uistate.TrainingProgramListUiState
import kotlinx.collections.immutable.persistentListOf
-import kotlinx.collections.immutable.toPersistentList
+import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-fun generateProgramSampleData(): ImmutableList {
- return List(20) {
- ProgramTempList(
- programName = "프로그램 이름",
- check = true,
- must = false
- )
- }.toPersistentList()
-}
-
@Composable
internal fun HomeDetailProgramRoute(
+ id: String,
onBackClick: () -> Unit,
- navigateToProgramDetail: () -> Unit
+ navigateToProgramDetail: (Long) -> Unit,
+ viewModel: HomeViewModel = hiltViewModel()
) {
+ val swipeRefreshLoading by viewModel.swipeRefreshLoading.collectAsStateWithLifecycle()
+ val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = swipeRefreshLoading)
+
+ val trainingProgramListUiState by viewModel.trainingProgramListUiState.collectAsStateWithLifecycle()
+ val standardProgramListUiState by viewModel.standardProgramListUiState.collectAsStateWithLifecycle()
+
+
HomeDetailProgramScreen(
- programItem = generateProgramSampleData(),
+ id = id,
onBackClick = onBackClick,
- navigateToProgramDetail = navigateToProgramDetail
+ navigateToProgramDetail = navigateToProgramDetail,
+ trainingProgramUiState = trainingProgramListUiState,
+ standardProgramListUiState = standardProgramListUiState,
+ swipeRefreshState = swipeRefreshState,
+ getTrainingProgramList = { viewModel.trainingProgramList(id) },
+ getStandardProgramList = { viewModel.standardProgramList(id) }
)
+
+ LaunchedEffect(id) {
+ viewModel.trainingProgramList(id)
+ viewModel.standardProgramList(id)
+ }
}
@Composable
internal fun HomeDetailProgramScreen(
+ id: String,
modifier: Modifier = Modifier,
+ swipeRefreshState: SwipeRefreshState,
+ trainingProgramUiState: TrainingProgramListUiState,
+ standardProgramListUiState: StandardProgramListUiState,
pagerState: PagerState = rememberPagerState(pageCount = { 2 }),
coroutineScope: CoroutineScope = rememberCoroutineScope(),
- programItem: ImmutableList,
onBackClick: () -> Unit,
- navigateToProgramDetail: () -> Unit,
+ navigateToProgramDetail: (Long) -> Unit,
+ getTrainingProgramList: () -> Unit,
+ getStandardProgramList: () -> Unit
) {
ExpoAndroidTheme { colors, typography ->
Column(
@@ -80,10 +104,12 @@ internal fun HomeDetailProgramScreen(
) {
ExpoTopBar(
- startIcon = { LeftArrowIcon(
+ startIcon = {
+ LeftArrowIcon(
tint = colors.black,
modifier = Modifier.expoClickable { onBackClick() }
- ) },
+ )
+ },
betweenText = "프로그램",
modifier = Modifier.padding(horizontal = 16.dp)
)
@@ -99,7 +125,7 @@ internal fun HomeDetailProgramScreen(
modifier = modifier.tabIndicatorOffset(tabPositions[pagerState.currentPage]),
)
},
- modifier = Modifier.width(230.dp)
+ modifier = Modifier.width(280.dp)
) {
persistentListOf(
"일반 프로그램",
@@ -160,20 +186,50 @@ internal fun HomeDetailProgramScreen(
}
}
- HorizontalPager(state = pagerState) { page ->
- when (page) {
- 0 -> {
- ProgramList(
- item = programItem,
- navigateToProgramDetail = navigateToProgramDetail
- )
- }
-
- 1 -> {
- ProgramList(
- item = programItem,
- navigateToProgramDetail = navigateToProgramDetail
- )
+ SwipeRefresh(
+ state = swipeRefreshState,
+ onRefresh = {
+ getTrainingProgramList()
+ getStandardProgramList()
+ },
+ indicator = { state, refreshTrigger ->
+ SwipeRefreshIndicator(
+ state = state,
+ refreshTriggerDistance = refreshTrigger,
+ contentColor = colors.main
+ )
+ }
+ ) {
+ HorizontalPager(state = pagerState) { page ->
+ when (page) {
+ 0 -> {
+ when (standardProgramListUiState) {
+ is StandardProgramListUiState.Loading -> Unit
+ is StandardProgramListUiState.Success -> {
+ StandardProgramList(
+ standardItem = standardProgramListUiState.data.toImmutableList(),
+ navigateToProgramDetail = navigateToProgramDetail
+ )
+ }
+ is StandardProgramListUiState.Empty -> Unit
+ is StandardProgramListUiState.Error -> Unit
+ }
+ }
+
+ 1 -> {
+ when (trainingProgramUiState) {
+ is TrainingProgramListUiState.Loading -> Unit
+ is TrainingProgramListUiState.Success -> {
+ ProgramList(
+ trainingItem = trainingProgramUiState.data.toImmutableList(),
+ navigateToProgramDetail = navigateToProgramDetail
+ )
+ }
+
+ is TrainingProgramListUiState.Empty -> Unit
+ is TrainingProgramListUiState.Error -> Unit
+ }
+ }
}
}
}
@@ -185,20 +241,13 @@ internal fun HomeDetailProgramScreen(
@Composable
private fun HomeDetailProgramScreenPreview() {
HomeDetailProgramScreen(
- programItem = persistentListOf(
- ProgramTempList(
- programName = "adsfasfas",
- check = true,
- must = true
- ),
-
- ProgramTempList(
- programName = "adsfasfas",
- check = true,
- must = true
- ),
- ),
onBackClick = {},
- navigateToProgramDetail = {}
+ navigateToProgramDetail = {},
+ id = "",
+ trainingProgramUiState = TrainingProgramListUiState.Loading,
+ standardProgramListUiState = StandardProgramListUiState.Loading,
+ swipeRefreshState = rememberSwipeRefreshState(isRefreshing = false),
+ getTrainingProgramList = {},
+ getStandardProgramList = {}
)
}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/QrScannerScreen.kt b/feature/home/src/main/java/com/school_of_company/home/view/QrScannerScreen.kt
new file mode 100644
index 00000000..f02efb5f
--- /dev/null
+++ b/feature/home/src/main/java/com/school_of_company/home/view/QrScannerScreen.kt
@@ -0,0 +1,114 @@
+package com.school_of_company.home.view
+
+import android.Manifest
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.isGranted
+import com.google.accompanist.permissions.rememberPermissionState
+import com.google.accompanist.permissions.shouldShowRationale
+import com.school_of_company.design_system.component.modifier.clickable.expoClickable
+import com.school_of_company.design_system.component.topbar.ExpoTopBar
+import com.school_of_company.design_system.icon.LeftArrowIcon
+import com.school_of_company.design_system.theme.ExpoAndroidTheme
+import com.school_of_company.home.view.component.QrcodeScanView
+import com.school_of_company.home.viewmodel.HomeViewModel
+import com.school_of_company.home.viewmodel.uistate.TrainingQrCodeUiState
+import com.school_of_company.model.param.attendance.TrainingQrCodeRequestParam
+import com.school_of_company.ui.toast.makeToast
+
+@OptIn(ExperimentalPermissionsApi::class)
+@Composable
+internal fun QrScannerRoute(
+ id: Long,
+ traineeId: Long,
+ onBackClick: () -> Unit,
+ onPermissionBlock: () -> Unit,
+ viewModel: HomeViewModel = hiltViewModel()
+) {
+ val trainingQrCodeUiState by viewModel.trainingQrCodeUiState.collectAsStateWithLifecycle()
+
+ val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
+
+ val context = LocalContext.current
+
+ LaunchedEffect("getPermission") {
+ if (!cameraPermissionState.status.isGranted && !cameraPermissionState.status.shouldShowRationale) {
+ cameraPermissionState.launchPermissionRequest()
+ }
+ }
+
+ LaunchedEffect(trainingQrCodeUiState) {
+ when (trainingQrCodeUiState) {
+ is TrainingQrCodeUiState.Loading -> {
+ makeToast(context, "로딩중..")
+ }
+ is TrainingQrCodeUiState.Success -> {
+ makeToast(context, "인식 성공!")
+ onBackClick()
+ }
+ is TrainingQrCodeUiState.Error -> {
+ makeToast(context, "인식을 하지 못하였습니다.")
+ }
+ }
+ }
+
+ if (cameraPermissionState.status.isGranted) {
+ QrScannerScreen(
+ onBackClick = onBackClick,
+ onQrcodeScan = {
+ viewModel.trainingQrCode(
+ trainingId = id,
+ body = TrainingQrCodeRequestParam(traineeId = traineeId)
+ )
+ }
+ )
+ } else {
+ onPermissionBlock()
+ }
+}
+
+@androidx.annotation.OptIn(androidx.camera.core.ExperimentalGetImage::class)
+@Composable
+internal fun QrScannerScreen(
+ modifier: Modifier = Modifier,
+ onBackClick: () -> Unit,
+ onQrcodeScan: (Long) -> Unit,
+) {
+ ExpoAndroidTheme { colors, _ ->
+
+ QrcodeScanView(onQrcodeScan = onQrcodeScan)
+
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .navigationBarsPadding()
+ .statusBarsPadding()
+ .padding(horizontal = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ ExpoTopBar(
+ startIcon = {
+ LeftArrowIcon(
+ tint = colors.white,
+ modifier = Modifier
+ .expoClickable { onBackClick() }
+ .padding(top = 16.dp)
+ )
+ }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/component/HomeDetailProgramParticipantList.kt b/feature/home/src/main/java/com/school_of_company/home/view/component/HomeDetailProgramParticipantList.kt
index 11115d3d..2ed294ef 100644
--- a/feature/home/src/main/java/com/school_of_company/home/view/component/HomeDetailProgramParticipantList.kt
+++ b/feature/home/src/main/java/com/school_of_company/home/view/component/HomeDetailProgramParticipantList.kt
@@ -6,17 +6,19 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.school_of_company.design_system.theme.ExpoAndroidTheme
+import com.school_of_company.model.entity.training.TeacherTrainingProgramResponseEntity
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Composable
fun HomeDetailProgramParticipantList(
modifier: Modifier = Modifier,
- item: ImmutableList = persistentListOf()
+ item: ImmutableList = persistentListOf()
) {
ExpoAndroidTheme { colors, _ ->
LazyColumn(
@@ -38,22 +40,5 @@ fun HomeDetailProgramParticipantList(
@Preview
@Composable
private fun HomeDetailProgramParticipantListPreview() {
- HomeDetailProgramParticipantList(
- item = persistentListOf(
- HomeDetailProgramParticipantData(
- name = "이명훈",
- company = "초등학교",
- position = "교사",
- schoolSubject = "컴퓨터공학",
- phone = "010-1234-5678"
- ),
- HomeDetailProgramParticipantData(
- name = "이명훈",
- company = "초등학교",
- position = "교사",
- schoolSubject = "컴퓨터공학",
- phone = "010-1234-5678"
- ),
- )
- )
+
}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/component/HomeDetailProgramParticipantListItem.kt b/feature/home/src/main/java/com/school_of_company/home/view/component/HomeDetailProgramParticipantListItem.kt
index bd4a72d8..b925da74 100644
--- a/feature/home/src/main/java/com/school_of_company/home/view/component/HomeDetailProgramParticipantListItem.kt
+++ b/feature/home/src/main/java/com/school_of_company/home/view/component/HomeDetailProgramParticipantListItem.kt
@@ -17,20 +17,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.school_of_company.design_system.theme.ExpoAndroidTheme
-
-data class HomeDetailProgramParticipantData(
- val name: String,
- val company: String,
- val position: String,
- val schoolSubject: String,
- val phone: String
-)
+import com.school_of_company.model.entity.training.TeacherTrainingProgramResponseEntity
@Composable
fun HomeDetailProgramParticipantListItem(
modifier: Modifier = Modifier,
index: Int,
- data: HomeDetailProgramParticipantData,
+ data: TeacherTrainingProgramResponseEntity,
horizontalScrollState: ScrollState = rememberScrollState()
) {
ExpoAndroidTheme { colors, typography ->
@@ -58,7 +51,7 @@ fun HomeDetailProgramParticipantListItem(
modifier = Modifier.width(80.dp)
)
Text(
- text = data.company,
+ text = data.organization,
style = typography.captionRegular2,
color = colors.black,
modifier = Modifier.width(100.dp)
@@ -70,13 +63,13 @@ fun HomeDetailProgramParticipantListItem(
modifier = Modifier.width(80.dp)
)
Text(
- text = data.schoolSubject,
+ text = "교사",
style = typography.captionRegular2,
color = colors.black,
modifier = Modifier.width(120.dp)
)
Text(
- text = data.phone,
+ text = "010-3825-1716",
style = typography.captionRegular2,
color = colors.black,
modifier = Modifier.width(100.dp)
@@ -88,14 +81,4 @@ fun HomeDetailProgramParticipantListItem(
@Preview
@Composable
private fun HomeDetailProgramParticipantListItemPreview() {
- HomeDetailProgramParticipantListItem(
- index = 1,
- data = HomeDetailProgramParticipantData(
- name = "이명훈",
- company = "초등학교",
- position = "교사",
- schoolSubject = "컴퓨터공학",
- phone = "010-1234-5678"
- )
- )
}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramList.kt b/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramList.kt
index 45a45074..d0d8a135 100644
--- a/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramList.kt
+++ b/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramList.kt
@@ -1,5 +1,6 @@
package com.school_of_company.home.view.component
+import android.text.style.TabStopSpan.Standard
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@@ -10,14 +11,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.school_of_company.design_system.theme.ExpoAndroidTheme
+import com.school_of_company.model.entity.standard.StandardProgramListResponseEntity
+import com.school_of_company.model.entity.training.TrainingProgramListResponseEntity
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Composable
fun ProgramList(
modifier: Modifier = Modifier,
- item: ImmutableList = persistentListOf(),
- navigateToProgramDetail: () -> Unit
+ trainingItem: ImmutableList = persistentListOf(),
+ navigateToProgramDetail: (Long) -> Unit
) {
ExpoAndroidTheme { colors, _ ->
@@ -27,7 +30,7 @@ fun ProgramList(
.background(color = colors.white)
.padding(horizontal = 16.dp)
) {
- itemsIndexed(item) { index, item ->
+ itemsIndexed(trainingItem) { index, item ->
ProgramListItem(
index = index + 1,
data = item,
@@ -38,42 +41,32 @@ fun ProgramList(
}
}
+@Composable
+fun StandardProgramList(
+ modifier: Modifier = Modifier,
+ standardItem: ImmutableList = persistentListOf(),
+ navigateToProgramDetail: (Long) -> Unit
+) {
+ ExpoAndroidTheme { colors, _ ->
+
+ LazyColumn(
+ modifier = modifier
+ .fillMaxSize()
+ .background(color = colors.white)
+ .padding(horizontal = 16.dp)
+ ) {
+ itemsIndexed(standardItem) { index, item ->
+ StandardProgramListItem(
+ index = index + 1,
+ data = item,
+ navigateToProgramDetail = navigateToProgramDetail
+ )
+ }
+ }
+ }
+}
+
@Preview
@Composable
private fun ProgramListPreview() {
- ProgramList(
- item = persistentListOf(
- ProgramTempList(
- programName = "adsfasfas",
- check = true,
- must = true
- ),
- ProgramTempList(
- programName = "adsfasdf",
- check = true,
- must = false
- ),
- ProgramTempList(
- programName = "adsfasdf",
- check = false,
- must = true
- ),
- ProgramTempList(
- programName = "adsfasdf",
- check = true,
- must = false
- ),
- ProgramTempList(
- programName = "adsfasdf",
- check = false,
- must = false
- ),
- ProgramTempList(
- programName = "adsfasdf",
- check = false,
- must = false
- ),
- ),
- navigateToProgramDetail = {}
- )
}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramListItem.kt b/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramListItem.kt
index d33cad53..b5334971 100644
--- a/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramListItem.kt
+++ b/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramListItem.kt
@@ -18,19 +18,15 @@ import com.school_of_company.design_system.component.modifier.clickable.expoClic
import com.school_of_company.design_system.icon.CircleIcon
import com.school_of_company.design_system.icon.XIcon
import com.school_of_company.design_system.theme.ExpoAndroidTheme
-
-data class ProgramTempList(
- val programName: String,
- val check: Boolean,
- val must: Boolean
-)
+import com.school_of_company.home.enum.ProgramEnum
+import com.school_of_company.model.entity.training.TrainingProgramListResponseEntity
@Composable
fun ProgramListItem(
modifier: Modifier = Modifier,
index: Int,
- data: ProgramTempList,
- navigateToProgramDetail: () -> Unit
+ data: TrainingProgramListResponseEntity,
+ navigateToProgramDetail: (Long) -> Unit
) {
ExpoAndroidTheme { colors, typography ->
@@ -42,7 +38,7 @@ fun ProgramListItem(
.fillMaxWidth()
.background(color = colors.white)
.padding(vertical = 10.dp)
- .expoClickable { navigateToProgramDetail() }
+ .expoClickable { navigateToProgramDetail(data.id) }
) {
Text(
@@ -54,7 +50,7 @@ fun ProgramListItem(
Text(
- text = data.programName,
+ text = data.title,
style = typography.captionRegular2,
color = colors.black,
maxLines = 1,
@@ -63,38 +59,42 @@ fun ProgramListItem(
)
- if (data.check) {
- CircleIcon(
- tint = colors.black,
- modifier = Modifier
- .size(16.dp)
- .weight(1f)
- )
- } else {
- XIcon(
- tint = colors.error,
- modifier = Modifier
- .size(16.dp)
- .weight(1f)
- )
- }
-
+ when (data.category) {
+ "ESSENTIAL" -> {
+ CircleIcon(
+ tint = colors.black,
+ modifier = Modifier
+ .size(16.dp)
+ .weight(1f)
+ )
+ XIcon(
+ tint = colors.error,
+ modifier = Modifier
+ .size(16.dp)
+ .weight(1f)
+ )
+ }
- if (data.must) {
- CircleIcon(
- tint = colors.black,
- modifier = Modifier
- .size(16.dp)
- .weight(1f)
- )
- } else {
- XIcon(
- tint = colors.error,
- modifier = Modifier
- .size(16.dp)
- .weight(1f)
- )
+ "CHOICE" -> {
+ XIcon(
+ tint = colors.error,
+ modifier = Modifier
+ .size(16.dp)
+ .weight(1f)
+ )
+ CircleIcon(
+ tint = colors.black,
+ modifier = Modifier
+ .size(16.dp)
+ .weight(1f)
+ )
+ }
+
+ else -> {
+ // 기본적으로 보여줄 UI가 있다면 여기에 작성
+ }
}
+
}
}
}
@@ -102,13 +102,4 @@ fun ProgramListItem(
@Preview
@Composable
private fun ProgramListItemPreview() {
- ProgramListItem(
- index = 1,
- data = ProgramTempList(
- programName = "프로그램 이름",
- check = true,
- must = false
- ),
- navigateToProgramDetail = {}
- )
}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramTabRowItem.kt b/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramTabRowItem.kt
index 238c9538..295e8b31 100644
--- a/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramTabRowItem.kt
+++ b/feature/home/src/main/java/com/school_of_company/home/view/component/ProgramTabRowItem.kt
@@ -1,5 +1,6 @@
package com.school_of_company.home.view.component
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
@@ -7,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.school_of_company.design_system.component.modifier.clickable.expoClickable
import com.school_of_company.design_system.theme.ExpoAndroidTheme
@@ -20,6 +22,7 @@ fun ProgramTabRowItem(
) {
ExpoAndroidTheme { colors, typography ->
Row(
+ horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.expoClickable(onClick = onClick)
@@ -30,6 +33,7 @@ fun ProgramTabRowItem(
style = typography.bodyBold1,
fontWeight = if (isCurrentIndex) FontWeight.SemiBold else FontWeight.Normal,
color = if (isCurrentIndex) colors.black else colors.gray500,
+ textAlign = TextAlign.Center
)
}
}
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/component/QrButton.kt b/feature/home/src/main/java/com/school_of_company/home/view/component/QrButton.kt
new file mode 100644
index 00000000..6a37bbc6
--- /dev/null
+++ b/feature/home/src/main/java/com/school_of_company/home/view/component/QrButton.kt
@@ -0,0 +1,55 @@
+package com.school_of_company.home.view.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+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.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.school_of_company.design_system.component.modifier.clickable.expoClickable
+import com.school_of_company.design_system.theme.ExpoAndroidTheme
+
+@Composable
+fun QrButton(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit
+) {
+ ExpoAndroidTheme { colors, typography ->
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier
+ .background(
+ color = colors.main,
+ shape = RoundedCornerShape(10.dp),
+ )
+ .expoClickable(
+ onClick = onClick,
+ rippleColor = colors.white
+ )
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.Start),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(
+ horizontal = 12.dp,
+ vertical = 8.dp
+ )
+ ) {
+ Text(
+ text = "QR 스캔",
+ style = typography.bodyBold2,
+ fontWeight = FontWeight.SemiBold,
+ color = colors.white,
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/component/QrScannView.kt b/feature/home/src/main/java/com/school_of_company/home/view/component/QrScannView.kt
new file mode 100644
index 00000000..817cdb2c
--- /dev/null
+++ b/feature/home/src/main/java/com/school_of_company/home/view/component/QrScannView.kt
@@ -0,0 +1,115 @@
+package com.school_of_company.home.view.component
+
+import android.util.Log
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.FocusMeteringAction
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.Preview
+import androidx.camera.core.resolutionselector.AspectRatioStrategy
+import androidx.camera.core.resolutionselector.ResolutionSelector
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.view.PreviewView
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.LifecycleOwner
+import com.school_of_company.home.util.QrcodeScanner
+import java.util.concurrent.Executors
+
+@androidx.camera.core.ExperimentalGetImage
+@Composable
+internal fun QrcodeScanView(
+ modifier: Modifier = Modifier,
+ onQrcodeScan: (Long) -> Unit
+) {
+ Scaffold(
+ modifier = modifier.fillMaxSize(),
+ ) { innerPadding: PaddingValues ->
+ AndroidView(
+ factory = { context ->
+ var isScanningEnabled = true
+
+ val cameraExecutor = Executors.newSingleThreadExecutor()
+ val previewView = PreviewView(context).apply {
+ scaleType = PreviewView.ScaleType.FILL_CENTER
+ }
+
+ val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
+
+ cameraProviderFuture.addListener({
+ val cameraProvider = cameraProviderFuture.get()
+
+ val preview = Preview.Builder()
+ .build()
+ .apply {
+ surfaceProvider = previewView.surfaceProvider
+ }
+
+ val imageCapture = ImageCapture.Builder().build()
+
+ val imageAnalyzer = ImageAnalysis.Builder()
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
+ .setResolutionSelector(
+ ResolutionSelector.Builder()
+ .setAspectRatioStrategy(
+ AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY
+ )
+ .build()
+ )
+ .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
+ .setTargetRotation(previewView.display.rotation)
+ .build()
+ .apply {
+ setAnalyzer(cameraExecutor, QrcodeScanner { qrcodeData ->
+ if (isScanningEnabled) {
+ isScanningEnabled = false
+ onQrcodeScan(qrcodeData)
+ }
+ })
+ }
+
+ val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
+
+ try {
+ cameraProvider.unbindAll()
+
+ val camera = cameraProvider.bindToLifecycle(
+ context as LifecycleOwner,
+ cameraSelector,
+ preview,
+ imageCapture,
+ imageAnalyzer
+ )
+
+ val cameraControl = camera.cameraControl
+
+ val meteringPointFactory = previewView.meteringPointFactory
+ val meteringPoint = meteringPointFactory.createPoint(
+ previewView.width / 2f,
+ previewView.height / 2f
+ )
+ val focusMeteringAction = FocusMeteringAction.Builder(meteringPoint)
+ .setAutoCancelDuration(5, java.util.concurrent.TimeUnit.SECONDS)
+ .build()
+
+ cameraControl.startFocusAndMetering(focusMeteringAction)
+ cameraControl.setExposureCompensationIndex(0)
+ } catch (e: Exception) {
+ Log.e("QrcodeScanView", "Camera binding failed", e)
+ }
+ }, ContextCompat.getMainExecutor(context))
+
+ previewView
+ },
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding)
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/view/component/StandardProgramListItem.kt b/feature/home/src/main/java/com/school_of_company/home/view/component/StandardProgramListItem.kt
new file mode 100644
index 00000000..7dfb1b86
--- /dev/null
+++ b/feature/home/src/main/java/com/school_of_company/home/view/component/StandardProgramListItem.kt
@@ -0,0 +1,73 @@
+package com.school_of_company.home.view.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+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.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.school_of_company.design_system.component.modifier.clickable.expoClickable
+import com.school_of_company.design_system.icon.XIcon
+import com.school_of_company.design_system.theme.ExpoAndroidTheme
+import com.school_of_company.model.entity.standard.StandardProgramListResponseEntity
+
+@Composable
+fun StandardProgramListItem(
+ modifier: Modifier = Modifier,
+ index: Int,
+ data: StandardProgramListResponseEntity,
+ navigateToProgramDetail: (Long) -> Unit
+) {
+ ExpoAndroidTheme { colors, typography ->
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier
+ .fillMaxWidth()
+ .background(color = colors.white)
+ .padding(vertical = 10.dp)
+ .expoClickable { navigateToProgramDetail(data.id) }
+ ) {
+
+ Text(
+ text = index.toString(),
+ style = typography.captionBold1,
+ color = colors.black,
+ modifier = Modifier.weight(0.5f)
+ )
+
+
+ Text(
+ text = data.title,
+ style = typography.captionRegular2,
+ color = colors.black,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier.weight(2f)
+ )
+
+ XIcon(
+ tint = colors.error,
+ modifier = Modifier
+ .size(16.dp)
+ .weight(1f)
+ )
+
+ XIcon(
+ tint = colors.error,
+ modifier = Modifier
+ .size(16.dp)
+ .weight(1f)
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/viewmodel/HomeViewModel.kt b/feature/home/src/main/java/com/school_of_company/home/viewmodel/HomeViewModel.kt
index ae7f3282..1f568304 100644
--- a/feature/home/src/main/java/com/school_of_company/home/viewmodel/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/school_of_company/home/viewmodel/HomeViewModel.kt
@@ -1,13 +1,37 @@
package com.school_of_company.home.viewmodel
+import android.util.Log
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.school_of_company.common.result.Result
+import com.school_of_company.common.result.asResult
+import com.school_of_company.domain.usecase.attendance.TrainingQrCodeRequestUseCase
+import com.school_of_company.domain.usecase.standard.StandardProgramAttendListUseCase
+import com.school_of_company.domain.usecase.standard.StandardProgramListUseCase
+import com.school_of_company.domain.usecase.training.TeacherTrainingProgramListUseCase
+import com.school_of_company.domain.usecase.training.TrainingProgramListUseCase
+import com.school_of_company.home.viewmodel.uistate.StandardProgramAttendListUiState
+import com.school_of_company.home.viewmodel.uistate.StandardProgramListUiState
+import com.school_of_company.home.viewmodel.uistate.TeacherTrainingProgramListUiState
+import com.school_of_company.home.viewmodel.uistate.TrainingProgramListUiState
+import com.school_of_company.home.viewmodel.uistate.TrainingQrCodeUiState
+import com.school_of_company.model.param.attendance.TrainingQrCodeRequestParam
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor(
- /* todo : add UseCase */
+ private val trainingProgramListUseCase: TrainingProgramListUseCase,
+ private val standardProgramListUseCase: StandardProgramListUseCase,
+ private val teacherTrainingProgramListUseCase: TeacherTrainingProgramListUseCase,
+ private val standardProgramAttendListUseCase: StandardProgramAttendListUseCase,
+ private val trainingQrCodeRequestUseCase: TrainingQrCodeRequestUseCase,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
companion object {
@@ -15,10 +39,142 @@ class HomeViewModel @Inject constructor(
private const val CONTENT = "content"
}
+ private val _swipeRefreshLoading = MutableStateFlow(false)
+ val swipeRefreshLoading = _swipeRefreshLoading.asStateFlow()
+
+ private val _trainingProgramListUiState = MutableStateFlow(TrainingProgramListUiState.Loading)
+ internal val trainingProgramListUiState = _trainingProgramListUiState.asStateFlow()
+
+ private val _standardProgramListUiState = MutableStateFlow(StandardProgramListUiState.Loading)
+ internal val standardProgramListUiState = _standardProgramListUiState.asStateFlow()
+
+ private val _teacherTrainingProgramListUiState = MutableStateFlow(TeacherTrainingProgramListUiState.Loading)
+ internal val teacherTrainingProgramListUiState = _teacherTrainingProgramListUiState.asStateFlow()
+
+ private val _standardProgramAttendListUiState = MutableStateFlow(StandardProgramAttendListUiState.Loading)
+ internal val standardProgramAttendListUiState = _standardProgramAttendListUiState.asStateFlow()
+
+ private val _trainingQrCodeUiState = MutableStateFlow(TrainingQrCodeUiState.Loading)
+ internal val trainingQrCodeUiState = _trainingQrCodeUiState.asStateFlow()
+
internal var title = savedStateHandle.getStateFlow(key = TITLE, initialValue = "")
internal var content = savedStateHandle.getStateFlow(key = CONTENT, initialValue = "")
+ internal fun trainingProgramList(expoId: String) = viewModelScope.launch {
+ _swipeRefreshLoading.value = true
+ trainingProgramListUseCase(expoId = expoId)
+ .asResult()
+ .collectLatest { result ->
+ when (result) {
+ is Result.Loading -> _trainingProgramListUiState.value = TrainingProgramListUiState.Loading
+ is Result.Success -> {
+ if (result.data.isEmpty()) {
+ _trainingProgramListUiState.value = TrainingProgramListUiState.Empty
+ _swipeRefreshLoading.value = false
+ } else {
+ _trainingProgramListUiState.value = TrainingProgramListUiState.Success(result.data)
+ _swipeRefreshLoading.value = false
+ }
+ }
+ is Result.Error -> {
+ _trainingProgramListUiState.value = TrainingProgramListUiState.Error(result.exception)
+ _swipeRefreshLoading.value = false
+ }
+ }
+ }
+ }
+
+ internal fun standardProgramList(expoId: String) = viewModelScope.launch {
+ _swipeRefreshLoading.value = true
+ standardProgramListUseCase(expoId = expoId)
+ .asResult()
+ .collectLatest { result ->
+ when (result) {
+ is Result.Loading -> _standardProgramListUiState.value = StandardProgramListUiState.Loading
+ is Result.Success -> {
+ if (result.data.isEmpty()) {
+ _standardProgramListUiState.value = StandardProgramListUiState.Empty
+ _swipeRefreshLoading.value = false
+ } else {
+ _standardProgramListUiState.value = StandardProgramListUiState.Success(result.data)
+ _swipeRefreshLoading.value = false
+ }
+ }
+ is Result.Error -> {
+ _standardProgramListUiState.value = StandardProgramListUiState.Error(result.exception)
+ _swipeRefreshLoading.value = false
+ }
+ }
+ }
+ }
+
+ internal fun teacherTrainingProgramList(trainingProId: Long) = viewModelScope.launch {
+ _teacherTrainingProgramListUiState.value = TeacherTrainingProgramListUiState.Loading
+ teacherTrainingProgramListUseCase(trainingProId = trainingProId)
+ .asResult()
+ .collectLatest { result ->
+ when (result) {
+ is Result.Loading -> _teacherTrainingProgramListUiState.value = TeacherTrainingProgramListUiState.Loading
+ is Result.Success -> {
+ if (result.data.isEmpty()) {
+ _teacherTrainingProgramListUiState.value = TeacherTrainingProgramListUiState.Empty
+ } else {
+ _teacherTrainingProgramListUiState.value = TeacherTrainingProgramListUiState.Success(result.data)
+ }
+ }
+ is Result.Error -> {
+ _teacherTrainingProgramListUiState.value = TeacherTrainingProgramListUiState.Error(result.exception)
+ }
+ }
+ }
+ }
+
+ internal fun standardProgramList(standardProId: Long) = viewModelScope.launch {
+ _swipeRefreshLoading.value = true
+ standardProgramAttendListUseCase(standardProId = standardProId)
+ .asResult()
+ .collectLatest { result ->
+ when (result) {
+ is Result.Loading -> _standardProgramAttendListUiState.value = StandardProgramAttendListUiState.Loading
+ is Result.Success -> {
+ if (result.data.isEmpty()) {
+ _standardProgramAttendListUiState.value = StandardProgramAttendListUiState.Empty
+ _swipeRefreshLoading.value = false
+ } else {
+ _standardProgramAttendListUiState.value = StandardProgramAttendListUiState.Success(result.data)
+ _swipeRefreshLoading.value = false
+ }
+ }
+ is Result.Error -> {
+ _standardProgramAttendListUiState.value = StandardProgramAttendListUiState.Error(result.exception)
+ _swipeRefreshLoading.value = false
+ }
+ }
+ }
+ }
+
+ internal fun trainingQrCode(
+ trainingId: Long,
+ body: TrainingQrCodeRequestParam
+ ) = viewModelScope.launch {
+ _trainingQrCodeUiState.value = TrainingQrCodeUiState.Loading
+ trainingQrCodeRequestUseCase(
+ trainingId = trainingId,
+ body = body
+ )
+ .onSuccess {
+ it.catch { remoteError ->
+ _trainingQrCodeUiState.value = TrainingQrCodeUiState.Error(remoteError)
+ }.collect {
+ _trainingQrCodeUiState.value = TrainingQrCodeUiState.Success
+ }
+ }
+ .onFailure { error ->
+ _trainingQrCodeUiState.value = TrainingQrCodeUiState.Error(error)
+ }
+ }
+
internal fun onTitleChange(value: String) {
savedStateHandle[TITLE] = value
}
diff --git a/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/StandardProgramAttendListUiState.kt b/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/StandardProgramAttendListUiState.kt
new file mode 100644
index 00000000..058c8465
--- /dev/null
+++ b/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/StandardProgramAttendListUiState.kt
@@ -0,0 +1,10 @@
+package com.school_of_company.home.viewmodel.uistate
+
+import com.school_of_company.model.entity.standard.StandardAttendListResponseEntity
+
+sealed interface StandardProgramAttendListUiState {
+ object Loading : StandardProgramAttendListUiState
+ object Empty : StandardProgramAttendListUiState
+ data class Success(val data: List) : StandardProgramAttendListUiState
+ data class Error(val exception: Throwable) : StandardProgramAttendListUiState
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/StandardProgramListUiState.kt b/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/StandardProgramListUiState.kt
new file mode 100644
index 00000000..6c212037
--- /dev/null
+++ b/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/StandardProgramListUiState.kt
@@ -0,0 +1,10 @@
+package com.school_of_company.home.viewmodel.uistate
+
+import com.school_of_company.model.entity.standard.StandardProgramListResponseEntity
+
+sealed interface StandardProgramListUiState {
+ object Loading : StandardProgramListUiState
+ object Empty : StandardProgramListUiState
+ data class Success(val data: List) : StandardProgramListUiState
+ data class Error(val exception: Throwable) : StandardProgramListUiState
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/TeacherTrainingProgramListUiState.kt b/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/TeacherTrainingProgramListUiState.kt
new file mode 100644
index 00000000..c020a481
--- /dev/null
+++ b/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/TeacherTrainingProgramListUiState.kt
@@ -0,0 +1,10 @@
+package com.school_of_company.home.viewmodel.uistate
+
+import com.school_of_company.model.entity.training.TeacherTrainingProgramResponseEntity
+
+sealed interface TeacherTrainingProgramListUiState {
+ object Loading: TeacherTrainingProgramListUiState
+ object Empty: TeacherTrainingProgramListUiState
+ data class Success(val data: List): TeacherTrainingProgramListUiState
+ data class Error(val exception: Throwable): TeacherTrainingProgramListUiState
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/TrainingProgramListUiState.kt b/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/TrainingProgramListUiState.kt
new file mode 100644
index 00000000..e9840ed6
--- /dev/null
+++ b/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/TrainingProgramListUiState.kt
@@ -0,0 +1,10 @@
+package com.school_of_company.home.viewmodel.uistate
+
+import com.school_of_company.model.entity.training.TrainingProgramListResponseEntity
+
+sealed interface TrainingProgramListUiState {
+ object Loading : TrainingProgramListUiState
+ object Empty : TrainingProgramListUiState
+ data class Success(val data: List) : TrainingProgramListUiState
+ data class Error(val exception: Throwable) : TrainingProgramListUiState
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/TrainingQrCodeUiState.kt b/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/TrainingQrCodeUiState.kt
new file mode 100644
index 00000000..d01485e1
--- /dev/null
+++ b/feature/home/src/main/java/com/school_of_company/home/viewmodel/uistate/TrainingQrCodeUiState.kt
@@ -0,0 +1,7 @@
+package com.school_of_company.home.viewmodel.uistate
+
+sealed interface TrainingQrCodeUiState {
+ object Loading : TrainingQrCodeUiState
+ object Success: TrainingQrCodeUiState
+ data class Error(val exception: Throwable) : TrainingQrCodeUiState
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 053a5414..d40a94bb 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -26,7 +26,7 @@ androidx-test-ext-junit = "1.2.1"
androidxWearCompose = "1.4.0"
androidxWindowManager = "1.3.0"
camera = "1.4.0"
-cameraRc = "1.3.0-beta01-rc01@aar"
+cameraRc = "1.4.0"
coil = "2.4.0"
converter-moshi = "2.9.0"
firebaseAnalytics = "21.6.2"
@@ -44,6 +44,8 @@ junit4 = "4.13.2"
junit = "4.13.2"
kotlin = "1.8.10"
kotlinxCoroutines = "1.7.1"
+kotlinxCoroutinesCore = "1.8.1"
+kotlinxCoroutinesPlayServices = "1.7.3"
kotlinxDateTime = "0.4.0"
kotlinxImmutable = "0.3.5"
kotlinxSerializationJson = "1.7.3"
@@ -128,7 +130,9 @@ hilt-ext-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hi
app-update-ktx = { group = "com.google.android.play", name = "app-update-ktx", version.ref = "inAppUpdate"}
kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
+kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinxCoroutines" }
+kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDateTime" }
kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" }