forked from github/szkolny
[Timetable] Implement lazy day loading. Introduce TimetableManager class.
This commit is contained in:
parent
06011bf4ae
commit
acd5e9b998
@ -47,6 +47,7 @@ import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
|
||||
import pl.szczodrzynski.edziennik.utils.*
|
||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager
|
||||
import pl.szczodrzynski.edziennik.utils.managers.NotificationChannelsManager
|
||||
import pl.szczodrzynski.edziennik.utils.managers.TimetableManager
|
||||
import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@ -67,6 +68,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
val notificationChannelsManager by lazy { NotificationChannelsManager(this) }
|
||||
val userActionManager by lazy { UserActionManager(this) }
|
||||
val gradesManager by lazy { GradesManager(this) }
|
||||
val timetableManager by lazy { TimetableManager(this) }
|
||||
|
||||
val db
|
||||
get() = App.db
|
||||
|
@ -5,10 +5,9 @@
|
||||
package pl.szczodrzynski.edziennik.data.db.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.*
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
import androidx.sqlite.db.SupportSQLiteQuery
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||
@ -53,6 +52,9 @@ interface TimetableDao {
|
||||
@Query("DELETE FROM timetable WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))")
|
||||
fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date)
|
||||
|
||||
@RawQuery(observedEntities = [Lesson::class])
|
||||
fun getRaw(query: SupportSQLiteQuery): LiveData<List<LessonFull>>
|
||||
|
||||
@Query("""
|
||||
$QUERY
|
||||
WHERE timetable.profileId = :profileId AND type != -1 AND type != 0
|
||||
@ -67,12 +69,11 @@ interface TimetableDao {
|
||||
""")
|
||||
fun getChangesForDateNow(profileId: Int, date: Date): List<LessonFull>
|
||||
|
||||
@Query("""
|
||||
fun getForDate(profileId: Int, date: Date) = getRaw(SimpleSQLiteQuery("""
|
||||
$QUERY
|
||||
WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date))
|
||||
WHERE timetable.profileId = $profileId AND ((type != 3 AND date = "${date.stringY_m_d}") OR ((type = 3 OR type = 1) AND oldDate = "${date.stringY_m_d}"))
|
||||
ORDER BY id, type
|
||||
""")
|
||||
fun getForDate(profileId: Int, date: Date): LiveData<List<LessonFull>>
|
||||
"""))
|
||||
|
||||
@Query("""
|
||||
$QUERY
|
||||
|
@ -5,6 +5,7 @@
|
||||
package pl.szczodrzynski.edziennik.data.db.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.Index
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
@ -66,6 +67,9 @@ open class Lesson(val profileId: Int, var id: Long) {
|
||||
|
||||
fun buildId(): Long = (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + (hashCode() and 0xFFFF)
|
||||
|
||||
@Ignore
|
||||
var showAsUnseen = false
|
||||
|
||||
override fun toString(): String {
|
||||
return "Lesson(profileId=$profileId, " +
|
||||
"id=$id, " +
|
||||
|
@ -25,7 +25,6 @@ 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.ui.modules.timetable.TimetableUtils
|
||||
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
@ -50,7 +49,7 @@ class LessonDetailsDialog(
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
private lateinit var adapter: EventListAdapter
|
||||
private val utils by lazy { TimetableUtils() }
|
||||
private val manager by lazy { app.timetableManager }
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
@ -91,7 +90,7 @@ class LessonDetailsDialog(
|
||||
val lessonTime = lesson.displayStartTime ?: return
|
||||
b.lessonDate.text = Week.getFullDayName(lessonDate.weekDay) + ", " + lessonDate.formattedString
|
||||
|
||||
b.annotationVisible = utils.getAnnotation(activity, lesson, b.annotation)
|
||||
b.annotationVisible = manager.getAnnotation(activity, lesson, b.annotation)
|
||||
|
||||
if (lesson.type >= Lesson.TYPE_SHIFTED_SOURCE) {
|
||||
b.shiftedLayout.visibility = View.VISIBLE
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-22.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.base
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
abstract class PagerFragment : Fragment() {
|
||||
private var isPageCreated = false
|
||||
|
||||
/**
|
||||
* Called when the page is first shown, or if previous
|
||||
* [onPageCreated] returned false
|
||||
*
|
||||
* @return true if the view is set up
|
||||
* @return false if the setup failed. The method may be then called
|
||||
* again, when page becomes visible.
|
||||
*/
|
||||
abstract fun onPageCreated(): Boolean
|
||||
|
||||
override fun onResume() {
|
||||
if (!isPageCreated) {
|
||||
isPageCreated = onPageCreated()
|
||||
}
|
||||
super.onResume()
|
||||
}
|
||||
}
|
@ -5,15 +5,15 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.timetable
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
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.setPadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import com.linkedin.android.tachyon.DayView
|
||||
import com.linkedin.android.tachyon.DayViewConfig
|
||||
@ -27,6 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||
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.PagerFragment
|
||||
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
|
||||
@ -35,7 +36,7 @@ import java.util.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.math.min
|
||||
|
||||
class TimetableDayFragment : Fragment(), CoroutineScope {
|
||||
class TimetableDayFragment : PagerFragment(), CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "TimetableDayFragment"
|
||||
}
|
||||
@ -44,7 +45,7 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
|
||||
private lateinit var activity: MainActivity
|
||||
private lateinit var inflater: AsyncLayoutInflater
|
||||
|
||||
private lateinit var job: Job
|
||||
private val job: Job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
@ -53,7 +54,7 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
|
||||
private var endHour = DEFAULT_END_HOUR
|
||||
private var firstEventMinute = 24 * 60
|
||||
|
||||
private val utils by lazy { TimetableUtils() }
|
||||
private val manager by lazy { app.timetableManager }
|
||||
|
||||
// find SwipeRefreshLayout in the hierarchy
|
||||
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
|
||||
@ -88,25 +89,23 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
activity = (getActivity() as MainActivity?) ?: return null
|
||||
if (context == null)
|
||||
return null
|
||||
context ?: return null
|
||||
app = activity.application as App
|
||||
job = Job()
|
||||
this.inflater = AsyncLayoutInflater(context!!)
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// TODO check if app, activity, b can be null
|
||||
if (app.profile == null || !isAdded)
|
||||
return
|
||||
|
||||
Log.d(TAG, "onViewCreated, date=$date")
|
||||
override fun onPageCreated(): Boolean {
|
||||
if (!isAdded)
|
||||
return false
|
||||
|
||||
// observe lesson database
|
||||
app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons ->
|
||||
@ -117,6 +116,8 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
|
||||
processLessonList(lessons, events)
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) {
|
||||
@ -174,6 +175,8 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
|
||||
}
|
||||
dayView.setHourLabelViews(hourLabelViews)
|
||||
|
||||
lessons.forEach { it.showAsUnseen = !it.seen }
|
||||
|
||||
buildLessonViews(lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }, events)
|
||||
}
|
||||
|
||||
@ -209,7 +212,6 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
|
||||
eventView.tag = lesson
|
||||
|
||||
eventView.setOnClickListener {
|
||||
Log.d(TAG, "Clicked ${it.tag}")
|
||||
if (isAdded && it.tag is LessonFull)
|
||||
LessonDetailsDialog(activity, it.tag as LessonFull)
|
||||
}
|
||||
@ -275,10 +277,13 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
|
||||
lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
|
||||
lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
|
||||
|
||||
lb.unread = lesson.type != Lesson.TYPE_NORMAL && !lesson.seen
|
||||
lb.unread = lesson.type != Lesson.TYPE_NORMAL && lesson.showAsUnseen
|
||||
if (!lesson.seen) {
|
||||
manager.markAsSeen(lesson)
|
||||
}
|
||||
|
||||
//lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
|
||||
lb.annotationVisible = utils.getAnnotation(activity, lesson, lb.annotation)
|
||||
lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation)
|
||||
|
||||
// The day view needs the event time ranges in the start minute/end minute format,
|
||||
// so calculate those here
|
||||
|
@ -15,7 +15,6 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
@ -26,12 +25,9 @@ import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
|
||||
import pl.szczodrzynski.edziennik.observeOnce
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.GenerateBlockTimetableDialog
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
|
||||
@ -50,7 +46,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
private lateinit var activity: MainActivity
|
||||
private lateinit var b: FragmentTimetableV2Binding
|
||||
|
||||
private lateinit var job: Job
|
||||
private val job: Job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
@ -61,11 +57,6 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
activity = (getActivity() as MainActivity?) ?: return null
|
||||
context ?: return null
|
||||
app = activity.application as App
|
||||
job = Job()
|
||||
context!!.theme.applyStyle(Themes.appTheme, true)
|
||||
if (app.profile == null)
|
||||
return inflater.inflate(R.layout.fragment_loading, container, false)
|
||||
// activity, context and profile is valid
|
||||
b = FragmentTimetableV2Binding.inflate(inflater)
|
||||
// TODO: 2020-01-05 resolve issues with page scrolling (and scrolling up) with viewpager and swipe to refresh
|
||||
//b.refreshLayout.setParent(activity.swipeRefreshLayout)
|
||||
@ -91,8 +82,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { launch {
|
||||
// TODO check if app, activity, b can be null
|
||||
if (app.profile == null || !isAdded)
|
||||
if (!isAdded)
|
||||
return@launch
|
||||
|
||||
if (app.profile.getStudentData("timetableNotPublic", false)) {
|
||||
@ -141,7 +131,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
startHour,
|
||||
endHour
|
||||
)
|
||||
b.viewPager.offscreenPageLimit = 2
|
||||
b.viewPager.offscreenPageLimit = 1
|
||||
b.viewPager.adapter = pagerAdapter
|
||||
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
@ -162,7 +152,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
activity.gainAttentionFAB()
|
||||
fabShown = true
|
||||
}
|
||||
markLessonsAsSeen()
|
||||
//markLessonsAsSeen()
|
||||
}
|
||||
})
|
||||
|
||||
@ -224,7 +214,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
})
|
||||
}}
|
||||
|
||||
private fun markLessonsAsSeen() = pageSelection?.let { date ->
|
||||
/*private fun markLessonsAsSeen() = pageSelection?.let { date ->
|
||||
app.db.timetableDao().getForDate(App.profileId, date).observeOnce(this@TimetableFragment, Observer { lessons ->
|
||||
lessons.forEach { lesson ->
|
||||
if (lesson.type != Lesson.TYPE_NORMAL && lesson.type != Lesson.TYPE_NO_LESSONS
|
||||
@ -233,5 +223,5 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
@ -33,11 +33,6 @@ class TimetablePagerAdapter(
|
||||
putInt("endHour", endHour)
|
||||
}
|
||||
}
|
||||
/*return TimetableDayFragment().apply {
|
||||
arguments = Bundle().also {
|
||||
it.putLong("date", items[position].value.toLong())
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
|
@ -1,22 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-16.
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-3-10.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.timetable
|
||||
package pl.szczodrzynski.edziennik.utils.managers
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.widget.TextView
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||
import pl.szczodrzynski.edziennik.resolveAttr
|
||||
import pl.szczodrzynski.edziennik.setText
|
||||
import pl.szczodrzynski.edziennik.setTintColor
|
||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class TimetableManager(val app: App) : CoroutineScope {
|
||||
|
||||
private val job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Default
|
||||
|
||||
fun markAsSeen(lesson: LessonFull) {
|
||||
lesson.seen = true
|
||||
startCoroutineTimer(500L, 0L) {
|
||||
app.db.metadataDao().setSeen(lesson.profileId, lesson, true)
|
||||
}
|
||||
}
|
||||
|
||||
class TimetableUtils {
|
||||
fun getAnnotation(context: Context, lesson: LessonFull, annotation: TextView): Boolean {
|
||||
var annotationVisible = false
|
||||
when (lesson.type) {
|
@ -74,8 +74,8 @@
|
||||
tools:text="pracownia urządzeń techniki komputerowej" />
|
||||
|
||||
<View
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="8dp"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
|
Loading…
x
Reference in New Issue
Block a user