Skip to content

Commit 9eba372

Browse files
committed
wip
Signed-off-by: alperozturk96 <alper_ozturk@proton.me>
1 parent 4b383d3 commit 9eba372

7 files changed

Lines changed: 112 additions & 117 deletions

File tree

ui/src/main/java/com/nextcloud/android/common/ui/share/ShareScreen.kt

Lines changed: 81 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.compose.foundation.background
1111
import androidx.compose.foundation.clickable
1212
import androidx.compose.foundation.combinedClickable
1313
import androidx.compose.foundation.layout.Box
14+
import androidx.compose.foundation.layout.PaddingValues
1415
import androidx.compose.foundation.layout.Row
1516
import androidx.compose.foundation.layout.Spacer
1617
import androidx.compose.foundation.layout.fillMaxSize
@@ -33,6 +34,7 @@ import androidx.compose.material3.CircularProgressIndicator
3334
import androidx.compose.material3.ColorScheme
3435
import androidx.compose.material3.DropdownMenu
3536
import androidx.compose.material3.DropdownMenuItem
37+
import androidx.compose.material3.ExperimentalMaterial3Api
3638
import androidx.compose.material3.FloatingActionButton
3739
import androidx.compose.material3.HorizontalDivider
3840
import androidx.compose.material3.Icon
@@ -44,6 +46,7 @@ import androidx.compose.material3.Scaffold
4446
import androidx.compose.material3.SnackbarHost
4547
import androidx.compose.material3.SnackbarHostState
4648
import androidx.compose.material3.Text
49+
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
4750
import androidx.compose.runtime.Composable
4851
import androidx.compose.runtime.LaunchedEffect
4952
import androidx.compose.runtime.getValue
@@ -92,9 +95,11 @@ import com.nextcloud.android.common.ui.share.model.ui.filtered
9295
import com.nextcloud.android.common.ui.share.model.ui.label
9396
import com.nextcloud.android.common.ui.share.repository.ShareRemoteRepository
9497

98+
private val FIRST_ITEM_TOP_SPACING = 16.dp
99+
private val ITEM_SPACING = 2.dp
100+
95101
@Composable
96102
private fun ShareScreen(
97-
sourceId: String,
98103
internalLink: String,
99104
viewModel: ShareViewModel
100105
) {
@@ -105,7 +110,6 @@ private fun ShareScreen(
105110
val editorEntry by viewModel.editorEntry.collectAsStateWithLifecycle()
106111
val snackbarHostState = remember { SnackbarHostState() }
107112
val resources = LocalResources.current
108-
val context = LocalContext.current
109113

110114
LaunchedEffect(errorMessageId) {
111115
errorMessageId?.let {
@@ -117,7 +121,7 @@ private fun ShareScreen(
117121
Scaffold(
118122
floatingActionButton = {
119123
FloatingActionButton(
120-
onClick = { viewModel.createDraftShare(sourceId) },
124+
onClick = { viewModel.createDraftShare() },
121125
) {
122126
Icon(painterResource(R.drawable.ic_person_add), contentDescription = "Add")
123127
}
@@ -137,53 +141,19 @@ private fun ShareScreen(
137141
}
138142
}
139143

140-
is ShareScreenState.Empty -> {
141-
ContentUnavailableView(
142-
iconId = R.drawable.ic_person_add,
143-
title = stringResource(R.string.share_view_empty_title),
144-
)
145-
}
146-
147-
is ShareScreenState.Loaded -> {
148-
LazyColumn(
149-
modifier = Modifier
150-
.padding(paddingValues)
151-
.fillMaxWidth(),
152-
horizontalAlignment = Alignment.CenterHorizontally
153-
) {
154-
val shares = state.shares.filtered()
155-
itemsIndexed(shares, key = { _, share -> share.id }) { index, share ->
156-
val type = ShareItemType.type(index, shares.lastIndex)
157-
val title = share.getHeadline(context, shares)
158-
159-
if (index == 0) {
160-
Spacer(modifier = Modifier.height(16.dp))
161-
} else {
162-
Spacer(modifier = Modifier.height(2.dp))
163-
}
144+
is ShareScreenState.Empty -> ShareList(
145+
shares = emptyList(),
146+
permissionPresets = permissionPresets,
147+
paddingValues = paddingValues,
148+
viewModel = viewModel
149+
)
164150

165-
ShareItem(
166-
share = share,
167-
title = title,
168-
type = type,
169-
permissionPresets = permissionPresets,
170-
onSelectShare = { selected ->
171-
viewModel.setActiveShare(selected, ShareEditorEntry.EDIT)
172-
},
173-
onCustomizeShare = { selected ->
174-
viewModel.setActiveShare(selected, ShareEditorEntry.CUSTOMIZE_PERMISSION)
175-
},
176-
onChangePreset = { selected, preset ->
177-
viewModel.updatePermissionPreset(selected.id, preset, updateActiveShare = false)
178-
},
179-
onDeleteShare = { viewModel.deleteShare(it.id) },
180-
onSendEmail = { selected ->
181-
viewModel.setActiveShare(selected, ShareEditorEntry.SEND_EMAIL)
182-
}
183-
)
184-
}
185-
}
186-
}
151+
is ShareScreenState.Loaded -> ShareList(
152+
shares = state.shares.filtered(),
153+
permissionPresets = permissionPresets,
154+
paddingValues = paddingValues,
155+
viewModel = viewModel
156+
)
187157
}
188158
}
189159

@@ -202,6 +172,66 @@ private fun ShareScreen(
202172
}
203173
}
204174

175+
@OptIn(ExperimentalMaterial3Api::class)
176+
@Composable
177+
private fun ShareList(
178+
shares: List<Share>,
179+
permissionPresets: List<PermissionPreset>,
180+
paddingValues: PaddingValues,
181+
viewModel: ShareViewModel
182+
) {
183+
val context = LocalContext.current
184+
val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle()
185+
186+
PullToRefreshBox(
187+
isRefreshing = isRefreshing,
188+
onRefresh = viewModel::refreshShares,
189+
modifier = Modifier
190+
.fillMaxSize()
191+
.padding(paddingValues)
192+
) {
193+
LazyColumn(
194+
modifier = Modifier.fillMaxSize(),
195+
horizontalAlignment = Alignment.CenterHorizontally
196+
) {
197+
if (shares.isEmpty()) {
198+
item {
199+
Box(modifier = Modifier.fillParentMaxSize()) {
200+
ContentUnavailableView(
201+
iconId = R.drawable.ic_person_add,
202+
title = stringResource(R.string.share_view_empty_title),
203+
)
204+
}
205+
}
206+
}
207+
208+
itemsIndexed(shares, key = { _, share -> share.id }) { index, share ->
209+
Spacer(modifier = Modifier.height(if (index == 0) FIRST_ITEM_TOP_SPACING else ITEM_SPACING))
210+
211+
ShareItem(
212+
share = share,
213+
title = share.getHeadline(context, shares),
214+
type = ShareItemType.type(index, shares.lastIndex),
215+
permissionPresets = permissionPresets,
216+
onSelectShare = { selected ->
217+
viewModel.setActiveShare(selected, ShareEditorEntry.EDIT)
218+
},
219+
onCustomizeShare = { selected ->
220+
viewModel.setActiveShare(selected, ShareEditorEntry.CUSTOMIZE_PERMISSION)
221+
},
222+
onChangePreset = { selected, preset ->
223+
viewModel.updatePermissionPreset(selected.id, preset, updateActiveShare = false)
224+
},
225+
onDeleteShare = { viewModel.deleteShare(it.id) },
226+
onSendEmail = { selected ->
227+
viewModel.setActiveShare(selected, ShareEditorEntry.SEND_EMAIL)
228+
}
229+
)
230+
}
231+
}
232+
}
233+
}
234+
205235
@Composable
206236
private fun ShareItem(
207237
share: Share,
@@ -396,7 +426,7 @@ fun ComposeView.initShareScreen(
396426
credentials: ServerCredentials,
397427
colorScheme: ColorScheme
398428
) {
399-
val factory = ShareViewModelFactory {
429+
val factory = ShareViewModelFactory(sourceId) {
400430
ShareRemoteRepository(NextcloudHttpClient.create(credentials))
401431
}
402432
val viewModel = ViewModelProvider.create(viewModelStoreOwner, factory)[ShareViewModel::class]
@@ -411,7 +441,7 @@ fun ComposeView.initShareScreen(
411441
MaterialTheme(
412442
colorScheme = colorScheme,
413443
content = {
414-
ShareScreen(sourceId, internalLink, viewModel)
444+
ShareScreen(internalLink, viewModel)
415445
}
416446
)
417447
}

ui/src/main/java/com/nextcloud/android/common/ui/share/ShareViewModel.kt

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import com.nextcloud.android.common.ui.share.model.api.permission.PermissionPres
1717
import com.nextcloud.android.common.ui.share.model.api.recipients.Recipient
1818
import com.nextcloud.android.common.ui.share.model.api.request.AddRecipientRequest
1919
import com.nextcloud.android.common.ui.share.model.api.request.AddSourceRequest
20-
import com.nextcloud.android.common.ui.share.model.api.request.GetShareRequest
2120
import com.nextcloud.android.common.ui.share.model.api.request.UpdateSharePermissionPresetRequest
2221
import com.nextcloud.android.common.ui.share.model.api.request.UpdateSharePermissionRequest
2322
import com.nextcloud.android.common.ui.share.model.api.request.UpdateSharePropertyRequest
@@ -51,6 +50,7 @@ import kotlin.time.Duration.Companion.milliseconds
5150

5251
class ShareViewModel(
5352
private val repository: ShareRepository,
53+
private val sourceId: String,
5454
savedStateHandle: SavedStateHandle
5555
) : ViewModel() {
5656

@@ -67,6 +67,9 @@ class ShareViewModel(
6767
private val _state = MutableStateFlow<ShareScreenState>(ShareScreenState.Loading)
6868
val state: StateFlow<ShareScreenState> = _state
6969

70+
private val _isRefreshing = MutableStateFlow(false)
71+
val isRefreshing: StateFlow<Boolean> = _isRefreshing
72+
7073
private val _activeShare = MutableStateFlow<ActiveShareState>(ActiveShareState.None)
7174
val activeShare: StateFlow<ActiveShareState> = _activeShare
7275

@@ -149,51 +152,31 @@ class ShareViewModel(
149152
// endregion
150153

151154
// region shares list
152-
fun fetchShares(
153-
filterSourceTypeClass: String? = null,
154-
filterSourceTypeValue: String? = null,
155-
lastShareID: String? = null,
156-
limit: Int = SHARES_PAGE_SIZE
157-
) {
155+
fun refreshShares() {
156+
if (_isRefreshing.value) return
157+
158158
viewModelScope.launch(Dispatchers.IO) {
159-
loadShares(filterSourceTypeClass, filterSourceTypeValue, lastShareID, limit)
159+
_isRefreshing.update { true }
160+
loadShares()
161+
_isRefreshing.update { false }
160162
}
161163
}
162164

163-
private suspend fun loadShares(
164-
filterSourceTypeClass: String? = null,
165-
filterSourceTypeValue: String? = null,
166-
lastShareID: String? = null,
167-
limit: Int = SHARES_PAGE_SIZE
168-
) {
169-
_state.update { ShareScreenState.Loading }
165+
private suspend fun loadShares() {
170166
_errorMessageId.update { null }
171167

172-
val result = repository.fetchShares(filterSourceTypeClass, filterSourceTypeValue, lastShareID, limit)
168+
val result = repository.fetchShares(filterSourceTypeValue = sourceId, limit = SHARES_PAGE_SIZE)
173169
val fetched = result.dataOrElse { _errorMessageId.update { R.string.share_view_fetch_error_message } }
174170
?: return
175171
_state.update {
176172
if (fetched.filtered().isEmpty()) ShareScreenState.Empty
177173
else ShareScreenState.Loaded(fetched)
178174
}
179175
}
180-
181-
fun fetchShare(id: String, request: GetShareRequest = GetShareRequest()) {
182-
viewModelScope.launch(Dispatchers.IO) {
183-
_errorMessageId.update { null }
184-
185-
val result = repository.fetchShare(id, request)
186-
val share = result.dataOrElse { _errorMessageId.update { R.string.share_view_fetch_error_message } }
187-
?: return@launch
188-
189-
updateActiveShare(share.toActiveShare())
190-
replaceInList(share)
191-
}
192-
}
193176
// endregion
194177

195178
// region create
196-
fun createDraftShare(sourceValue: String) {
179+
fun createDraftShare() {
197180
viewModelScope.launch(Dispatchers.IO) {
198181
_errorMessageId.update { null }
199182

@@ -205,7 +188,7 @@ class ShareViewModel(
205188
updateActiveShare(draft.toActiveShare())
206189
_state.update { ShareScreenState.Loaded(listOf(draft) + currentShares) }
207190

208-
applySource(draft.id, sourceValue)
191+
applySource(draft.id, sourceId)
209192
}
210193
}
211194
// endregion
@@ -231,12 +214,6 @@ class ShareViewModel(
231214
// endregion
232215

233216
// region sources
234-
fun addSource(id: String, value: String) {
235-
viewModelScope.launch(Dispatchers.IO) {
236-
applySource(id, value)
237-
}
238-
}
239-
240217
private suspend fun applySource(id: String, value: String) {
241218
// TODO pass from the clients this may vary depends on the client so notes and files for now uses this
242219
val clazz = "OCA\\Files\\Sharing\\Source\\NodeShareSourceType"
@@ -246,16 +223,6 @@ class ShareViewModel(
246223
refreshActiveShare(updated.toActiveShare())
247224
replaceInList(updated)
248225
}
249-
250-
fun removeSource(id: String, clazz: String, value: String) {
251-
viewModelScope.launch(Dispatchers.IO) {
252-
val result = repository.removeShareSource(id, clazz, value)
253-
val updated = result.dataOrElse { _errorMessageId.update { R.string.share_view_update_error_message } }
254-
?: return@launch
255-
refreshActiveShare(updated.toActiveShare())
256-
replaceInList(updated)
257-
}
258-
}
259226
// endregion
260227

261228
// region recipients

ui/src/main/java/com/nextcloud/android/common/ui/share/ShareViewModelFactory.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import androidx.lifecycle.viewmodel.CreationExtras
1515
import com.nextcloud.android.common.ui.share.repository.ShareRepository
1616

1717
class ShareViewModelFactory(
18+
private val sourceId: String,
1819
private val repositoryProvider: () -> ShareRepository
1920
) : ViewModelProvider.Factory {
2021
@Suppress("UNCHECKED_CAST")
@@ -27,6 +28,6 @@ class ShareViewModelFactory(
2728
}
2829

2930
val savedStateHandle: SavedStateHandle = extras.createSavedStateHandle()
30-
return ShareViewModel(repositoryProvider(), savedStateHandle) as T
31+
return ShareViewModel(repositoryProvider(), sourceId, savedStateHandle) as T
3132
}
3233
}

ui/src/main/java/com/nextcloud/android/common/ui/share/component/bottomsheet/AddOrEditShareBottomSheet.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ private fun AddOrEditShareBottomSheetPreview() {
460460
AddOrEditShareBottomSheet(
461461
share = previewShare,
462462
internalLink = "internal_link",
463-
viewModel = ShareViewModel(MockShareRepository(), SavedStateHandle())
463+
viewModel = ShareViewModel(MockShareRepository(), "preview-source", SavedStateHandle())
464464
)
465465
}
466466
}

ui/src/main/java/com/nextcloud/android/common/ui/share/repository/MockShareRepository.kt

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -324,18 +324,15 @@ class MockShareRepository : ShareRepository {
324324
}
325325

326326
override suspend fun fetchShares(
327+
filterSourceTypeValue: String,
328+
limit: Int,
327329
filterSourceTypeClass: String?,
328-
filterSourceTypeValue: String?,
329-
lastShareID: String?,
330-
limit: Int
330+
lastShareID: String?
331331
): NetworkResult<List<Share>> {
332-
var result = mockShares.toList()
333-
334-
if (filterSourceTypeClass != null) {
335-
result = result.filter { share ->
336-
share.sources.any {
337-
it.clazz == filterSourceTypeClass && (filterSourceTypeValue == null || it.value == filterSourceTypeValue)
338-
}
332+
var result = mockShares.filter { share ->
333+
share.sources.any {
334+
it.value == filterSourceTypeValue &&
335+
(filterSourceTypeClass == null || it.clazz == filterSourceTypeClass)
339336
}
340337
}
341338

0 commit comments

Comments
 (0)