Skip to content

Commit 34b96f5

Browse files
authored
Merge pull request #25 from DevelopersBreach/shreyas/topic-screen-paging
Integrate paging for `Topic Screen`
2 parents 04c029e + 24f240e commit 34b96f5

File tree

16 files changed

+156
-121
lines changed

16 files changed

+156
-121
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Access the latest APK for Kotlin Dictionary from the link below.
4242
- [ ] Add a `Contributors Page` to showcase project contributors
4343
- [ ] Add a `Settings Page` with basic preferences
4444
- [ ] Implement a `Splash Screen`
45+
- [x] Integrate multiplatform paging for `Topic Screen`
4546

4647
---
4748

composeApp/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ kotlin {
117117
implementation(libs.arrow.core)
118118
implementation(libs.arrow.fx.coroutines)
119119
implementation(project(":design-system"))
120+
implementation(libs.cashapp.paging.common)
121+
implementation(libs.cashapp.paging.compose.common)
120122
}
121123
desktopMain.dependencies {
122124
implementation(compose.desktop.currentOs)

composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/PreviewUtils.kt

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.developersbreach.kotlindictionarymultiplatform.previews
22

3+
import app.cash.paging.PagingData
34
import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.CodeExample
45
import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.KotlinTopicDetails
56
import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.Section
67
import com.developersbreach.kotlindictionarymultiplatform.data.detail.model.Syntax
7-
import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.Topic
8-
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.ItemTopic
8+
import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.TopicResponse
9+
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.Topic
10+
import kotlinx.coroutines.flow.Flow
11+
import kotlinx.coroutines.flow.flowOf
912

1013
internal fun sampleCodeSnippet(): String {
1114
return """
@@ -51,37 +54,41 @@ internal fun fakeTopicDetails(): KotlinTopicDetails {
5154
)
5255
}
5356

54-
private fun sampleTopicList(): List<Topic> {
57+
private fun sampleTopicList(): List<TopicResponse> {
5558
return listOf(
56-
Topic(
59+
TopicResponse(
5760
name = "Smart Casts",
5861
description = "Automatic casting by the compiler after type checks.",
5962
),
60-
Topic(
63+
TopicResponse(
6164
name = "Null Safety",
6265
description = "Kotlin's system to eliminate null pointer exceptions at compile time.",
6366
),
64-
Topic(
67+
TopicResponse(
6568
name = "Coroutines",
6669
description = "Lightweight threads for asynchronous and non-blocking programming.",
6770
),
68-
Topic(
71+
TopicResponse(
6972
name = "Lambdas",
7073
description = "Anonymous functions used to pass behavior as data.",
7174
),
72-
Topic(
75+
TopicResponse(
7376
name = "Sealed Classes",
7477
description = "Classes used to represent restricted class hierarchies for type safety.",
7578
),
7679
)
7780
}
7881

79-
internal fun sampleTopicUiList(): List<ItemTopic> {
82+
internal fun sampleTopicUiList(): List<Topic> {
8083
return sampleTopicList().map { topic ->
81-
ItemTopic(
84+
Topic(
8285
name = topic.name ?: "",
8386
initial = topic.name?.firstOrNull()?.uppercase() ?: "",
8487
description = topic.description ?: "",
8588
)
8689
}
90+
}
91+
92+
internal fun samplePagingData(): Flow<PagingData<Topic>> {
93+
return flowOf(PagingData.from(sampleTopicUiList()))
8794
}

composeApp/src/androidMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/previews/topic/TopicScreenPreview.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ package com.developersbreach.kotlindictionarymultiplatform.previews.topic
22

33
import androidx.compose.runtime.Composable
44
import androidx.compose.ui.tooling.preview.PreviewLightDark
5-
import com.developersbreach.kotlindictionarymultiplatform.previews.sampleTopicUiList
5+
import app.cash.paging.compose.collectAsLazyPagingItems
6+
import com.developersbreach.kotlindictionarymultiplatform.previews.samplePagingData
67
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.TopicScreenUI
78
import com.developersbreach.kotlindictionarymultiplatform.ui.theme.KotlinDictionaryTheme
89

910
@PreviewLightDark
1011
@Composable
1112
private fun TopicScreenPreview() {
1213
KotlinDictionaryTheme {
14+
val pagingItems = samplePagingData().collectAsLazyPagingItems()
1315
TopicScreenUI(
14-
topics = sampleTopicUiList(),
16+
topics = pagingItems,
1517
searchQuery = "Search",
1618
onQueryChange = { },
1719
onTopicClick = { },

composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/data/topic/model/Topic.kt

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
44
import kotlinx.serialization.Serializable
55

66
@Serializable
7-
data class TopicResponse(
7+
data class TopicsResponse(
88
@SerialName("documents") val topics: List<RawTopic>,
99
)
1010

@@ -24,8 +24,14 @@ data class RawField(
2424
@SerialName("stringValue") val value: String,
2525
)
2626

27-
fun RawTopic.toTopic(): Topic {
28-
return Topic(
27+
@Serializable
28+
data class TopicResponse(
29+
val name: String?,
30+
val description: String?,
31+
)
32+
33+
fun RawTopic.toTopic(): TopicResponse {
34+
return TopicResponse(
2935
name = fields.name.value,
3036
description = fields.description.value,
3137
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.developersbreach.kotlindictionarymultiplatform.data.topic.repository
2+
3+
import app.cash.paging.PagingSource
4+
import app.cash.paging.PagingState
5+
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.Topic
6+
7+
class TopicPagingSource(
8+
private val repository: TopicRepository,
9+
private val query: String,
10+
) : PagingSource<Int, Topic>() {
11+
12+
override suspend fun load(
13+
params: LoadParams<Int>,
14+
): LoadResult<Int, Topic> {
15+
val page = params.key ?: 1
16+
val pageSize = params.loadSize
17+
return try {
18+
val pageItems = repository.getTopicsPage(page, pageSize, query)
19+
LoadResult.Page(
20+
data = pageItems,
21+
prevKey = if (page == 1) null else page - 1,
22+
nextKey = if (pageItems.isEmpty()) null else page + 1,
23+
)
24+
} catch (e: Exception) {
25+
LoadResult.Error(e)
26+
}
27+
}
28+
29+
override fun getRefreshKey(
30+
state: PagingState<Int, Topic>,
31+
): Int? {
32+
return state.anchorPosition?.let { anchorPosition ->
33+
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
34+
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
35+
}
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,44 @@
11
package com.developersbreach.kotlindictionarymultiplatform.data.topic.repository
22

33
import arrow.core.Either
4+
import arrow.core.getOrElse
5+
import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.TopicsResponse
46
import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.TopicResponse
5-
import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.Topic
67
import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.toTopic
78
import com.developersbreach.kotlindictionarymultiplatform.core.network.topicSource.FirestoreConstants
9+
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.Topic
810
import io.ktor.client.HttpClient
911
import io.ktor.client.call.body
1012
import io.ktor.client.request.get
1113

1214
class TopicRepository(
1315
private val httpClient: HttpClient,
1416
) {
15-
suspend fun getTopics(): Either<Throwable, List<Topic>> {
17+
private suspend fun getTopics(): Either<Throwable, List<TopicResponse>> {
1618
return Either.catch {
17-
val topicResponse: TopicResponse = httpClient.get(FirestoreConstants.TOPICS_URL).body()
18-
topicResponse.topics.map { it.toTopic() }
19+
val topicsResponse: TopicsResponse = httpClient.get(FirestoreConstants.TOPICS_URL).body()
20+
topicsResponse.topics.map { it.toTopic() }
1921
}
2022
}
23+
24+
suspend fun getTopicsPage(
25+
page: Int,
26+
pageSize: Int,
27+
query: String,
28+
): List<Topic> {
29+
val allTopics = getTopics().getOrElse { emptyList() }
30+
val filteredTopics = allTopics
31+
.filter { it.name?.contains(query, ignoreCase = true) == true }
32+
.sortedBy { it.name?.lowercase() ?: "" }
33+
.map { topic ->
34+
Topic(
35+
name = topic.name ?: "",
36+
initial = topic.name?.firstOrNull()?.uppercase() ?: "",
37+
description = topic.description ?: "",
38+
)
39+
}
40+
val fromIndex = (page - 1) * pageSize
41+
val toIndex = (fromIndex + pageSize).coerceAtMost(filteredTopics.size)
42+
return if (fromIndex < filteredTopics.size) filteredTopics.subList(fromIndex, toIndex) else emptyList()
43+
}
2144
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic
2+
3+
data class Topic(
4+
val name: String,
5+
val initial: String,
6+
val description: String,
7+
)

composeApp/src/commonMain/kotlin/com/developersbreach/kotlindictionarymultiplatform/ui/screens/topic/TopicCard.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ import com.developersbreach.designsystem.components.KdText
2525

2626
@Composable
2727
fun TopicCard(
28-
itemTopic: ItemTopic,
29-
topic: String,
30-
description: String,
28+
topic: Topic,
3129
onCardClick: () -> Unit,
3230
) {
3331
KdSurface(
@@ -60,7 +58,7 @@ fun TopicCard(
6058
) {
6159
KdText(
6260
modifier = Modifier,
63-
text = itemTopic.initial,
61+
text = topic.initial,
6462
)
6563
}
6664

@@ -71,7 +69,7 @@ fun TopicCard(
7169
) {
7270
KdText(
7371
modifier = Modifier,
74-
text = topic,
72+
text = topic.name,
7573
style = MaterialTheme.typography.headlineMedium.copy(
7674
color = MaterialTheme.colorScheme.onPrimary,
7775
),
@@ -81,7 +79,7 @@ fun TopicCard(
8179
Spacer(modifier = Modifier.height(6.dp))
8280
KdText(
8381
modifier = Modifier,
84-
text = description,
82+
text = topic.description,
8583
style = MaterialTheme.typography.labelMedium.copy(
8684
color = MaterialTheme.colorScheme.onBackground,
8785
),

0 commit comments

Comments
 (0)