forked from github/szkolny
[UI] Add new attendance UI module.
This commit is contained in:
parent
6436a17036
commit
9167d53a1a
@ -30,6 +30,7 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEn
|
||||
val grades by lazy { ProfileConfigGrades(this) }
|
||||
val ui by lazy { ProfileConfigUI(this) }
|
||||
val sync by lazy { ProfileConfigSync(this) }
|
||||
val attendance by lazy { ProfileConfigAttendance(this) }
|
||||
/*
|
||||
val timetable by lazy { ConfigTimetable(this) }
|
||||
val grades by lazy { ConfigGrades(this) }*/
|
||||
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-29.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.config
|
||||
|
||||
import pl.szczodrzynski.edziennik.config.utils.get
|
||||
import pl.szczodrzynski.edziennik.config.utils.set
|
||||
|
||||
class ProfileConfigAttendance(private val config: ProfileConfig) {
|
||||
private var mAttendancePageSelection: Int? = null
|
||||
var attendancePageSelection: Int
|
||||
get() { mAttendancePageSelection = mAttendancePageSelection ?: config.values.get("attendancePageSelection", 1); return mAttendancePageSelection ?: 1 }
|
||||
set(value) { config.set("attendancePageSelection", value); mAttendancePageSelection = value }
|
||||
|
||||
private var mUseSymbols: Boolean? = null
|
||||
var useSymbols: Boolean
|
||||
get() { mUseSymbols = mUseSymbols ?: config.values.get("useSymbols", false); return mUseSymbols ?: false }
|
||||
set(value) { config.set("useSymbols", value); mUseSymbols = value }
|
||||
|
||||
private var mGroupConsecutiveDays: Boolean? = null
|
||||
var groupConsecutiveDays: Boolean
|
||||
get() { mGroupConsecutiveDays = mGroupConsecutiveDays ?: config.values.get("groupConsecutiveDays", true); return mGroupConsecutiveDays ?: true }
|
||||
set(value) { config.set("groupConsecutiveDays", value); mGroupConsecutiveDays = value }
|
||||
|
||||
private var mShowPresenceInMonth: Boolean? = null
|
||||
var showPresenceInMonth: Boolean
|
||||
get() { mShowPresenceInMonth = mShowPresenceInMonth ?: config.values.get("showPresenceInMonth", false); return mShowPresenceInMonth ?: false }
|
||||
set(value) { config.set("showPresenceInMonth", value); mShowPresenceInMonth = value }
|
||||
}
|
@ -69,6 +69,6 @@ abstract class AttendanceDao : BaseDao<Attendance, AttendanceFull> {
|
||||
fun getByIdNow(profileId: Int, id: Long) =
|
||||
getOneNow("$QUERY WHERE attendances.profileId = $profileId AND attendanceId = $id")
|
||||
|
||||
@Query("UPDATE attendances SET keep = 0 WHERE profileId = :profileId AND attendanceDate > :date")
|
||||
@Query("UPDATE attendances SET keep = 0 WHERE profileId = :profileId AND attendanceDate >= :date")
|
||||
abstract fun dontKeepAfterDate(profileId: Int, date: Date?)
|
||||
}
|
||||
|
@ -116,6 +116,8 @@ interface BaseDao<T : Keepable, F : T> {
|
||||
if (forceReplace)
|
||||
replaceAll(items)
|
||||
else
|
||||
upsertAll(items)
|
||||
upsertAll(items, removeNotKept = false)
|
||||
|
||||
if (removeNotKept) removeNotKept()
|
||||
}
|
||||
}
|
||||
|
@ -47,15 +47,18 @@ open class Attendance(
|
||||
var addedDate: Long = System.currentTimeMillis()
|
||||
) : Keepable() {
|
||||
companion object {
|
||||
const val TYPE_UNKNOWN = -1
|
||||
const val TYPE_PRESENT = 0
|
||||
const val TYPE_PRESENT_CUSTOM = 10 // count as presence AND show in the list
|
||||
const val TYPE_ABSENT = 1
|
||||
const val TYPE_ABSENT_EXCUSED = 2
|
||||
const val TYPE_RELEASED = 3
|
||||
const val TYPE_BELATED = 4
|
||||
const val TYPE_BELATED_EXCUSED = 5
|
||||
const val TYPE_DAY_FREE = 6
|
||||
const val TYPE_UNKNOWN = -1 // #3f51b5
|
||||
const val TYPE_PRESENT = 0 // #009688
|
||||
const val TYPE_PRESENT_CUSTOM = 10 // count as presence AND show in the list + custom color, fallback: #3f51b5
|
||||
const val TYPE_ABSENT = 1 // #ff3d00
|
||||
const val TYPE_ABSENT_EXCUSED = 2 // #76ff03
|
||||
const val TYPE_RELEASED = 3 // #9e9e9e
|
||||
const val TYPE_BELATED = 4 // #ffc107
|
||||
const val TYPE_BELATED_EXCUSED = 5 // #ffc107
|
||||
const val TYPE_DAY_FREE = 6 // #43a047
|
||||
|
||||
// attendance bar order:
|
||||
// day_free, present, present_custom, unknown, belated_excused, belated, released, absent_excused, absent,
|
||||
}
|
||||
|
||||
@ColumnInfo(name = "attendanceLessonTopic")
|
||||
@ -64,5 +67,5 @@ open class Attendance(
|
||||
var lessonNumber: Int? = null
|
||||
|
||||
@Ignore
|
||||
var showAsUnseen = false
|
||||
var showAsUnseen: Boolean? = null
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-5-4.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.AttendanceConfigDialogBinding
|
||||
import pl.szczodrzynski.edziennik.onChange
|
||||
|
||||
class AttendanceConfigDialog(
|
||||
val activity: AppCompatActivity,
|
||||
private val reloadOnDismiss: Boolean = true,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) {
|
||||
companion object {
|
||||
const val TAG = "GradesConfigDialog"
|
||||
}
|
||||
|
||||
private val app by lazy { activity.application as App }
|
||||
private val profileConfig by lazy { app.config.getFor(app.profileId).attendance }
|
||||
|
||||
private lateinit var b: AttendanceConfigDialogBinding
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
return@run
|
||||
b = AttendanceConfigDialogBinding.inflate(activity.layoutInflater)
|
||||
onShowListener?.invoke(TAG)
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.menu_attendance_config)
|
||||
.setView(b.root)
|
||||
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
.setOnDismissListener {
|
||||
saveConfig()
|
||||
onDismissListener?.invoke(TAG)
|
||||
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
|
||||
}
|
||||
.create()
|
||||
initView()
|
||||
loadConfig()
|
||||
dialog.show()
|
||||
}}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun loadConfig() {
|
||||
b.useSymbols.isChecked = profileConfig.useSymbols
|
||||
b.groupConsecutiveDays.isChecked = profileConfig.groupConsecutiveDays
|
||||
b.showPresenceInMonth.isChecked = profileConfig.showPresenceInMonth
|
||||
}
|
||||
|
||||
private fun saveConfig() {
|
||||
// nothing to do here, yet
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
b.useSymbols.onChange { _, isChecked ->
|
||||
profileConfig.useSymbols = isChecked
|
||||
}
|
||||
b.groupConsecutiveDays.onChange { _, isChecked ->
|
||||
profileConfig.groupConsecutiveDays = isChecked
|
||||
}
|
||||
b.showPresenceInMonth.onChange { _, isChecked ->
|
||||
profileConfig.showPresenceInMonth = isChecked
|
||||
}
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.os.AsyncTask;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import pl.szczodrzynski.edziennik.App;
|
||||
import pl.szczodrzynski.edziennik.R;
|
||||
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull;
|
||||
|
||||
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT;
|
||||
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT_EXCUSED;
|
||||
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED;
|
||||
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED_EXCUSED;
|
||||
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_DAY_FREE;
|
||||
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_PRESENT;
|
||||
import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_RELEASED;
|
||||
|
||||
public class AttendanceAdapter extends RecyclerView.Adapter<AttendanceAdapter.ViewHolder> {
|
||||
private Context context;
|
||||
public List<AttendanceFull> attendanceList;
|
||||
|
||||
//getting the context and product list with constructor
|
||||
public AttendanceAdapter(Context mCtx, List<AttendanceFull> noticeList) {
|
||||
this.context = mCtx;
|
||||
this.attendanceList = noticeList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
//inflating and returning our view holder
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
View view = inflater.inflate(R.layout.row_attendance_item, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
App app = (App) context.getApplicationContext();
|
||||
|
||||
AttendanceFull attendance = attendanceList.get(position);
|
||||
|
||||
holder.attendanceLessonTopic.setText(attendance.getLessonTopic());
|
||||
holder.attendanceTeacher.setText(attendance.getTeacherName());
|
||||
holder.attendanceSubject.setText(attendance.getSubjectLongName());
|
||||
holder.attendanceDate.setText(attendance.getDate().getStringDmy());
|
||||
holder.attendanceTime.setText(attendance.getStartTime().getStringHM());
|
||||
|
||||
switch (attendance.getBaseType()) {
|
||||
case TYPE_DAY_FREE:
|
||||
holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff166ee0, PorterDuff.Mode.MULTIPLY));
|
||||
holder.attendanceType.setText(R.string.attendance_free_day);
|
||||
break;
|
||||
case TYPE_ABSENT:
|
||||
holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xfff44336, PorterDuff.Mode.MULTIPLY));
|
||||
holder.attendanceType.setText(R.string.attendance_absent);
|
||||
break;
|
||||
case TYPE_ABSENT_EXCUSED:
|
||||
holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffaeea00, PorterDuff.Mode.MULTIPLY));
|
||||
holder.attendanceType.setText(R.string.attendance_absent_excused);
|
||||
break;
|
||||
case TYPE_BELATED:
|
||||
holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffca28, PorterDuff.Mode.MULTIPLY));
|
||||
holder.attendanceType.setText(R.string.attendance_belated);
|
||||
break;
|
||||
case TYPE_BELATED_EXCUSED:
|
||||
holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff4bb733, PorterDuff.Mode.MULTIPLY));
|
||||
holder.attendanceType.setText(R.string.attendance_belated_excused);
|
||||
break;
|
||||
case TYPE_RELEASED:
|
||||
holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff9e9e9e, PorterDuff.Mode.MULTIPLY));
|
||||
holder.attendanceType.setText(R.string.attendance_released);
|
||||
break;
|
||||
case TYPE_PRESENT:
|
||||
holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffae00, PorterDuff.Mode.MULTIPLY));
|
||||
holder.attendanceType.setText(R.string.attendance_present);
|
||||
break;
|
||||
default:
|
||||
holder.attendanceType.getBackground().setColorFilter(new PorterDuffColorFilter(0xff03a9f4, PorterDuff.Mode.MULTIPLY));
|
||||
holder.attendanceType.setText("?");
|
||||
break;
|
||||
}
|
||||
holder.attendanceType.setText(attendance.getTypeShort());
|
||||
|
||||
if (!attendance.getSeen()) {
|
||||
holder.attendanceLessonTopic.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_8dp));
|
||||
holder.attendanceLessonTopic.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY));
|
||||
attendance.setSeen(true);
|
||||
AsyncTask.execute(() -> {
|
||||
App.db.metadataDao().setSeen(App.Companion.getProfileId(), attendance, true);
|
||||
//Intent i = new Intent("android.intent.action.MAIN").putExtra(MainActivity.ACTION_UPDATE_BADGES, "yes, sure");
|
||||
//context.sendBroadcast(i);
|
||||
});
|
||||
}
|
||||
else {
|
||||
holder.attendanceLessonTopic.setBackground(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return attendanceList.size();
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
TextView attendanceType;
|
||||
TextView attendanceLessonTopic;
|
||||
TextView attendanceSubject;
|
||||
TextView attendanceTeacher;
|
||||
TextView attendanceDate;
|
||||
TextView attendanceTime;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
attendanceType = itemView.findViewById(R.id.attendanceType);
|
||||
attendanceLessonTopic = itemView.findViewById(R.id.attendanceLessonTopic);
|
||||
attendanceSubject = itemView.findViewById(R.id.attendanceSubject);
|
||||
attendanceTeacher = itemView.findViewById(R.id.attendanceTeacher);
|
||||
attendanceDate = itemView.findViewById(R.id.attendanceDate);
|
||||
attendanceTime = itemView.findViewById(R.id.attendanceTime);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-29.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isInvisible
|
||||
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.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.viewholder.*
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class AttendanceAdapter(
|
||||
val activity: AppCompatActivity,
|
||||
val type: Int,
|
||||
var onAttendanceClick: ((item: AttendanceFull) -> Unit)? = null
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "AttendanceAdapter"
|
||||
private const val ITEM_TYPE_ATTENDANCE = 0
|
||||
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
|
||||
const val STATE_CLOSED = 0
|
||||
const val STATE_OPENED = 1
|
||||
}
|
||||
|
||||
private val app = activity.applicationContext as App
|
||||
// optional: place the manager here
|
||||
|
||||
private val job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
var items = mutableListOf<Any>()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (viewType) {
|
||||
ITEM_TYPE_ATTENDANCE -> AttendanceViewHolder(inflater, parent)
|
||||
ITEM_TYPE_DAY_RANGE -> DayRangeViewHolder(inflater, parent)
|
||||
ITEM_TYPE_MONTH -> MonthViewHolder(inflater, parent)
|
||||
ITEM_TYPE_SUBJECT -> SubjectViewHolder(inflater, parent)
|
||||
ITEM_TYPE_EMPTY -> EmptyViewHolder(inflater, parent)
|
||||
else -> throw IllegalArgumentException("Incorrect viewType")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (items[position]) {
|
||||
is AttendanceFull -> ITEM_TYPE_ATTENDANCE
|
||||
is AttendanceDayRange -> ITEM_TYPE_DAY_RANGE
|
||||
is AttendanceMonth -> ITEM_TYPE_MONTH
|
||||
is AttendanceSubject -> ITEM_TYPE_SUBJECT
|
||||
is AttendanceEmpty -> ITEM_TYPE_EMPTY
|
||||
else -> throw IllegalArgumentException("Incorrect viewType")
|
||||
}
|
||||
}
|
||||
|
||||
private val onClickListener = View.OnClickListener { view ->
|
||||
val model = view.getTag(R.string.tag_key_model)
|
||||
if (model is AttendanceFull) {
|
||||
onAttendanceClick?.invoke(model)
|
||||
return@OnClickListener
|
||||
}
|
||||
if (model !is ExpandableItemModel<*>)
|
||||
return@OnClickListener
|
||||
expandModel(model, view)
|
||||
}
|
||||
|
||||
fun expandModel(model: ExpandableItemModel<*>?, view: View?, notifyAdapter: Boolean = true) {
|
||||
model ?: return
|
||||
val position = items.indexOf(model)
|
||||
if (position == -1)
|
||||
return
|
||||
|
||||
view?.findViewById<View>(R.id.dropdownIcon)?.let { dropdownIcon ->
|
||||
ObjectAnimator.ofFloat(
|
||||
dropdownIcon,
|
||||
View.ROTATION,
|
||||
if (model.state == STATE_CLOSED) 0f else 180f,
|
||||
if (model.state == STATE_CLOSED) 180f else 0f
|
||||
).setDuration(200).start();
|
||||
}
|
||||
|
||||
if (model is AttendanceDayRange || model is AttendanceMonth) {
|
||||
// hide the preview, show summary
|
||||
val preview = view?.findViewById<View>(R.id.previewContainer)
|
||||
val summary = view?.findViewById<View>(R.id.summaryContainer)
|
||||
val percentage = view?.findViewById<View>(R.id.percentage)
|
||||
preview?.isInvisible = model.state == STATE_CLOSED
|
||||
summary?.isInvisible = model.state != STATE_CLOSED
|
||||
percentage?.isVisible = model.state != STATE_CLOSED
|
||||
}
|
||||
|
||||
if (model.state == STATE_CLOSED) {
|
||||
|
||||
val subItems = when {
|
||||
model.items.isEmpty() -> listOf(AttendanceEmpty())
|
||||
else -> model.items
|
||||
}
|
||||
|
||||
model.state = STATE_OPENED
|
||||
items.addAll(position + 1, subItems.filterNotNull())
|
||||
if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size)
|
||||
}
|
||||
else {
|
||||
val start = position + 1
|
||||
var end: Int = items.size
|
||||
for (i in start until items.size) {
|
||||
val model1 = items[i]
|
||||
val level = (model1 as? ExpandableItemModel<*>)?.level ?: 3
|
||||
if (level <= model.level) {
|
||||
end = i
|
||||
break
|
||||
} else {
|
||||
if (model1 is ExpandableItemModel<*> && model1.state == STATE_OPENED) {
|
||||
model1.state = STATE_CLOSED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (end != -1) {
|
||||
items.subList(start, end).clear()
|
||||
if (notifyAdapter) notifyItemRangeRemoved(start, end - start)
|
||||
}
|
||||
|
||||
model.state = STATE_CLOSED
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
if (holder !is BindableViewHolder<*, *>)
|
||||
return
|
||||
|
||||
val viewType = when (holder) {
|
||||
is AttendanceViewHolder -> ITEM_TYPE_ATTENDANCE
|
||||
is DayRangeViewHolder -> ITEM_TYPE_DAY_RANGE
|
||||
is MonthViewHolder -> ITEM_TYPE_MONTH
|
||||
is SubjectViewHolder -> ITEM_TYPE_SUBJECT
|
||||
is EmptyViewHolder -> ITEM_TYPE_EMPTY
|
||||
else -> throw IllegalArgumentException("Incorrect viewType")
|
||||
}
|
||||
holder.itemView.setTag(R.string.tag_key_view_type, viewType)
|
||||
holder.itemView.setTag(R.string.tag_key_position, position)
|
||||
holder.itemView.setTag(R.string.tag_key_model, item)
|
||||
|
||||
when {
|
||||
holder is AttendanceViewHolder && item is AttendanceFull -> holder.onBind(activity, app, item, position, this)
|
||||
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 EmptyViewHolder && item is AttendanceEmpty -> holder.onBind(activity, app, item, position, this)
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
fun notifyItemChanged(model: Any) {
|
||||
startCoroutineTimer(1000L, 0L) {
|
||||
val index = items.indexOf(model)
|
||||
if (index != -1)
|
||||
notifyItemChanged(index)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-5-1.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.text.TextPaint
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import pl.szczodrzynski.edziennik.dp
|
||||
import pl.szczodrzynski.edziennik.utils.Colors
|
||||
|
||||
/* https://github.com/JakubekWeg/Mobishit/blob/master/app/src/main/java/jakubweg/mobishit/view/AttendanceBarView.kt */
|
||||
class AttendanceBar : View {
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
|
||||
|
||||
private var attendancesList = listOf<AttendanceItem>()
|
||||
private val mainPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).also {
|
||||
it.textAlign = Paint.Align.CENTER
|
||||
}
|
||||
private var mPath = Path()
|
||||
private var mCornerRadius: Float = 0.toFloat()
|
||||
|
||||
init {
|
||||
mCornerRadius = 4.dp.toFloat()
|
||||
|
||||
if (isInEditMode)
|
||||
setAttendanceData(mapOf(
|
||||
0xff43a047.toInt() to 23,
|
||||
0xff009688.toInt() to 187,
|
||||
0xff3f51b5.toInt() to 46,
|
||||
0xff3f51b5.toInt() to 5,
|
||||
0xffffc107.toInt() to 5,
|
||||
0xff9e9e9e.toInt() to 26,
|
||||
0xff76ff03.toInt() to 34,
|
||||
0xffff3d00.toInt() to 8
|
||||
))
|
||||
}
|
||||
|
||||
// color, count
|
||||
private class AttendanceItem(var color: Int, var count: Int)
|
||||
|
||||
fun setAttendanceData(list: Map<Int, Int>) {
|
||||
attendancesList = list.map { AttendanceItem(it.key, it.value) }
|
||||
setWillNotDraw(false)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
val r = RectF(0f, 0f, w.toFloat(), h.toFloat())
|
||||
mPath = Path().apply {
|
||||
addRoundRect(r, mCornerRadius, mCornerRadius, Path.Direction.CW)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DrawAllocation", "CanvasSize")
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
canvas ?: return
|
||||
|
||||
val sum = attendancesList.sumBy { it.count }
|
||||
if (sum == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
canvas.clipPath(mPath)
|
||||
|
||||
val top = paddingTop.toFloat()
|
||||
val bottom = (height - paddingBottom).toFloat()
|
||||
var left = paddingLeft.toFloat()
|
||||
val unitWidth = (width - paddingRight - paddingLeft).toFloat() / sum.toFloat()
|
||||
|
||||
textPaint.color = Color.BLACK
|
||||
textPaint.textSize = 14.dp.toFloat()
|
||||
|
||||
for (e in attendancesList) {
|
||||
if (e.count == 0)
|
||||
continue
|
||||
|
||||
val width = unitWidth * e.count
|
||||
mainPaint.color = e.color
|
||||
canvas.drawRect(left, top, left + width, bottom, mainPaint)
|
||||
|
||||
val textBounds = Rect()
|
||||
textPaint.getTextBounds(e.count.toString(), 0, e.count.toString().length, textBounds)
|
||||
if (width > textBounds.width() + 8.dp) {
|
||||
textPaint.color = Colors.legibleTextColor(e.color)
|
||||
canvas.drawText(e.count.toString(), left + width / 2, bottom - height / 2 + textBounds.height()/2, textPaint)
|
||||
}
|
||||
|
||||
left += width
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance
|
||||
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.databinding.AttendanceFragmentBinding
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
|
||||
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
|
||||
var pageSelection = 1
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: MainActivity
|
||||
private lateinit var b: AttendanceFragmentBinding
|
||||
|
||||
private val job: Job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
// local/private variables go here
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
activity = (getActivity() as MainActivity?) ?: return null
|
||||
context ?: return null
|
||||
app = activity.application as App
|
||||
b = AttendanceFragmentBinding.inflate(inflater)
|
||||
b.refreshLayout.setParent(activity.swipeRefreshLayout)
|
||||
return b.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
if (!isAdded) return
|
||||
|
||||
activity.bottomSheet.prependItems(
|
||||
BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_attendance_config)
|
||||
.withIcon(CommunityMaterial.Icon2.cmd_settings_outline)
|
||||
.withOnClickListener(View.OnClickListener {
|
||||
activity.bottomSheet.close()
|
||||
AttendanceConfigDialog(activity, true, null, null)
|
||||
}),
|
||||
BottomSheetSeparatorItem(true),
|
||||
BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_mark_as_read)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
||||
.withOnClickListener(View.OnClickListener {
|
||||
activity.bottomSheet.close()
|
||||
AsyncTask.execute { App.db.metadataDao().setAllSeen(App.profileId, Metadata.TYPE_ATTENDANCE, true) }
|
||||
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
)
|
||||
activity.gainAttention()
|
||||
|
||||
if (pageSelection == 1)
|
||||
pageSelection = app.config.forProfile().attendance.attendancePageSelection
|
||||
|
||||
val pagerAdapter = FragmentLazyPagerAdapter(
|
||||
fragmentManager ?: return,
|
||||
b.refreshLayout,
|
||||
listOf(
|
||||
AttendanceSummaryFragment() to getString(R.string.attendance_tab_summary),
|
||||
|
||||
AttendanceListFragment().apply {
|
||||
arguments = Bundle("viewType" to VIEW_DAYS)
|
||||
} to getString(R.string.attendance_tab_days),
|
||||
|
||||
AttendanceListFragment().apply {
|
||||
arguments = Bundle("viewType" to VIEW_MONTHS)
|
||||
} to getString(R.string.attendance_tab_months),
|
||||
|
||||
AttendanceListFragment().apply {
|
||||
arguments = Bundle("viewType" to VIEW_LIST)
|
||||
} to getString(R.string.attendance_tab_list)
|
||||
)
|
||||
)
|
||||
b.viewPager.apply {
|
||||
offscreenPageLimit = 1
|
||||
adapter = pagerAdapter
|
||||
currentItem = pageSelection
|
||||
addOnPageSelectedListener {
|
||||
pageSelection = it
|
||||
app.config.forProfile().attendance.attendancePageSelection = it
|
||||
}
|
||||
b.tabLayout.setupWithViewPager(this)
|
||||
}
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@ import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_RELEASED
|
||||
import static pl.szczodrzynski.edziennik.data.db.entity.LoginStore.LOGIN_TYPE_VULCAN;
|
||||
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ATTENDANCE;
|
||||
|
||||
public class AttendanceFragment extends Fragment {
|
||||
public class AttendanceFragment_ extends Fragment {
|
||||
|
||||
private App app = null;
|
||||
private MainActivity activity = null;
|
||||
@ -278,11 +278,12 @@ public class AttendanceFragment extends Fragment {
|
||||
b.attendanceView.setVisibility(View.VISIBLE);
|
||||
b.attendanceNoData.setVisibility(View.GONE);
|
||||
if ((adapter = (AttendanceAdapter) b.attendanceView.getAdapter()) != null) {
|
||||
adapter.attendanceList = filteredList;
|
||||
//adapter.setItems(filteredList);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
else {
|
||||
adapter = new AttendanceAdapter(getContext(), filteredList);
|
||||
//adapter = new AttendanceAdapter(activity, true, null);
|
||||
//adapter.setItems(filteredList);
|
||||
b.attendanceView.setAdapter(adapter);
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.coroutines.*
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
||||
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
|
||||
import pl.szczodrzynski.edziennik.databinding.AttendanceListFragmentBinding
|
||||
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.base.lazypager.LazyFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class AttendanceListFragment : LazyFragment(), CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "AttendanceListFragment"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: MainActivity
|
||||
private lateinit var b: AttendanceListFragmentBinding
|
||||
|
||||
private val job: Job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
// local/private variables go here
|
||||
private val manager by lazy { app.attendanceManager }
|
||||
private var viewType = AttendanceFragment.VIEW_DAYS
|
||||
private var expandSubjectId = 0L
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
activity = (getActivity() as MainActivity?) ?: return null
|
||||
context ?: return null
|
||||
app = activity.application as App
|
||||
b = AttendanceListFragmentBinding.inflate(inflater)
|
||||
b.refreshLayout.setParent(activity.swipeRefreshLayout)
|
||||
return b.root
|
||||
}
|
||||
|
||||
override fun onPageCreated(): Boolean { startCoroutineTimer(100L) {
|
||||
if (!isAdded) return@startCoroutineTimer
|
||||
|
||||
viewType = arguments?.getInt("viewType") ?: AttendanceFragment.VIEW_DAYS
|
||||
expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L
|
||||
|
||||
val adapter = AttendanceAdapter(activity, viewType)
|
||||
var firstRun = true
|
||||
|
||||
app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceListFragment, Observer { items -> this@AttendanceListFragment.launch {
|
||||
if (!isAdded) return@launch
|
||||
|
||||
// load & configure the adapter
|
||||
adapter.items = withContext(Dispatchers.Default) { processAttendance(items) }
|
||||
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
|
||||
b.list.adapter = adapter
|
||||
b.list.apply {
|
||||
setHasFixedSize(true)
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
addOnScrollListener(onScrollListener)
|
||||
}
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
if (firstRun) {
|
||||
expandSubject(adapter)
|
||||
firstRun = false
|
||||
}
|
||||
|
||||
// show/hide relevant views
|
||||
b.progressBar.isVisible = false
|
||||
if (items.isNullOrEmpty()) {
|
||||
b.list.isVisible = false
|
||||
b.noData.isVisible = true
|
||||
} else {
|
||||
b.list.isVisible = true
|
||||
b.noData.isVisible = false
|
||||
}
|
||||
}})
|
||||
|
||||
adapter.onAttendanceClick = {
|
||||
//GradeDetailsDialog(activity, it)
|
||||
}
|
||||
}; return true}
|
||||
|
||||
private fun expandSubject(adapter: AttendanceAdapter) {
|
||||
var expandSubjectModel: GradesSubject? = null
|
||||
if (expandSubjectId != 0L) {
|
||||
expandSubjectModel = adapter.items.firstOrNull { it is GradesSubject && it.subjectId == expandSubjectId } as? GradesSubject
|
||||
adapter.expandModel(
|
||||
model = expandSubjectModel,
|
||||
view = null,
|
||||
notifyAdapter = false
|
||||
)
|
||||
}
|
||||
|
||||
startCoroutineTimer(500L) {
|
||||
if (expandSubjectModel != null) {
|
||||
b.list.smoothScrollToPosition(
|
||||
adapter.items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SuspendFunctionOnCoroutineScope")
|
||||
private fun processAttendance(attendance: List<AttendanceFull>): MutableList<Any> {
|
||||
if (attendance.isEmpty())
|
||||
return mutableListOf()
|
||||
|
||||
val groupConsecutiveDays = app.config.forProfile().attendance.groupConsecutiveDays
|
||||
val showPresenceInMonth = app.config.forProfile().attendance.showPresenceInMonth
|
||||
|
||||
if (viewType == AttendanceFragment.VIEW_DAYS) {
|
||||
val items = attendance
|
||||
.filter { it.baseType != Attendance.TYPE_PRESENT }
|
||||
.groupBy { it.date }
|
||||
.map { AttendanceDayRange(
|
||||
rangeStart = it.key,
|
||||
rangeEnd = null,
|
||||
items = it.value.toMutableList()
|
||||
) }
|
||||
.toMutableList()
|
||||
|
||||
if (groupConsecutiveDays) {
|
||||
items.sortByDescending { it.rangeStart }
|
||||
val iterator = items.listIterator()
|
||||
|
||||
var element = iterator.next()
|
||||
while (iterator.hasNext()) {
|
||||
var nextElement = iterator.next()
|
||||
while (Date.diffDays(element.rangeStart, nextElement.rangeStart) <= 1) {
|
||||
if (element.rangeEnd == null)
|
||||
element.rangeEnd = element.rangeStart
|
||||
|
||||
element.items.addAll(nextElement.items)
|
||||
element.rangeStart = nextElement.rangeStart
|
||||
iterator.remove()
|
||||
nextElement = iterator.next()
|
||||
}
|
||||
element = nextElement
|
||||
}
|
||||
}
|
||||
|
||||
return items.toMutableList()
|
||||
}
|
||||
else if (viewType == AttendanceFragment.VIEW_MONTHS) {
|
||||
val items = attendance
|
||||
.groupBy { it.date.year to it.date.month }
|
||||
.map { AttendanceMonth(
|
||||
year = it.key.first,
|
||||
month = it.key.second,
|
||||
items = it.value.toMutableList()
|
||||
) }
|
||||
|
||||
items.forEach { month ->
|
||||
month.typeCountMap = month.items
|
||||
.groupBy { it.baseType }
|
||||
.map { it.key to it.value.size }
|
||||
.sortedBy { it.first }
|
||||
.toMap()
|
||||
|
||||
val totalCount = month.typeCountMap.entries.sumBy { it.value }
|
||||
val presenceCount = month.typeCountMap.entries.sumBy {
|
||||
when (it.key) {
|
||||
Attendance.TYPE_PRESENT,
|
||||
Attendance.TYPE_PRESENT_CUSTOM,
|
||||
Attendance.TYPE_BELATED,
|
||||
Attendance.TYPE_BELATED_EXCUSED,
|
||||
Attendance.TYPE_RELEASED -> it.value
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
month.percentage = if (totalCount == 0)
|
||||
0f
|
||||
else
|
||||
presenceCount.toFloat() / totalCount.toFloat() * 100f
|
||||
|
||||
if (!showPresenceInMonth)
|
||||
month.items.removeAll { it.baseType == Attendance.TYPE_PRESENT }
|
||||
}
|
||||
|
||||
return items.toMutableList()
|
||||
}
|
||||
return attendance.filter { it.baseType != Attendance.TYPE_PRESENT }.toMutableList()
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-5-4.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.coroutines.*
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
||||
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
|
||||
import pl.szczodrzynski.edziennik.databinding.AttendanceListFragmentBinding
|
||||
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
|
||||
import pl.szczodrzynski.edziennik.startCoroutineTimer
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment.Companion.VIEW_SUMMARY
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject
|
||||
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class AttendanceSummaryFragment : LazyFragment(), CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "AttendanceSummaryFragment"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: MainActivity
|
||||
private lateinit var b: AttendanceListFragmentBinding
|
||||
|
||||
private val job: Job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
// local/private variables go here
|
||||
private val manager by lazy { app.attendanceManager }
|
||||
private var expandSubjectId = 0L
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
activity = (getActivity() as MainActivity?) ?: return null
|
||||
context ?: return null
|
||||
app = activity.application as App
|
||||
b = AttendanceListFragmentBinding.inflate(inflater)
|
||||
b.refreshLayout.setParent(activity.swipeRefreshLayout)
|
||||
return b.root
|
||||
}
|
||||
|
||||
override fun onPageCreated(): Boolean { startCoroutineTimer(100L) {
|
||||
if (!isAdded) return@startCoroutineTimer
|
||||
|
||||
expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L
|
||||
|
||||
val adapter = AttendanceAdapter(activity, VIEW_SUMMARY)
|
||||
var firstRun = true
|
||||
|
||||
app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceSummaryFragment, Observer { items -> this@AttendanceSummaryFragment.launch {
|
||||
if (!isAdded) return@launch
|
||||
|
||||
// load & configure the adapter
|
||||
adapter.items = withContext(Dispatchers.Default) { processAttendance(items) }
|
||||
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
|
||||
b.list.adapter = adapter
|
||||
b.list.apply {
|
||||
setHasFixedSize(true)
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
addOnScrollListener(onScrollListener)
|
||||
}
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
if (firstRun) {
|
||||
expandSubject(adapter)
|
||||
firstRun = false
|
||||
}
|
||||
|
||||
// show/hide relevant views
|
||||
b.progressBar.isVisible = false
|
||||
if (items.isNullOrEmpty()) {
|
||||
b.list.isVisible = false
|
||||
b.noData.isVisible = true
|
||||
} else {
|
||||
b.list.isVisible = true
|
||||
b.noData.isVisible = false
|
||||
}
|
||||
}})
|
||||
|
||||
adapter.onAttendanceClick = {
|
||||
//GradeDetailsDialog(activity, it)
|
||||
}
|
||||
}; return true}
|
||||
|
||||
private fun expandSubject(adapter: AttendanceAdapter) {
|
||||
var expandSubjectModel: GradesSubject? = null
|
||||
if (expandSubjectId != 0L) {
|
||||
expandSubjectModel = adapter.items.firstOrNull { it is GradesSubject && it.subjectId == expandSubjectId } as? GradesSubject
|
||||
adapter.expandModel(
|
||||
model = expandSubjectModel,
|
||||
view = null,
|
||||
notifyAdapter = false
|
||||
)
|
||||
}
|
||||
|
||||
startCoroutineTimer(500L) {
|
||||
if (expandSubjectModel != null) {
|
||||
b.list.smoothScrollToPosition(
|
||||
adapter.items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SuspendFunctionOnCoroutineScope")
|
||||
private fun processAttendance(attendance: List<AttendanceFull>): MutableList<Any> {
|
||||
if (attendance.isEmpty())
|
||||
return mutableListOf()
|
||||
|
||||
val items = attendance
|
||||
.groupBy { it.subjectId }
|
||||
.map { AttendanceSubject(
|
||||
subjectId = it.key,
|
||||
subjectName = it.value.firstOrNull()?.subjectLongName ?: "",
|
||||
items = it.value.toMutableList()
|
||||
) }
|
||||
.sortedBy { it.subjectName.toLowerCase() }
|
||||
|
||||
items.forEach { subject ->
|
||||
subject.typeCountMap = subject.items
|
||||
.groupBy { it.baseType }
|
||||
.map { it.key to it.value.size }
|
||||
.sortedBy { it.first }
|
||||
.toMap()
|
||||
|
||||
val totalCount = subject.typeCountMap.entries.sumBy { it.value }
|
||||
val presenceCount = subject.typeCountMap.entries.sumBy {
|
||||
when (it.key) {
|
||||
Attendance.TYPE_PRESENT,
|
||||
Attendance.TYPE_PRESENT_CUSTOM,
|
||||
Attendance.TYPE_BELATED,
|
||||
Attendance.TYPE_BELATED_EXCUSED,
|
||||
Attendance.TYPE_RELEASED -> it.value
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
subject.percentage = if (totalCount == 0)
|
||||
0f
|
||||
else
|
||||
presenceCount.toFloat() / totalCount.toFloat() * 100f
|
||||
|
||||
if (!false /* showPresenceInSubject */)
|
||||
subject.items.removeAll { it.baseType == Attendance.TYPE_PRESENT }
|
||||
}
|
||||
|
||||
return items.toMutableList()
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-29.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
||||
import pl.szczodrzynski.edziennik.dp
|
||||
import pl.szczodrzynski.edziennik.setTintColor
|
||||
import pl.szczodrzynski.edziennik.utils.managers.AttendanceManager
|
||||
|
||||
class AttendanceView : AppCompatTextView {
|
||||
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr)
|
||||
|
||||
constructor(context: Context, attendance: Attendance, manager: AttendanceManager) : this(context, null) {
|
||||
setAttendance(attendance, manager, false)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun setAttendance(attendance: Attendance?, manager: AttendanceManager, bigView: Boolean = false) {
|
||||
if (attendance == null) {
|
||||
visibility = View.GONE
|
||||
return
|
||||
}
|
||||
visibility = View.VISIBLE
|
||||
|
||||
val attendanceName = if (manager.useSymbols)
|
||||
attendance.typeSymbol
|
||||
else
|
||||
attendance.typeShort
|
||||
|
||||
val attendanceColor = manager.getAttendanceColor(attendance)
|
||||
|
||||
text = when {
|
||||
attendanceName.isBlank() -> " "
|
||||
else -> attendanceName
|
||||
}
|
||||
|
||||
setTextColor(if (ColorUtils.calculateLuminance(attendanceColor) > 0.3)
|
||||
0xaa000000.toInt()
|
||||
else
|
||||
0xccffffff.toInt())
|
||||
|
||||
setBackgroundResource(if (bigView) R.drawable.bg_rounded_8dp else R.drawable.bg_rounded_4dp)
|
||||
background.setTintColor(attendanceColor)
|
||||
gravity = Gravity.CENTER
|
||||
|
||||
if (bigView) {
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 22f)
|
||||
setAutoSizeTextTypeUniformWithConfiguration(
|
||||
14,
|
||||
32,
|
||||
1,
|
||||
TypedValue.COMPLEX_UNIT_SP
|
||||
)
|
||||
setPadding(2.dp, 2.dp, 2.dp, 2.dp)
|
||||
}
|
||||
else {
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
|
||||
setPadding(5.dp, 0, 5.dp, 0)
|
||||
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
|
||||
setMargins(0, 0, 5.dp, 0)
|
||||
}
|
||||
maxLines = 1
|
||||
ellipsize = TextUtils.TruncateAt.END
|
||||
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance.models
|
||||
|
||||
class AttendanceCount {
|
||||
var normalSum = 0f
|
||||
var normalCount = 0
|
||||
var normalWeightedSum = 0f
|
||||
var normalWeightedCount = 0f
|
||||
|
||||
var pointSum = 0f
|
||||
|
||||
var pointAvgSum = 0f
|
||||
var pointAvgMax = 0f
|
||||
|
||||
var normalAvg: Float? = null
|
||||
var pointAvgPercent: Float? = null
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance.models
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
||||
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
data class AttendanceDayRange(
|
||||
var rangeStart: Date,
|
||||
var rangeEnd: Date?,
|
||||
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 }
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-5-4.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance.models
|
||||
|
||||
class AttendanceEmpty
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance.models
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
||||
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
|
||||
|
||||
data class AttendanceMonth(
|
||||
val year: Int,
|
||||
val month: Int,
|
||||
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 typeCountMap: Map<Int, Int> = mapOf()
|
||||
var percentage: Float = 0f
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-5-4.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance.models
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
||||
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
|
||||
|
||||
data class AttendanceSubject(
|
||||
val subjectId: Long,
|
||||
val subjectName: String,
|
||||
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 typeCountMap: Map<Int, Int> = mapOf()
|
||||
var percentage: Float = 0f
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder
|
||||
|
||||
import android.view.LayoutInflater
|
||||
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.R
|
||||
import pl.szczodrzynski.edziennik.concat
|
||||
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
|
||||
import pl.szczodrzynski.edziennik.databinding.AttendanceItemAttendanceBinding
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange
|
||||
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
|
||||
|
||||
class AttendanceViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: AttendanceItemAttendanceBinding = AttendanceItemAttendanceBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<AttendanceFull, AttendanceAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "AttendanceViewHolder"
|
||||
}
|
||||
|
||||
override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceFull, position: Int, adapter: AttendanceAdapter) {
|
||||
val manager = app.attendanceManager
|
||||
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
|
||||
|
||||
val bullet = " • "
|
||||
|
||||
b.attendanceView.setAttendance(item, manager, bigView = true)
|
||||
|
||||
b.type.text = item.typeName
|
||||
b.subjectName.text = item.subjectLongName ?: item.lessonTopic
|
||||
b.dateTime.text = listOf(
|
||||
item.date.formattedStringShort,
|
||||
item.startTime?.stringHM,
|
||||
item.lessonNumber?.let { app.getString(R.string.attendance_lesson_number_format, it) }
|
||||
).concat(bullet)
|
||||
|
||||
if (item.showAsUnseen == null)
|
||||
item.showAsUnseen = !item.seen
|
||||
|
||||
b.unread.isVisible = item.showAsUnseen == true
|
||||
if (!item.seen) {
|
||||
manager.markAsSeen(item)
|
||||
|
||||
val container = adapter.items.firstOrNull {
|
||||
it is ExpandableItemModel<*> && it.items.contains(item)
|
||||
} as? ExpandableItemModel<*> ?: return
|
||||
|
||||
var hasUnseen = true
|
||||
if (container is AttendanceDayRange) {
|
||||
hasUnseen = container.items.any { !it.seen }
|
||||
container.hasUnseen = hasUnseen
|
||||
}
|
||||
if (container is AttendanceMonth) {
|
||||
hasUnseen = container.items.any { !it.seen }
|
||||
container.hasUnseen = hasUnseen
|
||||
}
|
||||
|
||||
// check if the unseen status has changed
|
||||
if (!hasUnseen) {
|
||||
adapter.notifyItemChanged(container)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
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.R
|
||||
import pl.szczodrzynski.edziennik.concat
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
||||
import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerBinding
|
||||
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
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceDayRange
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
|
||||
class DayRangeViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: AttendanceItemContainerBinding = AttendanceItemContainerBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<AttendanceDayRange, AttendanceAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "DayRangeViewHolder"
|
||||
}
|
||||
|
||||
override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceDayRange, position: Int, adapter: AttendanceAdapter) {
|
||||
val manager = app.attendanceManager
|
||||
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
|
||||
|
||||
b.title.text = listOf(
|
||||
item.rangeStart.formattedString,
|
||||
item.rangeEnd?.formattedString
|
||||
).concat(" - ")
|
||||
|
||||
b.dropdownIcon.rotation = when (item.state) {
|
||||
STATE_CLOSED -> 0f
|
||||
else -> 180f
|
||||
}
|
||||
|
||||
b.unread.isVisible = item.hasUnseen
|
||||
|
||||
b.previewContainer.visibility = if (item.state == STATE_CLOSED) View.VISIBLE else View.INVISIBLE
|
||||
b.summaryContainer.visibility = if (item.state == STATE_CLOSED) View.INVISIBLE else View.VISIBLE
|
||||
|
||||
b.previewContainer.removeAllViews()
|
||||
|
||||
for (attendance in item.items) {
|
||||
if (attendance.baseType == Attendance.TYPE_PRESENT_CUSTOM || attendance.baseType == Attendance.TYPE_UNKNOWN)
|
||||
continue
|
||||
b.previewContainer.addView(AttendanceView(
|
||||
contextWrapper,
|
||||
attendance,
|
||||
manager
|
||||
))
|
||||
}
|
||||
if (item.items.isEmpty() || item.items.none { it.baseType != Attendance.TYPE_PRESENT_CUSTOM && it.baseType != Attendance.TYPE_UNKNOWN }) {
|
||||
b.previewContainer.addView(TextView(contextWrapper).also {
|
||||
it.setText(R.string.attendance_empty_text)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-5-4.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance.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.AttendanceItemEmptyBinding
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceEmpty
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
||||
|
||||
class EmptyViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: AttendanceItemEmptyBinding = AttendanceItemEmptyBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<AttendanceEmpty, AttendanceAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "EmptyViewHolder"
|
||||
}
|
||||
|
||||
override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceEmpty, position: Int, adapter: AttendanceAdapter) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.view.isInvisible
|
||||
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.ui.modules.attendance.AttendanceAdapter
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter.Companion.STATE_CLOSED
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceMonth
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class MonthViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: AttendanceItemContainerBarBinding = AttendanceItemContainerBarBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<AttendanceMonth, AttendanceAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "MonthViewHolder"
|
||||
}
|
||||
|
||||
override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceMonth, position: Int, adapter: AttendanceAdapter) {
|
||||
val manager = app.attendanceManager
|
||||
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
|
||||
|
||||
b.title.text = listOf(
|
||||
app.resources.getStringArray(R.array.material_calendar_months_array).getOrNull(item.month - 1)?.fixName(),
|
||||
item.year.toString()
|
||||
).concat(" ")
|
||||
|
||||
b.dropdownIcon.rotation = when (item.state) {
|
||||
STATE_CLOSED -> 0f
|
||||
else -> 180f
|
||||
}
|
||||
|
||||
b.unread.isVisible = item.hasUnseen
|
||||
|
||||
b.attendanceBar.setAttendanceData(item.typeCountMap.mapKeys { manager.getAttendanceColor(it.key) })
|
||||
|
||||
b.previewContainer.isInvisible = item.state != STATE_CLOSED
|
||||
b.summaryContainer.isInvisible = item.state == STATE_CLOSED
|
||||
b.percentage.isVisible = item.state == STATE_CLOSED
|
||||
|
||||
b.previewContainer.removeAllViews()
|
||||
|
||||
val sum = item.typeCountMap.entries.sumBy { it.value }.toFloat()
|
||||
item.typeCountMap.forEach { (type, count) ->
|
||||
val layout = LinearLayout(contextWrapper)
|
||||
val attendance = Attendance(
|
||||
profileId = 0,
|
||||
id = 0,
|
||||
baseType = type,
|
||||
typeName = "",
|
||||
typeShort = manager.getTypeShort(type),
|
||||
typeSymbol = manager.getTypeShort(type),
|
||||
typeColor = manager.getAttendanceColor(type),
|
||||
date = Date(0, 0, 0),
|
||||
startTime = null,
|
||||
semester = 0,
|
||||
teacherId = 0,
|
||||
subjectId = 0,
|
||||
addedDate = 0
|
||||
)
|
||||
layout.addView(AttendanceView(contextWrapper, attendance, manager))
|
||||
layout.addView(TextView(contextWrapper).also {
|
||||
it.setText(R.string.attendance_percentage_format, count/sum*100f)
|
||||
it.setPadding(0, 0, 5.dp, 0)
|
||||
})
|
||||
layout.setPadding(0, 8.dp, 0, 0)
|
||||
b.previewContainer.addView(layout)
|
||||
}
|
||||
|
||||
if (item.percentage == 0f) {
|
||||
b.percentage.isVisible = false
|
||||
b.percentage.text = null
|
||||
b.summaryContainer.isVisible = false
|
||||
b.summaryContainer.text = null
|
||||
}
|
||||
else {
|
||||
b.percentage.setText(R.string.attendance_percentage_format, item.percentage)
|
||||
b.summaryContainer.setText(R.string.attendance_period_summary_format, item.percentage)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-5-4.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.attendance.viewholder
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
|
||||
import pl.szczodrzynski.edziennik.databinding.AttendanceItemContainerBarBinding
|
||||
import pl.szczodrzynski.edziennik.dp
|
||||
import pl.szczodrzynski.edziennik.setText
|
||||
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
|
||||
import pl.szczodrzynski.edziennik.ui.modules.attendance.models.AttendanceSubject
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class SubjectViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
val b: AttendanceItemContainerBarBinding = AttendanceItemContainerBarBinding.inflate(inflater, parent, false)
|
||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<AttendanceSubject, AttendanceAdapter> {
|
||||
companion object {
|
||||
private const val TAG = "SubjectViewHolder"
|
||||
}
|
||||
|
||||
override fun onBind(activity: AppCompatActivity, app: App, item: AttendanceSubject, position: Int, adapter: AttendanceAdapter) {
|
||||
val manager = app.attendanceManager
|
||||
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
|
||||
|
||||
b.title.text = item.subjectName
|
||||
|
||||
b.dropdownIcon.rotation = when (item.state) {
|
||||
STATE_CLOSED -> 0f
|
||||
else -> 180f
|
||||
}
|
||||
|
||||
b.unread.isVisible = item.hasUnseen
|
||||
|
||||
b.attendanceBar.setAttendanceData(item.typeCountMap.mapKeys { manager.getAttendanceColor(it.key) })
|
||||
|
||||
b.previewContainer.isInvisible = item.state != STATE_CLOSED
|
||||
b.summaryContainer.isInvisible = item.state == STATE_CLOSED
|
||||
b.percentage.isVisible = item.state == STATE_CLOSED
|
||||
|
||||
b.previewContainer.removeAllViews()
|
||||
|
||||
val sum = item.typeCountMap.entries.sumBy { it.value }.toFloat()
|
||||
item.typeCountMap.forEach { (type, count) ->
|
||||
val layout = LinearLayout(contextWrapper)
|
||||
val attendance = Attendance(
|
||||
profileId = 0,
|
||||
id = 0,
|
||||
baseType = type,
|
||||
typeName = "",
|
||||
typeShort = manager.getTypeShort(type),
|
||||
typeSymbol = manager.getTypeShort(type),
|
||||
typeColor = manager.getAttendanceColor(type),
|
||||
date = Date(0, 0, 0),
|
||||
startTime = null,
|
||||
semester = 0,
|
||||
teacherId = 0,
|
||||
subjectId = 0,
|
||||
addedDate = 0
|
||||
)
|
||||
layout.addView(AttendanceView(contextWrapper, attendance, manager))
|
||||
layout.addView(TextView(contextWrapper).also {
|
||||
it.setText(R.string.attendance_percentage_format, count/sum*100f)
|
||||
it.setPadding(0, 0, 5.dp, 0)
|
||||
})
|
||||
layout.setPadding(0, 8.dp, 0, 0)
|
||||
b.previewContainer.addView(layout)
|
||||
}
|
||||
|
||||
if (item.percentage == 0f) {
|
||||
b.percentage.isVisible = false
|
||||
b.percentage.text = null
|
||||
b.summaryContainer.isVisible = false
|
||||
b.summaryContainer.text = null
|
||||
}
|
||||
else {
|
||||
b.percentage.setText(R.string.attendance_percentage_format, item.percentage)
|
||||
b.summaryContainer.setText(R.string.attendance_period_summary_format, item.percentage)
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.models
|
||||
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter.Companion.STATE_CLOSED
|
||||
|
||||
abstract class ExpandableItemModel<T>(val items: MutableList<T>) {
|
||||
abstract class ExpandableItemModel<T>(open val items: MutableList<T>) {
|
||||
open var level: Int = 3
|
||||
var state: Int = STATE_CLOSED
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ import pl.szczodrzynski.edziennik.network.NetworkUtils;
|
||||
import pl.szczodrzynski.edziennik.sync.SyncWorker;
|
||||
import pl.szczodrzynski.edziennik.sync.UpdateWorker;
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog;
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog;
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog;
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog;
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.sync.NotificationFilterDialog;
|
||||
@ -885,6 +886,15 @@ public class SettingsNewFragment extends MaterialAboutFragment {
|
||||
.color(IconicsColor.colorInt(iconColor))
|
||||
).setOnClickAction(() -> new GradesConfigDialog(activity, false, null, null)));
|
||||
|
||||
items.add(new MaterialAboutActionItem(
|
||||
getString(R.string.menu_attendance_config),
|
||||
null,
|
||||
new IconicsDrawable(activity)
|
||||
.icon(CommunityMaterial.Icon.cmd_calendar_remove_outline)
|
||||
.size(IconicsSize.dp(iconSizeDp))
|
||||
.color(IconicsColor.colorInt(iconColor))
|
||||
).setOnClickAction(() -> new AttendanceConfigDialog(activity, false, null, null)));
|
||||
|
||||
registerCardAllowRegistrationItem = new MaterialAboutSwitchItem(
|
||||
getString(R.string.settings_register_allow_registration_text),
|
||||
getString(R.string.settings_register_allow_registration_subtext),
|
||||
|
@ -19,10 +19,13 @@ class AttendanceManager(val app: App) : CoroutineScope {
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Default
|
||||
|
||||
val useSymbols
|
||||
get() = app.config.forProfile().attendance.useSymbols
|
||||
|
||||
fun getTypeShort(baseType: Int): String {
|
||||
return when (baseType) {
|
||||
Attendance.TYPE_PRESENT -> "ob"
|
||||
Attendance.TYPE_PRESENT_CUSTOM -> "ob?"
|
||||
Attendance.TYPE_PRESENT_CUSTOM -> " "
|
||||
Attendance.TYPE_ABSENT -> "nb"
|
||||
Attendance.TYPE_ABSENT_EXCUSED -> "u"
|
||||
Attendance.TYPE_RELEASED -> "zw"
|
||||
@ -33,6 +36,26 @@ class AttendanceManager(val app: App) : CoroutineScope {
|
||||
}
|
||||
}
|
||||
|
||||
fun getAttendanceColor(baseType: Int): Int {
|
||||
return when (baseType) {
|
||||
Attendance.TYPE_PRESENT -> 0xff009688.toInt()
|
||||
Attendance.TYPE_PRESENT_CUSTOM -> 0xff64b5f6.toInt()
|
||||
Attendance.TYPE_ABSENT -> 0xffff3d00.toInt()
|
||||
Attendance.TYPE_ABSENT_EXCUSED -> 0xff76ff03.toInt()
|
||||
Attendance.TYPE_RELEASED -> 0xff9e9e9e.toInt()
|
||||
Attendance.TYPE_BELATED -> 0xffffc107.toInt()
|
||||
Attendance.TYPE_BELATED_EXCUSED -> 0xffffc107.toInt()
|
||||
Attendance.TYPE_DAY_FREE -> 0xff43a047.toInt()
|
||||
else -> 0xff64b5f6.toInt()
|
||||
}
|
||||
}
|
||||
fun getAttendanceColor(attendance: Attendance): Int {
|
||||
return (if (useSymbols) attendance.typeColor else null) ?: when (attendance.baseType) {
|
||||
Attendance.TYPE_PRESENT_CUSTOM -> attendance.typeColor ?: 0xff64b5f6.toInt()
|
||||
else -> getAttendanceColor(attendance.baseType)
|
||||
}
|
||||
}
|
||||
|
||||
/* _ _ _____ _____ _ __ _
|
||||
| | | |_ _| / ____| (_)/ _(_)
|
||||
| | | | | | | (___ _ __ ___ ___ _| |_ _ ___
|
||||
|
59
app/src/main/res/layout/attendance_config_dialog.xml
Normal file
59
app/src/main/res/layout/attendance_config_dialog.xml
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-5-4.
|
||||
-->
|
||||
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.AppCompat.Small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/attendance_config_title" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/useSymbols"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="32dp"
|
||||
android:text="@string/attendance_config_use_symbols" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="@string/attendance_config_use_symbols_hint"
|
||||
android:textAppearance="@style/NavView.TextView.Helper"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="italic" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/groupConsecutiveDays"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="32dp"
|
||||
android:text="@string/attendance_config_group_consecutive_days" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/showPresenceInMonth"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="32dp"
|
||||
android:text="@string/attendance_config_show_presence_in_month"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</layout>
|
36
app/src/main/res/layout/attendance_fragment.xml
Normal file
36
app/src/main/res/layout/attendance_fragment.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
-->
|
||||
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
|
||||
android:id="@+id/refreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/colorSurface_6dp"
|
||||
app:tabIndicatorColor="?colorPrimary"
|
||||
app:tabMode="auto"
|
||||
app:tabSelectedTextColor="?colorPrimary"
|
||||
app:tabTextColor="?android:textColorPrimary" />
|
||||
|
||||
<pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyViewPager
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
</LinearLayout>
|
||||
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
|
||||
</layout>
|
95
app/src/main/res/layout/attendance_item_attendance.xml
Normal file
95
app/src/main/res/layout/attendance_item_attendance.xml
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-4-29.
|
||||
-->
|
||||
|
||||
<layout xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
<import type="android.view.View"/>
|
||||
<variable
|
||||
name="simpleMode"
|
||||
type="Boolean" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView
|
||||
android:id="@+id/attendanceView"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:background="@drawable/bg_rounded_8dp"
|
||||
tools:backgroundTint="#f44336"
|
||||
tools:gravity="center"
|
||||
tools:text="nb"
|
||||
tools:textSize="22sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/type"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:ellipsize="middle"
|
||||
android:maxLines="2"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/NavView.TextView.Helper"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/unread"
|
||||
app:layout_constraintStart_toEndOf="@+id/attendanceView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Nieobecność nieusprawiedliwiona" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subjectName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:ellipsize="middle"
|
||||
android:maxLines="2"
|
||||
android:singleLine="true"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/attendanceView"
|
||||
app:layout_constraintTop_toBottomOf="@+id/type"
|
||||
tools:text="Język angielski" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dateTime"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:ellipsize="middle"
|
||||
android:maxLines="2"
|
||||
android:singleLine="true"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/attendanceView"
|
||||
app:layout_constraintTop_toBottomOf="@+id/subjectName"
|
||||
tools:text="2 marca 2019 • 10:45 • lekcja 4" />
|
||||
|
||||
<View
|
||||
android:id="@+id/unread"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:background="@drawable/unread_red_circle"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/type"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
94
app/src/main/res/layout/attendance_item_container.xml
Normal file
94
app/src/main/res/layout/attendance_item_container.xml
Normal file
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
-->
|
||||
|
||||
<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:orientation="horizontal">
|
||||
|
||||
<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="2 marca - 3 marca" />
|
||||
|
||||
<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:src="@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">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/previewContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summaryContainer"
|
||||
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="6 nieob. • 2 nieob. nieuspr. • 5 spóźnień"
|
||||
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>
|
128
app/src/main/res/layout/attendance_item_container_bar.xml
Normal file
128
app/src/main/res/layout/attendance_item_container_bar.xml
Normal file
@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-5-1.
|
||||
-->
|
||||
|
||||
<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:orientation="horizontal">
|
||||
|
||||
<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="Kwiecień 2020" />
|
||||
|
||||
<View
|
||||
android:id="@+id/unread"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:visibility="gone"
|
||||
android:background="@drawable/unread_red_circle"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/percentage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
tools:text="99,5%" />
|
||||
|
||||
<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:src="@android:drawable/ic_menu_more" />
|
||||
</LinearLayout>
|
||||
|
||||
<pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceBar
|
||||
android:id="@+id/attendanceBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginHorizontal="8dp"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:id="@+id/previewContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:flexWrap="wrap"
|
||||
tools:visibility="visible">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:background="@drawable/bg_rounded_4dp"
|
||||
tools:backgroundTint="#43a047"
|
||||
tools:layout_marginEnd="5dp"
|
||||
tools:layout_marginRight="5dp"
|
||||
tools:paddingHorizontal="5dp"
|
||||
tools:singleLine="true"
|
||||
tools:text="w"
|
||||
tools:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout_marginEnd="5dp"
|
||||
tools:layout_marginRight="5dp"
|
||||
tools:text="6,8%" />
|
||||
</LinearLayout>
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summaryContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:paddingVertical="8dp"
|
||||
tools:text="Obecność w tym okresie: 100%" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</layout>
|
21
app/src/main/res/layout/attendance_item_empty.xml
Normal file
21
app/src/main/res/layout/attendance_item_empty.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-5-4.
|
||||
-->
|
||||
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:gravity="center"
|
||||
android:textSize="18sp"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textStyle="italic"
|
||||
android:text="@string/attendance_empty_text"/>
|
||||
</LinearLayout>
|
||||
</layout>
|
47
app/src/main/res/layout/attendance_list_fragment.xml
Normal file
47
app/src/main/res/layout/attendance_list_fragment.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-4-30.
|
||||
-->
|
||||
|
||||
<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">
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
|
||||
android:id="@+id/refreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/noData"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:text="@string/attendances_no_data"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"
|
||||
app:drawableTopCompat="@drawable/ic_no_grades"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
tools:listitem="@layout/attendance_item_attendance"
|
||||
tools:visibility="visible" />
|
||||
</FrameLayout>
|
||||
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
|
||||
</layout>
|
47
app/src/main/res/layout/attendance_summary_fragment.xml
Normal file
47
app/src/main/res/layout/attendance_summary_fragment.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-5-4.
|
||||
-->
|
||||
|
||||
<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">
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
|
||||
android:id="@+id/refreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/noData"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:text="@string/attendances_no_data"
|
||||
android:textSize="24sp"
|
||||
android:visibility="gone"
|
||||
app:drawableTopCompat="@drawable/ic_no_grades"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
tools:listitem="@layout/attendance_item_attendance"
|
||||
tools:visibility="visible" />
|
||||
</FrameLayout>
|
||||
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
|
||||
</layout>
|
@ -16,6 +16,6 @@
|
||||
android:textSize="18sp"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textStyle="italic"
|
||||
android:text="Nie ma ocen w tym semestrze."/>
|
||||
android:text="@string/grades_empty_text"/>
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
|
@ -69,7 +69,7 @@
|
||||
<string name="attendances_absent">Nieobecności:</string>
|
||||
<string name="attendances_absent_unexcused">W tym nieusprawiedliwione:</string>
|
||||
<string name="attendances_belated">Spóźnienia:</string>
|
||||
<string name="attendances_no_data">Brak nieobecności.</string>
|
||||
<string name="attendances_no_data">Nie masz żadnych nieobecności.</string>
|
||||
<string name="attendances_present">Obecności:</string>
|
||||
<string name="attendances_releases">Zwolnienia:</string>
|
||||
<string name="attendances_summary_subject_loading_format">Wszystkie przedmioty</string>
|
||||
@ -1288,4 +1288,19 @@
|
||||
<string name="you_are_offline_title">Połączenie sieciowe</string>
|
||||
<string name="settings_add_student_text">Dodaj nowego ucznia</string>
|
||||
<string name="settings_add_student_subtext">Zaloguj konto ucznia/rodzica w aplikacji</string>
|
||||
<string name="attendance_lesson_number_format">lekcja %d</string>
|
||||
<string name="menu_attendance_config">Ustawienia frekwencji</string>
|
||||
<string name="attendance_tab_days">Dni</string>
|
||||
<string name="attendance_tab_months">Miesiące</string>
|
||||
<string name="attendance_tab_summary">Podsumowanie</string>
|
||||
<string name="attendance_tab_list">Lista</string>
|
||||
<string name="attendance_period_summary_format">Obecność w tym okresie: %.2f%%</string>
|
||||
<string name="attendance_percentage_format">%.2f%%</string>
|
||||
<string name="grades_empty_text">Nie ma ocen w tym semestrze.</string>
|
||||
<string name="attendance_empty_text">Nie ma tutaj żadnych nieobecności.</string>
|
||||
<string name="attendance_config_title">Konfiguracja frekwencji</string>
|
||||
<string name="attendance_config_use_symbols">Używaj symboli i kolorów wg dziennika</string>
|
||||
<string name="attendance_config_group_consecutive_days">Grupuj kolejne dni na liście</string>
|
||||
<string name="attendance_config_show_presence_in_month">Wyświetlaj obecność w widoku miesięcy</string>
|
||||
<string name="attendance_config_use_symbols_hint">Widoczne po rozwinięciu listy</string>
|
||||
</resources>
|
||||
|
@ -17,7 +17,7 @@ buildscript {
|
||||
]
|
||||
|
||||
versions = [
|
||||
gradleAndroid : "4.0.0-beta03",
|
||||
gradleAndroid : '4.0.0-beta05',
|
||||
|
||||
kotlin : ext.kotlin_version,
|
||||
ktx : "1.2.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user