From 95baf9fb9cae8f9bcb888356968d28e5397b50f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 8 Apr 2021 00:39:59 +0200 Subject: [PATCH 01/34] [UI/Dialogs] Add context menu for teachers' names. (#21) * [UI] Rearrange BetterLink code. * [UI/Dialogs] Add context menu for teachers' names. * [Proguard] Update rules for BetterLink reflection. * [Proguard] Fix rules for BetterLink reflection. --- app/proguard-rules.pro | 3 +- .../ui/dialogs/event/EventDetailsDialog.kt | 15 +- .../ui/dialogs/grade/GradeDetailsDialog.kt | 9 + .../dialogs/timetable/LessonDetailsDialog.kt | 15 + .../attendance/AttendanceDetailsDialog.kt | 9 + .../ui/modules/behaviour/NoticesAdapter.kt | 9 + .../compose/MessagesComposeFragment.kt | 17 + .../edziennik/utils/BetterLink.kt | 344 ++++++++++++------ .../res/layout/attendance_details_dialog.xml | 1 + .../main/res/layout/dialog_event_details.xml | 1 + .../main/res/layout/dialog_grade_details.xml | 1 + .../main/res/layout/dialog_lesson_details.xml | 2 + 12 files changed, 311 insertions(+), 115 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 4d84d11d..0816fb00 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -32,8 +32,9 @@ -keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider -keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); } --keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; } -keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); } +-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; } +-keepclassmembernames class androidx.appcompat.view.menu.MenuItemImpl { private *; } -keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt index b41da04b..cdd2e50a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt @@ -199,10 +199,14 @@ class EventDetailsDialog( } b.downloadButton.attachToastHint(R.string.hint_download_again) + BetterLink.attach(b.topic, onActionSelected = dialog::dismiss) - b.topic.text = event.topic - BetterLink.attach(b.topic) { - dialog.dismiss() + event.teacherName?.let { name -> + BetterLink.attach( + b.teacherName, + teachers = mapOf(event.teacherId to name), + onActionSelected = dialog::dismiss + ) } if (event.homeworkBody == null && !event.addedManually && event.type == Event.TYPE_HOMEWORK) { @@ -220,10 +224,7 @@ class EventDetailsDialog( b.bodyTitle.isVisible = true b.bodyProgressBar.isVisible = false b.body.isVisible = true - b.body.text = event.homeworkBody - BetterLink.attach(b.body) { - dialog.dismiss() - } + BetterLink.attach(b.body, onActionSelected = dialog::dismiss) } if (event.attachmentIds.isNullOrEmpty() || event.attachmentNames.isNullOrEmpty()) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt index 5b9579ed..a42f9e2f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt @@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding import pl.szczodrzynski.edziennik.onClick import pl.szczodrzynski.edziennik.setTintColor import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter +import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import kotlin.coroutines.CoroutineContext @@ -68,6 +69,14 @@ class GradeDetailsDialog( GradesConfigDialog(activity, reloadOnDismiss = true) } + grade.teacherName?.let { name -> + BetterLink.attach( + b.teacherName, + teachers = mapOf(grade.teacherId to name), + onActionSelected = dialog::dismiss + ) + } + launch { val historyList = withContext(Dispatchers.Default) { app.db.gradeDao().getByParentIdNow(App.profileId, grade.id) 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 5896def0..f444f38e 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 @@ -25,6 +25,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment +import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Week @@ -216,5 +217,19 @@ class LessonDetailsDialog( b.eventsNoData.visibility = View.VISIBLE } }) + + lesson.displayTeacherName?.let { name -> + lesson.displayTeacherId ?: return@let + BetterLink.attach( + b.teacherNameView, + teachers = mapOf(lesson.displayTeacherId!! to name), + onActionSelected = dialog::dismiss + ) + BetterLink.attach( + b.oldTeacherNameView, + teachers = mapOf(lesson.displayTeacherId!! to name), + onActionSelected = dialog::dismiss + ) + } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt index 9e6c14d6..5ee6bdc3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceDetailsDialog.kt @@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding import pl.szczodrzynski.edziennik.setTintColor +import pl.szczodrzynski.edziennik.utils.BetterLink import kotlin.coroutines.CoroutineContext class AttendanceDetailsDialog( @@ -60,5 +61,13 @@ class AttendanceDetailsDialog( b.attendanceName.background.setTintColor(attendanceColor) b.attendanceIsCounted.setText(if (attendance.isCounted) R.string.yes else R.string.no) + + attendance.teacherName?.let { name -> + BetterLink.attach( + b.teacherName, + teachers = mapOf(attendance.teacherId to name), + onActionSelected = dialog::dismiss + ) + } }} } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt index a3288da2..61ac01f1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/behaviour/NoticesAdapter.kt @@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK import pl.szczodrzynski.edziennik.data.db.entity.Notice import pl.szczodrzynski.edziennik.data.db.full.NoticeFull +import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.Utils.bs import pl.szczodrzynski.edziennik.utils.models.Date @@ -83,6 +84,14 @@ class NoticesAdapter//getting the context and product list with constructor } else { holder.noticesItemReason.background = null } + + BetterLink.attach(holder.noticesItemReason) + + notice.teacherName?.let { name -> + BetterLink.attach(holder.noticesItemTeacherName, teachers = mapOf( + notice.teacherId to name + )) + } } override fun getItemCount(): Int { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt index 942876f2..051d86bd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt @@ -345,6 +345,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { b.recipients.setAdapter(adapter) handleReplyMessage() + handleMailToIntent() }} private fun handleReplyMessage() { launch { @@ -402,6 +403,22 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { } }} + private fun handleMailToIntent() { + val teacherId = arguments?.getLong("messageRecipientId") + if (teacherId == 0L) + return + + val chipList = mutableListOf() + teachers.firstOrNull { it.id == teacherId }?.let { teacher -> + teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName) + chipList += ChipInfo(teacher.fullName, teacher) + } + b.recipients.addTextWithChips(chipList) + + val subject = arguments?.getString("messageSubject") + b.subject.setText(subject ?: return) + } + private fun sendMessage() { b.recipientsLayout.error = null b.subjectLayout.error = null diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/BetterLink.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/BetterLink.kt index 53aa0429..6a5306bf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/BetterLink.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/BetterLink.kt @@ -7,6 +7,8 @@ package pl.szczodrzynski.edziennik.utils import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent import android.graphics.Color import android.text.Spannable import android.text.SpannableString @@ -19,141 +21,268 @@ import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuPopupHelper -import pl.szczodrzynski.edziennik.Intent -import pl.szczodrzynski.edziennik.copyToClipboard +import androidx.core.widget.addTextChangedListener +import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.Regexes -import pl.szczodrzynski.edziennik.get -import pl.szczodrzynski.edziennik.getTextPosition import pl.szczodrzynski.edziennik.utils.models.Date +@SuppressLint("RestrictedApi") object BetterLink { - @SuppressLint("RestrictedApi") - fun attach(textView: TextView, onActionSelected: (() -> Unit)? = null) { - textView.autoLinkMask = Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES - BetterLinkMovementMethod.linkify(textView.autoLinkMask, textView).setOnLinkClickListener { v, span: BetterLinkMovementMethod.ClickableSpanWithText -> - val url = span.text() - val c = v.context + /** + * Used in conjunction with the item's ID to execute the + * [attach]'s onActionSelected listener when the item is + * clicked. + */ + private const val FLAG_ACTION = 0x8000 - val s = v.text as Spanned - val start = s.getSpanStart(span.span()) - val end = s.getSpanEnd(span.span()) + private fun MenuBuilder.setTitle(title: CharSequence): MenuBuilder { + this::class.java.getDeclaredMethod("setHeaderTitleInt", CharSequence::class.java).let { + it.isAccessible = true + it.invoke(this, title) + } + return this + } - val parent = v.rootView.findViewById(android.R.id.content) - val parentLocation = intArrayOf(0, 0) - parent.getLocationOnScreen(parentLocation) - - val rect = textView.getTextPosition(start..end) - - val view = View(c) - view.layoutParams = ViewGroup.LayoutParams(rect.width(), rect.height()) - view.setBackgroundColor(Color.TRANSPARENT) - - parent.addView(view) - - view.x = rect.left.toFloat() - parentLocation[0] - view.y = rect.top.toFloat() - parentLocation[1] - - val menu = MenuBuilder(c) - val helper = MenuPopupHelper(c, menu, view) - val popup = helper.popup - - var menuTitle = url.substringAfter(":") - var date: Date? = null - - var urlItem: MenuItem? = null - var createEventItem: MenuItem? = null - //var goToTimetableItem: MenuItem? = null // TODO 2020-03-19: implement this - var mailItem: MenuItem? = null - var copyItem: MenuItem? = null - - when { - url.startsWith("mailto:") -> { - mailItem = menu.add(1, 20, 2, "Napisz e-mail") + private fun MenuItem.addListener(listener: (item: MenuItem) -> Boolean): MenuItem { + this::class.java.getDeclaredField("mClickListener").let { + it.isAccessible = true + val oldListener = it.get(this) as? MenuItem.OnMenuItemClickListener + it.set(this, object : MenuItem.OnMenuItemClickListener { + override fun onMenuItemClick(item: MenuItem): Boolean { + oldListener?.onMenuItemClick(item) + return listener(item) } - url.startsWith("dateYmd:") -> { - createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie") - //goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji") - date = parseDateYmd(menuTitle) - } - url.startsWith("dateDmy:") -> { - createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie") - //goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji") - date = parseDateDmy(menuTitle) - } - url.startsWith("dateAbs:") -> { - createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie") - //goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji") - date = parseDateAbs(menuTitle) - } - url.startsWith("dateRel:") -> { - createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie") - //goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji") - date = parseDateRel(menuTitle) - } - else -> { - urlItem = menu.add(1, 1, 2, "Otwórz w przeglądarce") - menuTitle = url - } - } - copyItem = menu.add(1, 1000, 1000, "Kopiuj tekst") + }) + } + return this + } - helper.setOnDismissListener { parent.removeView(view) } + private fun createUrlItems(menu: MenuBuilder, context: Context, url: String) { + menu.setTitle(url) + menu.add( + 1, + 2, + 2, + "Otwórz w przeglądarce" + ).setOnMenuItemClickListener { + Utils.openUrl(context, url) + true + } + } - urlItem?.setOnMenuItemClickListener { Utils.openUrl(c, url); true } - mailItem?.setOnMenuItemClickListener { Utils.openUrl(c, url); true } - copyItem?.setOnMenuItemClickListener { menuTitle.copyToClipboard(c); true } - createEventItem?.setOnMenuItemClickListener { - onActionSelected?.invoke() - val intent = Intent( - android.content.Intent.ACTION_MAIN, - "action" to "createManualEvent", - "eventDate" to date?.stringY_m_d - ) - c.sendBroadcast(intent) - true - } + private fun createMailtoItems(menu: MenuBuilder, context: Context, url: String) { + menu.add( + 1, + 3, + 3, + "Napisz e-mail" + ).setOnMenuItemClickListener { + Utils.openUrl(context, url) + true + } + } - menu::class.java.getDeclaredMethod("setHeaderTitleInt", CharSequence::class.java).let { - it.isAccessible = true - it.invoke(menu, menuTitle) - } - popup::class.java.getDeclaredField("mShowTitle").let { - it.isAccessible = true - it.set(popup, true) - } - helper::class.java.getDeclaredMethod("showPopup", Int::class.java, Int::class.java, Boolean::class.java, Boolean::class.java).let { - it.isAccessible = true - it.invoke(helper, 0, 0, false, true) - } + private fun createDateItems(menu: MenuBuilder, context: Context, date: Date?) { + date ?: return + menu.setTitle(date.formattedString) + menu.add( + 1, + 4 or FLAG_ACTION, + 4, + "Utwórz wydarzenie" + ).setOnMenuItemClickListener { + val intent = Intent( + Intent.ACTION_MAIN, + "action" to "createManualEvent", + "eventDate" to date.stringY_m_d + ) + context.sendBroadcast(intent) + true + } + } + + private fun createTeacherItems(menu: MenuBuilder, context: Context, teacherId: Long, fullName: String) { + menu.setTitle(fullName) + menu.add( + 1, + 5 or FLAG_ACTION, + 5, + "Napisz wiadomość" + ).setOnMenuItemClickListener { + val intent = Intent( + Intent.ACTION_MAIN, + "fragmentId" to MainActivity.TARGET_MESSAGES_COMPOSE, + "messageRecipientId" to teacherId + ) + context.sendBroadcast(intent) + true + } + } + + private fun onClickListener( + view: TextView, + span: BetterLinkMovementMethod.ClickableSpanWithText, + onActionSelected: (() -> Unit)? + ): Boolean { + val context = view.context + + val spanned = view.text as Spanned + val start = spanned.getSpanStart(span.span()) + val end = spanned.getSpanEnd(span.span()) + + val parent = view.rootView.findViewById(android.R.id.content) + val parentLocation = intArrayOf(0, 0) + parent.getLocationOnScreen(parentLocation) + + val rect = view.getTextPosition(start..end) + + val popupView = View(context) + popupView.layoutParams = ViewGroup.LayoutParams(rect.width(), rect.height()) + popupView.setBackgroundColor(Color.TRANSPARENT) + + parent.addView(popupView) + + popupView.x = rect.left.toFloat() - parentLocation[0] + popupView.y = rect.top.toFloat() - parentLocation[1] + + val menu = MenuBuilder(context) + val helper = MenuPopupHelper(context, menu, popupView) + val popup = helper.popup + + val spanUrl = span.text() + val spanParameter = spanUrl.substringAfter(":") + val spanText = spanned.substring(start, end) + + //goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji") + + // create appropriate items for spans + when { + spanUrl.startsWith("mailto:") -> createMailtoItems(menu, context, spanUrl) + spanUrl.startsWith("dateYmd:") -> createDateItems(menu, context, parseDateYmd(spanParameter)) + spanUrl.startsWith("dateDmy:") -> createDateItems(menu, context, parseDateDmy(spanParameter)) + spanUrl.startsWith("dateAbs:") -> createDateItems(menu, context, parseDateAbs(spanParameter)) + spanUrl.startsWith("dateRel:") -> createDateItems(menu, context, parseDateRel(spanParameter)) + spanUrl.startsWith("teacher:") -> createTeacherItems( + menu, + context, + teacherId = spanParameter.toLongOrNull() ?: -1, + fullName = spanText + ) + else -> createUrlItems(menu, context, spanUrl) + } + menu.add(1, 1000, 1000, "Kopiuj tekst").setOnMenuItemClickListener { + spanParameter.copyToClipboard(context) true } - val spanned = textView.text as? Spannable ?: { - SpannableString(textView.text) - }() + helper.setOnDismissListener { parent.removeView(popupView) } + + menu.visibleItems.forEach { item -> + if ((item.itemId and FLAG_ACTION) != FLAG_ACTION) + return@forEach + item.addListener { + onActionSelected?.invoke() + true + } + } + + popup::class.java.getDeclaredField("mShowTitle").let { + it.isAccessible = true + it.set(popup, true) + } + helper::class.java.getDeclaredMethod( + "showPopup", + Int::class.java, + Int::class.java, + Boolean::class.java, + Boolean::class.java + ).let { + it.isAccessible = true + it.invoke(helper, 0, 0, false, true) + } + return true + } + + fun attach( + textView: TextView, + teachers: Map? = null, + onActionSelected: (() -> Unit)? = null + ) { + textView.autoLinkMask = Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES + + BetterLinkMovementMethod + .linkify(textView.autoLinkMask, textView) + .setOnLinkClickListener { view, span -> + onClickListener(view, span, onActionSelected) + } + + textView.addTextChangedListener { + attachSpan(textView, teachers) + } + + attachSpan(textView, teachers) + } + + private fun attachSpan( + textView: TextView, + teachers: Map? = null + ) { + val spanned = textView.text as? Spannable ?: SpannableString(textView.text) + + teachers?.forEach { (id, fullName) -> + val index = textView.text.indexOf(fullName) + if (index == -1) + return@forEach + val span = URLSpan("teacher:$id") + spanned.setSpan( + span, + index, + index + fullName.length, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } Regexes.LINKIFY_DATE_YMD.findAll(textView.text).forEach { match -> val span = URLSpan("dateYmd:" + match.value) - spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spanned.setSpan( + span, + match.range.first, + match.range.last + 1, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) } Regexes.LINKIFY_DATE_DMY.findAll(textView.text).forEach { match -> val span = URLSpan("dateDmy:" + match.value) - spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spanned.setSpan( + span, + match.range.first, + match.range.last + 1, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) } Regexes.LINKIFY_DATE_ABSOLUTE.findAll(textView.text).forEach { match -> val span = URLSpan("dateAbs:" + match.value) - spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spanned.setSpan( + span, + match.range.first, + match.range.last + 1, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) } Regexes.LINKIFY_DATE_RELATIVE.findAll(textView.text).forEach { match -> val span = URLSpan("dateRel:" + match.value) - spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spanned.setSpan( + span, + match.range.first, + match.range.last + 1, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) } - //Linkify.addLinks(textView, LINKIFY_DATE_ABSOLUTE.toPattern(), "dateAbs:") - //Linkify.addLinks(textView, LINKIFY_DATE_RELATIVE.toPattern(), "dateRel:") } - private val monthNames = listOf("sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru") + private val monthNames = + listOf("sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru") private fun parseDateYmd(text: String): Date? { return Regexes.LINKIFY_DATE_YMD.find(text)?.let { @@ -163,6 +292,7 @@ object BetterLink { Date(year, month, day) } } + private fun parseDateDmy(text: String): Date? { return Regexes.LINKIFY_DATE_DMY.find(text)?.let { val day = it[1].toIntOrNull() ?: 1 @@ -194,7 +324,7 @@ object BetterLink { else -> 1 } - date.stepForward(0, 0, amount*unitInDays) + date.stepForward(0, 0, amount * unitInDays) } } } diff --git a/app/src/main/res/layout/attendance_details_dialog.xml b/app/src/main/res/layout/attendance_details_dialog.xml index 34a2dc0d..b18e1c89 100644 --- a/app/src/main/res/layout/attendance_details_dialog.xml +++ b/app/src/main/res/layout/attendance_details_dialog.xml @@ -94,6 +94,7 @@ android:textAppearance="@style/NavView.TextView.Helper" /> Date: Fri, 9 Apr 2021 15:30:58 +0200 Subject: [PATCH 02/34] [Agenda] Refactor agenda UI code. --- .../edziennik/data/db/entity/Event.kt | 9 +- .../ui/modules/agenda/AgendaFragment.kt | 195 +++----------- .../modules/agenda/AgendaFragmentDefault.kt | 142 ++++++++++ .../edziennik/ui/modules/agenda/BaseEvent.kt | 61 +++++ .../ui/modules/agenda/event/AgendaEvent.kt | 19 ++ .../agenda/event/AgendaEventRenderer.kt | 37 +++ .../lessonchange/LessonChangeCounter.kt | 19 -- .../lessonchange/LessonChangeEvent.java | 243 ------------------ .../lessonchange/LessonChangeEventRenderer.kt | 21 -- .../lessonchanges/LessonChangesEvent.kt | 21 ++ .../LessonChangesEventRenderer.kt | 21 ++ .../teacherabsence/TeacherAbsenceCounter.kt | 19 -- .../teacherabsence/TeacherAbsenceEvent.kt | 199 ++------------ .../TeacherAbsenceEventRenderer.kt | 22 +- .../edziennik/utils/models/Date.java | 28 +- app/src/main/res/layout/agenda_event_item.xml | 37 +++ ...tem.xml => agenda_lesson_changes_item.xml} | 16 +- ...em.xml => agenda_teacher_absence_item.xml} | 16 +- ...on_change.xml => agenda_wrapped_event.xml} | 10 +- ....xml => agenda_wrapped_lesson_changes.xml} | 4 +- .../layout/agenda_wrapped_teacher_absence.xml | 14 + app/src/main/res/layout/dialog_day.xml | 4 +- 22 files changed, 452 insertions(+), 705 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/BaseEvent.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEvent.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEvent.java delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEvent.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt create mode 100644 app/src/main/res/layout/agenda_event_item.xml rename app/src/main/res/layout/{row_lesson_change_item.xml => agenda_lesson_changes_item.xml} (72%) rename app/src/main/res/layout/{row_teacher_absence_item.xml => agenda_teacher_absence_item.xml} (72%) rename app/src/main/res/layout/{agenda_event_lesson_change.xml => agenda_wrapped_event.xml} (77%) rename app/src/main/res/layout/{agenda_event_teacher_absence.xml => agenda_wrapped_lesson_changes.xml} (86%) create mode 100644 app/src/main/res/layout/agenda_wrapped_teacher_absence.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt index 0dcaa83e..a3f44585 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt @@ -116,14 +116,7 @@ open class Event( var showAsUnseen: Boolean? = null val startTimeCalendar: Calendar - get() = Calendar.getInstance().also { it.set( - date.year, - date.month - 1, - date.day, - time?.hour ?: 0, - time?.minute ?: 0, - time?.second ?: 0 - ) } + get() = date.getAsCalendar(time) val endTimeCalendar: Calendar get() = startTimeCalendar.also { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt index 367b00da..dd3fbbbc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt @@ -12,10 +12,6 @@ import android.widget.Toast import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment 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.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.utils.colorInt @@ -31,15 +27,6 @@ import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog 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.models.Date import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem @@ -59,7 +46,8 @@ class AgendaFragment : Fragment(), CoroutineScope { get() = job + Dispatchers.Main 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? { if (getActivity() == null || context == null) return null @@ -82,38 +70,50 @@ class AgendaFragment : Fragment(), CoroutineScope { .withTitle(R.string.menu_add_event) .withDescription(R.string.menu_add_event_desc) .withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline) - .withOnClickListener(View.OnClickListener { + .withOnClickListener { activity.bottomSheet.close() - EventManualDialog(activity, app.profileId, defaultDate = actualDate) - }), + EventManualDialog( + activity, + app.profileId, + defaultDate = agendaDefault?.selectedDate + ) + }, BottomSheetPrimaryItem(true) .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) - .withOnClickListener(View.OnClickListener { + .withOnClickListener { 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 activity.reloadTarget() - }), + }, BottomSheetSeparatorItem(true), BottomSheetPrimaryItem(true) .withTitle(R.string.menu_mark_as_read) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) - .withOnClickListener(View.OnClickListener { launch { - activity.bottomSheet.close() - withContext(Dispatchers.Default) { - App.db.metadataDao().setAllSeen(app.profileId, Metadata.TYPE_EVENT, true) + .withOnClickListener { + launch { + activity.bottomSheet.close() + withContext(Dispatchers.Default) { + 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.fabExtendedText = getString(R.string.add) activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus - activity.navView.setFabOnClickListener(View.OnClickListener { - EventManualDialog(activity, app.profileId, defaultDate = actualDate) - }) + activity.navView.setFabOnClickListener { + EventManualDialog(activity, app.profileId, defaultDate = agendaDefault?.selectedDate) + } activity.gainAttention() activity.gainAttentionFAB() @@ -129,141 +129,8 @@ class AgendaFragment : Fragment(), CoroutineScope { return@launch delay(500) - val eventList = mutableListOf() - - 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() - - 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() - - 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() - - 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()) + agendaDefault = AgendaFragmentDefault(activity, app, b) + agendaDefault?.initView(this@AgendaFragment) b.progressBar.visibility = View.GONE }}} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt new file mode 100644 index 00000000..9e4e31ab --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt @@ -0,0 +1,142 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-8. + */ + +package pl.szczodrzynski.edziennik.ui.modules.agenda + +import android.util.SparseIntArray +import androidx.core.util.forEach +import androidx.core.util.set +import com.github.tibolte.agendacalendarview.CalendarPickerController +import com.github.tibolte.agendacalendarview.models.CalendarEvent +import com.github.tibolte.agendacalendarview.models.IDayItem +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding +import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog +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.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 +) { + private val unreadDates = mutableSetOf() + var selectedDate: Date = Date.getToday() + + suspend fun initView(fragment: AgendaFragment) { + val dateStart = app.profile.dateSemester1Start.asCalendar + val dateEnd = app.profile.dateYearEnd.asCalendar + + val events = withContext(Dispatchers.Default) { + val events = mutableListOf() + + addLessonChanges(events) + + val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true) + if (showTeacherAbsences) { + addTeacherAbsence(events) + } + + addEvents(events) + + return@withContext events + } + + if (!fragment.isAdded) + return + b.agendaDefaultView.init( + events, + dateStart, + dateEnd, + Locale.getDefault(), + object : CalendarPickerController { + override fun onDaySelected(dayItem: IDayItem) {} + + override fun onEventSelected(event: CalendarEvent) { + val date = Date.fromCalendar(event.instanceDay) + + when (event) { + is AgendaEvent -> DayDialog(activity, app.profileId, date) + is LessonChangesEvent -> LessonChangeDialog(activity, app.profileId, date) + is TeacherAbsenceEvent -> TeacherAbsenceDialog(activity, app.profileId, date) + } + } + + override fun onScrollToDate(calendar: Calendar) { + selectedDate = Date.fromCalendar(calendar) + + // Mark as read scrolled date + if (selectedDate.value in unreadDates) { + activity.launch(Dispatchers.Default) { + app.db.eventDao().setSeenByDate(app.profileId, selectedDate, true) + } + unreadDates.remove(selectedDate.value) + } + } + }, + AgendaEventRenderer(), + LessonChangesEventRenderer(), + TeacherAbsenceEventRenderer() + ) + } + + private fun addEvents(events: MutableList) { + val eventList = app.db.eventDao().getAllNow(app.profileId) + + events += eventList.map { + if (!it.seen) + unreadDates.add(it.date.value) + AgendaEvent(it) + } + } + + private fun addLessonChanges(events: MutableList) { + 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, + changeCount = changes.size + ) + } + } + + private fun addTeacherAbsence(events: MutableList) { + 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), + absenceCount = count + ) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/BaseEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/BaseEvent.kt new file mode 100644 index 00000000..f183485b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/BaseEvent.kt @@ -0,0 +1,61 @@ +/* + * 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 val showBadge: Boolean +) : 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 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 getShowBadge() = showBadge + 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 setShowBadge(value: Boolean) = Unit + override fun setPlaceholder(value: Boolean) = Unit + override fun setAllDay(value: Boolean) = Unit +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEvent.kt new file mode 100644 index 00000000..3cd05727 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEvent.kt @@ -0,0 +1,19 @@ +/* + * 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 +) : BaseEvent( + id = event.id, + time = event.startTimeCalendar, + color = event.eventColor, + showBadge = !event.seen +) { + override fun copy() = AgendaEvent(event) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt new file mode 100644 index 00000000..da28ffd5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt @@ -0,0 +1,37 @@ +/* + * 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 com.github.tibolte.agendacalendarview.render.EventRenderer +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventBinding +import pl.szczodrzynski.edziennik.utils.Colors + +class AgendaEventRenderer : EventRenderer() { + + @SuppressLint("SetTextI18n") + override fun render(view: View, aEvent: AgendaEvent) { + val b = AgendaWrappedEventBinding.bind(view).item + val event = aEvent.event + + b.card.setCardBackgroundColor(event.eventColor) + b.eventTitle.setTextColor(Colors.legibleTextColor(event.eventColor)) + b.eventSubtitle.setTextColor(Colors.legibleTextColor(event.eventColor)) + + b.eventTitle.text = "${event.typeName ?: "wydarzenie"} - ${event.topic}" + b.eventSubtitle.text = + (if (event.time == null) + view.context.getString(R.string.agenda_event_all_day) + else + event.time!!.stringHM) + + (event.subjectLongName?.let { ", $it" } ?: "") + + (event.teacherName?.let { ", $it" } ?: "") + + (event.teamName?.let { ", $it" } ?: "") + } + + override fun getEventLayout(): Int = R.layout.agenda_wrapped_event +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.kt deleted file mode 100644 index 2b264ede..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.kt +++ /dev/null @@ -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) - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEvent.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEvent.java deleted file mode 100644 index 077c2206..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEvent.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.kt deleted file mode 100644 index e2306039..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.kt +++ /dev/null @@ -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() { - override fun render(view: View?, event: LessonChangeEvent) { - val card = view?.findViewById(R.id.lesson_change_card) - val changeText = view?.findViewById(R.id.lesson_change_text) - val changeCount = view?.findViewById(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 -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEvent.kt new file mode 100644 index 00000000..e65c82bd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEvent.kt @@ -0,0 +1,21 @@ +/* + * 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 changeCount: Int +) : BaseEvent( + id = date.value.toLong(), + time = date.asCalendar, + color = 0xff78909c.toInt(), + showBadge = false +) { + override fun copy() = LessonChangesEvent(profileId, date, changeCount) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt new file mode 100644 index 00000000..5ffc788c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-8. + */ + +package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges + +import android.view.View +import com.github.tibolte.agendacalendarview.render.EventRenderer +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.AgendaWrappedLessonChangesBinding + +class LessonChangesEventRenderer : EventRenderer() { + + override fun render(view: View, event: LessonChangesEvent) { + val b = AgendaWrappedLessonChangesBinding.bind(view).item + + b.lessonChangeCount.text = event.changeCount.toString() + } + + override fun getEventLayout(): Int = R.layout.agenda_wrapped_lesson_changes +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt deleted file mode 100644 index 71dea025..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt +++ /dev/null @@ -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) - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt index fc1c28db..4208814b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt @@ -1,188 +1,21 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-8. + */ + package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence -import com.github.tibolte.agendacalendarview.models.CalendarEvent -import com.github.tibolte.agendacalendarview.models.IDayItem -import com.github.tibolte.agendacalendarview.models.IWeekItem +import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent import pl.szczodrzynski.edziennik.utils.models.Date -import java.util.* -class TeacherAbsenceEvent : CalendarEvent { - /** - * Id of the event. - */ - private var mId: Long = 0 - /** - * Color to be displayed in the agenda view. - */ - private var mColor: Int = 0 - /** - * Text color displayed on the background color - */ - 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 - } +class TeacherAbsenceEvent( + val profileId: Int, + val date: Date, + val absenceCount: Int +) : BaseEvent( + id = date.value.toLong(), + time = date.asCalendar, + color = 0xffff1744.toInt(), + showBadge = false +) { + override fun copy() = TeacherAbsenceEvent(profileId, date, absenceCount) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt index b437769b..756c60c9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt @@ -1,21 +1,21 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-8. + */ + package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence 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 +import pl.szczodrzynski.edziennik.databinding.AgendaWrappedTeacherAbsenceBinding class TeacherAbsenceEventRenderer : EventRenderer() { - override fun render(view: View?, event: TeacherAbsenceEvent) { - val card = view?.findViewById(R.id.teacherAbsenceCard) - val changeText = view?.findViewById(R.id.teacherAbsenceText) - val changeCount = view?.findViewById(R.id.teacherAbsenceCount) - card?.setCardBackgroundColor(event.color) - changeText?.setTextColor(event.textColor) - changeCount?.setTextColor(event.textColor) - changeCount?.text = event.teacherAbsenceCount.toString() + + override fun render(view: View, event: TeacherAbsenceEvent) { + val b = AgendaWrappedTeacherAbsenceBinding.bind(view).item + + b.teacherAbsenceCount.text = event.absenceCount.toString() } - override fun getEventLayout(): Int = R.layout.agenda_event_teacher_absence + override fun getEventLayout(): Int = R.layout.agenda_wrapped_teacher_absence } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java index b39d0298..ebc4570c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java @@ -94,17 +94,29 @@ public class Date implements Comparable { } } - public long getInMillis() { + public Calendar getAsCalendar() { Calendar c = Calendar.getInstance(); c.set(year, month - 1, day, 0, 0, 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(); } public long getInMillisUtc() { - Calendar c = Calendar.getInstance(); - c.set(year, month - 1, day, 0, 0, 0); - c.set(Calendar.MILLISECOND, 0); + Calendar c = getAsCalendar(); c.setTimeZone(TimeZone.getTimeZone("UTC")); return c.getTimeInMillis(); } @@ -179,13 +191,7 @@ public class Date implements Comparable { } public long combineWith(Time time) { - if (time == null) { - 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(); + return getAsCalendar(time).getTimeInMillis(); } public int getWeekDay() { diff --git a/app/src/main/res/layout/agenda_event_item.xml b/app/src/main/res/layout/agenda_event_item.xml new file mode 100644 index 00000000..b07106f5 --- /dev/null +++ b/app/src/main/res/layout/agenda_event_item.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/row_lesson_change_item.xml b/app/src/main/res/layout/agenda_lesson_changes_item.xml similarity index 72% rename from app/src/main/res/layout/row_lesson_change_item.xml rename to app/src/main/res/layout/agenda_lesson_changes_item.xml index 33515e89..8700a575 100644 --- a/app/src/main/res/layout/row_lesson_change_item.xml +++ b/app/src/main/res/layout/agenda_lesson_changes_item.xml @@ -1,11 +1,10 @@ - + app:cardCornerRadius="5dp"> + android:textColor="@color/md_white_1000" /> - - diff --git a/app/src/main/res/layout/row_teacher_absence_item.xml b/app/src/main/res/layout/agenda_teacher_absence_item.xml similarity index 72% rename from app/src/main/res/layout/row_teacher_absence_item.xml rename to app/src/main/res/layout/agenda_teacher_absence_item.xml index da78c625..4b749e8c 100644 --- a/app/src/main/res/layout/row_teacher_absence_item.xml +++ b/app/src/main/res/layout/agenda_teacher_absence_item.xml @@ -1,11 +1,10 @@ - + app:cardCornerRadius="5dp"> + android:textColor="@color/md_white_1000" /> - - diff --git a/app/src/main/res/layout/agenda_event_lesson_change.xml b/app/src/main/res/layout/agenda_wrapped_event.xml similarity index 77% rename from app/src/main/res/layout/agenda_event_lesson_change.xml rename to app/src/main/res/layout/agenda_wrapped_event.xml index c51f9b35..2ca38528 100644 --- a/app/src/main/res/layout/agenda_event_lesson_change.xml +++ b/app/src/main/res/layout/agenda_wrapped_event.xml @@ -1,15 +1,17 @@ + + - diff --git a/app/src/main/res/layout/agenda_event_teacher_absence.xml b/app/src/main/res/layout/agenda_wrapped_lesson_changes.xml similarity index 86% rename from app/src/main/res/layout/agenda_event_teacher_absence.xml rename to app/src/main/res/layout/agenda_wrapped_lesson_changes.xml index 785256d0..834d2ddd 100644 --- a/app/src/main/res/layout/agenda_event_teacher_absence.xml +++ b/app/src/main/res/layout/agenda_wrapped_lesson_changes.xml @@ -5,9 +5,9 @@ android:gravity="center_vertical" android:orientation="horizontal"> - diff --git a/app/src/main/res/layout/agenda_wrapped_teacher_absence.xml b/app/src/main/res/layout/agenda_wrapped_teacher_absence.xml new file mode 100644 index 00000000..4793b0d8 --- /dev/null +++ b/app/src/main/res/layout/agenda_wrapped_teacher_absence.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/layout/dialog_day.xml b/app/src/main/res/layout/dialog_day.xml index 59fd7f8c..f1544f59 100644 --- a/app/src/main/res/layout/dialog_day.xml +++ b/app/src/main/res/layout/dialog_day.xml @@ -49,7 +49,7 @@ Date: Fri, 9 Apr 2021 21:52:04 +0200 Subject: [PATCH 03/34] [Agenda] Implement updating event list when changed. --- .../edziennik/data/db/dao/TimetableDao.kt | 2 + .../ui/dialogs/event/EventManualDialog.kt | 5 -- .../ui/modules/agenda/AgendaFragment.kt | 8 ++- .../modules/agenda/AgendaFragmentDefault.kt | 62 ++++++++++++++----- 4 files changed, 55 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt index c7cbee12..8ce9ed9b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt @@ -84,6 +84,8 @@ abstract class TimetableDao : BaseDao { "LIMIT 1") 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") + fun getChanges(profileId: Int) = + getRaw("$QUERY WHERE timetable.profileId = $profileId AND $IS_CHANGED $ORDER_BY") // GET ALL - NOW fun getAllNow(profileId: Int) = diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt index 97e34009..9beac76b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt @@ -20,7 +20,6 @@ import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode 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.events.ApiTaskAllFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent @@ -599,8 +598,6 @@ class EventManualDialog( dialog.dismiss() Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show() - if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA) - activity.reloadTarget() } private fun finishRemoving() { editingEvent ?: return @@ -613,7 +610,5 @@ class EventManualDialog( removeEventDialog?.dismiss() dialog.dismiss() Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() - if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA) - activity.reloadTarget() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt index dd3fbbbc..0d5fd425 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt @@ -75,7 +75,7 @@ class AgendaFragment : Fragment(), CoroutineScope { EventManualDialog( activity, app.profileId, - defaultDate = agendaDefault?.selectedDate + defaultDate = AgendaFragmentDefault.selectedDate ) }, BottomSheetPrimaryItem(true) @@ -112,7 +112,11 @@ class AgendaFragment : Fragment(), CoroutineScope { activity.navView.bottomBar.fabExtendedText = getString(R.string.add) activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus activity.navView.setFabOnClickListener { - EventManualDialog(activity, app.profileId, defaultDate = agendaDefault?.selectedDate) + EventManualDialog( + activity, + app.profileId, + defaultDate = AgendaFragmentDefault.selectedDate + ) } activity.gainAttention() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt index 9e4e31ab..dff2722c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt @@ -7,7 +7,10 @@ package pl.szczodrzynski.edziennik.ui.modules.agenda import android.util.SparseIntArray import androidx.core.util.forEach import androidx.core.util.set +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.Dispatchers @@ -15,6 +18,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext 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.lessonchange.LessonChangeDialog @@ -33,30 +37,39 @@ class AgendaFragmentDefault( private val app: App, private val b: FragmentAgendaDefaultBinding ) { + companion object { + var selectedDate: Date = Date.getToday() + } + private val unreadDates = mutableSetOf() - var selectedDate: Date = Date.getToday() + private val events = mutableListOf() + private var isInitialized = false suspend fun initView(fragment: AgendaFragment) { - val dateStart = app.profile.dateSemester1Start.asCalendar - val dateEnd = app.profile.dateYearEnd.asCalendar - - val events = withContext(Dispatchers.Default) { - val events = mutableListOf() + isInitialized = false + withContext(Dispatchers.Default) { addLessonChanges(events) val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true) if (showTeacherAbsences) { addTeacherAbsence(events) } - - addEvents(events) - - return@withContext events } - if (!fragment.isAdded) - return + 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 + b.agendaDefaultView.init( events, dateStart, @@ -71,7 +84,11 @@ class AgendaFragmentDefault( when (event) { is AgendaEvent -> DayDialog(activity, app.profileId, date) is LessonChangesEvent -> LessonChangeDialog(activity, app.profileId, date) - is TeacherAbsenceEvent -> TeacherAbsenceDialog(activity, app.profileId, date) + is TeacherAbsenceEvent -> TeacherAbsenceDialog( + activity, + app.profileId, + date + ) } } @@ -91,10 +108,25 @@ class AgendaFragmentDefault( LessonChangesEventRenderer(), TeacherAbsenceEventRenderer() ) + + isInitialized = true } - private fun addEvents(events: MutableList) { - val eventList = app.db.eventDao().getAllNow(app.profileId) + private fun updateView() { + val manager = CalendarManager.getInstance() + manager.events.clear() + manager.loadEvents(events, BaseCalendarEvent()) + + val adapter = b.agendaDefaultView.agendaView.agendaListView.adapter as? AgendaAdapter + adapter?.updateEvents(manager.events) + b.agendaDefaultView.agendaView.agendaListView.scrollToCurrentDate(selectedDate.asCalendar) + } + + private fun addEvents( + events: MutableList, + eventList: List + ) { + events.removeAll { it is AgendaEvent } events += eventList.map { if (!it.seen) From b14ef5cd78e93fdc2b715855b855862290211f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 10 Apr 2021 18:49:24 +0200 Subject: [PATCH 04/34] [Agenda] Limit event text to 3 lines max. --- app/src/main/res/layout/agenda_event_item.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/layout/agenda_event_item.xml b/app/src/main/res/layout/agenda_event_item.xml index b07106f5..5f4ff89a 100644 --- a/app/src/main/res/layout/agenda_event_item.xml +++ b/app/src/main/res/layout/agenda_event_item.xml @@ -22,6 +22,8 @@ android:id="@+id/eventTitle" 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" /> From 3eae8fb58b1bbedecedf2d629752ea1264d82199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 10 Apr 2021 20:51:29 +0200 Subject: [PATCH 05/34] [Agenda] Add config dialog. Add compact mode. --- .../edziennik/config/ProfileConfigUI.kt | 30 ++++ .../ui/dialogs/agenda/AgendaConfigDialog.kt | 93 +++++++++++ .../ui/modules/agenda/AgendaFragment.kt | 10 +- .../modules/agenda/AgendaFragmentDefault.kt | 19 ++- .../agenda/event/AgendaEventRenderer.kt | 6 +- .../settings/cards/SettingsRegisterCard.kt | 8 + app/src/main/res/layout/agenda_event_item.xml | 67 ++++---- .../main/res/layout/dialog_config_agenda.xml | 148 ++++++++++++++++++ app/src/main/res/values/strings.xml | 14 ++ 9 files changed, 359 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/agenda/AgendaConfigDialog.kt create mode 100644 app/src/main/res/layout/dialog_config_agenda.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt index 5ce237a0..ca22cb76 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt @@ -15,6 +15,36 @@ class ProfileConfigUI(private val config: ProfileConfig) { get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT } 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? = null var homeCards: List get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/agenda/AgendaConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/agenda/AgendaConfigDialog.kt new file mode 100644 index 00000000..150d7d65 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/agenda/AgendaConfigDialog.kt @@ -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() { + + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt index 0d5fd425..8d228dcc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt @@ -25,6 +25,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding 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.event.EventManualDialog import pl.szczodrzynski.edziennik.utils.Themes @@ -78,6 +79,13 @@ class AgendaFragment : Fragment(), CoroutineScope { 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) .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) @@ -135,8 +143,6 @@ class AgendaFragment : Fragment(), CoroutineScope { agendaDefault = AgendaFragmentDefault(activity, app, b) agendaDefault?.initView(this@AgendaFragment) - - b.progressBar.visibility = View.GONE }}} private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt index dff2722c..680eeb18 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt @@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.ui.modules.agenda import android.util.SparseIntArray 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 @@ -21,6 +22,7 @@ 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 @@ -44,17 +46,17 @@ class AgendaFragmentDefault( private val unreadDates = mutableSetOf() private val events = mutableListOf() private var isInitialized = false + private val profileConfig by lazy { app.config.forProfile().ui } suspend fun initView(fragment: AgendaFragment) { isInitialized = false withContext(Dispatchers.Default) { - addLessonChanges(events) + if (profileConfig.agendaLessonChanges) + addLessonChanges(events) - val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true) - if (showTeacherAbsences) { + if (profileConfig.agendaTeacherAbsence) addTeacherAbsence(events) - } } app.db.eventDao().getAll(app.profileId).observe(fragment) { @@ -70,6 +72,8 @@ class AgendaFragmentDefault( val dateStart = app.profile.dateSemester1Start.asCalendar val dateEnd = app.profile.dateYearEnd.asCalendar + val isCompactMode = profileConfig.agendaCompactMode + b.agendaDefaultView.init( events, dateStart, @@ -82,13 +86,15 @@ class AgendaFragmentDefault( val date = Date.fromCalendar(event.instanceDay) when (event) { - is AgendaEvent -> DayDialog(activity, app.profileId, date) + is AgendaEvent -> EventDetailsDialog(activity, event.event) is LessonChangesEvent -> LessonChangeDialog(activity, app.profileId, date) is TeacherAbsenceEvent -> TeacherAbsenceDialog( activity, app.profileId, date ) + is BaseCalendarEvent -> if (event.isPlaceHolder) + DayDialog(activity, app.profileId, date) } } @@ -104,12 +110,13 @@ class AgendaFragmentDefault( } } }, - AgendaEventRenderer(), + AgendaEventRenderer(isCompactMode), LessonChangesEventRenderer(), TeacherAbsenceEventRenderer() ) isInitialized = true + b.progressBar.isVisible = false } private fun updateView() { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt index da28ffd5..6e78776a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt @@ -11,13 +11,17 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventBinding import pl.szczodrzynski.edziennik.utils.Colors -class AgendaEventRenderer : EventRenderer() { +class AgendaEventRenderer( + private val isCompact: Boolean +) : EventRenderer() { @SuppressLint("SetTextI18n") override fun render(view: View, aEvent: AgendaEvent) { val b = AgendaWrappedEventBinding.bind(view).item val event = aEvent.event + b.isCompact = isCompact + b.card.setCardBackgroundColor(event.eventColor) b.eventTitle.setTextColor(Colors.legibleTextColor(event.eventColor)) b.eventSubtitle.setTextColor(Colors.legibleTextColor(event.eventColor)) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsRegisterCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsRegisterCard.kt index 36239083..3dacd82d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsRegisterCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsRegisterCard.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.after import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED +import pl.szczodrzynski.edziennik.ui.dialogs.agenda.AgendaConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.bell.BellSyncConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradesConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog @@ -58,6 +59,13 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) { } override fun getItems() = listOfNotNull( + util.createActionItem( + text = R.string.menu_agenda_config, + icon = CommunityMaterial.Icon.cmd_calendar_outline + ) { + AgendaConfigDialog(activity, reloadOnDismiss = false) + }, + util.createActionItem( text = R.string.menu_grades_config, icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline diff --git a/app/src/main/res/layout/agenda_event_item.xml b/app/src/main/res/layout/agenda_event_item.xml index 5f4ff89a..c70c4a95 100644 --- a/app/src/main/res/layout/agenda_event_item.xml +++ b/app/src/main/res/layout/agenda_event_item.xml @@ -3,37 +3,50 @@ ~ Copyright (c) Kuba Szczodrzyński 2021-4-8. --> - + xmlns:tools="http://schemas.android.com/tools"> - + + + + + + + + app:cardCornerRadius="5dp" + tools:cardBackgroundColor="@color/blue_selected"> - - - - - + android:orientation="vertical" + android:padding="10dp"> + + + + + + + diff --git a/app/src/main/res/layout/dialog_config_agenda.xml b/app/src/main/res/layout/dialog_config_agenda.xml new file mode 100644 index 00000000..475c9d84 --- /dev/null +++ b/app/src/main/res/layout/dialog_config_agenda.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e5f21e05..6f3021e4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1425,4 +1425,18 @@ Aby móc zapisać wygenerowany plan lekcji musisz przyznać uprawnienia dostępu do pamięci urządzenia.\n\nKliknij OK, aby przyznać uprawnienia. przeczytanie Polityki prywatności i akceptujesz jej postanowienia.

Autorzy aplikacji nie biorą odpowiedzialności za korzystanie z aplikacji Szkolny.eu.]]>
Szkolny.eu v%s\n%s + Ustawienia terminarza + Wygląd + Pokazuj zmiany planu lekcji + Pokazuj nieobecności nauczycieli + Tryb kompaktowy + Mniejszy rozmiar wydarzeń na liście + Grupuj wydarzenia tego samego typu + Niedostępne w trybie kalendarza + Udostępnianie wydarzeń + Włącz Udostępnianie wydarzeń + Nauczanie zdalne + Ustaw wydarzenia jako lekcje on-line + Wybierz rodzaj wydarzeń + Grupuj lekcje on-line na liście From 777ae945e0e8ce7d477449d83f10bbd3242f711c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 10 Apr 2021 22:26:43 +0200 Subject: [PATCH 06/34] [Agenda] Implement grouping events by type. --- .../modules/agenda/AgendaFragmentDefault.kt | 34 ++++++++++-- .../modules/agenda/event/AgendaEventGroup.kt | 23 ++++++++ .../agenda/event/AgendaEventGroupRenderer.kt | 31 +++++++++++ app/src/main/res/layout/agenda_group_item.xml | 55 +++++++++++++++++++ .../main/res/layout/agenda_wrapped_group.xml | 18 ++++++ 5 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroupRenderer.kt create mode 100644 app/src/main/res/layout/agenda_group_item.xml create mode 100644 app/src/main/res/layout/agenda_wrapped_group.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt index 680eeb18..9aaf3d5a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt @@ -26,6 +26,8 @@ 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 @@ -111,6 +113,7 @@ class AgendaFragmentDefault( } }, AgendaEventRenderer(isCompactMode), + AgendaEventGroupRenderer(), LessonChangesEventRenderer(), TeacherAbsenceEventRenderer() ) @@ -135,10 +138,33 @@ class AgendaFragmentDefault( ) { events.removeAll { it is AgendaEvent } - events += eventList.map { - if (!it.seen) - unreadDates.add(it.date.value) - AgendaEvent(it) + 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, + typeName = event.typeName ?: "-", + typeColor = event.typeColor ?: event.eventColor, + eventCount = list.size + )) + } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt new file mode 100644 index 00000000..60e5f769 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt @@ -0,0 +1,23 @@ +/* + * 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 typeName: String, + val typeColor: Int, + val eventCount: Int +) : BaseEvent( + id = date.value.toLong(), + time = date.asCalendar, + color = typeColor, + showBadge = false +) { + override fun copy() = AgendaEventGroup(profileId, date, typeName, typeColor, eventCount) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroupRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroupRenderer.kt new file mode 100644 index 00000000..191f526c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroupRenderer.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-10. + */ + +package pl.szczodrzynski.edziennik.ui.modules.agenda.event + +import android.view.View +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() { + + override fun render(view: View, event: AgendaEventGroup) { + val b = AgendaWrappedGroupBinding.bind(view).item + + b.foreground.foreground.setTintColor(event.typeColor) + b.background.background.setTintColor(event.typeColor) + b.name.background.setTintColor(event.typeColor) + b.name.text = event.typeName + b.name.setTextColor(Colors.legibleTextColor(event.typeColor)) + b.count.text = event.eventCount.toString() + b.count.background.setTintColor(android.R.attr.colorBackground.resolveAttr(view.context)) + } + + override fun getEventLayout(): Int = R.layout.agenda_wrapped_group +} + diff --git a/app/src/main/res/layout/agenda_group_item.xml b/app/src/main/res/layout/agenda_group_item.xml new file mode 100644 index 00000000..23d3486d --- /dev/null +++ b/app/src/main/res/layout/agenda_group_item.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/agenda_wrapped_group.xml b/app/src/main/res/layout/agenda_wrapped_group.xml new file mode 100644 index 00000000..6b081d8e --- /dev/null +++ b/app/src/main/res/layout/agenda_wrapped_group.xml @@ -0,0 +1,18 @@ + + + + + + + From f5ceaa9afe612d8104d6a5fa04147cbf81a5df94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sun, 11 Apr 2021 22:08:33 +0200 Subject: [PATCH 07/34] [Agenda] Add unread badges to events and groups. --- .../ui/dialogs/event/EventDetailsDialog.kt | 4 + .../modules/agenda/AgendaFragmentDefault.kt | 117 ++++++++++++++++-- .../edziennik/ui/modules/agenda/BaseEvent.kt | 11 +- .../ui/modules/agenda/event/AgendaEvent.kt | 7 +- .../modules/agenda/event/AgendaEventGroup.kt | 7 +- .../agenda/event/AgendaEventGroupRenderer.kt | 12 +- .../agenda/event/AgendaEventRenderer.kt | 68 +++++++--- .../lessonchanges/LessonChangesEvent.kt | 10 +- .../LessonChangesEventRenderer.kt | 23 +++- .../teacherabsence/TeacherAbsenceEvent.kt | 4 +- .../TeacherAbsenceEventRenderer.kt | 19 ++- .../main/res/layout/agenda_counter_item.xml | 61 +++++++++ .../res/layout/agenda_event_compact_item.xml | 57 +++++++++ app/src/main/res/layout/agenda_event_item.xml | 66 ++++++---- app/src/main/res/layout/agenda_group_item.xml | 23 ++-- ...changes.xml => agenda_wrapped_counter.xml} | 6 +- ...e.xml => agenda_wrapped_event_compact.xml} | 6 +- 17 files changed, 407 insertions(+), 94 deletions(-) create mode 100644 app/src/main/res/layout/agenda_counter_item.xml create mode 100644 app/src/main/res/layout/agenda_event_compact_item.xml rename app/src/main/res/layout/{agenda_wrapped_lesson_changes.xml => agenda_wrapped_counter.xml} (83%) rename app/src/main/res/layout/{agenda_wrapped_teacher_absence.xml => agenda_wrapped_event_compact.xml} (83%) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt index cdd2e50a..0958188e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt @@ -92,6 +92,10 @@ class EventDetailsDialog( b.eventShared = eventShared b.eventOwn = eventOwn + if (!event.seen) { + app.eventManager.markAsSeen(event) + } + val bullet = " • " val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt index 9aaf3d5a..1aee0ab7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt @@ -5,6 +5,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 @@ -14,9 +16,7 @@ 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.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.data.db.full.EventFull @@ -40,16 +40,67 @@ 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() private val events = mutableListOf() 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() + + // TODO: 2021-04-11 find a way to attach the OnScrollListener automatically + // then set this to IDLE by default + // the FAB also needs the original listener, though + private var scrollState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL + private var updatePending = false + private var notifyPending = false + override fun onScrollStateChanged(view: AbsListView?, newScrollState: Int) { + scrollState = newScrollState + if (updatePending) updateData() + if (notifyPending) notifyDataSetChanged() + } + + /** + * 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 + } + + override fun onScroll( + view: AbsListView?, + firstVisibleItem: Int, + visibleItemCount: Int, + totalItemCount: Int + ) = Unit + suspend fun initView(fragment: AgendaFragment) { isInitialized = false @@ -95,9 +146,22 @@ class AgendaFragmentDefault( app.profileId, date ) + is AgendaEventGroup -> DayDialog(activity, app.profileId, date) 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) { @@ -105,6 +169,7 @@ class AgendaFragmentDefault( // Mark as read scrolled date if (selectedDate.value in unreadDates) { + setAsRead(calendar) activity.launch(Dispatchers.Default) { app.db.eventDao().setSeenByDate(app.profileId, selectedDate, true) } @@ -123,20 +188,45 @@ class AgendaFragmentDefault( } private fun updateView() { - val manager = CalendarManager.getInstance() manager.events.clear() manager.loadEvents(events, BaseCalendarEvent()) - val adapter = b.agendaDefaultView.agendaView.agendaListView.adapter as? AgendaAdapter adapter?.updateEvents(manager.events) - b.agendaDefaultView.agendaView.agendaListView.scrollToCurrentDate(selectedDate.asCalendar) + 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, eventList: List ) { - events.removeAll { it is AgendaEvent } + events.removeAll { it is AgendaEvent || it is AgendaEventGroup } if (!profileConfig.agendaGroupByType) { events += eventList.map { @@ -155,14 +245,14 @@ class AgendaFragmentDefault( if (!event.seen) unreadDates.add(event.date.value) events += AgendaEvent(event) - } - else { + } else { events.add(0, AgendaEventGroup( profileId = event.profileId, date = event.date, typeName = event.typeName ?: "-", typeColor = event.typeColor ?: event.eventColor, - eventCount = list.size + count = list.size, + showBadge = list.any { !it.seen } )) } } @@ -179,7 +269,8 @@ class AgendaFragmentDefault( LessonChangesEvent( app.profileId, date = date ?: return@mapNotNull null, - changeCount = changes.size + count = changes.size, + showBadge = changes.any { !it.seen } ) } } @@ -200,7 +291,7 @@ class AgendaFragmentDefault( events += TeacherAbsenceEvent( app.profileId, date = Date.fromValue(dateInt), - absenceCount = count + count = count ) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/BaseEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/BaseEvent.kt index f183485b..62569504 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/BaseEvent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/BaseEvent.kt @@ -13,7 +13,8 @@ open class BaseEvent( private val id: Long, private val time: Calendar, private val color: Int, - private val showBadge: Boolean + private var showBadge: Boolean, + var showItemBadge: Boolean = showBadge ) : CalendarEvent { override fun copy() = BaseEvent(id, time, color, showBadge) @@ -36,6 +37,12 @@ open class BaseEvent( 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 @@ -44,7 +51,6 @@ open class BaseEvent( override fun getLocation() = "" override fun getColor() = color override fun getTextColor() = 0 - override fun getShowBadge() = showBadge override fun isPlaceholder() = false override fun isAllDay() = false @@ -55,7 +61,6 @@ open class BaseEvent( override fun setDescription(value: String) = Unit override fun setLocation(value: String) = Unit override fun setTextColor(value: Int) = Unit - override fun setShowBadge(value: Boolean) = Unit override fun setPlaceholder(value: Boolean) = Unit override fun setAllDay(value: Boolean) = Unit } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEvent.kt index 3cd05727..e6ba6e23 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEvent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEvent.kt @@ -8,12 +8,13 @@ import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent class AgendaEvent( - val event: EventFull + val event: EventFull, + showBadge: Boolean = !event.seen ) : BaseEvent( id = event.id, time = event.startTimeCalendar, color = event.eventColor, - showBadge = !event.seen + showBadge = showBadge ) { - override fun copy() = AgendaEvent(event) + override fun copy() = AgendaEvent(event, showBadge) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt index 60e5f769..e50be2c3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt @@ -12,12 +12,13 @@ class AgendaEventGroup( val date: Date, val typeName: String, val typeColor: Int, - val eventCount: Int + val count: Int, + showBadge: Boolean ) : BaseEvent( id = date.value.toLong(), time = date.asCalendar, color = typeColor, - showBadge = false + showBadge = showBadge ) { - override fun copy() = AgendaEventGroup(profileId, date, typeName, typeColor, eventCount) + override fun copy() = AgendaEventGroup(profileId, date, typeName, typeColor, count, showBadge) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroupRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroupRenderer.kt index 191f526c..2a1f9af8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroupRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroupRenderer.kt @@ -5,6 +5,7 @@ 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 @@ -17,13 +18,14 @@ class AgendaEventGroupRenderer : EventRenderer() { override fun render(view: View, event: AgendaEventGroup) { val b = AgendaWrappedGroupBinding.bind(view).item - b.foreground.foreground.setTintColor(event.typeColor) - b.background.background.setTintColor(event.typeColor) - b.name.background.setTintColor(event.typeColor) + b.card.foreground.setTintColor(event.color) + b.card.background.setTintColor(event.color) b.name.text = event.typeName - b.name.setTextColor(Colors.legibleTextColor(event.typeColor)) - b.count.text = event.eventCount.toString() + 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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt index 6e78776a..dafdb853 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt @@ -6,36 +6,72 @@ package pl.szczodrzynski.edziennik.ui.modules.agenda.event import android.annotation.SuppressLint 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.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 class AgendaEventRenderer( - private val isCompact: Boolean + val isCompact: Boolean ) : EventRenderer() { @SuppressLint("SetTextI18n") override fun render(view: View, aEvent: AgendaEvent) { - val b = AgendaWrappedEventBinding.bind(view).item val event = aEvent.event - b.isCompact = isCompact + val timeText = if (event.time == null) + view.context.getString(R.string.agenda_event_all_day) + else + event.time!!.stringHM - b.card.setCardBackgroundColor(event.eventColor) - b.eventTitle.setTextColor(Colors.legibleTextColor(event.eventColor)) - b.eventSubtitle.setTextColor(Colors.legibleTextColor(event.eventColor)) + val eventTitle = "${event.typeName ?: "wydarzenie"} - ${event.topic}" - b.eventTitle.text = "${event.typeName ?: "wydarzenie"} - ${event.topic}" - b.eventSubtitle.text = - (if (event.time == null) - view.context.getString(R.string.agenda_event_all_day) - else - event.time!!.stringHM) + - (event.subjectLongName?.let { ", $it" } ?: "") + - (event.teacherName?.let { ", $it" } ?: "") + - (event.teamName?.let { ", $it" } ?: "") + val eventSubtitle = listOfNotNull( + timeText, + event.subjectLongName, + event.teacherName, + event.teamName + ).join(", ") + + if (isCompact) { + val b = AgendaWrappedEventCompactBinding.bind(view).item + + b.card.foreground.setTintColor(event.eventColor) + b.card.background.setTintColor(event.eventColor) + b.title.text = eventTitle + b.title.setTextColor(Colors.legibleTextColor(event.eventColor)) + + b.badgeBackground.isVisible = aEvent.showItemBadge + b.badgeBackground.background.setTintColor( + android.R.attr.colorBackground.resolveAttr(view.context) + ) + b.badge.isVisible = aEvent.showItemBadge + } + else { + val b = AgendaWrappedEventBinding.bind(view).item + + b.card.foreground.setTintColor(event.eventColor) + b.card.background.setTintColor(event.eventColor) + b.title.text = eventTitle + b.title.setTextColor(Colors.legibleTextColor(event.eventColor)) + b.subtitle.text = eventSubtitle + b.subtitle.setTextColor(Colors.legibleTextColor(event.eventColor)) + + b.badgeBackground.isVisible = aEvent.showItemBadge + b.badgeBackground.background.setTintColor( + android.R.attr.colorBackground.resolveAttr(view.context) + ) + b.badge.isVisible = aEvent.showItemBadge + } } - override fun getEventLayout(): Int = R.layout.agenda_wrapped_event + override fun getEventLayout() = if (isCompact) + R.layout.agenda_wrapped_event_compact + else + R.layout.agenda_wrapped_event } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEvent.kt index e65c82bd..b38d8829 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEvent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEvent.kt @@ -10,12 +10,16 @@ import pl.szczodrzynski.edziennik.utils.models.Date class LessonChangesEvent( val profileId: Int, val date: Date, - val changeCount: Int + val count: Int, + showBadge: Boolean ) : BaseEvent( id = date.value.toLong(), time = date.asCalendar, color = 0xff78909c.toInt(), - showBadge = false + showBadge = false, + showItemBadge = showBadge ) { - override fun copy() = LessonChangesEvent(profileId, date, changeCount) + override fun copy() = LessonChangesEvent(profileId, date, count, showItemBadge) + + override fun getShowBadge() = false } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt index 5ffc788c..8b8ff81b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt @@ -5,17 +5,32 @@ 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.AgendaWrappedLessonChangesBinding +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() { override fun render(view: View, event: LessonChangesEvent) { - val b = AgendaWrappedLessonChangesBinding.bind(view).item + val b = AgendaWrappedCounterBinding.bind(view).item - b.lessonChangeCount.text = event.changeCount.toString() + b.card.foreground.setTintColor(event.color) + b.card.background.setTintColor(event.color) + b.name.setText(R.string.agenda_lesson_changes) + b.name.setTextColor(Colors.legibleTextColor(event.color)) + b.count.text = event.count.toString() + b.count.setTextColor(b.name.currentTextColor) + + b.badgeBackground.isVisible = event.showItemBadge + b.badgeBackground.background.setTintColor( + android.R.attr.colorBackground.resolveAttr(view.context) + ) + b.badge.isVisible = event.showItemBadge } - override fun getEventLayout(): Int = R.layout.agenda_wrapped_lesson_changes + override fun getEventLayout(): Int = R.layout.agenda_wrapped_counter } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt index 4208814b..b0b01f50 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt @@ -10,12 +10,12 @@ import pl.szczodrzynski.edziennik.utils.models.Date class TeacherAbsenceEvent( val profileId: Int, val date: Date, - val absenceCount: Int + val count: Int ) : BaseEvent( id = date.value.toLong(), time = date.asCalendar, color = 0xffff1744.toInt(), showBadge = false ) { - override fun copy() = TeacherAbsenceEvent(profileId, date, absenceCount) + override fun copy() = TeacherAbsenceEvent(profileId, date, count) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt index 756c60c9..ec0a3915 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt @@ -5,17 +5,28 @@ package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence 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.AgendaWrappedTeacherAbsenceBinding +import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding +import pl.szczodrzynski.edziennik.setTintColor +import pl.szczodrzynski.edziennik.utils.Colors class TeacherAbsenceEventRenderer : EventRenderer() { override fun render(view: View, event: TeacherAbsenceEvent) { - val b = AgendaWrappedTeacherAbsenceBinding.bind(view).item + val b = AgendaWrappedCounterBinding.bind(view).item - b.teacherAbsenceCount.text = event.absenceCount.toString() + b.card.foreground.setTintColor(event.color) + b.card.background.setTintColor(event.color) + b.name.setText(R.string.agenda_teacher_absence) + b.name.setTextColor(Colors.legibleTextColor(event.color)) + b.count.text = event.count.toString() + b.count.setTextColor(b.name.currentTextColor) + + b.badgeBackground.isVisible = false + b.badge.isVisible = false } - override fun getEventLayout(): Int = R.layout.agenda_wrapped_teacher_absence + override fun getEventLayout(): Int = R.layout.agenda_wrapped_counter } diff --git a/app/src/main/res/layout/agenda_counter_item.xml b/app/src/main/res/layout/agenda_counter_item.xml new file mode 100644 index 00000000..334c419c --- /dev/null +++ b/app/src/main/res/layout/agenda_counter_item.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/agenda_event_compact_item.xml b/app/src/main/res/layout/agenda_event_compact_item.xml new file mode 100644 index 00000000..df48e70c --- /dev/null +++ b/app/src/main/res/layout/agenda_event_compact_item.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/agenda_event_item.xml b/app/src/main/res/layout/agenda_event_item.xml index c70c4a95..3ede7682 100644 --- a/app/src/main/res/layout/agenda_event_item.xml +++ b/app/src/main/res/layout/agenda_event_item.xml @@ -3,50 +3,64 @@ ~ Copyright (c) Kuba Szczodrzyński 2021-4-8. --> - + - - - - - - - - + android:orientation="horizontal"> + tools:text="sprawdzian - Język polski" + tools:textColor="@color/md_white_1000" /> + tools:text="9:05, biologia, Jan Kowalski, 7a" + tools:textColor="@color/md_white_1000" /> - - + + + + + + diff --git a/app/src/main/res/layout/agenda_group_item.xml b/app/src/main/res/layout/agenda_group_item.xml index 23d3486d..4787a8cf 100644 --- a/app/src/main/res/layout/agenda_group_item.xml +++ b/app/src/main/res/layout/agenda_group_item.xml @@ -5,33 +5,29 @@ + android:orientation="horizontal"> @@ -43,7 +39,6 @@ android:layout_marginRight="-1dp" android:background="@drawable/bg_rounded_8dp" android:gravity="center" - android:paddingVertical="10dp" android:paddingStart="16dp" android:paddingLeft="16dp" android:paddingEnd="18dp" @@ -52,4 +47,12 @@ tools:backgroundTint="?android:colorBackground" tools:text="3" /> + + diff --git a/app/src/main/res/layout/agenda_wrapped_lesson_changes.xml b/app/src/main/res/layout/agenda_wrapped_counter.xml similarity index 83% rename from app/src/main/res/layout/agenda_wrapped_lesson_changes.xml rename to app/src/main/res/layout/agenda_wrapped_counter.xml index 834d2ddd..31864636 100644 --- a/app/src/main/res/layout/agenda_wrapped_lesson_changes.xml +++ b/app/src/main/res/layout/agenda_wrapped_counter.xml @@ -1,4 +1,8 @@ + + diff --git a/app/src/main/res/layout/agenda_wrapped_teacher_absence.xml b/app/src/main/res/layout/agenda_wrapped_event_compact.xml similarity index 83% rename from app/src/main/res/layout/agenda_wrapped_teacher_absence.xml rename to app/src/main/res/layout/agenda_wrapped_event_compact.xml index 4793b0d8..997c24db 100644 --- a/app/src/main/res/layout/agenda_wrapped_teacher_absence.xml +++ b/app/src/main/res/layout/agenda_wrapped_event_compact.xml @@ -1,4 +1,8 @@ + + From 12619f6bde218e251745438b0a474223f0bda656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 12 Apr 2021 11:50:54 +0200 Subject: [PATCH 08/34] [Agenda] Update scroll listeners code. --- app/build.gradle | 2 +- .../modules/agenda/AgendaFragmentDefault.kt | 27 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 72c03e94..26ac63c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -152,7 +152,7 @@ dependencies { implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2" // Szkolny.eu libraries/forks - implementation "eu.szkolny:agendacalendarview:1799f8ef47" + implementation "eu.szkolny:agendacalendarview:5431f03098" implementation "eu.szkolny:cafebar:5bf0c618de" implementation "eu.szkolny.fslogin:lib:2.0.0" implementation "eu.szkolny:material-about-library:1d5ebaf47c" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt index 1aee0ab7..eb6f74fb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt @@ -59,18 +59,28 @@ class AgendaFragmentDefault( private val manager get() = CalendarManager.getInstance() - // TODO: 2021-04-11 find a way to attach the OnScrollListener automatically - // then set this to IDLE by default - // the FAB also needs the original listener, though - private var scrollState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL + 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. @@ -94,13 +104,6 @@ class AgendaFragmentDefault( } else notifyPending = true } - override fun onScroll( - view: AbsListView?, - firstVisibleItem: Int, - visibleItemCount: Int, - totalItemCount: Int - ) = Unit - suspend fun initView(fragment: AgendaFragment) { isInitialized = false @@ -183,6 +186,8 @@ class AgendaFragmentDefault( TeacherAbsenceEventRenderer() ) + listView.setOnScrollListener(this) + isInitialized = true b.progressBar.isVisible = false } From 3eb09033bf6236e0e447e4ec7e5d515a51fdce17 Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Mon, 12 Apr 2021 12:26:52 +0200 Subject: [PATCH 09/34] [Strings] Update copyright date (#26) * Update copyright date * Update copyright in other languages --- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-en/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9949f2e2..3dd3dcfc 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -856,7 +856,7 @@ Open-Source-Lizenzen Datenschutzrichtlinie E-Klassenbuch - © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - Februar 2021 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - April 2021 Klicken Sie hier, um nach Aktualisierungen zu suchen Aktualisierung Version diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 64b56cdb..86911c58 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -858,7 +858,7 @@ Open-source licenses Privacy policy E-register - © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - February 2021 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - April 2021 Click to check for updates Update Version diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e5f21e05..3b0ee64a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -921,7 +921,7 @@ Licencje open-source Polityka prywatności E-dziennik - © Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - luty 2021 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - kwiecień 2021 Kliknij, aby sprawdzić aktualizacje Aktualizacja Wersja From 8b1529f2404d65027b02b711528fc6806d3d02c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 12 Apr 2021 13:13:10 +0200 Subject: [PATCH 10/34] [App] Make registerAvailability flavor-aware. --- .../edziennik/config/ConfigSync.kt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt index 068400a1..387e5cf7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt @@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.config import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import pl.szczodrzynski.edziennik.BuildConfig import pl.szczodrzynski.edziennik.config.utils.get import pl.szczodrzynski.edziennik.config.utils.getIntList import pl.szczodrzynski.edziennik.config.utils.set @@ -123,6 +124,19 @@ class ConfigSync(private val config: Config) { private var mRegisterAvailability: Map? = null var registerAvailability: Map - get() { mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it -> gson.fromJson>(it, object: TypeToken>(){}.type) }; return mRegisterAvailability ?: mapOf() } - set(value) { config.setMap("registerAvailability", value); mRegisterAvailability = value } + get() { + val flavor = config.values.get("registerAvailabilityFlavor", null as String?) + if (BuildConfig.FLAVOR != flavor) + return mapOf() + + mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it -> + gson.fromJson(it, object: TypeToken>(){}.type) + } + return mRegisterAvailability ?: mapOf() + } + set(value) { + config.setMap("registerAvailability", value) + config.set("registerAvailabilityFlavor", BuildConfig.FLAVOR) + mRegisterAvailability = value + } } From 4647da780390b4b421b1a7c2530e98b51ae2c5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 12 Apr 2021 14:27:51 +0200 Subject: [PATCH 11/34] [IDE] Fix XML tags reordering when formatting. --- .idea/codeStyles/Project.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 29204aff..7643783a 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -15,6 +15,7 @@ xmlns:android + ^$ @@ -25,6 +26,7 @@ xmlns:.* + ^$ @@ -36,6 +38,7 @@ .*:id + http://schemas.android.com/apk/res/android @@ -46,6 +49,7 @@ .*:name + http://schemas.android.com/apk/res/android @@ -56,6 +60,7 @@ name + ^$ @@ -66,6 +71,7 @@ style + ^$ @@ -76,6 +82,7 @@ .* + ^$ @@ -87,6 +94,7 @@ .* + http://schemas.android.com/apk/res/android @@ -98,6 +106,7 @@ .* + .* From ccf0bdaf0554daa8afcc9b9e89a654e743e8eb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 12 Apr 2021 14:34:51 +0200 Subject: [PATCH 12/34] [4.7.1] Update build.gradle, signing and changelog. --- app/src/main/assets/pl-changelog.html | 12 +++--------- app/src/main/cpp/szkolny-signing.cpp | 2 +- .../data/api/szkolny/interceptor/Signing.kt | 2 +- build.gradle | 4 ++-- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index 2219ad8c..db18101b 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,13 +1,7 @@ -

Wersja 4.7, 2021-04-07

+

Wersja 4.7.1, 2021-04-12

    -
  • Szkolny.eu jest teraz open source! Zapraszamy na stronę https://szkolny.eu/ po więcej ważnych informacji.
  • -
  • Poprawiono wybieranie obrazków (tła nagłówka, tła aplikacji oraz profilu) z dowolnego źródła.
  • -
  • Ukończono tłumaczenie na język angielski. @MarcinK50
  • -
  • Dodano ekran informacji o kompilacji w Ustawieniach.
  • -
  • Zaktualizowano ekran licencji open source.
  • -
  • Naprawiono zatrzymanie aplikacji na Androidzie 4.4 i starszych.
  • -
  • Naprawiono problemy z połączeniem internetowym na Androidzie 4.4 i starszych.
  • -
  • Zoptymalizowano wielkość aplikacji.
  • +
  • Poprawiono sprawdzanie dostępności e-dziennika.
  • +
  • Zmieniono datę w informacjach o aplikacji. @Luncenok


diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp index f2b1cf8a..e2311acc 100644 --- a/app/src/main/cpp/szkolny-signing.cpp +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -9,7 +9,7 @@ /*secret password - removed for source code publication*/ static toys AES_IV[16] = { - 0xda, 0x9f, 0xd4, 0x2b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + 0xcc, 0x64, 0xdb, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt index 819194d8..d07afeb7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt @@ -46,6 +46,6 @@ object Signing { /*fun provideKey(param1: String, param2: Long): ByteArray {*/ fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { - return "$param1.MTIzNDU2Nzg5MDLPrcQX7M===.$param2".sha256() + return "$param1.MTIzNDU2Nzg5MDXHhAtZBW===.$param2".sha256() } } diff --git a/build.gradle b/build.gradle index cc8c8471..2e05ee75 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { kotlin_version = '1.4.31' release = [ - versionName: "4.7", - versionCode: 4070099 + versionName: "4.7.1", + versionCode: 4070199 ] setup = [ From 634ef16bc5cd2c1c1ac362ffdd598f565d46876d Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Mon, 12 Apr 2021 17:17:52 +0200 Subject: [PATCH 13/34] [UI] Add notification icons. (#23) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add notification icons * Change color of icons * [Notifications] Update icon colors. * [Notifications] Update lucky number icon. * Add icons to Notifications List Fragment * Update notifications_list_item.xml * Move the gravity to the LinearLayout * add paddingLeft * Change IconicsImageView to View. * Rearrange XML attributes. Co-authored-by: Kuba Szczodrzyński --- .../data/api/task/PostNotifications.kt | 14 ++++++++++ .../edziennik/data/db/entity/Notification.kt | 17 ++++++++++++ .../notifications/NotificationsAdapter.kt | 6 +++++ .../res/layout/notifications_list_item.xml | 26 +++++++++++++++---- 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt index 843f2174..28e62d59 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt @@ -8,6 +8,9 @@ import android.util.SparseIntArray import androidx.core.app.NotificationCompat import androidx.core.util.forEach import androidx.core.util.set +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial +import com.mikepenz.iconics.utils.* import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE import pl.szczodrzynski.edziennik.utils.models.Time @@ -107,6 +110,10 @@ class PostNotifications(val app: App, nList: List) { .setContentText(buildSummaryText(summaryCounts)) .setTicker(newNotificationsText) .setSmallIcon(R.drawable.ic_notification) + .setLargeIcon(IconicsDrawable(app).apply { + icon = CommunityMaterial.Icon.cmd_bell_ring_outline + colorRes = R.color.colorPrimary + }.toBitmap()) .setStyle(NotificationCompat.InboxStyle() .also { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { @@ -137,6 +144,9 @@ class PostNotifications(val app: App, nList: List) { .setSubText(if (it.type == TYPE_SERVER_MESSAGE) null else it.title) .setTicker("${it.profileName}: ${it.title}") .setSmallIcon(R.drawable.ic_notification) + .setLargeIcon(IconicsDrawable(app, it.getLargeIcon()).apply { + colorRes = R.color.colorPrimary + }.toBitmap()) .setStyle(NotificationCompat.BigTextStyle() .bigText(it.text)) .setWhen(it.addedDate) @@ -160,6 +170,10 @@ class PostNotifications(val app: App, nList: List) { .setContentText(buildSummaryText(summaryCounts)) .setTicker(newNotificationsText) .setSmallIcon(R.drawable.ic_notification) + .setLargeIcon(IconicsDrawable(app).apply { + icon = CommunityMaterial.Icon.cmd_bell_ring_outline + colorRes = R.color.colorPrimary + }.toBitmap()) .addDefaults() .setGroupSummary(true) .setContentIntent(summaryIntent) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt index 1e8b8e0e..6a2a787b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt @@ -10,6 +10,8 @@ import android.content.Intent import androidx.room.Entity import androidx.room.PrimaryKey import com.google.gson.JsonObject +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import pl.szczodrzynski.edziennik.MainActivity @Entity(tableName = "notifications") @@ -96,4 +98,19 @@ data class Notification( fillIntent(intent) return PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_ONE_SHOT) } + + fun getLargeIcon(): IIcon = when (type) { + TYPE_TIMETABLE_LESSON_CHANGE -> CommunityMaterial.Icon3.cmd_timetable + TYPE_NEW_GRADE -> CommunityMaterial.Icon3.cmd_numeric_5_box_outline + TYPE_NEW_EVENT -> CommunityMaterial.Icon.cmd_calendar_outline + TYPE_NEW_HOMEWORK -> CommunityMaterial.Icon3.cmd_notebook_outline + TYPE_NEW_SHARED_EVENT -> CommunityMaterial.Icon.cmd_calendar_outline + TYPE_NEW_SHARED_HOMEWORK -> CommunityMaterial.Icon3.cmd_notebook_outline + TYPE_NEW_MESSAGE -> CommunityMaterial.Icon.cmd_email_outline + TYPE_NEW_NOTICE -> CommunityMaterial.Icon.cmd_emoticon_outline + TYPE_NEW_ATTENDANCE -> CommunityMaterial.Icon.cmd_calendar_remove_outline + TYPE_LUCKY_NUMBER -> CommunityMaterial.Icon.cmd_emoticon_excited_outline + TYPE_NEW_ANNOUNCEMENT -> CommunityMaterial.Icon.cmd_bullhorn_outline + else -> CommunityMaterial.Icon.cmd_bell_ring_outline + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsAdapter.kt index 8df33a8c..6c0fe067 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsAdapter.kt @@ -4,6 +4,8 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.RecyclerView +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.colorRes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -43,6 +45,10 @@ class NotificationsAdapter( val date = Date.fromMillis(item.addedDate).formattedString val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) + b.notificationIcon.background = IconicsDrawable(app, item.getLargeIcon()).apply { + colorRes = R.color.colorPrimary + } + b.title.text = item.text b.profileDate.text = listOf( item.profileName ?: "", diff --git a/app/src/main/res/layout/notifications_list_item.xml b/app/src/main/res/layout/notifications_list_item.xml index 498f7e21..13df88d1 100644 --- a/app/src/main/res/layout/notifications_list_item.xml +++ b/app/src/main/res/layout/notifications_list_item.xml @@ -9,12 +9,28 @@ android:orientation="vertical" android:padding="8dp"> - + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + + Date: Mon, 12 Apr 2021 17:18:53 +0200 Subject: [PATCH 14/34] [UI] Add eggfall. (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add eggfall * Add egg.png * Add more eggs * [Gradle] Update android-snowfall to a fork. * [Eggfall] Add randomizing egg images. * [UI] Restore snowfall icon. Add separate eggfall setting. * [Eggfall] Limit eggfall to near-easter date only. Co-authored-by: GitHub Co-authored-by: Kuba Szczodrzyński --- app/build.gradle | 2 +- .../szczodrzynski/edziennik/MainActivity.kt | 20 +++-- .../edziennik/config/ConfigUI.kt | 5 ++ .../settings/cards/SettingsThemeCard.kt | 13 ++++ .../edziennik/utils/BigNightUtil.kt | 69 ++++++++++++++++++ app/src/main/res/drawable/egg1.webp | Bin 0 -> 6320 bytes app/src/main/res/drawable/egg2.webp | Bin 0 -> 5664 bytes app/src/main/res/drawable/egg3.webp | Bin 0 -> 5982 bytes app/src/main/res/drawable/egg4.webp | Bin 0 -> 7238 bytes app/src/main/res/drawable/egg5.webp | Bin 0 -> 7806 bytes app/src/main/res/drawable/egg6.webp | Bin 0 -> 2582 bytes app/src/main/res/layout/eggfall.xml | 14 ++++ app/src/main/res/values-de/strings.xml | 2 + app/src/main/res/values-en/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 15 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/utils/BigNightUtil.kt create mode 100644 app/src/main/res/drawable/egg1.webp create mode 100644 app/src/main/res/drawable/egg2.webp create mode 100644 app/src/main/res/drawable/egg3.webp create mode 100644 app/src/main/res/drawable/egg4.webp create mode 100644 app/src/main/res/drawable/egg5.webp create mode 100644 app/src/main/res/drawable/egg6.webp create mode 100644 app/src/main/res/layout/eggfall.xml diff --git a/app/build.gradle b/app/build.gradle index 72c03e94..838083cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -152,6 +152,7 @@ dependencies { implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2" // Szkolny.eu libraries/forks + implementation "eu.szkolny:android-snowfall:1ca9ea2da3" implementation "eu.szkolny:agendacalendarview:1799f8ef47" implementation "eu.szkolny:cafebar:5bf0c618de" implementation "eu.szkolny.fslogin:lib:2.0.0" @@ -180,7 +181,6 @@ dependencies { implementation "com.github.bassaer:chatmessageview:2.0.1" implementation "com.github.CanHub:Android-Image-Cropper:2.2.2" implementation "com.github.ChuckerTeam.Chucker:library:3.0.1" - implementation "com.github.jetradarmobile:android-snowfall:1.2.0" implementation "com.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31" implementation("com.heinrichreimersoftware:material-intro") { version { strictly "1.5.8" } } implementation "com.hypertrack:hyperlog:0.0.10" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 8dd83bf2..2cd4f7b7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.Observer import androidx.navigation.NavOptions import com.danimahardhika.cafebar.CafeBar import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.jetradarmobile.snowfall.SnowfallView import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.utils.colorInt @@ -81,12 +82,9 @@ import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsFragment import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment -import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch -import pl.szczodrzynski.edziennik.utils.Themes -import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.* import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.dpToPx -import pl.szczodrzynski.edziennik.utils.appManagerIntentList import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.NavTarget import pl.szczodrzynski.navlib.* @@ -470,9 +468,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope { // IT'S WINTER MY DUDES val today = Date.getToday() - if ((today.month == 12 || today.month == 1) && app.config.ui.snowfall) { + if ((today.month % 11 == 1) && app.config.ui.snowfall) { b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false)) } + else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) { + val eggfall = layoutInflater.inflate(R.layout.eggfall, b.rootFrame, false) as SnowfallView + eggfall.setSnowflakeBitmaps(listOf( + BitmapFactory.decodeResource(resources, R.drawable.egg1), + BitmapFactory.decodeResource(resources, R.drawable.egg2), + BitmapFactory.decodeResource(resources, R.drawable.egg3), + BitmapFactory.decodeResource(resources, R.drawable.egg4), + BitmapFactory.decodeResource(resources, R.drawable.egg5), + BitmapFactory.decodeResource(resources, R.drawable.egg6) + )) + b.rootFrame.addView(eggfall) + } // WHAT'S NEW DIALOG if (app.config.appVersion < BuildConfig.VERSION_CODE) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt index 7b39383e..f36652a1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt @@ -49,6 +49,11 @@ class ConfigUI(private val config: Config) { get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false } set(value) { config.set("snowfall", value); mSnowfall = value } + private var mEggfall: Boolean? = null + var eggfall: Boolean + get() { mEggfall = mEggfall ?: config.values.get("eggfall", false); return mEggfall ?: false } + set(value) { config.set("eggfall", value); mEggfall = value } + private var mBottomSheetOpened: Boolean? = null var bottomSheetOpened: Boolean get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsThemeCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsThemeCard.kt index 17b108e5..a009279d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsThemeCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsThemeCard.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.settings.MiniMenuConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.ThemeChooserDialog import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsCard import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsUtil +import pl.szczodrzynski.edziennik.utils.BigNightUtil import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.models.Date @@ -36,6 +37,18 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) { } else null, + if (BigNightUtil().isDataWielkanocyNearDzisiaj()) // cool klasa for utility to dzień wielkanocy + util.createPropertyItem( + text = R.string.settings_theme_eggfall_text, + subText = R.string.settings_theme_eggfall_subtext, + icon = CommunityMaterial.Icon.cmd_egg_easter, + value = configGlobal.ui.eggfall + ) { _, it -> + configGlobal.ui.eggfall = it + activity.recreate() + } + else null, + util.createActionItem( text = R.string.settings_theme_theme_text, subText = Themes.getThemeNameRes(), diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/BigNightUtil.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/BigNightUtil.kt new file mode 100644 index 00000000..c7f57ccc --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/BigNightUtil.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-8. + */ + +package pl.szczodrzynski.edziennik.utils + +import pl.szczodrzynski.edziennik.utils.models.Date +import kotlin.math.absoluteValue + +// Obliczanie daty wielkanocy - algorytm Gaussa +// www.algorytm.org +// (c) 2008 by Tomasz Lubinski +// http://www.algorytm.org/przetwarzanie-dat/wyznaczanie-daty-wielkanocy-algortym-gaussa/dwg-j.html + +class BigNightUtil { + + /* Pobierz wartosc A z tabeli lat */ + private fun getA(rok: Int) = when { + rok <= 1582 -> 15 + rok <= 1699 -> 22 + rok <= 1899 -> 23 + rok <= 2199 -> 24 + rok <= 2299 -> 25 + rok <= 2399 -> 26 + rok <= 2499 -> 25 + else -> 0 + } + + /* Pobierz wartosc B z tabeli lat */ + private fun getB(rok: Int) = when { + rok <= 1582 -> 6 + rok <= 1699 -> 2 + rok <= 1799 -> 3 + rok <= 1899 -> 4 + rok <= 2099 -> 5 + rok <= 2199 -> 6 + rok <= 2299 -> 0 + rok <= 2499 -> 1 + else -> 0 + } + + /* oblicz ile dni po 22 marca przypada wielkanoc */ + private fun Oblicz_Date_wielkanocy(rok: Int): Int { + val a = rok % 19 + val b = rok % 4 + val c = rok % 7 + var d = (a * 19 + getA(rok)) % 30 + val e = (2 * b + 4 * c + 6 * d + getB(rok)) % 7 + if (d == 29 && e == 6 || d == 28 && e == 6) { + d -= 7 + } + return d + e + } + + private fun get_dataOf_bigNight(): Date { + val date = Date.getToday() + date.month = 4 + date.day = 22 + Oblicz_Date_wielkanocy(date.year) + if (date.day > 31) + date.day = date.day % 31 + else + date.month = 3 + + return date + } + + fun isDataWielkanocyNearDzisiaj() = + Date.diffDays(Date.getToday(), get_dataOf_bigNight()).absoluteValue < 7 +} diff --git a/app/src/main/res/drawable/egg1.webp b/app/src/main/res/drawable/egg1.webp new file mode 100644 index 0000000000000000000000000000000000000000..94440758e1a18ebcfeb98c0631e04a70e5517aec GIT binary patch literal 6320 zcmV;h7*FR?Nk&Gf7ytlQMM6+kP&il$0000G0001=005N$06|PpNDu@700G~lplutb z%EBM^9)gI-@Fu&WmtGdqL)xvqX(P#&(qUQ}W@cvQnPq0)9p)WM>z}{WTCW$sMD%|G z=s($__L*A`AFNNdR;nQ}iF-@oxIapR>+XX*3d-{T&7cVCTRH|oQIDjU|05?m+x|dReIz+-N+AA#fkHF zt1w8VC(h%oyZ~K0c3yAf1gGG@dHya6OxA|;{)q{Sed!dg24&5u90*9i(;3$UqcT?F zjtT;ibKumzwLvh?oZ^Kbj5^J!h5(das&a?9@OaZHe*|yAsqX|=k<@pR2hDCcfDfTf zI*2X6iZ3wiN4TCkkaMt_9LmcQP<9)Jdje|M0j-6Ub&TYLK{V^2wgbvN!?JDgEIP2= zaB|PlY+E$*4sIuytW!K2V8$KbMkw|G(Mu>D4)S#*Z!z5jQtB|DXW>`}+hP1uMcT3wDzIVzB9sW~g^#O3Xy81&U{u$O5G?8X7=k;x_CoMf*GULo>ADEPdtGq|KKv|P} z68`m&_ERInf3JO|7H9#

P2`giN`Lu*woiqW{>Y<U*WHLR8F?d= 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 mwZx-yr#SM1B2C7IQ* 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 QhXJgErt!G|FZ!SW01OyDFaQ7m literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/egg6.webp b/app/src/main/res/drawable/egg6.webp new file mode 100644 index 0000000000000000000000000000000000000000..9dd98d89636e0cb691e206e5e98f2f6210100da1 GIT binary patch literal 2582 zcmV+x3hDJyNk&Ev3IG6CMM6+kP&il$0000G0001y005N$06|PpNWKC900GCNpluuJ z2@tXXq{Y)P_B+nU=rwr$(CZQIyhd;EXI)f%<#+l?F1{|PYv z5Y4XX>>KE5F7^E-5=|)81dzh`-(jccUvhb(qoFF?8rB(%3m#BU6Qe{T<$UN1) zD7MO&8btMti^%h=OS*}MMER$mC-|*Rrg&yVeJo59is_SCni&C%hgkd(0-0hN7Q`V=yZuRu)7t$-bdH1F6QS zj1p)H83TQ|HN+U|WoW?t7b*% zj6>!h+U2X?Wt_9})DGVhe&ejA8;L*k(C;(O8|Xes{kFG-a^uwgriJUoFTL#!78!?k z?rhADc2oq-e~JTEP&gne2LJ$YDgd1UDv|(}06tMBkVPY+A(RU=*f<3Q zvA1roy9#)Vy6=GIyA%Js*bbe(Bi%kcz<7XrTJ!+@fa@~!0R2_;!TqTCA^pYu&i0^- zQF#NrZds;!YJN}N>-(APl2+$Ydt`a!JM?Tp@T*%Lr2vvTby>Ka(%y8Rqrlj^kR{8{ z*+Pb<8Legzq_nP=fK#h6D*LCqmmTr!L{SaAnYiRS-3@pS(iU{zl&{`tm0o!k#ZXd_ z6?fMw74F!DmrJKL9&S$$8*5saolyHm{U#5KT)|E;VzMk%gO$lpP)z*A7OtEt4?60g!QllpQXXdpS$FMtPyUe z)hzjM($%OH7I>yepCzsAr zCW?%wDH8PiIYEE`{{6J+6{^>uuNd4^4xMY|)R5}QqL=k>{~2w^5EoN{0+A5g$|$l* z(EdOa4$mXq+z_Y3?)X|-q#b4Qz|a3ogZQFRtGa)5q7!9u3S}Om#Rj;&0h-pU}bLqYl|VysKrG zFwT1xiH!dJbH@-OlilMaT+CGg@#ml=8P(+fzAu-u`b18Ihlk23nEw#yeztVp^g2F) zCjD_ERu}BLH<|hhv`Av!Z}*|#N1Pr*mAzhgq{r=ol&PTKIKyu!yE+#NhL#kzFz||f zOf$MK&&DRg%;fZ=RZTQOpf@0T!LFmc?T7Ij4CJ&VOT7unQk}cXCn=qkI=9PC8<17( z{#|**N|gQn9)RI;ZNfLARCT8%r1;VXPAk10-0@I2?I~Vn=VxOHL-(T>BJOqp=N^XW$v##;AZ~1y69}$P43-3)BR_A)bY1JXeY zD2pBQps8uF@dnrKYiLfF1CnuPULK+<%%yJ>cv;nLN5#!-6Oph1uwBlLr{g$ctu8r6Amr&wTqoeUS zNU4MRiV1~O3nn6<*&1tW^98>fy_@N|GSIVu1y2h`pK??%=LVeqMX>vLtFSqit<@j< zf}03duZE=e`jiOdQq8i+8Z~PRdpb0kr}bmA zh#UBOZh?#+U26ZW+LH6S+<()#ctHGDwC7)Fe(|uh`{UY;jZL s%_3PL?_^)MD7_8u(Bk^u&Pyhe`00000011o_h5!Hn literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/eggfall.xml b/app/src/main/res/layout/eggfall.xml new file mode 100644 index 00000000..fbc6a0fc --- /dev/null +++ b/app/src/main/res/layout/eggfall.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3dd3dcfc..3af14e8f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -967,6 +967,8 @@ Öffnen Sie das Menü mit der Zurück-Taste Schlittenfahrt im Schnee Jingle Bells, Jingle Bells + Brrrr + Bajo jajo, bajo jajo Rosa System Thema diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 86911c58..4c0c5b89 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -969,6 +969,8 @@ Back button opens drawer Jingle all the way Jingle bells, Jingle bells + Brrrr + Bajo jajo, bajo jajo Pink System Theme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b0ee64a..a008fc94 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1034,6 +1034,8 @@ Otwieraj menu przyciskiem wstecz Dzwonią dzwonki sań Pada śnieg, pada śnieg + Brrrr + Bajo jajo, bajo jajo Różowy Systemowy Motyw From 5eaa754401277f956b52a9cc3b555c48b943ee03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 13 Apr 2021 20:50:51 +0200 Subject: [PATCH 15/34] [UI] Add icons for done and manual events. --- .../agenda/event/AgendaEventRenderer.kt | 81 ++++++++++++------- .../LessonChangesEventRenderer.kt | 5 +- .../TeacherAbsenceEventRenderer.kt | 5 +- .../res/layout/agenda_event_compact_item.xml | 2 +- app/src/main/res/layout/agenda_event_item.xml | 2 +- 5 files changed, 60 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt index dafdb853..ac65fcef 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt @@ -6,8 +6,15 @@ 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.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 pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventBinding import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventCompactBinding @@ -22,14 +29,33 @@ class AgendaEventRenderer( @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) - view.context.getString(R.string.agenda_event_all_day) + card.context.getString(R.string.agenda_event_all_day) else event.time!!.stringHM - val eventTitle = "${event.typeName ?: "wydarzenie"} - ${event.topic}" + var eventTitle = "${event.typeName ?: "wydarzenie"} - ${event.topic}" val eventSubtitle = listOfNotNull( timeText, @@ -38,36 +64,33 @@ class AgendaEventRenderer( event.teamName ).join(", ") - if (isCompact) { - val b = AgendaWrappedEventCompactBinding.bind(view).item - - b.card.foreground.setTintColor(event.eventColor) - b.card.background.setTintColor(event.eventColor) - b.title.text = eventTitle - b.title.setTextColor(Colors.legibleTextColor(event.eventColor)) - - b.badgeBackground.isVisible = aEvent.showItemBadge - b.badgeBackground.background.setTintColor( - android.R.attr.colorBackground.resolveAttr(view.context) - ) - b.badge.isVisible = aEvent.showItemBadge + if (event.addedManually) { + eventTitle = "{cmd-clipboard-edit-outline} $eventTitle" } - else { - val b = AgendaWrappedEventBinding.bind(view).item - b.card.foreground.setTintColor(event.eventColor) - b.card.background.setTintColor(event.eventColor) - b.title.text = eventTitle - b.title.setTextColor(Colors.legibleTextColor(event.eventColor)) - b.subtitle.text = eventSubtitle - b.subtitle.setTextColor(Colors.legibleTextColor(event.eventColor)) + card.foreground.setTintColor(event.eventColor) + card.background.setTintColor(event.eventColor) + title.text = eventTitle + title.setTextColor(textColor) + subtitle?.text = eventSubtitle + subtitle?.setTextColor(textColor) - b.badgeBackground.isVisible = aEvent.showItemBadge - b.badgeBackground.background.setTintColor( - android.R.attr.colorBackground.resolveAttr(view.context) - ) - b.badge.isVisible = aEvent.showItemBadge - } + title.setCompoundDrawables( + null, + null, + if (event.isDone) IconicsDrawable(card.context).apply { + icon = CommunityMaterial.Icon.cmd_check + colorInt = textColor + sizeDp = 24 + } else null, + null + ) + + badgeBackground.isVisible = aEvent.showItemBadge + badgeBackground.background.setTintColor( + android.R.attr.colorBackground.resolveAttr(card.context) + ) + badge.isVisible = aEvent.showItemBadge } override fun getEventLayout() = if (isCompact) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt index 8b8ff81b..2a904016 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt @@ -17,13 +17,14 @@ class LessonChangesEventRenderer : EventRenderer() { 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(Colors.legibleTextColor(event.color)) + b.name.setTextColor(textColor) b.count.text = event.count.toString() - b.count.setTextColor(b.name.currentTextColor) + b.count.setTextColor(textColor) b.badgeBackground.isVisible = event.showItemBadge b.badgeBackground.background.setTintColor( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt index ec0a3915..12dd1947 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt @@ -16,13 +16,14 @@ class TeacherAbsenceEventRenderer : EventRenderer() { override fun render(view: View, event: TeacherAbsenceEvent) { 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_teacher_absence) - b.name.setTextColor(Colors.legibleTextColor(event.color)) + b.name.setTextColor(textColor) b.count.text = event.count.toString() - b.count.setTextColor(b.name.currentTextColor) + b.count.setTextColor(textColor) b.badgeBackground.isVisible = false b.badge.isVisible = false diff --git a/app/src/main/res/layout/agenda_event_compact_item.xml b/app/src/main/res/layout/agenda_event_compact_item.xml index df48e70c..b97ddfa3 100644 --- a/app/src/main/res/layout/agenda_event_compact_item.xml +++ b/app/src/main/res/layout/agenda_event_compact_item.xml @@ -25,7 +25,7 @@ android:orientation="vertical" android:padding="10dp"> - - Date: Wed, 14 Apr 2021 10:16:22 +0200 Subject: [PATCH 16/34] [UI] Fix updating event dialog when editing or removing. --- .../ui/dialogs/event/EventDetailsDialog.kt | 14 +++++++++++--- .../ui/dialogs/event/EventManualDialog.kt | 3 +++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt index 0958188e..664cc078 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt @@ -32,7 +32,7 @@ import kotlin.coroutines.CoroutineContext class EventDetailsDialog( val activity: AppCompatActivity, - val event: EventFull, + var event: EventFull, val onShowListener: ((tag: String) -> Unit)? = null, val onDismissListener: ((tag: String) -> Unit)? = null ) : CoroutineScope { @@ -139,6 +139,7 @@ class EventDetailsDialog( launch(Dispatchers.Default) { app.db.eventDao().replace(event) } + update() b.checkDoneButton.isChecked = true } .setNegativeButton(R.string.cancel, null) @@ -149,6 +150,7 @@ class EventDetailsDialog( launch(Dispatchers.Default) { app.db.eventDao().replace(event) } + update() } } b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done) @@ -160,6 +162,14 @@ class EventDetailsDialog( activity, event.profileId, editingEvent = event, + onSaveListener = { + if (it == null) { + dialog.dismiss() + return@EventManualDialog + } + event = it + update() + }, onShowListener = onShowListener, onDismissListener = onDismissListener ) @@ -327,8 +337,6 @@ class EventDetailsDialog( removeEventDialog?.dismiss() dialog.dismiss() 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 { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt index 9beac76b..a40c5772 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt @@ -48,6 +48,7 @@ class EventManualDialog( val defaultTime: Time? = null, val defaultType: Long? = null, val editingEvent: EventFull? = null, + val onSaveListener: ((event: EventFull?) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null, val onDismissListener: ((tag: String) -> Unit)? = null ) : CoroutineScope { @@ -596,6 +597,7 @@ class EventManualDialog( } } + onSaveListener?.invoke(eventObject.withMetadata(metadataObject)) dialog.dismiss() Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show() } @@ -608,6 +610,7 @@ class EventManualDialog( } removeEventDialog?.dismiss() + onSaveListener?.invoke(null) dialog.dismiss() Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() } From db598af28aa73cba9f6cf02ddd0c9b1bfcab9cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 14 Apr 2021 10:20:00 +0200 Subject: [PATCH 17/34] [UI] Add legend in event details dialog. --- .../ui/dialogs/event/EventDetailsDialog.kt | 6 ++++++ app/src/main/res/layout/dialog_event_details.xml | 14 ++++++++++++++ app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 22 insertions(+) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt index 664cc078..4feb1d53 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt @@ -104,6 +104,12 @@ class EventDetailsDialog( } catch (_: Exception) {} + b.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 { activity.getString(it) }.join("\n") + b.legend.isVisible = b.legend.text.isNotBlank() + b.typeColor.background?.setTintColor(event.eventColor) b.details = mutableListOf( diff --git a/app/src/main/res/layout/dialog_event_details.xml b/app/src/main/res/layout/dialog_event_details.xml index 7a77a813..a2fe00f5 100644 --- a/app/src/main/res/layout/dialog_event_details.xml +++ b/app/src/main/res/layout/dialog_event_details.xml @@ -105,9 +105,23 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f3021e4..303100f2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1439,4 +1439,6 @@ Ustaw wydarzenia jako lekcje on-line Wybierz rodzaj wydarzeń Grupuj lekcje on-line na liście + {cmd-clipboard-edit-outline} wydarzenie dodane ręcznie + {cmd-check} oznaczono jako wykonane From 297867cbf3ed09ad73f30f1f1d28d910de1f49b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 14 Apr 2021 11:21:45 +0200 Subject: [PATCH 18/34] [UI] Add event type colors to type dropdown. --- .../ui/dialogs/event/EventManualDialog.kt | 69 +++++------ .../ui/modules/views/EventTypeDropdown.kt | 111 ++++++++++++++++++ .../ui/modules/views/TeacherDropdown.kt | 6 + .../edziennik/utils/TextInputDropDown.kt | 42 ++++++- .../res/layout/dialog_event_manual_v2.xml | 2 +- 5 files changed, 181 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/EventTypeDropdown.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt index a40c5772..0cd2affe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt @@ -4,8 +4,6 @@ package pl.szczodrzynski.edziennik.ui.dialogs.event -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog @@ -26,7 +24,6 @@ import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi 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.entity.Profile import pl.szczodrzynski.edziennik.data.db.full.EventFull @@ -35,7 +32,6 @@ import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog 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 kotlin.coroutines.CoroutineContext @@ -323,57 +319,41 @@ class EventManualDialog( selectDefault(defaultLesson?.displayTeacherId) } + with (b.typeDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + loadItems() + selectDefault(editingEvent?.type) + selectDefault(defaultType) - val deferred = async(Dispatchers.Default) { - // get the event type list - var eventTypes = app.db.eventTypeDao().getAllNow(profileId) - - if (eventTypes.none { it.id in -1L..10L }) { - eventTypes = app.db.eventTypeDao().addDefaultTypes(activity, profileId) + onTypeSelected = { + b.typeColor.background.setTintColor(it.color) + customColor = null } - - b.typeDropdown.clear() - 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 + // copy data from event being edited editingEvent?.let { b.topic.setText(it.topic) - b.typeDropdown.select(it.type)?.let { item -> - customColor = (item.tag as EventType).color - } - if (it.color != null && it.color != -1) + if (it.color != -1) customColor = it.color } + b.typeColor.background.setTintColor( + customColor + ?: b.typeDropdown.getSelected()?.color + ?: Event.COLOR_DEFAULT + ) + // copy IDs from the LessonFull defaultLesson?.let { 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 { - 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() .setColor(currentColor) .create() @@ -381,7 +361,7 @@ class EventManualDialog( object : ColorPickerDialogListener { override fun onDialogDismissed(dialogId: 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 } }) @@ -597,7 +577,12 @@ class EventManualDialog( } } - onSaveListener?.invoke(eventObject.withMetadata(metadataObject)) + onSaveListener?.invoke(eventObject.withMetadata(metadataObject).also { + it.subjectLongName = b.subjectDropdown.selected?.text?.toString() + it.teacherName = b.teacherDropdown.selected?.text?.toString() + it.teamName = b.teamDropdown.selected?.text?.toString() + it.typeName = b.typeDropdown.selected?.text?.toString() + }) dialog.dismiss() Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/EventTypeDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/EventTypeDropdown.kt new file mode 100644 index 00000000..5527c50e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/EventTypeDropdown.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-14. + */ + +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 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) + + 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 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() + + 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 + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeacherDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeacherDropdown.kt index 6411426b..684412f1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeacherDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeacherDropdown.kt @@ -84,6 +84,9 @@ class TeacherDropdown : TextInputDropDown { } } + /** + * Select a teacher by the [teacherId]. + */ fun selectTeacher(teacherId: Long) { if (select(teacherId) == null) select(Item( @@ -93,6 +96,9 @@ class TeacherDropdown : TextInputDropDown { )) } + /** + * Select a teacher by the [teacherId] **if it's not selected yet**. + */ fun selectDefault(teacherId: Long?) { if (teacherId == null || selected != null) return diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt index f8836374..19aa27d6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt @@ -1,7 +1,11 @@ package pl.szczodrzynski.edziennik.utils +import android.annotation.SuppressLint import android.content.Context +import android.graphics.drawable.Drawable import android.util.AttributeSet +import androidx.appcompat.view.menu.MenuBuilder +import androidx.appcompat.view.menu.MenuPopupHelper import androidx.appcompat.widget.PopupMenu import com.google.android.material.textfield.TextInputEditText import com.mikepenz.iconics.IconicsDrawable @@ -33,6 +37,7 @@ open class TextInputDropDown : TextInputEditText { setText(selected?.displayText ?: selected?.text) } + @SuppressLint("RestrictedApi") open fun create(context: Context) { val drawable = IconicsDrawable(context, CommunityMaterial.Icon.cmd_chevron_down).apply { colorInt = Themes.getPrimaryTextColor(context) @@ -58,7 +63,9 @@ open class TextInputDropDown : TextInputEditText { val popup = PopupMenu(context, this) 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 -> @@ -70,29 +77,46 @@ open class TextInputDropDown : TextInputEditText { true } - popup.setOnDismissListener { + val helper = MenuPopupHelper(context, popup.menu as MenuBuilder, this) + helper.setForceShowIcon(true) + helper.setOnDismissListener { clearFocus() } - - popup.show() + helper.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 updateText() error = null return item } + /** + * Select an item by its ID. Returns the selected item + * if found. + */ fun select(id: Long?): Item? { 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? { 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? { 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 + ) } diff --git a/app/src/main/res/layout/dialog_event_manual_v2.xml b/app/src/main/res/layout/dialog_event_manual_v2.xml index 8d2fc267..228fcc74 100644 --- a/app/src/main/res/layout/dialog_event_manual_v2.xml +++ b/app/src/main/res/layout/dialog_event_manual_v2.xml @@ -94,7 +94,7 @@ android:layout_weight="1" android:hint="@string/dialog_event_manual_type"> - Date: Wed, 14 Apr 2021 11:59:58 +0200 Subject: [PATCH 19/34] [UI] Refactor dropdown inputs code. --- .../ui/dialogs/event/EventManualDialog.kt | 32 +++++------ .../ui/modules/views/DateDropdown.kt | 2 +- .../ui/modules/views/EventTypeDropdown.kt | 18 +----- .../ui/modules/views/SubjectDropdown.kt | 41 +++++++------ .../ui/modules/views/TeacherDropdown.kt | 49 ++++++---------- .../ui/modules/views/TeamDropdown.kt | 57 +++++++++---------- .../ui/modules/views/TimeDropdown.kt | 2 +- .../edziennik/utils/TextInputDropDown.kt | 2 +- 8 files changed, 87 insertions(+), 116 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt index 0cd2affe..da684520 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt @@ -23,9 +23,7 @@ import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi -import pl.szczodrzynski.edziennik.data.db.entity.Event -import pl.szczodrzynski.edziennik.data.db.entity.Metadata -import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.* import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding @@ -396,11 +394,11 @@ class EventManualDialog( private fun saveEvent() { val date = b.dateDropdown.getSelected() as? Date val timeSelected = b.timeDropdown.getSelected() - val teamId = b.teamDropdown.getSelected() as? Long - val type = b.typeDropdown.selected?.id + val team = b.teamDropdown.getSelected() + val type = b.typeDropdown.getSelected() val topic = b.topic.text?.toString() - val subjectId = b.subjectDropdown.getSelected() as? Long - val teacherId = b.teacherDropdown.getSelected() + val subject = b.subjectDropdown.getSelected() as? Subject + val teacher = b.teacherDropdown.getSelected() val share = b.shareSwitch.isChecked @@ -431,7 +429,7 @@ class EventManualDialog( isError = true } - if (share && teamId == null) { + if (share && team == null) { b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose) if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown) isError = true @@ -467,10 +465,10 @@ class EventManualDialog( time = startTime, topic = topic, color = customColor, - type = type ?: Event.TYPE_DEFAULT, - teacherId = teacherId ?: -1, - subjectId = subjectId ?: -1, - teamId = teamId ?: -1, + type = type?.id ?: Event.TYPE_DEFAULT, + teacherId = teacher?.id ?: -1, + subjectId = subject?.id ?: -1, + teamId = team?.id ?: -1, addedDate = editingEvent?.addedDate ?: System.currentTimeMillis() ).also { it.addedManually = true @@ -478,7 +476,7 @@ class EventManualDialog( val metadataObject = Metadata( profileId, - when (type) { + when (type?.id) { Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK else -> Metadata.TYPE_EVENT }, @@ -578,10 +576,10 @@ class EventManualDialog( } onSaveListener?.invoke(eventObject.withMetadata(metadataObject).also { - it.subjectLongName = b.subjectDropdown.selected?.text?.toString() - it.teacherName = b.teacherDropdown.selected?.text?.toString() - it.teamName = b.teamDropdown.selected?.text?.toString() - it.typeName = b.typeDropdown.selected?.text?.toString() + 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() Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/DateDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/DateDropdown.kt index 92bf0515..6c96c352 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/DateDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/DateDropdown.kt @@ -175,7 +175,7 @@ class DateDropdown : TextInputDropDown { } } - fun pickerDialog() { + private fun pickerDialog() { val date = getSelected() as? Date ?: Date.getToday() MaterialDatePicker.Builder.datePicker() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/EventTypeDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/EventTypeDropdown.kt index 5527c50e..878264c6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/EventTypeDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/EventTypeDropdown.kt @@ -5,9 +5,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 com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.utils.colorInt @@ -23,18 +21,6 @@ class EventTypeDropdown : TextInputDropDown { 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 onTypeSelected: ((eventType: EventType) -> Unit)? = null @@ -83,9 +69,7 @@ class EventTypeDropdown : TextInputDropDown { /** * Select an event type by the [typeId]. */ - fun selectType(typeId: Long) { - select(typeId) - } + fun selectType(typeId: Long) = select(typeId) /** * Select an event type by the [typeId] **if it's not selected yet**. diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/SubjectDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/SubjectDropdown.kt index 06e01225..4285ec7d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/SubjectDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/SubjectDropdown.kt @@ -15,6 +15,7 @@ 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.data.db.entity.Subject import pl.szczodrzynski.edziennik.ui.dialogs.input import pl.szczodrzynski.edziennik.utils.TextInputDropDown @@ -40,7 +41,7 @@ class SubjectDropdown : TextInputDropDown { var showNoSubject = true var showCustomSubject = false var customSubjectName = "" - var onSubjectSelected: ((subjectId: Long?) -> Unit)? = null + var onSubjectSelected: ((subject: Subject?) -> Unit)? = null var onCustomSubjectSelected: ((subjectName: String) -> Unit)? = null override fun create(context: Context) { @@ -73,7 +74,7 @@ class SubjectDropdown : TextInputDropDown { list += subjects.map { Item( it.id, it.longName, - tag = it.id + tag = it ) } list @@ -91,10 +92,11 @@ class SubjectDropdown : TextInputDropDown { } -1L -> { // no subject + deselect() onSubjectSelected?.invoke(null) - true + false } - is Long -> { + is Subject -> { // selected a subject onSubjectSelected?.invoke(it.tag) true @@ -104,7 +106,7 @@ class SubjectDropdown : TextInputDropDown { } } - fun customNameDialog() { + private fun customNameDialog() { activity ?: return MaterialAlertDialogBuilder(activity!!) .setTitle("Własny przedmiot") @@ -127,32 +129,37 @@ class SubjectDropdown : TextInputDropDown { .show() } - fun selectSubject(subjectId: Long) { - if (select(subjectId) == null) - select(Item( - subjectId, - "nieznany przedmiot ($subjectId)", - tag = subjectId - )) + /** + * Select a subject by the [subjectId]. + */ + fun selectSubject(subjectId: Long): Item? { + if (subjectId == -1L) { + 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) - return - selectSubject(subjectId) + return null + return selectSubject(subjectId) } /** * Get the currently selected subject. * ### Returns: * - 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 */ fun getSelected(): Any? { return when (selected?.tag) { -1L -> null - is Long -> selected?.tag as Long + is Subject -> selected?.tag as Subject is String -> selected?.tag as String else -> null } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeacherDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeacherDropdown.kt index 684412f1..23053359 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeacherDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeacherDropdown.kt @@ -5,13 +5,12 @@ 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.Teacher import pl.szczodrzynski.edziennik.utils.TextInputDropDown class TeacherDropdown : TextInputDropDown { @@ -19,22 +18,10 @@ class TeacherDropdown : TextInputDropDown { 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 + var onTeacherSelected: ((teacher: Teacher?) -> Unit)? = null override fun create(context: Context) { super.create(context) @@ -58,7 +45,7 @@ class TeacherDropdown : TextInputDropDown { list += teachers.map { Item( it.id, it.fullName, - tag = it.id + tag = it ) } list @@ -71,10 +58,11 @@ class TeacherDropdown : TextInputDropDown { when (it.tag) { -1L -> { // no teacher + deselect() onTeacherSelected?.invoke(null) - true + false } - is Long -> { + is Teacher -> { // selected a teacher onTeacherSelected?.invoke(it.tag) true @@ -87,34 +75,33 @@ class TeacherDropdown : TextInputDropDown { /** * Select a teacher by the [teacherId]. */ - fun selectTeacher(teacherId: Long) { - if (select(teacherId) == null) - select(Item( - teacherId, - "nieznany nauczyciel ($teacherId)", - tag = teacherId - )) + fun selectTeacher(teacherId: Long): Item? { + if (teacherId == -1L) { + deselect() + return null + } + return select(teacherId) } /** * Select a teacher by the [teacherId] **if it's not selected yet**. */ - fun selectDefault(teacherId: Long?) { + fun selectDefault(teacherId: Long?): Item? { if (teacherId == null || selected != null) - return - selectTeacher(teacherId) + return null + return selectTeacher(teacherId) } /** * Get the currently selected teacher. * ### Returns: * - 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) { -1L -> null - is Long -> selected?.tag as Long + is Teacher -> selected?.tag as Teacher else -> null } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeamDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeamDropdown.kt index b31d2ca8..b5eee0ac 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeamDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TeamDropdown.kt @@ -5,9 +5,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 @@ -20,22 +18,10 @@ class TeamDropdown : TextInputDropDown { 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 + var onTeamSelected: ((team: Team?) -> Unit)? = null override fun create(context: Context) { super.create(context) @@ -59,7 +45,7 @@ class TeamDropdown : TextInputDropDown { list += teams.map { Item( it.id, it.name, - tag = it.id + tag = it ) } list @@ -72,10 +58,11 @@ class TeamDropdown : TextInputDropDown { when (it.tag) { -1L -> { // no team + deselect() onTeamSelected?.invoke(null) - true + false } - is Long -> { + is Team -> { // selected a team onTeamSelected?.invoke(it.tag) true @@ -85,21 +72,29 @@ class TeamDropdown : TextInputDropDown { } } - fun selectTeam(teamId: Long) { - if (select(teamId) == null) - select(Item( - teamId, - "nieznana grupa ($teamId)", - tag = teamId - )) + /** + * Select a teacher by the [teamId]. + */ + fun selectTeam(teamId: Long): Item? { + if (teamId == -1L) { + 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) - return - selectTeam(teamId) + return null + return selectTeam(teamId) } + /** + * Select a team of the [Team.TYPE_CLASS] type. + */ fun selectTeamClass() { select(items.singleOrNull { it.tag is Team && it.tag.type == Team.TYPE_CLASS @@ -110,12 +105,12 @@ class TeamDropdown : TextInputDropDown { * Get the currently selected team. * ### Returns: * - 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) { -1L -> null - is Long -> selected?.tag as Long + is Team -> selected?.tag as Team else -> null } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt index 989184af..afecaf7e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/TimeDropdown.kt @@ -175,7 +175,7 @@ class TimeDropdown : TextInputDropDown { return !noTimetable } - fun pickerDialog() { + private fun pickerDialog() { val time = (getSelected() as? Pair<*, *>)?.first as? Time ?: Time.getNow() MaterialTimePicker.Builder() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt index 19aa27d6..44db5d9a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputDropDown.kt @@ -33,7 +33,7 @@ open class TextInputDropDown : TextInputEditText { val selectedId get() = selected?.id - fun updateText() { + private fun updateText() { setText(selected?.displayText ?: selected?.text) } From 755b846b508bd51a5c7638f41cb95ccbb25ec06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 14 Apr 2021 16:34:31 +0200 Subject: [PATCH 20/34] [UI/Agenda] Move common code to EventManager. --- .../ui/dialogs/event/EventDetailsDialog.kt | 10 ++-- .../ui/dialogs/event/EventListAdapter.kt | 7 ++- .../dialogs/timetable/LessonDetailsDialog.kt | 3 +- .../modules/agenda/AgendaFragmentDefault.kt | 12 +++-- .../agenda/event/AgendaEventRenderer.kt | 25 ++-------- .../attendance/AttendanceListFragment.kt | 3 +- .../attendance/AttendanceSummaryFragment.kt | 3 +- .../ui/modules/grades/GradesAdapter.kt | 3 +- .../ui/modules/grades/GradesListFragment.kt | 9 ++-- .../modules/timetable/TimetableDayFragment.kt | 3 +- .../edziennik/utils/managers/EventManager.kt | 46 ++++++++++++++++++- app/src/main/res/layout/event_list_item.xml | 23 ++-------- 12 files changed, 84 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt index 4feb1d53..c80f0c63 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt @@ -46,6 +46,8 @@ class EventDetailsDialog( private var removeEventDialog: AlertDialog? = null private val eventShared = event.sharedBy != null private val eventOwn = event.sharedBy == "self" + private val manager + get() = app.eventManager private val job = Job() override val coroutineContext: CoroutineContext @@ -93,7 +95,7 @@ class EventDetailsDialog( b.eventOwn = eventOwn if (!event.seen) { - app.eventManager.markAsSeen(event) + manager.markAsSeen(event) } val bullet = " • " @@ -104,11 +106,7 @@ class EventDetailsDialog( } catch (_: Exception) {} - b.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 { activity.getString(it) }.join("\n") - b.legend.isVisible = b.legend.text.isNotBlank() + manager.setLegendText(b.legend, event) b.typeColor.background?.setTintColor(event.eventColor) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt index 34cc6a2d..9bb199ea 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt @@ -33,7 +33,8 @@ class EventListAdapter( ) : RecyclerView.Adapter(), CoroutineScope { private val app = context.applicationContext as App - private val manager = app.eventManager + private val manager + get() = app.eventManager private val job = Job() override val coroutineContext: CoroutineContext @@ -67,7 +68,7 @@ class EventListAdapter( b.simpleMode = simpleMode - b.topic.text = event.topic + manager.setEventTopic(b.topic, event, showType = false) b.topic.maxLines = if (simpleMode) 2 else 3 b.details.text = mutableListOf( @@ -102,8 +103,6 @@ class EventListAdapter( } b.editButton.attachToastHint(R.string.hint_edit_event) - b.isDone.isVisible = event.isDone - if (event.showAsUnseen == null) event.showAsUnseen = !event.seen 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 f444f38e..9b43f9be 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 @@ -50,7 +50,8 @@ class LessonDetailsDialog( get() = job + Dispatchers.Main private lateinit var adapter: EventListAdapter - private val manager by lazy { app.timetableManager } + private val manager + get() = app.timetableManager init { run { if (activity.isFinishing) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt index eb6f74fb..cc25c42f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt @@ -136,7 +136,13 @@ class AgendaFragmentDefault( dateEnd, Locale.getDefault(), object : CalendarPickerController { - override fun onDaySelected(dayItem: IDayItem) {} + 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) @@ -180,7 +186,7 @@ class AgendaFragmentDefault( } } }, - AgendaEventRenderer(isCompactMode), + AgendaEventRenderer(app.eventManager, isCompactMode), AgendaEventGroupRenderer(), LessonChangesEventRenderer(), TeacherAbsenceEventRenderer() @@ -197,7 +203,7 @@ class AgendaFragmentDefault( manager.loadEvents(events, BaseCalendarEvent()) adapter?.updateEvents(manager.events) - listView.scrollToCurrentDate(selectedDate.asCalendar) + //listView.scrollToCurrentDate(selectedDate.asCalendar) } private fun setAsRead(date: Calendar) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt index ac65fcef..2e857cd3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventRenderer.kt @@ -10,10 +10,6 @@ import android.widget.FrameLayout import android.widget.TextView import androidx.core.view.isVisible import com.github.tibolte.agendacalendarview.render.EventRenderer -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 pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventBinding @@ -22,8 +18,10 @@ 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() { @@ -55,8 +53,6 @@ class AgendaEventRenderer( else event.time!!.stringHM - var eventTitle = "${event.typeName ?: "wydarzenie"} - ${event.topic}" - val eventSubtitle = listOfNotNull( timeText, event.subjectLongName, @@ -64,28 +60,13 @@ class AgendaEventRenderer( event.teamName ).join(", ") - if (event.addedManually) { - eventTitle = "{cmd-clipboard-edit-outline} $eventTitle" - } - card.foreground.setTintColor(event.eventColor) card.background.setTintColor(event.eventColor) - title.text = eventTitle + manager.setEventTopic(title, event, doneIconColor = textColor) title.setTextColor(textColor) subtitle?.text = eventSubtitle subtitle?.setTextColor(textColor) - title.setCompoundDrawables( - null, - null, - if (event.isDone) IconicsDrawable(card.context).apply { - icon = CommunityMaterial.Icon.cmd_check - colorInt = textColor - sizeDp = 24 - } else null, - null - ) - badgeBackground.isVisible = aEvent.showItemBadge badgeBackground.background.setTintColor( android.R.attr.colorBackground.resolveAttr(card.context) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt index 8e68cdbb..523dd09c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceListFragment.kt @@ -41,7 +41,8 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope { get() = job + Dispatchers.Main // 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 expandSubjectId = 0L diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt index 5f61020b..39282325 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceSummaryFragment.kt @@ -47,7 +47,8 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope { get() = job + Dispatchers.Main // local/private variables go here - private val manager by lazy { app.attendanceManager } + private val manager + get() = app.attendanceManager private var expandSubjectId = 0L private var attendance = listOf() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesAdapter.kt index 46c75d8d..086f073d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesAdapter.kt @@ -40,7 +40,8 @@ class GradesAdapter( } private val app = activity.applicationContext as App - private val manager = app.gradesManager + private val manager + get() = app.gradesManager private val job = Job() override val coroutineContext: CoroutineContext diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesListFragment.kt index 63521e97..63c935ff 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesListFragment.kt @@ -48,9 +48,12 @@ class GradesListFragment : Fragment(), CoroutineScope { get() = job + Dispatchers.Main // local/private variables go here - private val manager by lazy { app.gradesManager } - private val dontCountEnabled by lazy { manager.dontCountEnabled } - private val dontCountGrades by lazy { manager.dontCountGrades } + private val manager + get() = app.gradesManager + private val dontCountEnabled + get() = manager.dontCountEnabled + private val dontCountGrades + get() = manager.dontCountGrades private var expandSubjectId = 0L override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt index e6464d7d..e92ca55c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt @@ -54,7 +54,8 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { private var endHour = DEFAULT_END_HOUR private var firstEventMinute = 24 * 60 - private val manager by lazy { app.timetableManager } + private val manager + get() = app.timetableManager // find SwipeRefreshLayout in the hierarchy private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/EventManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/EventManager.kt index ca444ca5..f5b7faa9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/EventManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/EventManager.kt @@ -4,12 +4,17 @@ 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.Dispatchers 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.startCoroutineTimer import kotlin.coroutines.CoroutineContext class EventManager(val app: App) : CoroutineScope { @@ -32,4 +37,41 @@ class EventManager(val app: App) : CoroutineScope { 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() + } } diff --git a/app/src/main/res/layout/event_list_item.xml b/app/src/main/res/layout/event_list_item.xml index 24445327..a69b74c0 100644 --- a/app/src/main/res/layout/event_list_item.xml +++ b/app/src/main/res/layout/event_list_item.xml @@ -4,8 +4,7 @@ --> + xmlns:android="http://schemas.android.com/apk/res/android"> @@ -61,12 +60,10 @@ android:gravity="center_vertical" android:orientation="horizontal"> - - - - + tools:visibility="visible" /> Date: Wed, 14 Apr 2021 16:59:12 +0200 Subject: [PATCH 21/34] [UI/Agenda] Update DayDialog for showing event group. --- .../edziennik/ui/dialogs/day/DayDialog.kt | 62 ++++-- .../modules/agenda/AgendaFragmentDefault.kt | 3 +- .../modules/agenda/event/AgendaEventGroup.kt | 3 +- .../LessonChangesEventRenderer.kt | 18 ++ .../TeacherAbsenceEventRenderer.kt | 15 ++ .../res/layout/agenda_lesson_changes_item.xml | 33 --- .../layout/agenda_teacher_absence_item.xml | 33 --- app/src/main/res/layout/dialog_day.xml | 192 +++++++++--------- 8 files changed, 170 insertions(+), 189 deletions(-) delete mode 100644 app/src/main/res/layout/agenda_lesson_changes_item.xml delete mode 100644 app/src/main/res/layout/agenda_teacher_absence_item.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt index e5b7768d..74ba0030 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt @@ -7,7 +7,7 @@ package pl.szczodrzynski.edziennik.ui.dialogs.day import android.view.View import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.Observer +import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder 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.lessonchange.LessonChangeDialog 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.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -29,6 +33,7 @@ class DayDialog( val activity: AppCompatActivity, val profileId: Int, val date: Date, + val eventTypeId: Long? = null, val onShowListener: ((tag: String) -> Unit)? = null, val onDismissListener: ((tag: String) -> Unit)? = null ) : CoroutineScope { @@ -109,38 +114,51 @@ class DayDialog( } lessonChanges.ifNotEmpty { - b.lessonChangeContainer.root.visibility = View.VISIBLE - b.lessonChangeContainer.lessonChangeCount.text = it.size.toString() + LessonChangesEventRenderer().render( + b.lessonChanges, LessonChangesEvent( + profileId = profileId, + date = date, + count = it.size, + showBadge = false + ) + ) - b.lessonChangeLayout.onClick { + b.lessonChangesFrame.onClick { LessonChangeDialog( - activity, - profileId, - date, - onShowListener = onShowListener, - onDismissListener = onDismissListener + activity, + profileId, + date, + onShowListener = onShowListener, + onDismissListener = onDismissListener ) } } + b.lessonChangesFrame.isVisible = lessonChanges.isNotEmpty() val teacherAbsences = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllByDateNow(profileId, date) } teacherAbsences.ifNotEmpty { - b.teacherAbsenceContainer.root.visibility = View.VISIBLE - b.teacherAbsenceContainer.teacherAbsenceCount.text = it.size.toString() + TeacherAbsenceEventRenderer().render( + b.teacherAbsence, TeacherAbsenceEvent( + profileId = profileId, + date = date, + count = it.size + ) + ) - b.teacherAbsenceLayout.onClick { + b.teacherAbsenceFrame.onClick { TeacherAbsenceDialog( - activity, - profileId, - date, - onShowListener = onShowListener, - onDismissListener = onDismissListener + activity, + profileId, + date, + onShowListener = onShowListener, + onDismissListener = onDismissListener ) } } + b.teacherAbsenceFrame.isVisible = teacherAbsences.isNotEmpty() adapter = EventListAdapter( activity, @@ -169,8 +187,12 @@ class DayDialog( } ) - app.db.eventDao().getAllByDate(profileId, date).observe(activity, Observer { events -> - adapter.items = events + app.db.eventDao().getAllByDate(profileId, date).observe(activity) { events -> + adapter.items = if (eventTypeId != null) + events.filter { it.type == eventTypeId } + else + events + if (b.eventsView.adapter == null) { b.eventsView.adapter = adapter b.eventsView.apply { @@ -189,6 +211,6 @@ class DayDialog( b.eventsView.visibility = View.GONE b.eventsNoData.visibility = View.VISIBLE } - }) + } }} } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt index cc25c42f..9167fb9a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragmentDefault.kt @@ -155,7 +155,7 @@ class AgendaFragmentDefault( app.profileId, date ) - is AgendaEventGroup -> DayDialog(activity, app.profileId, date) + is AgendaEventGroup -> DayDialog(activity, app.profileId, date, eventTypeId = event.typeId) is BaseCalendarEvent -> if (event.isPlaceHolder) DayDialog(activity, app.profileId, date) } @@ -260,6 +260,7 @@ class AgendaFragmentDefault( events.add(0, AgendaEventGroup( profileId = event.profileId, date = event.date, + typeId = event.type, typeName = event.typeName ?: "-", typeColor = event.typeColor ?: event.eventColor, count = list.size, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt index e50be2c3..3ef4eb47 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/event/AgendaEventGroup.kt @@ -10,6 +10,7 @@ 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, @@ -20,5 +21,5 @@ class AgendaEventGroup( color = typeColor, showBadge = showBadge ) { - override fun copy() = AgendaEventGroup(profileId, date, typeName, typeColor, count, showBadge) + override fun copy() = AgendaEventGroup(profileId, date, typeId, typeName, typeColor, count, showBadge) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt index 2a904016..5f66cc27 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchanges/LessonChangesEventRenderer.kt @@ -8,6 +8,7 @@ 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 @@ -33,5 +34,22 @@ class LessonChangesEventRenderer : EventRenderer() { 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 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt index 12dd1947..8d70f941 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt @@ -8,6 +8,7 @@ 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.setTintColor import pl.szczodrzynski.edziennik.utils.Colors @@ -29,5 +30,19 @@ class TeacherAbsenceEventRenderer : EventRenderer() { b.badge.isVisible = false } + 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 } diff --git a/app/src/main/res/layout/agenda_lesson_changes_item.xml b/app/src/main/res/layout/agenda_lesson_changes_item.xml deleted file mode 100644 index 8700a575..00000000 --- a/app/src/main/res/layout/agenda_lesson_changes_item.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/layout/agenda_teacher_absence_item.xml b/app/src/main/res/layout/agenda_teacher_absence_item.xml deleted file mode 100644 index 4b749e8c..00000000 --- a/app/src/main/res/layout/agenda_teacher_absence_item.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/layout/dialog_day.xml b/app/src/main/res/layout/dialog_day.xml index f1544f59..8bbc7b64 100644 --- a/app/src/main/res/layout/dialog_day.xml +++ b/app/src/main/res/layout/dialog_day.xml @@ -3,118 +3,108 @@ ~ Copyright (c) Kuba Szczodrzyński 2019-12-16. --> - - + android:layout_width="match_parent" + android:layout_height="match_parent"> - - - + + + + + + + + + + + + + + + + + android:paddingVertical="16dp" + android:visibility="gone" + tools:visibility="visible"> - + android:layout_gravity="center" + android:drawablePadding="16dp" + android:fontFamily="sans-serif-light" + android:gravity="center" + android:text="@string/dialog_day_no_events" + android:textSize="24sp" + app:drawableTopCompat="@drawable/ic_no_events" /> - - - - - - - - - - - - - - - - - - - - - + android:layout_gravity="center" + android:gravity="center" + android:text="@string/dialog_no_events_hint" + android:textStyle="italic" /> - - + + + + From 02eb5b7ee4499916a5eeca1cf7e3409cdc8d673c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 14 Apr 2021 17:03:44 +0200 Subject: [PATCH 22/34] [UI/Agenda] Disable not available config options. --- app/src/main/res/layout/dialog_config_agenda.xml | 14 +++++++++++--- app/src/main/res/values/strings.xml | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/dialog_config_agenda.xml b/app/src/main/res/layout/dialog_config_agenda.xml index 475c9d84..f17b5989 100644 --- a/app/src/main/res/layout/dialog_config_agenda.xml +++ b/app/src/main/res/layout/dialog_config_agenda.xml @@ -1,5 +1,4 @@ - - @@ -67,7 +66,7 @@ android:layout_marginHorizontal="16dp" android:enabled="@{isAgendaMode}" android:text="@string/agenda_config_compact_mode_hint" - android:textAppearance="@style/NavView.TextView.Small" + android:textAppearance="@style/NavView.TextView.Helper" tools:enabled="false" /> + + @@ -141,6 +148,7 @@ 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" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 80e158d8..9196508e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1443,4 +1443,5 @@ Grupuj lekcje on-line na liście {cmd-clipboard-edit-outline} wydarzenie dodane ręcznie {cmd-check} oznaczono jako wykonane + Funkcja jeszcze nie jest dostępna. From 1e8fb6a9aecaaff1f5da8cb5fb621d2563d5d144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 14 Apr 2021 20:19:04 +0200 Subject: [PATCH 23/34] [UI/Messages] Restore search string after closing a message. Add message counter. --- .../pl/szczodrzynski/edziennik/Extensions.kt | 57 ++++-- .../edziennik/data/db/full/MessageFull.kt | 2 +- .../ui/modules/messages/MessagesAdapter.kt | 187 ++---------------- .../modules/messages/MessagesListFragment.kt | 40 ++-- .../modules/messages/models/MessagesSearch.kt | 4 +- .../messages/utils/MessagesComparator.kt | 27 +++ .../modules/messages/utils/MessagesFilter.kt | 149 ++++++++++++++ .../messages/utils/SearchTextWatcher.kt | 43 ++++ .../messages/viewholder/MessageViewHolder.kt | 28 +-- .../messages/viewholder/SearchViewHolder.kt | 45 +++-- 10 files changed, 343 insertions(+), 239 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/MessagesComparator.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/MessagesFilter.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/SearchTextWatcher.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt index a07634a1..8d3b59d1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt @@ -15,6 +15,7 @@ import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle import android.text.* +import android.text.style.CharacterStyle import android.text.style.ForegroundColorSpan import android.text.style.StrikethroughSpan import android.text.style.StyleSpan @@ -552,28 +553,46 @@ fun CharSequence?.asBoldSpannable(): Spannable { spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) return spannable } -fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignoreCase: Boolean = false, ignoreDiacritics: Boolean = false): Spannable { +fun CharSequence.asSpannable( + vararg spans: CharacterStyle, + substring: CharSequence? = null, + ignoreCase: Boolean = false, + ignoreDiacritics: Boolean = false +): Spannable { val spannable = SpannableString(this) - if (substring == null) { - spans.forEach { - spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - } - } - else if (substring.isNotEmpty()) { - val string = - if (ignoreDiacritics) - this.cleanDiacritics() - else this + substring?.let { substr -> + val string = if (ignoreDiacritics) + this.cleanDiacritics() + else + this + val search = if (ignoreDiacritics) + substr.cleanDiacritics() + else + substr.toString() - var index = string.indexOf(substring, ignoreCase = ignoreCase) - .takeIf { it != -1 } ?: indexOf(substring, ignoreCase = ignoreCase) - while (index >= 0) { - spans.forEach { - spannable.setSpan(it, index, index + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + var index = 0 + do { + index = string.indexOf( + string = search, + startIndex = index, + ignoreCase = ignoreCase + ) + + if (index >= 0) { + spans.forEach { + spannable.setSpan( + CharacterStyle.wrap(it), + index, + index + substring.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + index += substring.length.coerceAtLeast(1) } - index = string.indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase) - .takeIf { it != -1 } ?: indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase) - } + } while (index >= 0) + + } ?: spans.forEach { + spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } return spannable } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt index 3fdcdd0d..96103f38 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/MessageFull.kt @@ -30,7 +30,7 @@ class MessageFull( @Ignore var filterWeight = 0 @Ignore - var searchHighlightText: String? = null + var searchHighlightText: CharSequence? = null // metadata var seen = false diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesAdapter.kt index f8ccb491..aded9f4a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesAdapter.kt @@ -1,11 +1,8 @@ package pl.szczodrzynski.edziennik.ui.modules.messages import android.graphics.Typeface -import android.text.Editable -import android.text.TextWatcher import android.view.LayoutInflater import android.view.ViewGroup -import android.widget.Filter import android.widget.Filterable import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.RecyclerView @@ -13,22 +10,19 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.cleanDiacritics -import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch +import pl.szczodrzynski.edziennik.ui.modules.messages.utils.MessagesFilter import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.MessageViewHolder import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.SearchViewHolder -import java.util.* import kotlin.coroutines.CoroutineContext -import kotlin.math.min class MessagesAdapter( - val activity: AppCompatActivity, - val teachers: List, - val onItemClick: ((item: MessageFull) -> Unit)? = null + val activity: AppCompatActivity, + val teachers: List, + val onItemClick: ((item: MessageFull) -> Unit)? = null ) : RecyclerView.Adapter(), CoroutineScope, Filterable { companion object { private const val TAG = "MessagesAdapter" @@ -43,41 +37,10 @@ class MessagesAdapter( override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main - var items = mutableListOf() - var allItems = mutableListOf() + var items = listOf() + var allItems = listOf() val typefaceNormal: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) } val typefaceBold: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) } - private val comparator by lazy { Comparator { o1: Any, o2: Any -> - if (o1 !is MessageFull || o2 !is MessageFull) - return@Comparator 0 - when { - // standard sorting - o1.filterWeight > o2.filterWeight -> return@Comparator 1 - o1.filterWeight < o2.filterWeight -> return@Comparator -1 - else -> when { - // reversed sorting - o1.addedDate > o2.addedDate -> return@Comparator -1 - o1.addedDate < o2.addedDate -> return@Comparator 1 - else -> return@Comparator 0 - } - } - }} - - val textWatcher by lazy { - object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - getFilter().filter(s.toString()) - } - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - /*items.getOrNull(0)?.let { - if (it is MessagesSearch) { - it.searchText = s?.toString() ?: "" - } - }*/ - } - } - } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) @@ -103,138 +66,16 @@ class MessagesAdapter( return when { - holder is MessageViewHolder && item is MessageFull -> holder.onBind(activity, app, item, position, this) - holder is SearchViewHolder && item is MessagesSearch -> holder.onBind(activity, app, item, position, this) + holder is MessageViewHolder + && item is MessageFull -> holder.onBind(activity, app, item, position, this) + holder is SearchViewHolder + && item is MessagesSearch -> holder.onBind(activity, app, item, position, this) } } + private val messagesFilter by lazy { + MessagesFilter(this) + } override fun getItemCount() = items.size - override fun getFilter() = filter - private var prevCount = -1 - private val filter by lazy { object : Filter() { - override fun performFiltering(prefix: CharSequence?): FilterResults { - val results = FilterResults() - - if (prevCount == -1) - prevCount = allItems.size - - if (prefix.isNullOrEmpty()) { - allItems.forEach { - if (it is MessageFull) - it.searchHighlightText = null - } - results.values = allItems.toList() - results.count = allItems.size - return results - } - - val items = mutableListOf() - val prefixString = prefix.toString() - - allItems.forEach { - if (it !is MessageFull) { - items.add(it) - return@forEach - } - it.filterWeight = 100 - it.searchHighlightText = null - - var weight: Int - if (it.type == Message.TYPE_SENT) { - it.recipients?.forEach { recipient -> - weight = getMatchWeight(recipient.fullName, prefixString) - if (weight != 100) { - if (weight == 3) - weight = 31 - it.filterWeight = min(it.filterWeight, 10 + weight) - } - } - } - else { - weight = getMatchWeight(it.senderName, prefixString) - if (weight != 100) { - if (weight == 3) - weight = 31 - it.filterWeight = min(it.filterWeight, 10 + weight) - } - } - - - weight = getMatchWeight(it.subject, prefixString) - if (weight != 100) { - if (weight == 3) - weight = 22 - it.filterWeight = min(it.filterWeight, 20 + weight) - } - - if (it.filterWeight != 100) { - it.searchHighlightText = prefixString - items.add(it) - } - } - - Collections.sort(items, comparator) - results.values = items - results.count = items.size - return results - } - - override fun publishResults(constraint: CharSequence?, results: FilterResults) { - results.values?.let { items = it as MutableList } - // do not re-bind the search box - val count = results.count - 1 - - // this tries to update every item except the search field - when { - count > prevCount -> { - notifyItemRangeInserted(prevCount + 1, count - prevCount) - notifyItemRangeChanged(1, prevCount) - } - count < prevCount -> { - notifyItemRangeRemoved(prevCount + 1, prevCount - count) - notifyItemRangeChanged(1, count) - } - else -> { - notifyItemRangeChanged(1, count) - } - } - - /*if (prevCount != count) { - items.getOrNull(0)?.let { - if (it is MessagesSearch) { - it.count = count - notifyItemChanged(0) - } - } - }*/ - - prevCount = count - } - }} - - private fun getMatchWeight(name: CharSequence?, prefix: String): Int { - if (name == null) - return 100 - - val nameClean = name.cleanDiacritics() - - // First match against the whole, non-split value - if (nameClean.startsWith(prefix, ignoreCase = true) || name.startsWith(prefix, ignoreCase = true)) { - return 1 - } else { - // check if prefix matches any of the words - val words = nameClean.split(" ").toTypedArray() + name.split(" ").toTypedArray() - for (word in words) { - if (word.startsWith(prefix, ignoreCase = true)) { - return 2 - } - } - } - // finally check if the prefix matches any part of the name - if (nameClean.contains(prefix, ignoreCase = true) || name.contains(prefix, ignoreCase = true)) { - return 3 - } - - return 100 - } + override fun getFilter() = messagesFilter } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.kt index 2e4a30b8..32761515 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.kt @@ -33,6 +33,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: MainActivity private lateinit var b: MessagesListFragmentBinding + private var adapter: MessagesAdapter? = null private val job: Job = Job() override val coroutineContext: CoroutineContext @@ -53,21 +54,22 @@ class MessagesListFragment : LazyFragment(), CoroutineScope { val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED) var topPosition = arguments.getInt("topPosition", NO_POSITION) var bottomPosition = arguments.getInt("bottomPosition", NO_POSITION) + val searchText = arguments.getString("searchText", "") teachers = withContext(Dispatchers.Default) { app.db.teacherDao().getAllNow(App.profileId) } - val adapter = MessagesAdapter(activity, teachers) { + adapter = MessagesAdapter(activity, teachers) { activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle( "messageId" to it.id )) } - app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { items -> + app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { messages -> if (!isAdded) return@Observer - items.forEach { message -> + messages.forEach { message -> message.recipients?.removeAll { it.profileId != message.profileId } message.recipients?.forEach { recipient -> if (recipient.fullName == null) { @@ -77,13 +79,22 @@ class MessagesListFragment : LazyFragment(), CoroutineScope { } // load & configure the adapter - adapter.items = items.toMutableList() - adapter.items.add(0, MessagesSearch().also { - it.count = items.size + val items = messages.toMutableList() + items.add(0, MessagesSearch().also { + it.searchText = searchText }) - adapter.allItems = adapter.items.toMutableList() + + adapter?.items = items + adapter?.allItems = items + if (items.isNotNullNorEmpty() && b.list.adapter == null) { - b.list.adapter = adapter + if (searchText.isNotBlank()) + adapter?.filter?.filter(searchText) { + b.list.adapter = adapter + } + else + b.list.adapter = adapter + b.list.apply { setHasFixedSize(true) layoutManager = LinearLayoutManager(context) @@ -92,7 +103,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope { addOnScrollListener(onScrollListener) } } - adapter.notifyDataSetChanged() + setSwipeToRefresh(messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT && items.isNullOrEmpty()) (b.list.layoutManager as? LinearLayoutManager)?.let { layoutManager -> @@ -119,10 +130,15 @@ class MessagesListFragment : LazyFragment(), CoroutineScope { override fun onDestroy() { super.onDestroy() - if (!isAdded) return + if (!isAdded) + return + val layoutManager = (b.list.layoutManager as? LinearLayoutManager) + val searchItem = adapter?.items?.firstOrNull { it is MessagesSearch } as? MessagesSearch + onPageDestroy?.invoke(position, Bundle( - "topPosition" to (b.list.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition(), - "bottomPosition" to (b.list.layoutManager as? LinearLayoutManager)?.findLastCompletelyVisibleItemPosition() + "topPosition" to layoutManager?.findFirstVisibleItemPosition(), + "bottomPosition" to layoutManager?.findLastCompletelyVisibleItemPosition(), + "searchText" to searchItem?.searchText?.toString() )) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/models/MessagesSearch.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/models/MessagesSearch.kt index ad206c62..71ed0486 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/models/MessagesSearch.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/models/MessagesSearch.kt @@ -5,7 +5,5 @@ package pl.szczodrzynski.edziennik.ui.modules.messages.models class MessagesSearch { - var isFocused = false - var searchText = "" - var count = 0 + var searchText: CharSequence = "" } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/MessagesComparator.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/MessagesComparator.kt new file mode 100644 index 00000000..2279b600 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/MessagesComparator.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-14. + */ + +package pl.szczodrzynski.edziennik.ui.modules.messages.utils + +import pl.szczodrzynski.edziennik.data.db.full.MessageFull + +class MessagesComparator : Comparator { + + override fun compare(o1: Any?, o2: Any?): Int { + if (o1 !is MessageFull || o2 !is MessageFull) + return 0 + + return when { + // standard sorting + o1.filterWeight > o2.filterWeight -> 1 + o1.filterWeight < o2.filterWeight -> -1 + else -> when { + // reversed sorting + o1.addedDate > o2.addedDate -> -1 + o1.addedDate < o2.addedDate -> 1 + else -> 0 + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/MessagesFilter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/MessagesFilter.kt new file mode 100644 index 00000000..202db386 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/MessagesFilter.kt @@ -0,0 +1,149 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-14. + */ + +package pl.szczodrzynski.edziennik.ui.modules.messages.utils + +import android.widget.Filter +import pl.szczodrzynski.edziennik.cleanDiacritics +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter +import java.util.* +import kotlin.math.min + +class MessagesFilter( + private val adapter: MessagesAdapter +) : Filter() { + companion object { + private const val NO_MATCH = 1000 + } + + private val comparator = MessagesComparator() + private var prevCount = -1 + + private val allItems + get() = adapter.allItems + + private fun getMatchWeight(name: CharSequence?, prefix: CharSequence): Int { + if (name == null) + return NO_MATCH + + val prefixClean = prefix.cleanDiacritics() + val nameClean = name.cleanDiacritics() + + return when { + // First match against the whole, non-split value + nameClean.startsWith(prefixClean, ignoreCase = true) -> 1 + // check if prefix matches any of the words + nameClean.split(" ").any { + it.startsWith(prefixClean, ignoreCase = true) + } -> 2 + // finally check if the prefix matches any part of the name + nameClean.contains(prefixClean, ignoreCase = true) -> 3 + + else -> NO_MATCH + } + } + + override fun performFiltering(prefix: CharSequence?): FilterResults { + val results = FilterResults() + + if (prevCount == -1) + prevCount = allItems.size + + if (prefix.isNullOrBlank()) { + allItems.forEach { + if (it is MessageFull) + it.searchHighlightText = null + } + results.values = allItems.toList() + results.count = allItems.size + return results + } + + val items = mutableListOf() + + allItems.forEach { + if (it !is MessageFull) { + items.add(it) + return@forEach + } + it.filterWeight = NO_MATCH + it.searchHighlightText = null + + var weight: Int + // weights 11..13 and 110 + if (it.type == Message.TYPE_SENT) { + it.recipients?.forEach { recipient -> + weight = getMatchWeight(recipient.fullName, prefix) + if (weight != NO_MATCH) { + if (weight == 3) + weight = 100 + it.filterWeight = min(it.filterWeight, 10 + weight) + } + } + } else { + weight = getMatchWeight(it.senderName, prefix) + if (weight != NO_MATCH) { + if (weight == 3) + weight = 100 + it.filterWeight = min(it.filterWeight, 10 + weight) + } + } + + // weights 21..23 and 120 + weight = getMatchWeight(it.subject, prefix) + if (weight != NO_MATCH) { + if (weight == 3) + weight = 100 + it.filterWeight = min(it.filterWeight, 20 + weight) + } + + // weights 31..33 and 130 + weight = getMatchWeight(it.body, prefix) + if (weight != NO_MATCH) { + if (weight == 3) + weight = 100 + it.filterWeight = min(it.filterWeight, 30 + weight) + } + + if (it.filterWeight != NO_MATCH) { + it.searchHighlightText = prefix + items.add(it) + } + } + + Collections.sort(items, comparator) + results.values = items + results.count = items.size + return results + } + + override fun publishResults(constraint: CharSequence?, results: FilterResults) { + results.values?.let { + adapter.items = it as MutableList + } + // do not re-bind the search box + val count = results.count - 1 + + // this tries to update every item except the search field + with(adapter) { + when { + count > prevCount -> { + notifyItemRangeInserted(prevCount + 1, count - prevCount) + notifyItemRangeChanged(1, prevCount) + } + count < prevCount -> { + notifyItemRangeRemoved(prevCount + 1, prevCount - count) + notifyItemRangeChanged(1, count) + } + else -> { + notifyItemRangeChanged(1, count) + } + } + } + + prevCount = count + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/SearchTextWatcher.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/SearchTextWatcher.kt new file mode 100644 index 00000000..7103ff29 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/utils/SearchTextWatcher.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-14. + */ + +package pl.szczodrzynski.edziennik.ui.modules.messages.utils + +import android.text.Editable +import android.text.TextWatcher +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.MessagesListItemSearchBinding +import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch + +class SearchTextWatcher( + private val b: MessagesListItemSearchBinding, + private val filter: MessagesFilter, + private val item: MessagesSearch +) : TextWatcher { + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit + + override fun afterTextChanged(s: Editable?) { + item.searchText = s ?: "" + filter.filter(s) { count -> + if (s.isNullOrBlank()) + b.searchLayout.helperText = " " + else + b.searchLayout.helperText = + b.root.context.getString(R.string.messages_search_results, count - 1) + } + } + + override fun equals(other: Any?): Boolean { + return other is SearchTextWatcher + } + + override fun hashCode(): Int { + var result = b.hashCode() + result = 31 * result + filter.hashCode() + result = 31 * result + item.hashCode() + return result + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/viewholder/MessageViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/viewholder/MessageViewHolder.kt index 62789a0c..a466d08b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/viewholder/MessageViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/viewholder/MessageViewHolder.kt @@ -22,17 +22,21 @@ import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils import pl.szczodrzynski.edziennik.utils.models.Date class MessageViewHolder( - inflater: LayoutInflater, - parent: ViewGroup, - val b: MessagesListItemBinding = MessagesListItemBinding.inflate(inflater, parent, false) + inflater: LayoutInflater, + parent: ViewGroup, + val b: MessagesListItemBinding = MessagesListItemBinding.inflate(inflater, parent, false) ) : RecyclerView.ViewHolder(b.root), BindableViewHolder { companion object { private const val TAG = "MessageViewHolder" } - override fun onBind(activity: AppCompatActivity, app: App, item: MessageFull, position: Int, adapter: MessagesAdapter) { - val manager = app.gradesManager - + override fun onBind( + activity: AppCompatActivity, + app: App, + item: MessageFull, + position: Int, + adapter: MessagesAdapter + ) { b.messageSubject.text = item.subject b.messageDate.text = Date.fromMillis(item.addedDate).formattedStringShort b.messageAttachmentImage.isVisible = item.hasAttachments @@ -55,15 +59,17 @@ class MessageViewHolder( b.messageProfileBackground.setImageBitmap(messageInfo.profileImage) b.messageSender.text = messageInfo.profileName - item.searchHighlightText?.let { highlight -> + item.searchHighlightText?.toString()?.let { highlight -> val colorHighlight = R.attr.colorControlHighlight.resolveAttr(activity) b.messageSubject.text = b.messageSubject.text.asSpannable( - StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight), - substring = highlight, ignoreCase = true, ignoreDiacritics = true) + StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight), + substring = highlight, ignoreCase = true, ignoreDiacritics = true + ) b.messageSender.text = b.messageSender.text.asSpannable( - StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight), - substring = highlight, ignoreCase = true, ignoreDiacritics = true) + StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight), + substring = highlight, ignoreCase = true, ignoreDiacritics = true + ) } adapter.onItemClick?.let { listener -> diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/viewholder/SearchViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/viewholder/SearchViewHolder.kt index 5adb1984..a0a7d69c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/viewholder/SearchViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/viewholder/SearchViewHolder.kt @@ -9,38 +9,43 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.RecyclerView import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.databinding.MessagesListItemSearchBinding import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch +import pl.szczodrzynski.edziennik.ui.modules.messages.utils.SearchTextWatcher class SearchViewHolder( - inflater: LayoutInflater, - parent: ViewGroup, - val b: MessagesListItemSearchBinding = MessagesListItemSearchBinding.inflate(inflater, parent, false) + inflater: LayoutInflater, + parent: ViewGroup, + val b: MessagesListItemSearchBinding = MessagesListItemSearchBinding.inflate( + inflater, + parent, + false + ) ) : RecyclerView.ViewHolder(b.root), BindableViewHolder { companion object { private const val TAG = "SearchViewHolder" } - override fun onBind(activity: AppCompatActivity, app: App, item: MessagesSearch, position: Int, adapter: MessagesAdapter) { - b.searchEdit.removeTextChangedListener(adapter.textWatcher) - b.searchEdit.addTextChangedListener(adapter.textWatcher) + override fun onBind( + activity: AppCompatActivity, + app: App, + item: MessagesSearch, + position: Int, + adapter: MessagesAdapter + ) { + val watcher = SearchTextWatcher(b, adapter.filter, item) + b.searchEdit.removeTextChangedListener(watcher) - /*b.searchEdit.setOnKeyboardListener(object : TextInputKeyboardEdit.KeyboardListener { - override fun onStateChanged(keyboardEditText: TextInputKeyboardEdit, showing: Boolean) { - item.isFocused = showing - } - })*/ + if (adapter.items.isEmpty() || adapter.items.size == adapter.allItems.size) + b.searchLayout.helperText = " " + else + b.searchLayout.helperText = + b.root.context.getString(R.string.messages_search_results, adapter.items.size - 1) + b.searchEdit.setText(item.searchText) - /*if (b.searchEdit.text.toString() != item.searchText) { - b.searchEdit.setText(item.searchText) - b.searchEdit.setSelection(item.searchText.length) - }*/ - - //b.searchLayout.helperText = app.getString(R.string.messages_search_results, item.count) - - /*if (item.isFocused && !b.searchEdit.isFocused) - b.searchEdit.requestFocus()*/ + b.searchEdit.addTextChangedListener(watcher) } } From 47ec1899a1af548c17373650390dc1ed02845742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 14 Apr 2021 21:08:27 +0200 Subject: [PATCH 24/34] [UI/Messages] Add greeting text configuration dialog. --- .../edziennik/config/ProfileConfigUI.kt | 20 +++++ .../edziennik/data/db/entity/Profile.kt | 3 + .../ui/dialogs/MessagesConfigDialog.kt | 66 +++++++++++++++++ .../ui/modules/messages/MessageFragment.kt | 14 +++- .../ui/modules/messages/MessagesFragment.kt | 16 +++- .../compose/MessagesComposeFragment.kt | 44 +++++++++-- .../settings/cards/SettingsRegisterCard.kt | 8 ++ .../res/layout/messages_config_dialog.xml | 73 +++++++++++++++++++ app/src/main/res/values/strings.xml | 6 ++ 9 files changed, 239 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/MessagesConfigDialog.kt create mode 100644 app/src/main/res/layout/messages_config_dialog.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt index 5ce237a0..2b493572 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt @@ -19,4 +19,24 @@ class ProfileConfigUI(private val config: ProfileConfig) { var homeCards: List get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() } set(value) { config.set("homeCards", value); mHomeCards = value } + + private var mMessagesGreetingOnCompose: Boolean? = null + var messagesGreetingOnCompose: Boolean + get() { mMessagesGreetingOnCompose = mMessagesGreetingOnCompose ?: config.values.get("messagesGreetingOnCompose", true); return mMessagesGreetingOnCompose ?: true } + set(value) { config.set("messagesGreetingOnCompose", value); mMessagesGreetingOnCompose = value } + + private var mMessagesGreetingOnReply: Boolean? = null + var messagesGreetingOnReply: Boolean + get() { mMessagesGreetingOnReply = mMessagesGreetingOnReply ?: config.values.get("messagesGreetingOnReply", true); return mMessagesGreetingOnReply ?: true } + set(value) { config.set("messagesGreetingOnReply", value); mMessagesGreetingOnReply = value } + + private var mMessagesGreetingOnForward: Boolean? = null + var messagesGreetingOnForward: Boolean + get() { mMessagesGreetingOnForward = mMessagesGreetingOnForward ?: config.values.get("messagesGreetingOnForward", false); return mMessagesGreetingOnForward ?: false } + set(value) { config.set("messagesGreetingOnForward", value); mMessagesGreetingOnForward = value } + + private var mMessagesGreetingText: String? = null + var messagesGreetingText: String? + get() { mMessagesGreetingText = mMessagesGreetingText ?: config.values["messagesGreetingText"]; return mMessagesGreetingText } + set(value) { config.set("messagesGreetingText", value); mMessagesGreetingText = value } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt index 9e52dc81..b3586a79 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt @@ -129,6 +129,9 @@ open class Profile( val isParent get() = accountName != null + val accountOwnerName + get() = accountName ?: studentNameLong + val registerName get() = when (loginStoreType) { LOGIN_TYPE_LIBRUS -> "librus" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/MessagesConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/MessagesConfigDialog.kt new file mode 100644 index 00000000..e42bf61f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/MessagesConfigDialog.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-14. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.MessagesConfigDialogBinding + +class MessagesConfigDialog( + 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 = "MessagesConfigDialog" + } + + 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: MessagesConfigDialogBinding + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + b = MessagesConfigDialogBinding.inflate(activity.layoutInflater) + onShowListener?.invoke(TAG) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.menu_messages_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.greetingText.setText( + profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}" + ) + } + + private fun saveConfig() { + val greetingText = b.greetingText.text?.toString()?.trim() + if (greetingText.isNullOrEmpty()) + profileConfig.messagesGreetingText = null + else + profileConfig.messagesGreetingText = "\n\n$greetingText" + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessageFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessageFragment.kt index 30f40c30..e147dea6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessageFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessageFragment.kt @@ -30,10 +30,12 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding +import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog import pl.szczodrzynski.edziennik.utils.Anim import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.colorAttr import kotlin.coroutines.CoroutineContext import kotlin.math.min @@ -64,10 +66,20 @@ class MessageFragment : Fragment(), CoroutineScope { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { if (!isAdded) return + activity.bottomSheet.prependItem( + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_messages_config) + .withIcon(CommunityMaterial.Icon.cmd_cog_outline) + .withOnClickListener { + activity.bottomSheet.close() + MessagesConfigDialog(activity, false, null, null) + } + ) + b.closeButton.setImageDrawable( IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_window_close).apply { colorAttr(activity, android.R.attr.textColorSecondary) - sizeDp = 16 + sizeDp = 24 } ) b.closeButton.setOnClickListener { activity.navigateUp() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt index add36a3c..1849d2f8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt @@ -12,7 +12,9 @@ import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.databinding.MessagesFragmentBinding +import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import kotlin.coroutines.CoroutineContext class MessagesFragment : Fragment(), CoroutineScope { @@ -100,9 +102,19 @@ class MessagesFragment : Fragment(), CoroutineScope { fabIcon = CommunityMaterial.Icon3.cmd_pencil_outline } - setFabOnClickListener(View.OnClickListener { + bottomSheet.prependItem( + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_messages_config) + .withIcon(CommunityMaterial.Icon.cmd_cog_outline) + .withOnClickListener { + activity.bottomSheet.close() + MessagesConfigDialog(activity, false, null, null) + } + ) + + setFabOnClickListener { activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE) - }) + } } activity.gainAttentionFAB() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt index 051d86bd..091dabef 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt @@ -4,6 +4,7 @@ package pl.szczodrzynski.edziennik.ui.modules.messages.compose +import android.annotation.SuppressLint import android.content.Context import android.graphics.Typeface import android.graphics.drawable.BitmapDrawable @@ -43,6 +44,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding +import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage import pl.szczodrzynski.edziennik.utils.Colors @@ -50,6 +52,7 @@ import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes.getPrimaryTextColor import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.elevateSurface import kotlin.coroutines.CoroutineContext import kotlin.text.replace @@ -67,6 +70,10 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + private val profileConfig by lazy { app.config.forProfile().ui } + private val greetingText + get() = profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}" + private var teachers = mutableListOf() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -131,6 +138,16 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { } }*/ + activity.bottomSheet.prependItem( + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_messages_config) + .withIcon(CommunityMaterial.Icon.cmd_cog_outline) + .withOnClickListener { + activity.bottomSheet.close() + MessagesConfigDialog(activity, false, null, null) + } + ) + launch { delay(100) getRecipientList() @@ -290,7 +307,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { b.recipients.setIllegalCharacterIdentifier { c -> c.toString().matches("[\\n;:_ ]".toRegex()) } - b.recipients.setOnChipRemoveListener { _ -> + b.recipients.setOnChipRemoveListener { b.recipients.setSelection(b.recipients.text.length) } @@ -318,14 +335,15 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { fabExtendedText = getString(R.string.messages_compose_send) fabIcon = CommunityMaterial.Icon3.cmd_send_outline - setFabOnClickListener(View.OnClickListener { + setFabOnClickListener { sendMessage() - }) + } } activity.gainAttentionFAB() } + @SuppressLint("SetTextI18n") private fun updateRecipientList(list: List) { launch { withContext(Dispatchers.Default) { teachers = list.sortedBy { it.fullName }.toMutableList() @@ -344,11 +362,14 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { val adapter = MessagesComposeSuggestionAdapter(activity, teachers) b.recipients.setAdapter(adapter) + if (profileConfig.messagesGreetingOnCompose) + b.text.setText(greetingText) + handleReplyMessage() handleMailToIntent() }} - private fun handleReplyMessage() { launch { + private fun handleReplyMessage() = launch { val replyMessage = arguments?.getString("message") if (replyMessage != null) { val chipList = mutableListOf() @@ -370,8 +391,10 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { if (arguments?.getString("type") == "reply") { // add greeting text - span.replace(0, 0, "\n\nZ poważaniem,\n${app.profile.accountName - ?: app.profile.studentNameLong ?: ""}\n\n\n") + if (profileConfig.messagesGreetingOnReply) + span.replace(0, 0, "$greetingText\n\n\n") + else + span.replace(0, 0, "\n\n") teachers.firstOrNull { it.id == msg.senderId }?.let { teacher -> teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName) @@ -379,7 +402,12 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { } subject = "Re: ${msg.subject}" } else { - span.replace(0, 0, "\n\n") + // add greeting text + if (profileConfig.messagesGreetingOnForward) + span.replace(0, 0, "$greetingText\n\n\n") + else + span.replace(0, 0, "\n\n") + subject = "Fwd: ${msg.subject}" } body = MessagesUtils.htmlToSpannable(activity, msg.body @@ -401,7 +429,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { else { b.recipients.requestFocus() } - }} + } private fun handleMailToIntent() { val teacherId = arguments?.getLong("messageRecipientId") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsRegisterCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsRegisterCard.kt index 36239083..98f32de5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsRegisterCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsRegisterCard.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.after import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED +import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.bell.BellSyncConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradesConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog @@ -65,6 +66,13 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) { GradesConfigDialog(activity, reloadOnDismiss = false) }, + util.createActionItem( + text = R.string.menu_messages_config, + icon = CommunityMaterial.Icon.cmd_calendar_outline + ) { + MessagesConfigDialog(activity, reloadOnDismiss = false) + }, + util.createActionItem( text = R.string.menu_attendance_config, icon = CommunityMaterial.Icon.cmd_calendar_remove_outline diff --git a/app/src/main/res/layout/messages_config_dialog.xml b/app/src/main/res/layout/messages_config_dialog.xml new file mode 100644 index 00000000..d0205b39 --- /dev/null +++ b/app/src/main/res/layout/messages_config_dialog.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a008fc94..621b35ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1427,4 +1427,10 @@ Aby móc zapisać wygenerowany plan lekcji musisz przyznać uprawnienia dostępu do pamięci urządzenia.\n\nKliknij OK, aby przyznać uprawnienia. przeczytanie Polityki prywatności i akceptujesz jej postanowienia.

Autorzy aplikacji nie biorą odpowiedzialności za korzystanie z aplikacji Szkolny.eu.]]>
Szkolny.eu v%s\n%s + Tworzenie wiadomości + Dodaj podpis przy tworzeniu wiadomości + Dodaj podpis przy odpowiadaniu na wiadomość + Dodaj podpis przy przekazywaniu wiadomości + Treść podpisu + Ustawienia wiadomości From 8609956ae7d356e8560052fd20210f6a5d817251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 14 Apr 2021 22:41:06 +0200 Subject: [PATCH 25/34] [UI/Timetable] Add current time marker line. (#30) --- .../modules/timetable/TimetableDayFragment.kt | 143 +++++++++++------- .../drawable/timetable_marker_triangle.xml | 15 ++ .../res/layout/fragment_timetable_v2_day.xml | 32 ---- .../res/layout/timetable_day_fragment.xml | 53 +++++++ 4 files changed, 157 insertions(+), 86 deletions(-) create mode 100644 app/src/main/res/drawable/timetable_marker_triangle.xml delete mode 100644 app/src/main/res/layout/fragment_timetable_v2_day.xml create mode 100644 app/src/main/res/layout/timetable_day_fragment.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt index e92ca55c..3fc9ce83 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt @@ -5,16 +5,16 @@ package pl.szczodrzynski.edziennik.ui.modules.timetable import android.os.Bundle -import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import android.widget.ProgressBar import android.widget.TextView import androidx.asynclayoutinflater.view.AsyncLayoutInflater +import androidx.core.view.isVisible +import androidx.core.view.marginTop 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.DayViewConfig 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.full.EventFull 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.TimetableNoTimetableBinding import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog 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_START_HOUR -import pl.szczodrzynski.edziennik.utils.ListenerScrollView import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time import java.util.* import kotlin.coroutines.CoroutineContext import kotlin.math.min @@ -44,76 +45,66 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: MainActivity private lateinit var inflater: AsyncLayoutInflater + private lateinit var b: TimetableDayFragmentBinding private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + private var timeIndicatorJob: Job? = null + private lateinit var date: Date private var startHour = DEFAULT_START_HOUR private var endHour = DEFAULT_END_HOUR private var firstEventMinute = 24 * 60 + private var paddingTop = 0 private val manager get() = app.timetableManager // find SwipeRefreshLayout in the hierarchy 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 { - val dayView = DayView(context!!, DayViewConfig( - startHour = startHour, - endHour = endHour, - dividerHeight = 1.dp, - halfHourHeight = 60.dp, - hourDividerColor = R.attr.hourDividerColor.resolveAttr(context), - halfHourDividerColor = R.attr.halfHourDividerColor.resolveAttr(context), - hourLabelWidth = 40.dp, - hourLabelMarginEnd = 10.dp, - eventMargin = 2.dp + val dayView = DayView(activity, DayViewConfig( + startHour = startHour, + endHour = endHour, + dividerHeight = 1.dp, + halfHourHeight = 60.dp, + hourDividerColor = R.attr.hourDividerColor.resolveAttr(context), + halfHourDividerColor = R.attr.halfHourDividerColor.resolveAttr(context), + hourLabelWidth = 40.dp, + hourLabelMarginEnd = 10.dp, + eventMargin = 2.dp ), true) dayView.setPadding(10.dp) - dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - dayScroll.addView(dayView) - dayView + return@lazy dayView } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as MainActivity?) ?: return null context ?: return null app = activity.application as App - this.inflater = AsyncLayoutInflater(context!!) + this.inflater = AsyncLayoutInflater(requireContext()) + date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday() startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR - return FrameLayout(activity).apply { - layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) - addView(ProgressBar(activity).apply { - layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER) - }) - } + + b = TimetableDayFragmentBinding.inflate(inflater, null, false) + return b.root } override fun onPageCreated(): Boolean { // 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 { val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllByDateNow(App.profileId, date) } processLessonList(lessons, events) } - }) + } return true } @@ -121,9 +112,10 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { private fun processLessonList(lessons: List, events: List) { // no lessons - timetable not downloaded yet if (lessons.isEmpty()) { - inflater.inflate(R.layout.timetable_no_timetable, view as FrameLayout?) { view, _, parent -> - parent?.removeAllViews() - parent?.addView(view) + inflater.inflate(R.layout.timetable_no_timetable, b.root) { view, _, _ -> + b.root.removeAllViews() + b.root.addView(view) + val b = TimetableNoTimetableBinding.bind(view) val weekStart = date.weekStart.stringY_m_d b.noTimetableSync.onClick { @@ -144,9 +136,9 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { } // one lesson indicating a day without lessons if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) { - inflater.inflate(R.layout.timetable_no_lessons, view as FrameLayout?) { view, _, parent -> - parent?.removeAllViews() - parent?.addView(view) + inflater.inflate(R.layout.timetable_no_lessons, b.root) { view, _, _ -> + b.root.removeAllViews() + b.root.addView(view) } return } @@ -158,12 +150,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { return } - // clear the root view and add the ScrollView - (view as FrameLayout?)?.removeAllViews() - (view as FrameLayout?)?.addView(dayScroll) + b.scrollView.isVisible = true + b.dayFrame.removeView(b.dayView) + b.dayFrame.addView(dayView, 0) // Inflate a label view for each hour the day view will display - val hourLabelViews = ArrayList() + val hourLabelViews = mutableListOf() for (i in dayView.startHour..dayView.endHour) { if (!isAdded) continue @@ -172,6 +164,11 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { hourLabelViews.add(hourLabelView) } 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 } @@ -202,8 +199,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { // Try to recycle an existing event view if there are enough left, otherwise inflate // a new one - val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, dayView, false)) - ?: continue + val eventView = + (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate( + R.layout.timetable_lesson, + dayView, + false + )) ?: continue val lb = TimetableLessonBinding.bind(eventView) eventViews += eventView @@ -291,16 +292,50 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute)) } + updateTimeIndicator() + dayView.setEventViews(eventViews, eventTimeRanges) 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 { + topMargin = (seconds * dayView.minuteHeight / 60f).toInt() + paddingTop + } + b.timeIndicatorMarker.updateLayoutParams { + topMargin = b.timeIndicator.marginTop - (16.dp / 2) + (1.dp / 2) + } + } + + if (timeIndicatorJob == null) { + timeIndicatorJob = startCoroutineTimer(repeatMillis = 30000) { + updateTimeIndicator() + } + } } override fun onResume() { super.onResume() - if (dayScrollDelegate.isInitialized()) { - val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight - dayScroll.scrollTo(0, firstEventTop.toInt()) - } + val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight + b.scrollView.scrollTo(0, firstEventTop.toInt()) + updateTimeIndicator() + } + + override fun onPause() { + super.onPause() + timeIndicatorJob?.cancel() + timeIndicatorJob = null } } diff --git a/app/src/main/res/drawable/timetable_marker_triangle.xml b/app/src/main/res/drawable/timetable_marker_triangle.xml new file mode 100644 index 00000000..97d2e71b --- /dev/null +++ b/app/src/main/res/drawable/timetable_marker_triangle.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/app/src/main/res/layout/fragment_timetable_v2_day.xml b/app/src/main/res/layout/fragment_timetable_v2_day.xml deleted file mode 100644 index a94602d3..00000000 --- a/app/src/main/res/layout/fragment_timetable_v2_day.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/timetable_day_fragment.xml b/app/src/main/res/layout/timetable_day_fragment.xml new file mode 100644 index 00000000..5464482d --- /dev/null +++ b/app/src/main/res/layout/timetable_day_fragment.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + From a31c68e87ae3918466b450112c32435662131bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 19 Apr 2021 14:18:28 +0200 Subject: [PATCH 26/34] [Agenda] Add e-learning event type and DB migration. --- .idea/dictionaries/Kuba.xml | 1 + .../szczodrzynski/edziennik/data/db/AppDb.kt | 5 +- .../edziennik/data/db/dao/EventTypeDao.kt | 48 +++------- .../edziennik/data/db/entity/Event.kt | 4 +- .../edziennik/data/db/entity/EventType.java | 35 -------- .../edziennik/data/db/entity/EventType.kt | 89 +++++++++++++++++++ .../data/db/migration/Migration92.kt | 60 +++++++++++++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 169 insertions(+), 74 deletions(-) delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration92.kt diff --git a/.idea/dictionaries/Kuba.xml b/.idea/dictionaries/Kuba.xml index 7910687c..592a5d5e 100644 --- a/.idea/dictionaries/Kuba.xml +++ b/.idea/dictionaries/Kuba.xml @@ -5,6 +5,7 @@ ciasteczko csrf edziennik + elearning gson hebe idziennik diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt index e6d7e40e..66fa4348 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt @@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.* LibrusLesson::class, TimetableManual::class, Metadata::class -], version = 91) +], version = 92) @TypeConverters( ConverterTime::class, ConverterDate::class, @@ -176,7 +176,8 @@ abstract class AppDb : RoomDatabase() { Migration88(), Migration89(), Migration90(), - Migration91() + Migration91(), + Migration92() ).allowMainThreadQueries().build() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt index 28be6d65..d5006970 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt @@ -9,30 +9,9 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING -import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ import pl.szczodrzynski.edziennik.data.db.entity.EventType +import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT @Dao abstract class EventTypeDao { @@ -58,19 +37,18 @@ abstract class EventTypeDao { abstract val allNow: List fun addDefaultTypes(context: Context, profileId: Int): List { - val typeList = listOf( - EventType(profileId, TYPE_HOMEWORK, context.getString(R.string.event_type_homework), COLOR_HOMEWORK), - EventType(profileId, TYPE_DEFAULT, context.getString(R.string.event_other), COLOR_DEFAULT), - EventType(profileId, TYPE_EXAM, context.getString(R.string.event_exam), COLOR_EXAM), - EventType(profileId, TYPE_SHORT_QUIZ, context.getString(R.string.event_short_quiz), COLOR_SHORT_QUIZ), - EventType(profileId, TYPE_ESSAY, context.getString(R.string.event_essay), COLOR_ESSAY), - EventType(profileId, TYPE_PROJECT, context.getString(R.string.event_project), COLOR_PROJECT), - EventType(profileId, TYPE_PT_MEETING, context.getString(R.string.event_pt_meeting), COLOR_PT_MEETING), - EventType(profileId, TYPE_EXCURSION, context.getString(R.string.event_excursion), COLOR_EXCURSION), - EventType(profileId, TYPE_READING, context.getString(R.string.event_reading), COLOR_READING), - EventType(profileId, TYPE_CLASS_EVENT, context.getString(R.string.event_class_event), COLOR_CLASS_EVENT), - EventType(profileId, TYPE_INFORMATION, context.getString(R.string.event_information), COLOR_INFORMATION) - ) + var order = 100 + val colorMap = EventType.getTypeColorMap() + val typeList = EventType.getTypeNameMap().map { (id, name) -> + EventType( + profileId = profileId, + id = id, + name = context.getString(name), + color = colorMap[id] ?: COLOR_DEFAULT, + order = order++, + source = SOURCE_DEFAULT + ) + } addAll(typeList) return typeList } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt index a3f44585..3e59cc17 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt @@ -45,6 +45,7 @@ open class Event( var addedDate: Long = System.currentTimeMillis() ) : Keepable() { companion object { + const val TYPE_ELEARNING = -5L const val TYPE_UNDEFINED = -2L const val TYPE_HOMEWORK = -1L const val TYPE_DEFAULT = 0L @@ -57,7 +58,7 @@ open class Event( const val TYPE_READING = 7L const val TYPE_CLASS_EVENT = 8L const val TYPE_INFORMATION = 9L - const val TYPE_TEACHER_ABSENCE = 10L + const val COLOR_ELEARNING = 0xfff57f17.toInt() const val COLOR_HOMEWORK = 0xff795548.toInt() const val COLOR_DEFAULT = 0xffffc107.toInt() const val COLOR_EXAM = 0xfff44336.toInt() @@ -69,7 +70,6 @@ open class Event( const val COLOR_READING = 0xFFFFEB3B.toInt() const val COLOR_CLASS_EVENT = 0xff388e3c.toInt() const val COLOR_INFORMATION = 0xff039be5.toInt() - const val COLOR_TEACHER_ABSENCE = 0xff039be5.toInt() } @ColumnInfo(name = "eventAddedManually") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.java deleted file mode 100644 index bf981e84..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.entity; - -import android.graphics.Color; - -import androidx.room.ColumnInfo; -import androidx.room.Entity; - -@Entity(tableName = "eventTypes", - primaryKeys = {"profileId", "eventType"}) -public class EventType { - public int profileId; - - @ColumnInfo(name = "eventType") - public long id; - - @ColumnInfo(name = "eventTypeName") - public String name; - @ColumnInfo(name = "eventTypeColor") - public int color; - - public EventType(int profileId, long id, String name, int color) { - this.profileId = profileId; - this.id = id; - this.name = name; - this.color = color; - } - - public EventType(int profileId, int id, String name, String color) { - this(profileId, id, name, Color.parseColor(color)); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.kt new file mode 100644 index 00000000..7753b38a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/EventType.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-19. + */ +package pl.szczodrzynski.edziennik.data.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ELEARNING +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ELEARNING +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ + +@Entity( + tableName = "eventTypes", + primaryKeys = ["profileId", "eventType"] +) +class EventType( + val profileId: Int, + + @ColumnInfo(name = "eventType") + val id: Long, + + @ColumnInfo(name = "eventTypeName") + val name: String, + @ColumnInfo(name = "eventTypeColor") + val color: Int, + @ColumnInfo(name = "eventTypeOrder") + var order: Int = id.toInt(), + @ColumnInfo(name = "eventTypeSource") + val source: Int = SOURCE_REGISTER +) { + companion object { + const val SOURCE_DEFAULT = 0 + const val SOURCE_REGISTER = 1 + const val SOURCE_CUSTOM = 2 + const val SOURCE_SHARED = 3 + + fun getTypeColorMap() = mapOf( + TYPE_ELEARNING to COLOR_ELEARNING, + TYPE_HOMEWORK to COLOR_HOMEWORK, + TYPE_DEFAULT to COLOR_DEFAULT, + TYPE_EXAM to COLOR_EXAM, + TYPE_SHORT_QUIZ to COLOR_SHORT_QUIZ, + TYPE_ESSAY to COLOR_ESSAY, + TYPE_PROJECT to COLOR_PROJECT, + TYPE_PT_MEETING to COLOR_PT_MEETING, + TYPE_EXCURSION to COLOR_EXCURSION, + TYPE_READING to COLOR_READING, + TYPE_CLASS_EVENT to COLOR_CLASS_EVENT, + TYPE_INFORMATION to COLOR_INFORMATION + ) + + fun getTypeNameMap() = mapOf( + TYPE_ELEARNING to R.string.event_type_elearning, + TYPE_HOMEWORK to R.string.event_type_homework, + TYPE_DEFAULT to R.string.event_other, + TYPE_EXAM to R.string.event_exam, + TYPE_SHORT_QUIZ to R.string.event_short_quiz, + TYPE_ESSAY to R.string.event_essay, + TYPE_PROJECT to R.string.event_project, + TYPE_PT_MEETING to R.string.event_pt_meeting, + TYPE_EXCURSION to R.string.event_excursion, + TYPE_READING to R.string.event_reading, + TYPE_CLASS_EVENT to R.string.event_class_event, + TYPE_INFORMATION to R.string.event_information + ) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration92.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration92.kt new file mode 100644 index 00000000..4b36c0e9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration92.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-4-15. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import android.content.ContentValues +import android.database.sqlite.SQLiteDatabase +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ELEARNING +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ELEARNING +import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION +import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT +import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_REGISTER +import pl.szczodrzynski.edziennik.getInt + +class Migration92 : Migration(91, 92) { + override fun migrate(database: SupportSQLiteDatabase) { + // make eventTypeName not nullable + database.execSQL("ALTER TABLE eventTypes RENAME TO _eventTypes;") + database.execSQL("CREATE TABLE eventTypes (" + + "profileId INTEGER NOT NULL, " + + "eventType INTEGER NOT NULL, " + + "eventTypeName TEXT NOT NULL, " + + "eventTypeColor INTEGER NOT NULL, " + + "PRIMARY KEY(profileId,eventType)" + + ");") + database.execSQL("INSERT INTO eventTypes " + + "(profileId, eventType, eventTypeName, eventTypeColor) " + + "SELECT profileId, eventType, eventTypeName, eventTypeColor " + + "FROM _eventTypes;") + database.execSQL("DROP TABLE _eventTypes;") + + // add columns for order and source + database.execSQL("ALTER TABLE eventTypes ADD COLUMN eventTypeOrder INTEGER NOT NULL DEFAULT 0;") + database.execSQL("ALTER TABLE eventTypes ADD COLUMN eventTypeSource INTEGER NOT NULL DEFAULT 0;") + + // migrate existing types to show correct order and source + database.execSQL("UPDATE eventTypes SET eventTypeOrder = eventType + 102;") + database.execSQL("UPDATE eventTypes SET eventTypeSource = $SOURCE_REGISTER WHERE eventType > $TYPE_INFORMATION;") + + // add new e-learning type + val cursor = database.query("SELECT profileId FROM profiles;") + cursor.use { + while (it.moveToNext()) { + val values = ContentValues().apply { + put("profileId", it.getInt("profileId")) + put("eventType", TYPE_ELEARNING) + put("eventTypeName", "lekcja online") + put("eventTypeColor", COLOR_ELEARNING) + put("eventTypeOrder", 100) + put("eventTypeSource", SOURCE_DEFAULT) + } + + database.insert("eventTypes", SQLiteDatabase.CONFLICT_REPLACE, values) + } + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31caf032..566be56b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1450,4 +1450,5 @@ Dodaj podpis przy przekazywaniu wiadomości Treść podpisu Ustawienia wiadomości + lekcja online From c855f08f9cae20f467e1f0b8757b8ac4a541e31f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 26 May 2021 17:46:03 +0200 Subject: [PATCH 27/34] [API/Mobidziennik] Update messages search query to contain student name. --- .../mobidziennik/data/web/MobidziennikWebMessagesAll.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt index 4e8f900a..5581dcc5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebMessagesAll.kt @@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.fixName import pl.szczodrzynski.edziennik.singleOrNull import pl.szczodrzynski.edziennik.utils.models.Date +import java.net.URLEncoder class MobidziennikWebMessagesAll(override val data: DataMobidziennik, override val lastSync: Long?, @@ -27,7 +28,8 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik, } init { - webGet(TAG, "/dziennik/wyszukiwarkawiadomosci?q=+") { text -> + val query = URLEncoder.encode(data.profile?.studentNameLong ?: "a", "UTF-8") + webGet(TAG, "/dziennik/wyszukiwarkawiadomosci?q=$query") { text -> MobidziennikLuckyNumberExtractor(data, text) val doc = Jsoup.parse(text) From c85dac2e4de4dde6a7a6f811d4d497edde314081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 26 May 2021 18:00:37 +0200 Subject: [PATCH 28/34] [Strings] Update copyright dates. Fix event mark as done translation. --- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-en/strings.xml | 4 ++-- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3af14e8f..431fd960 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -856,7 +856,7 @@ Open-Source-Lizenzen Datenschutzrichtlinie E-Klassenbuch - © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - April 2021 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - Mai 2021 Klicken Sie hier, um nach Aktualisierungen zu suchen Aktualisierung Version diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 4c0c5b89..e519d272 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -356,7 +356,7 @@ Sharing event… Removing event from the rest of the class… Removing shared event… - Are you sure you want to mark this task as completed?\n\nIt won\'t show up on the main or homework pages, but will still be available in the timetable + Are you sure you want to mark this task as completed?\n\nIt won\'t show up on the main or homework pages, but will still be available in the agenda. Mark as done other project @@ -858,7 +858,7 @@ Open-source licenses Privacy policy E-register - © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - April 2021 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - May 2021 Click to check for updates Update Version diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31caf032..8f0ae03f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -921,7 +921,7 @@ Licencje open-source Polityka prywatności E-dziennik - © Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - kwiecień 2021 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - maj 2021 Kliknij, aby sprawdzić aktualizacje Aktualizacja Wersja From 85d74bec1c1deed645e5a06a2c82c2916f431ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 26 May 2021 19:00:09 +0200 Subject: [PATCH 29/34] [UI] Add long text for notification descriptions. --- .../edziennik/data/api/task/Notifications.kt | 61 ++++++++++++++++++- .../data/api/task/PostNotifications.kt | 2 +- .../edziennik/data/db/entity/Notification.kt | 1 + .../WidgetNotificationsFactory.kt | 3 +- app/src/main/res/values/strings.xml | 6 ++ 5 files changed, 70 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt index 21a5c112..28b5c9e9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt @@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.* import pl.szczodrzynski.edziennik.getNotificationTitle import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Week class Notifications(val app: App, val notifications: MutableList, val profiles: List) { companion object { @@ -42,13 +43,22 @@ class Notifications(val app: App, val notifications: MutableList, val text = app.getString( R.string.notification_lesson_change_format, lesson.getDisplayChangeType(app), - if (lesson.displayDate == null) "" else lesson.displayDate!!.formattedString, + lesson.displayDate?.formattedString ?: "", lesson.changeSubjectName ) + val textLong = app.getString( + R.string.notification_lesson_change_long_format, + lesson.getDisplayChangeType(app), + lesson.displayDate?.formattedString ?: "-", + lesson.displayDate?.weekDay?.let { Week.getFullDayName(it) } ?: "-", + lesson.changeSubjectName, + lesson.changeTeacherName + ) notifications += Notification( id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id), title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE), text = text, + textLong = textLong, type = Notification.TYPE_TIMETABLE_LESSON_CHANGE, profileId = lesson.profileId, profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name, @@ -79,11 +89,21 @@ class Notifications(val app: App, val notifications: MutableList, event.date.formattedString, event.subjectLongName ) + val textLong = app.getString( + R.string.notification_event_long_format, + event.typeName ?: "-", + event.subjectLongName ?: "-", + event.date.formattedString, + Week.getFullDayName(event.date.weekDay), + event.time?.stringHM ?: app.getString(R.string.event_all_day), + event.topic.take(200) + ) val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT notifications += Notification( id = Notification.buildId(event.profileId, type, event.id), title = app.getNotificationTitle(type), text = text, + textLong = textLong, type = type, profileId = event.profileId, profileName = profiles.singleOrNull { it.id == event.profileId }?.name, @@ -102,11 +122,22 @@ class Notifications(val app: App, val notifications: MutableList, event.date.formattedString, event.topic ) + val textLong = app.getString( + R.string.notification_shared_event_long_format, + event.sharedByName, + event.typeName ?: "-", + event.subjectLongName ?: "-", + event.date.formattedString, + Week.getFullDayName(event.date.weekDay), + event.time?.stringHM ?: app.getString(R.string.event_all_day), + event.topic.take(200) + ) val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT notifications += Notification( id = Notification.buildId(event.profileId, type, event.id), title = app.getNotificationTitle(type), text = text, + textLong = textLong, type = type, profileId = event.profileId, profileName = profiles.singleOrNull { it.id == event.profileId }?.name, @@ -130,10 +161,20 @@ class Notifications(val app: App, val notifications: MutableList, gradeName, grade.subjectLongName ) + val textLong = app.getString( + R.string.notification_grade_long_format, + gradeName, + grade.weight.toString(), + grade.subjectLongName ?: "-", + grade.category ?: "-", + grade.description ?: "-", + grade.teacherName ?: "-" + ) notifications += Notification( id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id), title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE), text = text, + textLong = textLong, type = Notification.TYPE_NEW_GRADE, profileId = grade.profileId, profileName = profiles.singleOrNull { it.id == grade.profileId }?.name, @@ -158,10 +199,17 @@ class Notifications(val app: App, val notifications: MutableList, notice.teacherName, Date.fromMillis(notice.addedDate).formattedString ) + val textLong = app.getString( + R.string.notification_notice_long_format, + noticeTypeStr, + notice.teacherName ?: "-", + notice.text.take(200) + ) notifications += Notification( id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id), title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE), text = text, + textLong = textLong, type = Notification.TYPE_NEW_NOTICE, profileId = notice.profileId, profileName = profiles.singleOrNull { it.id == notice.profileId }?.name, @@ -193,10 +241,21 @@ class Notifications(val app: App, val notifications: MutableList, attendance.subjectLongName, attendance.date.formattedString ) + val textLong = app.getString( + R.string.notification_attendance_long_format, + attendanceTypeStr, + attendance.date.formattedString, + attendance.startTime?.stringHM ?: "-", + attendance.lessonNumber ?: "-", + attendance.subjectLongName ?: "-", + attendance.teacherName ?: "-", + attendance.lessonTopic ?: "-" + ) notifications += Notification( id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id), title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE), text = text, + textLong = textLong, type = Notification.TYPE_NEW_ATTENDANCE, profileId = attendance.profileId, profileName = profiles.singleOrNull { it.id == attendance.profileId }?.name, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt index 28e62d59..6597125a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/PostNotifications.kt @@ -148,7 +148,7 @@ class PostNotifications(val app: App, nList: List) { colorRes = R.color.colorPrimary }.toBitmap()) .setStyle(NotificationCompat.BigTextStyle() - .bigText(it.text)) + .bigText(it.textLong ?: it.text)) .setWhen(it.addedDate) .addDefaults() .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt index 6a2a787b..0a049216 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt @@ -21,6 +21,7 @@ data class Notification( val title: String, val text: String, + val textLong: String? = null, val type: Int, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsFactory.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsFactory.kt index b5207300..1b6d58e0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsFactory.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/notifications/WidgetNotificationsFactory.kt @@ -45,6 +45,7 @@ class WidgetNotificationsFactory(val app: App, val config: WidgetConfig) : Remot getLong("id") ?: 0, getString("title") ?: "", getString("text") ?: "", + getString("textLong"), getInt("type") ?: 0, getInt("profileId"), getString("profileName"), @@ -74,4 +75,4 @@ class WidgetNotificationsFactory(val app: App, val config: WidgetConfig) : Remot override fun hasStableIds() = true override fun getViewTypeCount() = 1 override fun onDestroy() = cursor?.close() ?: Unit -} \ No newline at end of file +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 42be8272..5aa206af 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1451,4 +1451,10 @@ Treść podpisu Ustawienia wiadomości lekcja online + Rodzaj: %s\nData: %s (%s)\nLekcja: %s\nNauczyciel: %s + Rodzaj: %s\nPrzedmiot: %s\nTermin: %s (%s), %s\nTreść: %s + Dodane przez: %s\nRodzaj: %s\nPrzedmiot: %s\nTermin: %s (%s), %s\nTreść: %s + Ocena: %s (waga %s)\nPrzedmiot: %s\nKategoria: %s\nOpis: %s\nNauczyciel: %s + Rodzaj: %s\nNauczyciel: %s\nTreść: %s + Rodzaj: %s\nTermin: %s, %s\nNr lekcji: %s\nPrzedmiot: %s\nNauczyciel: %s\nTemat lekcji: %s From 26645ee83cea0b274adc28a0fd2fb0aa1d4dfc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 26 May 2021 19:00:24 +0200 Subject: [PATCH 30/34] [DB] Add migration 93. --- .../pl/szczodrzynski/edziennik/data/db/AppDb.kt | 5 +++-- .../edziennik/data/db/migration/Migration93.kt | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration93.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt index 66fa4348..4547d852 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt @@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.* LibrusLesson::class, TimetableManual::class, Metadata::class -], version = 92) +], version = 93) @TypeConverters( ConverterTime::class, ConverterDate::class, @@ -177,7 +177,8 @@ abstract class AppDb : RoomDatabase() { Migration89(), Migration90(), Migration91(), - Migration92() + Migration92(), + Migration93() ).allowMainThreadQueries().build() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration93.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration93.kt new file mode 100644 index 00000000..9d5df5b3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration93.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-5-26. + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration93 : Migration(92, 93) { + override fun migrate(database: SupportSQLiteDatabase) { + // notifications - long text + database.execSQL("ALTER TABLE notifications ADD COLUMN textLong TEXT DEFAULT NULL;") + } +} From baa98f25c5987e0eea967ee7f00c96ca3c57de5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 26 May 2021 19:09:10 +0200 Subject: [PATCH 31/34] [UI] Fix easter egg prize receiving. --- .../edziennik/ui/modules/login/LoginEggsFragment.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEggsFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEggsFragment.kt index 0c11bb5a..a6b688b2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEggsFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEggsFragment.kt @@ -95,8 +95,10 @@ class LoginEggsFragment : Fragment(), CoroutineScope { anim.interpolator = AccelerateDecelerateInterpolator() anim.duration = 10 anim.fillAfter = true - activity.getRootView().startAnimation(anim) - nav.navigate(R.id.loginPrizeFragment, null, activity.navOptions) + activity.runOnUiThread { + activity.getRootView().startAnimation(anim) + nav.navigate(R.id.loginPrizeFragment, null, activity.navOptions) + } } }, "EggInterface") loadUrl("https://szkolny.eu/game/runner.html") From 75010c07714915bce16178e97a9809add94a8dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 26 May 2021 20:01:08 +0200 Subject: [PATCH 32/34] [4.8] Update build.gradle, signing and changelog. --- app/src/main/assets/pl-changelog.html | 12 +++++++++--- app/src/main/cpp/szkolny-signing.cpp | 2 +- .../data/api/szkolny/interceptor/Signing.kt | 2 +- build.gradle | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index db18101b..27c85f0f 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,7 +1,13 @@ -

Wersja 4.7.1, 2021-04-12

+

Wersja 4.8, 2021-05-26

    -
  • Poprawiono sprawdzanie dostępności e-dziennika.
  • -
  • Zmieniono datę w informacjach o aplikacji. @Luncenok
  • +
  • Dodano ikony dla powiadomień. @Luncenok
  • +
  • Terminarz: opcje konfiguracji, widok kompaktowy, grupowanie wydarzeń, znaczki nieprzeczytanych, nowe ikony i wiele innych usprawnień.
  • +
  • Wiadomości: usprawiono wyszukiwanie - zapisywanie szukanego tekstu po wejściu w wiadomość.
  • +
  • Wiadomości: dodano opcję konfiguracji podpisu przy wysyłaniu wiadomości.
  • +
  • Plan lekcji: dodano znacznik aktualnej pory dnia w planie lekcji.
  • +
  • Powiadomienia: dodano szczegółowy opis po rozwinięciu.
  • +
  • Wydarzenia: nowy rodzaj "lekcja online".
  • +
  • Naprawiono odbieranie nagrody w easter egg'u.


diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp index e2311acc..9af553cb 100644 --- a/app/src/main/cpp/szkolny-signing.cpp +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -9,7 +9,7 @@ /*secret password - removed for source code publication*/ static toys AES_IV[16] = { - 0xcc, 0x64, 0xdb, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + 0x71, 0xcf, 0xdf, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt index d07afeb7..a6095449 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt @@ -46,6 +46,6 @@ object Signing { /*fun provideKey(param1: String, param2: Long): ByteArray {*/ fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { - return "$param1.MTIzNDU2Nzg5MDXHhAtZBW===.$param2".sha256() + return "$param1.MTIzNDU2Nzg5MDZ/2nExVD===.$param2".sha256() } } diff --git a/build.gradle b/build.gradle index 2e05ee75..a2eb932b 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { kotlin_version = '1.4.31' release = [ - versionName: "4.7.1", - versionCode: 4070199 + versionName: "4.8", + versionCode: 4080099 ] setup = [ From 4184fbb2cd7bde19fc0447057b0e4373db51b03f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 26 May 2021 20:44:48 +0200 Subject: [PATCH 33/34] [Actions] Change Firebase token to service account file. --- .github/workflows/build-nightly-apk.yml | 2 +- .github/workflows/build-release-apk.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-nightly-apk.yml b/.github/workflows/build-nightly-apk.yml index bca267be..f04c5564 100644 --- a/.github/workflows/build-nightly-apk.yml +++ b/.github/workflows/build-nightly-apk.yml @@ -124,7 +124,7 @@ jobs: uses: wzieba/Firebase-Distribution-Github-Action@v1 with: appId: ${{ secrets.FIREBASE_APP_ID }} - token: ${{ secrets.FIREBASE_TOKEN }} + serviceCredentialsFile: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_JSON }} groups: ${{ secrets.FIREBASE_GROUPS_NIGHTLY }} file: ${{ needs.sign.outputs.signedReleaseFile }} releaseNotesFile: ${{ steps.changelog.outputs.commitLogPlainFile }} diff --git a/.github/workflows/build-release-apk.yml b/.github/workflows/build-release-apk.yml index 169704c1..1589290c 100644 --- a/.github/workflows/build-release-apk.yml +++ b/.github/workflows/build-release-apk.yml @@ -116,7 +116,7 @@ jobs: uses: wzieba/Firebase-Distribution-Github-Action@v1 with: appId: ${{ secrets.FIREBASE_APP_ID }} - token: ${{ secrets.FIREBASE_TOKEN }} + serviceCredentialsFile: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_JSON }} groups: ${{ secrets.FIREBASE_GROUPS_RELEASE }} file: ${{ needs.sign.outputs.signedReleaseFile }} releaseNotesFile: ${{ steps.changelog.outputs.changelogPlainTitledFile }} From 909899612efb84507cb9f85ca1e0aaf3d5bb4a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 26 May 2021 20:44:48 +0200 Subject: [PATCH 34/34] Revert "[Actions] Change Firebase token to service account file." This reverts commit 4184fbb2cd7bde19fc0447057b0e4373db51b03f. --- .github/workflows/build-nightly-apk.yml | 2 +- .github/workflows/build-release-apk.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-nightly-apk.yml b/.github/workflows/build-nightly-apk.yml index f04c5564..bca267be 100644 --- a/.github/workflows/build-nightly-apk.yml +++ b/.github/workflows/build-nightly-apk.yml @@ -124,7 +124,7 @@ jobs: uses: wzieba/Firebase-Distribution-Github-Action@v1 with: appId: ${{ secrets.FIREBASE_APP_ID }} - serviceCredentialsFile: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_JSON }} + token: ${{ secrets.FIREBASE_TOKEN }} groups: ${{ secrets.FIREBASE_GROUPS_NIGHTLY }} file: ${{ needs.sign.outputs.signedReleaseFile }} releaseNotesFile: ${{ steps.changelog.outputs.commitLogPlainFile }} diff --git a/.github/workflows/build-release-apk.yml b/.github/workflows/build-release-apk.yml index 1589290c..169704c1 100644 --- a/.github/workflows/build-release-apk.yml +++ b/.github/workflows/build-release-apk.yml @@ -116,7 +116,7 @@ jobs: uses: wzieba/Firebase-Distribution-Github-Action@v1 with: appId: ${{ secrets.FIREBASE_APP_ID }} - serviceCredentialsFile: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_JSON }} + token: ${{ secrets.FIREBASE_TOKEN }} groups: ${{ secrets.FIREBASE_GROUPS_RELEASE }} file: ${{ needs.sign.outputs.signedReleaseFile }} releaseNotesFile: ${{ steps.changelog.outputs.changelogPlainTitledFile }}