Add search in messages (#804)

This commit is contained in:
Mikołaj Pich 2020-05-20 14:12:32 +02:00 committed by GitHub
parent 6cd1877af7
commit 115da64167
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 133 additions and 20 deletions

View File

@ -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"

View File

@ -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)

View File

@ -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 onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.action_menu_message_tab, menu)
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>) { override fun updateData(data: List<Message>) {
with(tabAdapter) { tabAdapter.replaceAll(data)
items = data.toMutableList()
notifyDataSetChanged()
}
} }
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
} }

View File

@ -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()
}
}
} }

View File

@ -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)

View 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>

View File

@ -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"

View 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>

View File

@ -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>

View File

@ -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-->