forked from github/szkolny
[UI/Messages] Restore search string after closing a message. Add message counter.
This commit is contained in:
parent
b48b5589f4
commit
1e8fb6a9ae
@ -15,6 +15,7 @@ import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.*
|
||||
import android.text.style.CharacterStyle
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.StrikethroughSpan
|
||||
import android.text.style.StyleSpan
|
||||
@ -552,28 +553,46 @@ fun CharSequence?.asBoldSpannable(): Spannable {
|
||||
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return spannable
|
||||
}
|
||||
fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignoreCase: Boolean = false, ignoreDiacritics: Boolean = false): Spannable {
|
||||
fun CharSequence.asSpannable(
|
||||
vararg spans: CharacterStyle,
|
||||
substring: CharSequence? = null,
|
||||
ignoreCase: Boolean = false,
|
||||
ignoreDiacritics: Boolean = false
|
||||
): Spannable {
|
||||
val spannable = SpannableString(this)
|
||||
if (substring == null) {
|
||||
spans.forEach {
|
||||
spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
else if (substring.isNotEmpty()) {
|
||||
val string =
|
||||
if (ignoreDiacritics)
|
||||
substring?.let { substr ->
|
||||
val string = if (ignoreDiacritics)
|
||||
this.cleanDiacritics()
|
||||
else this
|
||||
else
|
||||
this
|
||||
val search = if (ignoreDiacritics)
|
||||
substr.cleanDiacritics()
|
||||
else
|
||||
substr.toString()
|
||||
|
||||
var index = string.indexOf(substring, ignoreCase = ignoreCase)
|
||||
.takeIf { it != -1 } ?: indexOf(substring, ignoreCase = ignoreCase)
|
||||
while (index >= 0) {
|
||||
var index = 0
|
||||
do {
|
||||
index = string.indexOf(
|
||||
string = search,
|
||||
startIndex = index,
|
||||
ignoreCase = ignoreCase
|
||||
)
|
||||
|
||||
if (index >= 0) {
|
||||
spans.forEach {
|
||||
spannable.setSpan(it, index, index + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
spannable.setSpan(
|
||||
CharacterStyle.wrap(it),
|
||||
index,
|
||||
index + substring.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
index = string.indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
|
||||
.takeIf { it != -1 } ?: indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
|
||||
index += substring.length.coerceAtLeast(1)
|
||||
}
|
||||
} while (index >= 0)
|
||||
|
||||
} ?: spans.forEach {
|
||||
spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
return spannable
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ class MessageFull(
|
||||
@Ignore
|
||||
var filterWeight = 0
|
||||
@Ignore
|
||||
var searchHighlightText: String? = null
|
||||
var searchHighlightText: CharSequence? = null
|
||||
|
||||
// metadata
|
||||
var seen = false
|
||||
|
@ -1,11 +1,8 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.messages
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Filter
|
||||
import android.widget.Filterable
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -13,17 +10,14 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.cleanDiacritics
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.utils.MessagesFilter
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.MessageViewHolder
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.SearchViewHolder
|
||||
import java.util.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.math.min
|
||||
|
||||
class MessagesAdapter(
|
||||
val activity: AppCompatActivity,
|
||||
@ -43,41 +37,10 @@ class MessagesAdapter(
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
var items = mutableListOf<Any>()
|
||||
var allItems = mutableListOf<Any>()
|
||||
var items = listOf<Any>()
|
||||
var allItems = listOf<Any>()
|
||||
val typefaceNormal: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) }
|
||||
val typefaceBold: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) }
|
||||
private val comparator by lazy { Comparator { o1: Any, o2: Any ->
|
||||
if (o1 !is MessageFull || o2 !is MessageFull)
|
||||
return@Comparator 0
|
||||
when {
|
||||
// standard sorting
|
||||
o1.filterWeight > o2.filterWeight -> return@Comparator 1
|
||||
o1.filterWeight < o2.filterWeight -> return@Comparator -1
|
||||
else -> when {
|
||||
// reversed sorting
|
||||
o1.addedDate > o2.addedDate -> return@Comparator -1
|
||||
o1.addedDate < o2.addedDate -> return@Comparator 1
|
||||
else -> return@Comparator 0
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
val textWatcher by lazy {
|
||||
object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
getFilter().filter(s.toString())
|
||||
}
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
/*items.getOrNull(0)?.let {
|
||||
if (it is MessagesSearch) {
|
||||
it.searchText = s?.toString() ?: ""
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
@ -103,138 +66,16 @@ class MessagesAdapter(
|
||||
return
|
||||
|
||||
when {
|
||||
holder is MessageViewHolder && item is MessageFull -> holder.onBind(activity, app, item, position, this)
|
||||
holder is SearchViewHolder && item is MessagesSearch -> holder.onBind(activity, app, item, position, this)
|
||||
holder is MessageViewHolder
|
||||
&& item is MessageFull -> holder.onBind(activity, app, item, position, this)
|
||||
holder is SearchViewHolder
|
||||
&& item is MessagesSearch -> holder.onBind(activity, app, item, position, this)
|
||||
}
|
||||
}
|
||||
|
||||
private val messagesFilter by lazy {
|
||||
MessagesFilter(this)
|
||||
}
|
||||
override fun getItemCount() = items.size
|
||||
override fun getFilter() = filter
|
||||
private var prevCount = -1
|
||||
private val filter by lazy { object : Filter() {
|
||||
override fun performFiltering(prefix: CharSequence?): FilterResults {
|
||||
val results = FilterResults()
|
||||
|
||||
if (prevCount == -1)
|
||||
prevCount = allItems.size
|
||||
|
||||
if (prefix.isNullOrEmpty()) {
|
||||
allItems.forEach {
|
||||
if (it is MessageFull)
|
||||
it.searchHighlightText = null
|
||||
}
|
||||
results.values = allItems.toList()
|
||||
results.count = allItems.size
|
||||
return results
|
||||
}
|
||||
|
||||
val items = mutableListOf<Any>()
|
||||
val prefixString = prefix.toString()
|
||||
|
||||
allItems.forEach {
|
||||
if (it !is MessageFull) {
|
||||
items.add(it)
|
||||
return@forEach
|
||||
}
|
||||
it.filterWeight = 100
|
||||
it.searchHighlightText = null
|
||||
|
||||
var weight: Int
|
||||
if (it.type == Message.TYPE_SENT) {
|
||||
it.recipients?.forEach { recipient ->
|
||||
weight = getMatchWeight(recipient.fullName, prefixString)
|
||||
if (weight != 100) {
|
||||
if (weight == 3)
|
||||
weight = 31
|
||||
it.filterWeight = min(it.filterWeight, 10 + weight)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
weight = getMatchWeight(it.senderName, prefixString)
|
||||
if (weight != 100) {
|
||||
if (weight == 3)
|
||||
weight = 31
|
||||
it.filterWeight = min(it.filterWeight, 10 + weight)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
weight = getMatchWeight(it.subject, prefixString)
|
||||
if (weight != 100) {
|
||||
if (weight == 3)
|
||||
weight = 22
|
||||
it.filterWeight = min(it.filterWeight, 20 + weight)
|
||||
}
|
||||
|
||||
if (it.filterWeight != 100) {
|
||||
it.searchHighlightText = prefixString
|
||||
items.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(items, comparator)
|
||||
results.values = items
|
||||
results.count = items.size
|
||||
return results
|
||||
}
|
||||
|
||||
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
|
||||
results.values?.let { items = it as MutableList<Any> }
|
||||
// do not re-bind the search box
|
||||
val count = results.count - 1
|
||||
|
||||
// this tries to update every item except the search field
|
||||
when {
|
||||
count > prevCount -> {
|
||||
notifyItemRangeInserted(prevCount + 1, count - prevCount)
|
||||
notifyItemRangeChanged(1, prevCount)
|
||||
}
|
||||
count < prevCount -> {
|
||||
notifyItemRangeRemoved(prevCount + 1, prevCount - count)
|
||||
notifyItemRangeChanged(1, count)
|
||||
}
|
||||
else -> {
|
||||
notifyItemRangeChanged(1, count)
|
||||
}
|
||||
}
|
||||
|
||||
/*if (prevCount != count) {
|
||||
items.getOrNull(0)?.let {
|
||||
if (it is MessagesSearch) {
|
||||
it.count = count
|
||||
notifyItemChanged(0)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
prevCount = count
|
||||
}
|
||||
}}
|
||||
|
||||
private fun getMatchWeight(name: CharSequence?, prefix: String): Int {
|
||||
if (name == null)
|
||||
return 100
|
||||
|
||||
val nameClean = name.cleanDiacritics()
|
||||
|
||||
// First match against the whole, non-split value
|
||||
if (nameClean.startsWith(prefix, ignoreCase = true) || name.startsWith(prefix, ignoreCase = true)) {
|
||||
return 1
|
||||
} else {
|
||||
// check if prefix matches any of the words
|
||||
val words = nameClean.split(" ").toTypedArray() + name.split(" ").toTypedArray()
|
||||
for (word in words) {
|
||||
if (word.startsWith(prefix, ignoreCase = true)) {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
}
|
||||
// finally check if the prefix matches any part of the name
|
||||
if (nameClean.contains(prefix, ignoreCase = true) || name.contains(prefix, ignoreCase = true)) {
|
||||
return 3
|
||||
}
|
||||
|
||||
return 100
|
||||
}
|
||||
override fun getFilter() = messagesFilter
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: MainActivity
|
||||
private lateinit var b: MessagesListFragmentBinding
|
||||
private var adapter: MessagesAdapter? = null
|
||||
|
||||
private val job: Job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
@ -53,21 +54,22 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
||||
val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED)
|
||||
var topPosition = arguments.getInt("topPosition", NO_POSITION)
|
||||
var bottomPosition = arguments.getInt("bottomPosition", NO_POSITION)
|
||||
val searchText = arguments.getString("searchText", "")
|
||||
|
||||
teachers = withContext(Dispatchers.Default) {
|
||||
app.db.teacherDao().getAllNow(App.profileId)
|
||||
}
|
||||
|
||||
val adapter = MessagesAdapter(activity, teachers) {
|
||||
adapter = MessagesAdapter(activity, teachers) {
|
||||
activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle(
|
||||
"messageId" to it.id
|
||||
))
|
||||
}
|
||||
|
||||
app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { items ->
|
||||
app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { messages ->
|
||||
if (!isAdded) return@Observer
|
||||
|
||||
items.forEach { message ->
|
||||
messages.forEach { message ->
|
||||
message.recipients?.removeAll { it.profileId != message.profileId }
|
||||
message.recipients?.forEach { recipient ->
|
||||
if (recipient.fullName == null) {
|
||||
@ -77,13 +79,22 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
||||
}
|
||||
|
||||
// load & configure the adapter
|
||||
adapter.items = items.toMutableList()
|
||||
adapter.items.add(0, MessagesSearch().also {
|
||||
it.count = items.size
|
||||
val items = messages.toMutableList<Any>()
|
||||
items.add(0, MessagesSearch().also {
|
||||
it.searchText = searchText
|
||||
})
|
||||
adapter.allItems = adapter.items.toMutableList()
|
||||
|
||||
adapter?.items = items
|
||||
adapter?.allItems = items
|
||||
|
||||
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
|
||||
if (searchText.isNotBlank())
|
||||
adapter?.filter?.filter(searchText) {
|
||||
b.list.adapter = adapter
|
||||
}
|
||||
else
|
||||
b.list.adapter = adapter
|
||||
|
||||
b.list.apply {
|
||||
setHasFixedSize(true)
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
@ -92,7 +103,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
||||
addOnScrollListener(onScrollListener)
|
||||
}
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
setSwipeToRefresh(messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT && items.isNullOrEmpty())
|
||||
|
||||
(b.list.layoutManager as? LinearLayoutManager)?.let { layoutManager ->
|
||||
@ -119,10 +130,15 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (!isAdded) return
|
||||
if (!isAdded)
|
||||
return
|
||||
val layoutManager = (b.list.layoutManager as? LinearLayoutManager)
|
||||
val searchItem = adapter?.items?.firstOrNull { it is MessagesSearch } as? MessagesSearch
|
||||
|
||||
onPageDestroy?.invoke(position, Bundle(
|
||||
"topPosition" to (b.list.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition(),
|
||||
"bottomPosition" to (b.list.layoutManager as? LinearLayoutManager)?.findLastCompletelyVisibleItemPosition()
|
||||
"topPosition" to layoutManager?.findFirstVisibleItemPosition(),
|
||||
"bottomPosition" to layoutManager?.findLastCompletelyVisibleItemPosition(),
|
||||
"searchText" to searchItem?.searchText?.toString()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,5 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.messages.models
|
||||
|
||||
class MessagesSearch {
|
||||
var isFocused = false
|
||||
var searchText = ""
|
||||
var count = 0
|
||||
var searchText: CharSequence = ""
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.messages.utils
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||
|
||||
class MessagesComparator : Comparator<Any> {
|
||||
|
||||
override fun compare(o1: Any?, o2: Any?): Int {
|
||||
if (o1 !is MessageFull || o2 !is MessageFull)
|
||||
return 0
|
||||
|
||||
return when {
|
||||
// standard sorting
|
||||
o1.filterWeight > o2.filterWeight -> 1
|
||||
o1.filterWeight < o2.filterWeight -> -1
|
||||
else -> when {
|
||||
// reversed sorting
|
||||
o1.addedDate > o2.addedDate -> -1
|
||||
o1.addedDate < o2.addedDate -> 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.messages.utils
|
||||
|
||||
import android.widget.Filter
|
||||
import pl.szczodrzynski.edziennik.cleanDiacritics
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
|
||||
class MessagesFilter(
|
||||
private val adapter: MessagesAdapter
|
||||
) : Filter() {
|
||||
companion object {
|
||||
private const val NO_MATCH = 1000
|
||||
}
|
||||
|
||||
private val comparator = MessagesComparator()
|
||||
private var prevCount = -1
|
||||
|
||||
private val allItems
|
||||
get() = adapter.allItems
|
||||
|
||||
private fun getMatchWeight(name: CharSequence?, prefix: CharSequence): Int {
|
||||
if (name == null)
|
||||
return NO_MATCH
|
||||
|
||||
val prefixClean = prefix.cleanDiacritics()
|
||||
val nameClean = name.cleanDiacritics()
|
||||
|
||||
return when {
|
||||
// First match against the whole, non-split value
|
||||
nameClean.startsWith(prefixClean, ignoreCase = true) -> 1
|
||||
// check if prefix matches any of the words
|
||||
nameClean.split(" ").any {
|
||||
it.startsWith(prefixClean, ignoreCase = true)
|
||||
} -> 2
|
||||
// finally check if the prefix matches any part of the name
|
||||
nameClean.contains(prefixClean, ignoreCase = true) -> 3
|
||||
|
||||
else -> NO_MATCH
|
||||
}
|
||||
}
|
||||
|
||||
override fun performFiltering(prefix: CharSequence?): FilterResults {
|
||||
val results = FilterResults()
|
||||
|
||||
if (prevCount == -1)
|
||||
prevCount = allItems.size
|
||||
|
||||
if (prefix.isNullOrBlank()) {
|
||||
allItems.forEach {
|
||||
if (it is MessageFull)
|
||||
it.searchHighlightText = null
|
||||
}
|
||||
results.values = allItems.toList()
|
||||
results.count = allItems.size
|
||||
return results
|
||||
}
|
||||
|
||||
val items = mutableListOf<Any>()
|
||||
|
||||
allItems.forEach {
|
||||
if (it !is MessageFull) {
|
||||
items.add(it)
|
||||
return@forEach
|
||||
}
|
||||
it.filterWeight = NO_MATCH
|
||||
it.searchHighlightText = null
|
||||
|
||||
var weight: Int
|
||||
// weights 11..13 and 110
|
||||
if (it.type == Message.TYPE_SENT) {
|
||||
it.recipients?.forEach { recipient ->
|
||||
weight = getMatchWeight(recipient.fullName, prefix)
|
||||
if (weight != NO_MATCH) {
|
||||
if (weight == 3)
|
||||
weight = 100
|
||||
it.filterWeight = min(it.filterWeight, 10 + weight)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
weight = getMatchWeight(it.senderName, prefix)
|
||||
if (weight != NO_MATCH) {
|
||||
if (weight == 3)
|
||||
weight = 100
|
||||
it.filterWeight = min(it.filterWeight, 10 + weight)
|
||||
}
|
||||
}
|
||||
|
||||
// weights 21..23 and 120
|
||||
weight = getMatchWeight(it.subject, prefix)
|
||||
if (weight != NO_MATCH) {
|
||||
if (weight == 3)
|
||||
weight = 100
|
||||
it.filterWeight = min(it.filterWeight, 20 + weight)
|
||||
}
|
||||
|
||||
// weights 31..33 and 130
|
||||
weight = getMatchWeight(it.body, prefix)
|
||||
if (weight != NO_MATCH) {
|
||||
if (weight == 3)
|
||||
weight = 100
|
||||
it.filterWeight = min(it.filterWeight, 30 + weight)
|
||||
}
|
||||
|
||||
if (it.filterWeight != NO_MATCH) {
|
||||
it.searchHighlightText = prefix
|
||||
items.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(items, comparator)
|
||||
results.values = items
|
||||
results.count = items.size
|
||||
return results
|
||||
}
|
||||
|
||||
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
|
||||
results.values?.let {
|
||||
adapter.items = it as MutableList<Any>
|
||||
}
|
||||
// do not re-bind the search box
|
||||
val count = results.count - 1
|
||||
|
||||
// this tries to update every item except the search field
|
||||
with(adapter) {
|
||||
when {
|
||||
count > prevCount -> {
|
||||
notifyItemRangeInserted(prevCount + 1, count - prevCount)
|
||||
notifyItemRangeChanged(1, prevCount)
|
||||
}
|
||||
count < prevCount -> {
|
||||
notifyItemRangeRemoved(prevCount + 1, prevCount - count)
|
||||
notifyItemRangeChanged(1, count)
|
||||
}
|
||||
else -> {
|
||||
notifyItemRangeChanged(1, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prevCount = count
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.messages.utils
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.MessagesListItemSearchBinding
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
||||
|
||||
class SearchTextWatcher(
|
||||
private val b: MessagesListItemSearchBinding,
|
||||
private val filter: MessagesFilter,
|
||||
private val item: MessagesSearch
|
||||
) : TextWatcher {
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
item.searchText = s ?: ""
|
||||
filter.filter(s) { count ->
|
||||
if (s.isNullOrBlank())
|
||||
b.searchLayout.helperText = " "
|
||||
else
|
||||
b.searchLayout.helperText =
|
||||
b.root.context.getString(R.string.messages_search_results, count - 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is SearchTextWatcher
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = b.hashCode()
|
||||
result = 31 * result + filter.hashCode()
|
||||
result = 31 * result + item.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
@ -30,9 +30,13 @@ class MessageViewHolder(
|
||||
private const val TAG = "MessageViewHolder"
|
||||
}
|
||||
|
||||
override fun onBind(activity: AppCompatActivity, app: App, item: MessageFull, position: Int, adapter: MessagesAdapter) {
|
||||
val manager = app.gradesManager
|
||||
|
||||
override fun onBind(
|
||||
activity: AppCompatActivity,
|
||||
app: App,
|
||||
item: MessageFull,
|
||||
position: Int,
|
||||
adapter: MessagesAdapter
|
||||
) {
|
||||
b.messageSubject.text = item.subject
|
||||
b.messageDate.text = Date.fromMillis(item.addedDate).formattedStringShort
|
||||
b.messageAttachmentImage.isVisible = item.hasAttachments
|
||||
@ -55,15 +59,17 @@ class MessageViewHolder(
|
||||
b.messageProfileBackground.setImageBitmap(messageInfo.profileImage)
|
||||
b.messageSender.text = messageInfo.profileName
|
||||
|
||||
item.searchHighlightText?.let { highlight ->
|
||||
item.searchHighlightText?.toString()?.let { highlight ->
|
||||
val colorHighlight = R.attr.colorControlHighlight.resolveAttr(activity)
|
||||
|
||||
b.messageSubject.text = b.messageSubject.text.asSpannable(
|
||||
StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight),
|
||||
substring = highlight, ignoreCase = true, ignoreDiacritics = true)
|
||||
substring = highlight, ignoreCase = true, ignoreDiacritics = true
|
||||
)
|
||||
b.messageSender.text = b.messageSender.text.asSpannable(
|
||||
StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight),
|
||||
substring = highlight, ignoreCase = true, ignoreDiacritics = true)
|
||||
substring = highlight, ignoreCase = true, ignoreDiacritics = true
|
||||
)
|
||||
}
|
||||
|
||||
adapter.onItemClick?.let { listener ->
|
||||
|
@ -9,38 +9,43 @@ import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.MessagesListItemSearchBinding
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.utils.SearchTextWatcher
|
||||
|
||||
class SearchViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: MessagesListItemSearchBinding = MessagesListItemSearchBinding.inflate(inflater, parent, false)
|
||||
val b: MessagesListItemSearchBinding = MessagesListItemSearchBinding.inflate(
|
||||
inflater,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<MessagesSearch, MessagesAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "SearchViewHolder"
|
||||
}
|
||||
|
||||
override fun onBind(activity: AppCompatActivity, app: App, item: MessagesSearch, position: Int, adapter: MessagesAdapter) {
|
||||
b.searchEdit.removeTextChangedListener(adapter.textWatcher)
|
||||
b.searchEdit.addTextChangedListener(adapter.textWatcher)
|
||||
override fun onBind(
|
||||
activity: AppCompatActivity,
|
||||
app: App,
|
||||
item: MessagesSearch,
|
||||
position: Int,
|
||||
adapter: MessagesAdapter
|
||||
) {
|
||||
val watcher = SearchTextWatcher(b, adapter.filter, item)
|
||||
b.searchEdit.removeTextChangedListener(watcher)
|
||||
|
||||
/*b.searchEdit.setOnKeyboardListener(object : TextInputKeyboardEdit.KeyboardListener {
|
||||
override fun onStateChanged(keyboardEditText: TextInputKeyboardEdit, showing: Boolean) {
|
||||
item.isFocused = showing
|
||||
}
|
||||
})*/
|
||||
|
||||
/*if (b.searchEdit.text.toString() != item.searchText) {
|
||||
if (adapter.items.isEmpty() || adapter.items.size == adapter.allItems.size)
|
||||
b.searchLayout.helperText = " "
|
||||
else
|
||||
b.searchLayout.helperText =
|
||||
b.root.context.getString(R.string.messages_search_results, adapter.items.size - 1)
|
||||
b.searchEdit.setText(item.searchText)
|
||||
b.searchEdit.setSelection(item.searchText.length)
|
||||
}*/
|
||||
|
||||
//b.searchLayout.helperText = app.getString(R.string.messages_search_results, item.count)
|
||||
|
||||
/*if (item.isFocused && !b.searchEdit.isFocused)
|
||||
b.searchEdit.requestFocus()*/
|
||||
b.searchEdit.addTextChangedListener(watcher)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user