mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-19 11:39:42 +01:00
Implement search in specific chat
Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>
This commit is contained in:
parent
d1d61e87a9
commit
b5d8f6ee95
@ -172,6 +172,11 @@
|
||||
android:name=".shareditems.activities.SharedItemsActivity"
|
||||
android:theme="@style/AppTheme"/>
|
||||
|
||||
<activity
|
||||
android:name=".messagesearch.MessageSearchActivity"
|
||||
android:theme="@style/AppTheme"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver android:name=".receivers.PackageReplacedReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
|
@ -42,7 +42,7 @@ data class MessageResultItem constructor(
|
||||
private val context: Context,
|
||||
private val currentUser: UserEntity,
|
||||
val messageEntry: SearchMessageEntry,
|
||||
private val showHeader: Boolean
|
||||
private val showHeader: Boolean = false
|
||||
) :
|
||||
AbstractFlexibleItem<MessageResultItem.ViewHolder>(),
|
||||
IFilterable<String>,
|
||||
|
@ -130,6 +130,7 @@ import com.nextcloud.talk.events.UserMentionClickEvent
|
||||
import com.nextcloud.talk.events.WebSocketCommunicationEvent
|
||||
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
|
||||
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
|
||||
import com.nextcloud.talk.messagesearch.MessageSearchActivity
|
||||
import com.nextcloud.talk.models.database.CapabilitiesUtil
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
@ -1346,6 +1347,7 @@ class ChatController(args: Bundle) :
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
if (resultCode != RESULT_OK) {
|
||||
// TODO for message search, CANCELED is fine
|
||||
Log.e(TAG, "resultCode for received intent was != ok")
|
||||
return
|
||||
}
|
||||
@ -1452,6 +1454,8 @@ class ChatController(args: Bundle) :
|
||||
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
|
||||
}
|
||||
}
|
||||
} else if (requestCode == REQUEST_CODE_MESSAGE_SEARCH) {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
@ -2469,28 +2473,32 @@ class ChatController(args: Bundle) :
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
(activity as MainActivity).resetConversationsList()
|
||||
return true
|
||||
true
|
||||
}
|
||||
R.id.conversation_video_call -> {
|
||||
startACall(false, false)
|
||||
return true
|
||||
true
|
||||
}
|
||||
R.id.conversation_voice_call -> {
|
||||
startACall(true, false)
|
||||
return true
|
||||
true
|
||||
}
|
||||
R.id.conversation_info -> {
|
||||
showConversationInfoScreen()
|
||||
return true
|
||||
true
|
||||
}
|
||||
R.id.shared_items -> {
|
||||
showSharedItems()
|
||||
return true
|
||||
true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
R.id.conversation_search -> {
|
||||
startMessageSearch()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2502,6 +2510,14 @@ class ChatController(args: Bundle) :
|
||||
activity!!.startActivity(intent)
|
||||
}
|
||||
|
||||
private fun startMessageSearch() {
|
||||
val intent = Intent(activity, MessageSearchActivity::class.java)
|
||||
intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName)
|
||||
intent.putExtra(KEY_ROOM_TOKEN, roomToken)
|
||||
intent.putExtra(KEY_USER_ENTITY, conversationUser as Parcelable)
|
||||
activity!!.startActivityForResult(intent, REQUEST_CODE_MESSAGE_SEARCH)
|
||||
}
|
||||
|
||||
private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
|
||||
val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
|
||||
val chatMessageIterator = chatMessageMap.iterator()
|
||||
@ -3087,6 +3103,7 @@ class ChatController(args: Bundle) :
|
||||
private const val AGE_THREHOLD_FOR_DELETE_MESSAGE: Int = 21600000 // (6 hours in millis = 6 * 3600 * 1000)
|
||||
private const val REQUEST_CODE_CHOOSE_FILE: Int = 555
|
||||
private const val REQUEST_CODE_SELECT_CONTACT: Int = 666
|
||||
private const val REQUEST_CODE_MESSAGE_SEARCH: Int = 777
|
||||
private const val REQUEST_RECORD_AUDIO_PERMISSION = 222
|
||||
private const val REQUEST_READ_CONTACT_PERMISSION = 234
|
||||
private const val REQUEST_CAMERA_PERMISSION = 223
|
||||
|
@ -74,7 +74,7 @@ import com.nextcloud.talk.adapters.items.MessagesTextHeaderItem;
|
||||
import com.nextcloud.talk.api.NcApi;
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||
import com.nextcloud.talk.controllers.base.BaseController;
|
||||
import com.nextcloud.talk.controllers.util.MessageSearchHelper;
|
||||
import com.nextcloud.talk.messagesearch.MessageSearchHelper;
|
||||
import com.nextcloud.talk.events.ConversationsListFetchDataEvent;
|
||||
import com.nextcloud.talk.events.EventStatus;
|
||||
import com.nextcloud.talk.interfaces.ConversationMenuInterface;
|
||||
|
@ -23,6 +23,7 @@ package com.nextcloud.talk.dagger.modules
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.nextcloud.talk.messagesearch.MessageSearchViewModel
|
||||
import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
|
||||
import dagger.Binds
|
||||
import dagger.MapKey
|
||||
@ -53,4 +54,9 @@ abstract class ViewModelModule {
|
||||
@IntoMap
|
||||
@ViewModelKey(SharedItemsViewModel::class)
|
||||
abstract fun sharedItemsViewModel(viewModel: SharedItemsViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(MessageSearchViewModel::class)
|
||||
abstract fun messageSearchViewModel(viewModel: MessageSearchViewModel): ViewModel
|
||||
}
|
||||
|
@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Álvaro Brey
|
||||
* Copyright (C) 2022 Álvaro Brey
|
||||
* Copyright (C) 2022 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.messagesearch
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.adapters.items.LoadMoreResultsItem
|
||||
import com.nextcloud.talk.adapters.items.MessageResultItem
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.controllers.ConversationsListController
|
||||
import com.nextcloud.talk.databinding.ActivityMessageSearchBinding
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.rx.SearchViewObservable.Companion.observeSearchView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class MessageSearchActivity : BaseActivity() {
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||
|
||||
private lateinit var binding: ActivityMessageSearchBinding
|
||||
private lateinit var searchView: SearchView
|
||||
|
||||
private lateinit var user: UserEntity
|
||||
|
||||
private lateinit var viewModel: MessageSearchViewModel
|
||||
|
||||
private var searchViewDisposable: Disposable? = null
|
||||
private var adapter: FlexibleAdapter<AbstractFlexibleItem<*>>? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
binding = ActivityMessageSearchBinding.inflate(layoutInflater)
|
||||
setupActionBar()
|
||||
setupSystemColors()
|
||||
setContentView(binding.root)
|
||||
|
||||
viewModel = ViewModelProvider(this, viewModelFactory)[MessageSearchViewModel::class.java]
|
||||
user = intent.getParcelableExtra(BundleKeys.KEY_USER_ENTITY)!!
|
||||
val roomToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
|
||||
viewModel.initialize(user, roomToken)
|
||||
setupStateObserver()
|
||||
}
|
||||
|
||||
private fun setupActionBar() {
|
||||
setSupportActionBar(binding.messageSearchToolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
val conversationName = intent.getStringExtra(BundleKeys.KEY_CONVERSATION_NAME)
|
||||
supportActionBar?.title = conversationName
|
||||
}
|
||||
|
||||
private fun setupSystemColors() {
|
||||
DisplayUtils.applyColorToStatusBar(
|
||||
this,
|
||||
ResourcesCompat.getColor(
|
||||
resources, R.color.appbar, null
|
||||
)
|
||||
)
|
||||
DisplayUtils.applyColorToNavigationBar(
|
||||
this.window,
|
||||
ResourcesCompat.getColor(resources, R.color.bg_default, null)
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupStateObserver() {
|
||||
viewModel.state.observe(this) { state ->
|
||||
when (state) {
|
||||
MessageSearchViewModel.EmptyState -> showEmpty()
|
||||
MessageSearchViewModel.InitialState -> showInitial()
|
||||
is MessageSearchViewModel.LoadedState -> showLoaded(state)
|
||||
MessageSearchViewModel.LoadingState -> showLoading()
|
||||
MessageSearchViewModel.ErrorState -> showError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showError() {
|
||||
Toast.makeText(this, "Error while searching", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun showLoading() {
|
||||
// TODO
|
||||
Toast.makeText(this, "LOADING", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
private fun showLoaded(state: MessageSearchViewModel.LoadedState) {
|
||||
binding.emptyContainer.emptyListView.visibility = View.GONE
|
||||
binding.messageSearchRecycler.visibility = View.VISIBLE
|
||||
setAdapterItems(state)
|
||||
}
|
||||
|
||||
private fun setAdapterItems(state: MessageSearchViewModel.LoadedState) {
|
||||
val loadMoreItems = if (state.hasMore) {
|
||||
listOf(LoadMoreResultsItem)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
val newItems =
|
||||
state.results.map { MessageResultItem(this, user, it) } + loadMoreItems
|
||||
|
||||
if (adapter != null) {
|
||||
adapter!!.updateDataSet(newItems)
|
||||
} else {
|
||||
createAdapter(newItems)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAdapter(items: List<AbstractFlexibleItem<out FlexibleViewHolder>>) {
|
||||
adapter = FlexibleAdapter(items)
|
||||
binding.messageSearchRecycler.adapter = adapter
|
||||
adapter!!.addListener(object : FlexibleAdapter.OnItemClickListener {
|
||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
||||
val item = adapter!!.getItem(position)
|
||||
if (item?.itemViewType == LoadMoreResultsItem.VIEW_TYPE) {
|
||||
viewModel.loadMore()
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun showInitial() {
|
||||
binding.messageSearchRecycler.visibility = View.GONE
|
||||
binding.emptyContainer.emptyListViewHeadline.text = "Start typing to search..."
|
||||
binding.emptyContainer.emptyListView.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun showEmpty() {
|
||||
binding.messageSearchRecycler.visibility = View.GONE
|
||||
binding.emptyContainer.emptyListViewHeadline.text = "No search results"
|
||||
binding.emptyContainer.emptyListView.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_search, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
|
||||
val menuItem = menu!!.findItem(R.id.action_search)
|
||||
searchView = menuItem.actionView as SearchView
|
||||
setupSearchView()
|
||||
menuItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
|
||||
searchView.requestFocus()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
|
||||
onBackPressed()
|
||||
return false
|
||||
}
|
||||
})
|
||||
menuItem.expandActionView()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setupSearchView() {
|
||||
searchView.queryHint = getString(R.string.nc_search_hint)
|
||||
searchViewDisposable = observeSearchView(searchView)
|
||||
.debounce { query ->
|
||||
when {
|
||||
TextUtils.isEmpty(query) -> Observable.empty()
|
||||
else -> Observable.timer(
|
||||
ConversationsListController.SEARCH_DEBOUNCE_INTERVAL_MS.toLong(),
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { newText -> viewModel.onQueryTextChange(newText) }
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
searchViewDisposable?.dispose()
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.controllers.util
|
||||
package com.nextcloud.talk.messagesearch
|
||||
|
||||
import android.util.Log
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
@ -28,9 +28,10 @@ import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.disposables.Disposable
|
||||
|
||||
class MessageSearchHelper(
|
||||
class MessageSearchHelper @JvmOverloads constructor(
|
||||
private val user: UserEntity,
|
||||
private val unifiedSearchRepository: UnifiedSearchRepository,
|
||||
private val fromRoom: String? = null
|
||||
) {
|
||||
|
||||
data class MessageSearchResults(val messages: List<SearchMessageEntry>, val hasMore: Boolean)
|
||||
@ -58,7 +59,7 @@ class MessageSearchHelper(
|
||||
|
||||
private fun doSearch(search: String, cursor: Int = 0): Observable<MessageSearchResults> {
|
||||
disposeIfPossible()
|
||||
return unifiedSearchRepository.searchMessages(user, search, cursor)
|
||||
return searchCall(search, cursor)
|
||||
.map { results ->
|
||||
previousSearch = search
|
||||
previousCursor = results.cursor
|
||||
@ -76,6 +77,29 @@ class MessageSearchHelper(
|
||||
.doOnComplete(this::disposeIfPossible)
|
||||
}
|
||||
|
||||
private fun searchCall(
|
||||
search: String,
|
||||
cursor: Int
|
||||
): Observable<UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry>> {
|
||||
return when {
|
||||
fromRoom != null -> {
|
||||
unifiedSearchRepository.searchInRoom(
|
||||
userEntity = user,
|
||||
roomToken = fromRoom,
|
||||
searchTerm = search,
|
||||
cursor = cursor
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
unifiedSearchRepository.searchMessages(
|
||||
userEntity = user,
|
||||
searchTerm = search,
|
||||
cursor = cursor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetCachedData() {
|
||||
previousSearch = null
|
||||
previousCursor = 0
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Álvaro Brey
|
||||
* Copyright (C) 2022 Álvaro Brey
|
||||
* Copyright (C) 2022 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.messagesearch
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.models.domain.SearchMessageEntry
|
||||
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Install PlantUML plugin to render this state diagram
|
||||
* @startuml
|
||||
* hide empty description
|
||||
* [*] --> InitialState
|
||||
* InitialState --> LoadingState
|
||||
* LoadingState --> EmptyState
|
||||
* LoadingState --> LoadedState
|
||||
* LoadingState --> LoadingState
|
||||
* LoadedState --> LoadingState
|
||||
* EmptyState --> LoadingState
|
||||
* LoadingState --> ErrorState
|
||||
* ErrorState --> LoadingState
|
||||
* @enduml
|
||||
*/
|
||||
class MessageSearchViewModel @Inject constructor(private val unifiedSearchRepository: UnifiedSearchRepository) :
|
||||
ViewModel() {
|
||||
|
||||
sealed class ViewState
|
||||
object InitialState : ViewState()
|
||||
object LoadingState : ViewState()
|
||||
object EmptyState : ViewState()
|
||||
object ErrorState : ViewState()
|
||||
class LoadedState(val results: List<SearchMessageEntry>, val hasMore: Boolean) : ViewState()
|
||||
|
||||
private lateinit var messageSearchHelper: MessageSearchHelper
|
||||
|
||||
private val _state: MutableLiveData<ViewState> = MutableLiveData(InitialState)
|
||||
val state: LiveData<ViewState>
|
||||
get() = _state
|
||||
|
||||
private var searchDisposable: Disposable? = null
|
||||
|
||||
fun initialize(user: UserEntity, roomToken: String) {
|
||||
messageSearchHelper = MessageSearchHelper(user, unifiedSearchRepository, roomToken)
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult") // handled by helper
|
||||
fun onQueryTextChange(newText: String) {
|
||||
if (newText.length >= MIN_CHARS_FOR_SEARCH) {
|
||||
_state.value = LoadingState
|
||||
messageSearchHelper.cancelSearch()
|
||||
messageSearchHelper.startMessageSearch(newText)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::onReceiveResults, this::onError)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult") // handled by helper
|
||||
fun loadMore() {
|
||||
_state.value = LoadingState
|
||||
messageSearchHelper.cancelSearch()
|
||||
messageSearchHelper.loadMore()
|
||||
?.subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
?.subscribe(this::onReceiveResults)
|
||||
}
|
||||
|
||||
private fun onReceiveResults(results: MessageSearchHelper.MessageSearchResults) {
|
||||
if (results.messages.isEmpty()) {
|
||||
_state.value = EmptyState
|
||||
} else {
|
||||
_state.value = LoadedState(results.messages, results.hasMore)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onError(throwable: Throwable) {
|
||||
Log.e(TAG, "onError:", throwable)
|
||||
messageSearchHelper.cancelSearch()
|
||||
_state.value = ErrorState
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = MessageSearchViewModel::class.simpleName
|
||||
private const val MIN_CHARS_FOR_SEARCH = 2
|
||||
}
|
||||
}
|
@ -18,7 +18,13 @@ interface UnifiedSearchRepository {
|
||||
limit: Int = DEFAULT_PAGE_SIZE
|
||||
): Observable<UnifiedSearchResults<SearchMessageEntry>>
|
||||
|
||||
fun searchInRoom(text: String, roomId: String): Observable<List<SearchMessageEntry>>
|
||||
fun searchInRoom(
|
||||
userEntity: UserEntity,
|
||||
roomToken: String,
|
||||
searchTerm: String,
|
||||
cursor: Int = 0,
|
||||
limit: Int = DEFAULT_PAGE_SIZE
|
||||
): Observable<UnifiedSearchResults<SearchMessageEntry>>
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_PAGE_SIZE = 5
|
||||
|
@ -48,10 +48,26 @@ class UnifiedSearchRepositoryImpl(private val api: NcApi) : UnifiedSearchReposit
|
||||
return apiObservable.map { mapToMessageResults(it.ocs?.data!!, searchTerm, limit) }
|
||||
}
|
||||
|
||||
override fun searchInRoom(text: String, roomId: String): Observable<List<SearchMessageEntry>> {
|
||||
TODO()
|
||||
override fun searchInRoom(
|
||||
userEntity: UserEntity,
|
||||
roomToken: String,
|
||||
searchTerm: String,
|
||||
cursor: Int,
|
||||
limit: Int
|
||||
): Observable<UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry>> {
|
||||
val apiObservable = api.performUnifiedSearch(
|
||||
ApiUtils.getCredentials(userEntity.username, userEntity.token),
|
||||
ApiUtils.getUrlForUnifiedSearch(userEntity.baseUrl, PROVIDER_TALK_MESSAGE_CURRENT),
|
||||
searchTerm,
|
||||
fromUrlForRoom(roomToken),
|
||||
limit,
|
||||
cursor
|
||||
)
|
||||
return apiObservable.map { mapToMessageResults(it.ocs?.data!!, searchTerm, limit) }
|
||||
}
|
||||
|
||||
private fun fromUrlForRoom(roomToken: String) = "/call/$roomToken"
|
||||
|
||||
companion object {
|
||||
private const val PROVIDER_TALK_MESSAGE = "talk-message"
|
||||
private const val PROVIDER_TALK_MESSAGE_CURRENT = "talk-message-current"
|
||||
|
63
app/src/main/res/layout/activity_message_search.xml
Normal file
63
app/src/main/res/layout/activity_message_search.xml
Normal file
@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Álvaro Brey
|
||||
~ Copyright (C) 2022 Álvaro Brey <alvaro.brey@nextcloud.com>
|
||||
~ Copyright (C) 2022 Nextcloud GmbH
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/bg_default"
|
||||
tools:context=".messagesearch.MessageSearchActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/message_search_appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/message_search_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/appbar"
|
||||
android:theme="?attr/actionBarPopupTheme"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:navigationIconTint="@color/fontAppbar"
|
||||
app:popupTheme="@style/appActionBarPopupMenu"
|
||||
app:titleTextColor="@color/fontAppbar"
|
||||
tools:title="@string/nc_app_product_name">
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/emptyContainer"
|
||||
layout="@layout/empty_list"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/message_search_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:listitem="@layout/rv_item_search_message" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -20,6 +20,7 @@
|
||||
License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/empty_list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@ -60,5 +61,7 @@
|
||||
android:paddingTop="@dimen/standard_half_padding"
|
||||
android:paddingBottom="@dimen/standard_half_padding"
|
||||
android:text=""
|
||||
tools:visibility="visible"
|
||||
tools:text="Empty list view text"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
@ -35,15 +35,22 @@
|
||||
android:title="@string/nc_conversation_menu_video_call"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/conversation_search"
|
||||
android:icon="@drawable/ic_search_white_24dp"
|
||||
android:orderInCategory="2"
|
||||
android:title="@string/nc_search"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/conversation_info"
|
||||
android:orderInCategory="1"
|
||||
android:orderInCategory="3"
|
||||
android:title="@string/nc_conversation_menu_conversation_info"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/shared_items"
|
||||
android:orderInCategory="1"
|
||||
android:orderInCategory="4"
|
||||
android:title="@string/nc_shared_items"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
30
app/src/main/res/menu/menu_search.xml
Normal file
30
app/src/main/res/menu/menu_search.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Álvaro Brey
|
||||
~ Copyright (C) 2022 Álvaro Brey
|
||||
~ Copyright (C) 2022 Nextcloud GmbH
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_search"
|
||||
android:icon="@drawable/ic_search_white_24dp"
|
||||
android:title="@string/nc_search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="always|collapseActionView" />
|
||||
</menu>
|
@ -273,14 +273,14 @@
|
||||
<string name="dnd">Do not disturb</string>
|
||||
<string name="away">Away</string>
|
||||
<string name="invisible">Invisible</string>
|
||||
<string translatable="false" name="divider">—</string>
|
||||
<string translatable="false" name="default_emoji">😃</string>
|
||||
<string translatable="false" name="emoji_thumbsUp">👍</string>
|
||||
<string translatable="false" name="emoji_thumbsDown">👎</string>
|
||||
<string translatable="false" name="emoji_heart">❤️</string>
|
||||
<string translatable="false" name="emoji_confused">😯</string>
|
||||
<string translatable="false" name="emoji_sad">😢</string>
|
||||
<string translatable="false" name="emoji_more">More emojis</string>
|
||||
<string name="divider" translatable="false">—</string>
|
||||
<string name="default_emoji" translatable="false">😃</string>
|
||||
<string name="emoji_thumbsUp" translatable="false">👍</string>
|
||||
<string name="emoji_thumbsDown" translatable="false">👎</string>
|
||||
<string name="emoji_heart" translatable="false">❤️</string>
|
||||
<string name="emoji_confused" translatable="false">😯</string>
|
||||
<string name="emoji_sad" translatable="false">😢</string>
|
||||
<string name="emoji_more" translatable="false">More emojis</string>
|
||||
<string name="dontClear">Don\'t clear</string>
|
||||
<string name="today">Today</string>
|
||||
<string name="thirtyMinutes">30 minutes</string>
|
||||
@ -525,5 +525,6 @@
|
||||
<string name="call_without_notification">Call without notification</string>
|
||||
<string name="messages">Messages</string>
|
||||
<string name="load_more_results">Load more results</string>
|
||||
<string name="nc_search_hint">Search…</string>
|
||||
|
||||
</resources>
|
||||
|
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.controllers.util
|
||||
package com.nextcloud.talk.messagesearch
|
||||
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.models.domain.SearchMessageEntry
|
@ -41,7 +41,14 @@ class FakeUnifiedSearchRepository : UnifiedSearchRepository {
|
||||
return Observable.just(response)
|
||||
}
|
||||
|
||||
override fun searchInRoom(text: String, roomId: String): Observable<List<SearchMessageEntry>> {
|
||||
TODO("Not yet implemented")
|
||||
override fun searchInRoom(
|
||||
userEntity: UserEntity,
|
||||
roomToken: String,
|
||||
searchTerm: String,
|
||||
cursor: Int,
|
||||
limit: Int
|
||||
): Observable<UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry>> {
|
||||
lastRequestedCursor = cursor
|
||||
return Observable.just(response)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user