@@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable
66import androidx.compose.foundation.layout.Arrangement
77import androidx.compose.foundation.layout.Box
88import androidx.compose.foundation.layout.Column
9+ import androidx.compose.foundation.layout.IntrinsicSize
910import androidx.compose.foundation.layout.PaddingValues
1011import androidx.compose.foundation.layout.Row
1112import androidx.compose.foundation.layout.Spacer
@@ -52,6 +53,7 @@ import androidx.compose.ui.Modifier
5253import androidx.compose.ui.graphics.painter.Painter
5354import androidx.compose.ui.graphics.vector.rememberVectorPainter
5455import androidx.compose.ui.layout.ContentScale
56+ import androidx.compose.ui.platform.LocalConfiguration
5557import androidx.compose.ui.platform.LocalContext
5658import androidx.compose.ui.platform.testTag
5759import androidx.compose.ui.res.painterResource
@@ -61,6 +63,7 @@ import androidx.compose.ui.text.style.TextAlign
6163import androidx.compose.ui.text.style.TextOverflow
6264import androidx.compose.ui.tooling.preview.Devices
6365import androidx.compose.ui.tooling.preview.Preview
66+ import androidx.compose.ui.unit.Dp
6467import androidx.compose.ui.unit.dp
6568import androidx.fragment.app.FragmentManager
6669import androidx.lifecycle.Lifecycle
@@ -88,6 +91,7 @@ import org.openedx.core.ui.HandleUIMessage
8891import org.openedx.core.ui.OfflineModeDialog
8992import org.openedx.core.ui.OpenEdXButton
9093import org.openedx.core.ui.TextIcon
94+ import org.openedx.core.ui.displayCutoutForLandscape
9195import org.openedx.core.ui.theme.OpenEdXTheme
9296import org.openedx.core.ui.theme.appColors
9397import org.openedx.core.ui.theme.appShapes
@@ -200,6 +204,7 @@ private fun DashboardGalleryView(
200204 Surface (
201205 modifier = Modifier
202206 .fillMaxSize()
207+ .displayCutoutForLandscape()
203208 .padding(paddingValues),
204209 color = MaterialTheme .appColors.background
205210 ) {
@@ -528,7 +533,7 @@ private fun PrimaryCourseCard(
528533 resumeBlockId : (enrolledCourse: EnrolledCourse , blockId: String ) -> Unit ,
529534 openCourse : (EnrolledCourse ) -> Unit ,
530535) {
531- val context = LocalContext .current
536+ val orientation = LocalConfiguration .current.orientation
532537 Card (
533538 modifier = Modifier
534539 .padding(horizontal = 16 .dp)
@@ -538,103 +543,184 @@ private fun PrimaryCourseCard(
538543 shape = MaterialTheme .appShapes.courseImageShape,
539544 elevation = 4 .dp
540545 ) {
541- Column (
542- modifier = Modifier
543- .clickable {
544- openCourse(primaryCourse)
545- }
546- ) {
547- AsyncImage (
548- model = ImageRequest .Builder (context)
549- .data(primaryCourse.course.courseImage.toImageLink(apiHostUrl))
550- .error(CoreR .drawable.core_no_image_course)
551- .placeholder(CoreR .drawable.core_no_image_course)
552- .build(),
553- contentDescription = null ,
554- contentScale = ContentScale .Crop ,
555- modifier = Modifier
556- .fillMaxWidth()
557- .height(140 .dp)
558- )
559- val progress: Float = try {
560- primaryCourse.progress.assignmentsCompleted.toFloat() /
561- primaryCourse.progress.totalAssignmentsCount.toFloat()
562- } catch (_: ArithmeticException ) {
563- 0f
564- }
565- LinearProgressIndicator (
566- modifier = Modifier
567- .fillMaxWidth()
568- .height(8 .dp),
569- progress = progress,
570- color = MaterialTheme .appColors.primary,
571- backgroundColor = MaterialTheme .appColors.divider
572- )
573- PrimaryCourseTitle (
574- modifier = Modifier
575- .fillMaxWidth()
576- .padding(horizontal = 12 .dp)
577- .padding(top = 8 .dp, bottom = 16 .dp),
578- primaryCourse = primaryCourse
579- )
580- val pastAssignments = primaryCourse.courseAssignments?.pastAssignments
581- if (! pastAssignments.isNullOrEmpty()) {
582- val nearestAssignment = pastAssignments.maxBy { it.date }
583- val title = if (pastAssignments.size == 1 ) nearestAssignment.title else null
584- Divider ()
585- AssignmentItem (
586- modifier = Modifier .clickable {
587- if (pastAssignments.size == 1 ) {
588- resumeBlockId(primaryCourse, nearestAssignment.blockId)
589- } else {
590- navigateToDates(primaryCourse)
546+ when (orientation) {
547+ Configuration .ORIENTATION_LANDSCAPE -> {
548+ Row (
549+ modifier = Modifier
550+ .clickable {
551+ openCourse(primaryCourse)
591552 }
592- },
593- painter = rememberVectorPainter( Icons . Default . Warning ),
594- title = title,
595- info = pluralStringResource(
596- R .plurals.dashboard_past_due_assignment ,
597- pastAssignments.size ,
598- pastAssignments.size
553+ .height( IntrinsicSize . Min )
554+ ) {
555+ PrimaryCourseCaption (
556+ modifier = Modifier .weight( 1f ),
557+ primaryCourse = primaryCourse ,
558+ apiHostUrl = apiHostUrl ,
559+ imageHeight = null ,
599560 )
600- )
561+ PrimaryCourseButtons (
562+ modifier = Modifier .weight(1f ),
563+ primaryCourse = primaryCourse,
564+ navigateToDates = navigateToDates,
565+ resumeBlockId = resumeBlockId,
566+ openCourse = openCourse,
567+ adjustHeight = true ,
568+ useRelativeDates = useRelativeDates,
569+ )
570+ }
601571 }
602- val futureAssignments = primaryCourse.courseAssignments?.futureAssignments
603- if (! futureAssignments.isNullOrEmpty()) {
604- val nearestAssignment = futureAssignments.minBy { it.date }
605- val title = if (futureAssignments.size == 1 ) nearestAssignment.title else null
606- Divider ()
607- AssignmentItem (
572+
573+ else -> {
574+ Column (
608575 modifier = Modifier .clickable {
609- if (futureAssignments.size == 1 ) {
610- resumeBlockId(primaryCourse, nearestAssignment.blockId)
611- } else {
612- navigateToDates(primaryCourse)
613- }
614- },
615- painter = painterResource(id = CoreR .drawable.ic_core_chapter_icon),
616- title = title,
617- info = stringResource(
618- R .string.dashboard_assignment_due,
619- nearestAssignment.assignmentType ? : " " ,
620- stringResource(
621- id = CoreR .string.core_date_format_assignment_due,
622- TimeUtils .formatToString(context, nearestAssignment.date, useRelativeDates)
623- )
576+ openCourse(primaryCourse)
577+ }
578+ ) {
579+ PrimaryCourseCaption (
580+ primaryCourse = primaryCourse,
581+ apiHostUrl = apiHostUrl,
624582 )
625- )
583+ PrimaryCourseButtons (
584+ primaryCourse = primaryCourse,
585+ navigateToDates = navigateToDates,
586+ resumeBlockId = resumeBlockId,
587+ openCourse = openCourse,
588+ useRelativeDates = useRelativeDates,
589+ )
590+ }
626591 }
627- ResumeButton (
628- primaryCourse = primaryCourse,
629- onClick = {
630- if (primaryCourse.courseStatus == null ) {
631- openCourse(primaryCourse)
592+ }
593+ }
594+ }
595+
596+ @Composable
597+ private fun PrimaryCourseButtons (
598+ modifier : Modifier = Modifier ,
599+ primaryCourse : EnrolledCourse ,
600+ useRelativeDates : Boolean ,
601+ adjustHeight : Boolean = false,
602+ navigateToDates : (EnrolledCourse ) -> Unit ,
603+ resumeBlockId : (enrolledCourse: EnrolledCourse , blockId: String ) -> Unit ,
604+ openCourse : (EnrolledCourse ) -> Unit ,
605+ ) {
606+ val context = LocalContext .current
607+ val pastAssignments = primaryCourse.courseAssignments?.pastAssignments
608+ Column (modifier = modifier) {
609+ var titleModifier = Modifier
610+ .fillMaxWidth()
611+ .padding(horizontal = 12 .dp)
612+ .padding(top = 8 .dp, bottom = 16 .dp)
613+ if (adjustHeight) {
614+ titleModifier = titleModifier.weight(1f )
615+ }
616+ PrimaryCourseTitle (
617+ modifier = titleModifier,
618+ primaryCourse = primaryCourse,
619+ )
620+ Divider ()
621+ if (! pastAssignments.isNullOrEmpty()) {
622+ val nearestAssignment = pastAssignments.maxBy { it.date }
623+ val title = if (pastAssignments.size == 1 ) nearestAssignment.title else null
624+ AssignmentItem (
625+ modifier = Modifier .clickable {
626+ if (pastAssignments.size == 1 ) {
627+ resumeBlockId(primaryCourse, nearestAssignment.blockId)
632628 } else {
633- resumeBlockId (primaryCourse, primaryCourse.courseStatus?.lastVisitedBlockId ? : " " )
629+ navigateToDates (primaryCourse)
634630 }
635- }
631+ },
632+ painter = rememberVectorPainter(Icons .Default .Warning ),
633+ title = title,
634+ info = pluralStringResource(
635+ R .plurals.dashboard_past_due_assignment,
636+ pastAssignments.size,
637+ pastAssignments.size
638+ )
639+ )
640+ }
641+ val futureAssignments = primaryCourse.courseAssignments?.futureAssignments
642+ if (! futureAssignments.isNullOrEmpty()) {
643+ val nearestAssignment = futureAssignments.minBy { it.date }
644+ val title = if (futureAssignments.size == 1 ) nearestAssignment.title else null
645+ Divider ()
646+ AssignmentItem (
647+ modifier = Modifier .clickable {
648+ if (futureAssignments.size == 1 ) {
649+ resumeBlockId(primaryCourse, nearestAssignment.blockId)
650+ } else {
651+ navigateToDates(primaryCourse)
652+ }
653+ },
654+ painter = painterResource(id = CoreR .drawable.ic_core_chapter_icon),
655+ title = title,
656+ info = stringResource(
657+ R .string.dashboard_assignment_due,
658+ nearestAssignment.assignmentType ? : " " ,
659+ stringResource(
660+ id = CoreR .string.core_date_format_assignment_due,
661+ TimeUtils .formatToString(context, nearestAssignment.date, useRelativeDates),
662+ )
663+ )
636664 )
637665 }
666+ ResumeButton (
667+ primaryCourse = primaryCourse,
668+ onClick = {
669+ if (primaryCourse.courseStatus == null ) {
670+ openCourse(primaryCourse)
671+ } else {
672+ resumeBlockId(
673+ primaryCourse,
674+ primaryCourse.courseStatus?.lastVisitedBlockId ? : " "
675+ )
676+ }
677+ }
678+ )
679+ }
680+ }
681+
682+ @Composable
683+ private fun PrimaryCourseCaption (
684+ modifier : Modifier = Modifier ,
685+ primaryCourse : EnrolledCourse ,
686+ imageHeight : Dp ? = 140.dp,
687+ apiHostUrl : String ,
688+ ) {
689+ val context = LocalContext .current
690+ Column (modifier = modifier) {
691+ val imageModifier = imageHeight?.let {
692+ Modifier
693+ .height(it)
694+ .fillMaxWidth()
695+ } ? : Modifier
696+ .height(IntrinsicSize .Max )
697+ .fillMaxWidth()
698+ .weight(1f )
699+
700+ AsyncImage (
701+ model = ImageRequest .Builder (context)
702+ .data(primaryCourse.course.courseImage.toImageLink(apiHostUrl))
703+ .error(CoreR .drawable.core_no_image_course)
704+ .placeholder(CoreR .drawable.core_no_image_course)
705+ .build(),
706+ contentDescription = null ,
707+ contentScale = ContentScale .Crop ,
708+ modifier = imageModifier,
709+ )
710+ val progress: Float = try {
711+ primaryCourse.progress.assignmentsCompleted.toFloat() /
712+ primaryCourse.progress.totalAssignmentsCount.toFloat()
713+ } catch (_: ArithmeticException ) {
714+ 0f
715+ }
716+ LinearProgressIndicator (
717+ modifier = Modifier
718+ .fillMaxWidth()
719+ .height(8 .dp),
720+ progress = progress,
721+ color = MaterialTheme .appColors.primary,
722+ backgroundColor = MaterialTheme .appColors.divider
723+ )
638724 }
639725}
640726
@@ -704,7 +790,7 @@ private fun PrimaryCourseTitle(
704790) {
705791 Column (
706792 modifier = modifier,
707- verticalArrangement = Arrangement .spacedBy( 4 .dp)
793+ verticalArrangement = Arrangement .Center
708794 ) {
709795 Text (
710796 modifier = Modifier .fillMaxWidth(),
@@ -713,15 +799,19 @@ private fun PrimaryCourseTitle(
713799 color = MaterialTheme .appColors.textFieldHint
714800 )
715801 Text (
716- modifier = Modifier .fillMaxWidth(),
802+ modifier = Modifier
803+ .fillMaxWidth()
804+ .padding(top = 4 .dp),
717805 text = primaryCourse.course.name,
718806 style = MaterialTheme .appTypography.titleLarge,
719807 color = MaterialTheme .appColors.textDark,
720808 overflow = TextOverflow .Ellipsis ,
721809 maxLines = 3
722810 )
723811 Text (
724- modifier = Modifier .fillMaxWidth(),
812+ modifier = Modifier
813+ .fillMaxWidth()
814+ .padding(top = 4 .dp),
725815 style = MaterialTheme .appTypography.labelMedium,
726816 color = MaterialTheme .appColors.textFieldHint,
727817 text = TimeUtils .getCourseFormattedDate(
0 commit comments