[Grades] Implement showing unseen badges and marking as seen. Change default "hide improved" config value.

This commit is contained in:
Kuba Szczodrzyński 2020-03-09 20:18:11 +01:00
parent 098beb14fe
commit 42ef40439e
16 changed files with 248 additions and 71 deletions

View File

@ -28,7 +28,7 @@ class ProfileConfigGrades(private val config: ProfileConfig) {
private var mHideImproved: Boolean? = null private var mHideImproved: Boolean? = null
var hideImproved: Boolean var hideImproved: Boolean
get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", true); return mHideImproved ?: true } get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false }
set(value) { config.set("hideImproved", value); mHideImproved = value } set(value) { config.set("hideImproved", value); mHideImproved = value }
private var mAverageWithoutWeight: Boolean? = null private var mAverageWithoutWeight: Boolean? = null

View File

@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.data.db.entity
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index import androidx.room.Index
/*public Grade(int profileId, long id, String category, int color, String description, String name, float value, float weight, int semester, long teacherId, long subjectId) { /*public Grade(int profileId, long id, String category, int color, String description, String name, float value, float weight, int semester, long teacherId, long subjectId) {
@ -81,5 +82,11 @@ open class Grade(
*/ */
@ColumnInfo(name = "gradeIsImprovement") @ColumnInfo(name = "gradeIsImprovement")
var isImprovement = false var isImprovement = false
@Ignore
var showAsUnseen = false
val isImproved
get() = parentId ?: -1L != -1L
} }

View File

@ -10,19 +10,24 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView 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.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.onClick import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.startCoroutineTimer
import pl.szczodrzynski.edziennik.ui.modules.grades.models.* import pl.szczodrzynski.edziennik.ui.modules.grades.models.*
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.* import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.*
import kotlin.coroutines.CoroutineContext
class GradesAdapter( class GradesAdapter(
val activity: AppCompatActivity, val activity: AppCompatActivity,
var onGradeClick: ((item: GradeFull) -> Unit)? = null, var onGradeClick: ((item: GradeFull) -> Unit)? = null,
var onGradesEditorClick: ((subject: GradesSubject, semester: GradesSemester) -> Unit)? = null var onGradesEditorClick: ((subject: GradesSubject, semester: GradesSemester) -> Unit)? = null
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope {
companion object { companion object {
private const val TAG = "GradesAdapter" private const val TAG = "GradesAdapter"
private const val ITEM_TYPE_SUBJECT = 0 private const val ITEM_TYPE_SUBJECT = 0
@ -34,6 +39,13 @@ class GradesAdapter(
const val STATE_OPENED = 1 const val STATE_OPENED = 1
} }
private val app = activity.applicationContext as App
private val manager = app.gradesManager
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
var items = mutableListOf<Any>() var items = mutableListOf<Any>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@ -67,13 +79,17 @@ class GradesAdapter(
} }
if (model !is ExpandableItemModel<*>) if (model !is ExpandableItemModel<*>)
return@OnClickListener return@OnClickListener
expandModel(model, view)
}
fun expandModel(model: ExpandableItemModel<*>?, view: View?, notifyAdapter: Boolean = true) {
model ?: return
val position = items.indexOf(model) val position = items.indexOf(model)
if (position == -1) if (position == -1)
return@OnClickListener return
//val position = it.getTag(R.string.tag_key_position) as? Int ?: return@OnClickListener
if (model is GradesSubject || model is GradesSemester) { if (model is GradesSubject || model is GradesSemester) {
view.findViewById<View>(R.id.dropdownIcon)?.let { dropdownIcon -> view?.findViewById<View>(R.id.dropdownIcon)?.let { dropdownIcon ->
ObjectAnimator.ofFloat( ObjectAnimator.ofFloat(
dropdownIcon, dropdownIcon,
View.ROTATION, View.ROTATION,
@ -83,40 +99,43 @@ class GradesAdapter(
} }
} }
if (model is GradesSubject) { if (model is GradesSubject) {
val preview = view.findViewById<View>(R.id.previewContainer) val preview = view?.findViewById<View>(R.id.previewContainer)
val summary = view.findViewById<View>(R.id.yearSummary) val summary = view?.findViewById<View>(R.id.yearSummary)
preview?.visibility = if (model.state == STATE_CLOSED) View.INVISIBLE else View.VISIBLE preview?.visibility = if (model.state == STATE_CLOSED) View.INVISIBLE else View.VISIBLE
summary?.visibility = if (model.state == STATE_CLOSED) View.VISIBLE else View.INVISIBLE summary?.visibility = if (model.state == STATE_CLOSED) View.VISIBLE else View.INVISIBLE
} }
if (model.state == STATE_CLOSED) { if (model.state == STATE_CLOSED) {
val subItems = if (model is GradesSemester && model.grades.isEmpty()) val subItems = when {
model is GradesSemester && model.grades.isEmpty() ->
listOf(GradesEmpty()) listOf(GradesEmpty())
else model is GradesSemester && manager.hideImproved ->
model.items model.items.filter { !it.seen || !it.isImproved }
else -> model.items
}
model.state = STATE_OPENED model.state = STATE_OPENED
items.addAll(position + 1, subItems.filterNotNull()) items.addAll(position + 1, subItems.filterNotNull())
notifyItemRangeInserted(position + 1, subItems.size) if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size)
/*notifyItemRangeChanged(
position + subItems.size,
items.size - (position + subItems.size)
)*/
//notifyItemRangeChanged(position, items.size - position)
if (model is GradesSubject) { if (model is GradesSubject) {
// auto expand first semester // auto expand first semester
if (model.semesters.isNotEmpty()) { if (model.semesters.isNotEmpty()) {
val semester = model.semesters.firstOrNull { it.grades.isNotEmpty() } ?: model.semesters.first() val semester = model.semesters.firstOrNull { it.grades.isNotEmpty() } ?: model.semesters.first()
val semesterIndex = model.semesters.indexOf(semester) val semesterIndex = model.semesters.indexOf(semester)
val grades = if (semester.grades.isEmpty())
val grades = when {
semester.grades.isEmpty() ->
listOf(GradesEmpty()) listOf(GradesEmpty())
else manager.hideImproved ->
semester.grades semester.grades.filter { !it.seen || !it.isImproved }
else -> semester.grades
}
semester.state = STATE_OPENED semester.state = STATE_OPENED
items.addAll(position + 2 + semesterIndex, grades) items.addAll(position + 2 + semesterIndex, grades)
notifyItemRangeInserted(position + 2 + semesterIndex, grades.size) if (notifyAdapter) notifyItemRangeInserted(position + 2 + semesterIndex, grades.size)
} }
} }
} }
@ -138,9 +157,7 @@ class GradesAdapter(
if (end != -1) { if (end != -1) {
items.subList(start, end).clear() items.subList(start, end).clear()
notifyItemRangeRemoved(start, end - start) if (notifyAdapter) notifyItemRangeRemoved(start, end - start)
//notifyItemRangeChanged(start, end - start)
//notifyItemRangeChanged(position, items.size - position)
} }
model.state = STATE_CLOSED model.state = STATE_CLOSED
@ -152,8 +169,6 @@ class GradesAdapter(
if (holder !is BindableViewHolder<*>) if (holder !is BindableViewHolder<*>)
return return
val app = activity.applicationContext as App
val viewType = when (holder) { val viewType = when (holder) {
is SubjectViewHolder -> ITEM_TYPE_SUBJECT is SubjectViewHolder -> ITEM_TYPE_SUBJECT
is SemesterViewHolder -> ITEM_TYPE_SEMESTER is SemesterViewHolder -> ITEM_TYPE_SEMESTER
@ -167,11 +182,11 @@ class GradesAdapter(
holder.itemView.setTag(R.string.tag_key_model, item) holder.itemView.setTag(R.string.tag_key_model, item)
when { when {
holder is SubjectViewHolder && item is GradesSubject -> holder.onBind(activity, app, item, position) holder is SubjectViewHolder && item is GradesSubject -> holder.onBind(activity, app, item, position, this)
holder is SemesterViewHolder && item is GradesSemester -> holder.onBind(activity, app, item, position) holder is SemesterViewHolder && item is GradesSemester -> holder.onBind(activity, app, item, position, this)
holder is EmptyViewHolder && item is GradesEmpty -> holder.onBind(activity, app, item, position) holder is EmptyViewHolder && item is GradesEmpty -> holder.onBind(activity, app, item, position, this)
holder is GradeViewHolder && item is GradeFull -> holder.onBind(activity, app, item, position) holder is GradeViewHolder && item is GradeFull -> holder.onBind(activity, app, item, position, this)
holder is StatsViewHolder && item is GradesStats -> holder.onBind(activity, app, item, position) holder is StatsViewHolder && item is GradesStats -> holder.onBind(activity, app, item, position, this)
} }
if (holder is SemesterViewHolder && item is GradesSemester) { if (holder is SemesterViewHolder && item is GradesSemester) {
@ -184,5 +199,23 @@ class GradesAdapter(
holder.itemView.setOnClickListener(onClickListener) holder.itemView.setOnClickListener(onClickListener)
} }
fun notifyItemChanged(model: Any) {
startCoroutineTimer(1000L, 0L) {
val index = items.indexOf(model)
if (index != -1)
notifyItemChanged(index)
}
}
fun removeItem(model: Any) {
startCoroutineTimer(2000L, 0L) {
val index = items.indexOf(model)
if (index != -1) {
items.removeAt(index)
notifyItemRemoved(index)
}
}
}
override fun getItemCount() = items.size override fun getItemCount() = items.size
} }

View File

@ -12,11 +12,8 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.coroutines.* import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.Bundle
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.MainActivity.Companion.TARGET_GRADES_EDITOR import pl.szczodrzynski.edziennik.MainActivity.Companion.TARGET_GRADES_EDITOR
import pl.szczodrzynski.edziennik.averageOrNull
import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.GradesFragmentBinding import pl.szczodrzynski.edziennik.databinding.GradesFragmentBinding
@ -48,6 +45,7 @@ class GradesFragment : Fragment(), CoroutineScope {
} }
private val manager by lazy { app.gradesManager } private val manager by lazy { app.gradesManager }
private val dontCountGrades by lazy { manager.dontCountGrades } private val dontCountGrades by lazy { manager.dontCountGrades }
private var expandSubjectId = 0L
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
@ -62,6 +60,8 @@ class GradesFragment : Fragment(), CoroutineScope {
if (!isAdded) if (!isAdded)
return return
expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L
app.db.gradeDao() app.db.gradeDao()
.getAllOrderBy(App.profileId, app.gradesManager.getOrderByString()) .getAllOrderBy(App.profileId, app.gradesManager.getOrderByString())
.observe(this, Observer { grades -> .observe(this, Observer { grades ->
@ -113,6 +113,7 @@ class GradesFragment : Fragment(), CoroutineScope {
} }
} }
@Suppress("SuspendFunctionOnCoroutineScope")
private suspend fun processGrades(grades: List<GradeFull>) { private suspend fun processGrades(grades: List<GradeFull>) {
val items = mutableListOf<GradesSubject>() val items = mutableListOf<GradesSubject>()
@ -146,6 +147,11 @@ class GradesFragment : Fragment(), CoroutineScope {
?: GradesSemester(subject.subjectId, grade.semester).also { subject.semesters += it } ?: GradesSemester(subject.subjectId, grade.semester).also { subject.semesters += it }
} }
grade.showAsUnseen = !grade.seen
if (!grade.seen) {
semester.hasUnseen = true
}
when (grade.type) { when (grade.type) {
Grade.TYPE_SEMESTER1_PROPOSED, Grade.TYPE_SEMESTER1_PROPOSED,
Grade.TYPE_SEMESTER2_PROPOSED -> semester.proposedGrade = grade Grade.TYPE_SEMESTER2_PROPOSED -> semester.proposedGrade = grade
@ -154,10 +160,7 @@ class GradesFragment : Fragment(), CoroutineScope {
Grade.TYPE_YEAR_PROPOSED -> subject.proposedGrade = grade Grade.TYPE_YEAR_PROPOSED -> subject.proposedGrade = grade
Grade.TYPE_YEAR_FINAL -> subject.finalGrade = grade Grade.TYPE_YEAR_FINAL -> subject.finalGrade = grade
else -> { else -> {
if (!hideImproved || grade.parentId ?: -1L == -1L) {
// hide improved grades if parent(new grade) ID is not set
semester.grades += grade semester.grades += grade
}
countGrade(grade, subject.averages) countGrade(grade, subject.averages)
countGrade(grade, semester.averages) countGrade(grade, semester.averages)
} }
@ -242,9 +245,27 @@ class GradesFragment : Fragment(), CoroutineScope {
adapter.items = items.toMutableList() adapter.items = items.toMutableList()
adapter.items.add(stats) adapter.items.add(stats)
var expandSubjectModel: GradesSubject? = null
if (expandSubjectId != 0L) {
expandSubjectModel = items.firstOrNull { it.subjectId == expandSubjectId }
adapter.expandModel(
model = expandSubjectModel,
view = null,
notifyAdapter = false
)
}
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
startCoroutineTimer(500L, 0L) {
if (expandSubjectModel != null) {
b.gradesRecyclerView.smoothScrollToPosition(
items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0)
)
}
}
} }
private fun countGrade(grade: Grade, averages: GradesAverages) { private fun countGrade(grade: Grade, averages: GradesAverages) {

View File

@ -4,16 +4,17 @@
package pl.szczodrzynski.edziennik.ui.modules.grades.models package pl.szczodrzynski.edziennik.ui.modules.grades.models
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.data.db.full.GradeFull
data class GradesSemester( data class GradesSemester(
val subjectId: Long, val subjectId: Long,
val number: Int, val number: Int,
val grades: MutableList<Grade> = mutableListOf() val grades: MutableList<GradeFull> = mutableListOf()
) : ExpandableItemModel<Grade>(grades) { ) : ExpandableItemModel<GradeFull>(grades) {
override var level = 2 override var level = 2
var hasUnseen = false
val averages = GradesAverages() val averages = GradesAverages()
var proposedGrade: GradeFull? = null var proposedGrade: GradeFull? = null
var finalGrade: GradeFull? = null var finalGrade: GradeFull? = null

View File

@ -16,6 +16,9 @@ data class GradesSubject(
var lastAddedDate = 0L var lastAddedDate = 0L
var semester: Int = 1 var semester: Int = 1
val hasUnseen
get() = semesters.any { it.hasUnseen }
val averages = GradesAverages() val averages = GradesAverages()
var proposedGrade: GradeFull? = null var proposedGrade: GradeFull? = null
var finalGrade: GradeFull? = null var finalGrade: GradeFull? = null

View File

@ -6,7 +6,8 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.viewholder
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
interface BindableViewHolder<T> { interface BindableViewHolder<T> {
fun onBind(activity: AppCompatActivity, app: App, item: T, position: Int) fun onBind(activity: AppCompatActivity, app: App, item: T, position: Int, adapter: GradesAdapter)
} }

View File

@ -10,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.databinding.GradesItemEmptyBinding import pl.szczodrzynski.edziennik.databinding.GradesItemEmptyBinding
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesEmpty import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesEmpty
class EmptyViewHolder( class EmptyViewHolder(
@ -21,7 +22,7 @@ class EmptyViewHolder(
private const val TAG = "EmptyViewHolder" private const val TAG = "EmptyViewHolder"
} }
override fun onBind(activity: AppCompatActivity, app: App, item: GradesEmpty, position: Int) { override fun onBind(activity: AppCompatActivity, app: App, item: GradesEmpty, position: Int, adapter: GradesAdapter) {
} }
} }

View File

@ -7,11 +7,14 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.viewholder
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.GradesItemGradeBinding import pl.szczodrzynski.edziennik.databinding.GradesItemGradeBinding
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class GradeViewHolder( class GradeViewHolder(
@ -24,7 +27,7 @@ class GradeViewHolder(
} }
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override fun onBind(activity: AppCompatActivity, app: App, grade: GradeFull, position: Int) { override fun onBind(activity: AppCompatActivity, app: App, grade: GradeFull, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager val manager = app.gradesManager
b.gradeName.setGrade(grade, manager, bigView = true) b.gradeName.setGrade(grade, manager, bigView = true)
@ -45,19 +48,35 @@ class GradeViewHolder(
grade.category grade.category
} }
b.gradeWeight.text = manager.getWeightString(activity, grade, showClassAverage = true) val weightText = manager.getWeightString(activity, grade, showClassAverage = true)
b.gradeWeight.text = weightText
b.gradeWeight.isVisible = weightText != null
b.gradeTeacherName.text = grade.teacherFullName b.gradeTeacherName.text = grade.teacherFullName
b.gradeAddedDate.text = Date.fromMillis(grade.addedDate).let { b.gradeAddedDate.text = Date.fromMillis(grade.addedDate).let {
it.getRelativeString(app, 5) ?: it.formattedStringShort it.getRelativeString(app, 5) ?: it.formattedStringShort
} }
/*if (!grade.seen) { b.unread.isVisible = grade.showAsUnseen
b.gradeDescription.setBackground(mContext.getResources().getDrawable(R.drawable.bg_rounded_4dp)) if (!grade.seen) {
b.gradeDescription.getBackground() manager.markAsSeen(grade)
.setColorFilter(PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)) val subject = adapter.items.firstOrNull {
} else { it is GradesSubject && it.subjectId == grade.subjectId
b.gradeDescription.setBackground(null) } as? GradesSubject ?: return
}*/
val semester = subject.semesters.firstOrNull { it.number == grade.semester } ?: return
semester.hasUnseen = semester.grades.any { !it.seen }
// check if the unseen status has changed
if (!semester.hasUnseen) {
adapter.notifyItemChanged(semester)
}
if (!subject.hasUnseen) {
adapter.notifyItemChanged(subject)
}
if (manager.hideImproved && grade.isImproved) {
adapter.removeItem(grade)
}
}
} }
} }

View File

@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.viewholder
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
@ -14,6 +15,7 @@ import pl.szczodrzynski.edziennik.databinding.GradesItemSemesterBinding
import pl.szczodrzynski.edziennik.setText import pl.szczodrzynski.edziennik.setText
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSemester import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSemester
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject
class SemesterViewHolder( class SemesterViewHolder(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -24,7 +26,7 @@ class SemesterViewHolder(
private const val TAG = "SemesterViewHolder" private const val TAG = "SemesterViewHolder"
} }
override fun onBind(activity: AppCompatActivity, app: App, item: GradesSemester, position: Int) { override fun onBind(activity: AppCompatActivity, app: App, item: GradesSemester, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager val manager = app.gradesManager
b.semesterName.setText(R.string.grades_semester_format, item.number) b.semesterName.setText(R.string.grades_semester_format, item.number)
b.dropdownIcon.rotation = when (item.state) { b.dropdownIcon.rotation = when (item.state) {
@ -32,6 +34,33 @@ class SemesterViewHolder(
else -> 180f else -> 180f
} }
b.unread.isVisible = item.hasUnseen
var unseenChanged = false
if (item.proposedGrade?.seen == false) {
manager.markAsSeen(item.proposedGrade!!)
unseenChanged = true
}
if (item.finalGrade?.seen == false) {
manager.markAsSeen(item.finalGrade!!)
unseenChanged = true
}
if (unseenChanged) {
val subject = adapter.items.firstOrNull {
it is GradesSubject && it.subjectId == item.subjectId
} as? GradesSubject ?: return
item.hasUnseen = item.grades.any { !it.seen }
// check if the unseen status has changed
if (!item.hasUnseen) {
adapter.notifyItemChanged(item)
}
if (!subject.hasUnseen) {
adapter.notifyItemChanged(subject)
}
}
b.average.text = manager.getAverageString(app, item.averages) b.average.text = manager.getAverageString(app, item.averages)
b.proposedGrade.setGrade(item.proposedGrade, manager) b.proposedGrade.setGrade(item.proposedGrade, manager)
b.finalGrade.setGrade(item.finalGrade, manager) b.finalGrade.setGrade(item.finalGrade, manager)

View File

@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.GradesItemStatsBinding import pl.szczodrzynski.edziennik.databinding.GradesItemStatsBinding
import pl.szczodrzynski.edziennik.onClick import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesStats import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesStats
import java.text.DecimalFormat import java.text.DecimalFormat
@ -28,7 +29,7 @@ class StatsViewHolder(
private const val TAG = "StatsViewHolder" private const val TAG = "StatsViewHolder"
} }
override fun onBind(activity: AppCompatActivity, app: App, item: GradesStats, position: Int) { override fun onBind(activity: AppCompatActivity, app: App, item: GradesStats, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager val manager = app.gradesManager
val showAverages = mutableListOf<Int>() val showAverages = mutableListOf<Int>()
val showPoint = mutableListOf<Int>() val showPoint = mutableListOf<Int>()
@ -109,7 +110,7 @@ class StatsViewHolder(
} }
private fun getSemesterString(context: Context, expected: Float, proposed: Float, final: Float, notAllFinal: Boolean) : Pair<String?, String?> { private fun getSemesterString(context: Context, expected: Float, proposed: Float, final: Float, notAllFinal: Boolean) : Pair<String?, String?> {
val format = DecimalFormat("#.##") val format = DecimalFormat("#.00")
val average = when { val average = when {
final != 0f -> final final != 0f -> final

View File

@ -14,6 +14,7 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.get import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
@ -21,6 +22,7 @@ import pl.szczodrzynski.edziennik.databinding.GradesItemSubjectBinding
import pl.szczodrzynski.edziennik.dp import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.setText import pl.szczodrzynski.edziennik.setText
import pl.szczodrzynski.edziennik.ui.modules.grades.GradeView import pl.szczodrzynski.edziennik.ui.modules.grades.GradeView
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter.Companion.STATE_CLOSED import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter.Companion.STATE_CLOSED
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
@ -34,7 +36,7 @@ class SubjectViewHolder(
private const val TAG = "SubjectViewHolder" private const val TAG = "SubjectViewHolder"
} }
override fun onBind(activity: AppCompatActivity, app: App, item: GradesSubject, position: Int) { override fun onBind(activity: AppCompatActivity, app: App, item: GradesSubject, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager val manager = app.gradesManager
val contextWrapper = ContextThemeWrapper(activity, Themes.themeInt) val contextWrapper = ContextThemeWrapper(activity, Themes.themeInt)
@ -44,6 +46,8 @@ class SubjectViewHolder(
else -> 180f else -> 180f
} }
b.unread.isVisible = item.hasUnseen
b.previewContainer.visibility = if (item.state == STATE_CLOSED) View.VISIBLE else View.INVISIBLE b.previewContainer.visibility = if (item.state == STATE_CLOSED) View.VISIBLE else View.INVISIBLE
b.yearSummary.visibility = if (item.state == STATE_CLOSED) View.INVISIBLE else View.VISIBLE b.yearSummary.visibility = if (item.state == STATE_CLOSED) View.INVISIBLE else View.VISIBLE
@ -71,7 +75,10 @@ class SubjectViewHolder(
}) })
}*/ }*/
val hideImproved = manager.hideImproved
for (grade in firstSemester.grades) { for (grade in firstSemester.grades) {
if (hideImproved && grade.isImproved)
continue
b.gradesContainer.addView(GradeView( b.gradesContainer.addView(GradeView(
contextWrapper, contextWrapper,
grade, grade,

View File

@ -5,18 +5,23 @@
package pl.szczodrzynski.edziennik.utils.managers package pl.szczodrzynski.edziennik.utils.managers
import android.content.Context import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesAverages import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesAverages
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSemester import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSemester
import java.text.DecimalFormat import java.text.DecimalFormat
import kotlin.coroutines.CoroutineContext
import kotlin.math.floor import kotlin.math.floor
class GradesManager(val app: App) { class GradesManager(val app: App) : CoroutineScope {
companion object { companion object {
const val ORDER_BY_DATE_DESC = 0 const val ORDER_BY_DATE_DESC = 0
const val ORDER_BY_SUBJECT_ASC = 1 const val ORDER_BY_SUBJECT_ASC = 1
@ -31,6 +36,10 @@ class GradesManager(val app: App) {
const val COLOR_MODE_WEIGHTED = 1 const val COLOR_MODE_WEIGHTED = 1
} }
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default
private val gradeRegex by lazy { """([0-6])([+-])?""".toRegex() } private val gradeRegex by lazy { """([0-6])([+-])?""".toRegex() }
private val format = DecimalFormat("#.##") private val format = DecimalFormat("#.##")
@ -144,6 +153,13 @@ class GradesManager(val app: App) {
return color or 0xff000000.toInt() return color or 0xff000000.toInt()
} }
fun markAsSeen(grade: GradeFull) {
grade.seen = true
startCoroutineTimer(500L, 0L) {
app.db.metadataDao().setSeen(grade.profileId, grade, true)
}
}
fun calculateAverages(averages: GradesAverages, semesters: List<GradesSemester>? = null) { fun calculateAverages(averages: GradesAverages, semesters: List<GradesSemester>? = null) {
if (averages.pointAvgMax != 0f) if (averages.pointAvgMax != 0f)
averages.pointAvgPercent = averages.pointAvgSum / averages.pointAvgMax * 100f averages.pointAvgPercent = averages.pointAvgSum / averages.pointAvgMax * 100f

View File

@ -20,6 +20,10 @@
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
tools:background="@drawable/bg_rounded_8dp"
tools:backgroundTint="#4caf50"
tools:textSize="24sp"
tools:gravity="center"
tools:text="5+" /> tools:text="5+" />
<LinearLayout <LinearLayout
@ -30,6 +34,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
@ -38,12 +43,22 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="4dp"
android:layout_marginRight="8dp" android:layout_marginRight="4dp"
android:layout_weight="1" android:layout_weight="1"
android:ellipsize="end" android:ellipsize="end"
android:singleLine="true" android:singleLine="true"
tools:text="kraje hehe no jak zwykle jedynka z geografii. to jest baaardzo długi tekst ale szkoda że się nie scrolluje." /> tools:text="kraje" />
<View
android:id="@+id/unread"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:visibility="gone"
android:background="@drawable/unread_red_circle"
tools:visibility="visible"/>
<TextView <TextView
android:id="@+id/gradeAddedDate" android:id="@+id/gradeAddedDate"
@ -82,7 +97,7 @@
android:maxWidth="200dp" android:maxWidth="200dp"
android:maxLines="1" android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Kartkówki - K1 123456789 12345678" /> tools:text="Kartkówki - K1" />
<TextView <TextView
android:id="@+id/gradeTeacherName" android:id="@+id/gradeTeacherName"
@ -95,7 +110,7 @@
android:gravity="end" android:gravity="end"
android:maxLines="1" android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Anna Jakaśtam-Cośtam" /> tools:text="Jan Kowalski" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -37,6 +37,17 @@
android:textSize="18sp" android:textSize="18sp"
tools:text="Semestr 1" /> tools:text="Semestr 1" />
<View
android:id="@+id/unread"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="gone"
android:background="@drawable/unread_red_circle"
tools:visibility="visible"/>
<TextView <TextView
android:id="@+id/average" android:id="@+id/average"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -20,7 +20,8 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView <TextView
android:id="@+id/subjectName" android:id="@+id/subjectName"
@ -33,7 +34,18 @@
android:maxLines="2" android:maxLines="2"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="20sp" android:textSize="20sp"
tools:text="systemy operacyjne\n1234" /> tools:text="systemy operacyjne" />
<View
android:id="@+id/unread"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="gone"
android:background="@drawable/unread_red_circle"
tools:visibility="visible"/>
<com.mikepenz.iconics.view.IconicsImageView <com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/dropdownIcon" android:id="@+id/dropdownIcon"
@ -85,7 +97,7 @@
tools:text1="Cały rok: 3 oceny • suma: 320 pkt" tools:text1="Cały rok: 3 oceny • suma: 320 pkt"
tools:text2="Cały rok: 15 ocen • średnia: 2,62" tools:text2="Cały rok: 15 ocen • średnia: 2,62"
tools:text="Cały rok: 6 ocen • punkty: 34.20/40 (87.5%)" tools:text="Cały rok: 6 ocen • punkty: 34.20/40 (87.5%)"
tools:visibility="gone"/> tools:visibility="visible"/>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
</layout> </layout>