Fix buggy timers in timetable (#1428)

Co-authored-by: Rafał Borcz <RafalBO99@outlook.com>
This commit is contained in:
Mateusz Idziejczak 2021-08-22 16:33:12 +02:00 committed by GitHub
parent d3b3939d26
commit eb94e06d54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 152 additions and 69 deletions

View File

@ -7,6 +7,7 @@ import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
@ -26,31 +27,58 @@ import kotlin.concurrent.timer
class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() { class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private enum class ViewType(val id: Int) { private enum class ViewType {
ITEM_NORMAL(1), ITEM_NORMAL,
ITEM_SMALL(2) ITEM_SMALL
} }
var items = mutableListOf<Timetable>()
set(value) {
field = value
resetTimers()
}
var onClickListener: (Timetable) -> Unit = {} var onClickListener: (Timetable) -> Unit = {}
var showWholeClassPlan: String = "no" private var showWholeClassPlan: String = "no"
var showGroupsInPlan: Boolean = false private var showGroupsInPlan: Boolean = false
var showTimers: Boolean = false private var showTimers: Boolean = false
private val timers = mutableMapOf<Int, Timer>() private val timers = mutableMapOf<Int, Timer?>()
fun resetTimers() { private val items = mutableListOf<Timetable>()
Timber.d("Timetable timers (${timers.size}) reset")
fun submitList(
newTimetable: List<Timetable>,
showWholeClassPlan: String = this.showWholeClassPlan,
showGroupsInPlan: Boolean = this.showGroupsInPlan,
showTimers: Boolean = this.showTimers
) {
val isFlagsDifferent = this.showWholeClassPlan != showWholeClassPlan
|| this.showGroupsInPlan != showGroupsInPlan
|| this.showTimers != showTimers
val diffResult = DiffUtil.calculateDiff(
TimetableAdapterDiffCallback(
oldList = items.toMutableList(),
newList = newTimetable,
isFlagsDifferent = isFlagsDifferent
)
)
this.showGroupsInPlan = showGroupsInPlan
this.showTimers = showTimers
this.showWholeClassPlan = showWholeClassPlan
items.clear()
items.addAll(newTimetable)
diffResult.dispatchUpdatesTo(this)
}
fun clearTimers() {
Timber.d("Timetable timers (${timers.size}) cleared")
with(timers) { with(timers) {
forEach { (_, timer) -> timer.cancel() } forEach { (_, timer) ->
timer?.cancel()
timer?.purge()
}
clear() clear()
} }
} }
@ -58,16 +86,20 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
override fun getItemCount() = items.size override fun getItemCount() = items.size
override fun getItemViewType(position: Int) = when { override fun getItemViewType(position: Int) = when {
!items[position].isStudentPlan && showWholeClassPlan == "small" -> ViewType.ITEM_SMALL.id !items[position].isStudentPlan && showWholeClassPlan == "small" -> ViewType.ITEM_SMALL.ordinal
else -> ViewType.ITEM_NORMAL.id else -> ViewType.ITEM_NORMAL.ordinal
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
return when (viewType) { return when (viewType) {
ViewType.ITEM_NORMAL.id -> ItemViewHolder(ItemTimetableBinding.inflate(inflater, parent, false)) ViewType.ITEM_NORMAL.ordinal -> ItemViewHolder(
ViewType.ITEM_SMALL.id -> SmallItemViewHolder(ItemTimetableSmallBinding.inflate(inflater, parent, false)) ItemTimetableBinding.inflate(inflater, parent, false)
)
ViewType.ITEM_SMALL.ordinal -> SmallItemViewHolder(
ItemTimetableSmallBinding.inflate(inflater, parent, false)
)
else -> throw IllegalStateException() else -> throw IllegalStateException()
} }
} }
@ -111,6 +143,12 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
bindNormalDescription(binding, lesson) bindNormalDescription(binding, lesson)
bindNormalColors(binding, lesson) bindNormalColors(binding, lesson)
timers[position]?.let {
it.cancel()
it.purge()
}
timers[position] = null
if (lesson.isStudentPlan && showTimers) { if (lesson.isStudentPlan && showTimers) {
timers[position] = timer(period = 1000) { timers[position] = timer(period = 1000) {
if (ViewCompat.isAttachedToWindow(root)) { if (ViewCompat.isAttachedToWindow(root)) {
@ -128,10 +166,12 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} }
private fun getPreviousLesson(position: Int): LocalDateTime? { private fun getPreviousLesson(position: Int): LocalDateTime? {
return items.filter { it.isStudentPlan }.getOrNull(position - 1 - items.filterIndexed { i, item -> i < position && !item.isStudentPlan }.size)?.let { return items.filter { it.isStudentPlan }
if (!it.canceled && it.isStudentPlan) it.end .getOrNull(position - 1 - items.filterIndexed { i, item -> i < position && !item.isStudentPlan }.size)
else null ?.let {
} if (!it.canceled && it.isStudentPlan) it.end
else null
}
} }
private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) { private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) {
@ -148,11 +188,18 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
timetableItemTimeLeft.visibility = GONE timetableItemTimeLeft.visibility = GONE
with(timetableItemTimeUntil) { with(timetableItemTimeUntil) {
visibility = VISIBLE visibility = VISIBLE
text = context.getString(R.string.timetable_time_until, text = context.getString(
R.string.timetable_time_until,
if (until.seconds <= 60) { if (until.seconds <= 60) {
context.getString(R.string.timetable_seconds, until.seconds.toString(10)) context.getString(
R.string.timetable_seconds,
until.seconds.toString(10)
)
} else { } else {
context.getString(R.string.timetable_minutes, until.toMinutes().toString(10)) context.getString(
R.string.timetable_minutes,
until.toMinutes().toString(10)
)
} }
) )
} }
@ -166,9 +213,15 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
text = context.getString( text = context.getString(
R.string.timetable_time_left, R.string.timetable_time_left,
if (left.seconds < 60) { if (left.seconds < 60) {
context.getString(R.string.timetable_seconds, left.seconds.toString(10)) context.getString(
R.string.timetable_seconds,
left.seconds.toString(10)
)
} else { } else {
context.getString(R.string.timetable_minutes, left.toMinutes().toString(10)) context.getString(
R.string.timetable_minutes,
left.toMinutes().toString(10)
)
} }
) )
} }
@ -189,8 +242,9 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} }
private fun bindSubjectStyle(subjectView: TextView, lesson: Timetable) { private fun bindSubjectStyle(subjectView: TextView, lesson: Timetable) {
subjectView.paintFlags = if (lesson.canceled) subjectView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG subjectView.paintFlags =
else subjectView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() if (lesson.canceled) subjectView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else subjectView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
} }
private fun bindSmallDescription(binding: ItemTimetableSmallBinding, lesson: Timetable) { private fun bindSmallDescription(binding: ItemTimetableSmallBinding, lesson: Timetable) {
@ -202,10 +256,12 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
timetableSmallItemRoom.visibility = GONE timetableSmallItemRoom.visibility = GONE
timetableSmallItemTeacher.visibility = GONE timetableSmallItemTeacher.visibility = GONE
timetableSmallItemDescription.setTextColor(root.context.getThemeAttrColor( timetableSmallItemDescription.setTextColor(
if (lesson.canceled) R.attr.colorPrimary root.context.getThemeAttrColor(
else R.attr.colorTimetableChange if (lesson.canceled) R.attr.colorPrimary
)) else R.attr.colorTimetableChange
)
)
} else { } else {
timetableSmallItemDescription.visibility = GONE timetableSmallItemDescription.visibility = GONE
timetableSmallItemRoom.visibility = VISIBLE timetableSmallItemRoom.visibility = VISIBLE
@ -224,14 +280,17 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
timetableItemGroup.visibility = GONE timetableItemGroup.visibility = GONE
timetableItemTeacher.visibility = GONE timetableItemTeacher.visibility = GONE
timetableItemDescription.setTextColor(root.context.getThemeAttrColor( timetableItemDescription.setTextColor(
if (lesson.canceled) R.attr.colorPrimary root.context.getThemeAttrColor(
else R.attr.colorTimetableChange if (lesson.canceled) R.attr.colorPrimary
)) else R.attr.colorTimetableChange
)
)
} else { } else {
timetableItemDescription.visibility = GONE timetableItemDescription.visibility = GONE
timetableItemRoom.visibility = VISIBLE timetableItemRoom.visibility = VISIBLE
timetableItemGroup.visibility = if (showGroupsInPlan && lesson.group.isNotBlank()) VISIBLE else GONE timetableItemGroup.visibility =
if (showGroupsInPlan && lesson.group.isNotBlank()) VISIBLE else GONE
timetableItemTeacher.visibility = VISIBLE timetableItemTeacher.visibility = VISIBLE
} }
} }
@ -240,7 +299,10 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
private fun bindSmallColors(binding: ItemTimetableSmallBinding, lesson: Timetable) { private fun bindSmallColors(binding: ItemTimetableSmallBinding, lesson: Timetable) {
with(binding) { with(binding) {
if (lesson.canceled) { if (lesson.canceled) {
updateNumberAndSubjectCanceledColor(timetableSmallItemNumber, timetableSmallItemSubject) updateNumberAndSubjectCanceledColor(
timetableSmallItemNumber,
timetableSmallItemSubject
)
} else { } else {
updateNumberColor(timetableSmallItemNumber, lesson) updateNumberColor(timetableSmallItemNumber, lesson)
updateSubjectColor(timetableSmallItemSubject, lesson) updateSubjectColor(timetableSmallItemSubject, lesson)
@ -269,31 +331,39 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} }
private fun updateNumberColor(numberView: TextView, lesson: Timetable) { private fun updateNumberColor(numberView: TextView, lesson: Timetable) {
numberView.setTextColor(numberView.context.getThemeAttrColor( numberView.setTextColor(
if (lesson.changes || lesson.info.isNotBlank()) R.attr.colorTimetableChange numberView.context.getThemeAttrColor(
else android.R.attr.textColorPrimary if (lesson.changes || lesson.info.isNotBlank()) R.attr.colorTimetableChange
)) else android.R.attr.textColorPrimary
)
)
} }
private fun updateSubjectColor(subjectView: TextView, lesson: Timetable) { private fun updateSubjectColor(subjectView: TextView, lesson: Timetable) {
subjectView.setTextColor(subjectView.context.getThemeAttrColor( subjectView.setTextColor(
if (lesson.subjectOld.isNotBlank() && lesson.subjectOld != lesson.subject) R.attr.colorTimetableChange subjectView.context.getThemeAttrColor(
else android.R.attr.textColorPrimary if (lesson.subjectOld.isNotBlank() && lesson.subjectOld != lesson.subject) R.attr.colorTimetableChange
)) else android.R.attr.textColorPrimary
)
)
} }
private fun updateRoomColor(roomView: TextView, lesson: Timetable) { private fun updateRoomColor(roomView: TextView, lesson: Timetable) {
roomView.setTextColor(roomView.context.getThemeAttrColor( roomView.setTextColor(
if (lesson.roomOld.isNotBlank() && lesson.roomOld != lesson.room) R.attr.colorTimetableChange roomView.context.getThemeAttrColor(
else android.R.attr.textColorSecondary if (lesson.roomOld.isNotBlank() && lesson.roomOld != lesson.room) R.attr.colorTimetableChange
)) else android.R.attr.textColorSecondary
)
)
} }
private fun updateTeacherColor(teacherTextView: TextView, lesson: Timetable) { private fun updateTeacherColor(teacherTextView: TextView, lesson: Timetable) {
teacherTextView.setTextColor(teacherTextView.context.getThemeAttrColor( teacherTextView.setTextColor(
if (lesson.teacherOld.isNotBlank() && lesson.teacherOld != lesson.teacher) R.attr.colorTimetableChange teacherTextView.context.getThemeAttrColor(
else android.R.attr.textColorSecondary if (lesson.teacherOld.isNotBlank() && lesson.teacherOld != lesson.teacher) R.attr.colorTimetableChange
)) else android.R.attr.textColorSecondary
)
)
} }
private class ItemViewHolder(val binding: ItemTimetableBinding) : private class ItemViewHolder(val binding: ItemTimetableBinding) :
@ -301,4 +371,21 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
private class SmallItemViewHolder(val binding: ItemTimetableSmallBinding) : private class SmallItemViewHolder(val binding: ItemTimetableSmallBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
class TimetableAdapterDiffCallback(
private val oldList: List<Timetable>,
private val newList: List<Timetable>,
private val isFlagsDifferent: Boolean
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].id == newList[newItemPosition].id
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition] && !isFlagsDifferent
}
} }

View File

@ -49,7 +49,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
override val titleStringId get() = R.string.timetable_title override val titleStringId get() = R.string.timetable_title
override val isViewEmpty get() = timetableAdapter.items.isEmpty() override val isViewEmpty get() = timetableAdapter.itemCount > 0
override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
@ -109,20 +109,16 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
showGroupsInPlanType: Boolean, showGroupsInPlanType: Boolean,
showTimetableTimers: Boolean showTimetableTimers: Boolean
) { ) {
with(timetableAdapter) { timetableAdapter.submitList(
items = data.toMutableList() newTimetable = data.toMutableList(),
showTimers = showTimetableTimers showGroupsInPlan = showGroupsInPlanType,
showTimers = showTimetableTimers,
showWholeClassPlan = showWholeClassPlanType showWholeClassPlan = showWholeClassPlanType
showGroupsInPlan = showGroupsInPlanType )
notifyDataSetChanged()
}
} }
override fun clearData() { override fun clearData() {
with(timetableAdapter) { timetableAdapter.submitList(listOf())
items = mutableListOf()
notifyDataSetChanged()
}
} }
override fun updateNavigationDay(date: String) { override fun updateNavigationDay(date: String) {
@ -226,7 +222,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
} }
override fun onDestroyView() { override fun onDestroyView() {
timetableAdapter.resetTimers() timetableAdapter.clearTimers()
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()
} }