Skip to content

Commit 6487fe5

Browse files
authored
feat: delete a share on an oCIS server (#4974)
* feat: show trashbin icon when user has permission Signed-off-by: Jorge Aguado Recio <jaguado@izertis.com> * feat: show confirmation dialog when removing a share Signed-off-by: Jorge Aguado Recio <jaguado@izertis.com> * feat: implement methods and network operation to remove a share on an oCIS server Signed-off-by: Jorge Aguado Recio <jaguado@izertis.com> * test: create new tests for OCShareRepositoryTest and OCRemoteShareDataSourceTest Signed-off-by: Jorge Aguado Recio <jaguado@izertis.com> * chore: add calens file Signed-off-by: Jorge Aguado Recio <jaguado@izertis.com> * refactor: update content description for remove share button Signed-off-by: Jorge Aguado Recio <jaguado@izertis.com> * refactor: improve remove share confirmation message Signed-off-by: Jorge Aguado Recio <jaguado@izertis.com> * feat: add header to remove share confirmation dialog Signed-off-by: Jorge Aguado Recio <jaguado@izertis.com> --------- Signed-off-by: Jorge Aguado Recio <jaguado@izertis.com>
1 parent 717ab22 commit 6487fe5

66 files changed

Lines changed: 365 additions & 57 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎changelog/unreleased/4974‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Enhancement: Remove a share over a file or a folder on an oCIS server
2+
3+
A new option to remove a share over a file or a folder on an oCIS has been added.
4+
It will be only visible for users with proper permissions.
5+
6+
https://github.com/owncloud/android/issues/4940
7+
https://github.com/owncloud/android/pull/4974

‎owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ import com.owncloud.android.domain.sharing.shares.usecases.GetGraphSharesAsyncUs
100100
import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUseCase
101101
import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase
102102
import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase
103+
import com.owncloud.android.domain.sharing.shares.usecases.RemoveGraphShareAsyncUseCase
103104
import com.owncloud.android.domain.spaces.usecases.CreateSpaceUseCase
104105
import com.owncloud.android.domain.spaces.usecases.DisableSpaceUseCase
105106
import com.owncloud.android.domain.spaces.usecases.EditSpaceImageUseCase
@@ -241,6 +242,7 @@ val useCaseModule = module {
241242
factoryOf(::GetShareesAsyncUseCase)
242243
factoryOf(::GetSharesAsLiveDataUseCase)
243244
factoryOf(::RefreshSharesFromServerAsyncUseCase)
245+
factoryOf(::RemoveGraphShareAsyncUseCase)
244246

245247
// Spaces
246248
factoryOf(::CreateSpaceUseCase)

‎owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphShareFragment.kt‎

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package com.owncloud.android.presentation.sharing
2222

23+
import android.app.AlertDialog
2324
import android.content.Context
2425
import android.os.Bundle
2526
import android.view.LayoutInflater
@@ -32,6 +33,8 @@ import com.owncloud.android.R
3233
import com.owncloud.android.databinding.MembersFragmentBinding
3334
import com.owncloud.android.domain.files.model.OCFile
3435
import com.owncloud.android.domain.roles.model.OCRole
36+
import com.owncloud.android.domain.sharing.shares.model.MemberPermission
37+
import com.owncloud.android.extensions.avoidScreenshotsIfNeeded
3538
import com.owncloud.android.extensions.collectLatestLifecycleFlow
3639
import com.owncloud.android.extensions.showErrorInSnackbar
3740
import com.owncloud.android.extensions.showMessageInSnackbar
@@ -40,7 +43,7 @@ import org.koin.androidx.viewmodel.ext.android.activityViewModel
4043
import org.koin.core.parameter.parametersOf
4144
import timber.log.Timber
4245

43-
class GraphShareFragment : Fragment() {
46+
class GraphShareFragment : Fragment(), GraphSharesAdapter.GraphSharesAdapterListener {
4447
private var _binding: MembersFragmentBinding? = null
4548
private val binding get() = _binding!!
4649

@@ -55,6 +58,7 @@ class GraphShareFragment : Fragment() {
5558

5659
private var roles: List<OCRole> = emptyList()
5760
private var listener: GraphShareFragmentListener? = null
61+
private var canRemoveShares: Boolean = false
5862

5963
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
6064
_binding = MembersFragmentBinding.inflate(inflater, container, false)
@@ -65,7 +69,7 @@ class GraphShareFragment : Fragment() {
6569
super.onViewCreated(view, savedInstanceState)
6670
binding.membersTitle.text = getString(R.string.share_with_people_title)
6771

68-
graphSharesAdapter = GraphSharesAdapter()
72+
graphSharesAdapter = GraphSharesAdapter(this)
6973
binding.membersRecyclerView.apply {
7074
layoutManager = LinearLayoutManager(requireContext())
7175
adapter = graphSharesAdapter
@@ -104,10 +108,23 @@ class GraphShareFragment : Fragment() {
104108
_binding = null
105109
}
106110

111+
override fun onRemoveShare(share: MemberPermission) {
112+
AlertDialog.Builder(requireContext())
113+
.setIcon(R.drawable.ic_warning)
114+
.setTitle(getString(R.string.confirmation_remove_private_share_title))
115+
.setMessage(getString(R.string.confirmation_remove_share_message, share.displayName))
116+
.setPositiveButton(getString(R.string.common_yes)) { _, _ -> graphShareViewModel.removeGraphShare(share.id) }
117+
.setNegativeButton(getString(R.string.common_no)) { dialog, _ -> dialog.dismiss() }
118+
.show()
119+
.avoidScreenshotsIfNeeded()
120+
}
121+
107122
private fun subscribeToViewModels() {
108123
observeRoles()
109124
observeShares()
125+
observeSpacePermissions()
110126
observeAddShareResult()
127+
observeRemoveShareResult()
111128
}
112129

113130
private fun observeRoles() {
@@ -139,7 +156,7 @@ class GraphShareFragment : Fragment() {
139156
val hasMembers = it.members.isNotEmpty()
140157
binding.membersRecyclerView.isVisible = hasMembers
141158
binding.noSharesMessage.isVisible = !hasMembers
142-
graphSharesAdapter.setShares(it.members, it.roles)
159+
graphSharesAdapter.setShares(it.members, it.roles, canRemoveShares)
143160
binding.swipeRefreshMembers.isRefreshing = false
144161
}
145162
}
@@ -154,6 +171,28 @@ class GraphShareFragment : Fragment() {
154171
}
155172
}
156173

174+
private fun observeSpacePermissions() {
175+
collectLatestLifecycleFlow(graphShareViewModel.spacePermissions) { event ->
176+
event?.let {
177+
when (val uiResult = event.peekContent()) {
178+
is UIResult.Success -> {
179+
uiResult.data?.let { spacePermissions ->
180+
checkPermissions(spacePermissions)
181+
}
182+
}
183+
is UIResult.Loading -> { }
184+
is UIResult.Error -> {
185+
Timber.e(uiResult.error, "Failed to retrieve space permissions")
186+
}
187+
}
188+
}
189+
}
190+
}
191+
192+
private fun checkPermissions(spacePermissions: List<String>) {
193+
canRemoveShares = DRIVES_DELETE_PERMISSION in spacePermissions
194+
}
195+
157196
private fun observeAddShareResult() {
158197
collectLatestLifecycleFlow(graphShareViewModel.addShareResultFlow) { event ->
159198
event?.peekContent()?.let { uiResult ->
@@ -169,13 +208,30 @@ class GraphShareFragment : Fragment() {
169208
}
170209
}
171210

211+
private fun observeRemoveShareResult() {
212+
collectLatestLifecycleFlow(graphShareViewModel.removeShareResultFlow) { uiResult ->
213+
when (uiResult) {
214+
is UIResult.Loading -> { }
215+
is UIResult.Success -> {
216+
showMessageInSnackbar(getString(R.string.share_remove_correctly))
217+
graphShareViewModel.getGraphShares()
218+
}
219+
is UIResult.Error -> {
220+
showErrorInSnackbar(R.string.share_remove_failed, uiResult.error)
221+
Timber.e(uiResult.error, "Failed to remove a graph share")
222+
}
223+
}
224+
}
225+
}
226+
172227
interface GraphShareFragmentListener {
173228
fun addGraphShare(file: OCFile, accountName: String)
174229
}
175230

176231
companion object {
177232
private const val ARG_FILE = "FILE"
178233
private const val ARG_ACCOUNT_NAME = "ACCOUNT_NAME"
234+
private const val DRIVES_DELETE_PERMISSION = "libre.graph/driveItem/permissions/delete"
179235

180236
fun newInstance(file: OCFile, accountName: String): GraphShareFragment {
181237
val args = Bundle().apply {

‎owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphShareViewModel.kt‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ import com.owncloud.android.domain.roles.model.OCRole
3333
import com.owncloud.android.domain.roles.usecases.GetRolesAsyncUseCase
3434
import com.owncloud.android.domain.sharing.shares.usecases.AddGraphShareAsyncUseCase
3535
import com.owncloud.android.domain.sharing.shares.usecases.GetGraphSharesAsyncUseCase
36+
import com.owncloud.android.domain.sharing.shares.usecases.RemoveGraphShareAsyncUseCase
3637
import com.owncloud.android.domain.sharing.shares.model.OCPermissions
3738
import com.owncloud.android.domain.user.usecases.GetUserIdAsyncUseCase
39+
import com.owncloud.android.domain.spaces.usecases.GetSpacePermissionsAsyncUseCase
3840
import com.owncloud.android.domain.utils.Event
3941
import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResult
4042
import com.owncloud.android.presentation.common.UIResult
@@ -55,6 +57,8 @@ class GraphShareViewModel(
5557
private val getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase,
5658
private val searchMembersUseCase: SearchMembersUseCase,
5759
private val getUserIdAsyncUseCase: GetUserIdAsyncUseCase,
60+
private val getSpacePermissionsAsyncUseCase: GetSpacePermissionsAsyncUseCase,
61+
private val removeGraphShareAsyncUseCase: RemoveGraphShareAsyncUseCase,
5862
private val accountName: String,
5963
private val file: OCFile,
6064
private val coroutineDispatcherProvider: CoroutinesDispatcherProvider,
@@ -78,9 +82,15 @@ class GraphShareViewModel(
7882
private val _addShareResultFlow = MutableStateFlow<Event<UIResult<Unit>>?>(null)
7983
val addShareResultFlow: StateFlow<Event<UIResult<Unit>>?> = _addShareResultFlow
8084

85+
private val _removeShareResultFlow = MutableSharedFlow<UIResult<Unit>>()
86+
val removeShareResultFlow: SharedFlow<UIResult<Unit>> = _removeShareResultFlow
87+
8188
private var searchJob: Job? = null
8289
var capabilities: OCCapability? = null
8390

91+
private val _spacePermissions = MutableStateFlow<Event<UIResult<List<String>>>?>(null)
92+
val spacePermissions: StateFlow<Event<UIResult<List<String>>>?> = _spacePermissions
93+
8494
init {
8595
runUseCaseWithResult(
8696
coroutineDispatcher = coroutineDispatcherProvider.io,
@@ -98,6 +108,24 @@ class GraphShareViewModel(
98108
viewModelScope.launch(coroutineDispatcherProvider.io) {
99109
capabilities = getStoredCapabilitiesUseCase(GetStoredCapabilitiesUseCase.Params(accountName))
100110
}
111+
getSpacePermissions()
112+
}
113+
114+
fun getSpacePermissions() {
115+
val spaceId = file.spaceId
116+
if (spaceId == null) {
117+
_spacePermissions.update { Event(UIResult.Error(error = IncompleteFileDataException())) }
118+
return
119+
}
120+
121+
runUseCaseWithResult(
122+
coroutineDispatcher = coroutineDispatcherProvider.io,
123+
flow = _spacePermissions,
124+
useCase = getSpacePermissionsAsyncUseCase,
125+
useCaseParams = GetSpacePermissionsAsyncUseCase.Params(accountName = accountName, spaceId = spaceId),
126+
showLoading = false,
127+
requiresConnection = true
128+
)
101129
}
102130

103131
fun getGraphShares() {
@@ -144,6 +172,29 @@ class GraphShareViewModel(
144172
)
145173
}
146174

175+
fun removeGraphShare(shareId: String) {
176+
val spaceId = file.spaceId
177+
val itemId = file.remoteId
178+
if (spaceId == null || itemId == null) {
179+
viewModelScope.launch(coroutineDispatcherProvider.io) {
180+
_removeShareResultFlow.emit(UIResult.Error(error = IncompleteFileDataException()))
181+
}
182+
return
183+
}
184+
185+
runUseCaseWithResult(
186+
coroutineDispatcher = coroutineDispatcherProvider.io,
187+
sharedFlow = _removeShareResultFlow,
188+
useCase = removeGraphShareAsyncUseCase,
189+
useCaseParams = RemoveGraphShareAsyncUseCase.Params(
190+
accountName = accountName,
191+
spaceId = spaceId,
192+
itemId = itemId,
193+
shareId = shareId,
194+
)
195+
)
196+
}
197+
147198
fun searchMembers(query: String) {
148199
searchJob?.cancel()
149200
searchJob = viewModelScope.launch(coroutineDispatcherProvider.io) {

‎owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphSharesAdapter.kt‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ import com.owncloud.android.domain.sharing.shares.model.MemberPermission
3333
import com.owncloud.android.utils.DisplayUtils
3434
import com.owncloud.android.utils.PreferenceUtils
3535

36-
class GraphSharesAdapter : RecyclerView.Adapter<GraphSharesAdapter.GraphShareViewHolder>() {
36+
class GraphSharesAdapter(
37+
private val listener: GraphSharesAdapterListener,
38+
) : RecyclerView.Adapter<GraphSharesAdapter.GraphShareViewHolder>() {
3739

3840
private var shares: List<MemberPermission> = emptyList()
3941
private var rolesMap: Map<String, String> = emptyMap()
42+
private var canRemoveShares = false
4043

4144
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GraphShareViewHolder {
4245
val inflater = LayoutInflater.from(parent.context)
@@ -58,6 +61,14 @@ class GraphSharesAdapter : RecyclerView.Adapter<GraphSharesAdapter.GraphShareVie
5861
)
5962
memberRole.text = roleNames.joinToString(", ")
6063

64+
removeMemberButton.apply {
65+
contentDescription = holder.itemView.context.getString(R.string.content_description_delete_share, share.displayName)
66+
isVisible = canRemoveShares
67+
setOnClickListener {
68+
listener.onRemoveShare(share)
69+
}
70+
}
71+
6172
val hasExpirationDate = share.expirationDateTime != null
6273
expirationCalendarIcon.isVisible = hasExpirationDate
6374
expirationDate.isVisible = hasExpirationDate
@@ -71,18 +82,24 @@ class GraphSharesAdapter : RecyclerView.Adapter<GraphSharesAdapter.GraphShareVie
7182

7283
override fun getItemCount(): Int = shares.size
7384

74-
fun setShares(shares: List<MemberPermission>, roles: List<OCRole>) {
85+
fun setShares(shares: List<MemberPermission>, roles: List<OCRole>, canRemoveShares: Boolean) {
86+
val hasUserPermissionsChanged = this.canRemoveShares != canRemoveShares
87+
this.canRemoveShares = canRemoveShares
7588
this.rolesMap = roles.associate { it.id to it.displayName }
7689
val sortedShares = shares.sortedWith(
7790
compareBy<MemberPermission> { it.isGroup }
7891
.thenBy { it.displayName.lowercase() }
7992
)
80-
val diffResult = DiffUtil.calculateDiff(GraphSharesDiffUtil(this.shares, sortedShares))
93+
val diffResult = DiffUtil.calculateDiff(GraphSharesDiffUtil(this.shares, sortedShares, hasUserPermissionsChanged))
8194
this.shares = sortedShares
8295
diffResult.dispatchUpdatesTo(this)
8396
}
8497

8598
class GraphShareViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
8699
val binding = MemberItemBinding.bind(itemView)
87100
}
101+
102+
interface GraphSharesAdapterListener {
103+
fun onRemoveShare(share: MemberPermission)
104+
}
88105
}

‎owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphSharesDiffUtil.kt‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.owncloud.android.domain.sharing.shares.model.MemberPermission
2626
class GraphSharesDiffUtil(
2727
private val oldList: List<MemberPermission>,
2828
private val newList: List<MemberPermission>,
29+
private val hasUserPermissionsChanged: Boolean = false,
2930
) : DiffUtil.Callback() {
3031

3132
override fun getOldListSize(): Int = oldList.size
@@ -36,5 +37,5 @@ class GraphSharesDiffUtil(
3637
oldList[oldItemPosition].id == newList[newItemPosition].id
3738

3839
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
39-
oldList[oldItemPosition] == newList[newItemPosition]
40+
oldList[oldItemPosition] == newList[newItemPosition] && !hasUserPermissionsChanged
4041
}

‎owncloudApp/src/main/res/values-ar/strings.xml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@
239239
<string name="confirmation_remove_account_alert">هل ترغب حقًا في إزالة حساب %1$s؟</string>
240240
<string name="confirmation_remove_public_share_title">إزالة الرابط</string>
241241
<string name="confirmation_remove_private_share_title">إزالة المشاركة الخاصة</string>
242-
<string name="confirmation_remove_share_message">هل ترغب حقًا في إزالة مشاركة %1$s؟</string>
243242
<string name="remove_success_msg">نجحت الإزالة</string>
244243
<string name="remove_fail_msg">فشلت الإزالة</string>
245244
<string name="rename_dialog_title">أدخل اسمًا جديدًا</string>

‎owncloudApp/src/main/res/values-ast/strings.xml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@
144144
<string name="confirmation_remove_file_alert">¿De xuru quies desaniciar %1$s?</string>
145145
<string name="confirmation_remove_folder_alert">¿De xuru quies desaniciar %1$s y los sos conteníos?</string>
146146
<string name="confirmation_remove_local">Namái llocal</string>
147-
<string name="confirmation_remove_share_message">¿De xuru quies desaniciar %1$s?</string>
148147
<string name="remove_success_msg">Desaniciu correutu</string>
149148
<string name="remove_fail_msg">Fallu nel desaniciu </string>
150149
<string name="rename_dialog_title">Introduz un nome nuevu</string>

‎owncloudApp/src/main/res/values-az/strings.xml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@
9393
<string name="confirmation_remove_file_alert">Siz həqiqətən %1$s silmək istəyirsiniz?</string>
9494
<string name="confirmation_remove_folder_alert">Siz həqiqətəndə %1$s və onun kontentini silmək istəyirsiniz?</string>
9595
<string name="confirmation_remove_local">Yalnız daxili</string>
96-
<string name="confirmation_remove_share_message">Siz həqiqətən %1$s silmək istəyirsiniz?</string>
9796
<string name="remove_success_msg">Silmə uğurlu oldu</string>
9897
<string name="remove_fail_msg">Silmək mümkün olmadı</string>
9998
<string name="rename_dialog_title">Yeni adı daxil edin</string>

‎owncloudApp/src/main/res/values-b+zh+Hans/strings.xml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,6 @@
299299
<string name="confirmation_remove_account_alert_camera_uploads">此帐户附加了相机上传功能,删除该帐户将禁用相机上传功能。 您真的要删除帐户%1$s吗?</string>
300300
<string name="confirmation_remove_public_share_title">移除連結</string>
301301
<string name="confirmation_remove_private_share_title">移除私有分享</string>
302-
<string name="confirmation_remove_share_message">您真的要删除%1$s分享吗?</string>
303302
<string name="remove_success_msg">成功删除</string>
304303
<string name="remove_fail_msg">刪除失敗</string>
305304
<string name="rename_dialog_title">輸入新名稱</string>

0 commit comments

Comments
 (0)