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