mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 16:57:50 +00:00
add search bottom bar ui
This commit is contained in:
parent
2b26876c4c
commit
61ff68b532
@ -39,7 +39,6 @@ import org.session.libsession.utilities.TextSecurePreferences
|
|||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher
|
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.*
|
import org.thoughtcrime.securesms.conversation.v2.dialogs.*
|
||||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
|
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
|
||||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate
|
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate
|
||||||
@ -291,7 +290,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||||
ConversationMenuHelper.onPrepareOptionsMenu(menu, menuInflater, thread, this) { onOptionsItemSelected(it) }
|
ConversationMenuHelper.onPrepareOptionsMenu(menu, menuInflater, thread, threadID, this) { onOptionsItemSelected(it) }
|
||||||
super.onPrepareOptionsMenu(menu)
|
super.onPrepareOptionsMenu(menu)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -750,4 +749,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
draftDB.insertDrafts(threadID, drafts)
|
draftDB.insertDrafts(threadID, drafts)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
// region Search
|
||||||
|
fun onSearchQueryUpdated(query: String?) {
|
||||||
|
adapter.onSearchQueryUpdated(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
}
|
}
|
@ -6,17 +6,26 @@ import android.graphics.PorterDuffColorFilter
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.appcompat.widget.SearchView.OnQueryTextListener
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import kotlinx.android.synthetic.main.activity_conversation_v2.*
|
||||||
|
import kotlinx.android.synthetic.main.session_logo_action_bar_content.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.ExpirationUtil
|
import org.session.libsession.utilities.ExpirationUtil
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.components.ConversationSearchBottomBar
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
|
||||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||||
|
|
||||||
object ConversationMenuHelper {
|
object ConversationMenuHelper {
|
||||||
|
|
||||||
fun onPrepareOptionsMenu(menu: Menu, inflater: MenuInflater, thread: Recipient, context: Context, onOptionsItemSelected: (MenuItem) -> Unit) {
|
fun onPrepareOptionsMenu(menu: Menu, inflater: MenuInflater, thread: Recipient, threadId: Long, context: Context, onOptionsItemSelected: (MenuItem) -> Unit) {
|
||||||
// Prepare
|
// Prepare
|
||||||
menu.clear()
|
menu.clear()
|
||||||
val isOpenGroup = thread.isOpenGroupRecipient
|
val isOpenGroup = thread.isOpenGroupRecipient
|
||||||
@ -61,6 +70,51 @@ object ConversationMenuHelper {
|
|||||||
} else {
|
} else {
|
||||||
inflater.inflate(R.menu.menu_conversation_unmuted, menu)
|
inflater.inflate(R.menu.menu_conversation_unmuted, menu)
|
||||||
}
|
}
|
||||||
// TODO: Implement search
|
// Search
|
||||||
|
val searchViewItem = menu.findItem(R.id.menu_search)
|
||||||
|
val searchView = searchViewItem.actionView as SearchView
|
||||||
|
val searchViewModel:SearchViewModel = ViewModelProvider(context as ConversationActivityV2).get(SearchViewModel::class.java)
|
||||||
|
val queryListener = object : OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
|
searchViewModel.onQueryUpdated(query, threadId)
|
||||||
|
context.searchBottomBar.showLoading()
|
||||||
|
context.onSearchQueryUpdated(query)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(query: String): Boolean {
|
||||||
|
searchViewModel.onQueryUpdated(query, threadId)
|
||||||
|
context.searchBottomBar.showLoading()
|
||||||
|
context.onSearchQueryUpdated(query)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
searchViewItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||||
|
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||||
|
searchView.setOnQueryTextListener(queryListener)
|
||||||
|
searchViewModel.onSearchOpened()
|
||||||
|
context.searchBottomBar.visibility = View.VISIBLE
|
||||||
|
context.searchBottomBar.setData(0, 0)
|
||||||
|
context.inputBar.visibility = View.GONE
|
||||||
|
context.supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||||
|
for (i in 0 until menu.size()) {
|
||||||
|
if (menu.getItem(i) != searchViewItem) {
|
||||||
|
menu.getItem(i).isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||||
|
searchView.setOnQueryTextListener(null)
|
||||||
|
searchViewModel.onSearchClosed()
|
||||||
|
context.searchBottomBar.visibility = View.GONE
|
||||||
|
context.inputBar.visibility = View.VISIBLE
|
||||||
|
context.supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
context.onSearchQueryUpdated(null)
|
||||||
|
context.invalidateOptionsMenu()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.v2.search
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import kotlinx.android.synthetic.main.view_search_bottom_bar.view.*
|
||||||
|
import network.loki.messenger.R
|
||||||
|
|
||||||
|
|
||||||
|
class SearchBottomBar : LinearLayout {
|
||||||
|
private var eventListener: EventListener? = null
|
||||||
|
|
||||||
|
// region Lifecycle
|
||||||
|
constructor(context: Context) : super(context) { initialize() }
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() }
|
||||||
|
|
||||||
|
fun initialize() {
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.view_search_bottom_bar, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setData(position: Int, count: Int) {
|
||||||
|
searchProgressWheel.visibility = GONE
|
||||||
|
searchUp.setOnClickListener { v: View? ->
|
||||||
|
if (eventListener != null) {
|
||||||
|
eventListener!!.onSearchMoveUpPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
searchDown.setOnClickListener { v: View? ->
|
||||||
|
if (eventListener != null) {
|
||||||
|
eventListener!!.onSearchMoveDownPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
searchPosition.text = resources.getString(R.string.ConversationActivity_search_position, position + 1, count)
|
||||||
|
} else {
|
||||||
|
searchPosition.setText(R.string.ConversationActivity_no_results)
|
||||||
|
}
|
||||||
|
setViewEnabled(searchUp, position < count - 1)
|
||||||
|
setViewEnabled(searchDown, position > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showLoading() {
|
||||||
|
searchProgressWheel.visibility = VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setViewEnabled(view: View, enabled: Boolean) {
|
||||||
|
view.isEnabled = enabled
|
||||||
|
view.alpha = if (enabled) 1f else 0.25f
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEventListener(eventListener: EventListener?) {
|
||||||
|
this.eventListener = eventListener
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventListener {
|
||||||
|
fun onSearchMoveUpPressed()
|
||||||
|
fun onSearchMoveDownPressed()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.v2.search
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import org.session.libsession.utilities.Debouncer
|
||||||
|
import org.session.libsession.utilities.Util.runOnMain
|
||||||
|
import org.session.libsession.utilities.concurrent.SignalExecutors
|
||||||
|
import org.thoughtcrime.securesms.contacts.ContactAccessor
|
||||||
|
import org.thoughtcrime.securesms.database.CursorList
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.search.SearchRepository
|
||||||
|
import org.thoughtcrime.securesms.search.model.MessageResult
|
||||||
|
import org.thoughtcrime.securesms.util.CloseableLiveData
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
|
||||||
|
class SearchViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
private val searchRepository: SearchRepository
|
||||||
|
private val result: CloseableLiveData<SearchResult>
|
||||||
|
private val debouncer: Debouncer
|
||||||
|
private var firstSearch = false
|
||||||
|
private var searchOpen = false
|
||||||
|
private var activeQuery: String? = null
|
||||||
|
private var activeThreadId: Long = 0
|
||||||
|
val searchResults: LiveData<SearchResult>
|
||||||
|
get() = result
|
||||||
|
|
||||||
|
fun onQueryUpdated(query: String, threadId: Long) {
|
||||||
|
if (firstSearch && query.length < 2) {
|
||||||
|
result.postValue(SearchResult(CursorList.emptyList(), 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (query == activeQuery) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateQuery(query, threadId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMissingResult() {
|
||||||
|
if (activeQuery != null) {
|
||||||
|
updateQuery(activeQuery!!, activeThreadId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMoveUp() {
|
||||||
|
debouncer.clear()
|
||||||
|
val messages = result.value!!.getResults() as CursorList<MessageResult?>
|
||||||
|
val position = Math.min(result.value!!.position + 1, messages.size - 1)
|
||||||
|
result.setValue(SearchResult(messages, position), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMoveDown() {
|
||||||
|
debouncer.clear()
|
||||||
|
val messages = result.value!!.getResults() as CursorList<MessageResult?>
|
||||||
|
val position = Math.max(result.value!!.position - 1, 0)
|
||||||
|
result.setValue(SearchResult(messages, position), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSearchOpened() {
|
||||||
|
searchOpen = true
|
||||||
|
firstSearch = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSearchClosed() {
|
||||||
|
searchOpen = false
|
||||||
|
debouncer.clear()
|
||||||
|
result.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
result.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateQuery(query: String, threadId: Long) {
|
||||||
|
activeQuery = query
|
||||||
|
activeThreadId = threadId
|
||||||
|
debouncer.publish {
|
||||||
|
firstSearch = false
|
||||||
|
searchRepository.query(query, threadId) { messages: CursorList<MessageResult?> ->
|
||||||
|
runOnMain {
|
||||||
|
if (searchOpen && query == activeQuery) {
|
||||||
|
result.setValue(SearchResult(messages, 0))
|
||||||
|
} else {
|
||||||
|
messages.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchResult(private val results: CursorList<MessageResult?>, val position: Int) : Closeable {
|
||||||
|
|
||||||
|
fun getResults(): List<MessageResult?> {
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
results.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val context = application.applicationContext
|
||||||
|
result = CloseableLiveData()
|
||||||
|
debouncer = Debouncer(500)
|
||||||
|
searchRepository = SearchRepository(context,
|
||||||
|
DatabaseFactory.getSearchDatabase(context),
|
||||||
|
DatabaseFactory.getThreadDatabase(context),
|
||||||
|
ContactAccessor.getInstance(),
|
||||||
|
SignalExecutors.SERIAL)
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,13 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true" />
|
android:layout_alignParentBottom="true" />
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
|
||||||
|
android:id="@+id/searchBottomBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/additionalContentContainer"
|
android:id="@+id/additionalContentContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
68
app/src/main/res/layout/view_search_bottom_bar.xml
Normal file
68
app/src/main/res/layout/view_search_bottom_bar.xml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/searchBottomBarConstraintLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/input_bar_height"
|
||||||
|
android:background="@color/compose_view_background"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:background="@color/separator" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/searchUp"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_baseline_keyboard_arrow_up_24"
|
||||||
|
android:tint="@color/accent"
|
||||||
|
tools:ignore="UseAppTint" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/searchDown"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_baseline_keyboard_arrow_down_24"
|
||||||
|
android:tint="@color/accent"
|
||||||
|
tools:ignore="UseAppTint" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/searchPosition"
|
||||||
|
style="@style/Signal.Text.Body"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:text="37 of 73" />
|
||||||
|
|
||||||
|
<com.github.ybq.android.spinkit.SpinKitView
|
||||||
|
style="@style/SpinKitView.DoubleBounce"
|
||||||
|
android:id="@+id/searchProgressWheel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:background="@color/compose_view_background"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
Loading…
x
Reference in New Issue
Block a user