diff --git a/data/src/main/java/com/kusitms/data/remote/api/KusitmsApi.kt b/data/src/main/java/com/kusitms/data/remote/api/KusitmsApi.kt index 34eca40e..72112f30 100644 --- a/data/src/main/java/com/kusitms/data/remote/api/KusitmsApi.kt +++ b/data/src/main/java/com/kusitms/data/remote/api/KusitmsApi.kt @@ -1,9 +1,7 @@ package com.kusitms.data.remote.api import com.kusitms.data.remote.entity.BaseResponse -import com.kusitms.data.remote.entity.request.CommentContentRequestBody -import com.kusitms.data.remote.entity.request.ReportCommentRequestBody -import com.kusitms.data.remote.entity.request.UpdatePasswordRequest +import com.kusitms.data.remote.entity.request.* import com.kusitms.data.remote.entity.response.CheckPasswordPayload import com.kusitms.data.remote.entity.response.FindPwCheckEmailResponse import com.kusitms.data.remote.entity.response.LoginMemberProfileResponse @@ -24,10 +22,9 @@ import retrofit2.http.* interface KusitmsApi { - @GET("v1/auth/login/MEMBER") + @POST("v1/auth/login/MEMBER") suspend fun loginMember( - @Query("email") email: String, - @Query("password") password: String, + @Body loginRequestBody: LoginRequestBody ): LoginResponse @Multipart @@ -132,39 +129,36 @@ interface KusitmsApi { ): BaseResponse - // SignInNonMember + // SignInNonMember (회원가입 가능 체크) @FormUrlEncoded @POST("v1/member/check/register") suspend fun signInRequestCheck( - @Field("email") email: String, - @Field("password") password: String, + @Body loginRequestBody: LoginRequestBody ): SignInRequestResponse - @FormUrlEncoded + @FormUrlEncoded //회원가입 @POST("v1/member/register") suspend fun signInRequest( - @Field("email") email: String, - @Field("password") password: String, + @Body loginRequestBody: LoginRequestBody ): BaseResponse // FindPw @FormUrlEncoded - @POST("v1/member/email") + @POST("v1/member/email") //이메일 존재 확인 suspend fun verifyEmailCheck( - @Field("email") email: String, + @Body checkEmailRequestBody: CheckEmailRequestBody ): FindPwCheckEmailResponse @FormUrlEncoded @POST("v1/member/verify") suspend fun sendCode( - @Field("email") email: String, + @Body checkEmailRequestBody: CheckEmailRequestBody ): BaseResponse @FormUrlEncoded - @POST("v1/member/verify/code") + @POST("v1/member/verify/code") //이메일 코드 검증 suspend fun verifyCode( - @Field("email") email: String, - @Field("code") code: String, + @Body emailVerifyRequestBody: EmailVerifyRequestBody ): FindPwCodeVerifyResponse @PUT("v1/member/password/unauthenticated") @@ -176,7 +170,7 @@ interface KusitmsApi { @POST("v1/member/password") suspend fun checkPassword( - @Query("password") password: String, + @Body passwordRequestBody: PasswordRequestBody ): BaseResponse @PUT("v1/member/password") @@ -207,9 +201,13 @@ interface KusitmsApi { @GET("v1/attend/info") suspend fun getAttendInfo(): BaseResponse - //커리큘럼 출석 조회 @GET("v1/attend/lists") suspend fun getAttendCurrentList(): BaseResponse> + //출석 정보 조회 + @GET("v1/attend") + suspend fun getAttendScore(): BaseResponse + + } \ No newline at end of file diff --git a/data/src/main/java/com/kusitms/data/remote/di/AuthTokenInterceptor.kt b/data/src/main/java/com/kusitms/data/remote/di/AuthTokenInterceptor.kt index 57cf280f..cd021c33 100644 --- a/data/src/main/java/com/kusitms/data/remote/di/AuthTokenInterceptor.kt +++ b/data/src/main/java/com/kusitms/data/remote/di/AuthTokenInterceptor.kt @@ -31,7 +31,7 @@ class AuthTokenInterceptor @Inject constructor( var response = chain.proceed(request) // 토큰 만료 감지 및 처리 - if (response.code == 500 && response.code == 401) { + if (response.code == 500 || response.code == 401) { // 토큰 매니저 인스턴스를 가져오고 토큰 갱신을 시도 runBlocking { launch(Dispatchers.IO) { diff --git a/data/src/main/java/com/kusitms/data/remote/entity/request/LoginRequestBody.kt b/data/src/main/java/com/kusitms/data/remote/entity/request/LoginRequestBody.kt new file mode 100644 index 00000000..a6c7a0c9 --- /dev/null +++ b/data/src/main/java/com/kusitms/data/remote/entity/request/LoginRequestBody.kt @@ -0,0 +1,51 @@ +package com.kusitms.data.remote.entity.request + +import com.kusitms.domain.model.login.LoginEmailModel +import com.kusitms.domain.model.login.LoginEmailVerifyModel +import com.kusitms.domain.model.login.LoginMemberModel +import com.kusitms.domain.model.login.LoginPasswordModel + +data class LoginRequestBody( + val email: String, + val password: String +) + +fun mapToLoginRequestBody(model: LoginMemberModel): LoginRequestBody { + return LoginRequestBody( + email = model.email, + password = model.password + ) +} + +data class CheckEmailRequestBody( + val email: String +) + +fun mapToEmailRequestBody(model: LoginEmailModel): CheckEmailRequestBody { + return CheckEmailRequestBody( + email = model.email + ) +} + +data class EmailVerifyRequestBody( + val email: String, + val code : String +) + + +fun mapToEmailVerifyBody(model: LoginEmailVerifyModel): EmailVerifyRequestBody { + return EmailVerifyRequestBody( + email = model.email, + code = model.code + ) +} + +data class PasswordRequestBody( + val password: String +) + +fun mapToPasswordRequestBody(model: LoginPasswordModel): PasswordRequestBody { + return PasswordRequestBody( + password = model.password + ) +} \ No newline at end of file diff --git a/data/src/main/java/com/kusitms/data/remote/entity/response/home/AttendInfoPayload.kt b/data/src/main/java/com/kusitms/data/remote/entity/response/home/AttendInfoPayload.kt index f9b9d505..264d2dbe 100644 --- a/data/src/main/java/com/kusitms/data/remote/entity/response/home/AttendInfoPayload.kt +++ b/data/src/main/java/com/kusitms/data/remote/entity/response/home/AttendInfoPayload.kt @@ -6,7 +6,8 @@ data class AttendInfoPayload( val curriculumId: Int, val curriculumName: String, val isAttended: Boolean, - val date: String + val date: String, + val time: String ) fun AttendInfoPayload.toModel() = @@ -14,5 +15,6 @@ fun AttendInfoPayload.toModel() = curriculumId = curriculumId ?: 0, curriculumName = curriculumName ?: "", isAttended = isAttended ?: false, - date = date ?: "2월 17일" + date = date ?: "2월 17일", + time = time ?: "오후 02:00" ) \ No newline at end of file diff --git a/data/src/main/java/com/kusitms/data/repository/ChangePwRepositoryImpl.kt b/data/src/main/java/com/kusitms/data/repository/ChangePwRepositoryImpl.kt index 72ebb622..14b5e754 100644 --- a/data/src/main/java/com/kusitms/data/repository/ChangePwRepositoryImpl.kt +++ b/data/src/main/java/com/kusitms/data/repository/ChangePwRepositoryImpl.kt @@ -2,6 +2,8 @@ package com.kusitms.data.repository import com.kusitms.data.remote.api.KusitmsApi import com.kusitms.data.remote.entity.request.UpdatePasswordRequest +import com.kusitms.data.remote.entity.request.mapToPasswordRequestBody +import com.kusitms.domain.model.login.LoginPasswordModel import com.kusitms.domain.repository.ChangePwRepository import javax.inject.Inject @@ -9,9 +11,11 @@ class ChangePwRepositoryImpl@Inject constructor( private val kusitmsApi: KusitmsApi ): ChangePwRepository { - override suspend fun checkPassword(password : String): Result { + override suspend fun checkPassword(password: String): Result { return try { - val response = kusitmsApi.checkPassword(password) + val model = LoginPasswordModel(password) + val request = mapToPasswordRequestBody(model) + val response = kusitmsApi.checkPassword(request) if (response.result.code == 200 && response.payload != null) { Result.success(response.payload.isCorrect) } else { diff --git a/data/src/main/java/com/kusitms/data/repository/FindPwRepositoryImpl.kt b/data/src/main/java/com/kusitms/data/repository/FindPwRepositoryImpl.kt index 1a374ff3..15a5a076 100644 --- a/data/src/main/java/com/kusitms/data/repository/FindPwRepositoryImpl.kt +++ b/data/src/main/java/com/kusitms/data/repository/FindPwRepositoryImpl.kt @@ -2,10 +2,14 @@ package com.kusitms.data.repository import com.kusitms.data.remote.api.KusitmsApi import com.kusitms.data.remote.entity.request.UpdatePasswordRequest +import com.kusitms.data.remote.entity.request.mapToEmailRequestBody +import com.kusitms.data.remote.entity.request.mapToEmailVerifyBody import com.kusitms.data.remote.entity.response.notice.toModel import com.kusitms.data.remote.entity.response.toModel import com.kusitms.domain.model.findpw.FindPwCheckEmailModel import com.kusitms.domain.model.findpw.FindPwCodeVerifyModel +import com.kusitms.domain.model.login.LoginEmailModel +import com.kusitms.domain.model.login.LoginEmailVerifyModel import com.kusitms.domain.repository.FindPwRepository import javax.inject.Inject @@ -17,7 +21,10 @@ class FindPwRepositoryImpl@Inject constructor( email: String ): Result { return try { - val response = kusitmsApi.verifyEmailCheck(email) + val model = LoginEmailModel(email) + val request = mapToEmailRequestBody(model) + val response = kusitmsApi.verifyEmailCheck(request) + if (response.result.code == 200 && response.payload != null) { Result.success(response.payload.toModel()) } else { @@ -30,7 +37,10 @@ class FindPwRepositoryImpl@Inject constructor( override suspend fun SendCode(email: String): Result { return try { - val response = kusitmsApi.sendCode(email) + val model = LoginEmailModel(email) + val request = mapToEmailRequestBody(model) + val response = kusitmsApi.sendCode(request) + if(response.result.code == 200) { Result.success(Unit) } else { @@ -43,7 +53,10 @@ class FindPwRepositoryImpl@Inject constructor( override suspend fun VerifyCode(email: String, code: String): Result { return try { - val response = kusitmsApi.verifyCode(email,code) + val model = LoginEmailVerifyModel(email, code) + val request = mapToEmailVerifyBody(model) + val response = kusitmsApi.verifyCode(request) + if(response.result.code == 200) { Result.success(response.payload.toModel()) } else { @@ -72,6 +85,4 @@ class FindPwRepositoryImpl@Inject constructor( Result.failure(e) } } - - } \ No newline at end of file diff --git a/data/src/main/java/com/kusitms/data/repository/HomeRepositoryImpl.kt b/data/src/main/java/com/kusitms/data/repository/HomeRepositoryImpl.kt index a276a697..533281eb 100644 --- a/data/src/main/java/com/kusitms/data/repository/HomeRepositoryImpl.kt +++ b/data/src/main/java/com/kusitms/data/repository/HomeRepositoryImpl.kt @@ -121,4 +121,17 @@ class HomeRepositoryImpl @Inject constructor( Result.failure(e) } } + + override suspend fun getAttendScore(): Result { + return try { + val response = kusitmsApi.getAttendScore() + if(response.result.code == 200) { + Result.success(response.payload.toModel()) + } else { + Result.failure(RuntimeException("출석 정보 조회 실패: ${response.result.message}")) + } + } catch (e: Exception) { + Result.failure(e) + } + } } \ No newline at end of file diff --git a/data/src/main/java/com/kusitms/data/repository/LoginRepositoryImpl.kt b/data/src/main/java/com/kusitms/data/repository/LoginRepositoryImpl.kt index c73ed29d..52b3110f 100644 --- a/data/src/main/java/com/kusitms/data/repository/LoginRepositoryImpl.kt +++ b/data/src/main/java/com/kusitms/data/repository/LoginRepositoryImpl.kt @@ -1,7 +1,11 @@ package com.kusitms.data.repository +import android.util.Log import com.kusitms.data.local.AuthDataStore import com.kusitms.data.remote.api.KusitmsApi +import com.kusitms.data.remote.entity.request.LoginRequestBody +import com.kusitms.data.remote.entity.request.mapToLoginRequestBody +import com.kusitms.domain.model.login.LoginMemberModel import com.kusitms.domain.repository.LoginRepository import javax.inject.Inject @@ -14,8 +18,11 @@ class LoginRepositoryImpl @Inject constructor( password: String ): Result { return try { - val response = kusitmsApi.loginMember(email, password) + val model = LoginMemberModel(email,password) + val request = mapToLoginRequestBody(model) + val response = kusitmsApi.loginMember(request) if (response.result.code == 200 && response.payload != null) { + Log.d("auth", response.payload.accessToken.toString()) AuthDataStore().authToken = response.payload.accessToken AuthDataStore().refreshToken = response.payload.refreshToken Result.success(Unit) @@ -26,7 +33,4 @@ class LoginRepositoryImpl @Inject constructor( Result.failure(e) } } - - - } diff --git a/data/src/main/java/com/kusitms/data/repository/SignInRepositoryImpl.kt b/data/src/main/java/com/kusitms/data/repository/SignInRepositoryImpl.kt index ef23d6d3..47bb5542 100644 --- a/data/src/main/java/com/kusitms/data/repository/SignInRepositoryImpl.kt +++ b/data/src/main/java/com/kusitms/data/repository/SignInRepositoryImpl.kt @@ -4,9 +4,11 @@ import android.util.Log import com.google.gson.Gson import com.kusitms.data.local.AuthDataStore import com.kusitms.data.remote.api.KusitmsApi +import com.kusitms.data.remote.entity.request.mapToLoginRequestBody import com.kusitms.data.remote.entity.request.toBody import com.kusitms.data.remote.entity.response.toModel import com.kusitms.domain.model.SignInProfile +import com.kusitms.domain.model.login.LoginMemberModel import com.kusitms.domain.model.login.LoginMemberProfile import com.kusitms.domain.model.signin.SignInRequestCheckModel import com.kusitms.domain.repository.SignInRepository @@ -47,7 +49,9 @@ class SignInRepositoryImpl @Inject constructor( password: String ): Result { return try { - val response = kusitmsApi.signInRequestCheck(email, password) + val model = LoginMemberModel(email, password) + val request = mapToLoginRequestBody(model) + val response = kusitmsApi.signInRequestCheck(request) if(response.payload == null) { Result.failure(RuntimeException("올바른 데이터를 받지 못했습니다.")) } else { @@ -63,7 +67,9 @@ class SignInRepositoryImpl @Inject constructor( password: String ): Result { return try { - val response = kusitmsApi.signInRequest(email, password) + val model = LoginMemberModel(email, password) + val request = mapToLoginRequestBody(model) + val response = kusitmsApi.signInRequest(request) if(response.result == null) { Result.failure(RuntimeException("올바른 데이터를 받지 못했습니다.")) } else { diff --git a/domain/src/main/java/com/kusitms/domain/model/home/AttendCurrentModel.kt b/domain/src/main/java/com/kusitms/domain/model/home/AttendCurrentModel.kt index 1e4d0450..fcaa6fd8 100644 --- a/domain/src/main/java/com/kusitms/domain/model/home/AttendCurrentModel.kt +++ b/domain/src/main/java/com/kusitms/domain/model/home/AttendCurrentModel.kt @@ -20,5 +20,6 @@ data class AttendInfoModel( val curriculumId: Int, val curriculumName: String, val isAttended: Boolean, - val date: String + val date: String, + val time:String ) \ No newline at end of file diff --git a/domain/src/main/java/com/kusitms/domain/model/login/LoginMemberProfile.kt b/domain/src/main/java/com/kusitms/domain/model/login/LoginMemberProfile.kt index 7ec6fa61..0418b0e2 100644 --- a/domain/src/main/java/com/kusitms/domain/model/login/LoginMemberProfile.kt +++ b/domain/src/main/java/com/kusitms/domain/model/login/LoginMemberProfile.kt @@ -6,4 +6,22 @@ data class LoginMemberProfile( val period: String?, val phoneNumber: String, val memberDetailExist: Boolean +) + +data class LoginMemberModel( + val email:String, + val password: String +) + +data class LoginEmailModel( + val email:String +) + +data class LoginPasswordModel( + val password: String +) + +data class LoginEmailVerifyModel( + val email: String, + val code : String ) \ No newline at end of file diff --git a/domain/src/main/java/com/kusitms/domain/repository/HomeRepository.kt b/domain/src/main/java/com/kusitms/domain/repository/HomeRepository.kt index 2b1e752d..a6da6c3a 100644 --- a/domain/src/main/java/com/kusitms/domain/repository/HomeRepository.kt +++ b/domain/src/main/java/com/kusitms/domain/repository/HomeRepository.kt @@ -14,4 +14,5 @@ interface HomeRepository { ): Result> suspend fun getAttendCurrentList(): Result> suspend fun getAttendInfo(): Result + suspend fun getAttendScore(): Result } \ No newline at end of file diff --git a/domain/src/main/java/com/kusitms/domain/usecase/home/GetAttendScoreUseCase.kt b/domain/src/main/java/com/kusitms/domain/usecase/home/GetAttendScoreUseCase.kt new file mode 100644 index 00000000..e98e916c --- /dev/null +++ b/domain/src/main/java/com/kusitms/domain/usecase/home/GetAttendScoreUseCase.kt @@ -0,0 +1,20 @@ +package com.kusitms.domain.usecase.home + +import com.kusitms.domain.model.home.AttendModel +import com.kusitms.domain.repository.HomeRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject + +class GetAttendScoreUseCase @Inject constructor( + private val homeRepository: HomeRepository +) { + operator fun invoke(): Flow = flow { + homeRepository.getAttendScore() + .onSuccess { + emit(it) + }.onFailure { + throw it + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/kusitms/presentation/common/util/NavUtil.kt b/presentation/src/main/java/com/kusitms/presentation/common/util/NavUtil.kt index 93a5cf23..964beba1 100644 --- a/presentation/src/main/java/com/kusitms/presentation/common/util/NavUtil.kt +++ b/presentation/src/main/java/com/kusitms/presentation/common/util/NavUtil.kt @@ -7,6 +7,7 @@ object NavUtil { val shownBottomBarNavRouteSet = setOf( NavRoutes.Notice.route, NavRoutes.HomeScreen.route, - NavRoutes.Profile.route + NavRoutes.Profile.route, + NavRoutes.AttendScreen.route ) } \ No newline at end of file diff --git a/presentation/src/main/java/com/kusitms/presentation/model/home/attend/AttendViewModel.kt b/presentation/src/main/java/com/kusitms/presentation/model/home/attend/AttendViewModel.kt index 21efcf3a..915af784 100644 --- a/presentation/src/main/java/com/kusitms/presentation/model/home/attend/AttendViewModel.kt +++ b/presentation/src/main/java/com/kusitms/presentation/model/home/attend/AttendViewModel.kt @@ -1,45 +1,76 @@ package com.kusitms.presentation.model.home.attend +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi import androidx.compose.ui.graphics.Color import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bumptech.glide.Glide.init import com.kusitms.domain.model.home.AttendCurrentModel +import com.kusitms.domain.model.home.AttendInfoModel import com.kusitms.domain.model.home.AttendModel import com.kusitms.domain.usecase.home.GetAttendCurrentListUseCase +import com.kusitms.domain.usecase.home.GetAttendInfoUseCase +import com.kusitms.domain.usecase.home.GetAttendScoreUseCase import com.kusitms.presentation.R import com.kusitms.presentation.common.ui.theme.KusitmsColorPalette import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import timber.log.Timber import java.text.SimpleDateFormat +import java.time.Duration +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.Year +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeParseException import java.util.* import javax.inject.Inject @HiltViewModel class AttendViewModel @Inject constructor( - getAttendCurrentListUseCase: GetAttendCurrentListUseCase + getAttendCurrentListUseCase: GetAttendCurrentListUseCase, + getAttendInfoUseCase: GetAttendInfoUseCase, + getAttendScoreUseCase: GetAttendScoreUseCase ):ViewModel() { - val attendListInit = getAttendCurrentListUseCase().catch { + val attendListInit: StateFlow> = getAttendCurrentListUseCase() + .catch { e -> + } + .map { list -> + list.map { model -> + model.copy( + date = formatDate(model.date), + time = formatTime(model.time) + ) + } + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = emptyList() + ) + + val upcomingAttend = getAttendInfoUseCase().catch { + }.stateIn( + viewModelScope, + started = SharingStarted.Eagerly, + initialValue = AttendInfoModel(0, "", false, "", "") + ) + + val attendScore = getAttendScoreUseCase().catch { }.stateIn( viewModelScope, started = SharingStarted.Eagerly, - initialValue = emptyList() + initialValue = AttendModel(0, 0, 0, 0, "수료 가능한 점수에요") ) private val _attendCurrentList = MutableStateFlow>(emptyList()) val attendCurrentList : StateFlow> = _attendCurrentList.asStateFlow() - private val _attendScore = MutableStateFlow( - AttendModel(penalty = 0, present = 0, absent = 0, late = 0, passYn = "수료 가능한 점수에요") - ) - val attendScore: StateFlow = _attendScore.asStateFlow() - - fun updateAttendScore(attendModel: AttendModel) { - _attendScore.value = attendModel - } fun formatDate(dateString: String): String { val originalFormat = SimpleDateFormat("MM월 dd일", Locale.KOREA) @@ -48,24 +79,41 @@ class AttendViewModel @Inject constructor( val parsedDate = originalFormat.parse(dateString) parsedDate?.let { targetFormat.format(it) } ?: dateString } catch (e: Exception) { - // 파싱에 실패시, 원본 날짜를 그대로 사용 + Log.d("변환 실패", "date") dateString } } - init { - viewModelScope.launch { - getAttendCurrentListUseCase().catch { + fun formatTime(timeString: String): String { + val inputFormat = SimpleDateFormat("a hh:mm", Locale.KOREAN) + val outputFormat = SimpleDateFormat("a h:mm", Locale.KOREAN) - }.collect() { - _attendCurrentList.value = it.map { attendModel -> - attendModel.copy(date = formatDate(attendModel.date)) - } - } + return try { + val date = inputFormat.parse(timeString) + outputFormat.format(date) + } catch (e: Exception) { + Log.d("변환 실패", "time") + timeString } } + @RequiresApi(Build.VERSION_CODES.O) + fun combineDateAndTime(date: String, time: String): LocalDateTime? { + val currentYear = LocalDate.now().year + // 날짜 형식에 연도 추가 + val dateFormatter = DateTimeFormatter.ofPattern("MM월 dd일", Locale.KOREAN) + val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 a hh:mm", Locale.KOREAN) + + return try { + // 날짜와 시간 문자열을 현재 연도와 결합 + val dateTimeStr = "${currentYear}년 $date $time" + LocalDateTime.parse(dateTimeStr, dateTimeFormatter) + } catch (e: Exception) { + null // 파싱 실패 시 null 반환 + } + } + enum class Status(val displayName: String) { PRESENT("출석"), ABSENT("결석"), diff --git a/presentation/src/main/java/com/kusitms/presentation/navigation/NavGraph.kt b/presentation/src/main/java/com/kusitms/presentation/navigation/NavGraph.kt index 49cc099e..84e61eaa 100644 --- a/presentation/src/main/java/com/kusitms/presentation/navigation/NavGraph.kt +++ b/presentation/src/main/java/com/kusitms/presentation/navigation/NavGraph.kt @@ -1,6 +1,8 @@ package com.kusitms.presentation.navigation import ProfileDetailScreen +import android.os.Build +import androidx.annotation.RequiresApi import androidx.compose.animation.AnimatedContentScope import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.EnterTransition @@ -36,6 +38,7 @@ import com.kusitms.presentation.common.ui.KusitmsBottomNavigationBar import com.kusitms.presentation.common.ui.KusitmsBottomNavigationItem import com.kusitms.presentation.common.ui.theme.KusitmsColorPalette import com.kusitms.presentation.common.util.NavUtil.shownBottomBarNavRouteSet +import com.kusitms.presentation.model.home.attend.AttendViewModel import com.kusitms.presentation.model.login.LoginViewModel import com.kusitms.presentation.model.login.findPw.FindPwViewModel import com.kusitms.presentation.model.setting.SettingViewModel @@ -69,6 +72,7 @@ import com.kusitms.presentation.ui.splash.SplashScreen import com.kusitms.presentation.ui.viewer.ImageViewerScreen import com.kusitms.presentation.ui.viewer.ImageViewerViewModel + @Composable fun MainNavigation() { val navController = rememberNavController() @@ -82,6 +86,7 @@ fun MainNavigation() { val imageViewerViewModel: ImageViewerViewModel = hiltViewModel() val splashViewModel: SplashViewModel = hiltViewModel() val snackbarHostState = remember { SnackbarHostState() } + val attendViewModel: AttendViewModel = hiltViewModel() Scaffold( @@ -232,10 +237,13 @@ fun MainNavigation() { ) } - kusitmsComposableWithAnimation(NavRoutes.AttendanceScreen.route) { - AttendScreen( - navController - ) + kusitmsComposableWithAnimation(NavRoutes.AttendScreen.route) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + AttendScreen( + attendViewModel, + navController + ) + } } kusitmsComposableWithAnimation(NavRoutes.CameraPreview.route) { @@ -288,7 +296,6 @@ fun MainNavigation() { // NoticeScreen kusitmsComposableWithAnimation(NavRoutes.Notice.route) { - NoticeScreen( viewModel = hiltViewModel(), onNoticeClick = { diff --git a/presentation/src/main/java/com/kusitms/presentation/navigation/NavRoutes.kt b/presentation/src/main/java/com/kusitms/presentation/navigation/NavRoutes.kt index ddf434c7..d4a7c040 100644 --- a/presentation/src/main/java/com/kusitms/presentation/navigation/NavRoutes.kt +++ b/presentation/src/main/java/com/kusitms/presentation/navigation/NavRoutes.kt @@ -57,7 +57,7 @@ sealed class NavRoutes( fun createRoute(teamId: Int, curriculumName: String) = "HomeTeamDetail/${teamId}/${curriculumName}" } - object AttendanceScreen: NavRoutes("Attendance") + object AttendScreen: NavRoutes("Attendance") object CameraPreview: NavRoutes("CameraPreview") @@ -97,11 +97,3 @@ sealed class NavRoutes( object ImageViewer : NavRoutes("ImageViewer") } - - -//SignIn Route -sealed class SignInNavRoutes(val route: String) { - object SignInScreen1 : NavRoutes("signin1") - object SignInScreen2 : NavRoutes("signin2") - object SignInScreen3 : NavRoutes("signin3") -} \ No newline at end of file diff --git a/presentation/src/main/java/com/kusitms/presentation/navigation/TopLevelDestination.kt b/presentation/src/main/java/com/kusitms/presentation/navigation/TopLevelDestination.kt index 5eed1feb..4ed077cc 100644 --- a/presentation/src/main/java/com/kusitms/presentation/navigation/TopLevelDestination.kt +++ b/presentation/src/main/java/com/kusitms/presentation/navigation/TopLevelDestination.kt @@ -21,7 +21,7 @@ enum class TopLevelDestination( ATTENDANCE( icon = KusitmsIcons.BottomNavAttendance, iconTextId = R.string.bottom_nav_attendance, - route = "" + route = NavRoutes.AttendScreen.route ), NOTICE( icon = KusitmsIcons.BottomNavNotice, diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendButton.kt b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendButton.kt index 8c4e299b..430aa8c1 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendButton.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendButton.kt @@ -34,9 +34,10 @@ fun AttendBtnOn(navController: NavHostController) { } } -@Preview @Composable -fun AttendBtnOff() { +fun AttendBtnOff( + leftTime: String +) { Button( modifier = Modifier .wrapContentWidth() @@ -51,7 +52,7 @@ fun AttendBtnOff() { verticalArrangement = Arrangement.Center ) { Text(text = stringResource(R.string.attend_btn_attend_wait), style = KusitmsTypo.current.Text_Semibold, color = KusitmsColorPalette.current.Grey400) - Text(text = stringResource(R.string.attend_btn_attend_wait), style = KusitmsTypo.current.Text_Semibold, color = KusitmsColorPalette.current.Grey400) + Text(text = leftTime, style = KusitmsTypo.current.Text_Semibold, color = KusitmsColorPalette.current.Grey400) } } } diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendScreen.kt b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendScreen.kt index 9d64d670..cc64a981 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendScreen.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendScreen.kt @@ -1,5 +1,7 @@ package com.kusitms.presentation.ui.home.attend +import android.os.Build +import androidx.annotation.RequiresApi import androidx.annotation.StringRes import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background @@ -10,7 +12,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.FloatingActionButton import androidx.compose.material.Text import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -29,40 +31,59 @@ import com.kusitms.presentation.common.ui.theme.KusitmsTypo import com.kusitms.presentation.model.home.attend.AttendViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import java.time.Duration +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.Year +import java.time.format.DateTimeFormatter +import java.util.* +@RequiresApi(Build.VERSION_CODES.O) @Composable fun AttendScreen( viewModel: AttendViewModel, navController: NavHostController ) { + val attendCurrentList by viewModel.attendListInit.collectAsState() val scrollState = rememberScrollState() - Box( - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - .background(color = KusitmsColorPalette.current.Grey800) // 배경색을 Grey800으로 적용 - ) { + Column( modifier = Modifier .wrapContentHeight() .fillMaxWidth() - .background(color = KusitmsColorPalette.current.Grey900), + .background(color = KusitmsColorPalette.current.Grey800), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Top ) { - AttendTopBar() - KusitmsMarginVerticalSpacer(size = 8) - AttendPreColumn(navController) - KusitmsMarginVerticalSpacer(size = 24) - AttendRecordColumn() - KusitmsMarginVerticalSpacer(size = 32) - AttendNotAttend() + Column( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .background(color = KusitmsColorPalette.current.Grey900), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top + ) { + AttendTopBar() + KusitmsMarginVerticalSpacer(size = 8) + AttendPreColumn(viewModel,navController) + KusitmsMarginVerticalSpacer(size = 24) + AttendRecordColumn(viewModel) + KusitmsMarginVerticalSpacer(size = 32) + } + if (attendCurrentList.isNotEmpty()) { + KusitmsMarginVerticalSpacer(size = 30) + attendCurrentList.forEach { model -> + CurriItem(model = model) + } + } else { + AttendNotAttend() + } Spacer(modifier = Modifier .weight(1f) .background(color = KusitmsColorPalette.current.Grey800)) } - } ScrollBtn(scrollState = scrollState) } @@ -84,8 +105,28 @@ fun AttendTopBar() { } } +@RequiresApi(Build.VERSION_CODES.O) @Composable -fun AttendPreColumn(navController: NavHostController) { +fun AttendPreColumn( + viewModel: AttendViewModel, + navController: NavHostController +) { + val curri by viewModel.upcomingAttend.collectAsState() + val curriculum = curri?.curriculumName ?: "" + val eventDateTime = viewModel.combineDateAndTime(curri.date, curri.time) + val currentTime = remember { mutableStateOf(LocalDateTime.now()) } + val duration = eventDateTime?.let { + Duration.between(currentTime.value, eventDateTime) + } ?: Duration.ZERO + + // 주기적으로 현재 시간 상태 업데이트 + LaunchedEffect(key1 = Unit) { + while (true) { + currentTime.value = LocalDateTime.now() + delay(60000) + } + } + Box(modifier = Modifier .fillMaxWidth() .padding(horizontal = 20.dp) @@ -107,15 +148,34 @@ fun AttendPreColumn(navController: NavHostController) { ) { Text(text = stringResource(R.string.attend_box1_title), style = KusitmsTypo.current.Caption1, color = KusitmsColorPalette.current.Main500) KusitmsMarginVerticalSpacer(size = 4) - Text(text = stringResource(R.string.attend_box1_subTitle), style = KusitmsTypo.current.SubTitle1_Semibold, color = KusitmsColorPalette.current.White) + Text(text = curriculum, style = KusitmsTypo.current.SubTitle1_Semibold, color = KusitmsColorPalette.current.White) + } + if (duration.isNegative) { + val minutesAfterStart = duration.abs().toMinutes() + if (minutesAfterStart <= 30) { + // 이벤트 시작 후 30분 이내 + AttendBtnOn(navController = navController) // 여기서 정책에 따라 AttendBtnFailure로 변경 가능 + } else { + // 이벤트 시작 후 30분 초과 + AttendBtnFailure() + } + } else if (duration.isZero || (duration.toMinutes() in 1..30)) { + // 이벤트 시작 전 30분 이내 + AttendBtnOn(navController = navController) + } else { + // 이벤트 시작까지 30분 이상 남음 + AttendBtnOff("D-${duration.toDaysPart()} ${duration.toHoursPart()}:${duration.toMinutesPart()}") } - AttendBtnOn(navController) } } } @Composable -fun AttendRecordColumn() { +fun AttendRecordColumn( + viewModel: AttendViewModel +) { + val attendModel by viewModel.attendScore.collectAsState() + val penalty = attendModel.penalty Box(modifier = Modifier .fillMaxWidth() .height(266.dp) @@ -149,23 +209,31 @@ fun AttendRecordColumn() { } } KusitmsMarginVerticalSpacer(size = 24) - Text(text = stringResource(R.string.attend_box3_title), style = KusitmsTypo.current.Header2, color = KusitmsColorPalette.current.Grey100) + Text(text = "벌점 ${penalty}점", style = KusitmsTypo.current.Header2, color = KusitmsColorPalette.current.Grey100) KusitmsMarginVerticalSpacer(size = 14) - AttendCanComplete() + AttendCanComplete(viewModel) KusitmsMarginVerticalSpacer(size = 24) - AttendBoxRow() + AttendBoxRow(viewModel) } } } @Composable -fun AttendCanComplete() { +fun AttendCanComplete( + viewModel: AttendViewModel +) { + val attendModel by viewModel.attendScore.collectAsState() + val penalty = attendModel.penalty Row(modifier = Modifier .fillMaxWidth() .wrapContentHeight(), horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically) { - Text(text = stringResource(R.string.attend_box3_subTitle_ok), style = KusitmsTypo.current.Text_Semibold, color = KusitmsColorPalette.current.Sub1) - KusitmsMarginHorizontalSpacer(size = 6) - Icon(painter = painterResource(id = R.drawable.ic_thumb), contentDescription = null, tint = Color.Unspecified) + if(penalty >= 6) { + AttendNotComplete() + } else { + Text(text = stringResource(R.string.attend_box3_subTitle_ok), style = KusitmsTypo.current.Text_Semibold, color = KusitmsColorPalette.current.Sub1) + KusitmsMarginHorizontalSpacer(size = 6) + Icon(painter = painterResource(id = R.drawable.ic_thumb), contentDescription = null, tint = Color.Unspecified) + } } } @@ -189,11 +257,12 @@ fun AttendNotAttend() { @Composable fun ScrollBtn(scrollState: ScrollState) { + val coroutineScope = rememberCoroutineScope() Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomEnd) { FloatingActionButton( modifier = Modifier.padding(16.dp), onClick = { - CoroutineScope(Dispatchers.Main).launch { + coroutineScope.launch { scrollState.animateScrollTo(0) } }, @@ -203,27 +272,35 @@ fun ScrollBtn(scrollState: ScrollState) { } @Composable -fun AttendBoxRow() { +fun AttendBoxRow( + viewModel: AttendViewModel +) { + val attendFlow by viewModel.attendScore.collectAsState() + val attendCount = attendFlow.present + val absentCount = attendFlow.absent + val lateCount = attendFlow.late + Row(modifier = Modifier .fillMaxWidth() .height(74.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { - AttendBoxItem(title = R.string.attend_box4_attend, Modifier.weight(1f)) + AttendBoxItem(title = R.string.attend_box4_attend, Modifier.weight(1f), "${attendCount}회") Spacer(Modifier.width(12.dp)) - AttendBoxItem(title = R.string.attend_box4_non_attend, Modifier.weight(1f)) + AttendBoxItem(title = R.string.attend_box4_non_attend, Modifier.weight(1f), "${absentCount}회") Spacer(Modifier.width(12.dp)) - AttendBoxItem(title = R.string.attend_box4_non_late, Modifier.weight(1f)) + AttendBoxItem(title = R.string.attend_box4_non_late, Modifier.weight(1f), "${lateCount}회") } } @Composable fun AttendBoxItem( @StringRes title: Int, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + text: String ) { Box(modifier = modifier .height(74.dp) @@ -241,7 +318,7 @@ fun AttendBoxItem( style = KusitmsTypo.current.Caption1, color = KusitmsColorPalette.current.Grey300, ) - Text(text ="0회", + Text(text = text, style = KusitmsTypo.current.SubTitle1_Semibold, color = KusitmsColorPalette.current.Grey100, ) diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/camera/CameraPreview.kt b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/camera/CameraPreview.kt index c3590c1f..fbf50854 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/camera/CameraPreview.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/camera/CameraPreview.kt @@ -31,6 +31,7 @@ import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.common.InputImage import com.kusitms.presentation.common.ui.theme.KusitmsColorPalette +@androidx.compose.ui.tooling.preview.Preview @Composable fun CameraPreviewWithOverlay() { Box { @@ -39,7 +40,6 @@ fun CameraPreviewWithOverlay() { } } -@androidx.compose.ui.tooling.preview.Preview @Composable fun Overlay() { Surface { diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/curriItem.kt b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/curriItem.kt index fd80f835..b1bd3f17 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/curriItem.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/curriItem.kt @@ -1,5 +1,6 @@ package com.kusitms.presentation.ui.home.attend +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -25,9 +26,12 @@ import com.kusitms.presentation.model.home.attend.AttendViewModel fun CurriItem( model: AttendCurrentModel ) { + Log.d("model data", model.time.toString()) + Log.d("model data", model.date.toString()) Row(modifier = Modifier .fillMaxWidth() - .background(color = KusitmsColorPalette.current.Grey600) + .padding(horizontal = 20.dp) + .background(color = KusitmsColorPalette.current.Grey800) .height(78.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/login/LoginScreen.kt b/presentation/src/main/java/com/kusitms/presentation/ui/login/LoginScreen.kt index 0f7f821b..e5d655d2 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/login/LoginScreen.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/login/LoginScreen.kt @@ -4,16 +4,7 @@ import LoginLogoIv import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -89,7 +80,7 @@ fun ButtonColumn(navController: NavHostController) { .fillMaxWidth() .padding(horizontal = 20.dp) .height(255.dp), - horizontalAlignment = Alignment.Start, + horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top ) { Button( @@ -119,7 +110,7 @@ fun ButtonColumn(navController: NavHostController) { Text(text = stringResource(id = R.string.login_btn2), style = KusitmsTypo.current.SubTitle2_Semibold, color = KusitmsColorPalette.current.Grey600) } Spacer(modifier = Modifier.height(20.dp)) - LoginTalkBall.DrawLoginTalk(modifier = Modifier.padding(horizontal = 20.dp)) + LoginTalkBall.DrawLoginTalk(modifier = Modifier.offset(x = (-90).dp)) loginBottomColumn(navController) } } @@ -129,8 +120,7 @@ fun loginBottomColumn(navController: NavController) { Column( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 0.dp) - .height(90.dp), + .padding(horizontal = 0.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top ) {