Skip to content

Commit 23d0352

Browse files
feat: video tab progress
1 parent 8a48a24 commit 23d0352

File tree

9 files changed

+116
-84
lines changed

9 files changed

+116
-84
lines changed

core/src/main/java/org/openedx/core/data/model/Progress.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ data class Progress(
1111
val totalAssignmentsCount: Int?,
1212
) {
1313
fun mapToDomain() = Progress(
14-
assignmentsCompleted = assignmentsCompleted ?: 0,
15-
totalAssignmentsCount = totalAssignmentsCount ?: 0
14+
completed = assignmentsCompleted ?: 0,
15+
total = totalAssignmentsCount ?: 0
1616
)
1717

1818
fun mapToRoomEntity() = ProgressDb(

core/src/main/java/org/openedx/core/domain/model/Progress.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import org.openedx.core.extension.safeDivBy
77

88
@Parcelize
99
data class Progress(
10-
val assignmentsCompleted: Int,
11-
val totalAssignmentsCount: Int,
10+
val completed: Int,
11+
val total: Int,
1212
) : Parcelable {
1313

1414
@IgnoredOnParcel
15-
val value: Float = assignmentsCompleted.toFloat().safeDivBy(totalAssignmentsCount.toFloat())
15+
val value: Float = completed.toFloat().safeDivBy(total.toFloat())
1616

1717
companion object {
1818
val DEFAULT_PROGRESS = Progress(0, 0)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="15dp"
3+
android:height="15dp"
4+
android:viewportWidth="15"
5+
android:viewportHeight="15">
6+
<path
7+
android:pathData="M7.5,7.5m-6.5,0a6.5,6.5 0,1 1,13 0a6.5,6.5 0,1 1,-13 0"
8+
android:fillColor="#ffffff" />
9+
<path
10+
android:pathData="M7.5,0.667C3.59,0.667 0.417,3.84 0.417,7.75C0.417,11.66 3.59,14.833 7.5,14.833C11.41,14.833 14.584,11.66 14.584,7.75C14.584,3.84 11.41,0.667 7.5,0.667ZM5.581,10.789L3.038,8.246C2.762,7.97 2.762,7.523 3.038,7.247C3.314,6.971 3.76,6.971 4.037,7.247L6.084,9.287L10.957,4.414C11.233,4.137 11.679,4.137 11.956,4.414C12.232,4.69 12.232,5.136 11.956,5.412L6.579,10.789C6.31,11.065 5.857,11.065 5.581,10.789Z"
11+
android:fillColor="#198571" />
12+
</vector>

core/src/main/res/values/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@
156156
<!-- endregion -->
157157

158158
<string name="core_no_course_content">No course content is currently available.</string>
159-
<string name="core_no_videos">There are currently no videos for this course.</string>
159+
<string name="core_no_videos">There are no videos currently available for this course.</string>
160160
<string name="core_no_dates">Course dates are currently not available.</string>
161161
<string name="core_no_progress">This course does not contain exams or graded assignments.</string>
162162
<string name="core_no_discussion">Unable to load discussions.\n Please try again later.</string>

course/src/main/java/org/openedx/course/presentation/outline/CourseContentAllScreen.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import androidx.compose.ui.Modifier
3131
import androidx.compose.ui.platform.AndroidUriHandler
3232
import androidx.compose.ui.platform.LocalContext
3333
import androidx.compose.ui.res.painterResource
34+
import androidx.compose.ui.res.pluralStringResource
3435
import androidx.compose.ui.res.stringResource
3536
import androidx.compose.ui.text.font.FontWeight
3637
import androidx.compose.ui.tooling.preview.Devices
@@ -270,7 +271,7 @@ private fun CourseContentAllUI(
270271
}
271272

272273
val progress = uiState.courseStructure.progress
273-
if (progress != null && progress.totalAssignmentsCount > 0) {
274+
if (progress != null && progress.total > 0) {
274275
item {
275276
CourseProgress(
276277
modifier = Modifier
@@ -279,7 +280,13 @@ private fun CourseContentAllUI(
279280
start = 24.dp,
280281
end = 24.dp
281282
),
282-
progress = progress
283+
progress = progress,
284+
description = pluralStringResource(
285+
R.plurals.course_assignments_complete,
286+
progress.completed,
287+
progress.completed,
288+
progress.total
289+
)
283290
)
284291
}
285292
}

course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt

Lines changed: 44 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ import androidx.compose.ui.layout.ContentScale
7474
import androidx.compose.ui.platform.LocalConfiguration
7575
import androidx.compose.ui.platform.LocalContext
7676
import androidx.compose.ui.res.painterResource
77-
import androidx.compose.ui.res.pluralStringResource
7877
import androidx.compose.ui.res.stringResource
7978
import androidx.compose.ui.semantics.semantics
8079
import androidx.compose.ui.text.font.FontWeight
@@ -308,7 +307,7 @@ fun CardArrow(
308307
Icon(
309308
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
310309
tint = tint,
311-
contentDescription = "Expandable Arrow",
310+
contentDescription = null,
312311
modifier = Modifier.rotate(degrees),
313312
)
314313
}
@@ -663,8 +662,6 @@ fun CourseVideoSection(
663662
@Composable
664663
fun CourseVideoItem(
665664
videoBlock: Block,
666-
progress: Float = 0.0f,
667-
isCompleted: Boolean = false,
668665
preview: Any?,
669666
onClick: () -> Unit
670667
) {
@@ -674,7 +671,7 @@ fun CourseVideoItem(
674671
.height(108.dp)
675672
.clip(MaterialTheme.appShapes.videoPreviewShape)
676673
.let {
677-
if (progress == 1f) {
674+
if (videoBlock.isCompleted()) {
678675
it.border(
679676
width = 3.dp,
680677
color = MaterialTheme.appColors.successGreen,
@@ -734,33 +731,38 @@ fun CourseVideoItem(
734731
)
735732

736733
// Progress bar (bottom)
737-
Box(
738-
modifier = Modifier
739-
.align(Alignment.BottomStart)
740-
.fillMaxWidth()
741-
.height(8.dp)
742-
.background(Color.Transparent)
743-
.padding(bottom = 8.dp, start = 8.dp, end = 8.dp)
744-
) {
745-
LinearProgressIndicator(
746-
progress = progress,
734+
if (videoBlock.completion > 0.0f) {
735+
Box(
747736
modifier = Modifier
748-
.fillMaxWidth()
749-
.height(8.dp)
750-
.clip(CircleShape),
751-
color = MaterialTheme.appColors.primary,
752-
backgroundColor = MaterialTheme.appColors.progressBarBackgroundColor
753-
)
754-
if (isCompleted) {
755-
Icon(
756-
painter = painterResource(id = coreR.drawable.ic_core_check),
757-
contentDescription = stringResource(R.string.course_accessibility_video_watched),
758-
tint = MaterialTheme.appColors.successGreen,
737+
.padding(bottom = 4.dp)
738+
.height(16.dp)
739+
.align(Alignment.BottomCenter),
740+
contentAlignment = Alignment.Center
741+
) {
742+
LinearProgressIndicator(
759743
modifier = Modifier
760-
.align(Alignment.CenterEnd)
761-
.size(24.dp)
762-
.offset(x = 8.dp)
744+
.fillMaxWidth()
745+
.height(4.dp)
746+
.padding(horizontal = 8.dp)
747+
.clip(CircleShape),
748+
progress = videoBlock.completion.toFloat(),
749+
color = if (videoBlock.isCompleted()) {
750+
MaterialTheme.appColors.progressBarColor
751+
} else {
752+
MaterialTheme.appColors.primary
753+
},
754+
backgroundColor = MaterialTheme.appColors.progressBarBackgroundColor
763755
)
756+
if (videoBlock.isCompleted()) {
757+
Image(
758+
modifier = Modifier
759+
.align(Alignment.BottomEnd)
760+
.size(16.dp)
761+
.offset(x = (-4).dp),
762+
painter = painterResource(id = coreR.drawable.ic_core_check),
763+
contentDescription = stringResource(R.string.course_accessibility_video_watched),
764+
)
765+
}
764766
}
765767
}
766768
}
@@ -794,7 +796,7 @@ fun CourseVideoSectionHeader(
794796
Text(
795797
text = stringResource(
796798
R.string.course_video_watched,
797-
videoBlocks?.mapNotNull { !it.isCompleted() }?.size ?: 0,
799+
videoBlocks?.filter { it.isCompleted() }?.size ?: 0,
798800
videoBlocks?.size ?: 0
799801
),
800802
style = MaterialTheme.appTypography.bodySmall,
@@ -1018,8 +1020,8 @@ fun CourseSubSectionItem(
10181020
MaterialTheme.appColors.onSurface
10191021
}
10201022
val due by rememberSaveable {
1021-
mutableStateOf(block.due?.let { TimeUtils.formatToString(context, it, useRelativeDates) }
1022-
?: "")
1023+
mutableStateOf(
1024+
block.due?.let { TimeUtils.formatToString(context, it, useRelativeDates) } ?: "")
10231025
}
10241026
val isAssignmentEnable =
10251027
!block.isCompleted() && block.assignmentProgress != null && due.isNotEmpty()
@@ -1446,9 +1448,10 @@ fun CourseMessage(
14461448
fun CourseProgress(
14471449
modifier: Modifier = Modifier,
14481450
progress: Progress,
1449-
onVisibilityChanged: ((Boolean) -> Unit)? = null
1451+
description: String,
1452+
isCompletedShown: Boolean = false,
1453+
onVisibilityChanged: (() -> Unit)? = null
14501454
) {
1451-
var isCompletedShown by remember { mutableStateOf(false) }
14521455
val arrowRotation by animateFloatAsState(
14531456
targetValue = if (isCompletedShown) {
14541457
-90f
@@ -1458,13 +1461,12 @@ fun CourseProgress(
14581461
label = ""
14591462
)
14601463
val buttonText = if (isCompletedShown) {
1461-
stringResource(R.string.course_show_completed)
1462-
} else {
14631464
stringResource(R.string.course_hide_completed)
1465+
} else {
1466+
stringResource(R.string.course_show_completed)
14641467
}
14651468
Column(
14661469
modifier = modifier,
1467-
verticalArrangement = Arrangement.spacedBy(8.dp)
14681470
) {
14691471
LinearProgressIndicator(
14701472
modifier = Modifier
@@ -1476,25 +1478,21 @@ fun CourseProgress(
14761478
backgroundColor = MaterialTheme.appColors.progressBarBackgroundColor
14771479
)
14781480
Row(
1479-
modifier = Modifier.fillMaxWidth(),
1481+
modifier = Modifier
1482+
.fillMaxWidth()
1483+
.height(24.dp),
14801484
verticalAlignment = Alignment.CenterVertically,
14811485
horizontalArrangement = Arrangement.SpaceBetween
14821486
) {
14831487
Text(
1484-
text = pluralStringResource(
1485-
R.plurals.course_assignments_complete,
1486-
progress.assignmentsCompleted,
1487-
progress.assignmentsCompleted,
1488-
progress.totalAssignmentsCount
1489-
),
1488+
text = description,
14901489
color = MaterialTheme.appColors.textDark,
14911490
style = MaterialTheme.appTypography.labelSmall
14921491
)
14931492
if (onVisibilityChanged != null) {
14941493
Row(
14951494
modifier = Modifier.clickable {
1496-
isCompletedShown = !isCompletedShown
1497-
onVisibilityChanged(!isCompletedShown)
1495+
onVisibilityChanged()
14981496
},
14991497
verticalAlignment = Alignment.CenterVertically,
15001498
horizontalArrangement = Arrangement.spacedBy(4.dp)

course/src/main/java/org/openedx/course/presentation/videos/CourseContentVideoScreen.kt

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import androidx.compose.runtime.mutableStateOf
2020
import androidx.compose.runtime.remember
2121
import androidx.compose.ui.Alignment
2222
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.res.stringResource
2324
import androidx.compose.ui.tooling.preview.Devices
2425
import androidx.compose.ui.tooling.preview.Preview
2526
import androidx.compose.ui.unit.Dp
@@ -41,6 +42,7 @@ import org.openedx.core.ui.NoContentScreen
4142
import org.openedx.core.ui.displayCutoutForLandscape
4243
import org.openedx.core.ui.theme.OpenEdXTheme
4344
import org.openedx.core.ui.theme.appColors
45+
import org.openedx.course.R
4446
import org.openedx.course.presentation.ui.CourseProgress
4547
import org.openedx.course.presentation.ui.CourseVideoSection
4648
import org.openedx.foundation.presentation.UIMessage
@@ -77,7 +79,7 @@ fun CourseContentVideoScreen(
7779
)
7880
},
7981
onCompletedSectionVisibilityChange = {
80-
viewModel.onCompletedSectionVisibilityChange(it)
82+
viewModel.onCompletedSectionVisibilityChange()
8183
}
8284
)
8385
}
@@ -89,7 +91,7 @@ private fun CourseVideosUI(
8991
uiMessage: UIMessage?,
9092
onVideoClick: (Block) -> Unit,
9193
onDownloadClick: (blocksIds: List<String>) -> Unit,
92-
onCompletedSectionVisibilityChange: (Boolean) -> Unit,
94+
onCompletedSectionVisibilityChange: () -> Unit,
9395
) {
9496
val scaffoldState = rememberScaffoldState()
9597

@@ -143,38 +145,50 @@ private fun CourseVideosUI(
143145
modifier = Modifier.fillMaxSize(),
144146
contentPadding = listBottomPadding
145147
) {
146-
val progress = uiState.courseStructure.progress
147-
if (progress != null && progress.totalAssignmentsCount > 0) {
148-
item {
149-
CourseProgress(
150-
modifier = Modifier
151-
.fillMaxWidth()
152-
.padding(
153-
bottom = 8.dp,
154-
start = 24.dp,
155-
end = 24.dp,
156-
),
157-
progress = progress,
158-
onVisibilityChanged = {
159-
onCompletedSectionVisibilityChange(it)
160-
}
148+
val allVideos = uiState.courseVideos.values.flatten()
149+
val progress = Progress(
150+
completed = allVideos.filter { it.isCompleted() }.size,
151+
total = allVideos.size,
152+
)
153+
item {
154+
CourseProgress(
155+
modifier = Modifier
156+
.fillMaxWidth()
157+
.padding(
158+
bottom = 8.dp,
159+
start = 24.dp,
160+
end = 24.dp,
161+
),
162+
progress = progress,
163+
isCompletedShown = uiState.isCompletedSectionsShown,
164+
onVisibilityChanged = if (progress.completed != 0) {
165+
{ onCompletedSectionVisibilityChange() }
166+
} else {
167+
null
168+
},
169+
description = stringResource(
170+
R.string.course_completed,
171+
progress.completed,
172+
progress.total
161173
)
162-
}
174+
)
163175
}
164176

165177
uiState.courseStructure.blockData.forEach { section ->
166178
val sectionVideos =
167179
uiState.courseVideos[section.id] ?: emptyList()
168180

169-
item {
170-
CourseVideoSection(
171-
block = section,
172-
videoBlocks = sectionVideos,
173-
downloadedStateMap = uiState.downloadedState,
174-
onVideoClick = onVideoClick,
175-
onDownloadClick = onDownloadClick,
176-
preview = uiState.videoPreview
177-
)
181+
if (sectionVideos.any { !it.isCompleted() } || uiState.isCompletedSectionsShown) {
182+
item {
183+
CourseVideoSection(
184+
block = section,
185+
videoBlocks = sectionVideos,
186+
downloadedStateMap = uiState.downloadedState,
187+
onVideoClick = onVideoClick,
188+
onDownloadClick = onDownloadClick,
189+
preview = uiState.videoPreview
190+
)
191+
}
178192
}
179193
}
180194
}

course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class CourseVideoViewModel(
145145
)
146146
}
147147
val isCompletedSectionsShown =
148-
(_uiState.value as? CourseVideoUIState.CourseData)?.isCompletedSectionsShown == true
148+
(_uiState.value as? CourseVideoUIState.CourseData)?.isCompletedSectionsShown == false
149149

150150
_uiState.value =
151151
CourseVideoUIState.CourseData(
@@ -270,11 +270,11 @@ class CourseVideoViewModel(
270270
}
271271
}
272272

273-
fun onCompletedSectionVisibilityChange(isVisible: Boolean) {
273+
fun onCompletedSectionVisibilityChange() {
274274
if (_uiState.value is CourseVideoUIState.CourseData) {
275275
val state = _uiState.value as CourseVideoUIState.CourseData
276276

277-
_uiState.value = state.copy(isCompletedSectionsShown = isVisible)
277+
_uiState.value = state.copy(isCompletedSectionsShown = !state.isCompletedSectionsShown)
278278
}
279279
}
280280

course/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,5 @@
7474
<string name="progress_current_and_max_weighted_graded_percent">%1$s / %2$s%%</string>
7575
<string name="course_progress_no_assignments">This course does not contain graded assignments.</string>
7676
<string name="course_video_watched">%1$s/%2$s Watched</string>
77+
<string name="course_completed">%1$s/%2$s Completed</string>
7778
</resources>

0 commit comments

Comments
 (0)