Merge branch 'develop' into feature/messages-fixes

This commit is contained in:
Kuba Szczodrzyński 2021-04-14 22:42:54 +02:00 committed by GitHub
commit e2b47db3fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1967 additions and 1148 deletions

View File

@ -153,7 +153,7 @@ dependencies {
// Szkolny.eu libraries/forks // Szkolny.eu libraries/forks
implementation "eu.szkolny:android-snowfall:1ca9ea2da3" implementation "eu.szkolny:android-snowfall:1ca9ea2da3"
implementation "eu.szkolny:agendacalendarview:1799f8ef47" implementation "eu.szkolny:agendacalendarview:5431f03098"
implementation "eu.szkolny:cafebar:5bf0c618de" implementation "eu.szkolny:cafebar:5bf0c618de"
implementation "eu.szkolny.fslogin:lib:2.0.0" implementation "eu.szkolny.fslogin:lib:2.0.0"
implementation "eu.szkolny:material-about-library:1d5ebaf47c" implementation "eu.szkolny:material-about-library:1d5ebaf47c"

View File

@ -15,6 +15,36 @@ class ProfileConfigUI(private val config: ProfileConfig) {
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT } get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
set(value) { config.set("agendaViewType", value); mAgendaViewType = value } set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
private var mAgendaCompactMode: Boolean? = null
var agendaCompactMode: Boolean
get() { mAgendaCompactMode = mAgendaCompactMode ?: config.values.get("agendaCompactMode", false); return mAgendaCompactMode ?: false }
set(value) { config.set("agendaCompactMode", value); mAgendaCompactMode = value }
private var mAgendaGroupByType: Boolean? = null
var agendaGroupByType: Boolean
get() { mAgendaGroupByType = mAgendaGroupByType ?: config.values.get("agendaGroupByType", false); return mAgendaGroupByType ?: false }
set(value) { config.set("agendaGroupByType", value); mAgendaGroupByType = value }
private var mAgendaLessonChanges: Boolean? = null
var agendaLessonChanges: Boolean
get() { mAgendaLessonChanges = mAgendaLessonChanges ?: config.values.get("agendaLessonChanges", true); return mAgendaLessonChanges ?: true }
set(value) { config.set("agendaLessonChanges", value); mAgendaLessonChanges = value }
private var mAgendaTeacherAbsence: Boolean? = null
var agendaTeacherAbsence: Boolean
get() { mAgendaTeacherAbsence = mAgendaTeacherAbsence ?: config.values.get("agendaTeacherAbsence", true); return mAgendaTeacherAbsence ?: true }
set(value) { config.set("agendaTeacherAbsence", value); mAgendaTeacherAbsence = value }
private var mAgendaElearningMark: Boolean? = null
var agendaElearningMark: Boolean
get() { mAgendaElearningMark = mAgendaElearningMark ?: config.values.get("agendaElearningMark", false); return mAgendaElearningMark ?: false }
set(value) { config.set("agendaElearningMark", value); mAgendaElearningMark = value }
private var mAgendaElearningGroup: Boolean? = null
var agendaElearningGroup: Boolean
get() { mAgendaElearningGroup = mAgendaElearningGroup ?: config.values.get("agendaElearningGroup", true); return mAgendaElearningGroup ?: true }
set(value) { config.set("agendaElearningGroup", value); mAgendaElearningGroup = value }
private var mHomeCards: List<HomeCardModel>? = null private var mHomeCards: List<HomeCardModel>? = null
var homeCards: List<HomeCardModel> var homeCards: List<HomeCardModel>
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() } get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }

View File

@ -84,6 +84,8 @@ abstract class TimetableDao : BaseDao<Lesson, LessonFull> {
"LIMIT 1") "LIMIT 1")
fun getBetweenDates(dateFrom: Date, dateTo: Date) = fun getBetweenDates(dateFrom: Date, dateTo: Date) =
getRaw("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY") getRaw("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY")
fun getChanges(profileId: Int) =
getRaw("$QUERY WHERE timetable.profileId = $profileId AND $IS_CHANGED $ORDER_BY")
// GET ALL - NOW // GET ALL - NOW
fun getAllNow(profileId: Int) = fun getAllNow(profileId: Int) =

View File

@ -116,14 +116,7 @@ open class Event(
var showAsUnseen: Boolean? = null var showAsUnseen: Boolean? = null
val startTimeCalendar: Calendar val startTimeCalendar: Calendar
get() = Calendar.getInstance().also { it.set( get() = date.getAsCalendar(time)
date.year,
date.month - 1,
date.day,
time?.hour ?: 0,
time?.minute ?: 0,
time?.second ?: 0
) }
val endTimeCalendar: Calendar val endTimeCalendar: Calendar
get() = startTimeCalendar.also { get() = startTimeCalendar.also {

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.agenda
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
import pl.szczodrzynski.edziennik.databinding.DialogConfigAgendaBinding
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog
import java.util.*
class AgendaConfigDialog(
private val activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
private val onShowListener: ((tag: String) -> Unit)? = null,
private val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
const val TAG = "AgendaConfigDialog"
}
private val app by lazy { activity.application as App }
private val config by lazy { app.config.ui }
private val profileConfig by lazy { app.config.forProfile().ui }
private lateinit var b: DialogConfigAgendaBinding
private lateinit var dialog: AlertDialog
init { run {
if (activity.isFinishing)
return@run
b = DialogConfigAgendaBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.menu_agenda_config)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
.create()
loadConfig()
dialog.show()
}}
private fun loadConfig() {
b.config = profileConfig
b.isAgendaMode = profileConfig.agendaViewType == Profile.AGENDA_DEFAULT
b.eventSharingEnabled.isChecked = app.profile.enableSharedEvents
&& app.profile.registration == REGISTRATION_ENABLED
b.eventSharingEnabled.onChange { _, isChecked ->
if (isChecked && app.profile.registration != REGISTRATION_ENABLED) {
b.eventSharingEnabled.isChecked = false
val dialog = RegistrationConfigDialog(activity, app.profile, onChangeListener = { enabled ->
b.eventSharingEnabled.isChecked = enabled
setEventSharingEnabled(enabled)
}, onShowListener, onDismissListener)
dialog.showEnableDialog()
return@onChange
}
setEventSharingEnabled(isChecked)
}
}
private fun setEventSharingEnabled(enabled: Boolean) {
if (enabled == app.profile.enableSharedEvents)
return
app.profile.enableSharedEvents = enabled
app.profileSave()
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.event_sharing)
.setMessage(
if (enabled)
R.string.settings_register_shared_events_dialog_enabled_text
else
R.string.settings_register_shared_events_dialog_disabled_text
)
.setPositiveButton(R.string.ok, null)
.show()
}
private fun saveConfig() {
}
}

View File

@ -7,7 +7,7 @@ package pl.szczodrzynski.edziennik.ui.dialogs.day
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.* import kotlinx.coroutines.*
@ -19,6 +19,10 @@ import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -29,6 +33,7 @@ class DayDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val profileId: Int, val profileId: Int,
val date: Date, val date: Date,
val eventTypeId: Long? = null,
val onShowListener: ((tag: String) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope { ) : CoroutineScope {
@ -109,10 +114,16 @@ class DayDialog(
} }
lessonChanges.ifNotEmpty { lessonChanges.ifNotEmpty {
b.lessonChangeContainer.root.visibility = View.VISIBLE LessonChangesEventRenderer().render(
b.lessonChangeContainer.lessonChangeCount.text = it.size.toString() b.lessonChanges, LessonChangesEvent(
profileId = profileId,
date = date,
count = it.size,
showBadge = false
)
)
b.lessonChangeLayout.onClick { b.lessonChangesFrame.onClick {
LessonChangeDialog( LessonChangeDialog(
activity, activity,
profileId, profileId,
@ -122,16 +133,22 @@ class DayDialog(
) )
} }
} }
b.lessonChangesFrame.isVisible = lessonChanges.isNotEmpty()
val teacherAbsences = withContext(Dispatchers.Default) { val teacherAbsences = withContext(Dispatchers.Default) {
app.db.teacherAbsenceDao().getAllByDateNow(profileId, date) app.db.teacherAbsenceDao().getAllByDateNow(profileId, date)
} }
teacherAbsences.ifNotEmpty { teacherAbsences.ifNotEmpty {
b.teacherAbsenceContainer.root.visibility = View.VISIBLE TeacherAbsenceEventRenderer().render(
b.teacherAbsenceContainer.teacherAbsenceCount.text = it.size.toString() b.teacherAbsence, TeacherAbsenceEvent(
profileId = profileId,
date = date,
count = it.size
)
)
b.teacherAbsenceLayout.onClick { b.teacherAbsenceFrame.onClick {
TeacherAbsenceDialog( TeacherAbsenceDialog(
activity, activity,
profileId, profileId,
@ -141,6 +158,7 @@ class DayDialog(
) )
} }
} }
b.teacherAbsenceFrame.isVisible = teacherAbsences.isNotEmpty()
adapter = EventListAdapter( adapter = EventListAdapter(
activity, activity,
@ -169,8 +187,12 @@ class DayDialog(
} }
) )
app.db.eventDao().getAllByDate(profileId, date).observe(activity, Observer { events -> app.db.eventDao().getAllByDate(profileId, date).observe(activity) { events ->
adapter.items = events adapter.items = if (eventTypeId != null)
events.filter { it.type == eventTypeId }
else
events
if (b.eventsView.adapter == null) { if (b.eventsView.adapter == null) {
b.eventsView.adapter = adapter b.eventsView.adapter = adapter
b.eventsView.apply { b.eventsView.apply {
@ -189,6 +211,6 @@ class DayDialog(
b.eventsView.visibility = View.GONE b.eventsView.visibility = View.GONE
b.eventsNoData.visibility = View.VISIBLE b.eventsNoData.visibility = View.VISIBLE
} }
}) }
}} }}
} }

View File

@ -32,7 +32,7 @@ import kotlin.coroutines.CoroutineContext
class EventDetailsDialog( class EventDetailsDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val event: EventFull, var event: EventFull,
val onShowListener: ((tag: String) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope { ) : CoroutineScope {
@ -46,6 +46,8 @@ class EventDetailsDialog(
private var removeEventDialog: AlertDialog? = null private var removeEventDialog: AlertDialog? = null
private val eventShared = event.sharedBy != null private val eventShared = event.sharedBy != null
private val eventOwn = event.sharedBy == "self" private val eventOwn = event.sharedBy == "self"
private val manager
get() = app.eventManager
private val job = Job() private val job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -92,6 +94,10 @@ class EventDetailsDialog(
b.eventShared = eventShared b.eventShared = eventShared
b.eventOwn = eventOwn b.eventOwn = eventOwn
if (!event.seen) {
manager.markAsSeen(event)
}
val bullet = "" val bullet = ""
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
@ -100,6 +106,8 @@ class EventDetailsDialog(
} }
catch (_: Exception) {} catch (_: Exception) {}
manager.setLegendText(b.legend, event)
b.typeColor.background?.setTintColor(event.eventColor) b.typeColor.background?.setTintColor(event.eventColor)
b.details = mutableListOf( b.details = mutableListOf(
@ -135,6 +143,7 @@ class EventDetailsDialog(
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
app.db.eventDao().replace(event) app.db.eventDao().replace(event)
} }
update()
b.checkDoneButton.isChecked = true b.checkDoneButton.isChecked = true
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
@ -145,6 +154,7 @@ class EventDetailsDialog(
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
app.db.eventDao().replace(event) app.db.eventDao().replace(event)
} }
update()
} }
} }
b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done) b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done)
@ -156,6 +166,14 @@ class EventDetailsDialog(
activity, activity,
event.profileId, event.profileId,
editingEvent = event, editingEvent = event,
onSaveListener = {
if (it == null) {
dialog.dismiss()
return@EventManualDialog
}
event = it
update()
},
onShowListener = onShowListener, onShowListener = onShowListener,
onDismissListener = onDismissListener onDismissListener = onDismissListener
) )
@ -323,8 +341,6 @@ class EventDetailsDialog(
removeEventDialog?.dismiss() removeEventDialog?.dismiss()
dialog.dismiss() dialog.dismiss()
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_AGENDA)
activity.reloadTarget()
} }
private fun openInCalendar() { launch { private fun openInCalendar() { launch {

View File

@ -33,7 +33,8 @@ class EventListAdapter(
) : RecyclerView.Adapter<EventListAdapter.ViewHolder>(), CoroutineScope { ) : RecyclerView.Adapter<EventListAdapter.ViewHolder>(), CoroutineScope {
private val app = context.applicationContext as App private val app = context.applicationContext as App
private val manager = app.eventManager private val manager
get() = app.eventManager
private val job = Job() private val job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -67,7 +68,7 @@ class EventListAdapter(
b.simpleMode = simpleMode b.simpleMode = simpleMode
b.topic.text = event.topic manager.setEventTopic(b.topic, event, showType = false)
b.topic.maxLines = if (simpleMode) 2 else 3 b.topic.maxLines = if (simpleMode) 2 else 3
b.details.text = mutableListOf<CharSequence?>( b.details.text = mutableListOf<CharSequence?>(
@ -102,8 +103,6 @@ class EventListAdapter(
} }
b.editButton.attachToastHint(R.string.hint_edit_event) b.editButton.attachToastHint(R.string.hint_edit_event)
b.isDone.isVisible = event.isDone
if (event.showAsUnseen == null) if (event.showAsUnseen == null)
event.showAsUnseen = !event.seen event.showAsUnseen = !event.seen

View File

@ -4,8 +4,6 @@
package pl.szczodrzynski.edziennik.ui.dialogs.event package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@ -20,23 +18,18 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
import pl.szczodrzynski.edziennik.utils.Anim import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -49,6 +42,7 @@ class EventManualDialog(
val defaultTime: Time? = null, val defaultTime: Time? = null,
val defaultType: Long? = null, val defaultType: Long? = null,
val editingEvent: EventFull? = null, val editingEvent: EventFull? = null,
val onSaveListener: ((event: EventFull?) -> Unit)? = null,
val onShowListener: ((tag: String) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope { ) : CoroutineScope {
@ -323,57 +317,41 @@ class EventManualDialog(
selectDefault(defaultLesson?.displayTeacherId) selectDefault(defaultLesson?.displayTeacherId)
} }
with (b.typeDropdown) {
db = app.db
profileId = this@EventManualDialog.profileId
loadItems()
selectDefault(editingEvent?.type)
selectDefault(defaultType)
val deferred = async(Dispatchers.Default) { onTypeSelected = {
// get the event type list b.typeColor.background.setTintColor(it.color)
var eventTypes = app.db.eventTypeDao().getAllNow(profileId) customColor = null
}
if (eventTypes.none { it.id in -1L..10L }) {
eventTypes = app.db.eventTypeDao().addDefaultTypes(activity, profileId)
} }
b.typeDropdown.clear() // copy data from event being edited
b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
}
deferred.await()
b.typeDropdown.isEnabled = true
defaultType?.let {
b.typeDropdown.select(it)
}
b.typeDropdown.selected?.let { item ->
customColor = (item.tag as EventType).color
}
// copy IDs from event being edited
editingEvent?.let { editingEvent?.let {
b.topic.setText(it.topic) b.topic.setText(it.topic)
b.typeDropdown.select(it.type)?.let { item -> if (it.color != -1)
customColor = (item.tag as EventType).color
}
if (it.color != null && it.color != -1)
customColor = it.color customColor = it.color
} }
b.typeColor.background.setTintColor(
customColor
?: b.typeDropdown.getSelected()?.color
?: Event.COLOR_DEFAULT
)
// copy IDs from the LessonFull // copy IDs from the LessonFull
defaultLesson?.let { defaultLesson?.let {
b.teamDropdown.select(it.displayTeamId) b.teamDropdown.select(it.displayTeamId)
} }
b.typeDropdown.setOnChangeListener {
b.typeColor.background.colorFilter = PorterDuffColorFilter((it.tag as EventType).color, PorterDuff.Mode.SRC_ATOP)
customColor = null
return@setOnChangeListener true
}
(customColor ?: Event.COLOR_DEFAULT).let {
b.typeColor.background.colorFilter = PorterDuffColorFilter(it, PorterDuff.Mode.SRC_ATOP)
}
b.typeColor.onClick { b.typeColor.onClick {
val currentColor = (b.typeDropdown.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT val currentColor = customColor
?: b.typeDropdown.getSelected()?.color
?: Event.COLOR_DEFAULT
val colorPickerDialog = ColorPickerDialog.newBuilder() val colorPickerDialog = ColorPickerDialog.newBuilder()
.setColor(currentColor) .setColor(currentColor)
.create() .create()
@ -381,7 +359,7 @@ class EventManualDialog(
object : ColorPickerDialogListener { object : ColorPickerDialogListener {
override fun onDialogDismissed(dialogId: Int) {} override fun onDialogDismissed(dialogId: Int) {}
override fun onColorSelected(dialogId: Int, color: Int) { override fun onColorSelected(dialogId: Int, color: Int) {
b.typeColor.background.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) b.typeColor.background.setTintColor(color)
customColor = color customColor = color
} }
}) })
@ -416,11 +394,11 @@ class EventManualDialog(
private fun saveEvent() { private fun saveEvent() {
val date = b.dateDropdown.getSelected() as? Date val date = b.dateDropdown.getSelected() as? Date
val timeSelected = b.timeDropdown.getSelected() val timeSelected = b.timeDropdown.getSelected()
val teamId = b.teamDropdown.getSelected() as? Long val team = b.teamDropdown.getSelected()
val type = b.typeDropdown.selected?.id val type = b.typeDropdown.getSelected()
val topic = b.topic.text?.toString() val topic = b.topic.text?.toString()
val subjectId = b.subjectDropdown.getSelected() as? Long val subject = b.subjectDropdown.getSelected() as? Subject
val teacherId = b.teacherDropdown.getSelected() val teacher = b.teacherDropdown.getSelected()
val share = b.shareSwitch.isChecked val share = b.shareSwitch.isChecked
@ -451,7 +429,7 @@ class EventManualDialog(
isError = true isError = true
} }
if (share && teamId == null) { if (share && team == null) {
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose) b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown) if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown)
isError = true isError = true
@ -487,10 +465,10 @@ class EventManualDialog(
time = startTime, time = startTime,
topic = topic, topic = topic,
color = customColor, color = customColor,
type = type ?: Event.TYPE_DEFAULT, type = type?.id ?: Event.TYPE_DEFAULT,
teacherId = teacherId ?: -1, teacherId = teacher?.id ?: -1,
subjectId = subjectId ?: -1, subjectId = subject?.id ?: -1,
teamId = teamId ?: -1, teamId = team?.id ?: -1,
addedDate = editingEvent?.addedDate ?: System.currentTimeMillis() addedDate = editingEvent?.addedDate ?: System.currentTimeMillis()
).also { ).also {
it.addedManually = true it.addedManually = true
@ -498,7 +476,7 @@ class EventManualDialog(
val metadataObject = Metadata( val metadataObject = Metadata(
profileId, profileId,
when (type) { when (type?.id) {
Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK
else -> Metadata.TYPE_EVENT else -> Metadata.TYPE_EVENT
}, },
@ -597,10 +575,14 @@ class EventManualDialog(
} }
} }
onSaveListener?.invoke(eventObject.withMetadata(metadataObject).also {
it.subjectLongName = (b.subjectDropdown.getSelected() as? Subject)?.longName
it.teacherName = b.teacherDropdown.getSelected()?.fullName
it.teamName = b.teamDropdown.getSelected()?.name
it.typeName = b.typeDropdown.getSelected()?.name
})
dialog.dismiss() dialog.dismiss()
Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show()
if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA)
activity.reloadTarget()
} }
private fun finishRemoving() { private fun finishRemoving() {
editingEvent ?: return editingEvent ?: return
@ -611,9 +593,8 @@ class EventManualDialog(
} }
removeEventDialog?.dismiss() removeEventDialog?.dismiss()
onSaveListener?.invoke(null)
dialog.dismiss() dialog.dismiss()
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA)
activity.reloadTarget()
} }
} }

View File

@ -50,7 +50,8 @@ class LessonDetailsDialog(
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
private lateinit var adapter: EventListAdapter private lateinit var adapter: EventListAdapter
private val manager by lazy { app.timetableManager } private val manager
get() = app.timetableManager
init { run { init { run {
if (activity.isFinishing) if (activity.isFinishing)

View File

@ -12,10 +12,6 @@ import android.widget.Toast
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.applandeo.materialcalendarview.EventDay import com.applandeo.materialcalendarview.EventDay
import com.github.tibolte.agendacalendarview.CalendarPickerController
import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent
import com.github.tibolte.agendacalendarview.models.CalendarEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.colorInt
@ -29,17 +25,9 @@ import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding
import pl.szczodrzynski.edziennik.ui.dialogs.agenda.AgendaConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeCounter
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceCounter
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
@ -59,7 +47,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
private var type: Int = Profile.AGENDA_DEFAULT private var type: Int = Profile.AGENDA_DEFAULT
private var actualDate: Date? = null
private var agendaDefault: AgendaFragmentDefault? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
if (getActivity() == null || context == null) return null if (getActivity() == null || context == null) return null
@ -82,38 +71,61 @@ class AgendaFragment : Fragment(), CoroutineScope {
.withTitle(R.string.menu_add_event) .withTitle(R.string.menu_add_event)
.withDescription(R.string.menu_add_event_desc) .withDescription(R.string.menu_add_event_desc)
.withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline) .withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
.withOnClickListener(View.OnClickListener { .withOnClickListener {
activity.bottomSheet.close() activity.bottomSheet.close()
EventManualDialog(activity, app.profileId, defaultDate = actualDate) EventManualDialog(
}), activity,
app.profileId,
defaultDate = AgendaFragmentDefault.selectedDate
)
},
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_agenda_config)
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
AgendaConfigDialog(activity, true, null, null)
},
BottomSheetPrimaryItem(true) BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_agenda_change_view) .withTitle(R.string.menu_agenda_change_view)
.withIcon(if (type == Profile.AGENDA_DEFAULT) CommunityMaterial.Icon.cmd_calendar_outline else CommunityMaterial.Icon2.cmd_format_list_bulleted_square) .withIcon(if (type == Profile.AGENDA_DEFAULT) CommunityMaterial.Icon.cmd_calendar_outline else CommunityMaterial.Icon2.cmd_format_list_bulleted_square)
.withOnClickListener(View.OnClickListener { .withOnClickListener {
activity.bottomSheet.close() activity.bottomSheet.close()
type = if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT type =
if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT
app.config.forProfile().ui.agendaViewType = type app.config.forProfile().ui.agendaViewType = type
activity.reloadTarget() activity.reloadTarget()
}), },
BottomSheetSeparatorItem(true), BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true) BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read) .withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(View.OnClickListener { launch { .withOnClickListener {
launch {
activity.bottomSheet.close() activity.bottomSheet.close()
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
App.db.metadataDao().setAllSeen(app.profileId, Metadata.TYPE_EVENT, true) App.db.metadataDao()
.setAllSeen(app.profileId, Metadata.TYPE_EVENT, true)
}
Toast.makeText(
activity,
R.string.main_menu_mark_as_read_success,
Toast.LENGTH_SHORT
).show()
}
} }
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
}})
) )
activity.navView.bottomBar.fabEnable = true activity.navView.bottomBar.fabEnable = true
activity.navView.bottomBar.fabExtendedText = getString(R.string.add) activity.navView.bottomBar.fabExtendedText = getString(R.string.add)
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus
activity.navView.setFabOnClickListener(View.OnClickListener { activity.navView.setFabOnClickListener {
EventManualDialog(activity, app.profileId, defaultDate = actualDate) EventManualDialog(
}) activity,
app.profileId,
defaultDate = AgendaFragmentDefault.selectedDate
)
}
activity.gainAttention() activity.gainAttention()
activity.gainAttentionFAB() activity.gainAttentionFAB()
@ -129,143 +141,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
return@launch return@launch
delay(500) delay(500)
val eventList = mutableListOf<CalendarEvent>() agendaDefault = AgendaFragmentDefault(activity, app, b)
agendaDefault?.initView(this@AgendaFragment)
val minDate = Calendar.getInstance().apply {
add(Calendar.MONTH, -2)
set(Calendar.DAY_OF_MONTH, 1)
}
val maxDate = Calendar.getInstance().apply { add(Calendar.MONTH, 2) }
/**
* LESSON CHANGES
*/
if (!isAdded)
return@launch
val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getChangesNow(app.profileId) }
val lessonChangeCounters = mutableListOf<LessonChangeCounter>()
lessons.forEach { lesson ->
lessonChangeCounters.firstOrNull { it.lessonChangeDate == lesson.displayDate }?.let {
it.lessonChangeCount += 1
} ?: run {
lessonChangeCounters.add(LessonChangeCounter(
lesson.displayDate ?: return@forEach,
1
))
}
}
lessonChangeCounters.forEach { counter ->
eventList.add(LessonChangeEvent(
counter.lessonChangeDate.inMillis,
0xff78909c.toInt(),
Colors.legibleTextColor(0xff78909c.toInt()),
counter.startTime,
counter.endTime,
app.profileId,
counter.lessonChangeDate,
counter.lessonChangeCount
))
}
/**
* TEACHER ABSENCES
*/
if (!isAdded)
return@launch
val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true)
if (showTeacherAbsences) {
val teacherAbsenceList = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllNow(app.profileId) }
val teacherAbsenceCounters = mutableListOf<TeacherAbsenceCounter>()
teacherAbsenceList.forEach { absence ->
val date = absence.dateFrom.clone()
while (date <= absence.dateTo) {
teacherAbsenceCounters.firstOrNull { it.teacherAbsenceDate == date }?.let {
it.teacherAbsenceCount += 1
} ?: run {
teacherAbsenceCounters.add(TeacherAbsenceCounter(date.clone(), 1))
}
date.stepForward(0, 0, 1)
}
}
teacherAbsenceCounters.forEach { counter ->
eventList.add(TeacherAbsenceEvent(
counter.teacherAbsenceDate.inMillis,
0xffff1744.toInt(),
Colors.legibleTextColor(0xffff1744.toInt()),
counter.startTime,
counter.endTime,
app.profileId,
counter.teacherAbsenceDate,
counter.teacherAbsenceCount
))
}
}
/**
* EVENTS
*/
if (!isAdded)
return@launch
val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllNow(app.profileId) }
val unreadEventDates = mutableSetOf<Int>()
events.forEach { event ->
eventList.add(BaseCalendarEvent(
"${event.typeName ?: "wydarzenie"} - ${event.topic}",
"",
(if (event.time == null) getString(R.string.agenda_event_all_day) else event.time!!.stringHM) +
(event.subjectLongName?.let { ", $it" } ?: "") +
(event.teacherName?.let { ", $it" } ?: "") +
(event.teamName?.let { ", $it" } ?: ""),
event.eventColor,
Colors.legibleTextColor(event.eventColor),
event.startTimeCalendar,
event.endTimeCalendar,
event.time == null,
event.id,
!event.seen
))
if (!event.seen) unreadEventDates.add(event.date.value)
}
b.agendaDefaultView.init(eventList, minDate, maxDate, Locale.getDefault(), object : CalendarPickerController {
override fun onDaySelected(dayItem: IDayItem?) {}
override fun onScrollToDate(calendar: Calendar) { this@AgendaFragment.launch {
val date = Date.fromCalendar(calendar)
actualDate = date
// Mark as read scrolled date
if (date.value in unreadEventDates) {
withContext(Dispatchers.Default) { app.db.eventDao().setSeenByDate(app.profileId, date, true) }
unreadEventDates.remove(date.value)
}
}}
override fun onEventSelected(event: CalendarEvent) {
val date = Date.fromCalendar(event.instanceDay)
when (event) {
is BaseCalendarEvent -> DayDialog(activity, app.profileId, date)
is LessonChangeEvent -> LessonChangeDialog(activity, app.profileId, date)
is TeacherAbsenceEvent -> TeacherAbsenceDialog(activity, app.profileId, date)
}
}
}, LessonChangeEventRenderer(), TeacherAbsenceEventRenderer())
b.progressBar.visibility = View.GONE
}}} }}}
private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch { private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch {

View File

@ -0,0 +1,310 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda
import android.util.SparseIntArray
import android.widget.AbsListView
import android.widget.AbsListView.OnScrollListener
import androidx.core.util.forEach
import androidx.core.util.set
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.CalendarManager
import com.github.tibolte.agendacalendarview.CalendarPickerController
import com.github.tibolte.agendacalendarview.agenda.AgendaAdapter
import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent
import com.github.tibolte.agendacalendarview.models.CalendarEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding
import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventGroup
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventGroupRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class AgendaFragmentDefault(
private val activity: MainActivity,
private val app: App,
private val b: FragmentAgendaDefaultBinding
) : OnScrollListener, CoroutineScope {
companion object {
var selectedDate: Date = Date.getToday()
}
override val coroutineContext = Job() + Dispatchers.Main
private val unreadDates = mutableSetOf<Int>()
private val events = mutableListOf<CalendarEvent>()
private var isInitialized = false
private val profileConfig by lazy { app.config.forProfile().ui }
private val listView
get() = b.agendaDefaultView.agendaView.agendaListView
private val adapter
get() = listView.adapter as? AgendaAdapter
private val manager
get() = CalendarManager.getInstance()
private var scrollState = OnScrollListener.SCROLL_STATE_IDLE
private var updatePending = false
private var notifyPending = false
override fun onScrollStateChanged(view: AbsListView?, newScrollState: Int) {
b.agendaDefaultView.agendaScrollListener.onScrollStateChanged(view, scrollState)
scrollState = newScrollState
if (updatePending) updateData()
if (notifyPending) notifyDataSetChanged()
}
override fun onScroll(
view: AbsListView?,
firstVisibleItem: Int,
visibleItemCount: Int,
totalItemCount: Int
) = b.agendaDefaultView.agendaScrollListener.onScroll(
view,
firstVisibleItem,
visibleItemCount,
totalItemCount
)
/**
* Mark the data as needing update, either after 1 second (when
* not scrolling) or 1 second after scrolling stops.
*/
private fun updateData() = launch {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
updatePending = false
delay(1000)
notifyDataSetChanged()
} else updatePending = true
}
/**
* Notify the adapter about changes, either instantly or after
* scrolling stops.
*/
private fun notifyDataSetChanged() {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
notifyPending = false
adapter?.notifyDataSetChanged()
} else notifyPending = true
}
suspend fun initView(fragment: AgendaFragment) {
isInitialized = false
withContext(Dispatchers.Default) {
if (profileConfig.agendaLessonChanges)
addLessonChanges(events)
if (profileConfig.agendaTeacherAbsence)
addTeacherAbsence(events)
}
app.db.eventDao().getAll(app.profileId).observe(fragment) {
addEvents(events, it)
if (isInitialized)
updateView()
else
initViewPriv()
}
}
private fun initViewPriv() {
val dateStart = app.profile.dateSemester1Start.asCalendar
val dateEnd = app.profile.dateYearEnd.asCalendar
val isCompactMode = profileConfig.agendaCompactMode
b.agendaDefaultView.init(
events,
dateStart,
dateEnd,
Locale.getDefault(),
object : CalendarPickerController {
override fun onDaySelected(dayItem: IDayItem) {
val c = Calendar.getInstance()
c.time = dayItem.date
if (c.timeInMillis == selectedDate.inMillis) {
DayDialog(activity, app.profileId, selectedDate)
}
}
override fun onEventSelected(event: CalendarEvent) {
val date = Date.fromCalendar(event.instanceDay)
when (event) {
is AgendaEvent -> EventDetailsDialog(activity, event.event)
is LessonChangesEvent -> LessonChangeDialog(activity, app.profileId, date)
is TeacherAbsenceEvent -> TeacherAbsenceDialog(
activity,
app.profileId,
date
)
is AgendaEventGroup -> DayDialog(activity, app.profileId, date, eventTypeId = event.typeId)
is BaseCalendarEvent -> if (event.isPlaceHolder)
DayDialog(activity, app.profileId, date)
}
if (event is BaseEvent && event.showItemBadge) {
val unreadCount = manager.events.count {
it.instanceDay.equals(event.instanceDay) && it.showBadge
}
// only clicked event is unread, remove the day badge
if (unreadCount == 1 && event.showBadge) {
event.dayReference.showBadge = false
unreadDates.remove(date.value)
}
setAsRead(event)
}
}
override fun onScrollToDate(calendar: Calendar) {
selectedDate = Date.fromCalendar(calendar)
// Mark as read scrolled date
if (selectedDate.value in unreadDates) {
setAsRead(calendar)
activity.launch(Dispatchers.Default) {
app.db.eventDao().setSeenByDate(app.profileId, selectedDate, true)
}
unreadDates.remove(selectedDate.value)
}
}
},
AgendaEventRenderer(app.eventManager, isCompactMode),
AgendaEventGroupRenderer(),
LessonChangesEventRenderer(),
TeacherAbsenceEventRenderer()
)
listView.setOnScrollListener(this)
isInitialized = true
b.progressBar.isVisible = false
}
private fun updateView() {
manager.events.clear()
manager.loadEvents(events, BaseCalendarEvent())
adapter?.updateEvents(manager.events)
//listView.scrollToCurrentDate(selectedDate.asCalendar)
}
private fun setAsRead(date: Calendar) {
// get all events matching the date
val events = manager.events.filter {
if (it.instanceDay.equals(date) && it.showBadge && it is AgendaEvent) {
// hide the day badge for the date
it.dayReference.showBadge = false
return@filter true
}
false
}
// set this date's events as read
setAsRead(*events.toTypedArray())
}
private fun setAsRead(vararg event: CalendarEvent) {
// hide per-event badges
for (e in event) {
events.firstOrNull {
it == e
}?.showBadge = false
e.showBadge = false
}
listView.setOnScrollListener(this)
updateData()
}
private fun addEvents(
events: MutableList<CalendarEvent>,
eventList: List<EventFull>
) {
events.removeAll { it is AgendaEvent || it is AgendaEventGroup }
if (!profileConfig.agendaGroupByType) {
events += eventList.map {
if (!it.seen)
unreadDates.add(it.date.value)
AgendaEvent(it)
}
return
}
eventList.groupBy {
it.date.value to it.type
}.forEach { (_, list) ->
val event = list.first()
if (list.size == 1) {
if (!event.seen)
unreadDates.add(event.date.value)
events += AgendaEvent(event)
} else {
events.add(0, AgendaEventGroup(
profileId = event.profileId,
date = event.date,
typeId = event.type,
typeName = event.typeName ?: "-",
typeColor = event.typeColor ?: event.eventColor,
count = list.size,
showBadge = list.any { !it.seen }
))
}
}
}
private fun addLessonChanges(events: MutableList<CalendarEvent>) {
val lessons = app.db.timetableDao().getChangesNow(app.profileId)
val grouped = lessons.groupBy {
it.displayDate
}
events += grouped.mapNotNull { (date, changes) ->
LessonChangesEvent(
app.profileId,
date = date ?: return@mapNotNull null,
count = changes.size,
showBadge = changes.any { !it.seen }
)
}
}
private fun addTeacherAbsence(events: MutableList<CalendarEvent>) {
val teacherAbsence = app.db.teacherAbsenceDao().getAllNow(app.profileId)
val countMap = SparseIntArray()
for (absence in teacherAbsence) {
while (absence.dateFrom <= absence.dateTo) {
countMap[absence.dateFrom.value] += 1
absence.dateFrom.stepForward(0, 0, 1)
}
}
countMap.forEach { dateInt, count ->
events += TeacherAbsenceEvent(
app.profileId,
date = Date.fromValue(dateInt),
count = count
)
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-9.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda
import com.github.tibolte.agendacalendarview.models.CalendarEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import com.github.tibolte.agendacalendarview.models.IWeekItem
import java.util.*
open class BaseEvent(
private val id: Long,
private val time: Calendar,
private val color: Int,
private var showBadge: Boolean,
var showItemBadge: Boolean = showBadge
) : CalendarEvent {
override fun copy() = BaseEvent(id, time, color, showBadge)
private lateinit var date: Calendar
override fun getInstanceDay() = date
override fun setInstanceDay(value: Calendar) {
date = value
}
private lateinit var dayReference: IDayItem
override fun getDayReference() = dayReference
override fun setDayReference(value: IDayItem) {
dayReference = value
}
private lateinit var weekReference: IWeekItem
override fun getWeekReference() = weekReference
override fun setWeekReference(value: IWeekItem) {
weekReference = value
}
override fun getShowBadge() = showBadge
override fun setShowBadge(value: Boolean) {
showBadge = value
showItemBadge = value
}
override fun getId() = id
override fun getStartTime() = time
override fun getEndTime() = time
override fun getTitle() = ""
override fun getDescription() = ""
override fun getLocation() = ""
override fun getColor() = color
override fun getTextColor() = 0
override fun isPlaceholder() = false
override fun isAllDay() = false
override fun setId(value: Long) = Unit
override fun setStartTime(value: Calendar) = Unit
override fun setEndTime(value: Calendar) = Unit
override fun setTitle(value: String) = Unit
override fun setDescription(value: String) = Unit
override fun setLocation(value: String) = Unit
override fun setTextColor(value: Int) = Unit
override fun setPlaceholder(value: Boolean) = Unit
override fun setAllDay(value: Boolean) = Unit
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
class AgendaEvent(
val event: EventFull,
showBadge: Boolean = !event.seen
) : BaseEvent(
id = event.id,
time = event.startTimeCalendar,
color = event.eventColor,
showBadge = showBadge
) {
override fun copy() = AgendaEvent(event, showBadge)
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
import pl.szczodrzynski.edziennik.utils.models.Date
class AgendaEventGroup(
val profileId: Int,
val date: Date,
val typeId: Long,
val typeName: String,
val typeColor: Int,
val count: Int,
showBadge: Boolean
) : BaseEvent(
id = date.value.toLong(),
time = date.asCalendar,
color = typeColor,
showBadge = showBadge
) {
override fun copy() = AgendaEventGroup(profileId, date, typeId, typeName, typeColor, count, showBadge)
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import android.view.View
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedGroupBinding
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
class AgendaEventGroupRenderer : EventRenderer<AgendaEventGroup>() {
override fun render(view: View, event: AgendaEventGroup) {
val b = AgendaWrappedGroupBinding.bind(view).item
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.text = event.typeName
b.name.setTextColor(Colors.legibleTextColor(event.color))
b.count.text = event.count.toString()
b.count.background.setTintColor(android.R.attr.colorBackground.resolveAttr(view.context))
b.badge.isVisible = event.showItemBadge
}
override fun getEventLayout(): Int = R.layout.agenda_wrapped_group
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import android.annotation.SuppressLint
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.render.EventRenderer
import com.mikepenz.iconics.view.IconicsTextView
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventBinding
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventCompactBinding
import pl.szczodrzynski.edziennik.join
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.managers.EventManager
class AgendaEventRenderer(
val manager: EventManager,
val isCompact: Boolean
) : EventRenderer<AgendaEvent>() {
@SuppressLint("SetTextI18n")
override fun render(view: View, aEvent: AgendaEvent) {
if (isCompact) {
val b = AgendaWrappedEventCompactBinding.bind(view).item
bindView(aEvent, b.card, b.title, null, b.badgeBackground, b.badge)
} else {
val b = AgendaWrappedEventBinding.bind(view).item
bindView(aEvent, b.card, b.title, b.subtitle, b.badgeBackground, b.badge)
}
}
private fun bindView(
aEvent: AgendaEvent,
card: FrameLayout,
title: IconicsTextView,
subtitle: TextView?,
badgeBackground: View,
badge: View
) {
val event = aEvent.event
val textColor = Colors.legibleTextColor(event.eventColor)
val timeText = if (event.time == null)
card.context.getString(R.string.agenda_event_all_day)
else
event.time!!.stringHM
val eventSubtitle = listOfNotNull(
timeText,
event.subjectLongName,
event.teacherName,
event.teamName
).join(", ")
card.foreground.setTintColor(event.eventColor)
card.background.setTintColor(event.eventColor)
manager.setEventTopic(title, event, doneIconColor = textColor)
title.setTextColor(textColor)
subtitle?.text = eventSubtitle
subtitle?.setTextColor(textColor)
badgeBackground.isVisible = aEvent.showItemBadge
badgeBackground.background.setTintColor(
android.R.attr.colorBackground.resolveAttr(card.context)
)
badge.isVisible = aEvent.showItemBadge
}
override fun getEventLayout() = if (isCompact)
R.layout.agenda_wrapped_event_compact
else
R.layout.agenda_wrapped_event
}

View File

@ -1,19 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class LessonChangeCounter(
val lessonChangeDate: Date,
var lessonChangeCount: Int
) {
val startTime: Calendar
get() = Calendar.getInstance().apply {
set(lessonChangeDate.year, lessonChangeDate.month - 1, lessonChangeDate.day, 10, 0, 0)
}
val endTime: Calendar
get() = Calendar.getInstance().apply {
timeInMillis = startTime.timeInMillis + (45 * 60 * 1000)
}
}

View File

@ -1,243 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange;
import com.github.tibolte.agendacalendarview.models.CalendarEvent;
import com.github.tibolte.agendacalendarview.models.IDayItem;
import com.github.tibolte.agendacalendarview.models.IWeekItem;
import java.util.Calendar;
import pl.szczodrzynski.edziennik.utils.models.Date;
public class LessonChangeEvent implements CalendarEvent {
/**
* Id of the event.
*/
private long mId;
/**
* Color to be displayed in the agenda view.
*/
private int mColor;
/**
* Text color displayed on the background color
*/
private int mTextColor;
/**
* Calendar instance helping sorting the events per section in the agenda view.
*/
private Calendar mInstanceDay;
/**
* Start time of the event.
*/
private Calendar mStartTime;
/**
* End time of the event.
*/
private Calendar mEndTime;
/**
* References to a DayItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private IDayItem mDayReference;
/**
* References to a WeekItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private IWeekItem mWeekReference;
private int profileId;
private Date lessonChangeDate;
private int lessonChangeCount;
public LessonChangeEvent(LessonChangeEvent calendarEvent) {
this.mId = calendarEvent.getId();
this.mColor = calendarEvent.getColor();
this.mTextColor = calendarEvent.getTextColor();
this.mStartTime = calendarEvent.getStartTime();
this.mEndTime = calendarEvent.getEndTime();
this.profileId = calendarEvent.getProfileId();
this.lessonChangeDate = calendarEvent.getLessonChangeDate();
this.lessonChangeCount = calendarEvent.getLessonChangeCount();
}
public LessonChangeEvent(long mId, int mColor, int mTextColor, Calendar mStartTime, Calendar mEndTime, int profileId, Date lessonChangeDate, int lessonChangeCount) {
this.mId = mId;
this.mColor = mColor;
this.mTextColor = mTextColor;
this.mStartTime = mStartTime;
this.mEndTime = mEndTime;
this.profileId = profileId;
this.lessonChangeDate = lessonChangeDate;
this.lessonChangeCount = lessonChangeCount;
}
public int getProfileId() {
return profileId;
}
public Date getLessonChangeDate() {
return lessonChangeDate;
}
public int getLessonChangeCount() {
return lessonChangeCount;
}
public void setProfileId(int profileId) {
this.profileId = profileId;
}
public void setLessonChangeDate(Date lessonChangeDate) {
this.lessonChangeDate = lessonChangeDate;
}
public void setLessonChangeCount(int lessonChangeCount) {
this.lessonChangeCount = lessonChangeCount;
}
@Override
public void setPlaceholder(boolean placeholder) {
}
@Override
public boolean isPlaceholder() {
return false;
}
@Override
public String getLocation() {
return null;
}
@Override
public void setLocation(String mLocation) {
}
@Override
public long getId() {
return mId;
}
@Override
public void setId(long mId) {
this.mId = mId;
}
@Override
public boolean getShowBadge() {
return false;
}
@Override
public void setShowBadge(boolean mShowBadge) {
}
@Override
public int getTextColor() {
return mTextColor;
}
@Override
public void setTextColor(int mTextColor) {
this.mTextColor = mTextColor;
}
@Override
public String getDescription() {
return null;
}
@Override
public void setDescription(String mDescription) {
}
@Override
public boolean isAllDay() {
return false;
}
@Override
public void setAllDay(boolean allDay) {
}
@Override
public Calendar getStartTime() {
return mStartTime;
}
@Override
public void setStartTime(Calendar mStartTime) {
this.mStartTime = mStartTime;
}
@Override
public Calendar getEndTime() {
return mEndTime;
}
@Override
public void setEndTime(Calendar mEndTime) {
this.mEndTime = mEndTime;
}
@Override
public String getTitle() {
return null;
}
@Override
public void setTitle(String mTitle) {
}
@Override
public Calendar getInstanceDay() {
return mInstanceDay;
}
@Override
public void setInstanceDay(Calendar mInstanceDay) {
this.mInstanceDay = mInstanceDay;
this.mInstanceDay.set(Calendar.HOUR, 0);
this.mInstanceDay.set(Calendar.MINUTE, 0);
this.mInstanceDay.set(Calendar.SECOND, 0);
this.mInstanceDay.set(Calendar.MILLISECOND, 0);
this.mInstanceDay.set(Calendar.AM_PM, 0);
}
@Override
public IDayItem getDayReference() {
return mDayReference;
}
@Override
public void setDayReference(IDayItem mDayReference) {
this.mDayReference = mDayReference;
}
@Override
public IWeekItem getWeekReference() {
return mWeekReference;
}
@Override
public void setWeekReference(IWeekItem mWeekReference) {
this.mWeekReference = mWeekReference;
}
@Override
public CalendarEvent copy() {
return new LessonChangeEvent(this);
}
@Override
public int getColor() {
return mColor;
}
}

View File

@ -1,21 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange
import android.view.View
import android.widget.TextView
import androidx.cardview.widget.CardView
import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R
class LessonChangeEventRenderer : EventRenderer<LessonChangeEvent>() {
override fun render(view: View?, event: LessonChangeEvent) {
val card = view?.findViewById<CardView>(R.id.lesson_change_card)
val changeText = view?.findViewById<TextView>(R.id.lesson_change_text)
val changeCount = view?.findViewById<TextView>(R.id.lessonChangeCount)
card?.setCardBackgroundColor(event.color)
changeText?.setTextColor(event.textColor)
changeCount?.setTextColor(event.textColor)
changeCount?.text = event.lessonChangeCount.toString()
}
override fun getEventLayout(): Int = R.layout.agenda_event_lesson_change
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
import pl.szczodrzynski.edziennik.utils.models.Date
class LessonChangesEvent(
val profileId: Int,
val date: Date,
val count: Int,
showBadge: Boolean
) : BaseEvent(
id = date.value.toLong(),
time = date.asCalendar,
color = 0xff78909c.toInt(),
showBadge = false,
showItemBadge = showBadge
) {
override fun copy() = LessonChangesEvent(profileId, date, count, showItemBadge)
override fun getShowBadge() = false
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges
import android.view.View
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaCounterItemBinding
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
class LessonChangesEventRenderer : EventRenderer<LessonChangesEvent>() {
override fun render(view: View, event: LessonChangesEvent) {
val b = AgendaWrappedCounterBinding.bind(view).item
val textColor = Colors.legibleTextColor(event.color)
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.setText(R.string.agenda_lesson_changes)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = event.showItemBadge
b.badgeBackground.background.setTintColor(
android.R.attr.colorBackground.resolveAttr(view.context)
)
b.badge.isVisible = event.showItemBadge
}
fun render(b: AgendaCounterItemBinding, event: LessonChangesEvent) {
val textColor = Colors.legibleTextColor(event.color)
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.setText(R.string.agenda_lesson_changes)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = event.showItemBadge
b.badgeBackground.background.setTintColor(
android.R.attr.colorBackground.resolveAttr(b.root.context)
)
b.badge.isVisible = event.showItemBadge
}
override fun getEventLayout(): Int = R.layout.agenda_wrapped_counter
}

View File

@ -1,19 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class TeacherAbsenceCounter (
val teacherAbsenceDate: Date,
var teacherAbsenceCount: Int = 0
) {
val startTime: Calendar
get() = Calendar.getInstance().apply {
set(teacherAbsenceDate.year, teacherAbsenceDate.month - 1, teacherAbsenceDate.day, 10, 0, 0)
}
val endTime: Calendar
get() = Calendar.getInstance().apply {
timeInMillis = startTime.timeInMillis + (45 * 60 * 1000)
}
}

View File

@ -1,188 +1,21 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import com.github.tibolte.agendacalendarview.models.CalendarEvent import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import com.github.tibolte.agendacalendarview.models.IWeekItem
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class TeacherAbsenceEvent : CalendarEvent { class TeacherAbsenceEvent(
/** val profileId: Int,
* Id of the event. val date: Date,
*/ val count: Int
private var mId: Long = 0 ) : BaseEvent(
/** id = date.value.toLong(),
* Color to be displayed in the agenda view. time = date.asCalendar,
*/ color = 0xffff1744.toInt(),
private var mColor: Int = 0 showBadge = false
/** ) {
* Text color displayed on the background color override fun copy() = TeacherAbsenceEvent(profileId, date, count)
*/
private var mTextColor: Int = 0
/**
* Calendar instance helping sorting the events per section in the agenda view.
*/
private var mInstanceDay: Calendar? = null
/**
* Start time of the event.
*/
private var mStartTime: Calendar? = null
/**
* End time of the event.
*/
private var mEndTime: Calendar? = null
/**
* References to a DayItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private var mDayReference: IDayItem? = null
/**
* References to a WeekItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private var mWeekReference: IWeekItem? = null
private var profileId: Int = 0
var teacherAbsenceDate: Date? = null
var teacherAbsenceCount: Int = 0
constructor(calendarEvent: TeacherAbsenceEvent) {
this.mId = calendarEvent.id
this.mColor = calendarEvent.color
this.mTextColor = calendarEvent.textColor
this.mStartTime = calendarEvent.startTime
this.mEndTime = calendarEvent.endTime
this.profileId = calendarEvent.profileId
this.teacherAbsenceDate = calendarEvent.teacherAbsenceDate
this.teacherAbsenceCount = calendarEvent.teacherAbsenceCount
}
constructor(mId: Long, mColor: Int, mTextColor: Int, mStartTime: Calendar, mEndTime: Calendar, profileId: Int, teacherAbsenceDate: Date, teacherAbsenceCount: Int) {
this.mId = mId
this.mColor = mColor
this.mTextColor = mTextColor
this.mStartTime = mStartTime
this.mEndTime = mEndTime
this.profileId = profileId
this.teacherAbsenceDate = teacherAbsenceDate
this.teacherAbsenceCount = teacherAbsenceCount
}
override fun setPlaceholder(placeholder: Boolean) {
}
override fun isPlaceholder(): Boolean {
return false
}
override fun getLocation(): String? {
return null
}
override fun setLocation(mLocation: String) {
}
override fun getId(): Long {
return mId
}
override fun setId(mId: Long) {
this.mId = mId
}
override fun getShowBadge(): Boolean {
return false
}
override fun setShowBadge(mShowBadge: Boolean) {
}
override fun getTextColor(): Int {
return mTextColor
}
override fun setTextColor(mTextColor: Int) {
this.mTextColor = mTextColor
}
override fun getDescription(): String? {
return null
}
override fun setDescription(mDescription: String) {
}
override fun isAllDay(): Boolean {
return false
}
override fun setAllDay(allDay: Boolean) {
}
override fun getStartTime(): Calendar? {
return mStartTime
}
override fun setStartTime(mStartTime: Calendar) {
this.mStartTime = mStartTime
}
override fun getEndTime(): Calendar? {
return mEndTime
}
override fun setEndTime(mEndTime: Calendar) {
this.mEndTime = mEndTime
}
override fun getTitle(): String? {
return null
}
override fun setTitle(mTitle: String) {
}
override fun getInstanceDay(): Calendar? {
return mInstanceDay
}
override fun setInstanceDay(mInstanceDay: Calendar) {
this.mInstanceDay = mInstanceDay
this.mInstanceDay!!.set(Calendar.HOUR, 0)
this.mInstanceDay!!.set(Calendar.MINUTE, 0)
this.mInstanceDay!!.set(Calendar.SECOND, 0)
this.mInstanceDay!!.set(Calendar.MILLISECOND, 0)
this.mInstanceDay!!.set(Calendar.AM_PM, 0)
}
override fun getDayReference(): IDayItem? {
return mDayReference
}
override fun setDayReference(mDayReference: IDayItem) {
this.mDayReference = mDayReference
}
override fun getWeekReference(): IWeekItem? {
return mWeekReference
}
override fun setWeekReference(mWeekReference: IWeekItem) {
this.mWeekReference = mWeekReference
}
override fun copy(): CalendarEvent {
return TeacherAbsenceEvent(this)
}
override fun getColor(): Int {
return mColor
}
} }

View File

@ -1,21 +1,48 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import android.view.View import android.view.View
import android.widget.TextView import androidx.core.view.isVisible
import androidx.cardview.widget.CardView
import com.github.tibolte.agendacalendarview.render.EventRenderer import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaCounterItemBinding
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
class TeacherAbsenceEventRenderer : EventRenderer<TeacherAbsenceEvent>() { class TeacherAbsenceEventRenderer : EventRenderer<TeacherAbsenceEvent>() {
override fun render(view: View?, event: TeacherAbsenceEvent) {
val card = view?.findViewById<CardView>(R.id.teacherAbsenceCard) override fun render(view: View, event: TeacherAbsenceEvent) {
val changeText = view?.findViewById<TextView>(R.id.teacherAbsenceText) val b = AgendaWrappedCounterBinding.bind(view).item
val changeCount = view?.findViewById<TextView>(R.id.teacherAbsenceCount) val textColor = Colors.legibleTextColor(event.color)
card?.setCardBackgroundColor(event.color)
changeText?.setTextColor(event.textColor) b.card.foreground.setTintColor(event.color)
changeCount?.setTextColor(event.textColor) b.card.background.setTintColor(event.color)
changeCount?.text = event.teacherAbsenceCount.toString() b.name.setText(R.string.agenda_teacher_absence)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = false
b.badge.isVisible = false
} }
override fun getEventLayout(): Int = R.layout.agenda_event_teacher_absence fun render(b: AgendaCounterItemBinding, event: TeacherAbsenceEvent) {
val textColor = Colors.legibleTextColor(event.color)
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.setText(R.string.agenda_teacher_absence)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = false
b.badge.isVisible = false
}
override fun getEventLayout(): Int = R.layout.agenda_wrapped_counter
} }

View File

@ -41,7 +41,8 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope {
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
// local/private variables go here // local/private variables go here
private val manager by lazy { app.attendanceManager } private val manager
get() = app.attendanceManager
private var viewType = AttendanceFragment.VIEW_DAYS private var viewType = AttendanceFragment.VIEW_DAYS
private var expandSubjectId = 0L private var expandSubjectId = 0L

View File

@ -47,7 +47,8 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope {
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
// local/private variables go here // local/private variables go here
private val manager by lazy { app.attendanceManager } private val manager
get() = app.attendanceManager
private var expandSubjectId = 0L private var expandSubjectId = 0L
private var attendance = listOf<AttendanceFull>() private var attendance = listOf<AttendanceFull>()

View File

@ -40,7 +40,8 @@ class GradesAdapter(
} }
private val app = activity.applicationContext as App private val app = activity.applicationContext as App
private val manager = app.gradesManager private val manager
get() = app.gradesManager
private val job = Job() private val job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext

View File

@ -48,9 +48,12 @@ class GradesListFragment : Fragment(), CoroutineScope {
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
// local/private variables go here // local/private variables go here
private val manager by lazy { app.gradesManager } private val manager
private val dontCountEnabled by lazy { manager.dontCountEnabled } get() = app.gradesManager
private val dontCountGrades by lazy { manager.dontCountGrades } private val dontCountEnabled
get() = manager.dontCountEnabled
private val dontCountGrades
get() = manager.dontCountGrades
private var expandSubjectId = 0L private var expandSubjectId = 0L
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

View File

@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.after
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.agenda.AgendaConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.bell.BellSyncConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.bell.BellSyncConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradesConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradesConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog
@ -59,6 +60,13 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) {
} }
override fun getItems() = listOfNotNull( override fun getItems() = listOfNotNull(
util.createActionItem(
text = R.string.menu_agenda_config,
icon = CommunityMaterial.Icon.cmd_calendar_outline
) {
AgendaConfigDialog(activity, reloadOnDismiss = false)
},
util.createActionItem( util.createActionItem(
text = R.string.menu_grades_config, text = R.string.menu_grades_config,
icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline

View File

@ -5,16 +5,16 @@
package pl.szczodrzynski.edziennik.ui.modules.timetable package pl.szczodrzynski.edziennik.ui.modules.timetable
import android.os.Bundle import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.asynclayoutinflater.view.AsyncLayoutInflater import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.view.isVisible
import androidx.core.view.marginTop
import androidx.core.view.setPadding import androidx.core.view.setPadding
import androidx.lifecycle.Observer import androidx.core.view.updateLayoutParams
import com.linkedin.android.tachyon.DayView import com.linkedin.android.tachyon.DayView
import com.linkedin.android.tachyon.DayViewConfig import com.linkedin.android.tachyon.DayViewConfig
import kotlinx.coroutines.* import kotlinx.coroutines.*
@ -24,14 +24,15 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.TimetableDayFragmentBinding
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR
import pl.szczodrzynski.edziennik.utils.ListenerScrollView
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.min import kotlin.math.min
@ -44,33 +45,28 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var inflater: AsyncLayoutInflater private lateinit var inflater: AsyncLayoutInflater
private lateinit var b: TimetableDayFragmentBinding
private val job: Job = Job() private val job: Job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
private var timeIndicatorJob: Job? = null
private lateinit var date: Date private lateinit var date: Date
private var startHour = DEFAULT_START_HOUR private var startHour = DEFAULT_START_HOUR
private var endHour = DEFAULT_END_HOUR private var endHour = DEFAULT_END_HOUR
private var firstEventMinute = 24 * 60 private var firstEventMinute = 24 * 60
private var paddingTop = 0
private val manager by lazy { app.timetableManager } private val manager
get() = app.timetableManager
// find SwipeRefreshLayout in the hierarchy // find SwipeRefreshLayout in the hierarchy
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) } private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
// the day ScrollView
private val dayScrollDelegate = lazy {
val dayScroll = ListenerScrollView(context!!)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
dayScroll.setOnRefreshLayoutEnabledListener { enabled ->
refreshLayout?.isEnabled = enabled
}
dayScroll
}
private val dayScroll by dayScrollDelegate
// the lesson DayView
private val dayView by lazy { private val dayView by lazy {
val dayView = DayView(context!!, DayViewConfig( val dayView = DayView(activity, DayViewConfig(
startHour = startHour, startHour = startHour,
endHour = endHour, endHour = endHour,
dividerHeight = 1.dp, dividerHeight = 1.dp,
@ -82,37 +78,33 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
eventMargin = 2.dp eventMargin = 2.dp
), true) ), true)
dayView.setPadding(10.dp) dayView.setPadding(10.dp)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) return@lazy dayView
dayScroll.addView(dayView)
dayView
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
context ?: return null context ?: return null
app = activity.application as App app = activity.application as App
this.inflater = AsyncLayoutInflater(context!!) this.inflater = AsyncLayoutInflater(requireContext())
date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday() date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday()
startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR
endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR
return FrameLayout(activity).apply {
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) b = TimetableDayFragmentBinding.inflate(inflater, null, false)
addView(ProgressBar(activity).apply { return b.root
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)
})
}
} }
override fun onPageCreated(): Boolean { override fun onPageCreated(): Boolean {
// observe lesson database // observe lesson database
app.db.timetableDao().getAllForDate(App.profileId, date).observe(this, Observer { lessons -> app.db.timetableDao().getAllForDate(App.profileId, date).observe(this) { lessons ->
launch { launch {
val events = withContext(Dispatchers.Default) { val events = withContext(Dispatchers.Default) {
app.db.eventDao().getAllByDateNow(App.profileId, date) app.db.eventDao().getAllByDateNow(App.profileId, date)
} }
processLessonList(lessons, events) processLessonList(lessons, events)
} }
}) }
return true return true
} }
@ -120,9 +112,10 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) { private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) {
// no lessons - timetable not downloaded yet // no lessons - timetable not downloaded yet
if (lessons.isEmpty()) { if (lessons.isEmpty()) {
inflater.inflate(R.layout.timetable_no_timetable, view as FrameLayout?) { view, _, parent -> inflater.inflate(R.layout.timetable_no_timetable, b.root) { view, _, _ ->
parent?.removeAllViews() b.root.removeAllViews()
parent?.addView(view) b.root.addView(view)
val b = TimetableNoTimetableBinding.bind(view) val b = TimetableNoTimetableBinding.bind(view)
val weekStart = date.weekStart.stringY_m_d val weekStart = date.weekStart.stringY_m_d
b.noTimetableSync.onClick { b.noTimetableSync.onClick {
@ -143,9 +136,9 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
} }
// one lesson indicating a day without lessons // one lesson indicating a day without lessons
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) { if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
inflater.inflate(R.layout.timetable_no_lessons, view as FrameLayout?) { view, _, parent -> inflater.inflate(R.layout.timetable_no_lessons, b.root) { view, _, _ ->
parent?.removeAllViews() b.root.removeAllViews()
parent?.addView(view) b.root.addView(view)
} }
return return
} }
@ -157,12 +150,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
return return
} }
// clear the root view and add the ScrollView b.scrollView.isVisible = true
(view as FrameLayout?)?.removeAllViews() b.dayFrame.removeView(b.dayView)
(view as FrameLayout?)?.addView(dayScroll) b.dayFrame.addView(dayView, 0)
// Inflate a label view for each hour the day view will display // Inflate a label view for each hour the day view will display
val hourLabelViews = ArrayList<View>() val hourLabelViews = mutableListOf<View>()
for (i in dayView.startHour..dayView.endHour) { for (i in dayView.startHour..dayView.endHour) {
if (!isAdded) if (!isAdded)
continue continue
@ -171,6 +164,11 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
hourLabelViews.add(hourLabelView) hourLabelViews.add(hourLabelView)
} }
dayView.setHourLabelViews(hourLabelViews) dayView.setHourLabelViews(hourLabelViews)
// measure dayView top padding needed for the timeIndicator
hourLabelViews.getOrNull(0)?.let {
it.measure(0, 0)
paddingTop = it.measuredHeight / 2 + dayView.paddingTop
}
lessons.forEach { it.showAsUnseen = !it.seen } lessons.forEach { it.showAsUnseen = !it.seen }
@ -201,8 +199,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
// Try to recycle an existing event view if there are enough left, otherwise inflate // Try to recycle an existing event view if there are enough left, otherwise inflate
// a new one // a new one
val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, dayView, false)) val eventView =
?: continue (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(
R.layout.timetable_lesson,
dayView,
false
)) ?: continue
val lb = TimetableLessonBinding.bind(eventView) val lb = TimetableLessonBinding.bind(eventView)
eventViews += eventView eventViews += eventView
@ -290,16 +292,50 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute)) eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute))
} }
updateTimeIndicator()
dayView.setEventViews(eventViews, eventTimeRanges) dayView.setEventViews(eventViews, eventTimeRanges)
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
dayScroll.scrollTo(0, firstEventTop.toInt()) b.scrollView.scrollTo(0, firstEventTop.toInt())
b.progressBar.isVisible = false
}
private fun updateTimeIndicator() {
val time = Time.getNow()
val isTimeInView =
date == Date.getToday() && time.hour in dayView.startHour..dayView.endHour
b.timeIndicator.isVisible = isTimeInView
b.timeIndicatorMarker.isVisible = isTimeInView
if (isTimeInView) {
val startTime = Time(dayView.startHour, 0, 0)
val seconds = time.inSeconds - startTime.inSeconds * 1f
b.timeIndicator.updateLayoutParams<FrameLayout.LayoutParams> {
topMargin = (seconds * dayView.minuteHeight / 60f).toInt() + paddingTop
}
b.timeIndicatorMarker.updateLayoutParams<FrameLayout.LayoutParams> {
topMargin = b.timeIndicator.marginTop - (16.dp / 2) + (1.dp / 2)
}
}
if (timeIndicatorJob == null) {
timeIndicatorJob = startCoroutineTimer(repeatMillis = 30000) {
updateTimeIndicator()
}
}
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (dayScrollDelegate.isInitialized()) {
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
dayScroll.scrollTo(0, firstEventTop.toInt()) b.scrollView.scrollTo(0, firstEventTop.toInt())
} updateTimeIndicator()
}
override fun onPause() {
super.onPause()
timeIndicatorJob?.cancel()
timeIndicatorJob = null
} }
} }

View File

@ -175,7 +175,7 @@ class DateDropdown : TextInputDropDown {
} }
} }
fun pickerDialog() { private fun pickerDialog() {
val date = getSelected() as? Date ?: Date.getToday() val date = getSelected() as? Date ?: Date.getToday()
MaterialDatePicker.Builder.datePicker() MaterialDatePicker.Builder.datePicker()

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.util.AttributeSet
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
class EventTypeDropdown : TextInputDropDown {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
lateinit var db: AppDb
var profileId: Int = 0
var onTypeSelected: ((eventType: EventType) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val types = withContext(Dispatchers.Default) {
val list = mutableListOf<Item>()
var types = db.eventTypeDao().getAllNow(profileId)
if (types.none { it.id in -1L..10L }) {
types = db.eventTypeDao().addDefaultTypes(context, profileId)
}
list += types.map {
Item(it.id, it.name, tag = it, icon = IconicsDrawable(context).apply {
icon = CommunityMaterial.Icon.cmd_circle
sizeDp = 24
colorInt = it.color
})
}
list
}
clear().append(types)
isEnabled = true
setOnChangeListener {
when (it.tag) {
is EventType -> {
// selected an event type
onTypeSelected?.invoke(it.tag)
true
}
else -> false
}
}
}
/**
* Select an event type by the [typeId].
*/
fun selectType(typeId: Long) = select(typeId)
/**
* Select an event type by the [typeId] **if it's not selected yet**.
*/
fun selectDefault(typeId: Long?) {
if (typeId == null || selected != null)
return
selectType(typeId)
}
/**
* Get the currently selected event type.
* ### Returns:
* - null if no valid type is selected
* - [EventType] - the selected event type
*/
fun getSelected(): EventType? {
return when (selected?.tag) {
is EventType -> selected?.tag as EventType
else -> null
}
}
}

View File

@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.crc16 import pl.szczodrzynski.edziennik.crc16
import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Subject
import pl.szczodrzynski.edziennik.ui.dialogs.input import pl.szczodrzynski.edziennik.ui.dialogs.input
import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.edziennik.utils.TextInputDropDown
@ -40,7 +41,7 @@ class SubjectDropdown : TextInputDropDown {
var showNoSubject = true var showNoSubject = true
var showCustomSubject = false var showCustomSubject = false
var customSubjectName = "" var customSubjectName = ""
var onSubjectSelected: ((subjectId: Long?) -> Unit)? = null var onSubjectSelected: ((subject: Subject?) -> Unit)? = null
var onCustomSubjectSelected: ((subjectName: String) -> Unit)? = null var onCustomSubjectSelected: ((subjectName: String) -> Unit)? = null
override fun create(context: Context) { override fun create(context: Context) {
@ -73,7 +74,7 @@ class SubjectDropdown : TextInputDropDown {
list += subjects.map { Item( list += subjects.map { Item(
it.id, it.id,
it.longName, it.longName,
tag = it.id tag = it
) } ) }
list list
@ -91,10 +92,11 @@ class SubjectDropdown : TextInputDropDown {
} }
-1L -> { -1L -> {
// no subject // no subject
deselect()
onSubjectSelected?.invoke(null) onSubjectSelected?.invoke(null)
true false
} }
is Long -> { is Subject -> {
// selected a subject // selected a subject
onSubjectSelected?.invoke(it.tag) onSubjectSelected?.invoke(it.tag)
true true
@ -104,7 +106,7 @@ class SubjectDropdown : TextInputDropDown {
} }
} }
fun customNameDialog() { private fun customNameDialog() {
activity ?: return activity ?: return
MaterialAlertDialogBuilder(activity!!) MaterialAlertDialogBuilder(activity!!)
.setTitle("Własny przedmiot") .setTitle("Własny przedmiot")
@ -127,32 +129,37 @@ class SubjectDropdown : TextInputDropDown {
.show() .show()
} }
fun selectSubject(subjectId: Long) { /**
if (select(subjectId) == null) * Select a subject by the [subjectId].
select(Item( */
subjectId, fun selectSubject(subjectId: Long): Item? {
"nieznany przedmiot ($subjectId)", if (subjectId == -1L) {
tag = subjectId deselect()
)) return null
}
return select(subjectId)
} }
fun selectDefault(subjectId: Long?) { /**
* Select a subject by the [subjectId] **if it's not selected yet**.
*/
fun selectDefault(subjectId: Long?): Item? {
if (subjectId == null || selected != null) if (subjectId == null || selected != null)
return return null
selectSubject(subjectId) return selectSubject(subjectId)
} }
/** /**
* Get the currently selected subject. * Get the currently selected subject.
* ### Returns: * ### Returns:
* - null if no valid subject is selected * - null if no valid subject is selected
* - [Long] - the selected subject's ID * - [Subject] - the selected subject
* - [String] - a custom subject name entered, if [showCustomSubject] == true * - [String] - a custom subject name entered, if [showCustomSubject] == true
*/ */
fun getSelected(): Any? { fun getSelected(): Any? {
return when (selected?.tag) { return when (selected?.tag) {
-1L -> null -1L -> null
is Long -> selected?.tag as Long is Subject -> selected?.tag as Subject
is String -> selected?.tag as String is String -> selected?.tag as String
else -> null else -> null
} }

View File

@ -5,13 +5,12 @@
package pl.szczodrzynski.edziennik.ui.modules.views package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.edziennik.utils.TextInputDropDown
class TeacherDropdown : TextInputDropDown { class TeacherDropdown : TextInputDropDown {
@ -19,22 +18,10 @@ class TeacherDropdown : TextInputDropDown {
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val activity: AppCompatActivity?
get() {
var context: Context? = context ?: return null
if (context is AppCompatActivity) return context
while (context is ContextWrapper) {
if (context is AppCompatActivity)
return context
context = context.baseContext
}
return null
}
lateinit var db: AppDb lateinit var db: AppDb
var profileId: Int = 0 var profileId: Int = 0
var showNoTeacher = true var showNoTeacher = true
var onTeacherSelected: ((teacherId: Long?) -> Unit)? = null var onTeacherSelected: ((teacher: Teacher?) -> Unit)? = null
override fun create(context: Context) { override fun create(context: Context) {
super.create(context) super.create(context)
@ -58,7 +45,7 @@ class TeacherDropdown : TextInputDropDown {
list += teachers.map { Item( list += teachers.map { Item(
it.id, it.id,
it.fullName, it.fullName,
tag = it.id tag = it
) } ) }
list list
@ -71,10 +58,11 @@ class TeacherDropdown : TextInputDropDown {
when (it.tag) { when (it.tag) {
-1L -> { -1L -> {
// no teacher // no teacher
deselect()
onTeacherSelected?.invoke(null) onTeacherSelected?.invoke(null)
true false
} }
is Long -> { is Teacher -> {
// selected a teacher // selected a teacher
onTeacherSelected?.invoke(it.tag) onTeacherSelected?.invoke(it.tag)
true true
@ -84,31 +72,36 @@ class TeacherDropdown : TextInputDropDown {
} }
} }
fun selectTeacher(teacherId: Long) { /**
if (select(teacherId) == null) * Select a teacher by the [teacherId].
select(Item( */
teacherId, fun selectTeacher(teacherId: Long): Item? {
"nieznany nauczyciel ($teacherId)", if (teacherId == -1L) {
tag = teacherId deselect()
)) return null
}
return select(teacherId)
} }
fun selectDefault(teacherId: Long?) { /**
* Select a teacher by the [teacherId] **if it's not selected yet**.
*/
fun selectDefault(teacherId: Long?): Item? {
if (teacherId == null || selected != null) if (teacherId == null || selected != null)
return return null
selectTeacher(teacherId) return selectTeacher(teacherId)
} }
/** /**
* Get the currently selected teacher. * Get the currently selected teacher.
* ### Returns: * ### Returns:
* - null if no valid teacher is selected * - null if no valid teacher is selected
* - [Long] - the selected teacher's ID * - [Teacher] - the selected teacher
*/ */
fun getSelected(): Long? { fun getSelected(): Teacher? {
return when (selected?.tag) { return when (selected?.tag) {
-1L -> null -1L -> null
is Long -> selected?.tag as Long is Teacher -> selected?.tag as Teacher
else -> null else -> null
} }
} }

View File

@ -5,9 +5,7 @@
package pl.szczodrzynski.edziennik.ui.modules.views package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
@ -20,22 +18,10 @@ class TeamDropdown : TextInputDropDown {
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val activity: AppCompatActivity?
get() {
var context: Context? = context ?: return null
if (context is AppCompatActivity) return context
while (context is ContextWrapper) {
if (context is AppCompatActivity)
return context
context = context.baseContext
}
return null
}
lateinit var db: AppDb lateinit var db: AppDb
var profileId: Int = 0 var profileId: Int = 0
var showNoTeam = true var showNoTeam = true
var onTeamSelected: ((teamId: Long?) -> Unit)? = null var onTeamSelected: ((team: Team?) -> Unit)? = null
override fun create(context: Context) { override fun create(context: Context) {
super.create(context) super.create(context)
@ -59,7 +45,7 @@ class TeamDropdown : TextInputDropDown {
list += teams.map { Item( list += teams.map { Item(
it.id, it.id,
it.name, it.name,
tag = it.id tag = it
) } ) }
list list
@ -72,10 +58,11 @@ class TeamDropdown : TextInputDropDown {
when (it.tag) { when (it.tag) {
-1L -> { -1L -> {
// no team // no team
deselect()
onTeamSelected?.invoke(null) onTeamSelected?.invoke(null)
true false
} }
is Long -> { is Team -> {
// selected a team // selected a team
onTeamSelected?.invoke(it.tag) onTeamSelected?.invoke(it.tag)
true true
@ -85,21 +72,29 @@ class TeamDropdown : TextInputDropDown {
} }
} }
fun selectTeam(teamId: Long) { /**
if (select(teamId) == null) * Select a teacher by the [teamId].
select(Item( */
teamId, fun selectTeam(teamId: Long): Item? {
"nieznana grupa ($teamId)", if (teamId == -1L) {
tag = teamId deselect()
)) return null
}
return select(teamId)
} }
fun selectDefault(teamId: Long?) { /**
* Select a team by the [teamId] **if it's not selected yet**.
*/
fun selectDefault(teamId: Long?): Item? {
if (teamId == null || selected != null) if (teamId == null || selected != null)
return return null
selectTeam(teamId) return selectTeam(teamId)
} }
/**
* Select a team of the [Team.TYPE_CLASS] type.
*/
fun selectTeamClass() { fun selectTeamClass() {
select(items.singleOrNull { select(items.singleOrNull {
it.tag is Team && it.tag.type == Team.TYPE_CLASS it.tag is Team && it.tag.type == Team.TYPE_CLASS
@ -110,12 +105,12 @@ class TeamDropdown : TextInputDropDown {
* Get the currently selected team. * Get the currently selected team.
* ### Returns: * ### Returns:
* - null if no valid team is selected * - null if no valid team is selected
* - [Long] - the team's ID * - [Team] - the selected team
*/ */
fun getSelected(): Any? { fun getSelected(): Team? {
return when (selected?.tag) { return when (selected?.tag) {
-1L -> null -1L -> null
is Long -> selected?.tag as Long is Team -> selected?.tag as Team
else -> null else -> null
} }
} }

View File

@ -175,7 +175,7 @@ class TimeDropdown : TextInputDropDown {
return !noTimetable return !noTimetable
} }
fun pickerDialog() { private fun pickerDialog() {
val time = (getSelected() as? Pair<*, *>)?.first as? Time ?: Time.getNow() val time = (getSelected() as? Pair<*, *>)?.first as? Time ?: Time.getNow()
MaterialTimePicker.Builder() MaterialTimePicker.Builder()

View File

@ -1,7 +1,11 @@
package pl.szczodrzynski.edziennik.utils package pl.szczodrzynski.edziennik.utils
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.view.menu.MenuPopupHelper
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
@ -29,10 +33,11 @@ open class TextInputDropDown : TextInputEditText {
val selectedId val selectedId
get() = selected?.id get() = selected?.id
fun updateText() { private fun updateText() {
setText(selected?.displayText ?: selected?.text) setText(selected?.displayText ?: selected?.text)
} }
@SuppressLint("RestrictedApi")
open fun create(context: Context) { open fun create(context: Context) {
val drawable = IconicsDrawable(context, CommunityMaterial.Icon.cmd_chevron_down).apply { val drawable = IconicsDrawable(context, CommunityMaterial.Icon.cmd_chevron_down).apply {
colorInt = Themes.getPrimaryTextColor(context) colorInt = Themes.getPrimaryTextColor(context)
@ -58,7 +63,9 @@ open class TextInputDropDown : TextInputEditText {
val popup = PopupMenu(context, this) val popup = PopupMenu(context, this)
items.forEachIndexed { index, item -> items.forEachIndexed { index, item ->
popup.menu.add(0, item.id.toInt(), index, item.text) popup.menu.add(0, item.id.toInt(), index, item.text).also {
it.icon = item.icon
}
} }
popup.setOnMenuItemClickListener { menuItem -> popup.setOnMenuItemClickListener { menuItem ->
@ -70,29 +77,46 @@ open class TextInputDropDown : TextInputEditText {
true true
} }
popup.setOnDismissListener { val helper = MenuPopupHelper(context, popup.menu as MenuBuilder, this)
helper.setForceShowIcon(true)
helper.setOnDismissListener {
clearFocus() clearFocus()
} }
helper.show()
popup.show()
} }
} }
fun select(item: Item): Item? { /**
* Select an arbitrary [item]. Allows to select an item not present
* in the original list.
*/
fun select(item: Item): Item {
selected = item selected = item
updateText() updateText()
error = null error = null
return item return item
} }
/**
* Select an item by its ID. Returns the selected item
* if found.
*/
fun select(id: Long?): Item? { fun select(id: Long?): Item? {
return items.singleOrNull { it.id == id }?.let { select(it) } return items.singleOrNull { it.id == id }?.let { select(it) }
} }
/**
* Select an item by its tag. Returns the selected item
* if found.
*/
fun select(tag: Any?): Item? { fun select(tag: Any?): Item? {
return items.singleOrNull { it.tag == tag }?.let { select(it) } return items.singleOrNull { it.tag == tag }?.let { select(it) }
} }
/**
* Select an item by its index. Returns the selected item
* if the index exists.
*/
fun select(index: Int): Item? { fun select(index: Int): Item? {
return items.getOrNull(index)?.let { select(it) } return items.getOrNull(index)?.let { select(it) }
} }
@ -143,5 +167,11 @@ open class TextInputDropDown : TextInputEditText {
} }
} }
class Item(val id: Long, val text: CharSequence, val displayText: CharSequence? = null, val tag: Any? = null) class Item(
val id: Long,
val text: CharSequence,
val displayText: CharSequence? = null,
val tag: Any? = null,
val icon: Drawable? = null
)
} }

View File

@ -4,12 +4,17 @@
package pl.szczodrzynski.edziennik.utils.managers package pl.szczodrzynski.edziennik.utils.managers
import androidx.core.view.isVisible
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import com.mikepenz.iconics.view.IconicsTextView
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.startCoroutineTimer
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class EventManager(val app: App) : CoroutineScope { class EventManager(val app: App) : CoroutineScope {
@ -32,4 +37,41 @@ class EventManager(val app: App) : CoroutineScope {
app.db.metadataDao().setSeen(event.profileId, event, true) app.db.metadataDao().setSeen(event.profileId, event, true)
} }
} }
fun setEventTopic(
title: IconicsTextView,
event: EventFull,
showType: Boolean = true,
doneIconColor: Int? = null
) {
var eventTopic = if (showType)
"${event.typeName ?: "wydarzenie"} - ${event.topic}"
else
event.topic
if (event.addedManually) {
eventTopic = "{cmd-clipboard-edit-outline} $eventTopic"
}
title.text = eventTopic
title.setCompoundDrawables(
null,
null,
if (event.isDone) IconicsDrawable(title.context).apply {
icon = CommunityMaterial.Icon.cmd_check
colorInt = doneIconColor ?: R.color.md_green_500.resolveColor(title.context)
sizeDp = 24
} else null,
null
)
}
fun setLegendText(legend: IconicsTextView, event: EventFull) {
legend.text = listOfNotNull(
if (event.addedManually) R.string.legend_event_added_manually else null,
if (event.isDone) R.string.legend_event_is_done else null
).map { legend.context.getString(it) }.join("\n")
legend.isVisible = legend.text.isNotBlank()
}
} }

View File

@ -94,17 +94,29 @@ public class Date implements Comparable<Date> {
} }
} }
public long getInMillis() { public Calendar getAsCalendar() {
Calendar c = Calendar.getInstance(); Calendar c = Calendar.getInstance();
c.set(year, month - 1, day, 0, 0, 0); c.set(year, month - 1, day, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0); c.set(Calendar.MILLISECOND, 0);
return c;
}
public Calendar getAsCalendar(Time time) {
if (time == null)
return getAsCalendar();
Calendar c = Calendar.getInstance();
c.set(year, month - 1, day, time.hour, time.minute, time.second);
c.set(Calendar.MILLISECOND, 0);
return c;
}
public long getInMillis() {
Calendar c = getAsCalendar();
return c.getTimeInMillis(); return c.getTimeInMillis();
} }
public long getInMillisUtc() { public long getInMillisUtc() {
Calendar c = Calendar.getInstance(); Calendar c = getAsCalendar();
c.set(year, month - 1, day, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0);
c.setTimeZone(TimeZone.getTimeZone("UTC")); c.setTimeZone(TimeZone.getTimeZone("UTC"));
return c.getTimeInMillis(); return c.getTimeInMillis();
} }
@ -179,13 +191,7 @@ public class Date implements Comparable<Date> {
} }
public long combineWith(Time time) { public long combineWith(Time time) {
if (time == null) { return getAsCalendar(time).getTimeInMillis();
return getInMillis();
}
Calendar c = Calendar.getInstance();
c.set(this.year, this.month - 1, this.day, time.hour, time.minute, time.second);
c.set(Calendar.MILLISECOND, 0);
return c.getTimeInMillis();
} }
public int getWeekDay() { public int getWeekDay() {

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-14.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/md_red_500"
android:pathData="M0,4V20L12,12.25" />
</vector>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-11.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_8dp"
android:foreground="@drawable/bg_rounded_8dp_outline"
tools:backgroundTint="#ff1744"
tools:foregroundTint="#ff1744">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:padding="10dp"
android:textAppearance="@style/NavView.TextView.Medium"
tools:text="@string/agenda_lesson_changes"
tools:textColor="@color/md_white_1000" />
<TextView
android:id="@+id/count"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginVertical="2dp"
android:gravity="center"
android:paddingHorizontal="16dp"
android:textSize="20sp"
tools:text="3"
tools:textColor="@color/md_white_1000" />
<View
android:id="@+id/badgeBackground"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_rounded_8dp"
android:layout_marginRight="-24dp"
android:layout_marginEnd="-24dp"
android:layout_marginTop="-24dp"
tools:backgroundTint="?android:colorBackground"/>
</LinearLayout>
<View
android:id="@+id/badge"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end"
android:layout_margin="8dp"
android:background="@drawable/unread_red_circle" />
</FrameLayout>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-8.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_8dp"
android:foreground="@drawable/bg_rounded_8dp_outline"
tools:backgroundTint="@color/blue_selected"
tools:foregroundTint="@color/blue_selected">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:padding="10dp">
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ellipsize="end"
android:maxLines="2"
android:textSize="16sp"
tools:text="sprawdzian - Język polski"
tools:textColor="@color/md_white_1000" />
</LinearLayout>
<View
android:id="@+id/badgeBackground"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="-24dp"
android:layout_marginEnd="-24dp"
android:layout_marginRight="-24dp"
android:background="@drawable/bg_rounded_8dp"
tools:backgroundTint="?android:colorBackground" />
</LinearLayout>
<View
android:id="@+id/badge"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end"
android:layout_margin="8dp"
android:background="@drawable/unread_red_circle" />
</FrameLayout>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-8.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_8dp"
android:foreground="@drawable/bg_rounded_8dp_outline"
tools:backgroundTint="@color/blue_selected"
tools:foregroundTint="@color/blue_selected">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:padding="10dp">
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ellipsize="end"
android:maxLines="3"
android:textSize="16sp"
tools:text="sprawdzian - Język polski"
tools:textColor="@color/md_white_1000" />
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textSize="12sp"
tools:text="9:05, biologia, Jan Kowalski, 7a"
tools:textColor="@color/md_white_1000" />
</LinearLayout>
<View
android:id="@+id/badgeBackground"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="-24dp"
android:layout_marginEnd="-24dp"
android:layout_marginRight="-24dp"
android:background="@drawable/bg_rounded_8dp"
tools:backgroundTint="?android:colorBackground" />
</LinearLayout>
<View
android:id="@+id/badge"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end"
android:layout_margin="8dp"
android:background="@drawable/unread_red_circle" />
</FrameLayout>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-10.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_8dp"
android:foreground="@drawable/bg_rounded_8dp_outline"
tools:backgroundTint="@color/blue_selected"
tools:foregroundTint="@color/blue_selected">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:padding="10dp"
android:textAppearance="@style/NavView.TextView.Medium"
tools:text="informacja"
tools:textColor="@color/md_white_1000" />
<TextView
android:id="@+id/count"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginVertical="2dp"
android:layout_marginRight="-1dp"
android:background="@drawable/bg_rounded_8dp"
android:gravity="center"
android:paddingStart="16dp"
android:paddingLeft="16dp"
android:paddingEnd="18dp"
android:paddingRight="18dp"
android:textSize="20sp"
tools:backgroundTint="?android:colorBackground"
tools:text="3" />
</LinearLayout>
<View
android:id="@+id/badge"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end"
android:layout_margin="6dp"
android:background="@drawable/unread_red_circle" />
</FrameLayout>

View File

@ -1,15 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-11.
-->
<com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android" <com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<include <include
layout="@layout/row_lesson_change_item" android:id="@+id/item"
layout="@layout/agenda_counter_item"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" /> android:layout_weight="1" />

View File

@ -1,13 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-9.
-->
<com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android" <com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<include <include
layout="@layout/row_teacher_absence_item" android:id="@+id/item"
layout="@layout/agenda_event_item"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" /> android:layout_weight="1" />

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-11.
-->
<com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<include
android:id="@+id/item"
layout="@layout/agenda_event_compact_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</com.github.tibolte.agendacalendarview.agenda.AgendaEventView>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-10.
-->
<com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<include
android:id="@+id/item"
layout="@layout/agenda_group_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</com.github.tibolte.agendacalendarview.agenda.AgendaEventView>

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-10.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="config"
type="pl.szczodrzynski.edziennik.config.ProfileConfigUI" />
<variable
name="isAgendaMode"
type="boolean" />
</data>
<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:padding="24dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/agenda_config_appearance"
android:textAppearance="@style/NavView.TextView.Subtitle" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaLessonChanges}"
android:minHeight="32dp"
android:text="@string/agenda_config_lesson_changes" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaTeacherAbsence}"
android:minHeight="32dp"
android:text="@string/agenda_config_teacher_absence" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaCompactMode}"
android:enabled="@{isAgendaMode}"
android:minHeight="32dp"
android:text="@string/agenda_config_compact_mode"
tools:enabled="false" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:enabled="@{isAgendaMode}"
android:text="@string/agenda_config_compact_mode_hint"
android:textAppearance="@style/NavView.TextView.Helper"
tools:enabled="false" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaGroupByType}"
android:enabled="@{isAgendaMode}"
android:minHeight="32dp"
android:text="@string/agenda_config_group_by_type"
tools:enabled="false" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/agenda_config_mode_unavailable"
android:textStyle="italic"
android:visibility="@{isAgendaMode ? View.GONE : View.VISIBLE}" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/agenda_config_event_sharing"
android:textAppearance="@style/NavView.TextView.Subtitle" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/eventSharingEnabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:minHeight="32dp"
android:text="@string/agenda_config_event_sharing_enabled" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/agenda_config_elearning"
android:textAppearance="@style/NavView.TextView.Subtitle" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/agenda_config_not_available_yet"
android:textAppearance="@style/NavView.TextView.Small"
android:textStyle="italic" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/elearningEnabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaElearningMark}"
android:enabled="false"
android:minHeight="32dp"
android:onClick="@{() -> elearningType.setEnabled(elearningEnabled.isChecked())}"
android:text="@string/agenda_config_elearning_mark" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/elearningType"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{config.agendaElearningMark}"
android:hint="@string/agenda_config_elearning_type">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/elearningTypeDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:focusable="true"
android:focusableInTouchMode="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaElearningGroup}"
android:enabled="false"
android:minHeight="32dp"
android:text="@string/agenda_config_elearning_group" />
</LinearLayout>
</ScrollView>
</layout>

View File

@ -3,15 +3,12 @@
~ Copyright (c) Kuba Szczodrzyński 2019-12-16. ~ Copyright (c) Kuba Szczodrzyński 2019-12-16.
--> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView 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" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -41,47 +38,43 @@
tools:text="8:00 - 14:20 (7 lekcji, 6 godzin, 20 minut)" tools:text="8:00 - 14:20 (7 lekcji, 6 godzin, 20 minut)"
tools:visibility="visible" /> tools:visibility="visible" />
<LinearLayout <FrameLayout
android:id="@+id/lessonChangeLayout" android:id="@+id/lessonChangesFrame"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?selectableItemBackground"> android:foreground="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:paddingVertical="5dp"
android:visibility="gone"
tools:visibility="visible">
<include <include
android:id="@+id/lessonChangeContainer" android:id="@+id/lessonChanges"
layout="@layout/row_lesson_change_item" layout="@layout/agenda_counter_item" />
android:layout_width="match_parent" </FrameLayout>
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="5dp"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
<LinearLayout <FrameLayout
android:id="@+id/teacherAbsenceLayout" android:id="@+id/teacherAbsenceFrame"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?selectableItemBackground"> android:foreground="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:paddingVertical="5dp"
android:visibility="gone"
tools:visibility="visible">
<include <include
android:id="@+id/teacherAbsenceContainer" android:id="@+id/teacherAbsence"
layout="@layout/row_teacher_absence_item" layout="@layout/agenda_counter_item" />
android:layout_width="match_parent" </FrameLayout>
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="5dp"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/eventsNoData" android:id="@+id/eventsNoData"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:paddingVertical="16dp"
android:orientation="vertical" android:orientation="vertical"
android:paddingVertical="16dp"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible">
@ -89,21 +82,20 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:gravity="center"
app:drawableTopCompat="@drawable/ic_no_events"
android:drawablePadding="16dp" android:drawablePadding="16dp"
android:fontFamily="sans-serif-light" android:fontFamily="sans-serif-light"
android:gravity="center"
android:text="@string/dialog_day_no_events" android:text="@string/dialog_day_no_events"
android:textSize="24sp" /> android:textSize="24sp"
app:drawableTopCompat="@drawable/ic_no_events" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/dialog_no_events_hint"
android:layout_gravity="center" android:layout_gravity="center"
android:gravity="center" android:gravity="center"
android:text="@string/dialog_no_events_hint"
android:textStyle="italic" /> android:textStyle="italic" />
</LinearLayout> </LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
@ -112,9 +104,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:clipToPadding="false" android:clipToPadding="false"
tools:visibility="visible" tools:listitem="@layout/event_list_item"
tools:listitem="@layout/event_list_item" /> tools:visibility="visible" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</layout>

View File

@ -105,9 +105,23 @@
</LinearLayout> </LinearLayout>
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/legend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="[!] wydarzenie dodane ręcznie\n[V] oznaczono jako wykonane" />
<View
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/divider"/>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper" android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_event_details_teacher" android:text="@string/dialog_event_details_teacher"
android:visibility="@{event.teacherName != null ? View.VISIBLE : View.GONE}"/> android:visibility="@{event.teacherName != null ? View.VISIBLE : View.GONE}"/>

View File

@ -94,7 +94,7 @@
android:layout_weight="1" android:layout_weight="1"
android:hint="@string/dialog_event_manual_type"> android:hint="@string/dialog_event_manual_type">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown <pl.szczodrzynski.edziennik.ui.modules.views.EventTypeDropdown
android:id="@+id/typeDropdown" android:id="@+id/typeDropdown"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -4,8 +4,7 @@
--> -->
<layout xmlns:tools="http://schemas.android.com/tools" <layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:app="http://schemas.android.com/apk/res-auto">
<data> <data>
<import type="android.view.View"/> <import type="android.view.View"/>
@ -61,12 +60,10 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/topic" android:id="@+id/topic"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_weight="1" android:layout_weight="1"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="3" android:maxLines="3"
@ -79,23 +76,13 @@
style="@style/Widget.MaterialComponents.Button.OutlinedButton" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:fontFamily="@font/community_material_font_v5_8_55" android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="0dp" android:minWidth="0dp"
android:text="\uf2f4" android:text="\uf2f4"
android:textSize="20sp" android:textSize="20sp"
tools:visibility="gone" /> tools:visibility="visible" />
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/isDone"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="top"
android:layout_marginHorizontal="4dp"
android:visibility="gone"
app:iiv_color="@color/md_green_500"
app:iiv_icon="cmd-check"
tools:background="@sample/check" />
</LinearLayout> </LinearLayout>
<com.mikepenz.iconics.view.IconicsTextView <com.mikepenz.iconics.view.IconicsTextView

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<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">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:id="@+id/dayScroll"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.linkedin.android.tachyon.DayView
android:id="@+id/day"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
app:dividerHeight="1dp"
app:endHour="18"
app:eventMargin="2dp"
app:halfHourDividerColor="#e0e0e0"
app:halfHourHeight="60dp"
app:hourDividerColor="#b0b0b0"
app:hourLabelMarginEnd="10dp"
app:hourLabelWidth="40dp"
app:startHour="5"
tools:visibility="gone"/>
</ScrollView>
</FrameLayout>
</layout>

View File

@ -1,35 +0,0 @@
<androidx.cardview.widget.CardView android:id="@+id/lesson_change_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="#78909c"
app:cardCornerRadius="5dp"
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="horizontal"
android:padding="10dp">
<TextView
android:id="@+id/lesson_change_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:textAppearance="@style/NavView.TextView.Medium"
android:text="@string/agenda_lesson_changes" />
<TextView
android:id="@+id/lessonChangeCount"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="20sp"
tools:text="3" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,35 +0,0 @@
<androidx.cardview.widget.CardView android:id="@+id/teacherAbsenceCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="#ff1744"
app:cardCornerRadius="5dp"
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="horizontal"
android:padding="10dp">
<TextView
android:id="@+id/teacherAbsenceText"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:textAppearance="@style/NavView.TextView.Medium"
android:text="@string/agenda_teacher_absence" />
<TextView
android:id="@+id/teacherAbsenceCount"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="20sp"
tools:text="3" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-14.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<pl.szczodrzynski.edziennik.utils.ListenerScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible">
<FrameLayout
android:id="@+id/dayFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout_height="match_parent">
<View
android:id="@+id/timeIndicatorMarker"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginHorizontal="8dp"
android:background="@drawable/timetable_marker_triangle"
tools:layout_marginTop="92.5dp" />
<View
android:id="@+id/timeIndicator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginHorizontal="8dp"
android:background="@color/md_red_500"
tools:layout_marginTop="100dp" />
<com.linkedin.android.tachyon.DayView
android:id="@+id/dayView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout_height="match_parent" />
</FrameLayout>
</pl.szczodrzynski.edziennik.utils.ListenerScrollView>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>

View File

@ -1427,6 +1427,23 @@
<string name="permissions_generate_timetable">Aby móc zapisać wygenerowany plan lekcji musisz przyznać uprawnienia dostępu do pamięci urządzenia.\n\nKliknij OK, aby przyznać uprawnienia.</string> <string name="permissions_generate_timetable">Aby móc zapisać wygenerowany plan lekcji musisz przyznać uprawnienia dostępu do pamięci urządzenia.\n\nKliknij OK, aby przyznać uprawnienia.</string>
<string name="privacy_policy_dialog_html"><![CDATA[Korzystając z aplikacji potwierdzasz <a href="https://szkolny.eu/privacy-policy">przeczytanie Polityki prywatności</a> i akceptujesz jej postanowienia.<br /><br />Autorzy aplikacji nie biorą odpowiedzialności za korzystanie z aplikacji Szkolny.eu.]]></string> <string name="privacy_policy_dialog_html"><![CDATA[Korzystając z aplikacji potwierdzasz <a href="https://szkolny.eu/privacy-policy">przeczytanie Polityki prywatności</a> i akceptujesz jej postanowienia.<br /><br />Autorzy aplikacji nie biorą odpowiedzialności za korzystanie z aplikacji Szkolny.eu.]]></string>
<string name="login_chooser_version_format">Szkolny.eu v%s\n%s</string> <string name="login_chooser_version_format">Szkolny.eu v%s\n%s</string>
<string name="menu_agenda_config">Ustawienia terminarza</string>
<string name="agenda_config_appearance">Wygląd</string>
<string name="agenda_config_lesson_changes">Pokazuj zmiany planu lekcji</string>
<string name="agenda_config_teacher_absence">Pokazuj nieobecności nauczycieli</string>
<string name="agenda_config_compact_mode">Tryb kompaktowy</string>
<string name="agenda_config_compact_mode_hint">Mniejszy rozmiar wydarzeń na liście</string>
<string name="agenda_config_group_by_type">Grupuj wydarzenia tego samego typu</string>
<string name="agenda_config_mode_unavailable">Niedostępne w trybie kalendarza</string>
<string name="agenda_config_event_sharing">Udostępnianie wydarzeń</string>
<string name="agenda_config_event_sharing_enabled">Włącz Udostępnianie wydarzeń</string>
<string name="agenda_config_elearning">Nauczanie zdalne</string>
<string name="agenda_config_elearning_mark">Ustaw wydarzenia jako lekcje on-line</string>
<string name="agenda_config_elearning_type">Wybierz rodzaj wydarzeń</string>
<string name="agenda_config_elearning_group">Grupuj lekcje on-line na liście</string>
<string name="legend_event_added_manually">{cmd-clipboard-edit-outline} wydarzenie dodane ręcznie</string>
<string name="legend_event_is_done">{cmd-check} oznaczono jako wykonane</string>
<string name="agenda_config_not_available_yet">Funkcja jeszcze nie jest dostępna.</string>
<string name="messages_config_compose">Tworzenie wiadomości</string> <string name="messages_config_compose">Tworzenie wiadomości</string>
<string name="messages_config_greeting_on_compose">Dodaj podpis przy tworzeniu wiadomości</string> <string name="messages_config_greeting_on_compose">Dodaj podpis przy tworzeniu wiadomości</string>
<string name="messages_config_greeting_on_reply">Dodaj podpis przy odpowiadaniu na wiadomość</string> <string name="messages_config_greeting_on_reply">Dodaj podpis przy odpowiadaniu na wiadomość</string>