Replace "Hide compose button while scrolling" setting with bottom padding (#4486)

As discussed in our contributors meeting.

Advantages:
- last element of list never obscured by action button
- less code that runs on every scroll
- less settings to worry about

Additionally: 
- Added a (smaller) padding to the bottom of lists without action
button, I think it looks nice if there is a bit of white space and the
nav bar divider and the last list divider don't touch.
- The list of filters had no dividers, I added them.
- Recyclerviews with fixed height (Drafts, Filters, edits) now have
scrollbars
- code formatted all touched xml files

closes https://github.com/tuskyapp/Tusky/issues/1563

<img
src="https://github.com/tuskyapp/Tusky/assets/10157047/cd50199f-e84f-4402-93e4-a5a1beba2a08"
width="280"/>
This commit is contained in:
Konrad Pozniak 2024-06-05 19:36:58 +02:00 committed by GitHub
commit 34b53a3c59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 98 additions and 151 deletions

View file

@ -134,6 +134,10 @@ class TuskyApplication : Application(), Configuration.Provider {
editor.remove(PrefKeys.TAB_SHOW_HOME_SELF_BOOSTS)
}
if (oldVersion < 2024060201) {
editor.remove(PrefKeys.Deprecated.FAB_HIDE)
}
editor.putInt(PrefKeys.SCHEMA_VERSION, newVersion)
editor.apply()
}

View file

@ -129,8 +129,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
private var animateAvatar: Boolean = false
private var animateEmojis: Boolean = false
// fields for scroll animation
private var hideFab: Boolean = false
// for scroll animation
private var oldOffset: Int = 0
@ColorInt
@ -170,7 +169,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
animateAvatar = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
handleWindowInsets()
setupToolbar()
@ -364,15 +362,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
supportActionBar?.setDisplayShowTitleEnabled(false)
}
if (hideFab && !blocking) {
if (verticalOffset > oldOffset) {
binding.accountFloatingActionButton.show()
}
if (verticalOffset < oldOffset) {
binding.accountFloatingActionButton.hide()
}
}
val scaledAvatarSize = (avatarSize + verticalOffset) / avatarSize
binding.accountAvatarImageView.scaleX = scaledAvatarSize

View file

@ -44,7 +44,6 @@ import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
import com.keylesspalace.tusky.components.account.AccountActivity
import com.keylesspalace.tusky.databinding.FragmentTimelineBinding
import com.keylesspalace.tusky.fragment.SFragment
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
import com.keylesspalace.tusky.interfaces.ReselectableFragment
import com.keylesspalace.tusky.interfaces.StatusActionListener
import com.keylesspalace.tusky.settings.PrefKeys
@ -86,8 +85,6 @@ class ConversationsFragment :
private var adapter: ConversationAdapter? = null
private var hideFab = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
@ -173,24 +170,6 @@ class ConversationsFragment :
}
})
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
val composeButton = (activity as ActionButtonActivity).actionButton
if (composeButton != null) {
if (hideFab) {
if (dy > 0 && composeButton.isShown) {
composeButton.hide() // hides the button if we're scrolling down
} else if (dy < 0 && !composeButton.isShown) {
composeButton.show() // shows it if we are scrolling up
}
} else if (!composeButton.isShown) {
composeButton.show()
}
}
}
})
viewLifecycleOwner.lifecycleScope.launch {
viewModel.conversationFlow.collectLatest { pagingData ->
adapter.submitData(pagingData)
@ -407,10 +386,6 @@ class ConversationsFragment :
private fun onPreferenceChanged(adapter: ConversationAdapter, key: String) {
when (key) {
PrefKeys.FAB_HIDE -> {
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
}
PrefKeys.MEDIA_PREVIEW_ENABLED -> {
val enabled = accountManager.activeAccount!!.mediaPreviewEnabled
val oldMediaPreviewEnabled = adapter.mediaPreviewEnabled

View file

@ -6,6 +6,7 @@ import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ActivityFiltersBinding
@ -51,6 +52,10 @@ class FiltersActivity : BaseActivity(), FiltersListener {
setTitle(R.string.pref_title_timeline_filters)
binding.filtersList.addItemDecoration(
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
)
observeViewModel()
}

View file

@ -13,7 +13,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import at.connyduck.calladapter.networkresult.fold
import com.google.android.material.snackbar.Snackbar
@ -24,7 +23,6 @@ import com.keylesspalace.tusky.components.compose.ComposeAutoCompleteAdapter
import com.keylesspalace.tusky.databinding.ActivityFollowedTagsBinding
import com.keylesspalace.tusky.interfaces.HashtagActionListener
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.copyToClipboard
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
@ -85,19 +83,6 @@ class FollowedTagsActivity :
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
)
(binding.followedTagsView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
val hideFab = sharedPreferences.getBoolean(PrefKeys.FAB_HIDE, false)
if (hideFab) {
binding.followedTagsView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0 && binding.fab.isShown) {
binding.fab.hide()
} else if (dy < 0 && !binding.fab.isShown) {
binding.fab.show()
}
}
})
}
}
private fun setupAdapter(): FollowedTagsAdapter {

View file

@ -55,7 +55,6 @@ import com.keylesspalace.tusky.databinding.NotificationsFilterBinding
import com.keylesspalace.tusky.entity.Notification
import com.keylesspalace.tusky.fragment.SFragment
import com.keylesspalace.tusky.interfaces.AccountActionListener
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
import com.keylesspalace.tusky.interfaces.ReselectableFragment
import com.keylesspalace.tusky.interfaces.StatusActionListener
import com.keylesspalace.tusky.settings.PrefKeys
@ -100,7 +99,6 @@ class NotificationsFragment :
private var adapter: NotificationsPagingAdapter? = null
private var hideFab: Boolean = false
private var showNotificationsFilterBar: Boolean = true
private var readingOrder: ReadingOrder = ReadingOrder.NEWEST_FIRST
@ -180,26 +178,8 @@ class NotificationsFragment :
DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
)
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
readingOrder = ReadingOrder.from(preferences.getString(PrefKeys.READING_ORDER, null))
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
val composeButton = (activity as ActionButtonActivity).actionButton
if (composeButton != null) {
if (hideFab) {
if (dy > 0 && composeButton.isShown) {
composeButton.hide() // hides the button if we're scrolling down
} else if (dy < 0 && !composeButton.isShown) {
composeButton.show() // shows it if we are scrolling up
}
} else if (!composeButton.isShown) {
composeButton.show()
}
}
}
})
adapter.addLoadStateListener { loadState ->
if (loadState.refresh != LoadState.Loading && loadState.source.refresh != LoadState.Loading) {
binding.swipeRefreshLayout.isRefreshing = false
@ -481,10 +461,6 @@ class NotificationsFragment :
private fun onPreferenceChanged(adapter: NotificationsPagingAdapter, key: String) {
when (key) {
PrefKeys.FAB_HIDE -> {
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
}
PrefKeys.MEDIA_PREVIEW_ENABLED -> {
val enabled = accountManager.activeAccount!!.mediaPreviewEnabled
val oldMediaPreviewEnabled = adapter.mediaPreviewEnabled

View file

@ -170,13 +170,6 @@ class PreferencesFragment : PreferenceFragmentCompat() {
isSingleLineTitle = false
}
switchPreference {
setDefaultValue(false)
key = PrefKeys.FAB_HIDE
setTitle(R.string.pref_title_hide_follow_button)
isSingleLineTitle = false
}
switchPreference {
setDefaultValue(false)
key = PrefKeys.ABSOLUTE_TIME_VIEW

View file

@ -25,6 +25,7 @@ import android.view.View
import android.view.accessibility.AccessibilityManager
import androidx.core.content.getSystemService
import androidx.core.view.MenuProvider
import androidx.core.view.updatePadding
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -109,7 +110,6 @@ class TimelineFragment :
private var adapter: TimelinePagingAdapter? = null
private var isSwipeToRefreshEnabled = true
private var hideFab = false
/**
* Adapter position of the placeholder that was most recently clicked to "Load more". If null
@ -279,26 +279,6 @@ class TimelineFragment :
}
}
if (actionButtonPresent()) {
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
val composeButton = (activity as ActionButtonActivity).actionButton
if (composeButton != null) {
if (hideFab) {
if (dy > 0 && composeButton.isShown) {
composeButton.hide() // hides the button if we're scrolling down
} else if (dy < 0 && !composeButton.isShown) {
composeButton.show() // shows it if we are scrolling up
}
} else if (!composeButton.isShown) {
composeButton.show()
}
}
}
})
}
viewLifecycleOwner.lifecycleScope.launch {
eventHub.events.collect { event ->
when (event) {
@ -411,6 +391,14 @@ class TimelineFragment :
val divider = DividerItemDecoration(context, RecyclerView.VERTICAL)
binding.recyclerView.addItemDecoration(divider)
val recyclerViewBottomPadding = if ((activity as? ActionButtonActivity?)?.actionButton != null) {
resources.getDimensionPixelSize(R.dimen.recyclerview_bottom_padding_actionbutton)
} else {
resources.getDimensionPixelSize(R.dimen.recyclerview_bottom_padding_no_actionbutton)
}
binding.recyclerView.updatePadding(bottom = recyclerViewBottomPadding)
// CWs are expanded without animation, buttons animate itself, we don't need it basically
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
binding.recyclerView.adapter = adapter
@ -573,10 +561,6 @@ class TimelineFragment :
private fun onPreferenceChanged(adapter: TimelinePagingAdapter, key: String) {
when (key) {
PrefKeys.FAB_HIDE -> {
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
}
PrefKeys.MEDIA_PREVIEW_ENABLED -> {
val enabled = accountManager.activeAccount!!.mediaPreviewEnabled
val oldMediaPreviewEnabled = adapter.mediaPreviewEnabled

View file

@ -56,7 +56,6 @@ object PrefKeys {
const val SCHEMA_VERSION: String = "schema_version"
const val APP_THEME = "appTheme"
const val FAB_HIDE = "fabHide"
const val LANGUAGE = "language"
const val STATUS_TEXT_SIZE = "statusTextSize"
const val READING_ORDER = "readingOrder"
@ -112,4 +111,8 @@ object PrefKeys {
/** UI text scaling factor, stored as float, 100 = 100% = no scaling */
const val UI_TEXT_SCALE_RATIO = "uiTextScaleRatio"
object Deprecated {
const val FAB_HIDE = "fabHide"
}
}