From 4d78460d76229d0e5b13b91cfa2618f3f943e6d0 Mon Sep 17 00:00:00 2001 From: Volodymyr Chekyrta Date: Tue, 14 May 2024 22:47:47 +0300 Subject: [PATCH] feat: Course home. Moved certificate access. --- .../outline/CourseOutlineScreen.kt | 23 ++ .../course/presentation/ui/CourseUI.kt | 304 ++++-------------- .../res/drawable/ic_course_certificate.xml | 9 + .../res/drawable/ic_course_completed_mark.xml | 31 -- course/src/main/res/values-uk/strings.xml | 6 +- course/src/main/res/values/strings.xml | 8 +- 6 files changed, 102 insertions(+), 279 deletions(-) create mode 100644 course/src/main/res/drawable/ic_course_certificate.xml delete mode 100644 course/src/main/res/drawable/ic_course_completed_mark.xml diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt index 7e950cba8..6b9f9626d 100644 --- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt +++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt @@ -66,6 +66,7 @@ import org.openedx.course.presentation.CourseRouter import org.openedx.course.presentation.ui.CourseDatesBanner import org.openedx.course.presentation.ui.CourseDatesBannerTablet import org.openedx.course.presentation.ui.CourseExpandableChapterCard +import org.openedx.course.presentation.ui.CourseMessage import org.openedx.course.presentation.ui.CourseSectionCard import org.openedx.course.presentation.ui.CourseSubSectionItem import java.io.File @@ -272,6 +273,28 @@ private fun CourseOutlineUI( } } } + + val certificate = uiState.courseStructure.certificate + if (certificate?.isCertificateEarned() == true) { + item { + CourseMessage( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp) + .then(listPadding), + icon = painterResource(R.drawable.ic_course_certificate), + message = stringResource( + R.string.course_you_earned_certificate, + uiState.courseStructure.name + ), + action = stringResource(R.string.course_view_certificate), + onActionClick = { + onCertificateClick(certificate.certificateURL ?: "") + } + ) + } + } + if (uiState.resumeComponent != null) { item { Box(listPadding) { diff --git a/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt b/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt index f9f028c0f..50d569308 100644 --- a/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt +++ b/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding @@ -45,8 +44,6 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.TaskAlt import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -60,32 +57,24 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import coil.compose.AsyncImage -import coil.request.ImageRequest import org.jsoup.Jsoup import org.openedx.core.BlockType import org.openedx.core.domain.model.Block import org.openedx.core.domain.model.BlockCounts -import org.openedx.core.domain.model.Certificate import org.openedx.core.domain.model.CourseDatesBannerInfo -import org.openedx.core.domain.model.CourseSharingUtmParameters -import org.openedx.core.domain.model.CoursewareAccess -import org.openedx.core.domain.model.EnrolledCourse -import org.openedx.core.domain.model.EnrolledCourseData -import org.openedx.core.extension.isLinkValid import org.openedx.core.extension.nonZero import org.openedx.core.extension.toFileSize import org.openedx.core.module.db.DownloadModel @@ -97,7 +86,6 @@ import org.openedx.core.ui.OpenEdXButton import org.openedx.core.ui.OpenEdXOutlinedButton import org.openedx.core.ui.displayCutoutForLandscape import org.openedx.core.ui.noRippleClickable -import org.openedx.core.ui.rememberWindowSize import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appShapes @@ -110,89 +98,6 @@ import subtitleFile.TimedTextObject import java.util.Date import org.openedx.core.R as coreR -@Composable -fun CourseImageHeader( - modifier: Modifier, - apiHostUrl: String, - courseImage: String?, - courseCertificate: Certificate?, - onCertificateClick: (String) -> Unit = {}, - courseName: String, -) { - val configuration = LocalConfiguration.current - val windowSize = rememberWindowSize() - val contentScale = - if (!windowSize.isTablet && configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { - ContentScale.Fit - } else { - ContentScale.Crop - } - val imageUrl = if (courseImage?.isLinkValid() == true) { - courseImage - } else { - apiHostUrl.dropLast(1) + courseImage - } - Box(modifier = modifier, contentAlignment = Alignment.Center) { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(imageUrl) - .error(coreR.drawable.core_no_image_course) - .placeholder(coreR.drawable.core_no_image_course) - .build(), - contentDescription = stringResource( - id = coreR.string.core_accessibility_header_image_for, - courseName - ), - contentScale = contentScale, - modifier = Modifier - .fillMaxSize() - .clip(MaterialTheme.appShapes.cardShape) - ) - if (courseCertificate?.isCertificateEarned() == true) { - Column( - Modifier - .fillMaxSize() - .clip(MaterialTheme.appShapes.cardShape) - .background(MaterialTheme.appColors.certificateForeground), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Icon( - modifier = Modifier.testTag("ic_congratulations"), - painter = painterResource(id = R.drawable.ic_course_completed_mark), - contentDescription = stringResource(id = R.string.course_congratulations), - tint = Color.White - ) - Spacer(Modifier.height(6.dp)) - Text( - modifier = Modifier.testTag("txt_congratulations"), - text = stringResource(id = R.string.course_congratulations), - style = MaterialTheme.appTypography.headlineMedium, - color = Color.White - ) - Spacer(Modifier.height(4.dp)) - Text( - modifier = Modifier.testTag("txt_course_passed"), - text = stringResource(id = R.string.course_passed), - style = MaterialTheme.appTypography.bodyMedium, - color = Color.White - ) - Spacer(Modifier.height(20.dp)) - OpenEdXOutlinedButton( - modifier = Modifier, - borderColor = Color.White, - textColor = MaterialTheme.appColors.buttonText, - text = stringResource(id = R.string.course_view_certificate), - onClick = { - courseCertificate.certificateURL?.let { - onCertificateClick(it) - } - }) - } - } - } -} - @Composable fun CourseSectionCard( block: Block, @@ -377,48 +282,6 @@ fun CardArrow( ) } -@Composable -fun SequentialItem( - block: Block, - onClick: (Block) -> Unit -) { - val icon = if (block.isCompleted()) Icons.Filled.TaskAlt else Icons.Filled.Home - val iconColor = - if (block.isCompleted()) MaterialTheme.appColors.primary else MaterialTheme.appColors.onSurface - Row( - Modifier - .fillMaxWidth() - .padding( - horizontal = 20.dp, - vertical = 12.dp - ) - .clickable { onClick(block) }, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row(Modifier.weight(1f)) { - Icon( - imageVector = icon, - contentDescription = null, - tint = iconColor - ) - Spacer(modifier = Modifier.width(16.dp)) - Text( - block.displayName, - style = MaterialTheme.appTypography.titleMedium, - color = MaterialTheme.appColors.textPrimary, - overflow = TextOverflow.Ellipsis, - maxLines = 1 - ) - } - Icon( - imageVector = Icons.Filled.ChevronRight, - tint = MaterialTheme.appColors.onSurface, - contentDescription = "Expandable Arrow" - ) - } -} - @Composable fun VideoTitle( text: String, @@ -859,36 +722,6 @@ fun CourseSubSectionItem( } } -@Composable -fun CourseToolbar( - title: String, - onBackClick: () -> Unit -) { - OpenEdXTheme { - Box( - modifier = Modifier - .fillMaxWidth() - .displayCutoutForLandscape() - .zIndex(1f) - .statusBarsPadding(), - contentAlignment = Alignment.CenterStart - ) { - BackBtn { onBackClick() } - Text( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 56.dp), - text = title, - color = MaterialTheme.appColors.textPrimary, - style = MaterialTheme.appTypography.titleMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center - ) - } - } -} - @Composable fun CourseUnitToolbar( title: String, @@ -1207,6 +1040,49 @@ fun DatesShiftedSnackBar( } } +@Composable +fun CourseMessage( + modifier: Modifier = Modifier, + icon: Painter, + message: String, + action: String? = null, + onActionClick: () -> Unit = {} +) { + Column { + Row( + modifier + .semantics(mergeDescendants = true) {} + .noRippleClickable(onActionClick) + ) { + Icon( + painter = icon, + contentDescription = null, + modifier = Modifier.align(Alignment.CenterVertically), + tint = MaterialTheme.appColors.textPrimary + ) + Column(Modifier.padding(start = 12.dp)) { + Text( + text = message, + color = MaterialTheme.appColors.textPrimary, + style = MaterialTheme.appTypography.labelLarge + ) + if (action != null) { + Text( + text = action, + modifier = Modifier.padding(top = 4.dp), + color = MaterialTheme.appColors.textPrimary, + style = MaterialTheme.appTypography.labelLarge.copy(textDecoration = TextDecoration.Underline) + ) + } + } + } + Divider( + color = MaterialTheme.appColors.divider + ) + } + +} + @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable @@ -1263,17 +1139,6 @@ private fun NavigationUnitsButtonsWithNextPreview() { } } -@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun SequentialItemPreview() { - OpenEdXTheme { - Surface(color = MaterialTheme.appColors.background) { - SequentialItem(block = mockChapterBlock, onClick = {}) - } - } -} - @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable @@ -1290,26 +1155,6 @@ private fun CourseChapterItemPreview() { } } -@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun CourseHeaderPreview() { - OpenEdXTheme { - Surface(color = MaterialTheme.appColors.background) { - CourseImageHeader( - modifier = Modifier - .fillMaxWidth() - .height(200.dp) - .padding(6.dp), - apiHostUrl = "", - courseCertificate = Certificate(""), - courseImage = "", - courseName = "" - ) - } - } -} - @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable @@ -1361,42 +1206,27 @@ private fun OfflineQueueCardPreview() { } } -private val mockCourse = EnrolledCourse( - auditAccessExpires = Date(), - created = "created", - certificate = Certificate(""), - mode = "mode", - isActive = true, - course = EnrolledCourseData( - id = "id", - name = "Course name", - number = "", - org = "Org", - start = Date(), - startDisplay = "", - startType = "", - end = Date(), - dynamicUpgradeDeadline = "", - subscriptionId = "", - coursewareAccess = CoursewareAccess( - true, - "", - "", - "", - "", - "" - ), - media = null, - courseImage = "", - courseAbout = "", - courseSharingUtmParameters = CourseSharingUtmParameters("", ""), - courseUpdates = "", - courseHandouts = "", - discussionUrl = "", - videoOutline = "", - isSelfPaced = false - ) -) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun CourseMessagePreview() { + OpenEdXTheme { + Surface(color = MaterialTheme.appColors.background) { + CourseMessage( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 12.dp), + icon = painterResource(R.drawable.ic_course_certificate), + message = stringResource( + R.string.course_you_earned_certificate, + "Demo Course" + ), + action = stringResource(R.string.course_view_certificate), + ) + } + } +} + private val mockChapterBlock = Block( id = "id", blockId = "blockId", diff --git a/course/src/main/res/drawable/ic_course_certificate.xml b/course/src/main/res/drawable/ic_course_certificate.xml new file mode 100644 index 000000000..53ca91779 --- /dev/null +++ b/course/src/main/res/drawable/ic_course_certificate.xml @@ -0,0 +1,9 @@ + + + diff --git a/course/src/main/res/drawable/ic_course_completed_mark.xml b/course/src/main/res/drawable/ic_course_completed_mark.xml deleted file mode 100644 index bf3307778..000000000 --- a/course/src/main/res/drawable/ic_course_completed_mark.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - diff --git a/course/src/main/res/values-uk/strings.xml b/course/src/main/res/values-uk/strings.xml index ffbf7c459..14f3487c4 100644 --- a/course/src/main/res/values-uk/strings.xml +++ b/course/src/main/res/values-uk/strings.xml @@ -5,12 +5,8 @@ Одиниці курсу Підрозділи курсу Відео - Ви успішно пройшли курс! Тепер ви можете отримати сертифікат - Ви успішно пройшли курс - Вітаємо! + Вітаємо, ви отримали сертифікат про проходження курсу \"%s\". Переглянути сертифікат - Ви можете отримати сертифікат після проходження курсу (заробіть необхідну оцінку) - Отримати сертифікат Назад Попередня одиниця Далі diff --git a/course/src/main/res/values/strings.xml b/course/src/main/res/values/strings.xml index c6b370267..802065471 100644 --- a/course/src/main/res/values/strings.xml +++ b/course/src/main/res/values/strings.xml @@ -5,12 +5,8 @@ Course units Course subsections Videos - You have passed the course! Now you can get the certificate - You’ve completed the course - Congratulations! - View the certificate - You can get a certificate after completing the course (earn required grade) - Get the certificate + Congratulations, you have earned this course certificate in \"%s\". + View certificate Prev Previous Unit Next