diff --git a/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/common/LoadingIndicator.kt b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/common/LoadingIndicator.kt new file mode 100644 index 000000000..5a80796f9 --- /dev/null +++ b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/common/LoadingIndicator.kt @@ -0,0 +1,27 @@ +/* + * Designed and developed by Duckie Team, 2022 + * + * Licensed under the MIT. + * Please see full license: https://github.com/duckie-team/duckie-android/blob/develop/LICENSE + */ + +package team.duckie.app.android.feature.ui.exam.result.common + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import team.duckie.quackquack.ui.color.QuackColor + +// TODO(EvergreenTree97): QuackLoadingIndicator로 통합 필요 +@Composable +internal fun LoadingIndicator() { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator(color = QuackColor.DuckieOrange.composeColor) + } +} diff --git a/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/common/ResultBottomBar.kt b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/common/ResultBottomBar.kt new file mode 100644 index 000000000..2ddf7d28b --- /dev/null +++ b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/common/ResultBottomBar.kt @@ -0,0 +1,85 @@ +/* + * Designed and developed by Duckie Team, 2022 + * + * Licensed under the MIT. + * Please see full license: https://github.com/duckie-team/duckie-android/blob/develop/LICENSE + */ + +package team.duckie.app.android.feature.ui.exam.result.common + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import team.duckie.app.android.feature.ui.exam.result.R +import team.duckie.quackquack.ui.border.QuackBorder +import team.duckie.quackquack.ui.color.QuackColor +import team.duckie.quackquack.ui.component.QuackSmallButton +import team.duckie.quackquack.ui.component.QuackSmallButtonType +import team.duckie.quackquack.ui.component.QuackSubtitle +import team.duckie.quackquack.ui.component.QuackSurface + +@Composable +internal fun ResultBottomBar( + onClickRetryButton: () -> Unit, + onClickExitButton: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 20.dp, + vertical = 12.dp, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + ) { + GrayBorderSmallButton( + modifier = Modifier + .heightIn(min = 44.dp) + .weight(1f), + text = stringResource(id = R.string.solve_retry), + onClick = onClickRetryButton, + ) + QuackSmallButton( + modifier = Modifier + .heightIn(44.dp) + .weight(1f), + type = QuackSmallButtonType.Fill, + text = stringResource(id = R.string.exit_exam), + enabled = true, + onClick = onClickExitButton, + ) + } +} + +@Composable +private fun GrayBorderSmallButton( + modifier: Modifier = Modifier, + text: String, + onClick: () -> Unit, +) { + QuackSurface( + modifier = modifier, + backgroundColor = QuackColor.White, + border = QuackBorder(color = QuackColor.Gray3), + shape = RoundedCornerShape(size = 8.dp), + onClick = onClick, + ) { + QuackSubtitle( + modifier = Modifier.padding( + vertical = 12.dp, + horizontal = 12.dp, + ), + text = text, + singleLine = true, + ) + } +} diff --git a/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/screen/ExamResultScreen.kt b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/screen/ExamResultScreen.kt index 2451d0b7b..fd960e8e2 100644 --- a/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/screen/ExamResultScreen.kt +++ b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/screen/ExamResultScreen.kt @@ -7,41 +7,29 @@ package team.duckie.app.android.feature.ui.exam.result.screen -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Scaffold 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.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import team.duckie.app.android.feature.ui.exam.result.ExamResultActivity import team.duckie.app.android.feature.ui.exam.result.R +import team.duckie.app.android.feature.ui.exam.result.common.LoadingIndicator +import team.duckie.app.android.feature.ui.exam.result.common.ResultBottomBar +import team.duckie.app.android.feature.ui.exam.result.screen.exam.ExamResultContent +import team.duckie.app.android.feature.ui.exam.result.screen.quiz.QuizResultContent import team.duckie.app.android.feature.ui.exam.result.viewmodel.ExamResultState import team.duckie.app.android.feature.ui.exam.result.viewmodel.ExamResultViewModel import team.duckie.app.android.shared.ui.compose.ErrorScreen import team.duckie.app.android.shared.ui.compose.quack.QuackCrossfade import team.duckie.app.android.util.compose.activityViewModel -import team.duckie.quackquack.ui.border.QuackBorder -import team.duckie.quackquack.ui.color.QuackColor -import team.duckie.quackquack.ui.component.QuackImage -import team.duckie.quackquack.ui.component.QuackSmallButton -import team.duckie.quackquack.ui.component.QuackSmallButtonType -import team.duckie.quackquack.ui.component.QuackSubtitle -import team.duckie.quackquack.ui.component.QuackSurface import team.duckie.quackquack.ui.component.QuackTopAppBar import team.duckie.quackquack.ui.icon.QuackIcon @@ -70,54 +58,37 @@ internal fun ExamResultScreen( ) }, bottomBar = { - Row( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = 20.dp, - vertical = 12.dp, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(space = 8.dp), - ) { - GrayBorderSmallButton( - modifier = Modifier - .heightIn(min = 44.dp) - .weight(1f), - text = stringResource(id = R.string.solve_retry), - onClick = { // TODO(EvergreenTree97) 해당 시험 본 적 있는지 플래그 생기면 enabled 설정 해야함 - viewModel.clickRetry(activity.getString(R.string.feature_prepare)) - }, - ) - QuackSmallButton( - modifier = Modifier - .heightIn(44.dp) - .weight(1f), - type = QuackSmallButtonType.Fill, - text = stringResource(id = R.string.exit_exam), - enabled = true, - onClick = viewModel::exitExam, - ) - } + ResultBottomBar( + onClickRetryButton = { + viewModel.clickRetry(activity.getString(R.string.feature_prepare)) + }, + onClickExitButton = viewModel::exitExam, + ) }, ) { padding -> - QuackCrossfade(targetState = state) { + QuackCrossfade( + modifier = Modifier + .fillMaxSize() + .padding(padding), + targetState = state, + ) { when (it) { is ExamResultState.Loading -> { LoadingIndicator() } is ExamResultState.Success -> { - Box( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .padding(all = 16.dp), - contentAlignment = Alignment.Center, - ) { - QuackImage( - modifier = Modifier.fillMaxSize(), - src = it.reportUrl, + if (it.isQuiz) { + QuizResultContent( + resultImageUrl = it.reportUrl, + correctProblemCount = 1, + time = 2, + mainTag = "메인", + rank = 3, + ) + } else { + ExamResultContent( + resultImageUrl = it.reportUrl, ) } } @@ -133,38 +104,3 @@ internal fun ExamResultScreen( } } } - -@Composable -internal fun GrayBorderSmallButton( - modifier: Modifier = Modifier, - text: String, - onClick: () -> Unit, -) { - QuackSurface( - modifier = modifier, - backgroundColor = QuackColor.White, - border = QuackBorder(color = QuackColor.Gray3), - shape = RoundedCornerShape(size = 8.dp), - onClick = onClick, - ) { - QuackSubtitle( - modifier = Modifier.padding( - vertical = 12.dp, - horizontal = 12.dp, - ), - text = text, - singleLine = true, - ) - } -} - -// TODO(EvergreenTree97): QuackLoadingIndicator로 통합 필요 -@Composable -internal fun LoadingIndicator() { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator(color = QuackColor.DuckieOrange.composeColor) - } -} diff --git a/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/screen/exam/ExamResultContent.kt b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/screen/exam/ExamResultContent.kt new file mode 100644 index 000000000..ada9cd6d7 --- /dev/null +++ b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/screen/exam/ExamResultContent.kt @@ -0,0 +1,34 @@ +/* + * Designed and developed by Duckie Team, 2022 + * + * Licensed under the MIT. + * Please see full license: https://github.com/duckie-team/duckie-android/blob/develop/LICENSE + */ + +package team.duckie.app.android.feature.ui.exam.result.screen.exam + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import team.duckie.quackquack.ui.component.QuackImage + +@Composable +internal fun ExamResultContent( + resultImageUrl: String, +) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(all = 16.dp), + contentAlignment = Alignment.Center, + ) { + QuackImage( + modifier = Modifier.fillMaxSize(), + src = resultImageUrl, + ) + } +} diff --git a/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/screen/quiz/QuizResultContent.kt b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/screen/quiz/QuizResultContent.kt new file mode 100644 index 000000000..71082b90f --- /dev/null +++ b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/screen/quiz/QuizResultContent.kt @@ -0,0 +1,81 @@ +/* + * Designed and developed by Duckie Team, 2022 + * + * Licensed under the MIT. + * Please see full license: https://github.com/duckie-team/duckie-android/blob/develop/LICENSE + */ + +package team.duckie.app.android.feature.ui.exam.result.screen.quiz + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import kotlinx.collections.immutable.persistentListOf +import team.duckie.app.android.feature.ui.exam.result.R +import team.duckie.app.android.shared.ui.compose.QuackAnnotatedText +import team.duckie.app.android.shared.ui.compose.Spacer +import team.duckie.app.android.util.kotlin.toHourMinuteSecond +import team.duckie.quackquack.ui.component.QuackBody1 +import team.duckie.quackquack.ui.component.QuackDivider +import team.duckie.quackquack.ui.component.QuackImage +import team.duckie.quackquack.ui.textstyle.QuackTextStyle + +@Composable +internal fun QuizResultContent( + resultImageUrl: String, + time: Int, + correctProblemCount: Int, + mainTag: String, + rank: Int, +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(all = 16.dp), + ) { + QuackImage( + modifier = Modifier.fillMaxWidth(), + src = resultImageUrl, + ) + Spacer(space = 28.dp) + QuackDivider() + Spacer(space = 20.dp) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + QuackBody1(text = stringResource(id = R.string.total_time)) + QuackBody1(text = time.toHourMinuteSecond()) + } + Spacer(space = 8.dp) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + QuackBody1(text = stringResource(id = R.string.correct_problem)) + QuackBody1( + text = stringResource( + id = R.string.correct_problem_unit, + correctProblemCount.toString(), + ), + ) + } + Spacer(space = 28.dp) + QuackAnnotatedText( + text = stringResource(id = R.string.rank_by_tag, mainTag, rank.toString()), + highlightTextPairs = persistentListOf( + mainTag to null, + rank.toString() to null, + ), + style = QuackTextStyle.HeadLine1, + ) + Spacer(space = 38.dp) + } +} diff --git a/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/viewmodel/ExamResultState.kt b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/viewmodel/ExamResultState.kt index acd8ebcdc..ecbf61197 100644 --- a/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/viewmodel/ExamResultState.kt +++ b/feature-ui-exam-result/src/main/java/team/duckie/app/android/feature/ui/exam/result/viewmodel/ExamResultState.kt @@ -12,6 +12,7 @@ sealed class ExamResultState { data class Success( val reportUrl: String = "", + val isQuiz: Boolean = true, ) : ExamResultState() data class Error( diff --git a/feature-ui-exam-result/src/main/res/values/strings.xml b/feature-ui-exam-result/src/main/res/values/strings.xml index 44b45cab1..251fdcf33 100644 --- a/feature-ui-exam-result/src/main/res/values/strings.xml +++ b/feature-ui-exam-result/src/main/res/values/strings.xml @@ -9,4 +9,8 @@ 다시 풀기 시험 끝내기 준비 중인 기능입니다. + 총 응시시간 + 맞은 문제 + %s덕 + 당신은 %s 영역 %s위 입니다. diff --git a/util-kotlin/src/main/kotlin/team/duckie/app/android/util/kotlin/times.kt b/util-kotlin/src/main/kotlin/team/duckie/app/android/util/kotlin/times.kt index 303e1e85e..c5f623d00 100644 --- a/util-kotlin/src/main/kotlin/team/duckie/app/android/util/kotlin/times.kt +++ b/util-kotlin/src/main/kotlin/team/duckie/app/android/util/kotlin/times.kt @@ -4,6 +4,7 @@ * Licensed under the MIT. * Please see full license: https://github.com/duckie-team/duckie-android/blob/develop/LICENSE */ +@file:Suppress("MagicNumber") package team.duckie.app.android.util.kotlin @@ -21,3 +22,13 @@ inline val Double.seconds: Long get() = (this * 1000).toLong() * 주어진 분을 MS 로 반환합니다. */ inline val Int.minutes: Long get() = this.seconds * 60 + +/** + * 주어진 초를 HH:MM:SS 형식 으로 변환합니다. + */ +fun Int.toHourMinuteSecond(): String { + val hours = this / 3600 + val minutes = (this % 3600) / 60 + val seconds = this % 60 + return "%02d:%02d:%02d".format(hours, minutes, seconds) +} diff --git a/util-kotlin/src/test/kotlin/team/duckie/app/android/util/kotlin/TimesTest.kt b/util-kotlin/src/test/kotlin/team/duckie/app/android/util/kotlin/TimesTest.kt index 2565ca9a3..31dfca2ab 100644 --- a/util-kotlin/src/test/kotlin/team/duckie/app/android/util/kotlin/TimesTest.kt +++ b/util-kotlin/src/test/kotlin/team/duckie/app/android/util/kotlin/TimesTest.kt @@ -65,4 +65,18 @@ class TimesTest { expectThat(900.minutes).isEqualTo(54000000L) expectThat(1000.minutes).isEqualTo(60000000L) } + + @Test + fun `should HH,MM,SS when convert the seconds contains minute`() { + val actual = 100.toHourMinuteSecond() + val expected = "00:01:40" + expectThat(actual).isEqualTo(expected) + } + + @Test + fun `should HH,MM,SS when convert the seconds cointains hour`() { + val actual = 10000.toHourMinuteSecond() + val expected = "02:46:40" + expectThat(actual).isEqualTo(expected) + } }