Skip to content

Commit fdb1e6e

Browse files
feat: paging and caching
1 parent 87b3517 commit fdb1e6e

File tree

11 files changed

+194
-21
lines changed

11 files changed

+194
-21
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ val appModule = module {
175175
room.calendarDao()
176176
}
177177

178+
single {
179+
val room = get<AppDatabase>()
180+
room.datesDao()
181+
}
182+
178183
single {
179184
FileDownloader()
180185
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,8 @@ val screenModule = module {
498498
factory {
499499
DatesRepository(
500500
api = get(),
501-
preferencesManager = get()
501+
dao = get(),
502+
preferencesManager = get(),
502503
)
503504
}
504505
factory {

app/src/main/java/org/openedx/app/room/AppDatabase.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import org.openedx.core.module.db.DownloadModelEntity
1414
import org.openedx.course.data.storage.CourseConverter
1515
import org.openedx.course.data.storage.CourseDao
1616
import org.openedx.dashboard.data.DashboardDao
17+
import org.openedx.dates.data.storage.CourseDateEntity
18+
import org.openedx.dates.data.storage.DatesDao
1719
import org.openedx.discovery.data.converter.DiscoveryConverter
1820
import org.openedx.discovery.data.model.room.CourseEntity
1921
import org.openedx.discovery.data.storage.DiscoveryDao
@@ -29,7 +31,8 @@ const val DATABASE_NAME = "OpenEdX_db"
2931
DownloadModelEntity::class,
3032
OfflineXBlockProgress::class,
3133
CourseCalendarEventEntity::class,
32-
CourseCalendarStateEntity::class
34+
CourseCalendarStateEntity::class,
35+
CourseDateEntity::class,
3336
],
3437
version = DATABASE_VERSION,
3538
exportSchema = false
@@ -40,5 +43,6 @@ abstract class AppDatabase : RoomDatabase() {
4043
abstract fun courseDao(): CourseDao
4144
abstract fun dashboardDao(): DashboardDao
4245
abstract fun downloadDao(): DownloadDao
46+
abstract fun datesDao(): DatesDao
4347
abstract fun calendarDao(): CalendarDao
4448
}

core/src/main/java/org/openedx/core/data/api/CourseApi.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,6 @@ interface CourseApi {
108108
@GET("/api/mobile/v1/course_dates/{username}/")
109109
suspend fun getUserDates(
110110
@Path("username") username: String,
111-
@Query("page") page: Int = 1
111+
@Query("page") page: Int
112112
): CourseDatesResponse
113113
}

dates/src/main/java/org/openedx/dates/data/repository/DatesRepository.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,24 @@ package org.openedx.dates.data.repository
22

33
import org.openedx.core.data.api.CourseApi
44
import org.openedx.core.data.storage.CorePreferences
5+
import org.openedx.core.domain.model.CourseDate
56
import org.openedx.core.domain.model.CourseDatesResponse
7+
import org.openedx.dates.data.storage.CourseDateEntity
8+
import org.openedx.dates.data.storage.DatesDao
69

710
class DatesRepository(
811
private val api: CourseApi,
12+
private val dao: DatesDao,
913
private val preferencesManager: CorePreferences
1014
) {
11-
suspend fun getUserDates(): CourseDatesResponse {
15+
suspend fun getUserDates(page: Int): CourseDatesResponse {
1216
val username = preferencesManager.user?.username ?: ""
13-
return api.getUserDates(username).mapToDomain()
17+
val response = api.getUserDates(username, page)
18+
dao.insertCourseDateEntities(response.results.map { CourseDateEntity.createFrom(it) })
19+
return response.mapToDomain()
20+
}
21+
22+
suspend fun getUserDatesFromCache(): List<CourseDate> {
23+
return dao.getCourseDateEntities().mapNotNull { it.mapToDomain() }
1424
}
1525
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package org.openedx.dates.data.storage
2+
3+
import androidx.room.ColumnInfo
4+
import androidx.room.Entity
5+
import androidx.room.PrimaryKey
6+
import org.openedx.core.data.model.CourseDate
7+
import org.openedx.core.utils.TimeUtils
8+
import org.openedx.core.domain.model.CourseDate as DomainCourseDate
9+
10+
@Entity(tableName = "course_date_table")
11+
data class CourseDateEntity(
12+
@PrimaryKey
13+
@ColumnInfo("assignmentBlockId")
14+
val assignmentBlockId: String,
15+
@ColumnInfo("courseId")
16+
val courseId: String,
17+
@ColumnInfo("dueDate")
18+
val dueDate: String?,
19+
@ColumnInfo("assignmentTitle")
20+
val assignmentTitle: String?,
21+
@ColumnInfo("learnerHasAccess")
22+
val learnerHasAccess: Boolean?,
23+
@ColumnInfo("courseName")
24+
val courseName: String?,
25+
) {
26+
27+
fun mapToDomain(): DomainCourseDate? {
28+
val dueDate = TimeUtils.iso8601ToDate(dueDate ?: "")
29+
return DomainCourseDate(
30+
courseId = courseId,
31+
assignmentBlockId = assignmentBlockId,
32+
dueDate = dueDate ?: return null,
33+
assignmentTitle = assignmentTitle ?: "",
34+
learnerHasAccess = learnerHasAccess ?: false,
35+
courseName = courseName ?: ""
36+
)
37+
}
38+
39+
companion object {
40+
fun createFrom(courseDate: CourseDate): CourseDateEntity {
41+
with(courseDate) {
42+
return CourseDateEntity(
43+
courseId = courseId,
44+
assignmentBlockId = assignmentBlockId,
45+
dueDate = dueDate,
46+
assignmentTitle = assignmentTitle,
47+
learnerHasAccess = learnerHasAccess,
48+
courseName = courseName
49+
)
50+
}
51+
}
52+
}
53+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.openedx.dates.data.storage
2+
3+
import androidx.room.Dao
4+
import androidx.room.Insert
5+
import androidx.room.OnConflictStrategy
6+
import androidx.room.Query
7+
8+
@Dao
9+
interface DatesDao {
10+
11+
@Query("SELECT * FROM course_date_table")
12+
suspend fun getCourseDateEntities(): List<CourseDateEntity>
13+
14+
@Insert(onConflict = OnConflictStrategy.REPLACE)
15+
suspend fun insertCourseDateEntities(courseDate: List<CourseDateEntity>)
16+
17+
@Query("DELETE FROM course_date_table")
18+
suspend fun clearCachedData()
19+
}

dates/src/main/java/org/openedx/dates/domain/interactor/DatesInteractor.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ class DatesInteractor(
66
private val repository: DatesRepository
77
) {
88

9-
suspend fun getUserDates() = repository.getUserDates()
9+
suspend fun getUserDates(page: Int) = repository.getUserDates(page)
10+
11+
suspend fun getUserDatesFromCache() = repository.getUserDatesFromCache()
1012

1113
}

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

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.width
2222
import androidx.compose.foundation.layout.widthIn
2323
import androidx.compose.foundation.lazy.LazyColumn
2424
import androidx.compose.foundation.lazy.itemsIndexed
25+
import androidx.compose.foundation.lazy.rememberLazyListState
2526
import androidx.compose.foundation.rememberScrollState
2627
import androidx.compose.foundation.shape.RoundedCornerShape
2728
import androidx.compose.foundation.verticalScroll
@@ -40,6 +41,7 @@ import androidx.compose.material.rememberScaffoldState
4041
import androidx.compose.runtime.Composable
4142
import androidx.compose.runtime.collectAsState
4243
import androidx.compose.runtime.getValue
44+
import androidx.compose.runtime.mutableIntStateOf
4345
import androidx.compose.runtime.mutableStateOf
4446
import androidx.compose.runtime.remember
4547
import androidx.compose.runtime.saveable.rememberSaveable
@@ -66,12 +68,14 @@ import org.openedx.core.ui.HandleUIMessage
6668
import org.openedx.core.ui.MainScreenTitle
6769
import org.openedx.core.ui.OfflineModeDialog
6870
import org.openedx.core.ui.displayCutoutForLandscape
71+
import org.openedx.core.ui.shouldLoadMore
6972
import org.openedx.core.ui.statusBarsInset
7073
import org.openedx.core.ui.theme.OpenEdXTheme
7174
import org.openedx.core.ui.theme.appColors
7275
import org.openedx.core.ui.theme.appTypography
7376
import org.openedx.core.utils.TimeUtils
7477
import org.openedx.dates.R
78+
import org.openedx.dates.presentation.dates.DatesFragment.Companion.LOAD_MORE_THRESHOLD
7579
import org.openedx.foundation.presentation.UIMessage
7680
import org.openedx.foundation.presentation.rememberWindowSize
7781
import org.openedx.foundation.presentation.windowSizeValue
@@ -109,8 +113,15 @@ class DatesFragment : Fragment() {
109113
viewModel.refreshData()
110114
}
111115

112-
is DatesViewActions.OpenEvent -> {
116+
DatesViewActions.LoadMore -> {
117+
viewModel.fetchMore()
118+
}
113119

120+
is DatesViewActions.OpenEvent -> {
121+
viewModel.navigateToCourseOutline(
122+
requireActivity().supportFragmentManager,
123+
action.date
124+
)
114125
}
115126
}
116127
}
@@ -119,6 +130,10 @@ class DatesFragment : Fragment() {
119130
}
120131
}
121132

133+
companion object {
134+
const val LOAD_MORE_THRESHOLD = 4
135+
}
136+
122137
}
123138

124139
@OptIn(ExperimentalMaterialApi::class)
@@ -146,6 +161,10 @@ private fun DatesScreen(
146161
var isInternetConnectionShown by rememberSaveable {
147162
mutableStateOf(false)
148163
}
164+
val scrollState = rememberLazyListState()
165+
val firstVisibleIndex = remember {
166+
mutableIntStateOf(scrollState.firstVisibleItemIndex)
167+
}
149168

150169
Scaffold(
151170
scaffoldState = scaffoldState,
@@ -169,7 +188,7 @@ private fun DatesScreen(
169188
.fillMaxSize()
170189
.pullRefresh(pullRefreshState)
171190
) {
172-
if (uiState.isLoading) {
191+
if (uiState.isLoading && uiState.dates.isEmpty()) {
173192
Box(
174193
modifier = Modifier
175194
.fillMaxSize(),
@@ -189,7 +208,8 @@ private fun DatesScreen(
189208
contentAlignment = Alignment.TopCenter
190209
) {
191210
LazyColumn(
192-
modifier = contentWidth,
211+
modifier = contentWidth.fillMaxSize(),
212+
state = scrollState,
193213
contentPadding = PaddingValues(bottom = 20.dp)
194214
) {
195215
uiState.dates.keys.forEach { dueDateCategory ->
@@ -213,12 +233,24 @@ private fun DatesScreen(
213233
lineColor = dueDateCategory.color,
214234
itemPosition = itemPosition,
215235
onClick = {
216-
onAction(DatesViewActions.OpenEvent())
236+
onAction(DatesViewActions.OpenEvent(date))
217237
}
218238
)
219239
}
220240
}
221241
}
242+
if (uiState.isLoading) {
243+
item {
244+
CircularProgressIndicator(
245+
modifier = Modifier
246+
.align(Alignment.Center),
247+
color = MaterialTheme.appColors.primary
248+
)
249+
}
250+
}
251+
}
252+
if (scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD)) {
253+
onAction(DatesViewActions.LoadMore)
222254
}
223255
}
224256
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ import org.openedx.core.domain.model.CourseDate
55
data class DatesUIState(
66
val isLoading: Boolean = true,
77
val isRefreshing: Boolean = false,
8+
val canLoadMore: Boolean = false,
89
val dates: Map<DueDateCategory, List<CourseDate>> = emptyMap()
910
)

0 commit comments

Comments
 (0)