-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* AttendanceCodeDialog 구현 (#852) * Attendance screen compose setting (#676) * feat: implement NewAttendanceActivity * feat: implement NewAttendanceViewModel * chore: add compose-lifecycle dependency * feat: define AttendanceAction * feat: implement screens * feat: use SoptTheme in designsystem * chore: data class -> class로 변경 * chore: 람다 프로퍼티 이름 명시 * chore: SoptTheme darkTheme 기본값 사용 * chore: 필요없는 함수 제거 * chore: 구현 안 된 함수에 TODO 삽입 * chore: 동작하지 않는 Preview 제거 * chore: AttendanceAction 내 뷰모델 참조 제거 * chore: code format 변경 * chore: make stamp design system internal * chore: extract string resource * feat: implement AttendanceCodeCard * feat: implement AttendanceCodeCardList * chore: change logic * feat: implement AttendanceCodeDialog * feat: implement attendance button * chore: string resource 추출 * chore: change parameter List to ImmutableList * chore: reformat codee * feat: implement domain models * feat: implement Repository * chore: remove unnecessary code * chore: change attendanceScore to Float * chore: reflect type name changed * chore: remove totalCount from parameters * feat: add action onClickRefresh * feat: implement bindDefaultAttendanceRepository * chore: add background at route * chore: change button radius * chore: reformat code * feat: implement Domain Model to UiModel * chore: rename AttendanceInfo to Attendance * chore: rename SessionInfo * feat: format LocalDateTime * chore: use DateTimeFormatter * chore: use getOrNull * chore: don't use it * feat: implement AttendanceMapper * chore: remove duplicate code * chore: change to Extension * chore: change parameter name * chore: change code clear * chore: make code clear * chore: separate package * chore: change function name * feat: implement FinalAttendanceTest * chore: remove duplicate tests
- Loading branch information
1 parent
d2b9c0d
commit 816168f
Showing
25 changed files
with
835 additions
and
137 deletions.
There are no files selected for viewing
84 changes: 84 additions & 0 deletions
84
app/src/main/java/org/sopt/official/data/AttendanceMapper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package org.sopt.official.data | ||
|
||
import org.sopt.official.data.model.attendance.AttendanceHistoryResponse | ||
import org.sopt.official.data.model.attendance.AttendanceHistoryResponse.AttendanceResponse | ||
import org.sopt.official.data.model.attendance.SoptEventResponse | ||
import org.sopt.official.domain.entity.attendance.Attendance | ||
import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType | ||
import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType.HasAttendance.RoundAttendance | ||
import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType.HasAttendance.RoundAttendance.RoundAttendanceState | ||
import org.sopt.official.domain.entity.attendance.Attendance.Session | ||
import org.sopt.official.domain.entity.attendance.Attendance.User.AttendanceLog.AttendanceState | ||
import java.time.LocalDateTime | ||
|
||
fun mapToAttendance( | ||
attendanceHistoryResponse: AttendanceHistoryResponse?, | ||
soptEventResponse: SoptEventResponse? | ||
): Attendance { | ||
return Attendance( | ||
sessionId = soptEventResponse?.id ?: Attendance.UNKNOWN_SESSION_ID, | ||
user = Attendance.User( | ||
name = attendanceHistoryResponse?.name ?: Attendance.User.UNKNOWN_NAME, | ||
generation = attendanceHistoryResponse?.generation ?: Attendance.User.UNKNOWN_GENERATION, | ||
part = Attendance.User.Part.valueOf(attendanceHistoryResponse?.part ?: Attendance.User.UNKNOWN_PART), | ||
attendanceScore = attendanceHistoryResponse?.score ?: 0.0, | ||
attendanceCount = Attendance.User.AttendanceCount( | ||
attendanceCount = attendanceHistoryResponse?.attendanceCount?.normal ?: 0, | ||
lateCount = attendanceHistoryResponse?.attendanceCount?.late ?: 0, | ||
absenceCount = attendanceHistoryResponse?.attendanceCount?.abnormal ?: 0, | ||
), | ||
attendanceHistory = attendanceHistoryResponse?.attendances?.map { attendanceResponse: AttendanceResponse -> | ||
Attendance.User.AttendanceLog( | ||
sessionName = attendanceResponse.eventName, | ||
date = attendanceResponse.date, | ||
attendanceState = AttendanceState.valueOf(attendanceResponse.attendanceState) | ||
) | ||
} ?: emptyList(), | ||
), | ||
attendanceDayType = soptEventResponse.toAttendanceDayType() | ||
) | ||
} | ||
|
||
private fun SoptEventResponse?.toAttendanceDayType(): AttendanceDayType { | ||
return when (this?.type) { | ||
"HAS_ATTENDANCE" -> { | ||
val firstAttendanceResponse: SoptEventResponse.AttendanceResponse? = attendances.getOrNull(0) | ||
val secondAttendanceResponse: SoptEventResponse.AttendanceResponse? = attendances.getOrNull(1) | ||
AttendanceDayType.HasAttendance( | ||
session = Session( | ||
name = eventName, | ||
location = location.ifBlank { null }, | ||
startAt = LocalDateTime.parse(startAt), | ||
endAt = LocalDateTime.parse(endAt), | ||
), | ||
firstRoundAttendance = RoundAttendance( | ||
state = RoundAttendanceState.valueOf(firstAttendanceResponse?.status ?: RoundAttendanceState.NOT_YET.name), | ||
attendedAt = LocalDateTime.parse(firstAttendanceResponse?.attendedAt), | ||
), | ||
secondRoundAttendance = RoundAttendance( | ||
state = RoundAttendanceState.valueOf(secondAttendanceResponse?.status ?: RoundAttendanceState.NOT_YET.name), | ||
attendedAt = LocalDateTime.parse(secondAttendanceResponse?.attendedAt), | ||
), | ||
) | ||
} | ||
|
||
"NO_ATTENDANCE" -> { | ||
AttendanceDayType.NoAttendance( | ||
session = Session( | ||
name = eventName, | ||
location = location.ifBlank { null }, | ||
startAt = LocalDateTime.parse(startAt), | ||
endAt = LocalDateTime.parse(endAt), | ||
) | ||
) | ||
} | ||
|
||
"NO_SESSION" -> { | ||
AttendanceDayType.NoSession | ||
} | ||
|
||
else -> { | ||
AttendanceDayType.NoSession | ||
} | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
...src/main/java/org/sopt/official/data/repository/attendance/DefaultAttendanceRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package org.sopt.official.data.repository.attendance | ||
|
||
import kotlinx.serialization.json.Json | ||
import kotlinx.serialization.json.JsonObject | ||
import kotlinx.serialization.json.contentOrNull | ||
import kotlinx.serialization.json.jsonObject | ||
import kotlinx.serialization.json.jsonPrimitive | ||
import org.sopt.official.data.mapToAttendance | ||
import org.sopt.official.data.model.attendance.AttendanceHistoryResponse | ||
import org.sopt.official.data.model.attendance.AttendanceRoundResponse | ||
import org.sopt.official.data.model.attendance.RequestAttendanceCode | ||
import org.sopt.official.data.model.attendance.SoptEventResponse | ||
import org.sopt.official.data.service.attendance.AttendanceService | ||
import org.sopt.official.domain.entity.attendance.Attendance | ||
import org.sopt.official.domain.entity.attendance.ConfirmAttendanceCodeResult | ||
import org.sopt.official.domain.entity.attendance.FetchAttendanceCurrentRoundResult | ||
import org.sopt.official.domain.repository.attendance.NewAttendanceRepository | ||
import retrofit2.HttpException | ||
import javax.inject.Inject | ||
|
||
class DefaultAttendanceRepository @Inject constructor( | ||
private val attendanceService: AttendanceService, | ||
private val json: Json | ||
) : NewAttendanceRepository { | ||
override suspend fun fetchAttendanceInfo(): Attendance { | ||
val soptEventResponse: SoptEventResponse? = runCatching { attendanceService.getSoptEvent().data }.getOrNull() | ||
val attendanceHistoryResponse: AttendanceHistoryResponse? = | ||
runCatching { attendanceService.getAttendanceHistory().data }.getOrNull() | ||
|
||
val attendance: Attendance = | ||
mapToAttendance(attendanceHistoryResponse = attendanceHistoryResponse, soptEventResponse = soptEventResponse) | ||
return attendance | ||
} | ||
|
||
override suspend fun fetchAttendanceCurrentRound(lectureId: Long): FetchAttendanceCurrentRoundResult { | ||
return runCatching { attendanceService.getAttendanceRound(lectureId).data }.fold( | ||
onSuccess = { attendanceRoundResponse: AttendanceRoundResponse? -> | ||
FetchAttendanceCurrentRoundResult.Success(attendanceRoundResponse?.round) | ||
}, | ||
onFailure = { error: Throwable -> | ||
if (error !is HttpException) return FetchAttendanceCurrentRoundResult.Failure(null) | ||
|
||
val message: String? = error.jsonErrorMessage | ||
FetchAttendanceCurrentRoundResult.Failure(message) | ||
}, | ||
) | ||
} | ||
|
||
override suspend fun confirmAttendanceCode( | ||
subLectureId: Long, | ||
code: String | ||
): ConfirmAttendanceCodeResult { | ||
return runCatching { | ||
attendanceService.confirmAttendanceCode(RequestAttendanceCode(subLectureId = subLectureId, code = code)) | ||
}.fold( | ||
onSuccess = { ConfirmAttendanceCodeResult.Success }, | ||
onFailure = { error: Throwable -> | ||
if (error !is HttpException) return ConfirmAttendanceCodeResult.Failure(null) | ||
|
||
val message: String? = error.jsonErrorMessage | ||
ConfirmAttendanceCodeResult.Failure(message) | ||
}, | ||
) | ||
} | ||
|
||
private val HttpException.jsonErrorMessage: String? | ||
get() { | ||
val errorBody: String = this.response()?.errorBody()?.string() ?: return null | ||
val jsonObject: JsonObject = json.parseToJsonElement(errorBody).jsonObject | ||
val errorMessage: String? = jsonObject["message"]?.jsonPrimitive?.contentOrNull | ||
return errorMessage | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
app/src/main/java/org/sopt/official/domain/entity/attendance/Attendance.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package org.sopt.official.domain.entity.attendance | ||
|
||
import java.time.LocalDateTime | ||
|
||
data class Attendance( | ||
val sessionId: Int, | ||
val user: User, | ||
val attendanceDayType: AttendanceDayType, | ||
) { | ||
data class User( | ||
val name: String, | ||
val generation: Int, | ||
val part: Part, | ||
val attendanceScore: Number, | ||
val attendanceCount: AttendanceCount, | ||
val attendanceHistory: List<AttendanceLog> | ||
) { | ||
enum class Part(val partName: String) { | ||
PLAN("기획"), | ||
DESIGN("디자인"), | ||
ANDROID("안드로이드"), | ||
IOS("iOS"), | ||
WEB("웹"), | ||
SERVER("서버"), | ||
UNKNOWN("") | ||
} | ||
|
||
data class AttendanceCount( | ||
/** 출석 전체 횟수 */ | ||
val attendanceCount: Int, | ||
/** 지각 전체 횟수 */ | ||
val lateCount: Int, | ||
/** 결석 전체 횟수 */ | ||
val absenceCount: Int, | ||
) { | ||
/** 전체 횟수 */ | ||
val totalCount: Int | ||
get() = attendanceCount + lateCount + absenceCount | ||
} | ||
|
||
data class AttendanceLog( | ||
val sessionName: String, | ||
val date: String, | ||
val attendanceState: AttendanceState | ||
) { | ||
enum class AttendanceState { | ||
/** 참여(출석 체크 X)*/ | ||
PARTICIPATE, | ||
|
||
/** 출석 */ | ||
ATTENDANCE, | ||
|
||
/** 지각 */ | ||
TARDY, | ||
|
||
/** 결석 */ | ||
ABSENT | ||
} | ||
} | ||
|
||
companion object { | ||
const val UNKNOWN_NAME = "회원" | ||
const val UNKNOWN_GENERATION = -1 | ||
const val UNKNOWN_PART = "UNKNOWN" | ||
} | ||
} | ||
|
||
sealed interface AttendanceDayType { | ||
|
||
/** 일정이 없는 날 */ | ||
data object NoSession : AttendanceDayType | ||
|
||
/** 일정이 있고, 출석 체크가 있는 날 */ | ||
data class HasAttendance( | ||
val session: Session, | ||
val firstRoundAttendance: RoundAttendance, | ||
val secondRoundAttendance: RoundAttendance | ||
) : AttendanceDayType { | ||
/** n차 출석에 관한 정보 */ | ||
data class RoundAttendance( | ||
val state: RoundAttendanceState, | ||
val attendedAt: LocalDateTime? | ||
) { | ||
/** n차 출석 상태 */ | ||
enum class RoundAttendanceState { | ||
ABSENT, ATTENDANCE, NOT_YET, | ||
} | ||
} | ||
} | ||
|
||
/** 일정이 있고, 출석 체크가 없는 날 */ | ||
data class NoAttendance(val session: Session) : AttendanceDayType | ||
} | ||
|
||
/** 솝트의 세션에 관한 정보 | ||
* @property name 세션 이름 (OT, 1차 세미나, 솝커톤 등) | ||
* @property location 세션 장소, 정해진 장소가 없을 경우(온라인) null | ||
* @property startAt 세션 시작 시각 | ||
* @property endAt 세션 종료 시각 | ||
* */ | ||
data class Session( | ||
val name: String, | ||
val location: String?, | ||
val startAt: LocalDateTime, | ||
val endAt: LocalDateTime, | ||
) | ||
|
||
companion object { | ||
const val UNKNOWN_SESSION_ID = -1 | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
app/src/main/java/org/sopt/official/domain/entity/attendance/ConfirmAttendanceCodeResult.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package org.sopt.official.domain.entity.attendance | ||
|
||
sealed interface ConfirmAttendanceCodeResult { | ||
data object Success : ConfirmAttendanceCodeResult | ||
data class Failure(val errorMessage: String?) : ConfirmAttendanceCodeResult | ||
} |
6 changes: 6 additions & 0 deletions
6
...main/java/org/sopt/official/domain/entity/attendance/FetchAttendanceCurrentRoundResult.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package org.sopt.official.domain.entity.attendance | ||
|
||
sealed interface FetchAttendanceCurrentRoundResult { | ||
data class Success(val round: Int?) : FetchAttendanceCurrentRoundResult | ||
data class Failure(val errorMessage: String?) : FetchAttendanceCurrentRoundResult | ||
} |
11 changes: 11 additions & 0 deletions
11
app/src/main/java/org/sopt/official/domain/repository/attendance/NewAttendanceRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package org.sopt.official.domain.repository.attendance | ||
|
||
import org.sopt.official.domain.entity.attendance.Attendance | ||
import org.sopt.official.domain.entity.attendance.ConfirmAttendanceCodeResult | ||
import org.sopt.official.domain.entity.attendance.FetchAttendanceCurrentRoundResult | ||
|
||
interface NewAttendanceRepository { | ||
suspend fun fetchAttendanceInfo(): Attendance | ||
suspend fun fetchAttendanceCurrentRound(lectureId: Long): FetchAttendanceCurrentRoundResult | ||
suspend fun confirmAttendanceCode(subLectureId: Long, code: String): ConfirmAttendanceCodeResult | ||
} |
67 changes: 67 additions & 0 deletions
67
app/src/main/java/org/sopt/official/feature/attendance/AttendanceMapper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package org.sopt.official.feature.attendance | ||
|
||
import kotlinx.collections.immutable.ImmutableMap | ||
import kotlinx.collections.immutable.persistentMapOf | ||
import org.sopt.official.domain.entity.attendance.Attendance | ||
import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType.HasAttendance.RoundAttendance.RoundAttendanceState | ||
import org.sopt.official.feature.attendance.model.AttendanceDayType | ||
import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceResultType | ||
import org.sopt.official.feature.attendance.model.MidtermAttendance | ||
import org.sopt.official.feature.attendance.model.MidtermAttendance.NotYet.AttendanceSession | ||
|
||
fun Attendance.AttendanceDayType.toUiAttendanceDayType(): AttendanceDayType { | ||
return when (this) { | ||
is Attendance.AttendanceDayType.HasAttendance -> { | ||
AttendanceDayType.AttendanceDay.of( | ||
session, | ||
firstRoundAttendance, | ||
secondRoundAttendance | ||
) | ||
} | ||
|
||
is Attendance.AttendanceDayType.NoAttendance -> { | ||
AttendanceDayType.Event.of(session) | ||
} | ||
|
||
is Attendance.AttendanceDayType.NoSession -> { | ||
AttendanceDayType.None | ||
} | ||
} | ||
} | ||
|
||
fun Attendance.User.AttendanceCount.toTotalAttendanceResult(): ImmutableMap<AttendanceResultType, Int> { | ||
return persistentMapOf( | ||
AttendanceResultType.ALL to totalCount, | ||
AttendanceResultType.PRESENT to attendanceCount, | ||
AttendanceResultType.LATE to lateCount, | ||
AttendanceResultType.ABSENT to absenceCount, | ||
) | ||
} | ||
|
||
fun Attendance.AttendanceDayType.HasAttendance.RoundAttendance.toUiFirstRoundAttendance(): MidtermAttendance { | ||
return when (state) { | ||
RoundAttendanceState.ATTENDANCE -> MidtermAttendance.Present( | ||
attendanceAt = attendedAt.toString() | ||
) | ||
|
||
RoundAttendanceState.NOT_YET -> MidtermAttendance.NotYet( | ||
attendanceSession = AttendanceSession.FIRST | ||
) | ||
|
||
RoundAttendanceState.ABSENT -> MidtermAttendance.Absent | ||
} | ||
} | ||
|
||
fun Attendance.AttendanceDayType.HasAttendance.RoundAttendance.toUiSecondRoundAttendance(): MidtermAttendance { | ||
return when (state) { | ||
RoundAttendanceState.ATTENDANCE -> MidtermAttendance.Present( | ||
attendanceAt = attendedAt.toString() | ||
) | ||
|
||
RoundAttendanceState.NOT_YET -> MidtermAttendance.NotYet( | ||
attendanceSession = AttendanceSession.SECOND | ||
) | ||
|
||
RoundAttendanceState.ABSENT -> MidtermAttendance.Absent | ||
} | ||
} |
Oops, something went wrong.