Skip to content

Commit 22d8000

Browse files
authored
feat: pagination for cell file lists (#WPB-16903) (#3984)
1 parent f2646b9 commit 22d8000

File tree

21 files changed

+358
-290
lines changed

21 files changed

+358
-290
lines changed

.github/workflows/check-kalium-commitish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88

99
jobs:
1010
validate-kalium-ref:
11-
runs-on: ubuntu-20.04
11+
runs-on: ubuntu-latest
1212

1313
steps:
1414
- name: Checkout

app/src/main/kotlin/com/wire/android/di/accountScoped/CellsModule.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,21 @@ import com.wire.android.di.KaliumCoreLogic
2222
import com.wire.kalium.cells.CellsScope
2323
import com.wire.kalium.cells.domain.CellUploadManager
2424
import com.wire.kalium.cells.domain.usecase.AddAttachmentDraftUseCase
25-
import com.wire.kalium.cells.domain.usecase.publiclink.CreatePublicLinkUseCase
2625
import com.wire.kalium.cells.domain.usecase.DeleteCellAssetUseCase
2726
import com.wire.kalium.cells.domain.usecase.DownloadCellFileUseCase
28-
import com.wire.kalium.cells.domain.usecase.ObserveAttachmentDraftsUseCase
2927
import com.wire.kalium.cells.domain.usecase.GetCellFilesUseCase
28+
import com.wire.kalium.cells.domain.usecase.GetPaginatedFilesFlowUseCase
29+
import com.wire.kalium.cells.domain.usecase.ObserveAttachmentDraftsUseCase
3030
import com.wire.kalium.cells.domain.usecase.PublishAttachmentsUseCase
3131
import com.wire.kalium.cells.domain.usecase.RefreshCellAssetStateUseCase
3232
import com.wire.kalium.cells.domain.usecase.RemoveAttachmentDraftUseCase
3333
import com.wire.kalium.cells.domain.usecase.RemoveAttachmentDraftsUseCase
3434
import com.wire.kalium.cells.domain.usecase.RetryAttachmentUploadUseCase
3535
import com.wire.kalium.cells.domain.usecase.SetWireCellForConversationUseCase
36+
import com.wire.kalium.cells.domain.usecase.publiclink.CreatePublicLinkUseCase
3637
import com.wire.kalium.cells.domain.usecase.publiclink.DeletePublicLinkUseCase
3738
import com.wire.kalium.cells.domain.usecase.publiclink.GetPublicLinkUseCase
39+
import com.wire.kalium.cells.paginatedFilesFlowUseCase
3840
import com.wire.kalium.logic.CoreLogic
3941
import com.wire.kalium.logic.data.user.UserId
4042
import dagger.Module
@@ -83,6 +85,10 @@ class CellsModule {
8385
@Provides
8486
fun provideObserveFilesUseCase(cellsScope: CellsScope): GetCellFilesUseCase = cellsScope.observeFiles
8587

88+
@ViewModelScoped
89+
@Provides
90+
fun provideObservePagedFilesUseCase(cellsScope: CellsScope): GetPaginatedFilesFlowUseCase = cellsScope.paginatedFilesFlowUseCase
91+
8692
@ViewModelScoped
8793
@Provides
8894
fun provideEnableCellUseCase(cellsScope: CellsScope): SetWireCellForConversationUseCase = cellsScope.enableWireCell

app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ import com.wire.kalium.logic.data.mls.CipherSuite
141141
import kotlinx.coroutines.launch
142142
import kotlinx.datetime.Instant
143143

144+
@Suppress("CyclomaticComplexMethod")
144145
@RootNavGraph
145146
@WireDestination(
146147
navArgsDelegate = GroupConversationDetailsNavArgs::class,
@@ -174,7 +175,7 @@ fun GroupConversationDetailsScreen(
174175
}
175176

176177
val onConversationMediaClick: () -> Unit = {
177-
if (groupOptions.isWireCellEnabled) {
178+
if (groupOptions.isWireCellEnabled && groupOptions.isWireCellFeatureEnabled) {
178179
navigator.navigate(NavigationCommand(ConversationFilesScreenDestination(viewModel.conversationId.toString())))
179180
} else {
180181
navigator.navigate(NavigationCommand(ConversationMediaScreenDestination(viewModel.conversationId)))

app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchServicesViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import androidx.compose.runtime.setValue
2323
import androidx.lifecycle.ViewModel
2424
import androidx.lifecycle.viewModelScope
2525
import com.wire.android.mapper.ContactMapper
26+
import com.wire.android.ui.common.DEFAULT_SEARCH_QUERY_DEBOUNCE
2627
import com.wire.android.ui.home.newconversation.model.Contact
2728
import com.wire.android.util.EMPTY
2829
import com.wire.kalium.logic.feature.service.ObserveAllServicesUseCase

app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUserViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.lifecycle.SavedStateHandle
2424
import androidx.lifecycle.ViewModel
2525
import androidx.lifecycle.viewModelScope
2626
import com.wire.android.mapper.ContactMapper
27+
import com.wire.android.ui.common.DEFAULT_SEARCH_QUERY_DEBOUNCE
2728
import com.wire.android.ui.home.newconversation.model.Contact
2829
import com.wire.android.ui.navArgs
2930
import com.wire.android.util.EMPTY

app/src/main/kotlin/com/wire/android/ui/home/conversations/search/messages/SearchConversationMessagesViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import androidx.compose.runtime.setValue
2424
import androidx.lifecycle.SavedStateHandle
2525
import androidx.lifecycle.ViewModel
2626
import com.wire.android.ui.common.textfield.textAsFlow
27-
import com.wire.android.ui.home.conversations.search.DEFAULT_SEARCH_QUERY_DEBOUNCE
27+
import com.wire.android.ui.common.DEFAULT_SEARCH_QUERY_DEBOUNCE
2828
import com.wire.android.ui.home.conversations.usecase.GetConversationMessagesFromSearchUseCase
2929
import com.wire.android.ui.navArgs
3030
import com.wire.android.util.dispatchers.DispatcherProvider

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import com.wire.android.model.SnackBarMessage
3838
import com.wire.android.ui.common.bottomsheet.conversation.ConversationTypeDetail
3939
import com.wire.android.ui.common.dialogs.BlockUserDialogState
4040
import com.wire.android.ui.home.HomeSnackBarMessage
41-
import com.wire.android.ui.home.conversations.search.DEFAULT_SEARCH_QUERY_DEBOUNCE
41+
import com.wire.android.ui.common.DEFAULT_SEARCH_QUERY_DEBOUNCE
4242
import com.wire.android.ui.home.conversations.usecase.GetConversationsFromSearchUseCase
4343
import com.wire.android.ui.home.conversationslist.common.previewConversationFoldersFlow
4444
import com.wire.android.ui.home.conversationslist.model.BadgeEventType

app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaAuthenticatedViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import com.wire.android.appLogger
3535
import com.wire.android.model.ImageAsset
3636
import com.wire.android.model.SnackBarMessage
3737
import com.wire.android.ui.common.textfield.textAsFlow
38-
import com.wire.android.ui.home.conversations.search.DEFAULT_SEARCH_QUERY_DEBOUNCE
38+
import com.wire.android.ui.common.DEFAULT_SEARCH_QUERY_DEBOUNCE
3939
import com.wire.android.ui.home.conversations.usecase.GetConversationsFromSearchUseCase
4040
import com.wire.android.ui.home.conversations.usecase.HandleUriAssetUseCase
4141
import com.wire.android.ui.home.conversationslist.model.ConversationFolderItem
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
*
1919
*/
2020

21-
package com.wire.android.ui.home.conversations.search
21+
package com.wire.android.ui.common
2222

2323
const val DEFAULT_SEARCH_QUERY_DEBOUNCE = 500L

features/cells/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ dependencies {
4242

4343
implementation(libs.ktx.dateTime)
4444

45+
implementation(libs.androidx.paging3)
46+
implementation(libs.androidx.paging3Compose)
47+
4548
testImplementation(libs.junit5.core)
4649
testImplementation(libs.coroutines.test)
4750
testImplementation(libs.mockk.core)
4851
testImplementation(libs.turbine)
52+
testImplementation(libs.androidx.paging.testing)
4953
testRuntimeOnly(libs.junit5.engine)
5054
androidTestImplementation(libs.androidx.test.extJunit)
5155
androidTestImplementation(libs.androidx.espresso.core)

features/cells/src/main/java/com/wire/android/feature/cells/ui/AllFilesScreen.kt

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ package com.wire.android.feature.cells.ui
1919

2020
import androidx.compose.runtime.Composable
2121
import androidx.compose.runtime.LaunchedEffect
22-
import androidx.compose.runtime.collectAsState
23-
import androidx.compose.runtime.getValue
2422
import androidx.hilt.navigation.compose.hiltViewModel
23+
import androidx.paging.compose.collectAsLazyPagingItems
2524
import com.wire.android.feature.cells.ui.destinations.PublicLinkScreenDestination
2625
import com.wire.android.navigation.NavigationCommand
2726
import com.wire.android.navigation.WireNavigator
@@ -39,7 +38,7 @@ fun AllFilesScreen(
3938
viewModel: CellViewModel = hiltViewModel(),
4039
) {
4140

42-
val state by viewModel.state.collectAsState()
41+
val pagingListItems = viewModel.filesFlow.collectAsLazyPagingItems()
4342

4443
LaunchedEffect(searchBarState.searchQueryTextState.text) {
4544
if (searchBarState.searchQueryTextState.text.isNotEmpty()) {
@@ -49,20 +48,22 @@ fun AllFilesScreen(
4948
}
5049

5150
val isSearchVisible = when {
52-
state is CellViewState.Files -> true
53-
state.isEmptySearch() -> true
54-
else -> false
51+
pagingListItems.isLoading() -> false
52+
pagingListItems.isError() -> false
53+
pagingListItems.itemCount == 0 && !viewModel.hasSearchQuery() -> false
54+
else -> true
5555
}
5656

5757
searchBarState.searchVisibleChanged(isSearchVisible)
5858

5959
CellScreenContent(
6060
actionsFlow = viewModel.actions,
61-
viewState = state,
61+
pagingListItems = pagingListItems,
6262
sendIntent = { viewModel.sendIntent(it) },
63-
downloadFileState = viewModel.downloadFile,
63+
downloadFileState = viewModel.downloadFileSheet,
6464
fileMenuState = viewModel.menu,
6565
isAllFiles = true,
66+
isSearchResult = viewModel.hasSearchQuery(),
6667
showPublicLinkScreen = { assetId, fileName, linkId ->
6768
navigator.navigate(
6869
NavigationCommand(
@@ -76,5 +77,3 @@ fun AllFilesScreen(
7677
},
7778
)
7879
}
79-
80-
private fun CellViewState.isEmptySearch() = this is CellViewState.Empty && isSearchResult

features/cells/src/main/java/com/wire/android/feature/cells/ui/CellFilesScreen.kt

Lines changed: 129 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,78 +19,165 @@ package com.wire.android.feature.cells.ui
1919

2020
import androidx.compose.foundation.background
2121
import androidx.compose.foundation.clickable
22+
import androidx.compose.foundation.layout.Arrangement
23+
import androidx.compose.foundation.layout.Box
24+
import androidx.compose.foundation.layout.Column
25+
import androidx.compose.foundation.layout.Row
2226
import androidx.compose.foundation.layout.fillMaxWidth
27+
import androidx.compose.foundation.layout.height
28+
import androidx.compose.foundation.layout.padding
2329
import androidx.compose.foundation.lazy.LazyColumn
24-
import androidx.compose.foundation.lazy.items
30+
import androidx.compose.foundation.shape.RoundedCornerShape
31+
import androidx.compose.material.Text
2532
import androidx.compose.runtime.Composable
33+
import androidx.compose.ui.Alignment
2634
import androidx.compose.ui.Modifier
27-
import com.wire.android.feature.cells.domain.model.AttachmentFileType
35+
import androidx.compose.ui.res.stringResource
36+
import androidx.compose.ui.text.style.TextAlign
37+
import androidx.paging.LoadState
38+
import androidx.paging.compose.LazyPagingItems
39+
import androidx.paging.compose.itemContentType
40+
import androidx.paging.compose.itemKey
41+
import com.wire.android.feature.cells.R
2842
import com.wire.android.feature.cells.ui.model.CellFileUi
43+
import com.wire.android.feature.cells.ui.util.PreviewMultipleThemes
44+
import com.wire.android.ui.common.button.WireSecondaryButton
2945
import com.wire.android.ui.common.colorsScheme
46+
import com.wire.android.ui.common.dimensions
3047
import com.wire.android.ui.common.divider.WireDivider
3148
import com.wire.android.ui.common.preview.MultipleThemePreviews
49+
import com.wire.android.ui.common.progress.WireCircularProgressIndicator
50+
import com.wire.android.ui.common.typography
3251
import com.wire.android.ui.theme.WireTheme
3352

3453
@Composable
3554
internal fun CellFilesScreen(
36-
files: List<CellFileUi>,
55+
files: LazyPagingItems<CellFileUi>,
3756
onFileClick: (CellFileUi) -> Unit,
3857
onFileMenuClick: (CellFileUi) -> Unit,
39-
// onRefresh: () -> Unit
4058
) {
4159

42-
// TODO: Enable PullToRefresh support on HomeScreen level?
43-
// PullToRefreshBox(
44-
// isRefreshing = state.refreshing,
45-
// onRefresh = { onRefresh() }
46-
// ) {
4760
LazyColumn(
4861
modifier = Modifier
4962
.background(color = colorsScheme().surface)
5063
.fillMaxWidth(),
5164
) {
5265
items(
53-
items = files,
54-
key = { it.uuid },
55-
) { file ->
56-
CellListItem(
57-
modifier = Modifier
58-
.animateItem()
59-
.clickable { onFileClick(file) },
60-
file = file,
61-
onMenuClick = { onFileMenuClick(file) }
62-
)
63-
WireDivider(modifier = Modifier.fillMaxWidth())
66+
count = files.itemCount,
67+
key = files.itemKey { it.uuid },
68+
contentType = files.itemContentType { it }
69+
) { index ->
70+
71+
files[index]?.let { file ->
72+
CellListItem(
73+
modifier = Modifier
74+
.animateItem()
75+
.background(color = colorsScheme().surface)
76+
.clickable { onFileClick(file) },
77+
file = file,
78+
onMenuClick = { onFileMenuClick(file) }
79+
)
80+
WireDivider(modifier = Modifier.fillMaxWidth())
81+
}
82+
}
83+
84+
when (files.loadState.append) {
85+
is LoadState.Error -> item(contentType = "error") {
86+
ErrorFooter(
87+
onRetry = { files.retry() }
88+
)
89+
}
90+
91+
is LoadState.Loading -> item(contentType = "progress") {
92+
ProgressFooter()
93+
}
94+
95+
is LoadState.NotLoading -> {}
6496
}
6597
}
66-
// }
6798
}
6899

69100
@MultipleThemePreviews
70101
@Composable
71-
fun PreviewCellFilesScreen() {
72-
WireTheme {
73-
CellFilesScreen(
74-
files = listOf(
75-
CellFileUi(
76-
uuid = "uuid",
77-
fileName = "Image name",
78-
mimeType = "image/png",
79-
assetType = AttachmentFileType.IMAGE,
80-
assetSize = 1234L,
81-
localPath = "path/to/local/file",
82-
),
83-
CellFileUi(
84-
uuid = "uuid2",
85-
fileName = "PDF name",
86-
mimeType = "application/pdf",
87-
assetType = AttachmentFileType.PDF,
88-
assetSize = 99234L,
89-
localPath = "path/to/local/file",
102+
private fun ProgressFooter() {
103+
Box(
104+
modifier = Modifier
105+
.height(dimensions().spacing56x)
106+
.fillMaxWidth(),
107+
contentAlignment = Alignment.Center,
108+
) {
109+
Row(
110+
modifier = Modifier
111+
.height(dimensions().spacing24x)
112+
.background(
113+
color = colorsScheme().surface,
114+
shape = RoundedCornerShape(dimensions().corner10x)
90115
)
116+
.padding(
117+
horizontal = dimensions().spacing16x,
118+
),
119+
verticalAlignment = Alignment.CenterVertically,
120+
horizontalArrangement = Arrangement.spacedBy(
121+
space = dimensions().spacing6x,
122+
alignment = Alignment.CenterHorizontally,
91123
),
92-
onFileClick = {},
93-
onFileMenuClick = {}
124+
) {
125+
WireCircularProgressIndicator(
126+
progressColor = colorsScheme().secondaryText,
127+
size = dimensions().spacing14x,
128+
)
129+
Text(
130+
text = stringResource(R.string.loading_files),
131+
style = typography().subline01
132+
)
133+
}
134+
}
135+
}
136+
137+
@Composable
138+
private fun ErrorFooter(onRetry: () -> Unit) {
139+
Column(
140+
modifier = Modifier
141+
.fillMaxWidth()
142+
.background(color = colorsScheme().errorVariant)
143+
.padding(dimensions().spacing12x),
144+
verticalArrangement = Arrangement.spacedBy(
145+
space = dimensions().spacing8x,
146+
alignment = Alignment.CenterVertically,
147+
),
148+
horizontalAlignment = Alignment.CenterHorizontally
149+
) {
150+
151+
Text(
152+
text = stringResource(R.string.file_list_load_error),
153+
style = typography().label03,
154+
color = colorsScheme().error,
155+
textAlign = TextAlign.Center,
156+
)
157+
158+
WireSecondaryButton(
159+
modifier = Modifier.height(dimensions().spacing32x),
160+
text = stringResource(R.string.retry),
161+
onClick = onRetry,
162+
fillMaxWidth = false,
163+
)
164+
}
165+
}
166+
167+
@PreviewMultipleThemes
168+
@Composable
169+
private fun PreviewProgressFooter() {
170+
WireTheme {
171+
ProgressFooter()
172+
}
173+
}
174+
175+
@PreviewMultipleThemes
176+
@Composable
177+
private fun PreviewErrorFooter() {
178+
WireTheme {
179+
ErrorFooter(
180+
onRetry = {}
94181
)
95182
}
96183
}

0 commit comments

Comments
 (0)