Skip to content

Commit d9592a8

Browse files
feat: pagination
1 parent 9352d41 commit d9592a8

File tree

7 files changed

+79
-57
lines changed

7 files changed

+79
-57
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import org.openedx.core.domain.model.CourseDatesResponse as DomainCourseDatesRes
88
data class CourseDate(
99
@SerializedName("course_id")
1010
val courseId: String,
11-
@SerializedName("assignment_block_id")
12-
val assignmentBlockId: String,
11+
@SerializedName("first_component_block_id")
12+
val firstComponentBlockId: String?,
1313
@SerializedName("due_date")
1414
val dueDate: String?,
1515
@SerializedName("assignment_title")
@@ -25,7 +25,7 @@ data class CourseDate(
2525
val dueDate = TimeUtils.iso8601ToDate(dueDate ?: "")
2626
return DomainCourseDate(
2727
courseId = courseId,
28-
assignmentBlockId = assignmentBlockId,
28+
firstComponentBlockId = firstComponentBlockId ?: "",
2929
dueDate = dueDate ?: return null,
3030
assignmentTitle = assignmentTitle ?: "",
3131
learnerHasAccess = learnerHasAccess ?: false,
@@ -39,9 +39,9 @@ data class CourseDatesResponse(
3939
@SerializedName("count")
4040
val count: Int,
4141
@SerializedName("next")
42-
val next: Int?,
42+
val next: String?,
4343
@SerializedName("previous")
44-
val previous: Int?,
44+
val previous: String?,
4545
@SerializedName("results")
4646
val results: List<CourseDate>
4747
) {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import java.util.Date
44

55
data class CourseDatesResponse(
66
val count: Int,
7-
val next: Int?,
8-
val previous: Int?,
7+
val next: String?,
8+
val previous: String?,
99
val results: List<CourseDate>
1010
)
1111

1212
data class CourseDate(
1313
val courseId: String,
14-
val assignmentBlockId: String,
14+
val firstComponentBlockId: String,
1515
val dueDate: Date,
1616
val assignmentTitle: String,
1717
val learnerHasAccess: Boolean,

core/src/main/java/org/openedx/core/presentation/dates/DatesUI.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ private fun CourseDateItem(
273273
.fillMaxWidth()
274274
.padding(end = 4.dp)
275275
.clickable(
276-
enabled = dateBlock.assignmentBlockId.isNotEmpty() && dateBlock.learnerHasAccess,
276+
enabled = dateBlock.firstComponentBlockId.isNotEmpty() && dateBlock.learnerHasAccess,
277277
onClick = { onItemClick(dateBlock) }
278278
)
279279
) {
@@ -296,7 +296,7 @@ private fun CourseDateItem(
296296
overflow = TextOverflow.Ellipsis,
297297
)
298298
Spacer(modifier = Modifier.width(7.dp))
299-
if (dateBlock.assignmentBlockId.isNotEmpty() && dateBlock.learnerHasAccess) {
299+
if (dateBlock.firstComponentBlockId.isNotEmpty() && dateBlock.learnerHasAccess) {
300300
Icon(
301301
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
302302
tint = MaterialTheme.appColors.textDark,

dates/src/main/java/org/openedx/dates/data/storage/CourseDateEntity.kt

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,30 @@ import org.openedx.core.domain.model.CourseDate as DomainCourseDate
99

1010
@Entity(tableName = "course_date_table")
1111
data class CourseDateEntity(
12-
@PrimaryKey
13-
@ColumnInfo("assignmentBlockId")
14-
val assignmentBlockId: String,
15-
@ColumnInfo("courseId")
12+
@PrimaryKey(autoGenerate = true)
13+
@ColumnInfo("course_date_id")
14+
val id: Int,
15+
@ColumnInfo("course_date_first_component_block_id")
16+
val firstComponentBlockId: String?,
17+
@ColumnInfo("course_date_courseId")
1618
val courseId: String,
17-
@ColumnInfo("dueDate")
19+
@ColumnInfo("course_date_dueDate")
1820
val dueDate: String?,
19-
@ColumnInfo("assignmentTitle")
21+
@ColumnInfo("course_date_assignmentTitle")
2022
val assignmentTitle: String?,
21-
@ColumnInfo("learnerHasAccess")
23+
@ColumnInfo("course_date_learnerHasAccess")
2224
val learnerHasAccess: Boolean?,
23-
@ColumnInfo("relative")
25+
@ColumnInfo("course_date_relative")
2426
val relative: Boolean?,
25-
@ColumnInfo("courseName")
27+
@ColumnInfo("course_date_courseName")
2628
val courseName: String?,
2729
) {
2830

2931
fun mapToDomain(): DomainCourseDate? {
3032
val dueDate = TimeUtils.iso8601ToDate(dueDate ?: "")
3133
return DomainCourseDate(
3234
courseId = courseId,
33-
assignmentBlockId = assignmentBlockId,
35+
firstComponentBlockId = firstComponentBlockId ?: "",
3436
dueDate = dueDate ?: return null,
3537
assignmentTitle = assignmentTitle ?: "",
3638
learnerHasAccess = learnerHasAccess ?: false,
@@ -43,8 +45,9 @@ data class CourseDateEntity(
4345
fun createFrom(courseDate: CourseDate): CourseDateEntity {
4446
with(courseDate) {
4547
return CourseDateEntity(
48+
id = 0,
4649
courseId = courseId,
47-
assignmentBlockId = assignmentBlockId,
50+
firstComponentBlockId = firstComponentBlockId,
4851
dueDate = dueDate,
4952
assignmentTitle = assignmentTitle,
5053
learnerHasAccess = learnerHasAccess,

dates/src/main/java/org/openedx/dates/presentation/dates/DatesFragment.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ import androidx.compose.material.pullrefresh.rememberPullRefreshState
3131
import androidx.compose.material.rememberScaffoldState
3232
import androidx.compose.runtime.Composable
3333
import androidx.compose.runtime.collectAsState
34+
import androidx.compose.runtime.derivedStateOf
3435
import androidx.compose.runtime.getValue
35-
import androidx.compose.runtime.mutableIntStateOf
3636
import androidx.compose.runtime.mutableStateOf
3737
import androidx.compose.runtime.remember
3838
import androidx.compose.runtime.saveable.rememberSaveable
@@ -57,7 +57,6 @@ import org.openedx.core.ui.MainScreenTitle
5757
import org.openedx.core.ui.OfflineModeDialog
5858
import org.openedx.core.ui.OpenEdXButton
5959
import org.openedx.core.ui.displayCutoutForLandscape
60-
import org.openedx.core.ui.shouldLoadMore
6160
import org.openedx.core.ui.statusBarsInset
6261
import org.openedx.core.ui.theme.OpenEdXTheme
6362
import org.openedx.core.ui.theme.appColors
@@ -126,7 +125,7 @@ class DatesFragment : Fragment() {
126125
}
127126

128127
companion object {
129-
const val LOAD_MORE_THRESHOLD = 4
128+
const val LOAD_MORE_THRESHOLD = 0.8f
130129
}
131130
}
132131

@@ -157,9 +156,7 @@ private fun DatesScreen(
157156
mutableStateOf(false)
158157
}
159158
val scrollState = rememberLazyListState()
160-
val firstVisibleIndex = remember {
161-
mutableIntStateOf(scrollState.firstVisibleItemIndex)
162-
}
159+
val layoutInfo by remember { derivedStateOf { scrollState.layoutInfo } }
163160

164161
Scaffold(
165162
scaffoldState = scaffoldState,
@@ -249,7 +246,10 @@ private fun DatesScreen(
249246
}
250247
}
251248
}
252-
if (scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD)) {
249+
val lastVisibleItemIndex =
250+
layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
251+
val totalItemsCount = layoutInfo.totalItemsCount
252+
if (totalItemsCount > 0 && lastVisibleItemIndex >= (totalItemsCount * LOAD_MORE_THRESHOLD).toInt()) {
253253
onAction(DatesViewActions.LoadMore)
254254
}
255255
}

dates/src/main/java/org/openedx/dates/presentation/dates/DatesViewModel.kt

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,15 @@ class DatesViewModel(
6767
isRefreshing = refresh,
6868
)
6969
}
70-
if (refresh) page = 1
71-
val response = if (networkConnection.isOnline() || page > 1) {
70+
val response = if (refresh) {
71+
page = 1
7272
datesInteractor.getUserDates(page)
7373
} else {
74-
null
74+
if (networkConnection.isOnline() || page > 1) {
75+
datesInteractor.getUserDates(page)
76+
} else {
77+
null
78+
}
7579
}
7680
if (response != null) {
7781
if (response.next.isNotNull() && page != response.count) {
@@ -82,9 +86,14 @@ class DatesViewModel(
8286
page = -1
8387
}
8488
_uiState.update { state ->
85-
state.copy(
86-
dates = state.dates + groupCourseDates(response.results)
87-
)
89+
if (refresh) {
90+
state.copy(
91+
dates = groupCourseDates(response.results)
92+
)
93+
} else {
94+
val newDates = groupCourseDates(response.results)
95+
state.copy(dates = mergeDates(state.dates, newDates))
96+
}
8897
}
8998
} else {
9099
val cachedList = datesInteractor.getUserDatesFromCache()
@@ -130,6 +139,7 @@ class DatesViewModel(
130139
val courseIds = pastDueDates
131140
.filter { it.relative }
132141
.map { it.courseId }
142+
.distinct()
133143
datesInteractor.shiftDueDate(courseIds)
134144
refreshData()
135145
} catch (e: Exception) {
@@ -176,34 +186,43 @@ class DatesViewModel(
176186
courseId = courseDate.courseId,
177187
courseTitle = courseDate.courseName,
178188
openTab = "",
179-
resumeBlockId = courseDate.assignmentBlockId
189+
resumeBlockId = courseDate.firstComponentBlockId
180190
)
181191
}
182192

183193
private fun groupCourseDates(dates: List<CourseDate>): Map<DatesSection, List<CourseDate>> {
184194
val now = Date()
185195
val calNow = Calendar.getInstance().apply { time = now }
186-
val grouped = dates.groupBy { courseDate ->
187-
val dueDate = courseDate.dueDate
188-
if (dueDate.before(now)) {
189-
DatesSection.PAST_DUE
190-
} else if (dueDate.isToday()) {
191-
DatesSection.TODAY
192-
} else {
193-
val calDue = dueDate.toCalendar()
194-
val weekNow = calNow.get(Calendar.WEEK_OF_YEAR)
195-
val weekDue = calDue.get(Calendar.WEEK_OF_YEAR)
196-
val yearNow = calNow.get(Calendar.YEAR)
197-
val yearDue = calDue.get(Calendar.YEAR)
198-
if (weekNow == weekDue && yearNow == yearDue) {
199-
DatesSection.THIS_WEEK
200-
} else {
201-
DatesSection.UPCOMING
196+
return dates.groupBy { courseDate ->
197+
when {
198+
courseDate.dueDate.before(now) -> DatesSection.PAST_DUE
199+
courseDate.dueDate.isToday() -> DatesSection.TODAY
200+
else -> {
201+
val calDue = courseDate.dueDate.toCalendar()
202+
val weekNow = calNow.get(Calendar.WEEK_OF_YEAR)
203+
val weekDue = calDue.get(Calendar.WEEK_OF_YEAR)
204+
val yearNow = calNow.get(Calendar.YEAR)
205+
val yearDue = calDue.get(Calendar.YEAR)
206+
if (weekNow == weekDue && yearNow == yearDue) {
207+
DatesSection.THIS_WEEK
208+
} else {
209+
DatesSection.UPCOMING
210+
}
202211
}
203212
}
204213
}
214+
}
205215

206-
return grouped
216+
private fun mergeDates(
217+
oldDates: Map<DatesSection, List<CourseDate>>,
218+
newDates: Map<DatesSection, List<CourseDate>>
219+
): Map<DatesSection, List<CourseDate>> {
220+
val merged = oldDates.toMutableMap()
221+
newDates.forEach { (section, newList) ->
222+
val existingList = merged[section] ?: emptyList()
223+
merged[section] = existingList + newList
224+
}
225+
return merged
207226
}
208227

209228
private fun logEvent(

dates/src/test/java/org/openedx/dates/DatesViewModelTest.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ class DatesViewModelTest {
7979
val courseDate: CourseDate = mockk(relaxed = true)
8080
val courseDatesResponse = CourseDatesResponse(
8181
count = 10,
82-
next = 2,
83-
previous = 1,
82+
next = "",
83+
previous = "",
8484
results = listOf(courseDate)
8585
)
8686
coEvery { datesInteractor.getUserDates(1) } returns courseDatesResponse
@@ -283,7 +283,7 @@ class DatesViewModelTest {
283283
val courseDate: CourseDate = mockk(relaxed = true) {
284284
every { courseId } returns "course-123"
285285
every { courseName } returns "Test Course"
286-
every { assignmentBlockId } returns "block-1"
286+
every { firstComponentBlockId } returns "block-1"
287287
}
288288

289289
viewModel.navigateToCourseOutline(fragmentManager, courseDate)
@@ -304,8 +304,8 @@ class DatesViewModelTest {
304304
val courseDate: CourseDate = mockk(relaxed = true)
305305
val courseDatesResponse = CourseDatesResponse(
306306
count = 10,
307-
next = 2,
308-
previous = 1,
307+
next = "",
308+
previous = "",
309309
results = listOf(courseDate)
310310
)
311311

0 commit comments

Comments
 (0)