Merge pull request #4674 from nextcloud/issue-4453-4657-improve-main-search

Fixing and improving main search
This commit is contained in:
Marcel Hibbe 2025-02-06 10:43:13 +00:00 committed by GitHub
commit b7477658ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 142 additions and 47 deletions

View File

@ -66,6 +66,9 @@
<option name="composableFile" value="true" /> <option name="composableFile" value="true" />
<option name="previewFile" value="true" /> <option name="previewFile" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true"> <inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" /> <option name="composableFile" value="true" />
<option name="previewFile" value="true" /> <option name="previewFile" value="true" />

View File

@ -194,6 +194,7 @@ class ContactItem(
} }
companion object { companion object {
const val VIEW_TYPE = FlexibleItemViewType.CONTACT_ITEM
private const val FULLY_OPAQUE: Float = 1.0f private const val FULLY_OPAQUE: Float = 1.0f
private const val SEMI_TRANSPARENT: Float = 0.38f private const val SEMI_TRANSPARENT: Float = 0.38f
} }

View File

@ -6,14 +6,13 @@
*/ */
package com.nextcloud.talk.adapters.items package com.nextcloud.talk.adapters.items
class FlexibleItemViewType { object FlexibleItemViewType {
companion object { const val CONVERSATION_ITEM: Int = 1120391230
const val CONVERSATION_ITEM: Int = 1120391230 const val LOAD_MORE_RESULTS_ITEM: Int = 1120391231
const val LOAD_MORE_RESULTS_ITEM: Int = 1120391231 const val MESSAGE_RESULT_ITEM: Int = 1120391232
const val MESSAGE_RESULT_ITEM: Int = 1120391232 const val MESSAGES_TEXT_HEADER_ITEM: Int = 1120391233
const val MESSAGES_TEXT_HEADER_ITEM: Int = 1120391233 const val POLL_RESULT_HEADER_ITEM: Int = 1120391234
const val POLL_RESULT_HEADER_ITEM: Int = 1120391234 const val POLL_RESULT_VOTER_ITEM: Int = 1120391235
const val POLL_RESULT_VOTER_ITEM: Int = 1120391235 const val POLL_RESULT_VOTERS_OVERVIEW_ITEM: Int = 1120391236
const val POLL_RESULT_VOTERS_OVERVIEW_ITEM: Int = 1120391236 const val CONTACT_ITEM: Int = 2131558687
}
} }

View File

@ -12,6 +12,11 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
interface ContactsRepository { interface ContactsRepository {
suspend fun getContacts(searchQuery: String?, shareTypes: List<String>): AutocompleteOverall suspend fun getContacts(searchQuery: String?, shareTypes: List<String>): AutocompleteOverall
suspend fun createRoom(roomType: String, sourceType: String, userId: String, conversationName: String?): RoomOverall suspend fun createRoom(
roomType: String,
sourceType: String?,
userId: String,
conversationName: String?
): RoomOverall
fun getImageUri(avatarId: String, requestBigSize: Boolean): String fun getImageUri(avatarId: String, requestBigSize: Boolean): String
} }

View File

@ -45,7 +45,7 @@ class ContactsRepositoryImpl(
override suspend fun createRoom( override suspend fun createRoom(
roomType: String, roomType: String,
sourceType: String, sourceType: String?,
userId: String, userId: String,
conversationName: String? conversationName: String?
): RoomOverall { ): RoomOverall {

View File

@ -87,7 +87,7 @@ class ContactsViewModel @Inject constructor(
} }
@Suppress("Detekt.TooGenericExceptionCaught") @Suppress("Detekt.TooGenericExceptionCaught")
fun createRoom(roomType: String, sourceType: String, userId: String, conversationName: String?) { fun createRoom(roomType: String, sourceType: String?, userId: String, conversationName: String?) {
viewModelScope.launch { viewModelScope.launch {
try { try {
val room = repository.createRoom( val room = repository.createRoom(

View File

@ -70,6 +70,7 @@ import com.nextcloud.talk.account.WebViewLoginActivity
import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.activities.CallActivity import com.nextcloud.talk.activities.CallActivity
import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.adapters.items.ContactItem
import com.nextcloud.talk.adapters.items.ConversationItem import com.nextcloud.talk.adapters.items.ConversationItem
import com.nextcloud.talk.adapters.items.GenericTextHeaderItem import com.nextcloud.talk.adapters.items.GenericTextHeaderItem
import com.nextcloud.talk.adapters.items.LoadMoreResultsItem import com.nextcloud.talk.adapters.items.LoadMoreResultsItem
@ -80,6 +81,9 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.contacts.ContactsActivityCompose import com.nextcloud.talk.contacts.ContactsActivityCompose
import com.nextcloud.talk.contacts.ContactsUiState
import com.nextcloud.talk.contacts.ContactsViewModel
import com.nextcloud.talk.contacts.RoomUiState
import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel
import com.nextcloud.talk.data.network.NetworkMonitor import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
@ -95,6 +99,9 @@ import com.nextcloud.talk.messagesearch.MessageSearchHelper
import com.nextcloud.talk.messagesearch.MessageSearchHelper.MessageSearchResults import com.nextcloud.talk.messagesearch.MessageSearchHelper.MessageSearchResults
import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.conversations.ConversationEnums import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import com.nextcloud.talk.settings.SettingsActivity import com.nextcloud.talk.settings.SettingsActivity
import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment
@ -178,6 +185,9 @@ class ConversationsListActivity :
@Inject @Inject
lateinit var networkMonitor: NetworkMonitor lateinit var networkMonitor: NetworkMonitor
@Inject
lateinit var contactsViewModel: ContactsViewModel
lateinit var conversationsListViewModel: ConversationsListViewModel lateinit var conversationsListViewModel: ConversationsListViewModel
override val appBarLayoutType: AppBarLayoutType override val appBarLayoutType: AppBarLayoutType
@ -317,6 +327,7 @@ class ConversationsListActivity :
showSearchOrToolbar() showSearchOrToolbar()
} }
@Suppress("LongMethod")
private fun initObservers() { private fun initObservers() {
this.lifecycleScope.launch { this.lifecycleScope.launch {
networkMonitor.isOnline.onEach { isOnline -> networkMonitor.isOnline.onEach { isOnline ->
@ -387,6 +398,63 @@ class ConversationsListActivity :
setConversationList(list) setConversationList(list)
}.collect() }.collect()
} }
lifecycleScope.launch {
contactsViewModel.roomViewState.onEach { state ->
when (state) {
is RoomUiState.Success -> {
val conversation = state.conversation
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation?.token)
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(chatIntent)
}
else -> {}
}
}.collect()
}
lifecycleScope.launch {
contactsViewModel.contactsViewState.onEach { state ->
when (state) {
is ContactsUiState.Success -> {
if (state.contacts.isNullOrEmpty()) return@onEach
val userItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
val actorTypeConverter = EnumActorTypeConverter()
var genericTextHeaderItem: GenericTextHeaderItem
for (autocompleteUser in state.contacts) {
val headerTitle = resources!!.getString(R.string.nc_user)
if (!callHeaderItems.containsKey(headerTitle)) {
genericTextHeaderItem = GenericTextHeaderItem(headerTitle, viewThemeUtils)
callHeaderItems[headerTitle] = genericTextHeaderItem
}
val participant = Participant()
participant.actorId = autocompleteUser.id
participant.actorType = actorTypeConverter.getFromString(autocompleteUser.source)
participant.displayName = autocompleteUser.label
val contactItem = ContactItem(
participant,
currentUser!!,
callHeaderItems[headerTitle],
viewThemeUtils
)
userItems.add(contactItem)
}
searchableConversationItems.addAll(userItems)
}
else -> {}
}
}.collect()
}
} }
private fun setConversationList(list: List<ConversationModel>) { private fun setConversationList(list: List<ConversationModel>) {
@ -411,6 +479,9 @@ class ConversationsListActivity :
intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1) intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1)
) )
fetchOpenConversations(apiVersion) fetchOpenConversations(apiVersion)
// Get users
fetchUsers()
} }
private fun hasFilterEnabled(): Boolean { private fun hasFilterEnabled(): Boolean {
@ -944,40 +1015,45 @@ class ConversationsListActivity :
) )
) { ) {
val openConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList() val openConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
// openConversationsQueryDisposable = ncApi.getOpenConversations( openConversationsQueryDisposable = ncApi.getOpenConversations(
// credentials, credentials,
// ApiUtils.getUrlForOpenConversations(apiVersion, currentUser!!.baseUrl!!) ApiUtils.getUrlForOpenConversations(apiVersion, currentUser!!.baseUrl!!),
// ) ""
// .subscribeOn(Schedulers.io()) )
// .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io())
// .subscribe({ (ocs): RoomsOverall -> .observeOn(AndroidSchedulers.mainThread())
// for (conversation in ocs!!.data!!) { .subscribe({ (ocs): RoomsOverall ->
// val headerTitle = resources!!.getString(R.string.openConversations) for (conversation in ocs!!.data!!) {
// var genericTextHeaderItem: GenericTextHeaderItem val headerTitle = resources!!.getString(R.string.openConversations)
// if (!callHeaderItems.containsKey(headerTitle)) { var genericTextHeaderItem: GenericTextHeaderItem
// genericTextHeaderItem = GenericTextHeaderItem(headerTitle, viewThemeUtils) if (!callHeaderItems.containsKey(headerTitle)) {
// callHeaderItems[headerTitle] = genericTextHeaderItem genericTextHeaderItem = GenericTextHeaderItem(headerTitle, viewThemeUtils)
// } callHeaderItems[headerTitle] = genericTextHeaderItem
// val conversationItem = ConversationItem( }
// conversation, val conversationItem = ConversationItem(
// currentUser!!, ConversationModel.mapToConversationModel(conversation, currentUser!!),
// this, currentUser!!,
// callHeaderItems[headerTitle], this,
// viewThemeUtils callHeaderItems[headerTitle],
// ) viewThemeUtils
// openConversationItems.add(conversationItem) )
// } openConversationItems.add(conversationItem)
// searchableConversationItems.addAll(openConversationItems) }
// }, { throwable: Throwable -> searchableConversationItems.addAll(openConversationItems)
// Log.e(TAG, "fetchData - getRooms - ERROR", throwable) }, { throwable: Throwable ->
// handleHttpExceptions(throwable) Log.e(TAG, "fetchData - getRooms - ERROR", throwable)
// dispose(openConversationsQueryDisposable) handleHttpExceptions(throwable)
// }) { dispose(openConversationsQueryDisposable) } dispose(openConversationsQueryDisposable)
}) { dispose(openConversationsQueryDisposable) }
} else { } else {
Log.d(TAG, "no open conversations fetched because of missing capability") Log.d(TAG, "no open conversations fetched because of missing capability")
} }
} }
private fun fetchUsers() {
contactsViewModel.getContactsFromSearchParams()
}
private fun handleHttpExceptions(throwable: Throwable) { private fun handleHttpExceptions(throwable: Throwable) {
if (throwable is HttpException) { if (throwable is HttpException) {
when (throwable.code()) { when (throwable.code()) {
@ -1247,6 +1323,16 @@ class ConversationsListActivity :
ConversationItem.VIEW_TYPE -> { ConversationItem.VIEW_TYPE -> {
handleConversation((Objects.requireNonNull(item) as ConversationItem).model) handleConversation((Objects.requireNonNull(item) as ConversationItem).model)
} }
ContactItem.VIEW_TYPE -> {
val contact = item as ContactItem
contactsViewModel.createRoom(
ROOM_TYPE_ONE_ONE,
null,
contact.model.actorId!!,
null
)
}
} }
} }
return true return true
@ -1923,8 +2009,8 @@ class ConversationsListActivity :
if (results.hasMore) { if (results.hasMore) {
adapterItems.add(LoadMoreResultsItem) adapterItems.add(LoadMoreResultsItem)
} }
// add unified search result at the end of the list
adapter!!.addItems(adapter!!.mainItemCount + adapter!!.scrollableHeaders.size, adapterItems) adapter!!.addItems(0, adapterItems)
binding.recyclerView?.scrollToPosition(0) binding.recyclerView?.scrollToPosition(0)
} }
} }
@ -1965,7 +2051,7 @@ class ConversationsListActivity :
const val BOTTOM_SHEET_DELAY: Long = 2500 const val BOTTOM_SHEET_DELAY: Long = 2500
private const val KEY_SEARCH_QUERY = "ConversationsListActivity.searchQuery" private const val KEY_SEARCH_QUERY = "ConversationsListActivity.searchQuery"
const val SEARCH_DEBOUNCE_INTERVAL_MS = 300 const val SEARCH_DEBOUNCE_INTERVAL_MS = 300
const val SEARCH_MIN_CHARS = 2 const val SEARCH_MIN_CHARS = 1
const val HTTP_UNAUTHORIZED = 401 const val HTTP_UNAUTHORIZED = 401
const val HTTP_CLIENT_UPGRADE_REQUIRED = 426 const val HTTP_CLIENT_UPGRADE_REQUIRED = 426
const val CLIENT_UPGRADE_MARKET_LINK = "market://details?id=" const val CLIENT_UPGRADE_MARKET_LINK = "market://details?id="
@ -1977,5 +2063,6 @@ class ConversationsListActivity :
const val DAYS_FOR_NOTIFICATION_WARNING = 5L const val DAYS_FOR_NOTIFICATION_WARNING = 5L
const val NOTIFICATION_WARNING_DATE_NOT_SET = 0L const val NOTIFICATION_WARNING_DATE_NOT_SET = 0L
const val OFFSET_HEIGHT_DIVIDER: Int = 3 const val OFFSET_HEIGHT_DIVIDER: Int = 3
const val ROOM_TYPE_ONE_ONE = "1"
} }
} }

View File

@ -20,7 +20,7 @@ class FakeRepositoryError : ContactsRepository {
@Suppress("Detekt.TooGenericExceptionThrown") @Suppress("Detekt.TooGenericExceptionThrown")
override suspend fun createRoom( override suspend fun createRoom(
roomType: String, roomType: String,
sourceType: String, sourceType: String?,
userId: String, userId: String,
conversationName: String? conversationName: String?
): RoomOverall { ): RoomOverall {

View File

@ -19,7 +19,7 @@ class FakeRepositorySuccess : ContactsRepository {
override suspend fun createRoom( override suspend fun createRoom(
roomType: String, roomType: String,
sourceType: String, sourceType: String?,
userId: String, userId: String,
conversationName: String? conversationName: String?
): RoomOverall { ): RoomOverall {