Skip to content

Commit 91cd4a6

Browse files
committed
Issue #94: Home page now show all folders with pagination
1 parent 8a2f13b commit 91cd4a6

File tree

9 files changed

+151
-58
lines changed

9 files changed

+151
-58
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ dependencies {
7979
implementation(libs.bundles.room)
8080
ksp(libs.room.compiler)
8181

82+
implementation(libs.bundles.paging)
83+
8284
implementation(libs.hilt.android)
8385
ksp(libs.hilt.compiler)
8486

app/src/main/java/com/amrdeveloper/linkhub/data/source/FolderDataSource.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.amrdeveloper.linkhub.data.source
22

3+
import androidx.paging.PagingSource
34
import com.amrdeveloper.linkhub.data.Folder
45
import kotlinx.coroutines.flow.Flow
56

@@ -15,6 +16,8 @@ interface FolderDataSource {
1516

1617
suspend fun getFolderList(): Result<List<Folder>>
1718

19+
fun getMoseUsedFoldersWithPagination(): PagingSource<Int, Folder>
20+
1821
fun getSortedFolders(
1922
keyword: String? = null,
2023
isPinned: Boolean? = null,

app/src/main/java/com/amrdeveloper/linkhub/data/source/FolderRepository.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.amrdeveloper.linkhub.data.source
22

3+
import androidx.paging.PagingSource
34
import com.amrdeveloper.linkhub.data.Folder
45
import kotlinx.coroutines.flow.Flow
56

@@ -21,6 +22,10 @@ class FolderRepository(private val dataSource: FolderDataSource) {
2122
return dataSource.getFolderList()
2223
}
2324

25+
fun getMoseUsedFoldersWithPagination(): PagingSource<Int, Folder> {
26+
return dataSource.getMoseUsedFoldersWithPagination()
27+
}
28+
2429
fun getSortedFolders(
2530
keyword: String? = null,
2631
isPinned: Boolean? = null,

app/src/main/java/com/amrdeveloper/linkhub/data/source/local/FolderDao.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.amrdeveloper.linkhub.data.source.local
22

3+
import androidx.paging.PagingSource
34
import androidx.room.Dao
45
import androidx.room.Query
56
import androidx.room.Transaction
@@ -18,6 +19,12 @@ interface FolderDao : BaseDao<Folder> {
1819
@Query("SELECT * FROM folder")
1920
suspend fun getFolders(): List<Folder>
2021

22+
@Query("""
23+
SELECT * FROM folder
24+
ORDER BY pinned DESC, click_count DESC
25+
""")
26+
fun getMoseUsedFoldersWithPagination(): PagingSource<Int, Folder>
27+
2128
@Query("""
2229
SELECT * FROM folder
2330
WHERE ((:keyword IS NULL) OR (:keyword = '') OR (name LIKE '%' || :keyword || '%'))

app/src/main/java/com/amrdeveloper/linkhub/data/source/local/FolderLocalDataSource.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.amrdeveloper.linkhub.data.source.local
22

3+
import androidx.paging.PagingSource
34
import com.amrdeveloper.linkhub.data.Folder
45
import com.amrdeveloper.linkhub.data.source.FolderDataSource
56
import kotlinx.coroutines.CoroutineDispatcher
@@ -53,6 +54,10 @@ class FolderLocalDataSource internal constructor(
5354
}
5455
}
5556

57+
override fun getMoseUsedFoldersWithPagination(): PagingSource<Int, Folder> {
58+
return folderDao.getMoseUsedFoldersWithPagination()
59+
}
60+
5661
override fun getSortedFolders(
5762
keyword: String?,
5863
isPinned: Boolean?,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.amrdeveloper.linkhub.ui.components
2+
3+
import androidx.compose.animation.core.animateDpAsState
4+
import androidx.compose.foundation.background
5+
import androidx.compose.foundation.layout.Arrangement
6+
import androidx.compose.foundation.layout.Box
7+
import androidx.compose.foundation.layout.Row
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.layout.size
11+
import androidx.compose.foundation.pager.PagerState
12+
import androidx.compose.foundation.shape.CircleShape
13+
import androidx.compose.runtime.Composable
14+
import androidx.compose.runtime.getValue
15+
import androidx.compose.ui.Alignment
16+
import androidx.compose.ui.Modifier
17+
import androidx.compose.ui.draw.clip
18+
import androidx.compose.ui.graphics.Color
19+
import androidx.compose.ui.res.colorResource
20+
import androidx.compose.ui.unit.dp
21+
import com.amrdeveloper.linkhub.R
22+
23+
@Composable
24+
fun PagerIndicator(
25+
pagerState: PagerState,
26+
modifier: Modifier = Modifier,
27+
selectedIndicatorColor: Color = colorResource(R.color.light_blue_200),
28+
unSelectedIndicatorColor: Color = colorResource(R.color.light_blue_900),
29+
) {
30+
Row(
31+
modifier = modifier
32+
.fillMaxWidth()
33+
.padding(vertical = 16.dp),
34+
horizontalArrangement = Arrangement.Center,
35+
verticalAlignment = Alignment.CenterVertically
36+
) {
37+
repeat(pagerState.pageCount) { iteration ->
38+
val isCurrentPage = pagerState.currentPage == iteration
39+
val indicatorColor = if (isCurrentPage) selectedIndicatorColor
40+
else unSelectedIndicatorColor
41+
42+
val size by animateDpAsState(
43+
targetValue = if (isCurrentPage) 10.dp else 8.dp, label = "Indicator dor color"
44+
)
45+
46+
Box(
47+
modifier = Modifier
48+
.padding(4.dp)
49+
.clip(CircleShape)
50+
.background(indicatorColor)
51+
.size(size)
52+
)
53+
}
54+
}
55+
}

app/src/main/java/com/amrdeveloper/linkhub/ui/home/HomeScreen.kt

Lines changed: 60 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package com.amrdeveloper.linkhub.ui.home
22

3-
import androidx.compose.foundation.clickable
4-
import androidx.compose.foundation.layout.Arrangement
53
import androidx.compose.foundation.layout.Column
6-
import androidx.compose.foundation.layout.Row
74
import androidx.compose.foundation.layout.fillMaxSize
85
import androidx.compose.foundation.layout.fillMaxWidth
96
import androidx.compose.foundation.layout.padding
10-
import androidx.compose.foundation.layout.size
11-
import androidx.compose.material3.Icon
7+
import androidx.compose.foundation.lazy.grid.GridCells
8+
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
9+
import androidx.compose.foundation.pager.HorizontalPager
10+
import androidx.compose.foundation.pager.rememberPagerState
1211
import androidx.compose.material3.MaterialTheme
1312
import androidx.compose.material3.Scaffold
1413
import androidx.compose.material3.Text
@@ -19,34 +18,40 @@ import androidx.compose.runtime.remember
1918
import androidx.compose.runtime.setValue
2019
import androidx.compose.ui.Alignment
2120
import androidx.compose.ui.Modifier
22-
import androidx.compose.ui.graphics.Color
2321
import androidx.compose.ui.platform.LocalContext
2422
import androidx.compose.ui.res.colorResource
25-
import androidx.compose.ui.res.painterResource
26-
import androidx.compose.ui.text.font.FontWeight
2723
import androidx.compose.ui.unit.dp
2824
import androidx.core.os.bundleOf
2925
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3026
import androidx.lifecycle.viewmodel.compose.viewModel
3127
import androidx.navigation.NavController
28+
import androidx.paging.compose.collectAsLazyPagingItems
29+
import androidx.paging.compose.itemKey
3230
import com.amrdeveloper.linkhub.R
3331
import com.amrdeveloper.linkhub.data.Link
34-
import com.amrdeveloper.linkhub.ui.components.FolderList
35-
import com.amrdeveloper.linkhub.ui.components.FolderViewKind
32+
import com.amrdeveloper.linkhub.ui.components.FolderItem
3633
import com.amrdeveloper.linkhub.ui.components.LinkActionsBottomSheet
3734
import com.amrdeveloper.linkhub.ui.components.LinkList
3835
import com.amrdeveloper.linkhub.ui.components.LinkhubToolbar
36+
import com.amrdeveloper.linkhub.ui.components.PagerIndicator
3937
import com.amrdeveloper.linkhub.util.UiPreferences
4038
import com.amrdeveloper.linkhub.util.openLinkIntent
4139

40+
private const val NUMBER_OF_FOLDERS_PER_PAGE = 6
41+
4242
@Composable
4343
fun HomeScreen(
4444
viewModel: HomeViewModel = viewModel(),
4545
uiPreferences: UiPreferences,
4646
navController: NavController
4747
) {
4848
val context = LocalContext.current
49-
val folders = viewModel.mostUsedLimitedFoldersState.collectAsStateWithLifecycle()
49+
50+
val folders = viewModel.mostUsedLimitedFoldersState.collectAsLazyPagingItems()
51+
val totalPages =
52+
(folders.itemCount + NUMBER_OF_FOLDERS_PER_PAGE - 1) / NUMBER_OF_FOLDERS_PER_PAGE
53+
val foldersPagerState = rememberPagerState(pageCount = { totalPages })
54+
5055
val links = viewModel.sortedLinksState.collectAsStateWithLifecycle()
5156

5257
var lastClickedLink by remember { mutableStateOf<Link?>(value = null) }
@@ -58,9 +63,9 @@ fun HomeScreen(
5863
.fillMaxSize()
5964
.padding(padding)
6065
) {
61-
if (folders.value.data.isNotEmpty()) {
66+
if (folders.itemCount != 0) {
6267
Text(
63-
text = "Most used Folders",
68+
text = "Folders",
6469
modifier = Modifier
6570
.fillMaxWidth()
6671
.padding(8.dp),
@@ -69,45 +74,50 @@ fun HomeScreen(
6974
color = colorResource(R.color.light_blue_600)
7075
)
7176

72-
FolderList(
73-
folders = folders.value.data,
74-
viewKind = FolderViewKind.Grid,
75-
onClick = { folder ->
76-
viewModel.incrementFolderClickCount(folder)
77-
val bundle = bundleOf("folder" to folder)
78-
navController.navigate(R.id.linkListFragment, bundle)
79-
},
80-
onLongClick = { folder ->
81-
val bundle = bundleOf("folder" to folder)
82-
navController.navigate(R.id.folderFragment, bundle)
83-
},
84-
minimalModeEnabled = uiPreferences.isMinimalModeEnabled()
85-
)
77+
HorizontalPager(
78+
state = foldersPagerState,
79+
key = folders.itemKey { it.id },
80+
verticalAlignment = Alignment.Top,
81+
beyondViewportPageCount = 1,
82+
modifier = Modifier.fillMaxWidth(),
83+
pageContent = { pageIndex ->
84+
LazyVerticalGrid(columns = GridCells.Fixed(count = 2)) {
85+
for (elementIndexInUI in 0 until NUMBER_OF_FOLDERS_PER_PAGE) {
86+
val itemIndex =
87+
(pageIndex * NUMBER_OF_FOLDERS_PER_PAGE) + elementIndexInUI
88+
if (itemIndex >= folders.itemCount) {
89+
break
90+
}
8691

87-
Row(
88-
modifier = Modifier
89-
.fillMaxWidth()
90-
.padding(4.dp)
91-
.clickable {
92-
navController.navigate(R.id.folderListFragment)
93-
},
94-
horizontalArrangement = Arrangement.End,
95-
verticalAlignment = Alignment.CenterVertically
96-
) {
97-
Text(
98-
text = "Show all",
99-
style = MaterialTheme.typography.titleSmall,
100-
fontWeight = FontWeight.Bold,
101-
color = colorResource(R.color.light_blue_600)
102-
)
92+
folders[itemIndex]?.let { folder ->
93+
item {
94+
FolderItem(
95+
folder = folder,
96+
onClick = { folder ->
97+
viewModel.incrementFolderClickCount(folder)
98+
val bundle = bundleOf("folder" to folder)
99+
navController.navigate(
100+
R.id.linkListFragment,
101+
bundle
102+
)
103+
},
104+
onLongClick = { folder ->
105+
val bundle = bundleOf("folder" to folder)
106+
navController.navigate(R.id.folderFragment, bundle)
107+
},
108+
minimalModeEnabled = uiPreferences.isMinimalModeEnabled(),
109+
modifier = Modifier
110+
.fillMaxWidth()
111+
.padding(4.dp)
112+
)
113+
}
114+
}
115+
}
116+
}
117+
}
118+
)
103119

104-
Icon(
105-
painter = painterResource(R.drawable.ic_arrow_forward),
106-
contentDescription = "Show all",
107-
tint = Color.Unspecified,
108-
modifier = Modifier.size(18.dp)
109-
)
110-
}
120+
PagerIndicator(pagerState = foldersPagerState)
111121
}
112122

113123
if (links.value.data.isNotEmpty()) {

app/src/main/java/com/amrdeveloper/linkhub/ui/home/HomeViewModel.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package com.amrdeveloper.linkhub.ui.home
22

33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
5+
import androidx.paging.Pager
6+
import androidx.paging.PagingConfig
7+
import androidx.paging.cachedIn
58
import com.amrdeveloper.linkhub.common.LazyValue
69
import com.amrdeveloper.linkhub.data.Folder
710
import com.amrdeveloper.linkhub.data.Link
@@ -23,13 +26,10 @@ class HomeViewModel @Inject constructor(
2326
private val linkRepository: LinkRepository,
2427
) : ViewModel() {
2528

26-
val mostUsedLimitedFoldersState: StateFlow<LazyValue<List<Folder>>> =
27-
folderRepository.getSortedFolders(limit = NUMBER_OF_TOP_FOLDERS)
28-
.map { LazyValue(data = it, isLoading = false) }.stateIn(
29-
scope = viewModelScope,
30-
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000L),
31-
initialValue = LazyValue(data = listOf(), isLoading = true)
32-
)
29+
val mostUsedLimitedFoldersState =
30+
Pager(config = PagingConfig(pageSize = NUMBER_OF_TOP_FOLDERS, prefetchDistance = 2)) {
31+
folderRepository.getMoseUsedFoldersWithPagination()
32+
}.flow.cachedIn(viewModelScope)
3333

3434
val sortedLinksState: StateFlow<LazyValue<List<Link>>> =
3535
linkRepository.getSortedLinks()

gradle/libs.versions.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ lifecycle-livedata-ktx = "2.10.0"
1919
navigation-fragment-ktx = "2.9.6"
2020
navigation-safeargs = "2.9.6"
2121
room = "2.8.4"
22+
paging = "3.4.0"
2223
hilt = "2.57.2"
2324
legacy_support = "1.0.0"
2425

@@ -69,7 +70,11 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
6970

7071
lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" }
7172

73+
paging-runtime = { module = "androidx.paging:paging-runtime-ktx", version.ref = "paging" }
74+
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging" }
75+
7276
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
77+
room-paging = { module = "androidx.room:room-paging", version.ref = "room" }
7378
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
7479
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
7580

@@ -88,7 +93,8 @@ kotlinx-coroutines-testing = { module = "org.jetbrains.kotlinx:kotlinx-coroutine
8893
leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary-android" }
8994

9095
[bundles]
91-
room = ["room-runtime", "room-ktx"]
96+
room = ["room-runtime", "room-paging", "room-ktx"]
97+
paging = ["paging-runtime", "paging-compose"]
9298

9399
[plugins]
94100
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }

0 commit comments

Comments
 (0)