[Events/Manual] Create custom views for dropdowns. Simplify dialog code. Fix wrong start time saving.

This commit is contained in:
Kuba Szczodrzyński 2020-03-07 20:03:47 +01:00
parent 70c307b796
commit 611ab0f100
10 changed files with 953 additions and 334 deletions

View File

@ -12,8 +12,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL
import androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jaredrummler.android.colorpicker.ColorPickerDialog
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
@ -21,15 +19,17 @@ import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
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.Time
import pl.szczodrzynski.edziennik.utils.models.Week
import kotlin.coroutines.CoroutineContext
class EventManualDialog(
@ -56,7 +56,6 @@ class EventManualDialog(
private lateinit var b: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog
private lateinit var event: Event
private var customColor: Int? = null
private val editingShared = editingEvent?.sharedBy != null
private val editingOwn = editingEvent?.sharedBy == "self"
@ -104,12 +103,6 @@ class EventManualDialog(
show()
}
event = editingEvent?.clone() ?: Event().also { event ->
event.profileId = profileId
defaultType?.let {
event.type = it
}
}
b.shareSwitch.isChecked = editingShared
b.shareSwitch.isEnabled = !editingShared || (editingShared && editingOwn)
@ -144,41 +137,85 @@ class EventManualDialog(
else -> R.string.dialog_event_manual_share_first_notice
}
b.shareDetails.setText(text, event.sharedByName ?: "")
b.shareDetails.setText(text, editingEvent?.sharedByName ?: "")
}
private fun loadLists() { launch {
with (b.dateDropdown) {
db = app.db
profileId = App.profileId
showWeekDays = false
showDays = true
showOtherDate = true
defaultLesson?.let {
nextLessonSubjectId = it.displaySubjectId
nextLessonSubjectName = it.displaySubjectName
nextLessonTeamId = it.displayTeamId
}
loadItems()
selectDefault(editingEvent?.eventDate)
selectDefault(defaultLesson?.displayDate ?: defaultDate)
onDateSelected = { date, lesson ->
b.timeDropdown.deselect()
b.timeDropdown.lessonsDate = date
this@EventManualDialog.launch {
b.timeDropdown.loadItems()
lesson?.displayStartTime?.let { b.timeDropdown.selectTime(it) }
lesson?.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect()
lesson?.displayTeacherId?.let { b.teacherDropdown.selectTeacher(it) } ?: b.teacherDropdown.deselect()
lesson?.displayTeamId?.let { b.teamDropdown.selectTeam(it) } ?: b.teamDropdown.selectTeamClass()
}
}
}
with (b.timeDropdown) {
db = app.db
profileId = App.profileId
showAllDay = true
showCustomTime = true
lessonsDate = b.dateDropdown.getSelected() as? Date ?: Date.getToday()
displayMode = DISPLAY_LESSONS
loadItems()
selectDefault(editingEvent?.startTime)
selectDefault(defaultLesson?.displayStartTime ?: defaultTime)
onLessonSelected = { lesson ->
lesson.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect()
lesson.displayTeacherId?.let { b.teacherDropdown.selectTeacher(it) } ?: b.teacherDropdown.deselect()
lesson.displayTeamId?.let { b.teamDropdown.selectTeam(it) } ?: b.teamDropdown.selectTeamClass()
}
}
with (b.teamDropdown) {
db = app.db
profileId = App.profileId
showNoTeam = true
loadItems()
selectTeamClass()
selectDefault(editingEvent?.teamId)
selectDefault(defaultLesson?.displayTeamId)
}
with (b.subjectDropdown) {
db = app.db
profileId = App.profileId
showNoSubject = true
showCustomSubject = false
loadItems()
selectDefault(editingEvent?.subjectId)
selectDefault(defaultLesson?.displaySubjectId)
}
with (b.teacherDropdown) {
db = app.db
profileId = App.profileId
showNoTeacher = true
loadItems()
selectDefault(editingEvent?.teacherId)
selectDefault(defaultLesson?.displayTeacherId)
}
val deferred = async(Dispatchers.Default) {
// get the team list
val teams = app.db.teamDao().getAllNow(profileId)
b.teamDropdown.clear()
b.teamDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_team),
""
)
b.teamDropdown += teams.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
// get the subject list
val subjects = app.db.subjectDao().getAllNow(profileId)
b.subjectDropdown.clear()
b.subjectDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_subject),
""
)
b.subjectDropdown += subjects.map { TextInputDropDown.Item(it.id, it.longName, tag = it) }
// get the teacher list
val teachers = app.db.teacherDao().getAllNow(profileId)
b.teacherDropdown.clear()
b.teacherDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_teacher),
""
)
b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) }
// get the event type list
val eventTypes = app.db.eventTypeDao().getAllNow(profileId)
b.typeDropdown.clear()
@ -186,13 +223,10 @@ class EventManualDialog(
}
deferred.await()
b.teamDropdown.isEnabled = true
b.subjectDropdown.isEnabled = true
b.teacherDropdown.isEnabled = true
b.typeDropdown.isEnabled = true
defaultType?.let {
b.typeDropdown.select(it.toLong())
b.typeDropdown.select(it)
}
b.typeDropdown.selected?.let { item ->
@ -201,9 +235,6 @@ class EventManualDialog(
// copy IDs from event being edited
editingEvent?.let {
b.teamDropdown.select(it.teamId)
b.subjectDropdown.select(it.subjectId)
b.teacherDropdown.select(it.teacherId)
b.topic.setText(it.topic)
b.typeDropdown.select(it.type.toLong())?.let { item ->
customColor = (item.tag as EventType).color
@ -215,8 +246,6 @@ class EventManualDialog(
// copy IDs from the LessonFull
defaultLesson?.let {
b.teamDropdown.select(it.displayTeamId)
b.subjectDropdown.select(it.displaySubjectId)
b.teacherDropdown.select(it.displayTeacherId)
}
b.typeDropdown.setOnChangeListener {
@ -244,278 +273,8 @@ class EventManualDialog(
})
colorPickerDialog.show(activity.fragmentManager, "color-picker-dialog")
}
loadDates()
}}
private fun loadDates() { launch {
val date = Date.getToday()
val today = date.value
var weekDay = date.weekDay
val deferred = async(Dispatchers.Default) {
val dates = mutableListOf<TextInputDropDown.Item>()
// item choosing the next lesson of specific subject
b.subjectDropdown.selected?.let {
if (it.tag is Subject) {
dates += TextInputDropDown.Item(
-it.id,
activity.getString(R.string.dialog_event_manual_date_next_lesson, it.tag.longName)
)
}
}
// TODAY
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_today, date.formattedString),
tag = date.clone()
)
// TOMORROW
if (weekDay < 4) {
date.stepForward(0, 0, 1)
weekDay++
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_tomorrow, date.formattedString),
tag = date.clone()
)
}
// REMAINING SCHOOL DAYS OF THE CURRENT WEEK
while (weekDay < 4) {
date.stepForward(0, 0, 1) // step one day forward
weekDay++
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_this_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
}
// go to next week Monday
date.stepForward(0, 0, -weekDay + 7)
weekDay = 0
// ALL SCHOOL DAYS OF THE NEXT WEEK
while (weekDay < 4) {
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_next_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
date.stepForward(0, 0, 1) // step one day forward
weekDay++
}
dates += TextInputDropDown.Item(
-1L,
activity.getString(R.string.dialog_event_manual_date_other)
)
dates
}
val dates = deferred.await()
b.dateDropdown.clear().append(dates)
defaultDate?.let {
event.eventDate = it
if (b.dateDropdown.select(it) == null)
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
}
editingEvent?.eventDate?.let {
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
}
defaultLesson?.displayDate?.let {
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
}
if (b.dateDropdown.selected == null) {
b.dateDropdown.select(today.toLong())
}
b.dateDropdown.isEnabled = true
b.dateDropdown.setOnChangeListener { item ->
when {
// next lesson with specified subject
item.id < -1 -> {
val teamId = defaultLesson?.teamId ?: -1
val selectedLessonDate = defaultLesson?.date ?: Date.getToday()
when (teamId) {
-1L -> app.db.timetableDao().getNextWithSubject(profileId, selectedLessonDate, -item.id)
else -> app.db.timetableDao().getNextWithSubjectAndTeam(profileId, selectedLessonDate, -item.id, teamId)
}.observeOnce(activity, Observer {
val lessonDate = it?.displayDate ?: return@Observer
b.dateDropdown.select(TextInputDropDown.Item(
lessonDate.value.toLong(),
lessonDate.formattedString,
tag = lessonDate
))
b.teamDropdown.select(it.displayTeamId)
b.subjectDropdown.select(it.displaySubjectId)
b.teacherDropdown.select(it.displayTeacherId)
defaultLoaded = false
loadHours(it.displayStartTime)
})
return@setOnChangeListener false
}
// custom date
item.id == -1L -> {
MaterialDatePicker.Builder
.datePicker()
.setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) }
?: Date.getToday()).inMillis)
.build()
.apply {
addOnPositiveButtonClickListener {
val dateSelected = Date.fromMillis(it)
b.dateDropdown.select(TextInputDropDown.Item(
dateSelected.value.toLong(),
dateSelected.formattedString,
tag = dateSelected
))
loadHours()
}
show(this@EventManualDialog.activity.supportFragmentManager, "MaterialDatePicker")
}
return@setOnChangeListener false
}
// a specific date
else -> {
b.dateDropdown.select(item)
loadHours()
}
}
return@setOnChangeListener true
}
loadHours()
}}
private fun loadHours(defaultHour: Time? = null) {
b.timeDropdown.isEnabled = false
// get the selected date
val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return
// get all lessons for selected date
app.db.timetableDao().getForDate(profileId, date).observeOnce(activity, Observer { lessons ->
val hours = mutableListOf<TextInputDropDown.Item>()
// add All day time choice
hours += TextInputDropDown.Item(
0L,
activity.getString(R.string.dialog_event_manual_all_day)
)
lessons.forEach { lesson ->
if (lesson.type == Lesson.TYPE_NO_LESSONS) {
// indicate there are no lessons this day
hours += TextInputDropDown.Item(
-2L,
activity.getString(R.string.dialog_event_manual_no_lessons)
)
return@forEach
}
// create the lesson caption
val text = listOfNotEmpty(
lesson.displayStartTime?.stringHM ?: "",
lesson.displaySubjectName?.let {
when {
lesson.type == Lesson.TYPE_CANCELLED
|| lesson.type == Lesson.TYPE_SHIFTED_SOURCE -> it.asStrikethroughSpannable()
lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable()
else -> it
}
} ?: ""
)
// add an item with LessonFull as the tag
hours += TextInputDropDown.Item(
lesson.displayStartTime?.value?.toLong() ?: -1,
text.concat(" "),
tag = lesson
)
}
b.timeDropdown.clear().append(hours)
if (defaultLoaded) {
b.timeDropdown.deselect()
// select the TEAM_CLASS if possible
b.teamDropdown.items.singleOrNull {
it.tag is Team && it.tag.type == Team.TYPE_CLASS
}?.let {
b.teamDropdown.select(it)
} ?: b.teamDropdown.deselect()
// clear subject, teacher selection
b.subjectDropdown.deselect()
b.teacherDropdown.deselect()
}
else {
val setTime: (Time) -> Unit = {
event.startTime = it
if (b.timeDropdown.select(it) == null)
b.timeDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.stringHM,
tag = it
))
}
defaultTime?.let(setTime)
editingEvent?.startTime?.let(setTime)
defaultLesson?.displayStartTime?.let(setTime)
defaultHour?.let(setTime)
}
defaultLoaded = true
b.timeDropdown.isEnabled = true
// attach a listener to time dropdown
b.timeDropdown.setOnChangeListener { item ->
when (item.id) {
// no lessons this day
-2L -> {
b.timeDropdown.deselect()
return@setOnChangeListener false
}
// custom start hour
-1L -> return@setOnChangeListener false
// selected a specific lesson
else -> {
if (item.tag is LessonFull) {
// update team, subject, teacher dropdowns,
// using the LessonFull from item tag
b.teamDropdown.deselect()
b.subjectDropdown.deselect()
b.teacherDropdown.deselect()
item.tag.displayTeamId?.let {
b.teamDropdown.select(it)
}
item.tag.displaySubjectId?.let {
b.subjectDropdown.select(it)
}
item.tag.displayTeacherId?.let {
b.teacherDropdown.select(it)
}
}
}
}
return@setOnChangeListener true
}
})
}
private fun showRemoveEventDialog() {
val shareNotice = when {
editingShared && editingOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared_self)
@ -541,22 +300,29 @@ class EventManualDialog(
}
private fun saveEvent() {
val date = b.dateDropdown.selected?.tag.instanceOfOrNull<Date>()
val startTime = b.timeDropdown.selected?.tag.instanceOfOrNull<Time>()
val teamId = b.teamDropdown.selected?.id
val date = b.dateDropdown.getSelected() as? Date
val startTimePair = b.timeDropdown.getSelected() as? Pair<*, *>
val startTime = startTimePair?.first as? Time
val teamId = b.teamDropdown.getSelected() as? Long
val type = b.typeDropdown.selected?.id
val topic = b.topic.text?.toString()
val subjectId = b.subjectDropdown.selected?.id
val teacherId = b.teacherDropdown.selected?.id
val subjectId = b.subjectDropdown.getSelected() as? Long
val teacherId = b.teacherDropdown.getSelected() as? Long
val share = b.shareSwitch.isChecked
b.dateDropdown.error = null
b.teamDropdown.error = null
b.typeDropdown.error = null
b.topic.error = null
var isError = false
if (date == null) {
b.dateDropdown.error = app.getString(R.string.dialog_event_manual_date_choose)
isError = true
}
if (share && teamId == null) {
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
isError = true

View File

@ -0,0 +1,240 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import com.google.android.material.datepicker.MaterialDatePicker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.observeOnce
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
class DateDropdown : 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)
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
var profileId: Int = 0
var showWeekDays = false
var showDays = true
var showOtherDate = true
var nextLessonSubjectId: Long? = null
var nextLessonSubjectName: String? = null
var nextLessonTeamId: Long? = null
var onDateSelected: ((date: Date, lesson: LessonFull?) -> Unit)? = null
var onWeekDaySelected: ((weekDay: Int) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val date = Date.getToday()
val today = date.value
var weekDay = date.weekDay
val dates = withContext(Dispatchers.Default) {
val dates = mutableListOf<Item>()
nextLessonSubjectId?.let {
// item choosing the next lesson of specific subject - relative to selected date
dates += Item(
-it,
context.getString(R.string.dialog_event_manual_date_next_lesson, nextLessonSubjectName),
tag = nextLessonSubjectName
)
}
if (showWeekDays) {
for (i in Week.MONDAY..Week.SUNDAY) {
dates += Item(
i.toLong(),
Week.getFullDayName(i),
tag = i
)
}
}
if (showDays) {
// TODAY
dates += Item(
date.value.toLong(),
context.getString(R.string.dialog_event_manual_date_today, date.formattedString),
tag = date.clone()
)
// TOMORROW
if (weekDay < 4) {
date.stepForward(0, 0, 1)
weekDay++
dates += Item(
date.value.toLong(),
context.getString(R.string.dialog_event_manual_date_tomorrow, date.formattedString),
tag = date.clone()
)
}
// REMAINING SCHOOL DAYS OF THE CURRENT WEEK
while (weekDay < 4) {
date.stepForward(0, 0, 1) // step one day forward
weekDay++
dates += Item(
date.value.toLong(),
context.getString(R.string.dialog_event_manual_date_this_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
}
// go to next week Monday
date.stepForward(0, 0, -weekDay + 7)
weekDay = 0
// ALL SCHOOL DAYS OF THE NEXT WEEK
while (weekDay < 4) {
dates += Item(
date.value.toLong(),
context.getString(R.string.dialog_event_manual_date_next_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
date.stepForward(0, 0, 1) // step one day forward
weekDay++
}
}
if (showOtherDate) {
dates += Item(
-1L,
context.getString(R.string.dialog_event_manual_date_other),
tag = -1L
)
}
dates
}
clear().append(dates)
isEnabled = true
setOnChangeListener { item ->
when (item.tag) {
-1L -> {
pickerDialog()
false
}
is Date -> {
onDateSelected?.invoke(item.tag, null)
true
}
is Int -> {
onWeekDaySelected?.invoke(item.tag)
true
}
is String -> {
/* next lesson of subject */
activity ?: return@setOnChangeListener false
val subjectId = -item.id
val startDate = getSelected() as? Date ?: Date.getToday()
when (nextLessonTeamId) {
null -> db.timetableDao().getNextWithSubject(profileId, startDate, subjectId)
else -> db.timetableDao().getNextWithSubjectAndTeam(profileId, startDate, subjectId, nextLessonTeamId ?: -1)
}.observeOnce(activity!!, Observer {
if (it == null) {
Toast.makeText(context, R.string.dropdown_date_no_more_lessons, Toast.LENGTH_LONG).show()
return@Observer
}
val lessonDate = it.displayDate ?: return@Observer
selectDate(lessonDate)
onDateSelected?.invoke(lessonDate, it)
})
false
}
else -> false
}
}
}
fun pickerDialog() {
MaterialDatePicker.Builder
.datePicker()
.setSelection(
if (selected?.tag is Date)
(selected?.tag as Date).inMillis
else
Date.getToday().inMillis
)
.build()
.apply {
addOnPositiveButtonClickListener {
val dateSelected = Date.fromMillis(it)
selectDate(dateSelected)
}
this@DateDropdown.activity ?: return@apply
show(this@DateDropdown.activity!!.supportFragmentManager, "MaterialDatePicker")
}
}
fun selectDate(date: Date) {
if (select(date) == null)
select(Item(
date.value.toLong(),
date.formattedString,
tag = date
))
}
fun selectWeekDay(weekDay: Int) {
if (select(tag = weekDay) == null)
select(Item(
weekDay.toLong(),
Week.getFullDayName(weekDay),
tag = weekDay
))
}
fun selectDefault(date: Date?) {
if (date == null || selected != null)
return
selectDate(date)
}
fun selectDefault(weekDay: Int?) {
if (weekDay == null || selected != null)
return
selectWeekDay(weekDay)
}
/**
* Get the currently selected date.
* ### Returns:
* - null if no valid date is selected
* - [Date] - the selected date, if [showDays] or [showOtherDate] == true
* - [Int] - the selected week day, if [showWeekDays] == true
*/
fun getSelected(): Any? {
return when (val tag = selected?.tag) {
is Date -> tag
is Int -> tag
else -> null
}
}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.content.ContextWrapper
import android.text.InputType
import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import com.afollestad.materialdialogs.MaterialDialog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.crc16
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
class SubjectDropdown : 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)
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
var profileId: Int = 0
var showNoSubject = true
var showCustomSubject = false
var customSubjectName = ""
var onSubjectSelected: ((subjectId: Long?) -> Unit)? = null
var onCustomSubjectSelected: ((subjectName: String) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val subjects = withContext(Dispatchers.Default) {
val list = mutableListOf<Item>()
if (showNoSubject) {
list += Item(
-1L,
context.getString(R.string.dialog_event_manual_no_subject),
tag = -1L
)
}
if (showCustomSubject) {
list += Item(
-2L,
context.getString(R.string.dropdown_subject_custom),
tag = -2L
)
}
val subjects = db.subjectDao().getAllNow(profileId)
list += subjects.map { Item(
it.id,
it.longName,
tag = it.id
) }
list
}
clear().append(subjects)
isEnabled = true
setOnChangeListener {
when (it.tag) {
-2L -> {
// custom subject
customNameDialog()
false
}
-1L -> {
// no subject
onSubjectSelected?.invoke(null)
true
}
is Long -> {
// selected a subject
onSubjectSelected?.invoke(it.tag)
true
}
else -> false
}
}
}
fun customNameDialog() {
activity ?: return
MaterialDialog.Builder(activity!!)
.title("Własny przedmiot")
.inputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)
.input("Nazwa", "") { _: MaterialDialog?, input: CharSequence ->
customSubjectName = input.toString()
select(Item(
-1L * customSubjectName.crc16(),
customSubjectName,
tag = customSubjectName
))
onCustomSubjectSelected?.invoke(customSubjectName)
}
.show()
}
fun selectSubject(subjectId: Long) {
if (select(subjectId) == null)
select(Item(
subjectId,
"nieznany przedmiot ($subjectId)",
tag = subjectId
))
}
fun selectDefault(subjectId: Long?) {
if (subjectId == null || selected != null)
return
selectSubject(subjectId)
}
/**
* Get the currently selected subject.
* ### Returns:
* - null if no valid subject is selected
* - [Long] - the selected subject's ID
* - [String] - a custom subject name entered, if [showCustomSubject] == true
*/
fun getSelected(): Any? {
return when (selected?.tag) {
-1L -> null
is Long -> selected?.tag as Long
is String -> selected?.tag as String
else -> null
}
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
class TeacherDropdown : 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)
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
var profileId: Int = 0
var showNoTeacher = true
var onTeacherSelected: ((teacherId: Long?) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val teachers = withContext(Dispatchers.Default) {
val list = mutableListOf<Item>()
if (showNoTeacher) {
list += Item(
-1L,
context.getString(R.string.dialog_event_manual_no_teacher),
tag = -1L
)
}
val teachers = db.teacherDao().getAllNow(profileId)
list += teachers.map { Item(
it.id,
it.fullName,
tag = it.id
) }
list
}
clear().append(teachers)
isEnabled = true
setOnChangeListener {
when (it.tag) {
-1L -> {
// no teacher
onTeacherSelected?.invoke(null)
true
}
is Long -> {
// selected a teacher
onTeacherSelected?.invoke(it.tag)
true
}
else -> false
}
}
}
fun selectTeacher(teacherId: Long) {
if (select(teacherId) == null)
select(Item(
teacherId,
"nieznany nauczyciel ($teacherId)",
tag = teacherId
))
}
fun selectDefault(teacherId: Long?) {
if (teacherId == null || selected != null)
return
selectTeacher(teacherId)
}
/**
* Get the currently selected teacher.
* ### Returns:
* - null if no valid teacher is selected
* - [Long] - the selected teacher's ID
*/
fun getSelected(): Long? {
return when (selected?.tag) {
-1L -> null
is Long -> selected?.tag as Long
else -> null
}
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-7.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
class TeamDropdown : 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)
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
var profileId: Int = 0
var showNoTeam = true
var onTeamSelected: ((teamId: Long?) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val teams = withContext(Dispatchers.Default) {
val list = mutableListOf<Item>()
if (showNoTeam) {
list += Item(
-1L,
context.getString(R.string.dialog_event_manual_no_team),
tag = -1L
)
}
val teams = db.teamDao().getAllNow(profileId)
list += teams.map { Item(
it.id,
it.name,
tag = it.id
) }
list
}
clear().append(teams)
isEnabled = true
setOnChangeListener {
when (it.tag) {
-1L -> {
// no team
onTeamSelected?.invoke(null)
true
}
is Long -> {
// selected a team
onTeamSelected?.invoke(it.tag)
true
}
else -> false
}
}
}
fun selectTeam(teamId: Long) {
if (select(teamId) == null)
select(Item(
teamId,
"nieznana grupa ($teamId)",
tag = teamId
))
}
fun selectDefault(teamId: Long?) {
if (teamId == null || selected != null)
return
selectTeam(teamId)
}
fun selectTeamClass() {
select(items.singleOrNull {
it.tag is Team && it.tag.type == Team.TYPE_CLASS
})
}
/**
* Get the currently selected team.
* ### Returns:
* - null if no valid team is selected
* - [Long] - the team's ID
*/
fun getSelected(): Any? {
return when (selected?.tag) {
-1L -> null
is Long -> selected?.tag as Long
else -> null
}
}
}

View File

@ -0,0 +1,214 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.entity.LessonRange
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class TimeDropdown : TextInputDropDown {
companion object {
const val DISPLAY_LESSON_RANGES = 0
const val DISPLAY_LESSONS = 1
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
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
var profileId: Int = 0
var showAllDay = false
var showCustomTime = true
var displayMode = DISPLAY_LESSON_RANGES
var lessonsDate: Date? = null
var onTimeSelected: ((startTime: Time?, endTime: Time?, lessonNumber: Int?) -> Unit)? = null
var onLessonSelected: ((lesson: LessonFull) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val hours = withContext(Dispatchers.Default) {
val hours = mutableListOf<Item>()
if (showAllDay) {
hours += Item(
0L,
context.getString(R.string.dialog_event_manual_all_day),
tag = 0L
)
}
if (showCustomTime) {
hours += Item(
-1,
context.getString(R.string.dialog_event_manual_custom_time),
tag = -1L
)
}
if (displayMode == DISPLAY_LESSON_RANGES) {
val lessonRanges = db.lessonRangeDao().getAllNow(profileId)
hours += lessonRanges.map { Item(
it.startTime.value.toLong(),
context.getString(R.string.timetable_manual_dialog_time_format, it.startTime.stringHM, it.lessonNumber),
tag = it
) }
}
else if (displayMode == DISPLAY_LESSONS && lessonsDate != null) {
val lessons = db.timetableDao().getForDateNow(profileId, lessonsDate!!)
hours += lessons.map { lesson ->
if (lesson.type == Lesson.TYPE_NO_LESSONS) {
// indicate there are no lessons this day
return@map Item(
-2L,
context.getString(R.string.dialog_event_manual_no_lessons),
tag = -2L
)
}
// create the lesson caption
val text = listOfNotEmpty(
lesson.displayStartTime?.stringHM ?: "",
if (lesson.displaySubjectName != null) "-" else "",
lesson.displaySubjectName?.let {
when {
lesson.type == Lesson.TYPE_CANCELLED
|| lesson.type == Lesson.TYPE_SHIFTED_SOURCE -> it.asStrikethroughSpannable()
lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable()
else -> it
}
} ?: ""
)
// add an item with LessonFull as the tag
return@map Item(
lesson.displayStartTime?.value?.toLong() ?: -1,
text.concat(" "),
tag = lesson
)
}
}
hours
}
clear().append(hours)
isEnabled = true
setOnChangeListener {
when (it.tag) {
-2L -> {
// no lessons this day
deselect()
false
}
-1L -> {
// custom start hour
pickerDialog()
false
}
0L -> {
// selected all day
onTimeSelected?.invoke(null, null, null)
true
}
is LessonFull -> {
// selected a specific lesson
onLessonSelected?.invoke(it.tag)
true
}
is LessonRange -> {
// selected a lesson range
onTimeSelected?.invoke(it.tag.startTime, it.tag.endTime, it.tag.lessonNumber)
true
}
is Time -> {
// selected a time
onTimeSelected?.invoke(it.tag, null, null)
true
}
else -> false
}
}
}
fun pickerDialog() {
/*MaterialDatePicker.Builder
.datePicker()
.setSelection((selectedId?.let { Date.fromValue(it.toInt()) }
?: Date.getToday()).inMillis)
.build()
.apply {
addOnPositiveButtonClickListener {
val dateSelected = Date.fromMillis(it)
selectDate(dateSelected)
}
this@DateDropdown.activity ?: return@apply
show(this@DateDropdown.activity!!.supportFragmentManager, "MaterialDatePicker")
}*/
}
fun selectTime(time: Time) {
if (select(time.value.toLong()) == null)
select(Item(
time.value.toLong(),
time.stringHM,
tag = time
))
}
fun selectDefault(time: Time?) {
if (time == null || selected != null)
return
selectTime(time)
}
/**
* Get the currently selected time.
* ### Returns:
* - null if no valid time is selected
* - a [Pair] of [Time] and [Time]? - the selected time object, if [displayMode] == [DISPLAY_LESSONS] or [showCustomTime]
* - [LessonRange] - the selected lesson range object, if [displayMode] == [DISPLAY_LESSON_RANGES]
*/
fun getSelected(): Any? {
return when (val tag = selected?.tag) {
0L -> null
is LessonFull ->
if (tag.displayStartTime != null)
tag.displayStartTime!! to tag.displayEndTime
else
null
is LessonRange -> tag
is Time -> tag to null
else -> null
}
}
}

View File

@ -7,7 +7,7 @@ import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.material.textfield.TextInputEditText
import pl.szczodrzynski.edziennik.R
class TextInputDropDown : TextInputEditText {
open class TextInputDropDown : TextInputEditText {
constructor(context: Context) : super(context) {
create(context)
}
@ -31,7 +31,7 @@ class TextInputDropDown : TextInputEditText {
setText(selected?.displayText ?: selected?.text)
}
fun create(context: Context) {
open fun create(context: Context) {
val drawable = context.resources.getDrawable(R.drawable.dropdown_arrow)
val wrappedDrawable = DrawableCompat.wrap(drawable)
DrawableCompat.setTint(wrappedDrawable, Themes.getPrimaryTextColor(context))

View File

@ -24,7 +24,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_event_manual_date">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
<pl.szczodrzynski.edziennik.ui.modules.views.DateDropdown
android:id="@+id/dateDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_time">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
<pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown
android:id="@+id/timeDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -53,7 +53,7 @@
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_team">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
<pl.szczodrzynski.edziennik.ui.modules.views.TeamDropdown
android:id="@+id/teamDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -158,7 +158,7 @@
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_subject">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
<pl.szczodrzynski.edziennik.ui.modules.views.SubjectDropdown
android:id="@+id/subjectDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -173,7 +173,7 @@
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_teacher">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
<pl.szczodrzynski.edziennik.ui.modules.views.TeacherDropdown
android:id="@+id/teacherDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -294,6 +294,12 @@
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="@drawable/divider"/>
<LinearLayout
android:id="@+id/eventsNoData"
android:layout_width="match_parent"
@ -331,7 +337,6 @@
android:id="@+id/eventsView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clipToPadding="false"
tools:visibility="visible"
tools:listitem="@layout/event_list_item" />

View File

@ -1233,4 +1233,8 @@
<string name="grades_stats_help_text">Ocena przewidywana z danego przedmiotu jest obliczana na podstawie aktualnej średniej ważonej.\n\nOcena jest liczbą całkowitą, jaką wystawił by nauczyciel bazując na średniej. Liczba zaokrąglona jest w górę jeśli część po przecinku przekroczy ,75.\nPrzykładowo: średnie 3,75 jak również 4,74 dają w wyniku ocenę dobrą (4).\n\nŚrednia przewidywana ze wszystkich przedmiotów obejmuje obliczone w ten sposób oceny końcowe.</string>
<string name="grades_stats_custom_value_notice">Została ustawiona własna wartość plusa/minusa. Jeśli uważasz, że średnia się nie zgadza, kliknij Konfiguruj.</string>
<string name="configure">Konfiguruj</string>
<string name="menu_timetable_manual">Edytor planu lekcji</string>
<string name="dropdown_subject_custom">Własny przedmiot</string>
<string name="dialog_event_manual_date_choose">Wybierz datę</string>
<string name="dropdown_date_no_more_lessons">Nie ma więcej lekcji tego przedmiotu. Pobierz plan lekcji i spróbuj ponownie.</string>
</resources>