mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2024-11-24 10:54:36 -06:00
[UI/Timetable] Add current time marker line. (#30)
This commit is contained in:
parent
e25ca930e0
commit
8609956ae7
@ -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<LessonFull>, events: List<EventFull>) {
|
||||
// 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<View>()
|
||||
val hourLabelViews = mutableListOf<View>()
|
||||
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<FrameLayout.LayoutParams> {
|
||||
topMargin = (seconds * dayView.minuteHeight / 60f).toInt() + paddingTop
|
||||
}
|
||||
b.timeIndicatorMarker.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||
topMargin = b.timeIndicator.marginTop - (16.dp / 2) + (1.dp / 2)
|
||||
}
|
||||
}
|
||||
|
||||
if (timeIndicatorJob == null) {
|
||||
timeIndicatorJob = startCoroutineTimer(repeatMillis = 30000) {
|
||||
updateTimeIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
15
app/src/main/res/drawable/timetable_marker_triangle.xml
Normal file
15
app/src/main/res/drawable/timetable_marker_triangle.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2021-4-14.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/md_red_500"
|
||||
android:pathData="M0,4V20L12,12.25" />
|
||||
</vector>
|
||||
|
@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/dayScroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<com.linkedin.android.tachyon.DayView
|
||||
android:id="@+id/day"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
app:dividerHeight="1dp"
|
||||
app:endHour="18"
|
||||
app:eventMargin="2dp"
|
||||
app:halfHourDividerColor="#e0e0e0"
|
||||
app:halfHourHeight="60dp"
|
||||
app:hourDividerColor="#b0b0b0"
|
||||
app:hourLabelMarginEnd="10dp"
|
||||
app:hourLabelWidth="40dp"
|
||||
app:startHour="5"
|
||||
tools:visibility="gone"/>
|
||||
</ScrollView>
|
||||
|
||||
</FrameLayout>
|
||||
</layout>
|
53
app/src/main/res/layout/timetable_day_fragment.xml
Normal file
53
app/src/main/res/layout/timetable_day_fragment.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2021-4-14.
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.ListenerScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/dayFrame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout_height="match_parent">
|
||||
|
||||
<View
|
||||
android:id="@+id/timeIndicatorMarker"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:background="@drawable/timetable_marker_triangle"
|
||||
tools:layout_marginTop="92.5dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/timeIndicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:background="@color/md_red_500"
|
||||
tools:layout_marginTop="100dp" />
|
||||
|
||||
<com.linkedin.android.tachyon.DayView
|
||||
android:id="@+id/dayView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
</pl.szczodrzynski.edziennik.utils.ListenerScrollView>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
</FrameLayout>
|
Loading…
Reference in New Issue
Block a user