forked from github/wulkanowy-mirror
Add search in messages (#804)
This commit is contained in:
parent
6cd1877af7
commit
115da64167
@ -39,7 +39,8 @@
|
|||||||
android:name=".ui.modules.main.MainActivity"
|
android:name=".ui.modules.main.MainActivity"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:label="@string/main_title"
|
android:label="@string/main_title"
|
||||||
android:theme="@style/WulkanowyTheme.NoActionBar" />
|
android:theme="@style/WulkanowyTheme.NoActionBar"
|
||||||
|
android:windowSoftInputMode="adjustPan" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.message.send.SendMessageActivity"
|
android:name=".ui.modules.message.send.SendMessageActivity"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
|
@ -5,6 +5,8 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.SortedList
|
||||||
|
import androidx.recyclerview.widget.SortedListAdapterCallback
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.repositories.message.MessageFolder
|
import io.github.wulkanowy.data.repositories.message.MessageFolder
|
||||||
@ -15,11 +17,41 @@ import javax.inject.Inject
|
|||||||
class MessageTabAdapter @Inject constructor() :
|
class MessageTabAdapter @Inject constructor() :
|
||||||
RecyclerView.Adapter<MessageTabAdapter.ItemViewHolder>() {
|
RecyclerView.Adapter<MessageTabAdapter.ItemViewHolder>() {
|
||||||
|
|
||||||
var items = mutableListOf<Message>()
|
|
||||||
|
|
||||||
var onClickListener: (Message, position: Int) -> Unit = { _, _ -> }
|
var onClickListener: (Message, position: Int) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
override fun getItemCount() = items.size
|
private val items = SortedList(Message::class.java, object :
|
||||||
|
SortedListAdapterCallback<Message>(this) {
|
||||||
|
|
||||||
|
override fun compare(item1: Message, item2: Message): Int {
|
||||||
|
return item2.date.compareTo(item1.date)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Message?, newItem: Message?): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areItemsTheSame(item1: Message, item2: Message): Boolean {
|
||||||
|
return item1 == item2
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fun replaceAll(models: List<Message>) {
|
||||||
|
items.beginBatchedUpdates()
|
||||||
|
for (i in items.size() - 1 downTo 0) {
|
||||||
|
val model = items.get(i)
|
||||||
|
if (model !in models) {
|
||||||
|
items.remove(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items.addAll(models)
|
||||||
|
items.endBatchedUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateItem(position: Int, item: Message) {
|
||||||
|
items.updateItemAt(position, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||||
ItemMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
ItemMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package io.github.wulkanowy.ui.modules.message.tab
|
package io.github.wulkanowy.ui.modules.message.tab
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
@ -39,7 +42,12 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val isViewEmpty
|
override val isViewEmpty
|
||||||
get() = tabAdapter.items.isEmpty()
|
get() = tabAdapter.itemCount == 0
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
@ -65,18 +73,28 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<Message>) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
with(tabAdapter) {
|
super.onCreateOptionsMenu(menu, inflater)
|
||||||
items = data.toMutableList()
|
inflater.inflate(R.menu.action_menu_message_tab, menu)
|
||||||
notifyDataSetChanged()
|
|
||||||
|
val searchView = menu.findItem(R.id.action_search).actionView as SearchView
|
||||||
|
searchView.queryHint = getString(R.string.all_search_hint)
|
||||||
|
searchView.maxWidth = Int.MAX_VALUE
|
||||||
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String) = false
|
||||||
|
override fun onQueryTextChange(query: String): Boolean {
|
||||||
|
presenter.onSearchQueryTextChange(query)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateData(data: List<Message>) {
|
||||||
|
tabAdapter.replaceAll(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateItem(item: Message, position: Int) {
|
override fun updateItem(item: Message, position: Int) {
|
||||||
with(tabAdapter) {
|
tabAdapter.updateItem(position, item)
|
||||||
items[position] = item
|
|
||||||
notifyItemChanged(position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
@ -87,6 +105,10 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
|||||||
binding.messageTabSwipe.isEnabled = enable
|
binding.messageTabSwipe.isEnabled = enable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun resetListPosition() {
|
||||||
|
binding.messageTabRecycler.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
override fun showContent(show: Boolean) {
|
||||||
binding.messageTabRecycler.visibility = if (show) VISIBLE else INVISIBLE
|
binding.messageTabRecycler.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.message.tab
|
package io.github.wulkanowy.ui.modules.message.tab
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.repositories.message.MessageFolder
|
import io.github.wulkanowy.data.repositories.message.MessageFolder
|
||||||
import io.github.wulkanowy.data.repositories.message.MessageRepository
|
import io.github.wulkanowy.data.repositories.message.MessageRepository
|
||||||
@ -9,6 +10,7 @@ import io.github.wulkanowy.ui.base.BasePresenter
|
|||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||||
import io.github.wulkanowy.utils.SchedulersProvider
|
import io.github.wulkanowy.utils.SchedulersProvider
|
||||||
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -25,6 +27,10 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
|
|
||||||
private lateinit var lastError: Throwable
|
private lateinit var lastError: Throwable
|
||||||
|
|
||||||
|
private var lastSearchQuery = ""
|
||||||
|
|
||||||
|
private var messages = emptyList<Message>()
|
||||||
|
|
||||||
fun onAttachView(view: MessageTabView, folder: MessageFolder) {
|
fun onAttachView(view: MessageTabView, folder: MessageFolder) {
|
||||||
super.onAttachView(view)
|
super.onAttachView(view)
|
||||||
view.initView()
|
view.initView()
|
||||||
@ -89,12 +95,8 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
.subscribe({
|
.subscribe({
|
||||||
Timber.i("Loading $folder message result: Success")
|
Timber.i("Loading $folder message result: Success")
|
||||||
view?.run {
|
messages = it
|
||||||
showEmpty(it.isEmpty())
|
onSearchQueryTextChange(lastSearchQuery)
|
||||||
showContent(it.isNotEmpty())
|
|
||||||
showErrorView(false)
|
|
||||||
updateData(it)
|
|
||||||
}
|
|
||||||
analytics.logEvent("load_messages", "items" to it.size, "folder" to folder.name)
|
analytics.logEvent("load_messages", "items" to it.size, "folder" to folder.name)
|
||||||
}) {
|
}) {
|
||||||
Timber.i("Loading $folder message result: An exception occurred")
|
Timber.i("Loading $folder message result: An exception occurred")
|
||||||
@ -113,4 +115,33 @@ class MessageTabPresenter @Inject constructor(
|
|||||||
} else showError(message, error)
|
} else showError(message, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
fun onSearchQueryTextChange(query: String) {
|
||||||
|
lastSearchQuery = query
|
||||||
|
|
||||||
|
val lowerCaseQuery = query.toLowerCase()
|
||||||
|
val filteredList = mutableListOf<Message>()
|
||||||
|
messages.forEach {
|
||||||
|
if (lowerCaseQuery in it.subject.toLowerCase() ||
|
||||||
|
lowerCaseQuery in it.sender.toLowerCase() ||
|
||||||
|
lowerCaseQuery in it.recipient.toLowerCase() ||
|
||||||
|
lowerCaseQuery in it.date.toFormattedString()
|
||||||
|
) {
|
||||||
|
filteredList.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData(filteredList)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateData(data: List<Message>) {
|
||||||
|
view?.run {
|
||||||
|
showEmpty(data.isEmpty())
|
||||||
|
showContent(data.isNotEmpty())
|
||||||
|
showErrorView(false)
|
||||||
|
updateData(data)
|
||||||
|
resetListPosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ interface MessageTabView : BaseView {
|
|||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
|
fun resetListPosition()
|
||||||
|
|
||||||
fun updateData(data: List<Message>)
|
fun updateData(data: List<Message>)
|
||||||
|
|
||||||
fun updateItem(item: Message, position: Int)
|
fun updateItem(item: Message, position: Int)
|
||||||
|
9
app/src/main/res/drawable/ic_search.xml
Normal file
9
app/src/main/res/drawable/ic_search.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFF"
|
||||||
|
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
|
||||||
|
</vector>
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/mainContainer"
|
android:id="@+id/mainContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
@ -8,7 +9,8 @@
|
|||||||
android:id="@+id/mainToolbar"
|
android:id="@+id/mainToolbar"
|
||||||
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
app:contentInsetStartWithNavigation="0dp" />
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/mainFragmentContainer"
|
android:id="@+id/mainFragmentContainer"
|
||||||
|
11
app/src/main/res/menu/action_menu_message_tab.xml
Normal file
11
app/src/main/res/menu/action_menu_message_tab.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<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"
|
||||||
|
android:title="@string/all_search"
|
||||||
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
|
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||||
|
app:showAsAction="collapseActionView|ifRoom" />
|
||||||
|
</menu>
|
@ -298,6 +298,7 @@
|
|||||||
<string name="all_subject">Przedmiot</string>
|
<string name="all_subject">Przedmiot</string>
|
||||||
<string name="all_prev">Poprzedni</string>
|
<string name="all_prev">Poprzedni</string>
|
||||||
<string name="all_next">Następny</string>
|
<string name="all_next">Następny</string>
|
||||||
|
<string name="all_search">Szukaj</string>
|
||||||
<!--Timetable Widget-->
|
<!--Timetable Widget-->
|
||||||
<string name="widget_timetable_no_items">Brak lekcji</string>
|
<string name="widget_timetable_no_items">Brak lekcji</string>
|
||||||
<string name="widget_timetable_theme_title">Wybierz motyw</string>
|
<string name="widget_timetable_theme_title">Wybierz motyw</string>
|
||||||
|
@ -329,6 +329,8 @@
|
|||||||
<string name="all_subject">Subject</string>
|
<string name="all_subject">Subject</string>
|
||||||
<string name="all_prev">Prev</string>
|
<string name="all_prev">Prev</string>
|
||||||
<string name="all_next">Next</string>
|
<string name="all_next">Next</string>
|
||||||
|
<string name="all_search">Search</string>
|
||||||
|
<string name="all_search_hint">Search...</string>
|
||||||
|
|
||||||
|
|
||||||
<!--Timetable Widget-->
|
<!--Timetable Widget-->
|
||||||
|
Loading…
x
Reference in New Issue
Block a user