Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog/unreleased/4791
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not add a new entry in calens for this enhancement. Instead, I'd add the issue and PR ids to 4754 file (since this is not a new feature at all) 🤔

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Make share search minimum length capability-driven

The minimum number of characters required to search users for sharing and members for joining has been made configurable through the files_sharing.search_min_lenght capability.

https://github.com/owncloud/android/pull/4791
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ static public class ProviderTableMeta implements BaseColumns {
public static final String CAPABILITIES_SHARING_PUBLIC_MULTIPLE = "sharing_public_multiple";
public static final String CAPABILITIES_SHARING_PUBLIC_SUPPORTS_UPLOAD_ONLY = "supports_upload_only";
public static final String CAPABILITIES_SHARING_RESHARING = "sharing_resharing";
public static final String CAPABILITIES_SHARING_SEARCH_MIN_LENGTH = "search_min_length";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed. db/ProviderMeta.java is deprecated as it belongs to the old database

public static final String CAPABILITIES_SHARING_FEDERATION_OUTGOING = "sharing_federation_outgoing";
public static final String CAPABILITIES_SHARING_FEDERATION_INCOMING = "sharing_federation_incoming";
public static final String CAPABILITIES_FILES_BIGFILECHUNKING = "files_bigfilechunking";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ class ReleaseNotesViewModel(
subtitle = R.string.release_notes_4_8_0_subtitle_spaces_permanent_links,
type = ReleaseNoteType.ENHANCEMENT
),
ReleaseNote(
title = R.string.release_notes_4_8_0_title_search_minimum_length,
subtitle = R.string.release_notes_4_8_0_subtitle_search_minimum_length,
type = ReleaseNoteType.ENHANCEMENT
),
Comment on lines +61 to +65
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release note not needed in this case 😄

ReleaseNote(
title = R.string.release_notes_bugfixes_title,
subtitle = R.string.release_notes_bugfixes_subtitle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import android.provider.BaseColumns
import android.widget.Toast
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.domain.capabilities.model.OCCapability
import com.owncloud.android.presentation.authentication.AccountUtils
import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
import com.owncloud.android.domain.sharing.sharees.GetShareesAsyncUseCase
Expand Down Expand Up @@ -134,6 +135,12 @@ class UsersAndGroupsSearchProvider : ContentProvider() {
accountName = account.name
)
)
val minSearchLength = capabilities?.getFilesSharingSearchMinLength()
?: OCCapability.DEFAULT_FILES_SHARING_SEARCH_MIN_LENGTH

if (userQuery.length < minSearchLength) {
return MatrixCursor(COLUMNS)
}
Comment on lines +138 to +143
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could yo try to refactor this? Detekt workflow (link) is failing in the CI system because the method searchUsersAndGroups exceeds 100 lines


val getShareesAsyncUseCase: GetShareesAsyncUseCase by inject()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.owncloud.android.R
import com.owncloud.android.databinding.AddMemberFragmentBinding
import com.owncloud.android.domain.capabilities.model.OCCapability
import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
import com.owncloud.android.domain.members.model.OCMember
import com.owncloud.android.domain.members.model.OCMemberType
import com.owncloud.android.domain.roles.model.OCRole
Expand All @@ -41,6 +43,7 @@ import com.owncloud.android.extensions.collectLatestLifecycleFlow
import com.owncloud.android.extensions.showErrorInSnackbar
import com.owncloud.android.presentation.common.UIResult
import com.owncloud.android.utils.DisplayUtils
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.activityViewModel
import org.koin.core.parameter.parametersOf
import timber.log.Timber
Expand All @@ -51,6 +54,7 @@ import java.util.TimeZone
class AddMemberFragment: Fragment(), SearchMembersAdapter.SearchMembersAdapterListener {
private var _binding: AddMemberFragmentBinding? = null
private val binding get() = _binding!!
private val getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase by inject()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you inject a use case directly into the fragment, we are not following the Clean Architecture. In these cases, we use the ViewModels, which contain the methods that execute the use cases.

In this case, if you want to run getStoredCapabilitiesUseCase you would need to use the CapabilityViewModel in the fragment (or create a new method in SpaceMembersViewModel that runs that use case)

private val capabilityViewModel: CapabilityViewModel by viewModel {
        parametersOf(
            requireArguments().getString(ARG_ACCOUNT_NAME)
        )
}

Let us know if you have any questions about the app architecture 😄


private val spaceMembersViewModel: SpaceMembersViewModel by activityViewModel {
parametersOf(
Expand All @@ -66,6 +70,7 @@ class AddMemberFragment: Fragment(), SearchMembersAdapter.SearchMembersAdapterLi

private var editMode = false
private var selectedMemberId = ""
private var minimumSearchLength = OCCapability.DEFAULT_FILES_SHARING_SEARCH_MIN_LENGTH

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = AddMemberFragmentBinding.inflate(inflater, container, false)
Expand All @@ -91,6 +96,7 @@ class AddMemberFragment: Fragment(), SearchMembersAdapter.SearchMembersAdapterLi
}
}

initSearchMinLength()
subscribeToViewModels()

binding.searchBar.apply {
Expand All @@ -101,7 +107,11 @@ class AddMemberFragment: Fragment(), SearchMembersAdapter.SearchMembersAdapterLi
override fun onQueryTextSubmit(query: String): Boolean = true

override fun onQueryTextChange(newText: String): Boolean {
if (newText.length > 2) { spaceMembersViewModel.searchMembers(newText) } else { spaceMembersViewModel.clearSearch() }
if (newText.length >= minimumSearchLength) {
spaceMembersViewModel.searchMembers(newText)
} else {
spaceMembersViewModel.clearSearch()
}
Comment on lines +110 to +114
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not use more than line of code for this block. My suggestion:

if (newText.length >= minimumSearchLength) { spaceMembersViewModel.searchMembers(newText) } else { spaceMembersViewModel.clearSearch() }

return true
}
})
Expand All @@ -111,7 +121,7 @@ class AddMemberFragment: Fragment(), SearchMembersAdapter.SearchMembersAdapterLi
private fun showOrHideEmptyView(hasMembers: Boolean) {
binding.membersRecyclerView.isVisible = hasMembers
binding.emptyDataParent.apply {
val shouldShow = !hasMembers && binding.searchBar.query.length > 2
val shouldShow = !hasMembers && binding.searchBar.query.length >= minimumSearchLength
root.isVisible = shouldShow
if (shouldShow) {
listEmptyDatasetIcon.setImageResource(R.drawable.ic_share_generic_white)
Expand All @@ -121,6 +131,13 @@ class AddMemberFragment: Fragment(), SearchMembersAdapter.SearchMembersAdapterLi
}
}

private fun initSearchMinLength() {
val accountName = requireArguments().getString(ARG_ACCOUNT_NAME) ?: return
minimumSearchLength = getStoredCapabilitiesUseCase(
GetStoredCapabilitiesUseCase.Params(accountName = accountName)
)?.getFilesSharingSearchMinLength() ?: OCCapability.DEFAULT_FILES_SHARING_SEARCH_MIN_LENGTH
}

Comment on lines +134 to +140
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the comment above about Clean Architecture, this method is not longer needed. Instead, you must run the ViewModel method

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
requireActivity().setTitle(if (editMode) R.string.members_edit else R.string.members_add)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1478,6 +1478,8 @@ class FileContentProvider(val executors: Executors = Executors()) : ContentProvi
ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_SUPPORTS_UPLOAD_ONLY
capabilityProjectionMap[ProviderTableMeta.CAPABILITIES_SHARING_RESHARING] =
ProviderTableMeta.CAPABILITIES_SHARING_RESHARING
capabilityProjectionMap[ProviderTableMeta.CAPABILITIES_SHARING_SEARCH_MIN_LENGTH] =
ProviderTableMeta.CAPABILITIES_SHARING_SEARCH_MIN_LENGTH
Comment on lines +1481 to +1482
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as one comment above. These lines are not needed because it's the old database (the new one is OwnCloudDatabase.kt and data/ProviderMeta.java)

capabilityProjectionMap[ProviderTableMeta.CAPABILITIES_SHARING_FEDERATION_OUTGOING] =
ProviderTableMeta.CAPABILITIES_SHARING_FEDERATION_OUTGOING
capabilityProjectionMap[ProviderTableMeta.CAPABILITIES_SHARING_FEDERATION_INCOMING] =
Expand Down
2 changes: 2 additions & 0 deletions owncloudApp/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,8 @@
<string name="release_notes_4_8_0_subtitle_space_membership">Infinite Scale users can see all members of a space and manage them with right permissions</string>
<string name="release_notes_4_8_0_title_spaces_permanent_links">Permanent links for spaces</string>
<string name="release_notes_4_8_0_subtitle_spaces_permanent_links">Infinite Scale users can now get a permanent link for a space and share it with other members</string>
<string name="release_notes_4_8_0_title_search_minimum_length">Configurable minimum search length</string>
<string name="release_notes_4_8_0_subtitle_search_minimum_length">The minimum number of characters before searching users and members is now configured by server capabilities</string>
Comment on lines +750 to +751
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove these strings - release note not needed in this case


<!-- Open in web -->
<string name="ic_action_open_in_web">Open in web</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ data class RemoteCapability(
var filesSharingPublicMultiple: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
var filesSharingPublicSupportsUploadOnly: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
var filesSharingResharing: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
var filesSharingSearchMinLength: Int? = null,
var filesSharingFederationOutgoing: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
var filesSharingFederationIncoming: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
var filesSharingUserProfilePicture: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ data class CapabilityResponse(
filesSharingPublicExpireDateEnforced = CapabilityBooleanType.fromBooleanValue(
capabilities?.fileSharingCapabilities?.fileSharingPublic?.fileSharingPublicExpireDate?.enforced
),
filesSharingSearchMinLength = capabilities?.fileSharingCapabilities?.fileSharingSearchMinLenght
?: capabilities?.fileSharingCapabilities?.fileSharingSearchMinLength,
Comment on lines +80 to +81
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it OK? The same code appears before and after ?:

capabilities?.fileSharingCapabilities?.fileSharingSearchMinLenght ?: capabilities?.fileSharingCapabilities?.fileSharingSearchMinLength

filesBigFileChunking = CapabilityBooleanType.fromBooleanValue(capabilities?.fileCapabilities?.bigfilechunking),
filesUndelete = CapabilityBooleanType.fromBooleanValue(capabilities?.fileCapabilities?.undelete),
filesVersioning = CapabilityBooleanType.fromBooleanValue(capabilities?.fileCapabilities?.versioning),
Expand Down Expand Up @@ -123,6 +125,10 @@ data class FileSharingCapabilities(
val fileSharingPublic: FileSharingPublic?,
@Json(name = "resharing")
val fileSharingReSharing: Boolean?,
@Json(name = "search_min_lenght")
val fileSharingSearchMinLenght: Int?,
@Json(name = "search_min_length")
val fileSharingSearchMinLength: Int?,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated fields? 🤔

@Json(name = "federation")
val fileSharingFederation: FileSharingFederation?,
@Json(name = "user")
Expand Down
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unit tests for response objects are not maintained at this moment in the project. In the future, we will have to discuss about them but right now we only create unit tests for datasources and repositories. So, you can remove this file 😄

Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* ownCloud Android Library is available under MIT license
*
* Copyright (C) 2026 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
* OR OTHER DEALINGS IN THE SOFTWARE.
*/

package com.owncloud.android.lib.resources.status.responses

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Test

class CapabilityResponseTest {

private val adapter: JsonAdapter<CapabilityResponse> = Moshi.Builder()
.build()
.adapter(CapabilityResponse::class.java)

@Test
fun `toRemoteCapability maps misspelled search min length field`() {
val capabilityResponse = adapter.fromJson(
"""
{
"version": {
"major": 10,
"minor": 0,
"micro": 0,
"string": "10.0.0",
"edition": "community"
},
"capabilities": {
"files_sharing": {
"search_min_lenght": 5
}
}
}
""".trimIndent()
)

assertNotNull(capabilityResponse)
assertEquals(5, capabilityResponse?.toRemoteCapability()?.filesSharingSearchMinLength)
}

@Test
fun `toRemoteCapability maps correctly spelled search min length field`() {
val capabilityResponse = adapter.fromJson(
"""
{
"version": {
"major": 10,
"minor": 0,
"micro": 0,
"string": "10.0.0",
"edition": "community"
},
"capabilities": {
"files_sharing": {
"search_min_length": 4
}
}
}
""".trimIndent()
)

assertNotNull(capabilityResponse)
assertEquals(4, capabilityResponse?.toRemoteCapability()?.filesSharingSearchMinLength)
}
}
Loading
Loading