From b95b52901541a184d8d81bd03c8f44cdd6c9bd3f Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Wed, 20 May 2020 16:06:24 +0200 Subject: [PATCH] Add lesson time left display (#550) --- .../preferences/PreferencesRepository.kt | 3 + .../login/recover/LoginRecoverFragment.kt | 2 + .../ui/modules/timetable/TimetableAdapter.kt | 125 +++++++++++++++--- .../ui/modules/timetable/TimetableFragment.kt | 7 +- .../modules/timetable/TimetablePresenter.kt | 2 +- .../ui/modules/timetable/TimetableView.kt | 2 +- .../wulkanowy/utils/TimetableExtension.kt | 29 ++++ .../background_timetable_time_left.xml | 6 + app/src/main/res/layout/item_timetable.xml | 56 +++++++- app/src/main/res/values-pl/strings.xml | 6 + .../main/res/values/preferences_defaults.xml | 1 + app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/xml/scheme_preferences.xml | 5 + .../repositories => }/TestEnityCreator.kt | 29 +++- .../attendance/AttendanceRemoteTest.kt | 3 +- .../CompletedLessonsRemoteTest.kt | 2 +- .../data/repositories/exam/ExamRemoteTest.kt | 2 +- .../GradeStatisticsRemoteTest.kt | 2 +- .../luckynumber/LuckyNumberRemoteTest.kt | 2 +- .../MobileDeviceRepositoryTest.kt | 2 +- .../semester/SemesterRepositoryTest.kt | 2 +- .../timetable/TimetableRemoteTest.kt | 2 +- .../modules/grade/GradeAverageProviderTest.kt | 2 +- .../wulkanowy/utils/TimetableExtensionTest.kt | 43 ++++++ 25 files changed, 309 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt create mode 100644 app/src/main/res/drawable/background_timetable_time_left.xml rename app/src/test/java/io/github/wulkanowy/{data/repositories => }/TestEnityCreator.kt (67%) create mode 100644 app/src/test/java/io/github/wulkanowy/utils/TimetableExtensionTest.kt diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt index b916bf96f..6b13563a4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt @@ -75,6 +75,9 @@ class PreferencesRepository @Inject constructor( val showWholeClassPlan: String get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class) + val showTimetableTimers: Boolean + get() = getBoolean(R.string.pref_key_timetable_show_timers, R.bool.pref_default_timetable_show_timers) + private fun getString(id: Int, default: Int) = getString(context.getString(id), default) private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt index 06c91cbea..97e45be9b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt @@ -179,6 +179,8 @@ class LoginRecoverFragment : loadDataWithBaseURL(url, html, "text/html", "UTF-8", null) addJavascriptInterface(object { + + @Suppress("UNUSED") @JavascriptInterface fun captchaCallback(reCaptchaResponse: String) { activity?.runOnUiThread { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt index b4b2671e0..5354442aa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt @@ -2,6 +2,8 @@ package io.github.wulkanowy.ui.modules.timetable import android.graphics.Paint import android.view.LayoutInflater +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView @@ -10,8 +12,16 @@ import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.databinding.ItemTimetableBinding import io.github.wulkanowy.databinding.ItemTimetableSmallBinding import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.isJustFinished +import io.github.wulkanowy.utils.isShowTimeUntil +import io.github.wulkanowy.utils.left import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.until +import org.threeten.bp.LocalDateTime +import timber.log.Timber +import java.util.Timer import javax.inject.Inject +import kotlin.concurrent.timer class TimetableAdapter @Inject constructor() : RecyclerView.Adapter() { @@ -20,12 +30,28 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter() + var items = mutableListOf() + set(value) { + field = value + resetTimers() + } var onClickListener: (Timetable) -> Unit = {} var showWholeClassPlan: String = "no" + var showTimers: Boolean = false + + private val timers = mutableMapOf() + + private fun resetTimers() { + Timber.d("Timetable timers reset") + with(timers) { + forEach { (_, timer) -> timer.cancel() } + clear() + } + } + override fun getItemCount() = items.size override fun getItemViewType(position: Int) = when { @@ -43,11 +69,16 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter bindNormalView(holder.binding, lesson) + is ItemViewHolder -> bindNormalView(holder.binding, lesson, position) is SmallItemViewHolder -> bindSmallView(holder.binding, lesson) } } @@ -68,7 +99,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter i < position && !item.isStudentPlan }.size)?.let { + if (!it.canceled && it.isStudentPlan) it.end + else null + } + } + + private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) { + with(binding) { + when { + // before lesson + lesson.isShowTimeUntil(getPreviousLesson(position)) -> { + Timber.d("Show time until lesson: $position") + timetableItemTimeLeft.visibility = GONE + with(timetableItemTimeUntil) { + visibility = VISIBLE + text = context.getString(R.string.timetable_time_until, + if (lesson.until.seconds <= 60) { + context.getString(R.string.timetable_seconds, lesson.until.seconds.toString(10)) + } else { + context.getString(R.string.timetable_minutes, lesson.until.toMinutes().toString(10)) + } + ) + } + } + // after lesson start + lesson.left != null -> { + Timber.d("Show time left lesson: $position") + timetableItemTimeUntil.visibility = GONE + with(timetableItemTimeLeft) { + visibility = VISIBLE + text = context.getString( + R.string.timetable_time_left, + if (lesson.left!!.seconds < 60) { + context.getString(R.string.timetable_seconds, lesson.left?.seconds?.toString(10)) + } else { + context.getString(R.string.timetable_minutes, lesson.left?.toMinutes()?.toString(10)) + } + ) + } + } + // right after lesson finish + lesson.isJustFinished -> { + Timber.d("Show just finished lesson: $position") + timetableItemTimeUntil.visibility = GONE + timetableItemTimeLeft.visibility = VISIBLE + timetableItemTimeLeft.text = root.context.getString(R.string.timetable_finished) + } + else -> { + timetableItemTimeUntil.visibility = GONE + timetableItemTimeLeft.visibility = GONE + } + } + } + } + private fun bindSubjectStyle(subjectView: TextView, lesson: Timetable) { subjectView.paintFlags = if (lesson.canceled) subjectView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG else subjectView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() @@ -93,20 +188,20 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter(R.layout.fragme else false } - override fun updateData(data: List, showWholeClassPlanType: String) { + override fun updateData(data: List, showWholeClassPlanType: String, showTimetableTimers: Boolean) { with(timetableAdapter) { - items = data + items = data.toMutableList() + showTimers = showTimetableTimers showWholeClassPlan = showWholeClassPlanType notifyDataSetChanged() } @@ -96,7 +97,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme override fun clearData() { with(timetableAdapter) { - items = emptyList() + items = mutableListOf() notifyDataSetChanged() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index 03d79081d..50c123646 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -149,7 +149,7 @@ class TimetablePresenter @Inject constructor( .subscribe({ Timber.i("Loading timetable result: Success") view?.apply { - updateData(it, prefRepository.showWholeClassPlan) + updateData(it, prefRepository.showWholeClassPlan, prefRepository.showTimetableTimers) showEmpty(it.isEmpty()) showErrorView(false) showContent(it.isNotEmpty()) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt index 8399498c6..1efa320fc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt @@ -12,7 +12,7 @@ interface TimetableView : BaseView { fun initView() - fun updateData(data: List, showWholeClassPlanType: String) + fun updateData(data: List, showWholeClassPlanType: String, showTimetableTimers: Boolean) fun updateNavigationDay(date: String) diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt new file mode 100644 index 000000000..ccb2afeb0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/TimetableExtension.kt @@ -0,0 +1,29 @@ +package io.github.wulkanowy.utils + +import io.github.wulkanowy.data.db.entities.Timetable +import org.threeten.bp.Duration +import org.threeten.bp.Duration.between +import org.threeten.bp.LocalDateTime +import org.threeten.bp.LocalDateTime.now + +fun Timetable.isShowTimeUntil(previousLessonEnd: LocalDateTime?) = when { + !isStudentPlan -> false + canceled -> false + now().isAfter(start) -> false + previousLessonEnd != null && now().isBefore(previousLessonEnd) -> false + else -> between(now(), start) <= Duration.ofMinutes(60) +} + +inline val Timetable.left: Duration? + get() = when { + canceled -> null + !isStudentPlan -> null + end.isAfter(now()) && start.isBefore(now()) -> between(now(), end) + else -> null + } + +inline val Timetable.until: Duration + get() = between(now(), start) + +inline val Timetable.isJustFinished: Boolean + get() = end.isBefore(now()) && end.plusSeconds(15).isAfter(now()) && !canceled diff --git a/app/src/main/res/drawable/background_timetable_time_left.xml b/app/src/main/res/drawable/background_timetable_time_left.xml new file mode 100644 index 000000000..dd974a4c1 --- /dev/null +++ b/app/src/main/res/drawable/background_timetable_time_left.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_timetable.xml b/app/src/main/res/layout/item_timetable.xml index b15e46754..f2218105e 100644 --- a/app/src/main/res/layout/item_timetable.xml +++ b/app/src/main/res/layout/item_timetable.xml @@ -29,12 +29,12 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:layout_marginEnd="40dp" + android:layout_marginEnd="16dp" android:ellipsize="end" android:maxLines="1" android:textColor="?android:textColorPrimary" android:textSize="15sp" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@id/timetableItemTimeBarrier" app:layout_constraintStart_toEndOf="@+id/timetableItemTimeStart" app:layout_constraintTop_toTopOf="parent" tools:text="@tools:sample/lorem" /> @@ -106,4 +106,56 @@ tools:text="Lekcja odwołana: uczniowie zwolnieni do domu" tools:visibility="visible" /> + + + + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index e6fe6da64..a4af0dec4 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -121,6 +121,11 @@ Godziny Zmiany Brak lekcji w tym dniu + %s min + %s sek + jeszcze %1$s + za %1$s + Zakończona Teraz: %s Za chwilę: %s Później: %s @@ -317,6 +322,7 @@ Motyw aplikacji Rozwiń oceny Pokazuj listę wykresów w ocenach klasy + Oznaczaj bieżącą lekcję na planie Pokazuj lekcje całej klasy Schemat kolorów ocen Język aplikacji diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index c8704a50b..a82b14eb7 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -19,4 +19,5 @@ 0.33 true no + false diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 1d43f79fd..868a3c898 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -20,4 +20,5 @@ grade_modifier_minus fill_message_content show_whole_class_plan + timetable_show_timers diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bfaece677..06f136c7c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -130,6 +130,11 @@ Hours Changes No lessons this day + %s min + %s sec + %1$s left + in %1$s + Finished Now: %s Next: %s Later: %s @@ -352,6 +357,7 @@ Show presence in attendance Application theme Expand grades + Mark current lesson in timetable Show chart list in class grades Show whole class lessons Grades color scheme diff --git a/app/src/main/res/xml/scheme_preferences.xml b/app/src/main/res/xml/scheme_preferences.xml index d890fdb24..a138177f4 100644 --- a/app/src/main/res/xml/scheme_preferences.xml +++ b/app/src/main/res/xml/scheme_preferences.xml @@ -29,6 +29,11 @@ app:iconSpaceReserved="false" app:key="@string/pref_key_expand_grade" app:title="@string/pref_view_expand_grade" /> +