[UI] Add listing attendance by type.

This commit is contained in:
Kuba Szczodrzyński 2020-05-08 22:19:55 +02:00
parent c49755c0eb
commit 192dd0c4c7
19 changed files with 280 additions and 25 deletions

View File

@ -15,6 +15,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSEN
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_BELATED
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT_CUSTOM
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_UNKNOWN
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
@ -132,13 +133,22 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
}
entry = entry.removePrefix(typeSymbol)
var isCounted = true
val baseType = when (typeSymbol) {
"." -> TYPE_PRESENT
"|" -> TYPE_ABSENT
"+" -> TYPE_ABSENT_EXCUSED
"s" -> TYPE_BELATED
"z" -> TYPE_RELEASED
else -> TYPE_UNKNOWN
else -> {
isCounted = false
when (typeSymbol) {
"e" -> TYPE_PRESENT_CUSTOM
"en" -> TYPE_ABSENT
"ep" -> TYPE_PRESENT_CUSTOM
else -> TYPE_UNKNOWN
}
}
}
val typeName = types?.get(typeSymbol) ?: ""
@ -166,6 +176,7 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
subjectId = subjectId
).also {
it.lessonTopic = topic
it.isCounted = isCounted
}
data.attendanceList.add(attendanceObject)

View File

@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
LibrusLesson::class,
TimetableManual::class,
Metadata::class
], version = 86)
], version = 87)
@TypeConverters(
ConverterTime::class,
ConverterDate::class,
@ -171,7 +171,8 @@ abstract class AppDb : RoomDatabase() {
Migration83(),
Migration84(),
Migration85(),
Migration86()
Migration86(),
Migration87()
).allowMainThreadQueries().build()
}
}

View File

@ -65,6 +65,8 @@ open class Attendance(
var lessonTopic: String? = null
@ColumnInfo(name = "attendanceLessonNumber")
var lessonNumber: Int? = null
@ColumnInfo(name = "attendanceIsCounted")
var isCounted: Boolean = true
@Ignore
var showAsUnseen: Boolean? = null

View File

@ -0,0 +1,14 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-5-8.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration87 : Migration(86, 87) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE attendances ADD COLUMN attendanceIsCounted INTEGER NOT NULL DEFAULT 1")
}
}

View File

@ -19,10 +19,7 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
import pl.szczodrzynski.edziennik.startCoroutineTimer
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceEmpty
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.*
import pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder.*
import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
@ -39,7 +36,8 @@ class AttendanceAdapter(
private const val ITEM_TYPE_DAY_RANGE = 1
private const val ITEM_TYPE_MONTH = 2
private const val ITEM_TYPE_SUBJECT = 3
private const val ITEM_TYPE_EMPTY = 4
private const val ITEM_TYPE_TYPE = 4
private const val ITEM_TYPE_EMPTY = 5
const val STATE_CLOSED = 0
const val STATE_OPENED = 1
}
@ -60,6 +58,7 @@ class AttendanceAdapter(
ITEM_TYPE_DAY_RANGE -> DayRangeViewHolder(inflater, parent)
ITEM_TYPE_MONTH -> MonthViewHolder(inflater, parent)
ITEM_TYPE_SUBJECT -> SubjectViewHolder(inflater, parent)
ITEM_TYPE_TYPE -> TypeViewHolder(inflater, parent)
ITEM_TYPE_EMPTY -> EmptyViewHolder(inflater, parent)
else -> throw IllegalArgumentException("Incorrect viewType")
}
@ -71,6 +70,7 @@ class AttendanceAdapter(
is AttendanceDayRange -> ITEM_TYPE_DAY_RANGE
is AttendanceMonth -> ITEM_TYPE_MONTH
is AttendanceSubject -> ITEM_TYPE_SUBJECT
is AttendanceTypeGroup -> ITEM_TYPE_TYPE
is AttendanceEmpty -> ITEM_TYPE_EMPTY
else -> throw IllegalArgumentException("Incorrect viewType")
}
@ -102,7 +102,7 @@ class AttendanceAdapter(
).setDuration(200).start();
}
if (model is AttendanceDayRange || model is AttendanceMonth) {
if (model is AttendanceDayRange || model is AttendanceMonth || model is AttendanceTypeGroup) {
// hide the preview, show summary
val preview = view?.findViewById<View>(R.id.previewContainer)
val summary = view?.findViewById<View>(R.id.summaryContainer)
@ -158,6 +158,7 @@ class AttendanceAdapter(
is DayRangeViewHolder -> ITEM_TYPE_DAY_RANGE
is MonthViewHolder -> ITEM_TYPE_MONTH
is SubjectViewHolder -> ITEM_TYPE_SUBJECT
is TypeViewHolder -> ITEM_TYPE_TYPE
is EmptyViewHolder -> ITEM_TYPE_EMPTY
else -> throw IllegalArgumentException("Incorrect viewType")
}
@ -170,6 +171,7 @@ class AttendanceAdapter(
holder is DayRangeViewHolder && item is AttendanceDayRange -> holder.onBind(activity, app, item, position, this)
holder is MonthViewHolder && item is AttendanceMonth -> holder.onBind(activity, app, item, position, this)
holder is SubjectViewHolder && item is AttendanceSubject -> holder.onBind(activity, app, item, position, this)
holder is TypeViewHolder && item is AttendanceTypeGroup -> holder.onBind(activity, app, item, position, this)
holder is EmptyViewHolder && item is AttendanceEmpty -> holder.onBind(activity, app, item, position, this)
}

View File

@ -27,10 +27,11 @@ import kotlin.coroutines.CoroutineContext
class AttendanceFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "AttendanceFragment"
const val VIEW_DAYS = 0
const val VIEW_MONTHS = 1
const val VIEW_SUMMARY = 2
const val VIEW_LIST = 3
const val VIEW_SUMMARY = 0
const val VIEW_DAYS = 1
const val VIEW_MONTHS = 2
const val VIEW_TYPES = 3
const val VIEW_LIST = 4
var pageSelection = 1
}
@ -93,6 +94,10 @@ class AttendanceFragment : Fragment(), CoroutineScope {
arguments = Bundle("viewType" to VIEW_MONTHS)
} to getString(R.string.attendance_tab_months),
AttendanceListFragment().apply {
arguments = Bundle("viewType" to VIEW_TYPES)
} to getString(R.string.attendance_tab_types),
AttendanceListFragment().apply {
arguments = Bundle("viewType" to VIEW_LIST)
} to getString(R.string.attendance_tab_list)

View File

@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import pl.szczodrzynski.edziennik.startCoroutineTimer
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceTypeGroup
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject
import pl.szczodrzynski.edziennik.utils.models.Date
@ -146,7 +147,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope {
var element = iterator.next()
while (iterator.hasNext()) {
var nextElement = iterator.next()
while (Date.diffDays(element.rangeStart, nextElement.rangeStart) <= 1) {
while (Date.diffDays(element.rangeStart, nextElement.rangeStart) <= 1 && iterator.hasNext()) {
if (element.rangeEnd == null)
element.rangeEnd = element.rangeStart
@ -173,7 +174,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope {
items.forEach { month ->
month.typeCountMap = month.items
.groupBy { it.typeObject }
.map { it.key to it.value.size }
.map { it.key to it.value.count { a -> a.isCounted } }
.sortedBy { it.first }
.toMap()
@ -205,6 +206,25 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope {
return items.toMutableList()
}
else if (viewType == AttendanceFragment.VIEW_TYPES) {
val items = attendance
.groupBy { it.typeObject }
.map { AttendanceTypeGroup(
type = it.key,
items = it.value.toMutableList()
) }
items.forEach { type ->
type.percentage = if (attendance.isEmpty())
0f
else
type.items.size.toFloat() / attendance.size.toFloat() * 100f
type.semesterCount = type.items.count { it.semester == app.profile.currentSemester }
}
return items.toMutableList()
}
return attendance.filter { it.baseType != Attendance.TYPE_PRESENT }.toMutableList()
}
}

View File

@ -78,6 +78,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope {
b.list.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
isNestedScrollingEnabled = false
}
}
adapter.notifyDataSetChanged()
@ -183,7 +184,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope {
items.forEach { subject ->
subject.typeCountMap = subject.items
.groupBy { it.typeObject }
.map { it.key to it.value.size }
.map { it.key to it.value.count { a -> a.isCounted } }
.sortedBy { it.first }
.toMap()
@ -217,7 +218,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope {
val typeCountMap = attendance
.groupBy { it.typeObject }
.map { it.key to it.value.size }
.map { it.key to it.value.count { a -> a.isCounted } }
.sortedBy { it.first }
.toMap()

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-5-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.attendance.models
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
data class AttendanceTypeGroup(
val type: AttendanceType,
override val items: MutableList<AttendanceFull> = mutableListOf()
) : ExpandableItemModel<AttendanceFull>(items) {
override var level = 1
var lastAddedDate = 0L
var hasUnseen: Boolean = false
get() = field || items.any { it.baseType != Attendance.TYPE_PRESENT && !it.seen }
var percentage: Float = 0f
var semesterCount: Int = 0
}

View File

@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth
import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Week
class AttendanceViewHolder(
inflater: LayoutInflater,
@ -42,6 +43,7 @@ class AttendanceViewHolder(
b.type.text = item.typeName
b.subjectName.text = item.subjectLongName ?: item.lessonTopic
b.dateTime.text = listOf(
Week.getFullDayName(item.date.weekDay),
item.date.formattedStringShort,
item.startTime?.stringHM,
item.lessonNumber?.let { app.getString(R.string.attendance_lesson_number_format, it) }

View File

@ -16,7 +16,7 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.concat
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerBinding
import pl.szczodrzynski.edziennik.databinding.AttendanceItemDayRangeBinding
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView
@ -27,7 +27,7 @@ import pl.szczodrzynski.edziennik.utils.Themes
class DayRangeViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
val b: AttendanceItemContainerBinding = AttendanceItemContainerBinding.inflate(inflater, parent, false)
val b: AttendanceItemDayRangeBinding = AttendanceItemDayRangeBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<AttendanceDayRange, AttendanceAdapter> {
companion object {
private const val TAG = "DayRangeViewHolder"

View File

@ -15,7 +15,7 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerBarBinding
import pl.szczodrzynski.edziennik.databinding.AttendanceItemMonthBinding
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView
@ -27,7 +27,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date
class MonthViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
val b: AttendanceItemContainerBarBinding = AttendanceItemContainerBarBinding.inflate(inflater, parent, false)
val b: AttendanceItemMonthBinding = AttendanceItemMonthBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<AttendanceMonth, AttendanceAdapter> {
companion object {
private const val TAG = "MonthViewHolder"
@ -80,7 +80,7 @@ class MonthViewHolder(
it.setText(R.string.attendance_percentage_format, count/sum*100f)
it.setPadding(0, 0, 5.dp, 0)
})
layout.setPadding(0, 8.dp, 0, 8.dp)
layout.setPadding(0, 8.dp, 0, 0)
b.previewContainer.addView(layout)
}

View File

@ -12,7 +12,7 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerSubjectBinding
import pl.szczodrzynski.edziennik.databinding.AttendanceItemSubjectBinding
import pl.szczodrzynski.edziennik.setText
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED
@ -23,7 +23,7 @@ import pl.szczodrzynski.edziennik.utils.Themes
class SubjectViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
val b: AttendanceItemContainerSubjectBinding = AttendanceItemContainerSubjectBinding.inflate(inflater, parent, false)
val b: AttendanceItemSubjectBinding = AttendanceItemSubjectBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<AttendanceSubject, AttendanceAdapter> {
companion object {
private const val TAG = "SubjectViewHolder"

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-5-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.databinding.AttendanceItemTypeBinding
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceTypeGroup
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Date
class TypeViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
val b: AttendanceItemTypeBinding = AttendanceItemTypeBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<AttendanceTypeGroup, AttendanceAdapter> {
companion object {
private const val TAG = "TypeViewHolder"
}
override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceTypeGroup, position: Int, adapter: AttendanceAdapter) {
val manager = app.attendanceManager
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
val type = item.type
b.title.text = type.typeName
b.dropdownIcon.rotation = when (item.state) {
AttendanceAdapter.STATE_CLOSED -> 0f
else -> 180f
}
b.unread.isVisible = item.hasUnseen
b.previewContainer.visibility = if (item.state == AttendanceAdapter.STATE_CLOSED) View.VISIBLE else View.INVISIBLE
b.type.setAttendance(Attendance(
profileId = 0,
id = 0,
baseType = type.baseType,
typeName = "",
typeShort = type.typeShort,
typeSymbol = type.typeSymbol,
typeColor = type.typeColor,
date = Date(0, 0, 0),
startTime = null,
semester = 0,
teacherId = 0,
subjectId = 0,
addedDate = 0
), manager, bigView = false)
}
}

View File

@ -86,6 +86,7 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:orientation="horizontal"
android:paddingBottom="8dp"
android:visibility="gone"
app:flexWrap="wrap"
tools:visibility="visible">
@ -93,7 +94,7 @@
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingVertical="8dp">
android:paddingTop="8dp">
<pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView
android:layout_width="wrap_content"

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-5-8.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="?selectableItemBackground">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@drawable/divider"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<FrameLayout
android:layout_width="32dp"
android:layout_height="wrap_content">
<pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView
android:id="@+id/type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
tools:background="@drawable/bg_rounded_4dp"
tools:backgroundTint="#43a047"
tools:paddingHorizontal="5dp"
tools:singleLine="true"
tools:text="ob"
tools:textSize="14sp" />
</FrameLayout>
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:ellipsize="end"
android:fontFamily="sans-serif"
android:maxLines="2"
android:textColor="?android:textColorPrimary"
android:textSize="20sp"
tools:text="obecność" />
<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
android:id="@+id/dropdownIcon"
android:layout_width="24dp"
android:layout_height="36dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:scaleType="centerInside"
app:iiv_color="?android:textColorSecondary"
app:iiv_icon="cmd-chevron-down"
app:iiv_size="18dp"
tools:background="@android:drawable/ic_menu_more" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/previewContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:textSize="14sp"
android:visibility="gone"
tools:text="57,67% • 521 przez cały rok • 134 w tym semestrze"
tools:text1="Cały rok: 3 oceny • suma: 320 pkt"
tools:text2="Cały rok: 15 ocen • średnia: 2,62"
tools:visibility="visible" />
</FrameLayout>
</LinearLayout>
</layout>

View File

@ -1304,4 +1304,5 @@
<string name="yesterday">wczoraj</string>
<string name="you_are_offline_text">Jesteś offline. Spróbuj włączyć Wi-Fi lub dane komórkowe.</string>
<string name="you_are_offline_title">Połączenie sieciowe</string>
<string name="attendance_tab_types">Wg typu</string>
</resources>