forked from github/szkolny
[Messages] Add a search bar. Fix Grades not loading.
This commit is contained in:
parent
91a6366548
commit
c214b48409
@ -572,7 +572,7 @@ 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): Spannable {
|
||||
fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignoreCase: Boolean = false, ignoreDiacritics: Boolean = false): Spannable {
|
||||
val spannable = SpannableString(this)
|
||||
if (substring == null) {
|
||||
spans.forEach {
|
||||
@ -580,17 +580,44 @@ fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignor
|
||||
}
|
||||
}
|
||||
else if (substring.isNotEmpty()) {
|
||||
var index = indexOf(substring, ignoreCase = ignoreCase)
|
||||
val string =
|
||||
if (ignoreDiacritics)
|
||||
this.cleanDiacritics()
|
||||
else this
|
||||
|
||||
var index = string.indexOf(substring, ignoreCase = ignoreCase)
|
||||
.takeIf { it != -1 } ?: indexOf(substring, ignoreCase = ignoreCase)
|
||||
while (index >= 0) {
|
||||
spans.forEach {
|
||||
spannable.setSpan(it, index, index + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
index = indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
|
||||
index = string.indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
|
||||
.takeIf { it != -1 } ?: indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
|
||||
}
|
||||
}
|
||||
return spannable
|
||||
}
|
||||
|
||||
fun CharSequence.cleanDiacritics(): String {
|
||||
val nameClean = StringBuilder()
|
||||
forEach {
|
||||
val ch = when (it) {
|
||||
'ż' -> 'z'
|
||||
'ó' -> 'o'
|
||||
'ł' -> 'l'
|
||||
'ć' -> 'c'
|
||||
'ę' -> 'e'
|
||||
'ś' -> 's'
|
||||
'ą' -> 'a'
|
||||
'ź' -> 'z'
|
||||
'ń' -> 'n'
|
||||
else -> it
|
||||
}
|
||||
nameClean.append(ch)
|
||||
}
|
||||
return nameClean.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new read-only list only of those given elements, that are not empty.
|
||||
* Applies for CharSequence and descendants.
|
||||
|
@ -3,6 +3,7 @@
|
||||
*/
|
||||
package pl.szczodrzynski.edziennik.data.db.full
|
||||
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.Relation
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
|
||||
@ -24,6 +25,11 @@ class MessageFull(
|
||||
return this
|
||||
}
|
||||
|
||||
@Ignore
|
||||
var filterWeight = 0
|
||||
@Ignore
|
||||
var searchHighlightText: String? = null
|
||||
|
||||
// metadata
|
||||
var seen = false
|
||||
var notified = false
|
||||
|
@ -166,7 +166,7 @@ class GradesAdapter(
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
if (holder !is BindableViewHolder<*>)
|
||||
if (holder !is BindableViewHolder<*, *>)
|
||||
return
|
||||
|
||||
val viewType = when (holder) {
|
||||
|
@ -71,7 +71,7 @@ class GradesListFragment : Fragment(), CoroutineScope {
|
||||
val adapter = GradesAdapter(activity)
|
||||
var firstRun = true
|
||||
|
||||
app.db.gradeDao().getAllOrderBy(App.profileId, app.gradesManager.getOrderByString()).observe(this@GradesListFragment, Observer { items -> launch {
|
||||
app.db.gradeDao().getAllOrderBy(App.profileId, app.gradesManager.getOrderByString()).observe(this@GradesListFragment, Observer { items -> this@GradesListFragment.launch {
|
||||
if (!isAdded) return@launch
|
||||
|
||||
// load & configure the adapter
|
||||
|
@ -6,8 +6,7 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.viewholder
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
|
||||
|
||||
interface BindableViewHolder<T> {
|
||||
fun onBind(activity: AppCompatActivity, app: App, item: T, position: Int, adapter: GradesAdapter)
|
||||
interface BindableViewHolder<T, A> {
|
||||
fun onBind(activity: AppCompatActivity, app: App, item: T, position: Int, adapter: A)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class EmptyViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: GradesItemEmptyBinding = GradesItemEmptyBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesEmpty> {
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesEmpty, GradesAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "EmptyViewHolder"
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class GradeViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: GradesItemGradeBinding = GradesItemGradeBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradeFull> {
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradeFull, GradesAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "GradeViewHolder"
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class SemesterViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: GradesItemSemesterBinding = GradesItemSemesterBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesSemester> {
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesSemester, GradesAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "SemesterViewHolder"
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class StatsViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: GradesItemStatsBinding = GradesItemStatsBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesStats> {
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesStats, GradesAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "StatsViewHolder"
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class SubjectViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: GradesItemSubjectBinding = GradesItemSubjectBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesSubject> {
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesSubject, GradesAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "SubjectViewHolder"
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ class MessageFragment : Fragment(), CoroutineScope {
|
||||
}
|
||||
|
||||
private fun showMessage() {
|
||||
b.body.text = MessagesUtils.htmlToSpannable(activity, message.body ?: "")
|
||||
b.body.text = MessagesUtils.htmlToSpannable(activity, message.body.toString())
|
||||
b.date.text = getString(R.string.messages_date_time_format, Date.fromMillis(message.addedDate).formattedStringShort, Time.fromMillis(message.addedDate).stringHM)
|
||||
|
||||
val messageInfo = MessagesUtils.getMessageInfo(app, message, 40, 20, 14, 10)
|
||||
|
@ -1,31 +1,38 @@
|
||||
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.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.cleanDiacritics
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||
import pl.szczodrzynski.edziennik.databinding.MessagesListItemBinding
|
||||
import pl.szczodrzynski.edziennik.onClick
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
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.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,
|
||||
val teachers: List<Teacher>,
|
||||
val onItemClick: ((item: MessageFull) -> Unit)? = null
|
||||
) : RecyclerView.Adapter<MessagesAdapter.ViewHolder>(), CoroutineScope {
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope, Filterable {
|
||||
companion object {
|
||||
private const val TAG = "TemplateAdapter"
|
||||
private const val TAG = "MessagesAdapter"
|
||||
private const val ITEM_TYPE_MESSAGE = 0
|
||||
private const val ITEM_TYPE_SEARCH = 1
|
||||
}
|
||||
|
||||
private val app = activity.applicationContext as App
|
||||
@ -35,55 +42,184 @@ class MessagesAdapter(
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
var items = listOf<MessageFull>()
|
||||
private val typefaceNormal by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) }
|
||||
private val typefaceBold by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) }
|
||||
var items = mutableListOf<Any>()
|
||||
var allItems = mutableListOf<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
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
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)
|
||||
val view = MessagesListItemBinding.inflate(inflater, parent, false)
|
||||
return ViewHolder(view)
|
||||
return when (viewType) {
|
||||
ITEM_TYPE_MESSAGE -> MessageViewHolder(inflater, parent)
|
||||
ITEM_TYPE_SEARCH -> SearchViewHolder(inflater, parent)
|
||||
else -> throw IllegalArgumentException("Incorrect viewType")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (items[position]) {
|
||||
is MessageFull -> ITEM_TYPE_MESSAGE
|
||||
is MessagesSearch -> ITEM_TYPE_SEARCH
|
||||
else -> throw IllegalArgumentException("Incorrect viewType")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
val b = holder.b
|
||||
if (holder !is BindableViewHolder<*, *>)
|
||||
return
|
||||
|
||||
item.recipients?.forEach { recipient ->
|
||||
if (recipient.fullName == null) {
|
||||
recipient.fullName = teachers.firstOrNull { it.id == recipient.id }?.fullName ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
b.messageSubject.text = item.subject
|
||||
b.messageDate.text = Date.fromMillis(item.addedDate).formattedStringShort
|
||||
b.messageAttachmentImage.isVisible = item.hasAttachments
|
||||
|
||||
val text = item.body?.take(200) ?: ""
|
||||
b.messageBody.text = MessagesUtils.htmlToSpannable(activity, text)
|
||||
|
||||
val isRead = item.type == Message.TYPE_SENT || item.type == Message.TYPE_DRAFT || item.seen
|
||||
val typeface = if (isRead) typefaceNormal else typefaceBold
|
||||
val style = if (isRead) R.style.NavView_TextView_Small else R.style.NavView_TextView_Normal
|
||||
// set text styles
|
||||
b.messageSender.setTextAppearance(activity, style)
|
||||
b.messageSender.typeface = typeface
|
||||
b.messageSubject.setTextAppearance(activity, style)
|
||||
b.messageSubject.typeface = typeface
|
||||
b.messageDate.setTextAppearance(activity, style)
|
||||
b.messageDate.typeface = typeface
|
||||
|
||||
val messageInfo = MessagesUtils.getMessageInfo(app, item, 48, 24, 18, 12)
|
||||
b.messageProfileBackground.setImageBitmap(messageInfo.profileImage)
|
||||
b.messageSender.text = messageInfo.profileName
|
||||
|
||||
onItemClick?.let { listener ->
|
||||
b.root.onClick { listener(item) }
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
class ViewHolder(val b: MessagesListItemBinding) : RecyclerView.ViewHolder(b.root)
|
||||
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 = 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
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||
import pl.szczodrzynski.edziennik.databinding.MessagesListFragmentBinding
|
||||
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
||||
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@ -68,7 +69,11 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
||||
}
|
||||
|
||||
// load & configure the adapter
|
||||
adapter.items = items
|
||||
adapter.items = items.toMutableList()
|
||||
adapter.items.add(0, MessagesSearch().also {
|
||||
it.count = items.size
|
||||
})
|
||||
adapter.allItems = adapter.items.toMutableList()
|
||||
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
|
||||
b.list.adapter = adapter
|
||||
b.list.apply {
|
||||
|
@ -13,6 +13,7 @@ import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.asSpannable
|
||||
import pl.szczodrzynski.edziennik.cleanDiacritics
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||
import pl.szczodrzynski.edziennik.resolveAttr
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage
|
||||
@ -85,36 +86,15 @@ class MessagesComposeSuggestionAdapter(
|
||||
val list = mutableListOf<Teacher>()
|
||||
|
||||
originalList.forEach { teacher ->
|
||||
val teacherFullName = teacher.fullName
|
||||
teacher.recipientWeight = 0
|
||||
teacher.recipientWeight = getMatchWeight(teacher.fullName, prefixString)
|
||||
|
||||
// First match against the whole, non-split value
|
||||
var found = false
|
||||
if (teacherFullName.startsWith(prefixString, ignoreCase = true)) {
|
||||
teacher.recipientWeight = 1
|
||||
found = true
|
||||
} else {
|
||||
// check if prefix matches any of the words
|
||||
val words = teacherFullName.split(" ").toTypedArray()
|
||||
for (word in words) {
|
||||
if (word.startsWith(prefixString, ignoreCase = true)) {
|
||||
teacher.recipientWeight = 2
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// finally check if the prefix matches any part of the name
|
||||
if (!found && teacherFullName.contains(prefixString, ignoreCase = true)) {
|
||||
teacher.recipientWeight = 3
|
||||
}
|
||||
|
||||
if (teacher.recipientWeight != 0) {
|
||||
teacher.recipientDisplayName = teacherFullName.asSpannable(
|
||||
if (teacher.recipientWeight != 100) {
|
||||
teacher.recipientDisplayName = teacher.fullName.asSpannable(
|
||||
StyleSpan(BOLD),
|
||||
BackgroundColorSpan(R.attr.colorControlHighlight.resolveAttr(context)),
|
||||
substring = prefixString,
|
||||
ignoreCase = true
|
||||
ignoreCase = true,
|
||||
ignoreDiacritics = true
|
||||
)
|
||||
list += teacher
|
||||
}
|
||||
@ -137,4 +117,29 @@ class MessagesComposeSuggestionAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-5.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.messages.models
|
||||
|
||||
class MessagesSearch {
|
||||
var isFocused = false
|
||||
var searchText = ""
|
||||
var count = 0
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-5.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.messages.viewholder
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.style.BackgroundColorSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||
import pl.szczodrzynski.edziennik.databinding.MessagesListItemBinding
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter
|
||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class MessageViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: MessagesListItemBinding = MessagesListItemBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<MessageFull, MessagesAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "MessageViewHolder"
|
||||
}
|
||||
|
||||
override fun onBind(activity: AppCompatActivity, app: App, item: MessageFull, position: Int, adapter: MessagesAdapter) {
|
||||
val manager = app.gradesManager
|
||||
|
||||
item.recipients?.forEach { recipient ->
|
||||
if (recipient.fullName == null) {
|
||||
recipient.fullName = adapter.teachers.firstOrNull { it.id == recipient.id }?.fullName ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
b.messageSubject.text = item.subject
|
||||
b.messageDate.text = Date.fromMillis(item.addedDate).formattedStringShort
|
||||
b.messageAttachmentImage.isVisible = item.hasAttachments
|
||||
|
||||
val text = item.body?.take(200) ?: ""
|
||||
b.messageBody.text = MessagesUtils.htmlToSpannable(activity, text)
|
||||
|
||||
val isRead = item.type == Message.TYPE_SENT || item.type == Message.TYPE_DRAFT || item.seen
|
||||
val typeface = if (isRead) adapter.typefaceNormal else adapter.typefaceBold
|
||||
val style = if (isRead) R.style.NavView_TextView_Small else R.style.NavView_TextView_Normal
|
||||
// set text styles
|
||||
b.messageSender.setTextAppearance(activity, style)
|
||||
b.messageSender.typeface = typeface
|
||||
b.messageSubject.setTextAppearance(activity, style)
|
||||
b.messageSubject.typeface = typeface
|
||||
b.messageDate.setTextAppearance(activity, style)
|
||||
b.messageDate.typeface = typeface
|
||||
|
||||
val messageInfo = MessagesUtils.getMessageInfo(app, item, 48, 24, 18, 12)
|
||||
b.messageProfileBackground.setImageBitmap(messageInfo.profileImage)
|
||||
b.messageSender.text = messageInfo.profileName
|
||||
|
||||
item.searchHighlightText?.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)
|
||||
b.messageSender.text = b.messageSender.text.asSpannable(
|
||||
StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight),
|
||||
substring = highlight, ignoreCase = true, ignoreDiacritics = true)
|
||||
}
|
||||
|
||||
adapter.onItemClick?.let { listener ->
|
||||
b.root.onClick { listener(item) }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-5.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.messages.viewholder
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
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
|
||||
|
||||
class SearchViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
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)
|
||||
|
||||
/*b.searchEdit.setOnKeyboardListener(object : TextInputKeyboardEdit.KeyboardListener {
|
||||
override fun onStateChanged(keyboardEditText: TextInputKeyboardEdit, showing: Boolean) {
|
||||
item.isFocused = showing
|
||||
}
|
||||
})*/
|
||||
|
||||
/*if (b.searchEdit.text.toString() != item.searchText) {
|
||||
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()*/
|
||||
}
|
||||
}
|
@ -10,9 +10,9 @@ import android.util.AttributeSet
|
||||
import android.view.KeyEvent
|
||||
import android.view.KeyEvent.KEYCODE_BACK
|
||||
import androidx.annotation.NonNull
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
|
||||
class TextInputKeyboardEdit : TextInputEditText {
|
||||
class TextInputKeyboardEdit : AppCompatEditText {
|
||||
|
||||
/**
|
||||
* Keyboard Listener
|
||||
@ -53,4 +53,4 @@ class TextInputKeyboardEdit : TextInputEditText {
|
||||
interface KeyboardListener {
|
||||
fun onStateChanged(keyboardEditText: TextInputKeyboardEdit, showing: Boolean)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@
|
||||
app:counterEnabled="true"
|
||||
tools:counterMaxLength="180">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
|
||||
android:id="@+id/subject"
|
||||
style="@style/Widget.MaterialComponents.TextInputEditText.FilledBox"
|
||||
android:layout_width="match_parent"
|
||||
@ -69,7 +69,7 @@
|
||||
app:boxBackgroundMode="filled"
|
||||
app:counterEnabled="true"
|
||||
tools:counterMaxLength="1983">
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
26
app/src/main/res/layout/messages_list_item_search.xml
Normal file
26
app/src/main/res/layout/messages_list_item_search.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-4-5.
|
||||
-->
|
||||
|
||||
<layout 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">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/searchLayout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="@string/messages_search"
|
||||
app:hintEnabled="true"
|
||||
tools:helperText="Znaleziono 400 wiadomości">
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
|
||||
android:id="@+id/searchEdit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</layout>
|
||||
|
@ -1289,4 +1289,6 @@
|
||||
<string name="menu_lab">Laboratorium</string>
|
||||
<string name="messages_no_data">Nie masz żadnych wiadomości.</string>
|
||||
<string name="messages_tab_deleted">Usunięte</string>
|
||||
<string name="messages_search">Szukaj</string>
|
||||
<string name="messages_search_results">Znaleziono %d wiadomości</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user