[Timetable] Add SwipeToRefresh. Select start&end hours based on lesson ranges.

This commit is contained in:
Kuba Szczodrzyński 2019-11-16 21:16:18 +01:00
parent a3e5f824c8
commit 37ea65e3fc
12 changed files with 475 additions and 268 deletions

View File

@ -169,7 +169,7 @@ dependencies {
implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584' implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
implementation 'com.github.kuba2k2:Tachyon:bbd421f994' implementation 'com.github.kuba2k2:Tachyon:551943a6b5'
} }
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -4,7 +4,9 @@ import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.* import android.text.*
@ -13,9 +15,10 @@ import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.util.LongSparseArray import android.util.LongSparseArray
import android.util.SparseArray import android.util.SparseArray
import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.annotation.StringRes import androidx.annotation.*
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@ -446,3 +449,49 @@ fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observ
} }
}) })
} }
/**
* Convert a value in dp to pixels.
*/
val Int.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
/**
* Convert a value in pixels to dp.
*/
val Int.px: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
@ColorInt
fun @receiver:AttrRes Int.resolveAttr(context: Context?): Int {
val typedValue = TypedValue()
context?.theme?.resolveAttribute(this, typedValue, true)
return typedValue.data
}
@ColorInt
fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getColor(this, context.theme)
}
else {
context.resources.getColor(this)
}
}
fun @receiver:DrawableRes Int.resolveDrawable(context: Context): Drawable {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getDrawable(this, context.theme)
}
else {
context.resources.getDrawable(this)
}
}
fun View.findParentById(targetId: Int): View? {
if (id == targetId) {
return this
}
val viewParent = this.parent ?: return null
if (viewParent is View) {
return viewParent.findParentById(targetId)
}
return null
}

View File

@ -5,12 +5,14 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
@ -18,16 +20,25 @@ import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class TimetableFragment : Fragment() { class TimetableFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "TimetableFragment" private const val TAG = "TimetableFragment"
const val ACTION_SCROLL_TO_DATE = "pl.szczodrzynski.edziennik.timetable.SCROLL_TO_DATE" const val ACTION_SCROLL_TO_DATE = "pl.szczodrzynski.edziennik.timetable.SCROLL_TO_DATE"
const val DEFAULT_START_HOUR = 6
const val DEFAULT_END_HOUR = 19
var pageSelection: Date? = null
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: FragmentTimetableV2Binding private lateinit var b: FragmentTimetableV2Binding
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private var fabShown = false private var fabShown = false
private val items = mutableListOf<Date>() private val items = mutableListOf<Date>()
@ -36,11 +47,13 @@ class TimetableFragment : Fragment() {
if (context == null) if (context == null)
return null return null
app = activity.application as App app = activity.application as App
job = Job()
context!!.theme.applyStyle(Themes.appTheme, true) context!!.theme.applyStyle(Themes.appTheme, true)
if (app.profile == null) if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false) return inflater.inflate(R.layout.fragment_loading, container, false)
// activity, context and profile is valid // activity, context and profile is valid
b = FragmentTimetableV2Binding.inflate(inflater) b = FragmentTimetableV2Binding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root return b.root
} }
@ -62,48 +75,65 @@ class TimetableFragment : Fragment() {
activity.unregisterReceiver(broadcastReceiver) activity.unregisterReceiver(broadcastReceiver)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { launch {
// TODO check if app, activity, b can be null // TODO check if app, activity, b can be null
if (app.profile == null || !isAdded) if (app.profile == null || !isAdded)
return return@launch
if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) { if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) {
b.timetableLayout.visibility = View.GONE b.timetableLayout.visibility = View.GONE
b.timetableNotPublicLayout.visibility = View.VISIBLE b.timetableNotPublicLayout.visibility = View.VISIBLE
return return@launch
} }
b.timetableLayout.visibility = View.VISIBLE b.timetableLayout.visibility = View.VISIBLE
b.timetableNotPublicLayout.visibility = View.GONE b.timetableNotPublicLayout.visibility = View.GONE
items.clear()
val monthDayCount = listOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
val today = Date.getToday().value val today = Date.getToday().value
val yearStart = app.profile.dateSemester1Start?.clone() ?: return var startHour = DEFAULT_START_HOUR
val yearEnd = app.profile.dateYearEnd ?: return var endHour = DEFAULT_END_HOUR
while (yearStart.value <= yearEnd.value) { val deferred = async(Dispatchers.Default) {
items += yearStart.clone() items.clear()
var maxDays = monthDayCount[yearStart.month-1]
if (yearStart.month == 2 && yearStart.isLeap)
maxDays++
yearStart.day++
if (yearStart.day > maxDays) {
yearStart.day = 1
yearStart.month++
}
if (yearStart.month > 12) {
yearStart.month = 1
yearStart.year++
}
}
val pagerAdapter = TimetablePagerAdapter(fragmentManager ?: return, items) val monthDayCount = listOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
val yearStart = app.profile.dateSemester1Start?.clone() ?: return@async
val yearEnd = app.profile.dateYearEnd ?: return@async
while (yearStart.value <= yearEnd.value) {
items += yearStart.clone()
var maxDays = monthDayCount[yearStart.month-1]
if (yearStart.month == 2 && yearStart.isLeap)
maxDays++
yearStart.day++
if (yearStart.day > maxDays) {
yearStart.day = 1
yearStart.month++
}
if (yearStart.month > 12) {
yearStart.month = 1
yearStart.year++
}
}
val lessonRanges = app.db.lessonRangeDao().getAllNow(App.profileId)
startHour = lessonRanges.map { it.startTime.hour }.min() ?: DEFAULT_START_HOUR
endHour = lessonRanges.map { it.endTime.hour }.max()?.plus(1) ?: DEFAULT_END_HOUR
}
deferred.await()
val pagerAdapter = TimetablePagerAdapter(
fragmentManager ?: return@launch,
items,
startHour,
endHour
)
b.viewPager.offscreenPageLimit = 2 b.viewPager.offscreenPageLimit = 2
b.viewPager.adapter = pagerAdapter b.viewPager.adapter = pagerAdapter
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) { override fun onPageScrollStateChanged(state: Int) {
if (b.refreshLayout != null) {
Log.d(TAG, "State $state")
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
} }
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
@ -111,6 +141,7 @@ class TimetableFragment : Fragment() {
} }
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = items[position]
activity.navView.bottomBar.fabEnable = items[position].value != today activity.navView.bottomBar.fabEnable = items[position].value != today
if (activity.navView.bottomBar.fabEnable && !fabShown) { if (activity.navView.bottomBar.fabEnable && !fabShown) {
activity.gainAttentionFAB() activity.gainAttentionFAB()
@ -123,10 +154,10 @@ class TimetableFragment : Fragment() {
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, false) b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, false)
//activity.navView.bottomBar.fabEnable = true //activity.navView.bottomBar.fabEnable = true
activity.navView.bottomBar.fabExtendedText = getString(pl.szczodrzynski.edziennik.R.string.timetable_today) activity.navView.bottomBar.fabExtendedText = getString(R.string.timetable_today)
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon.cmd_calendar_today activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon.cmd_calendar_today
activity.navView.setFabOnClickListener(View.OnClickListener { activity.navView.setFabOnClickListener(View.OnClickListener {
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true) b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true)
}) })
} }}
} }

View File

@ -8,7 +8,12 @@ import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.day.TimetableDayFragme
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week import pl.szczodrzynski.edziennik.utils.models.Week
class TimetablePagerAdapter(val fragmentManager: FragmentManager, val items: List<Date>) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { class TimetablePagerAdapter(
fragmentManager: FragmentManager,
private val items: List<Date>,
private val startHour: Int,
private val endHour: Int
) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
companion object { companion object {
private const val TAG = "TimetablePagerAdapter" private const val TAG = "TimetablePagerAdapter"
} }
@ -21,6 +26,8 @@ class TimetablePagerAdapter(val fragmentManager: FragmentManager, val items: Lis
return TimetableDayFragment().apply { return TimetableDayFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putInt("date", items[position].value) putInt("date", items[position].value)
putInt("startHour", startHour)
putInt("endHour", endHour)
} }
} }
/*return TimetableDayFragment().apply { /*return TimetableDayFragment().apply {

View File

@ -7,42 +7,97 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView import android.widget.TextView
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.linkedin.android.tachyon.DayView import com.linkedin.android.tachyon.DayView
import com.linkedin.android.tachyon.DayViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2DayBinding
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding 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.dialogs.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment.Companion.DEFAULT_END_HOUR
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment.Companion.DEFAULT_START_HOUR
import pl.szczodrzynski.edziennik.utils.ListenerScrollView
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.getColorFromAttr import pl.szczodrzynski.navlib.getColorFromAttr
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.min import kotlin.math.min
class TimetableDayFragment() : Fragment() { class TimetableDayFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "TimetableDayFragment" private const val TAG = "TimetableDayFragment"
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: FragmentTimetableV2DayBinding private lateinit var inflater: AsyncLayoutInflater
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var date: Date private lateinit var date: Date
private var startHour = DEFAULT_START_HOUR
private var endHour = DEFAULT_END_HOUR
private var firstEventMinute = 24*60
// 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
), true)
dayView.setPadding(10.dp)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
dayScroll.addView(dayView)
dayView
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
if (context == null) if (context == null)
return null return null
app = activity.application as App app = activity.application as App
b = FragmentTimetableV2DayBinding.inflate(inflater) job = Job()
this.inflater = AsyncLayoutInflater(context!!)
date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday() date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday()
return b.root 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)
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -52,45 +107,42 @@ class TimetableDayFragment() : Fragment() {
Log.d(TAG, "onViewCreated, date=$date") Log.d(TAG, "onViewCreated, date=$date")
// Inflate a label view for each hour the day view will display // observe lesson database
val hourLabelViews = ArrayList<View>()
for (i in b.day.startHour..b.day.endHour) {
val hourLabelView = layoutInflater.inflate(R.layout.timetable_hour_label, b.day, false) as TextView
hourLabelView.text = "$i:00"
hourLabelViews.add(hourLabelView)
}
b.day.setHourLabelViews(hourLabelViews)
app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer<List<LessonFull>> { lessons -> app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer<List<LessonFull>> { lessons ->
buildLessonViews(lessons) processLessonList(lessons)
}) })
} }
private fun buildLessonViews(lessons: List<LessonFull>) { private fun processLessonList(lessons: List<LessonFull>) {
// no lessons - timetable not downloaded yet
if (lessons.isEmpty()) { if (lessons.isEmpty()) {
b.dayScroll.visibility = View.GONE inflater.inflate(R.layout.timetable_no_timetable, view as FrameLayout) { view, _, parent ->
b.noTimetableLayout.visibility = View.VISIBLE parent?.removeAllViews()
b.noLessonsLayout.visibility = View.GONE parent?.addView(view)
val weekStart = date.clone().stepForward(0, 0, -date.weekDay).stringY_m_d val b = TimetableNoTimetableBinding.bind(view)
b.noTimetableSync.onClick { val weekStart = date.clone().stepForward(0, 0, -date.weekDay).stringY_m_d
it.isEnabled = false b.noTimetableSync.onClick {
EdziennikTask.syncProfile( it.isEnabled = false
profileId = App.profileId, EdziennikTask.syncProfile(
viewIds = listOf( profileId = App.profileId,
DRAWER_ITEM_TIMETABLE to 0 viewIds = listOf(
), DRAWER_ITEM_TIMETABLE to 0
arguments = JsonObject( ),
"weekStart" to weekStart arguments = JsonObject(
) "weekStart" to weekStart
).enqueue(activity) )
).enqueue(activity)
}
b.noTimetableWeek.setText(R.string.timetable_no_timetable_week, weekStart)
} }
b.noTimetableWeek.setText(R.string.timetable_no_timetable_week, weekStart)
return return
} }
// one lesson indicating a day without lessons
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) { if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
b.dayScroll.visibility = View.GONE inflater.inflate(R.layout.timetable_no_lessons, view as FrameLayout) { view, _, parent ->
b.noTimetableLayout.visibility = View.GONE parent?.removeAllViews()
b.noLessonsLayout.visibility = View.VISIBLE parent?.addView(view)
}
return return
} }
@ -101,23 +153,37 @@ class TimetableDayFragment() : Fragment() {
return return
} }
b.dayScroll.visibility = View.VISIBLE // clear the root view and add the ScrollView
b.noTimetableLayout.visibility = View.GONE (view as FrameLayout).removeAllViews()
b.noLessonsLayout.visibility = View.GONE (view as FrameLayout).addView(dayScroll)
var firstEventMinute = 24*60 // Inflate a label view for each hour the day view will display
val hourLabelViews = ArrayList<View>()
for (i in dayView.startHour..dayView.endHour) {
val hourLabelView = layoutInflater.inflate(R.layout.timetable_hour_label, dayView, false) as TextView
hourLabelView.text = "$i:00"
hourLabelViews.add(hourLabelView)
}
dayView.setHourLabelViews(hourLabelViews)
buildLessonViews(lessons)
}
private fun buildLessonViews(lessons: List<LessonFull>) {
if (!isAdded)
return
val eventViews = mutableListOf<View>() val eventViews = mutableListOf<View>()
val eventTimeRanges = mutableListOf<DayView.EventTimeRange>() val eventTimeRanges = mutableListOf<DayView.EventTimeRange>()
// Reclaim all of the existing event views so we can reuse them if needed, this process // Reclaim all of the existing event views so we can reuse them if needed, this process
// can be useful if your day view is hosted in a recycler view for example // can be useful if your day view is hosted in a recycler view for example
val recycled = b.day.removeEventViews() val recycled = dayView.removeEventViews()
var remaining = recycled?.size ?: 0 var remaining = recycled?.size ?: 0
val arrowRight = "" val arrowRight = ""
val bullet = "" val bullet = ""
val colorSecondary = getColorFromAttr(activity, android.R.attr.textColorSecondary) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
for (lesson in lessons) { for (lesson in lessons) {
val startTime = lesson.displayStartTime ?: continue val startTime = lesson.displayStartTime ?: continue
@ -127,7 +193,7 @@ class TimetableDayFragment() : Fragment() {
// Try to recycle an existing event view if there are enough left, otherwise inflate // Try to recycle an existing event view if there are enough left, otherwise inflate
// a new one // a new one
val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, b.day, false)) val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, dayView, false))
?: continue ?: continue
val lb = TimetableLessonBinding.bind(eventView) val lb = TimetableLessonBinding.bind(eventView)
eventViews += eventView eventViews += eventView
@ -274,9 +340,16 @@ class TimetableDayFragment() : Fragment() {
eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute)) eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute))
} }
val minuteHeight = (b.day.getHourTop(1) - b.day.getHourTop(0)).toFloat() / 60f dayView.setEventViews(eventViews, eventTimeRanges)
val firstEventTop = (firstEventMinute - b.day.startHour * 60) * minuteHeight val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
b.day.setEventViews(eventViews, eventTimeRanges) dayScroll.scrollTo(0, firstEventTop.toInt())
b.dayScroll.scrollTo(0, firstEventTop.toInt()) }
override fun onResume() {
super.onResume()
if (dayScrollDelegate.isInitialized()) {
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
dayScroll.scrollTo(0, firstEventTop.toInt())
}
} }
} }

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-16.
*/
package pl.szczodrzynski.edziennik.utils
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ScrollView
class ListenerScrollView(
context: Context,
attrs: AttributeSet? = null
) : ScrollView(context, attrs) {
private var onScrollChangedListener: ((v: ListenerScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) -> Unit)? = null
private var onRefreshLayoutEnabledListener: ((enabled: Boolean) -> Unit)? = null
private var refreshLayoutEnabled = true
init {
setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP) {
refreshLayoutEnabled = scrollY < 10
onRefreshLayoutEnabledListener?.invoke(refreshLayoutEnabled)
}
false
}
}
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
onScrollChangedListener?.invoke(this, l, t, oldl, oldt)
if (t > 10 && refreshLayoutEnabled) {
refreshLayoutEnabled = false
onRefreshLayoutEnabledListener?.invoke(refreshLayoutEnabled)
}
}
fun setOnScrollChangedListener(l: ((v: ListenerScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) -> Unit)?) {
onScrollChangedListener = l
}
fun setOnRefreshLayoutEnabledListener(l: ((enabled: Boolean) -> Unit)?) {
onRefreshLayoutEnabledListener = l
}
}

View File

@ -2,89 +2,96 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
android:id="@+id/refreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout <FrameLayout
android:id="@+id/timetableLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
tools:visibility="gone">
<com.google.android.material.appbar.AppBarLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:id="@+id/timetableLayout"
android:layout_height="wrap_content"
android:background="?colorSurface"
style="@style/Widget.MaterialComponents.AppBarLayout.Surface">
<com.nshmura.recyclertablayout.RecyclerTabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/colorSurface_6dp"
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab"
app:rtl_tabIndicatorColor="?colorPrimary"
app:rtl_tabMinWidth="90dp"
app:rtl_tabMaxWidth="300dp"
app:rtl_tabSelectedTextColor="?colorPrimary"
app:rtl_tabPaddingStart="16dp"
app:rtl_tabPaddingEnd="16dp"
app:rtl_tabPaddingTop="12dp"
app:rtl_tabPaddingBottom="12dp"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> tools:visibility="gone">
</androidx.coordinatorlayout.widget.CoordinatorLayout> <com.google.android.material.appbar.AppBarLayout
style="@style/Widget.MaterialComponents.AppBarLayout.Surface"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorSurface">
<LinearLayout <com.nshmura.recyclertablayout.RecyclerTabLayout
android:id="@+id/timetableNotPublicLayout" android:id="@+id/tabLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="48dp"
android:orientation="vertical" android:background="@color/colorSurface_6dp"
android:visibility="gone" app:rtl_tabIndicatorColor="?colorPrimary"
android:gravity="center" app:rtl_tabMaxWidth="300dp"
tools:visibility="visible"> app:rtl_tabMinWidth="90dp"
app:rtl_tabPaddingBottom="12dp"
app:rtl_tabPaddingEnd="16dp"
app:rtl_tabPaddingStart="16dp"
app:rtl_tabPaddingTop="12dp"
app:rtl_tabSelectedTextColor="?colorPrimary"
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab" />
</com.google.android.material.appbar.AppBarLayout>
<ImageView <androidx.viewpager.widget.ViewPager
android:layout_width="wrap_content" android:id="@+id/viewPager"
android:layout_height="wrap_content" android:layout_width="match_parent"
app:srcCompat="@drawable/ic_no_timetable" /> android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<TextView </androidx.coordinatorlayout.widget.CoordinatorLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_not_public_title"
android:textSize="24sp" />
<TextView <LinearLayout
android:layout_width="wrap_content" android:id="@+id/timetableNotPublicLayout"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:layout_marginTop="16dp" android:layout_height="match_parent"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center" android:gravity="center"
android:text="@string/timetable_not_public_text" android:orientation="vertical"
android:textSize="16sp" /> android:visibility="gone"
tools:visibility="visible">
<TextView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" app:srcCompat="@drawable/ic_no_timetable" />
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_hint"
android:textSize="14sp" />
</LinearLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_not_public_title"
android:textSize="24sp" />
</FrameLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_text"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_hint"
android:textSize="14sp" />
</LinearLayout>
</FrameLayout>
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
</layout> </layout>

View File

@ -28,126 +28,5 @@
tools:visibility="gone"/> tools:visibility="gone"/>
</ScrollView> </ScrollView>
<LinearLayout
android:id="@+id/noLessonsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_timetable" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_lessons_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/freeDayLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_sunbed" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_free_day_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_text"
android:textSize="14sp" />
<TextView
android:id="@+id/freeDayText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
tools:text="Dzień wolny dla szkoły z puli dyrektorskiej z okazji obchodów Światowego Dnia Wtorku w mieście Poznań i na przedmieśiach"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/freeDayShowTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_show" />
</LinearLayout>
<LinearLayout
android:id="@+id/noTimetableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_sync" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_timetable_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_no_timetable_text"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/noTimetableSync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_no_timetable_sync" />
<TextView
android:id="@+id/noTimetableWeek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/timetable_no_timetable_week"
android:textSize="12sp"
android:textStyle="italic"/>
</LinearLayout>
</FrameLayout> </FrameLayout>
</layout> </layout>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-15.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/freeDayLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_sunbed"
android:drawablePadding="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_free_day_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_text"
android:textSize="14sp" />
<TextView
android:id="@+id/freeDayText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:textSize="16sp"
tools:text="Dzień wolny dla szkoły z puli dyrektorskiej z okazji obchodów Światowego Dnia Wtorku w mieście Poznań i na przedmieśiach" />
<com.google.android.material.button.MaterialButton
android:id="@+id/freeDayShowTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_show" />
</LinearLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-15.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableTop="@drawable/ic_timetable"
android:drawablePadding="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_lessons_title"
android:textSize="24sp" />

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-15.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/noTimetableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_sync"
android:drawablePadding="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_timetable_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_no_timetable_text"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/noTimetableSync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_no_timetable_sync" />
<TextView
android:id="@+id/noTimetableWeek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textStyle="italic"
tools:text="@string/timetable_no_timetable_week" />
</LinearLayout>
</layout>

View File

@ -101,6 +101,8 @@
<item name="timetable_lesson_change_color">#ffb300</item> <item name="timetable_lesson_change_color">#ffb300</item>
<item name="timetable_lesson_shifted_source_color">#A1887F</item> <item name="timetable_lesson_shifted_source_color">#A1887F</item>
<item name="timetable_lesson_shifted_target_color">#4caf50</item> <item name="timetable_lesson_shifted_target_color">#4caf50</item>
<item name="hourDividerColor">#b0b0b0</item>
<item name="halfHourDividerColor">#e0e0e0</item>
</style> </style>
<style name="AppTheme.Dark" parent="NavView.Dark"> <style name="AppTheme.Dark" parent="NavView.Dark">
<item name="colorPrimary">#64b5f6</item> <item name="colorPrimary">#64b5f6</item>
@ -131,6 +133,8 @@
<item name="timetable_lesson_change_color">#ffb300</item> <item name="timetable_lesson_change_color">#ffb300</item>
<item name="timetable_lesson_shifted_source_color">#A1887F</item> <item name="timetable_lesson_shifted_source_color">#A1887F</item>
<item name="timetable_lesson_shifted_target_color">#4caf50</item> <item name="timetable_lesson_shifted_target_color">#4caf50</item>
<item name="hourDividerColor">#7fffffff</item>
<item name="halfHourDividerColor">#40ffffff</item>
</style> </style>