Skip to content

Commit fcf0409

Browse files
feat: reuse dates UI from CourseDatesScreen
1 parent 0c93493 commit fcf0409

File tree

13 files changed

+382
-373
lines changed

13 files changed

+382
-373
lines changed

app/src/main/java/org/openedx/app/di/ScreenModule.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,8 @@ val screenModule = module {
512512
datesRouter = get(),
513513
networkConnection = get(),
514514
resourceManager = get(),
515-
datesInteractor = get()
515+
datesInteractor = get(),
516+
corePreferences = get()
516517
)
517518
}
518519
}

core/src/main/java/org/openedx/core/config/Config.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class Config(context: Context) {
9393
}
9494

9595
fun getDatesConfig(): DatesConfig {
96-
return getObjectOrNewInstance(DATES, DatesConfig::class.java)
96+
return getObjectOrNewInstance(APP_LEVEL_DATES, DatesConfig::class.java)
9797
}
9898

9999
fun getBranchConfig(): BranchConfig {
@@ -183,7 +183,7 @@ class Config(context: Context) {
183183
private const val DISCOVERY = "DISCOVERY"
184184
private const val PROGRAM = "PROGRAM"
185185
private const val DASHBOARD = "DASHBOARD"
186-
private const val DATES = "DATES"
186+
private const val APP_LEVEL_DATES = "APP_LEVEL_DATES"
187187
private const val BRANCH = "BRANCH"
188188
private const val UI_COMPONENTS = "UI_COMPONENTS"
189189
private const val PLATFORM_NAME = "PLATFORM_NAME"
Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package org.openedx.core.domain.model
22

3+
import androidx.compose.material.MaterialTheme
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.ui.graphics.Color
36
import org.openedx.core.R
7+
import org.openedx.core.ui.theme.appColors
48

59
enum class DatesSection(val stringResId: Int) {
610
COMPLETED(R.string.core_date_type_completed),
@@ -9,5 +13,19 @@ enum class DatesSection(val stringResId: Int) {
913
THIS_WEEK(R.string.core_date_type_this_week),
1014
NEXT_WEEK(R.string.core_date_type_next_week),
1115
UPCOMING(R.string.core_date_type_upcoming),
12-
NONE(R.string.core_date_type_none)
16+
NONE(R.string.core_date_type_none);
17+
18+
val color: Color
19+
@Composable
20+
get() {
21+
return when (this) {
22+
COMPLETED -> MaterialTheme.appColors.cardViewBackground
23+
PAST_DUE -> MaterialTheme.appColors.datesSectionBarPastDue
24+
TODAY -> MaterialTheme.appColors.datesSectionBarToday
25+
THIS_WEEK -> MaterialTheme.appColors.datesSectionBarThisWeek
26+
NEXT_WEEK -> MaterialTheme.appColors.datesSectionBarNextWeek
27+
UPCOMING -> MaterialTheme.appColors.datesSectionBarUpcoming
28+
else -> MaterialTheme.appColors.background
29+
}
30+
}
1331
}
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
package org.openedx.core.presentation.dates
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.clickable
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.IntrinsicSize
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.Spacer
10+
import androidx.compose.foundation.layout.fillMaxHeight
11+
import androidx.compose.foundation.layout.fillMaxWidth
12+
import androidx.compose.foundation.layout.height
13+
import androidx.compose.foundation.layout.padding
14+
import androidx.compose.foundation.layout.size
15+
import androidx.compose.foundation.layout.width
16+
import androidx.compose.foundation.layout.wrapContentHeight
17+
import androidx.compose.material.Icon
18+
import androidx.compose.material.MaterialTheme
19+
import androidx.compose.material.Text
20+
import androidx.compose.material.icons.Icons
21+
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
22+
import androidx.compose.runtime.Composable
23+
import androidx.compose.ui.Alignment
24+
import androidx.compose.ui.Modifier
25+
import androidx.compose.ui.platform.LocalContext
26+
import androidx.compose.ui.res.painterResource
27+
import androidx.compose.ui.res.stringResource
28+
import androidx.compose.ui.text.style.TextOverflow
29+
import androidx.compose.ui.unit.dp
30+
import org.openedx.core.R
31+
import org.openedx.core.domain.model.CourseDate
32+
import org.openedx.core.domain.model.CourseDateBlock
33+
import org.openedx.core.domain.model.DatesSection
34+
import org.openedx.core.ui.theme.appColors
35+
import org.openedx.core.ui.theme.appTypography
36+
import org.openedx.core.utils.TimeUtils.formatToString
37+
import org.openedx.core.utils.clearTime
38+
39+
// --- Generic composables for reusability ---
40+
41+
@Composable
42+
private fun CourseDateBlockSectionGeneric(
43+
sectionKey: DatesSection = DatesSection.NONE,
44+
useRelativeDates: Boolean,
45+
content: @Composable () -> Unit
46+
) {
47+
Column(modifier = Modifier.padding(start = 8.dp)) {
48+
if (sectionKey != DatesSection.COMPLETED) {
49+
Text(
50+
modifier = Modifier
51+
.fillMaxWidth()
52+
.padding(top = 16.dp, bottom = 4.dp),
53+
text = stringResource(id = sectionKey.stringResId),
54+
color = MaterialTheme.appColors.textDark,
55+
style = MaterialTheme.appTypography.titleMedium,
56+
)
57+
}
58+
Row(
59+
modifier = Modifier
60+
.fillMaxWidth()
61+
.height(IntrinsicSize.Min) // ensures all cards share the height of the tallest one.
62+
) {
63+
if (sectionKey != DatesSection.COMPLETED) {
64+
DateBullet(section = sectionKey)
65+
}
66+
content()
67+
}
68+
}
69+
}
70+
71+
@Composable
72+
private fun DateBlockContainer(content: @Composable () -> Unit) {
73+
Column(
74+
modifier = Modifier
75+
.fillMaxWidth()
76+
.wrapContentHeight()
77+
.padding(start = 8.dp, end = 8.dp)
78+
) {
79+
content()
80+
}
81+
}
82+
83+
@Composable
84+
fun CourseDateBlockSection(
85+
sectionKey: DatesSection = DatesSection.NONE,
86+
useRelativeDates: Boolean,
87+
sectionDates: List<CourseDateBlock>,
88+
onItemClick: (CourseDateBlock) -> Unit,
89+
) {
90+
CourseDateBlockSectionGeneric(sectionKey = sectionKey, useRelativeDates = useRelativeDates) {
91+
DateBlock(
92+
dateBlocks = sectionDates,
93+
onItemClick = onItemClick,
94+
useRelativeDates = useRelativeDates
95+
)
96+
}
97+
}
98+
99+
@JvmName("CourseDateBlockSectionCourseDates")
100+
@Composable
101+
fun CourseDateBlockSection(
102+
sectionKey: DatesSection = DatesSection.NONE,
103+
useRelativeDates: Boolean,
104+
sectionDates: List<CourseDate>,
105+
onItemClick: (CourseDate) -> Unit,
106+
) {
107+
CourseDateBlockSectionGeneric(sectionKey = sectionKey, useRelativeDates = useRelativeDates) {
108+
DateBlock(
109+
dateBlocks = sectionDates,
110+
onItemClick = onItemClick,
111+
useRelativeDates = useRelativeDates
112+
)
113+
}
114+
}
115+
116+
@Composable
117+
private fun DateBullet(
118+
section: DatesSection = DatesSection.NONE,
119+
) {
120+
Box(
121+
modifier = Modifier
122+
.width(8.dp)
123+
.fillMaxHeight()
124+
.padding(top = 2.dp, bottom = 2.dp)
125+
.background(
126+
color = section.color,
127+
shape = MaterialTheme.shapes.medium
128+
)
129+
)
130+
}
131+
132+
@Composable
133+
private fun DateBlock(
134+
dateBlocks: List<CourseDateBlock>,
135+
useRelativeDates: Boolean,
136+
onItemClick: (CourseDateBlock) -> Unit,
137+
) {
138+
DateBlockContainer {
139+
var lastAssignmentDate = dateBlocks.first().date.clearTime()
140+
dateBlocks.forEachIndexed { index, dateBlock ->
141+
val canShowDate = if (index == 0) true else (lastAssignmentDate != dateBlock.date)
142+
CourseDateItem(dateBlock, canShowDate, index != 0, useRelativeDates, onItemClick)
143+
lastAssignmentDate = dateBlock.date
144+
}
145+
}
146+
}
147+
148+
@JvmName("DateBlockCourseDate")
149+
@Composable
150+
private fun DateBlock(
151+
dateBlocks: List<CourseDate>,
152+
useRelativeDates: Boolean,
153+
onItemClick: (CourseDate) -> Unit,
154+
) {
155+
DateBlockContainer {
156+
dateBlocks.forEachIndexed { index, dateBlock ->
157+
CourseDateItem(dateBlock, index != 0, useRelativeDates, onItemClick)
158+
}
159+
}
160+
}
161+
162+
@Composable
163+
private fun CourseDateItem(
164+
dateBlock: CourseDateBlock,
165+
canShowDate: Boolean,
166+
isMiddleChild: Boolean,
167+
useRelativeDates: Boolean,
168+
onItemClick: (CourseDateBlock) -> Unit,
169+
) {
170+
val context = LocalContext.current
171+
Column(
172+
modifier = Modifier
173+
.wrapContentHeight()
174+
.fillMaxWidth()
175+
) {
176+
if (isMiddleChild) {
177+
Spacer(modifier = Modifier.height(20.dp))
178+
}
179+
if (canShowDate) {
180+
val timeTitle = formatToString(context, dateBlock.date, useRelativeDates)
181+
Text(
182+
text = timeTitle,
183+
style = MaterialTheme.appTypography.labelMedium,
184+
color = MaterialTheme.appColors.textDark,
185+
maxLines = 1,
186+
)
187+
}
188+
Row(
189+
modifier = Modifier
190+
.fillMaxWidth()
191+
.padding(end = 4.dp)
192+
.clickable(
193+
enabled = dateBlock.blockId.isNotEmpty() && dateBlock.learnerHasAccess,
194+
onClick = { onItemClick(dateBlock) }
195+
)
196+
) {
197+
dateBlock.dateType.drawableResId?.let { icon ->
198+
Icon(
199+
modifier = Modifier
200+
.padding(end = 4.dp)
201+
.align(Alignment.CenterVertically),
202+
painter = painterResource(
203+
id = if (!dateBlock.learnerHasAccess) {
204+
R.drawable.core_ic_lock
205+
} else {
206+
icon
207+
}
208+
),
209+
contentDescription = null,
210+
tint = MaterialTheme.appColors.textDark
211+
)
212+
}
213+
Text(
214+
modifier = Modifier
215+
.weight(1f)
216+
.align(Alignment.CenterVertically),
217+
text = if (!dateBlock.assignmentType.isNullOrEmpty()) {
218+
"${dateBlock.assignmentType}: ${dateBlock.title}"
219+
} else {
220+
dateBlock.title
221+
},
222+
style = MaterialTheme.appTypography.titleMedium,
223+
color = MaterialTheme.appColors.textDark,
224+
maxLines = 1,
225+
overflow = TextOverflow.Ellipsis,
226+
)
227+
Spacer(modifier = Modifier.width(7.dp))
228+
if (dateBlock.blockId.isNotEmpty() && dateBlock.learnerHasAccess) {
229+
Icon(
230+
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
231+
tint = MaterialTheme.appColors.textDark,
232+
contentDescription = "Open Block Arrow",
233+
modifier = Modifier
234+
.size(24.dp)
235+
.align(Alignment.CenterVertically)
236+
)
237+
}
238+
}
239+
if (dateBlock.description.isNotEmpty()) {
240+
Text(
241+
modifier = Modifier
242+
.fillMaxWidth()
243+
.padding(top = 4.dp),
244+
text = dateBlock.description,
245+
style = MaterialTheme.appTypography.labelMedium,
246+
)
247+
}
248+
}
249+
}
250+
251+
@Composable
252+
private fun CourseDateItem(
253+
dateBlock: CourseDate,
254+
isMiddleChild: Boolean,
255+
useRelativeDates: Boolean,
256+
onItemClick: (CourseDate) -> Unit,
257+
) {
258+
val context = LocalContext.current
259+
Column(
260+
modifier = Modifier
261+
.wrapContentHeight()
262+
.fillMaxWidth()
263+
) {
264+
if (isMiddleChild) {
265+
Spacer(modifier = Modifier.height(20.dp))
266+
}
267+
val timeTitle = formatToString(context, dateBlock.dueDate, useRelativeDates)
268+
Text(
269+
text = timeTitle,
270+
style = MaterialTheme.appTypography.labelMedium,
271+
color = MaterialTheme.appColors.textDark,
272+
maxLines = 1,
273+
)
274+
Row(
275+
modifier = Modifier
276+
.fillMaxWidth()
277+
.padding(end = 4.dp)
278+
.clickable(
279+
enabled = dateBlock.assignmentBlockId.isNotEmpty() && dateBlock.learnerHasAccess,
280+
onClick = { onItemClick(dateBlock) }
281+
)
282+
) {
283+
Icon(
284+
modifier = Modifier
285+
.padding(end = 4.dp)
286+
.align(Alignment.CenterVertically),
287+
painter = painterResource(R.drawable.core_ic_assignment),
288+
contentDescription = null,
289+
tint = MaterialTheme.appColors.textDark
290+
)
291+
Text(
292+
modifier = Modifier
293+
.weight(1f)
294+
.align(Alignment.CenterVertically),
295+
text = dateBlock.assignmentTitle,
296+
style = MaterialTheme.appTypography.titleMedium,
297+
color = MaterialTheme.appColors.textDark,
298+
maxLines = 1,
299+
overflow = TextOverflow.Ellipsis,
300+
)
301+
Spacer(modifier = Modifier.width(7.dp))
302+
if (dateBlock.assignmentBlockId.isNotEmpty() && dateBlock.learnerHasAccess) {
303+
Icon(
304+
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
305+
tint = MaterialTheme.appColors.textDark,
306+
contentDescription = "Open Block Arrow",
307+
modifier = Modifier
308+
.size(24.dp)
309+
.align(Alignment.CenterVertically)
310+
)
311+
}
312+
}
313+
Text(
314+
modifier = Modifier
315+
.fillMaxWidth()
316+
.padding(top = 4.dp),
317+
text = dateBlock.courseName,
318+
style = MaterialTheme.appTypography.labelMedium,
319+
)
320+
}
321+
}

0 commit comments

Comments
 (0)