Skip to content

Commit 6ac162b

Browse files
authored
Merge pull request #4816 from owncloud/feature/remove_space_link
[FEATURE REQUEST] Remove space link
2 parents 2372758 + 26a16ca commit 6ac162b

19 files changed

Lines changed: 324 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ ownCloud admins and users.
5151
* Change - Stream handling and double-close in UploadFileFromContentUriWorker: [#4798](https://github.com/owncloud/android/issues/4798)
5252
* Change - Update actions dependencies to be compliant with Node24 requirements: [#4804](https://github.com/owncloud/android/pull/4804)
5353
* Change - Actions with SHA commits instead of versions: [#4815](https://github.com/owncloud/android/pull/4815)
54+
* Enhancement - Show destination folder snackbar for copy/move operations: [#4379](https://github.com/owncloud/android/issues/4379)
5455
* Enhancement - Show members of a space: [#4612](https://github.com/owncloud/android/issues/4612)
5556
* Enhancement - Add a member to a space: [#4613](https://github.com/owncloud/android/issues/4613)
5657
* Enhancement - Set emoji as space image: [#4707](https://github.com/owncloud/android/issues/4707)
@@ -59,10 +60,12 @@ ownCloud admins and users.
5960
* Enhancement - Workflow to build APK: [#4751](https://github.com/owncloud/android/pull/4751)
6061
* Enhancement - List links over a space: [#4752](https://github.com/owncloud/android/issues/4752)
6162
* Enhancement - Add a public link over a space: [#4753](https://github.com/owncloud/android/issues/4753)
63+
* Enhancement - Remove space link: [#4757](https://github.com/owncloud/android/issues/4757)
6264
* Enhancement - Copy permanent link of a space: [#4758](https://github.com/owncloud/android/issues/4758)
6365
* Enhancement - Workflow to check Conventional Commits: [#4759](https://github.com/owncloud/android/pull/4759)
6466
* Enhancement - QA Content Provider: [#4776](https://github.com/owncloud/android/pull/4776)
6567
* Enhancement - Workflow to validate source strings: [#4807](https://github.com/owncloud/android/pull/4807)
68+
* Enhancement - Resource leaks in fragment view bindings: [#4813](https://github.com/owncloud/android/issues/4813)
6669

6770
## Details
6871

@@ -151,6 +154,14 @@ ownCloud admins and users.
151154

152155
https://github.com/owncloud/android/pull/4815
153156

157+
* Enhancement - Show destination folder snackbar for copy/move operations: [#4379](https://github.com/owncloud/android/issues/4379)
158+
159+
A snackbar message has been displayed after copy or move operations with an
160+
action button that allows users to quickly navigate to the destination folder.
161+
162+
https://github.com/owncloud/android/issues/4379
163+
https://github.com/owncloud/android/pull/4802
164+
154165
* Enhancement - Show members of a space: [#4612](https://github.com/owncloud/android/issues/4612)
155166

156167
A new option to view all members of a space has been added to the bottom sheet,
@@ -161,10 +172,12 @@ ownCloud admins and users.
161172
https://github.com/owncloud/android/issues/4612
162173
https://github.com/owncloud/android/issues/4763
163174
https://github.com/owncloud/android/issues/4772
175+
https://github.com/owncloud/android/issues/4782
164176
https://github.com/owncloud/android/pull/4728
165177
https://github.com/owncloud/android/pull/4765
166178
https://github.com/owncloud/android/pull/4779
167179
https://github.com/owncloud/android/pull/4784
180+
https://github.com/owncloud/android/pull/4809
168181

169182
* Enhancement - Add a member to a space: [#4613](https://github.com/owncloud/android/issues/4613)
170183

@@ -227,6 +240,14 @@ ownCloud admins and users.
227240
https://github.com/owncloud/android/issues/4753
228241
https://github.com/owncloud/android/pull/4794
229242

243+
* Enhancement - Remove space link: [#4757](https://github.com/owncloud/android/issues/4757)
244+
245+
A new option to remove space public links from a space has been added. It will
246+
be only visible for users with proper permissions.
247+
248+
https://github.com/owncloud/android/issues/4757
249+
https://github.com/owncloud/android/pull/4816
250+
230251
* Enhancement - Copy permanent link of a space: [#4758](https://github.com/owncloud/android/issues/4758)
231252

232253
A new option to copy and share the permanent link of a space has been added next
@@ -256,6 +277,14 @@ ownCloud admins and users.
256277

257278
https://github.com/owncloud/android/pull/4807
258279

280+
* Enhancement - Resource leaks in fragment view bindings: [#4813](https://github.com/owncloud/android/issues/4813)
281+
282+
View binding references have been cleared in onDestroyView() across fragments to
283+
prevent memory leaks when fragment instances outlive their views.
284+
285+
https://github.com/owncloud/android/issues/4813
286+
https://github.com/owncloud/android/pull/4814
287+
259288
# Changelog for ownCloud Android Client [4.7.0] (2025-11-17)
260289

261290
The following sections list the changes in ownCloud Android Client 4.7.0 relevant to

changelog/unreleased/4816

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Enhancement: Remove space link
2+
3+
A new option to remove space public links from a space has been added. It will be only visible for users with proper permissions.
4+
5+
https://github.com/owncloud/android/issues/4757
6+
https://github.com/owncloud/android/pull/4816

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import com.owncloud.android.domain.files.usecases.SortFilesUseCase
8080
import com.owncloud.android.domain.files.usecases.SortFilesWithSyncInfoUseCase
8181
import com.owncloud.android.domain.files.usecases.UpdateAlreadyDownloadedFilesPathUseCase
8282
import com.owncloud.android.domain.links.usecases.AddLinkUseCase
83+
import com.owncloud.android.domain.links.usecases.RemoveLinkUseCase
8384
import com.owncloud.android.domain.members.usecases.AddMemberUseCase
8485
import com.owncloud.android.domain.members.usecases.EditMemberUseCase
8586
import com.owncloud.android.domain.members.usecases.RemoveMemberUseCase
@@ -320,4 +321,5 @@ val useCaseModule = module {
320321

321322
// Links
322323
factoryOf(::AddLinkUseCase)
324+
factoryOf(::RemoveLinkUseCase)
323325
}

owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksAdapter.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class SpaceLinksAdapter(
3838
): RecyclerView.Adapter<SpaceLinksAdapter.SpaceLinksViewHolder>() {
3939

4040
private var spaceLinks: List<OCLink> = emptyList()
41+
private var canRemoveLinks = false
4142

4243
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SpaceLinksViewHolder {
4344
val inflater = LayoutInflater.from(parent.context)
@@ -69,12 +70,21 @@ class SpaceLinksAdapter(
6970
listener.onCopyOrSendPublicLink(spaceLink.webUrl)
7071
}
7172
}
73+
74+
removePublicLinkButton.apply {
75+
contentDescription = holder.itemView.context.getString(R.string.content_description_delete_public_link, spaceLink.displayName)
76+
isVisible = canRemoveLinks
77+
setOnClickListener {
78+
listener.onRemovePublicLink(spaceLink.id, spaceLink.displayName)
79+
}
80+
}
7281
}
7382
}
7483

7584
override fun getItemCount(): Int = spaceLinks.size
7685

77-
fun setSpaceLinks(spaceLinks: List<OCLink>) {
86+
fun setSpaceLinks(spaceLinks: List<OCLink>, canRemoveLinks: Boolean) {
87+
this.canRemoveLinks = canRemoveLinks
7888
val diffCallback = SpaceLinksDiffUtil(this.spaceLinks, spaceLinks)
7989
val diffResult = DiffUtil.calculateDiff(diffCallback)
8090
this.spaceLinks = spaceLinks
@@ -87,5 +97,6 @@ class SpaceLinksAdapter(
8797

8898
interface SpaceLinksAdapterListener {
8999
fun onCopyOrSendPublicLink(publicLinkUrl: String)
100+
fun onRemovePublicLink(publicLinkId: String, publicLinkDisplayName: String)
90101
}
91102
}

owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksViewModel.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,21 @@ package com.owncloud.android.presentation.spaces.links
2323
import androidx.lifecycle.ViewModel
2424
import com.owncloud.android.domain.links.model.OCLinkType
2525
import com.owncloud.android.domain.links.usecases.AddLinkUseCase
26+
import com.owncloud.android.domain.links.usecases.RemoveLinkUseCase
2627
import com.owncloud.android.domain.spaces.model.OCSpace
2728
import com.owncloud.android.domain.utils.Event
2829
import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResult
2930
import com.owncloud.android.presentation.common.UIResult
3031
import com.owncloud.android.providers.CoroutinesDispatcherProvider
32+
import kotlinx.coroutines.flow.MutableSharedFlow
3133
import kotlinx.coroutines.flow.MutableStateFlow
34+
import kotlinx.coroutines.flow.SharedFlow
3235
import kotlinx.coroutines.flow.StateFlow
3336
import kotlinx.coroutines.flow.update
3437

3538
class SpaceLinksViewModel(
3639
private val addLinkUseCase: AddLinkUseCase,
40+
private val removeLinkUseCase: RemoveLinkUseCase,
3741
private val accountName: String,
3842
private val space: OCSpace,
3943
private val coroutineDispatcherProvider: CoroutinesDispatcherProvider
@@ -45,6 +49,9 @@ class SpaceLinksViewModel(
4549
private val _addLinkResultFlow = MutableStateFlow<Event<UIResult<Unit>>?>(null)
4650
val addLinkResultFlow: StateFlow<Event<UIResult<Unit>>?> = _addLinkResultFlow
4751

52+
private val _removeLinkResultFlow = MutableSharedFlow<UIResult<Unit>>()
53+
val removeLinkResultFlow: SharedFlow<UIResult<Unit>> = _removeLinkResultFlow
54+
4855
init {
4956
_addPublicLinkUIState.value = AddPublicLinkUIState()
5057
}
@@ -79,6 +86,19 @@ class SpaceLinksViewModel(
7986
}
8087
}
8188

89+
fun removePublicLink(linkId: String) {
90+
runUseCaseWithResult(
91+
coroutineDispatcher = coroutineDispatcherProvider.io,
92+
sharedFlow = _removeLinkResultFlow,
93+
useCase = removeLinkUseCase,
94+
useCaseParams = RemoveLinkUseCase.Params(
95+
accountName = accountName,
96+
spaceId = space.id,
97+
linkId = linkId
98+
)
99+
)
100+
}
101+
82102
fun resetViewModel() {
83103
_addLinkResultFlow.value = null
84104
_addPublicLinkUIState.value = AddPublicLinkUIState()

owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersFragment.kt

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package com.owncloud.android.presentation.spaces.members
2222

2323
import android.app.AlertDialog
2424
import android.content.Context
25+
import android.content.DialogInterface
2526
import android.os.Bundle
2627
import android.view.LayoutInflater
2728
import android.view.View
@@ -38,6 +39,7 @@ import com.owncloud.android.domain.spaces.model.OCSpace
3839
import com.owncloud.android.domain.spaces.model.SpaceMember
3940
import com.owncloud.android.extensions.avoidScreenshotsIfNeeded
4041
import com.owncloud.android.extensions.collectLatestLifecycleFlow
42+
import com.owncloud.android.extensions.showAlertDialog
4143
import com.owncloud.android.extensions.showErrorInSnackbar
4244
import com.owncloud.android.extensions.showMessageInSnackbar
4345
import com.owncloud.android.presentation.common.UIResult
@@ -77,7 +79,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
7779
private var addMemberRoles: List<OCRole> = emptyList()
7880
private var spaceMembers: List<SpaceMember> = emptyList()
7981
private var listener: SpaceMemberFragmentListener? = null
80-
private var canRemoveMembers = false
82+
private var canRemoveMembersAndLinks = false
8183
private var canEditMembers = false
8284
private var canReadMembers = false
8385
private var numberOfManagers = 1
@@ -109,7 +111,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
109111

110112
currentSpace = requireArguments().getParcelable<OCSpace>(ARG_CURRENT_SPACE) ?: return
111113
savedInstanceState?.let {
112-
canRemoveMembers = it.getBoolean(CAN_REMOVE_MEMBERS, false)
114+
canRemoveMembersAndLinks = it.getBoolean(CAN_REMOVE_MEMBERS, false)
113115
canEditMembers = it.getBoolean(CAN_EDIT_MEMBERS, false)
114116
canReadMembers = it.getBoolean(CAN_READ_MEMBERS, false)
115117
}
@@ -155,7 +157,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
155157

156158
override fun onSaveInstanceState(outState: Bundle) {
157159
super.onSaveInstanceState(outState)
158-
outState.putBoolean(CAN_REMOVE_MEMBERS, canRemoveMembers)
160+
outState.putBoolean(CAN_REMOVE_MEMBERS, canRemoveMembersAndLinks)
159161
outState.putBoolean(CAN_EDIT_MEMBERS, canEditMembers)
160162
outState.putBoolean(CAN_READ_MEMBERS, canReadMembers)
161163
}
@@ -185,6 +187,16 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
185187
listener?.copyOrSendPublicLink(publicLinkUrl, currentSpace.name)
186188
}
187189

190+
override fun onRemovePublicLink(publicLinkId: String, publicLinkDisplayName: String) {
191+
showAlertDialog(
192+
title = getString(R.string.public_link_remove_dialog_title, publicLinkDisplayName),
193+
message = getString(R.string.public_link_remove_dialog_message),
194+
positiveButtonText = getString(R.string.common_yes),
195+
positiveButtonListener = { _: DialogInterface?, _: Int -> spaceLinksViewModel.removePublicLink(publicLinkId) },
196+
negativeButtonText = getString(R.string.common_no)
197+
)
198+
}
199+
188200
private fun subscribeToViewModels() {
189201
observeRoles()
190202
observeSpaceMembers()
@@ -193,6 +205,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
193205
observeRemoveMemberResult()
194206
observeEditMemberResult()
195207
observeAddLinkResult()
208+
observeRemoveLinkResult()
196209
}
197210

198211
private fun observeRoles() {
@@ -226,7 +239,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
226239
spaceMembers = it.members
227240
addMemberRoles = it.roles
228241
if (canReadMembers) {
229-
spaceMembersAdapter.setSpaceMembers(spaceMembers, roles, canRemoveMembers, canEditMembers, numberOfManagers)
242+
showSpaceMembers()
230243
val hasLinks = it.links.isNotEmpty()
231244
showOrHideEmptyView(hasLinks)
232245
if (hasLinks) { showSpaceLinks(it.links) }
@@ -253,7 +266,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
253266
uiResult.data?.let { spacePermissions ->
254267
checkPermissions(spacePermissions)
255268
if (canReadMembers) {
256-
spaceMembersAdapter.setSpaceMembers(spaceMembers, roles, canRemoveMembers, canEditMembers, numberOfManagers)
269+
showSpaceMembers()
257270
}
258271
}
259272
}
@@ -327,9 +340,25 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
327340
}
328341
}
329342

343+
private fun observeRemoveLinkResult() {
344+
collectLatestLifecycleFlow(spaceLinksViewModel.removeLinkResultFlow) { uiResult ->
345+
when (uiResult) {
346+
is UIResult.Loading -> { }
347+
is UIResult.Success -> {
348+
showMessageInSnackbar(getString(R.string.public_link_remove_correctly))
349+
spaceMembersViewModel.getSpaceMembers()
350+
}
351+
is UIResult.Error -> {
352+
Timber.e(uiResult.error, "Failed to remove a public link from space: ${currentSpace.id}")
353+
showErrorInSnackbar(R.string.public_link_remove_failed, uiResult.error)
354+
}
355+
}
356+
}
357+
}
358+
330359
private fun checkPermissions(spacePermissions: List<String>) {
331360
val hasCreatePermission = DRIVES_CREATE_PERMISSION in spacePermissions
332-
canRemoveMembers = DRIVES_DELETE_PERMISSION in spacePermissions
361+
canRemoveMembersAndLinks = DRIVES_DELETE_PERMISSION in spacePermissions
333362
canEditMembers = DRIVES_UPDATE_PERMISSION in spacePermissions
334363
canReadMembers = DRIVES_READ_PERMISSION in spacePermissions
335364
binding.apply {
@@ -347,17 +376,30 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
347376
}
348377
}
349378

379+
private fun showSpaceMembers() {
380+
spaceMembersAdapter.setSpaceMembers(
381+
spaceMembers = spaceMembers,
382+
roles = roles,
383+
canRemoveMembers = canRemoveMembersAndLinks,
384+
canEditMembers = canEditMembers,
385+
numberOfManagers = numberOfManagers
386+
)
387+
}
388+
350389
private fun showSpaceLinks(spaceLinks: List<OCLink>) {
351390
val formatter = SimpleDateFormat(DisplayUtils.DATE_FORMAT_ISO, Locale.ROOT).apply {
352391
timeZone = TimeZone.getTimeZone("UTC")
353392
}
354-
spaceLinksAdapter.setSpaceLinks(spaceLinks.sortedByDescending { spaceLink ->
355-
if (spaceLink.createdDateTime.isNotEmpty()) {
356-
formatter.parse(spaceLink.createdDateTime)
357-
} else {
358-
Date(0)
359-
}
360-
})
393+
spaceLinksAdapter.setSpaceLinks(
394+
spaceLinks = spaceLinks.sortedByDescending { spaceLink ->
395+
if (spaceLink.createdDateTime.isNotEmpty()) {
396+
formatter.parse(spaceLink.createdDateTime)
397+
} else {
398+
Date(0)
399+
}
400+
},
401+
canRemoveLinks = canRemoveMembersAndLinks
402+
)
361403
}
362404

363405
interface SpaceMemberFragmentListener {

owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ class SpaceMembersViewModel(
109109
requiresConnection = true
110110
)
111111

112+
// Used to fetch all members and public links of a space
112113
fun getSpaceMembers() = runUseCaseWithResult(
113114
coroutineDispatcher = coroutineDispatcherProvider.io,
114115
flow = _spaceMembers,

owncloudApp/src/main/res/layout/public_link_item.xml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,27 @@
101101
android:layout_height="40dp"
102102
android:layout_gravity="center_vertical"
103103
android:layout_marginStart="@dimen/standard_half_margin"
104-
android:layout_marginEnd="@dimen/standard_margin"
104+
android:layout_marginEnd="@dimen/standard_half_margin"
105105
android:scaleType="centerCrop"
106106
android:padding="@dimen/standard_half_padding"
107107
android:background="?android:attr/selectableItemBackground"
108108
android:focusable="true"
109109
android:src="@drawable/copy_link"
110110
android:visibility="visible"/>
111111

112+
<ImageButton
113+
android:id="@+id/remove_public_link_button"
114+
android:layout_width="40dp"
115+
android:layout_height="40dp"
116+
android:layout_gravity="center_vertical"
117+
android:layout_marginEnd="@dimen/standard_half_margin"
118+
android:padding="@dimen/standard_half_padding"
119+
android:background="?android:attr/selectableItemBackground"
120+
android:focusable="true"
121+
android:src="@drawable/ic_action_delete_grey"
122+
android:visibility="gone"
123+
tools:visibility="visible" />
124+
112125
</LinearLayout>
113126

114127
<View

owncloudApp/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,10 @@
932932
<string name="public_link_default_display_name">Unnamed Link</string>
933933
<string name="public_link_add_correctly">Public link created correctly</string>
934934
<string name="public_link_add_failed">Public link could not be created</string>
935+
<string name="public_link_remove_dialog_title">Do you really want to remove the link: %1$s?</string>
936+
<string name="public_link_remove_dialog_message">Recreating the same link again is not possible</string>
937+
<string name="public_link_remove_correctly">Public link removed correctly</string>
938+
<string name="public_link_remove_failed">Public link could not be removed</string>
935939

936940
<string name="feedback_dialog_get_in_contact_description"><![CDATA[ Ask for help in our <a href=\"%1$s\"><b>forum</b></a> or contribute in our <a href=\"%2$s\"><b>GitHub repo</b></a>]]></string>
937941

0 commit comments

Comments
 (0)