[UI] Enable colored app bars on scroll (#211)
Some checks failed
Schedule/dispatch / Build nightly release (APK) (push) Has been skipped
Schedule/dispatch / Check new changes (push) Failing after 25s

* Extract fragment scroll listener to separate file

* Replace canRefresh with isScrolled

* Add empty helper class to animate app bar color

* Implement AppBarColorAnimator

* Rename getRefreshScrollingView() to getScrollingView()

* Set isScrolled on drag start

* Clear isScrolled on fling to top

* Add getSyncParams() to fragments

* Convert getAppBars() to property

* Tint TabLayout background drawable
This commit is contained in:
Kuba Szczodrzyński 2024-07-09 18:15:03 +02:00 committed by GitHub
parent 58d9dec33c
commit 37a94595c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 254 additions and 100 deletions

View File

@ -19,6 +19,7 @@ import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.enums.FeatureType
import pl.szczodrzynski.edziennik.data.enums.MetadataType
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding
@ -45,6 +46,7 @@ class AgendaFragment : BaseFragment<ViewBinding, MainActivity>(
override fun getFab() = R.string.add to CommunityMaterial.Icon3.cmd_plus
override fun getMarkAsReadType() = MetadataType.EVENT
override fun getSyncParams() = FeatureType.AGENDA to null
override fun getBottomSheetItems() = listOf(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_add_event)
@ -114,6 +116,7 @@ class AgendaFragment : BaseFragment<ViewBinding, MainActivity>(
private suspend fun createDefaultAgendaView(b: FragmentAgendaDefaultBinding) {
if (!isAdded)
return
canRefreshDisabled = true
checkEventTypes()
delay(500)

View File

@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.ui.attendance
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.enums.FeatureType
import pl.szczodrzynski.edziennik.data.enums.MetadataType
import pl.szczodrzynski.edziennik.databinding.BasePagerFragmentBinding
import pl.szczodrzynski.edziennik.ext.Bundle
@ -32,6 +33,7 @@ class AttendanceFragment : PagerFragment<BasePagerFragmentBinding, MainActivity>
}
override fun getMarkAsReadType() = MetadataType.ATTENDANCE
override fun getSyncParams() = FeatureType.ATTENDANCE to null
override fun getBottomSheetItems() = listOf(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_attendance_config)

View File

@ -29,7 +29,7 @@ class AttendanceListFragment : BaseFragment<AttendanceListFragmentBinding, MainA
inflater = AttendanceListFragmentBinding::inflate,
) {
override fun getRefreshScrollingView() = b.list
override fun getScrollingView() = b.list
private var viewType = AttendanceFragment.VIEW_DAYS
private var expandSubjectId = 0L

View File

@ -43,7 +43,7 @@ class AttendanceSummaryFragment : BaseFragment<AttendanceSummaryFragmentBinding,
private var periodSelection = 0
}
override fun getRefreshScrollingView() = b.scrollView
override fun getScrollingView() = b.scrollView
private val manager
get() = app.attendanceManager

View File

@ -4,11 +4,7 @@
package pl.szczodrzynski.edziennik.ui.base.fragment
import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -18,49 +14,6 @@ import pl.szczodrzynski.edziennik.ui.login.LoginActivity
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
@SuppressLint("ClickableViewAccessibility")
internal fun BaseFragment<*, *>.setupCanRefresh() {
when (val view = getRefreshScrollingView()) {
is RecyclerView -> {
canRefresh = !view.canScrollVertically(-1)
view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
// disable refresh when scrolled down
if (recyclerView.canScrollVertically(-1))
canRefresh = false
// enable refresh when scrolled to the top and not scrolling anymore
else if (newState == RecyclerView.SCROLL_STATE_IDLE)
canRefresh = true
}
})
}
is View -> {
canRefresh = !view.canScrollVertically(-1)
var isTouched = false
view.setOnTouchListener { _, event ->
// keep track of the touch state
when (event.action) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> isTouched = false
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> isTouched = true
}
// disable refresh when scrolled down
if (view.canScrollVertically(-1))
canRefresh = false
// enable refresh when scrolled to the top and not touching anymore
else if (!isTouched)
canRefresh = true
false
}
}
else -> {
// dispatch the default value to the activity
canRefresh = canRefresh
}
}
}
internal fun BaseFragment<*, *>.setupMainActivity(activity: MainActivity) {
val items = getBottomSheetItems().toMutableList()
getMarkAsReadType()?.let { metadataType ->
@ -97,6 +50,8 @@ internal fun BaseFragment<*, *>.setupMainActivity(activity: MainActivity) {
}
}
}
appBars += activity.navView.toolbar
}
internal fun BaseFragment<*, *>.setupLoginActivity(activity: LoginActivity) {}

View File

@ -12,6 +12,7 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import com.google.gson.JsonObject
import com.mikepenz.iconics.typeface.IIcon
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -19,6 +20,7 @@ import kotlinx.coroutines.Job
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.enums.FeatureType
import pl.szczodrzynski.edziennik.data.enums.MetadataType
import pl.szczodrzynski.edziennik.ext.registerSafe
import pl.szczodrzynski.edziennik.ext.startCoroutineTimer
@ -38,31 +40,37 @@ abstract class BaseFragment<B : ViewBinding, A : AppCompatActivity>(
private var isViewReady: Boolean = false
private var inState: Bundle? = null
private var appBarAnimator: AppBarColorAnimator? = null
/**
* Enables or disables the activity's SwipeRefreshLayout.
* Use only if [getRefreshScrollingView] is not used.
*
* The [PagerFragment] manages its [canRefresh] state
* based on the value of the currently selected page.
* Whether the view is currently being scrolled
* or is left scrolled away from the top.
*/
internal var canRefresh = false
set(value) {
internal var isScrolled = false
set(value) { // cannot be private - PagerFragment onPageScrollStateChanged
field = value
(activity as? MainActivity)?.swipeRefreshLayout?.isEnabled =
!canRefreshDisabled && value
dispatchCanRefresh()
appBarAnimator?.dispatchLiftOnScroll()
}
/**
* Forcefully disables the activity's SwipeRefreshLayout
* if [getRefreshScrollingView] is used.
* Forcefully disables the activity's SwipeRefreshLayout.
*
* The [PagerFragment] manages its [canRefreshDisabled] state
* based on the value of the currently selected page.
*/
internal var canRefreshDisabled = false
set(value) {
field = value
canRefresh = canRefresh
dispatchCanRefresh()
}
/**
* A list of views (usually app bars) that should have their
* background color elevated when the fragment is scrolled.
*/
internal var appBars = mutableSetOf<View>()
private var job = Job()
final override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
@ -83,6 +91,7 @@ abstract class BaseFragment<B : ViewBinding, A : AppCompatActivity>(
?: return null
isViewReady = false // reinitialize the view in onResume()
inState = savedInstanceState // save the instance state for onResume()
appBarAnimator = AppBarColorAnimator(activity, appBars)
return b.root
}
@ -92,9 +101,17 @@ abstract class BaseFragment<B : ViewBinding, A : AppCompatActivity>(
if (!isAdded || isViewReady)
return
isViewReady = true
setupCanRefresh()
// setup the activity (bottom sheet, FAB, etc.)
// run before setupScrollListener {} to populate appBars
(activity as? MainActivity)?.let(::setupMainActivity)
(activity as? LoginActivity)?.let(::setupLoginActivity)
// listen to scroll state changes
var first = true
setupScrollListener {
if (isScrolled != it || first)
isScrolled = it
first = false
}
// let the UI transition for a moment
startCoroutineTimer(100L) {
if (!isAdded)
@ -141,9 +158,10 @@ abstract class BaseFragment<B : ViewBinding, A : AppCompatActivity>(
/**
* Called to retrieve the scrolling view contained in the fragment.
* The scrolling view is configured to act nicely with the SwipeRefreshLayout.
* The scrolling view is configured to work nicely with the app bars
* and the SwipeRefreshLayout.
*/
open fun getRefreshScrollingView(): View? = null
open fun getScrollingView(): View? = null
/**
* Called to retrieve the FAB label resource and the icon.
@ -157,6 +175,22 @@ abstract class BaseFragment<B : ViewBinding, A : AppCompatActivity>(
*/
open fun getMarkAsReadType(): MetadataType? = null
/**
* Called to retrieve the [FeatureType] this fragment is associated with.
* May also return arguments for the sync task.
*
* If not provided, swipe-to-refresh is disabled and the manual sync dialog
* selects all features by default.
*
* If [FeatureType] is null, all features are synced (and selected by the
* manual sync dialog).
*
* It is important to return the desired [FeatureType] from the first
* call of this method, which runs before [onViewReady]. Otherwise,
* swipe-to-refresh will not be enabled unless the view is scrolled.
*/
open fun getSyncParams(): Pair<FeatureType?, JsonObject?>? = null
/**
* Called to retrieve any extra bottom sheet items that should be displayed.
*/

View File

@ -23,8 +23,8 @@ abstract class PagerFragment<B : ViewBinding, A : AppCompatActivity>(
inflater: ((inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean) -> B)?,
) : BaseFragment<B, A>(inflater) {
private lateinit var pages: List<Pair<Fragment, String>>
private val fragmentCache = mutableMapOf<Int, Fragment>()
private lateinit var pages: List<Pair<BaseFragment<*, *>, String>>
private val fragmentCache = mutableMapOf<Int, BaseFragment<*, *>>()
/**
* Stores the default page index that is activated when
@ -37,6 +37,18 @@ abstract class PagerFragment<B : ViewBinding, A : AppCompatActivity>(
*/
protected open var savedPageSelection = -1
protected val currentFragment: BaseFragment<*, *>?
get() = fragmentCache[getViewPager().currentItem]
final override fun getScrollingView() = null
override fun getSyncParams() = currentFragment?.getSyncParams()
override fun onResume() {
// add TabLayout before super's setupScrollListener {}
appBars += getTabLayout()
super.onResume()
}
override suspend fun onViewReady(savedInstanceState: Bundle?) {
if (savedPageSelection == -1)
savedPageSelection = savedInstanceState?.getInt("pageSelection") ?: 0
@ -47,6 +59,7 @@ abstract class PagerFragment<B : ViewBinding, A : AppCompatActivity>(
override fun getItemCount() = getPageCount()
override fun createFragment(position: Int): Fragment {
val fragment = getPageFragment(position)
fragment.appBars += getTabLayout()
fragmentCache[position] = fragment
return fragment
}
@ -58,15 +71,15 @@ abstract class PagerFragment<B : ViewBinding, A : AppCompatActivity>(
it.setCurrentItem(savedPageSelection, false)
it.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
canRefresh = when (state) {
ViewPager2.SCROLL_STATE_IDLE -> {
val fragment =
fragmentCache[it.currentItem] as? BaseFragment<*, *>
fragment != null && !fragment.canRefreshDisabled && fragment.canRefresh
}
else -> false
if (state != ViewPager2.SCROLL_STATE_IDLE) {
// disable swipe-to-refresh during scrolling
canRefreshDisabled = true
return
}
// take child fragment's values
val fragment = currentFragment
canRefreshDisabled = fragment?.canRefreshDisabled == true
isScrolled = fragment?.isScrolled == true
}
override fun onPageSelected(position: Int) {
@ -125,7 +138,7 @@ abstract class PagerFragment<B : ViewBinding, A : AppCompatActivity>(
* Only used with the default implementation of [getPageCount], [getPageFragment]
* and [getPageTitle].
*/
open suspend fun onCreatePages() = listOf<Pair<Fragment, String>>()
open suspend fun onCreatePages() = listOf<Pair<BaseFragment<*, *>, String>>()
open fun getPageCount() = pages.size
open fun getPageFragment(position: Int) = pages[position].first

View File

@ -0,0 +1,130 @@
/*
* Copyright (c) Kuba Szczodrzyński 2024-7-9.
*/
package pl.szczodrzynski.edziennik.ui.base.fragment
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.LayerDrawable
import android.view.MotionEvent
import android.view.View
import android.view.animation.LinearInterpolator
import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.color.MaterialColors
import com.google.android.material.motion.MotionUtils
import com.google.android.material.shape.MaterialShapeDrawable
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.ext.resolveAttr
import pl.szczodrzynski.edziennik.ext.setTintColor
@SuppressLint("ClickableViewAccessibility")
internal fun BaseFragment<*, *>.setupScrollListener(setIsScrolled: (Boolean) -> Unit) {
when (val view = getScrollingView()) {
is RecyclerView -> {
setIsScrolled(view.canScrollVertically(-1))
view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (view.canScrollVertically(-1))
setIsScrolled(true)
else if (newState == RecyclerView.SCROLL_STATE_IDLE)
setIsScrolled(false)
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (view.canScrollVertically(-1))
setIsScrolled(true)
}
})
}
is View -> {
setIsScrolled(view.canScrollVertically(-1))
var isTouched = false
view.setOnTouchListener { _, event ->
// keep track of the touch state
when (event.action) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> isTouched = false
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> isTouched = true
}
if (view.canScrollVertically(-1))
setIsScrolled(true)
else if (!isTouched)
setIsScrolled(false)
false
}
(view as? NestedScrollView)?.setOnScrollChangeListener(
NestedScrollView.OnScrollChangeListener { _, _, _, _, _ ->
if (!isTouched && !view.canScrollVertically(-1))
setIsScrolled(false)
}
)
}
else -> { // null
// dispatch the default value to the activity
setIsScrolled(false)
}
}
}
internal fun BaseFragment<*, *>.dispatchCanRefresh() {
(activity as? MainActivity)?.swipeRefreshLayout?.isEnabled =
!canRefreshDisabled && !isScrolled && getSyncParams() != null
}
internal class AppBarColorAnimator(
context: Context,
private val bars: Set<View>,
) : ValueAnimator.AnimatorUpdateListener {
companion object {
// keep track of the current animation value applied globally to the view
private var currentValue = 0.0f
}
private lateinit var animator: ValueAnimator
private val barColor = R.attr.colorSurface.resolveAttr(context)
private val liftColor = R.attr.colorSurfaceContainer.resolveAttr(context)
context(BaseFragment<*, *>)
fun dispatchLiftOnScroll() {
if (::animator.isInitialized)
animator.cancel()
animator = ValueAnimator.ofFloat(
currentValue,
if (isScrolled) 1.0f else 0.0f,
)
animator.duration = MotionUtils.resolveThemeDuration(
activity,
R.attr.motionDurationMedium2,
resources.getInteger(R.integer.app_bar_elevation_anim_duration),
).toLong()
animator.interpolator = MotionUtils.resolveThemeInterpolator(
activity,
R.attr.motionEasingStandardInterpolator,
LinearInterpolator(),
)
animator.addUpdateListener(this)
animator.start()
}
override fun onAnimationUpdate(animation: ValueAnimator) {
currentValue = animation.animatedValue as Float
val mixedColor = MaterialColors.layer(
barColor,
liftColor,
currentValue,
)
for (bar in bars) {
when (val drawable = bar.background) {
is MaterialShapeDrawable -> drawable.fillColor = ColorStateList.valueOf(mixedColor)
is LayerDrawable -> drawable.getDrawable(0).setTintColor(mixedColor)
}
}
}
}

View File

@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Notice
import pl.szczodrzynski.edziennik.data.db.full.NoticeFull
import pl.szczodrzynski.edziennik.data.enums.FeatureType
import pl.szczodrzynski.edziennik.data.enums.MetadataType
import pl.szczodrzynski.edziennik.databinding.FragmentBehaviourBinding
import pl.szczodrzynski.edziennik.ext.resolveAttr
@ -20,8 +21,9 @@ class BehaviourFragment : BaseFragment<FragmentBehaviourBinding, MainActivity>(
inflater = FragmentBehaviourBinding::inflate,
) {
override fun getRefreshScrollingView() = b.noticesView
override fun getScrollingView() = b.noticesView
override fun getMarkAsReadType() = MetadataType.NOTICE
override fun getSyncParams() = FeatureType.BEHAVIOUR to null
private var displayMode = MODE_YEAR
private var noticeList: List<NoticeFull>? = null

View File

@ -39,7 +39,7 @@ class LabPageFragment : BaseFragment<LabFragmentBinding, AppCompatActivity>(
inflater = LabFragmentBinding::inflate,
) {
override fun getRefreshScrollingView() = b.scrollView
override fun getScrollingView() = b.scrollView
override suspend fun onViewReady(savedInstanceState: Bundle?) {
b.app = app

View File

@ -17,7 +17,7 @@ class LabPlaygroundFragment : BaseFragment<LabPlaygroundBinding, AppCompatActivi
inflater = LabPlaygroundBinding::inflate,
) {
override fun getRefreshScrollingView() = b.root
override fun getScrollingView() = b.root
override suspend fun onViewReady(savedInstanceState: Bundle?) {
Timber.d("textColorSecondary: ${android.R.attr.textColorSecondary.resolveAttr(activity)}")

View File

@ -29,7 +29,7 @@ class LabProfileFragment : BaseFragment<TemplateListPageFragmentBinding, AppComp
private const val TAG = "LabProfileFragment"
}
override fun getRefreshScrollingView() = b.list
override fun getScrollingView() = b.list
private lateinit var adapter: LabJsonAdapter
private val loginStore by lazy {

View File

@ -18,6 +18,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.core.manager.GradesManager
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.data.enums.FeatureType
import pl.szczodrzynski.edziennik.data.enums.MetadataType
import pl.szczodrzynski.edziennik.data.enums.NavTarget
import pl.szczodrzynski.edziennik.databinding.GradesListFragmentBinding
@ -38,8 +39,9 @@ class GradesListFragment : BaseFragment<GradesListFragmentBinding, MainActivity>
inflater = GradesListFragmentBinding::inflate,
) {
override fun getRefreshScrollingView() = b.list
override fun getScrollingView() = b.list
override fun getMarkAsReadType() = MetadataType.GRADE
override fun getSyncParams() = FeatureType.GRADES to null
override fun getBottomSheetItems() = listOf(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_grades_config)

View File

@ -78,7 +78,8 @@ class HomeFragment : BaseFragment<FragmentHomeBinding, MainActivity>(
}
}
override fun getRefreshScrollingView() = b.scrollView
override fun getScrollingView() = b.scrollView
override fun getSyncParams() = null to null
override fun getBottomSheetItems() = listOf(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_add_remove_cards)

View File

@ -7,6 +7,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.enums.FeatureType
import pl.szczodrzynski.edziennik.databinding.HomeworkListFragmentBinding
import pl.szczodrzynski.edziennik.ext.getInt
import pl.szczodrzynski.edziennik.ui.base.fragment.BaseFragment
@ -20,7 +21,8 @@ class HomeworkListFragment : BaseFragment<HomeworkListFragmentBinding, MainActiv
inflater = HomeworkListFragmentBinding::inflate,
) {
override fun getRefreshScrollingView() = b.list
override fun getScrollingView() = b.list
override fun getSyncParams() = FeatureType.HOMEWORK to null
override suspend fun onViewReady(savedInstanceState: Bundle?) {
val homeworkDate = arguments.getInt("homeworkDate", HomeworkDate.CURRENT)

View File

@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.enums.FeatureType
import pl.szczodrzynski.edziennik.data.enums.NavTarget
import pl.szczodrzynski.edziennik.databinding.MessagesListFragmentBinding
import pl.szczodrzynski.edziennik.ext.Bundle
@ -27,16 +28,24 @@ class MessagesListFragment : BaseFragment<MessagesListFragmentBinding, MainActiv
inflater = MessagesListFragmentBinding::inflate,
) {
override fun getRefreshScrollingView() = b.list
override fun getScrollingView() = b.list
override fun getSyncParams() = when (messageType) {
Message.TYPE_RECEIVED -> FeatureType.MESSAGES_INBOX to null
Message.TYPE_SENT -> FeatureType.MESSAGES_SENT to null
else -> null
}
private lateinit var adapter: MessagesAdapter
private val manager
get() = app.messageManager
var teachers = listOf<Teacher>()
private val messageType by lazy {
arguments.getInt("messageType", Message.TYPE_RECEIVED)
}
@SuppressLint("RestrictedApi")
override suspend fun onViewReady(savedInstanceState: Bundle?) {
val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED)
var recyclerViewState =
savedInstanceState?.getParcelable<LinearLayoutManager.SavedState>("recyclerViewState")
val searchText = savedInstanceState?.getString("searchText")
@ -75,9 +84,6 @@ class MessagesListFragment : BaseFragment<MessagesListFragmentBinding, MainActiv
}
}
if (messageType != Message.TYPE_RECEIVED && messageType != Message.TYPE_SENT)
canRefreshDisabled = true
// show/hide relevant views
b.progressBar.isVisible = false
b.list.isVisible = messages.isNotEmpty()

View File

@ -24,6 +24,7 @@ class NotesFragment : BaseFragment<NotesFragmentBinding, MainActivity>(
inflater = NotesFragmentBinding::inflate,
) {
override fun getScrollingView() = b.list
override fun getFab() =
R.string.notes_action_add to CommunityMaterial.Icon3.cmd_text_box_plus_outline

View File

@ -27,6 +27,7 @@ class NotificationsListFragment : BaseFragment<NotificationsListFragmentBinding,
inflater = NotificationsListFragmentBinding::inflate,
) {
override fun getScrollingView() = b.list
override fun getBottomSheetItems() = listOf(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_remove_notifications)

View File

@ -22,6 +22,8 @@ class TeachersListFragment : BaseFragment<TeachersListFragmentBinding, MainActiv
inflater = TeachersListFragmentBinding::inflate,
) {
override fun getScrollingView() = b.list
override suspend fun onViewReady(savedInstanceState: Bundle?) {
val adapter = TeachersAdapter(activity)

View File

@ -18,7 +18,7 @@ class TemplateListFragment : BaseFragment<TemplateListFragmentBinding, MainActiv
inflater = TemplateListFragmentBinding::inflate,
) {
override fun getRefreshScrollingView() = b.list
override fun getScrollingView() = b.list
override suspend fun onViewReady(savedInstanceState: Bundle?) {
val adapter = TemplateAdapter(activity)

View File

@ -18,7 +18,7 @@ class TemplateListPageFragment : BaseFragment<TemplateListPageFragmentBinding, M
inflater = TemplateListPageFragmentBinding::inflate,
) {
override fun getRefreshScrollingView() = b.list
override fun getScrollingView() = b.list
override suspend fun onViewReady(savedInstanceState: Bundle?) {
val adapter = TemplateAdapter(activity)

View File

@ -18,11 +18,11 @@ class TemplatePageFragment : BaseFragment<TemplatePageFragmentBinding, MainActiv
b.text.text = "Fragment VIEW READY"
b.editText.setText(savedInstanceState.getString("editText", "default"))
canRefresh = true
canRefreshDisabled = false
b.button.addOnCheckedChangeListener { button, isChecked ->
canRefresh = isChecked
canRefreshDisabled = !isChecked
}
b.button.isChecked = canRefresh
b.button.isChecked = !canRefreshDisabled
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@ -74,6 +74,7 @@ class TimetableDayFragment : BaseFragment<TimetableDayFragmentBinding, MainActiv
private var endHour = DEFAULT_END_HOUR
private var firstEventMinute = 24 * 60
private var paddingTop = 0
private var syncArgs = JsonObject()
private var viewsRemoved = false
@ -101,7 +102,8 @@ class TimetableDayFragment : BaseFragment<TimetableDayFragmentBinding, MainActiv
}
private val dayView by dayViewDelegate
override fun getRefreshScrollingView() = b.scrollView
override fun getScrollingView() = b.scrollView
override fun getSyncParams() = FeatureType.TIMETABLE to syncArgs
override suspend fun onViewReady(savedInstanceState: Bundle?) {
this.inflater = AsyncLayoutInflater(requireContext())
@ -110,6 +112,10 @@ class TimetableDayFragment : BaseFragment<TimetableDayFragmentBinding, MainActiv
startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR
endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR
syncArgs = JsonObject(
"weekStart" to date.weekStart.stringY_m_d
)
// observe lesson database
app.db.timetableDao().getAllForDate(App.profileId, date).observe(this) { lessons ->
launch {

View File

@ -92,16 +92,10 @@
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?colorSurfaceContainerLow"
android:foreground="@color/colorSurface_2dp"
android:minHeight="?actionBarSize"
android:visibility="gone"
app:tabIndicatorColor="?colorPrimary"
app:tabMode="auto"
app:tabSelectedTextColor="?colorPrimary"
app:tabTextColor="?android:textColorPrimary"
tools:visibility="visible" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>