diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt index 10040fde..d323a796 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt @@ -4,11 +4,13 @@ import android.Manifest import android.app.Activity import android.content.Context import android.content.pm.PackageManager +import android.graphics.Typeface import android.os.Build import android.os.Bundle import android.text.* import android.text.style.ForegroundColorSpan import android.text.style.StrikethroughSpan +import android.text.style.StyleSpan import android.util.LongSparseArray import android.util.SparseArray import android.view.View @@ -16,6 +18,9 @@ import android.widget.TextView import androidx.annotation.StringRes import androidx.core.app.ActivityCompat import androidx.core.util.forEach +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject @@ -344,6 +349,11 @@ fun CharSequence?.asStrikethroughSpannable(): Spannable { spannable.setSpan(StrikethroughSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) return spannable } +fun CharSequence?.asItalicSpannable(): Spannable { + val spannable = SpannableString(this) + spannable.setSpan(StyleSpan(Typeface.ITALIC), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + return spannable +} /** * Returns a new read-only list only of those given elements, that are not empty. @@ -416,4 +426,13 @@ inline fun T.onClick(crossinline onClickListener: (v: T) -> Unit) { setOnClickListener { v: View -> onClickListener(v as T) } +} + +fun LiveData.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer) { + observe(lifecycleOwner, object : Observer { + override fun onChanged(t: T?) { + observer.onChanged(t) + removeObserver(this) + } + }) } \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/events/Event.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/events/Event.java index b572ab48..549ece88 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/events/Event.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/events/Event.java @@ -5,6 +5,7 @@ import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.Ignore; import androidx.room.Index; + import pl.szczodrzynski.edziennik.utils.models.Date; import pl.szczodrzynski.edziennik.utils.models.Time; @@ -85,6 +86,23 @@ public class Event { this.teamId = teamId; } + @Override + public Event clone() throws CloneNotSupportedException { + return new Event( + profileId, + id, + eventDate.clone(), + startTime == null ? null : startTime.clone(), + topic, + color, + type, + addedManually, + subjectId, + teacherId, + teamId + ); + } + @Override public String toString() { return "Event{" + diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/LessonFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/LessonFull.kt index 2586ea4d..49ea2c82 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/LessonFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/LessonFull.kt @@ -62,6 +62,25 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) { return classroom ?: oldClassroom } + val displayTeamId: Long? + get() { + if (type == TYPE_SHIFTED_SOURCE) + return oldTeamId + return teamId ?: oldTeamId + } + val displaySubjectId: Long? + get() { + if (type == TYPE_SHIFTED_SOURCE) + return oldSubjectId + return subjectId ?: oldSubjectId + } + val displayTeacherId: Long? + get() { + if (type == TYPE_SHIFTED_SOURCE) + return oldTeacherId + return teacherId ?: oldTeacherId + } + // metadata var seen: Boolean = false var notified: Boolean = false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/TimetableDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/TimetableDao.kt index f5beeaf3..fc566ac5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/TimetableDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/TimetableDao.kt @@ -43,7 +43,31 @@ interface TimetableDao { LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId WHERE timetable.profileId = :profileId AND (type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date) - ORDER BY type + ORDER BY id, type """) fun getForDate(profileId: Int, date: Date) : LiveData> + + @Query(""" + SELECT + timetable.*, + subjects.subjectLongName AS subjectName, + teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName, + teams.teamName AS teamName, + oldS.subjectLongName AS oldSubjectName, + oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName, + oldG.teamName AS oldTeamName, + metadata.seen, metadata.notified, metadata.addedDate + FROM timetable + LEFT JOIN subjects USING(profileId, subjectId) + LEFT JOIN teachers USING(profileId, teacherId) + LEFT JOIN teams USING(profileId, teamId) + LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId + LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId + LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId + LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId + WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId + ORDER BY id, type + LIMIT 1 + """) + fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long) : LiveData } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventAddTypeDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventAddTypeDialog.kt new file mode 100644 index 00000000..cc3a6d63 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventAddTypeDialog.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-12. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.event + +import android.app.Activity +import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class EventAddTypeDialog( + val activity: Activity, + val profileId: Int, + val date: Date? = null, + val time: Time? = null +) { + companion object { + private const val TAG = "EventAddTypeDialog" + } + + private lateinit var dialog: AlertDialog + + init { run { + dialog = MaterialAlertDialogBuilder(activity) + .setItems(R.array.main_menu_add_options) { dialog, which -> + dialog.dismiss() + EventManualDialog(activity, profileId) + .show( + activity.application as App, + null, + date, + time, + when (which) { + 1 -> EventManualDialog.DIALOG_HOMEWORK + else -> EventManualDialog.DIALOG_EVENT + } + ) + + } + .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } + .show() + }} +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualV2Dialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualV2Dialog.kt new file mode 100644 index 00000000..64644fb7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualV2Dialog.kt @@ -0,0 +1,379 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-12. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.event + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import com.google.android.material.datepicker.MaterialDatePicker +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.modules.events.Event +import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject +import pl.szczodrzynski.edziennik.data.db.modules.teams.Team +import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson +import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull +import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding +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 EventManualV2Dialog( + val activity: AppCompatActivity, + val profileId: Int, + val defaultLesson: LessonFull? = null, + val defaultDate: Date? = null, + val defaultTime: Time? = null, + val defaultType: Int? = null, + val editingEvent: Event? = null +) : CoroutineScope { + + companion object { + private const val TAG = "EventManualDialog" + } + + private lateinit var job: Job + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private val app by lazy { activity.application as App } + private lateinit var b: DialogEventManualV2Binding + private lateinit var dialog: AlertDialog + private lateinit var event: Event + private var defaultLoaded = false + + init { run { + job = Job() + + b = DialogEventManualV2Binding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.dialog_event_manual_title) + .setView(b.root) + .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } + .setPositiveButton(R.string.save) { _, _ -> saveEvent() } + .show() + + event = editingEvent?.clone() ?: Event().also { event -> + event.profileId = profileId + /*defaultDate?.let { + event.eventDate = it + b.date = it + } + defaultTime?.let { + event.startTime = it + b.time = it + } + defaultType?.let { + event.type = it + }*/ + } + + loadLists() + }} + + private fun loadLists() { launch { + 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) } + } + deferred.await() + + b.teamDropdown.isEnabled = true + b.subjectDropdown.isEnabled = true + b.teacherDropdown.isEnabled = true + + // copy IDs from event being edited + editingEvent?.let { + b.teamDropdown.select(it.teamId) + b.subjectDropdown.select(it.subjectId) + b.teacherDropdown.select(it.teacherId) + } + + // copy IDs from the LessonFull + defaultLesson?.let { + b.teamDropdown.select(it.displayTeamId) + b.subjectDropdown.select(it.displaySubjectId) + b.teacherDropdown.select(it.displayTeacherId) + } + + 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() + // 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) + + editingEvent?.let { + b.dateDropdown.select(it.eventDate.value.toLong()) + } + + defaultLesson?.let { + b.dateDropdown.select(it.displayDate?.value?.toLong()) + } + + 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 -> { + app.db.timetableDao().getNextWithSubject(profileId, Date.getToday(), -item.id).observeOnce(activity, Observer { + val lessonDate = it?.displayDate ?: return@Observer + b.dateDropdown.selected = TextInputDropDown.Item( + lessonDate.value.toLong(), + lessonDate.formattedString, + tag = lessonDate + ) + // TODO load correct hour when selecting next lesson + b.dateDropdown.updateText() + it.let { + b.teamDropdown.select(it.displayTeamId) + b.subjectDropdown.select(it.displaySubjectId) + b.teacherDropdown.select(it.displayTeacherId) + } + defaultLoaded = false + loadHours() + }) + 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.selected = TextInputDropDown.Item( + dateSelected.value.toLong(), + dateSelected.formattedString, + tag = dateSelected + ) + b.dateDropdown.updateText() + loadHours() + } + show(this@EventManualV2Dialog.activity.supportFragmentManager, "MaterialDatePicker") + } + + return@setOnChangeListener false + } + // a specific date + else -> { + b.dateDropdown.select(item) + loadHours() + } + } + return@setOnChangeListener true + } + + loadHours() + }} + + private fun loadHours() { + 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() + // 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 -> 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 { + editingEvent?.let { + b.timeDropdown.select(it.startTime?.value?.toLong()) + } + + defaultLesson?.let { + b.timeDropdown.select(it.displayStartTime?.value?.toLong()) + } + } + defaultLoaded = true + b.timeDropdown.isEnabled = true + + // attach a listener to time dropdown + b.timeDropdown.setOnChangeListener { item -> + when { + // custom start hour + item.id == -1L -> { + + return@setOnChangeListener false + } + // no lessons this day + item.id == -2L -> { + b.timeDropdown.deselect() + 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 saveEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt index 06f924d0..325cf419 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt @@ -4,39 +4,43 @@ package pl.szczodrzynski.edziennik.ui.dialogs.timetable -import android.app.Activity import android.content.Intent import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder -import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding import pl.szczodrzynski.edziennik.setText -import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualV2Dialog import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Week class LessonDetailsDialog( - val activity: Activity, + val activity: AppCompatActivity, val lesson: LessonFull ) { companion object { private const val TAG = "LessonDetailsDialog" } + private lateinit var b: DialogLessonDetailsBinding + private lateinit var dialog: AlertDialog + init { run { - val b = DialogLessonDetailsBinding.inflate(activity.layoutInflater) - val dialog = MaterialAlertDialogBuilder(activity) + b = DialogLessonDetailsBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) .setView(b.root) .setPositiveButton(R.string.close) { dialog, _ -> dialog.dismiss() } .setNeutralButton(R.string.add) { dialog, _ -> dialog.dismiss() - MaterialAlertDialogBuilder(activity) + EventManualV2Dialog(activity, lesson.profileId, lesson) + /*MaterialAlertDialogBuilder(activity) .setItems(R.array.main_menu_add_options) { dialog2, which -> dialog2.dismiss() EventManualDialog(activity, lesson.profileId) @@ -53,11 +57,15 @@ class LessonDetailsDialog( } .setNegativeButton(R.string.cancel) { dialog2, _ -> dialog2.dismiss() } - .show() + .show()*/ } .show() + update() + }} + + private fun update() { b.lesson = lesson - val lessonDate = lesson.displayDate ?: return@run + val lessonDate = lesson.displayDate ?: return b.lessonDate.text = Week.getFullDayName(lessonDate.weekDay) + ", " + lessonDate.formattedString if (lesson.type >= Lesson.TYPE_SHIFTED_SOURCE) { @@ -135,5 +143,5 @@ class LessonDetailsDialog( if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teamId != null) { b.teamName = lesson.teamName } - }} + } } \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.java deleted file mode 100644 index d8eaa3e8..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.java +++ /dev/null @@ -1,55 +0,0 @@ -package pl.szczodrzynski.edziennik.utils; - -import android.content.Context; -import com.google.android.material.textfield.TextInputEditText; - -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; - -import androidx.core.graphics.drawable.DrawableCompat; - -import pl.szczodrzynski.edziennik.R; - -public class TextInputDropDown extends TextInputEditText { - public TextInputDropDown(Context context) { - super(context); - create(context); - } - - public TextInputDropDown(Context context, AttributeSet attrs) { - super(context, attrs); - create(context); - } - - public TextInputDropDown(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - create(context); - } - - public void create(Context context) { - Drawable drawable = context.getResources().getDrawable(R.drawable.dropdown_arrow); - Drawable wrappedDrawable = DrawableCompat.wrap(drawable); - DrawableCompat.setTint(wrappedDrawable, Themes.INSTANCE.getPrimaryTextColor(context)); - - setCompoundDrawablesWithIntrinsicBounds(null, null, wrappedDrawable, null); - setFocusableInTouchMode(false); - setCursorVisible(false); - setLongClickable(false); - setMaxLines(1); - setInputType(0); - setKeyListener(null); - setOnFocusChangeListener((v, hasFocus) -> { - if (!hasFocus) { - v.setFocusableInTouchMode(false); - } - }); - } - - public final void setOnClickListener(OnClickListener onClickListener) { - super.setOnClickListener(v -> { - setFocusableInTouchMode(true); - requestFocus(); - onClickListener.onClick(v); - }); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt new file mode 100644 index 00000000..5ace6f58 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt @@ -0,0 +1,142 @@ +package pl.szczodrzynski.edziennik.utils + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.PopupMenu +import androidx.core.graphics.drawable.DrawableCompat +import com.google.android.material.textfield.TextInputEditText +import pl.szczodrzynski.edziennik.R + +class TextInputDropDown : TextInputEditText { + constructor(context: Context) : super(context) { + create(context) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + create(context) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + create(context) + } + + var items = mutableListOf() + private var onChangeListener: ((item: Item) -> Boolean)? = null + + var selected: Item? = null + val selectedId + get() = selected?.id + + fun updateText() { + setText(selected?.displayText ?: selected?.text) + } + + fun create(context: Context) { + val drawable = context.resources.getDrawable(R.drawable.dropdown_arrow) + val wrappedDrawable = DrawableCompat.wrap(drawable) + DrawableCompat.setTint(wrappedDrawable, Themes.getPrimaryTextColor(context)) + + setCompoundDrawablesWithIntrinsicBounds(null, null, wrappedDrawable, null) + isFocusableInTouchMode = false + isCursorVisible = false + isLongClickable = false + maxLines = 1 + inputType = 0 + keyListener = null + setOnFocusChangeListener { v, hasFocus -> + if (!hasFocus) { + v.isFocusableInTouchMode = false + } + } + + setOnClickListener { + isFocusableInTouchMode = true + requestFocus() + val popup = PopupMenu(context, this) + + items.forEachIndexed { index, item -> + popup.menu.add(0, item.id.toInt(), index, item.text) + } + + popup.setOnMenuItemClickListener { menuItem -> + val item = items[menuItem.order] + if (onChangeListener?.invoke(item) != false) { + select(item) + } + clearFocus() + true + } + + popup.setOnDismissListener { + clearFocus() + } + + popup.show() + } + } + + fun select(item: Item) { + selected = item + updateText() + } + + fun select(id: Long?) { + items.singleOrNull { it.id == id }?.let { select(it) } + } + + fun select(tag: Any?) { + items.singleOrNull { it.tag == tag }?.let { select(it) } + } + + fun select(index: Int) { + items.getOrNull(index)?.let { select(it) } + } + + fun deselect(): TextInputDropDown { + selected = null + text = null + return this + } + + fun clear(): TextInputDropDown { + items.clear() + return this + } + + fun append(items: List): TextInputDropDown { + this.items.addAll(items) + return this + } + + fun prepend(items: List): TextInputDropDown{ + this.items.addAll(0, items) + return this + } + + operator fun plusAssign(items: Item) { + this.items.add(items) + } + operator fun plusAssign(items: List) { + this.items.addAll(items) + } + + /** + * Set the listener called when other item is selected. + * + * The listener should return true to allow the item to be selected, false otherwise. + */ + fun setOnChangeListener(onChangeListener: ((item: Item) -> Boolean)? = null): TextInputDropDown { + this.onChangeListener = onChangeListener + return this + } + + override fun setOnClickListener(onClickListener: OnClickListener?) { + super.setOnClickListener { v -> + isFocusableInTouchMode = true + requestFocus() + onClickListener!!.onClick(v) + } + } + + class Item(val id: Long, val text: CharSequence, val displayText: CharSequence? = null, val tag: Any? = null) +} diff --git a/app/src/main/res/layout/dialog_event_manual.xml b/app/src/main/res/layout/dialog_event_manual.xml index d26685c6..0acaf493 100644 --- a/app/src/main/res/layout/dialog_event_manual.xml +++ b/app/src/main/res/layout/dialog_event_manual.xml @@ -97,7 +97,8 @@ android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:text="@string/dialog_event_manual_share_first_notice" - android:visibility="gone" /> + android:visibility="gone" + tools:visibility="visible" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e7f882d..41589ce0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1013,4 +1013,13 @@ Nr lekcji Lekcja przeniesiona na %s Lekcja przeniesiona z %s + Dodaj wpis do terminarza + Lekcja/godzina + nast. lekcja %s + jutro (%s) + %s (%s) + -- inna data -- + dzisiaj (%s) + następny %s (%s) + Nie ma lekcji tego dnia