Skip to content

Commit

Permalink
Add predictive back animation for all fragments
Browse files Browse the repository at this point in the history
  • Loading branch information
brahmkshatriya committed Dec 10, 2024
1 parent 9e20e65 commit 016505f
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,8 @@ class OfflineExtension(
override suspend fun searchTabs(query: String) =
listOf("All", "Tracks", "Albums", "Artists").map { Tab(it, it) }

override fun searchFeed(query: String?, tab: Tab?) = run {
query ?: return@run emptyList()
override fun searchFeed(query: String, tab: Tab?) = run {
query.ifBlank { return@run emptyList() }
saveInHistory(query)
val tracks = library.songList.map { it }.searchBy(query) {
listOf(it.title, it.album?.title) + it.artists.map { artist -> artist.name }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import androidx.paging.PagingConfig
import androidx.paging.PagingState
import dev.brahmkshatriya.echo.common.helpers.Page

class ContinuationSource<C : Any, P : Any>(
private val load: suspend (token: P?) -> Page<C, P?>,
private val invalidate: (token: P?) -> Unit
) : ErrorPagingSource<P, C>() {
class ContinuationSource<C : Any>(
private val load: suspend (token: String?) -> Page<C>,
private val invalidate: (token: String?) -> Unit
) : ErrorPagingSource<String, C>() {

override val config = PagingConfig(pageSize = 10, enablePlaceholders = false)
override suspend fun loadData(params: LoadParams<P>): LoadResult.Page<P, C> {
override suspend fun loadData(params: LoadParams<String>): LoadResult.Page<String, C> {
val token = params.key
val page = load(token)
return LoadResult.Page(
Expand All @@ -20,8 +20,8 @@ class ContinuationSource<C : Any, P : Any>(
)
}

override fun getRefreshKey(state: PagingState<P, C>) = state.anchorPosition?.let { position ->
state.closestPageToPosition(position)?.nextKey?.let { key ->
override fun getRefreshKey(state: PagingState<String, C>) = state.anchorPosition?.let { pos ->
state.closestPageToPosition(pos)?.nextKey?.let { key ->
invalidate(key)
key
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ abstract class ErrorPagingSource<Key : Any, Value : Any> : PagingSource<Key, Val
pagingSourceFactory = { this }
).flow

override val keyReuseSupported = true

abstract val config: PagingConfig

override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import dev.brahmkshatriya.echo.common.helpers.PagedData
fun <T : Any> PagedData<T>.toFlow() = when (this) {
is PagedData.Single -> SingleSource({ loadList() }, { clear() }).toFlow()
is PagedData.Continuous ->
ContinuationSource<T, String>({ loadList(it) }, { invalidate(it) }).toFlow()
ContinuationSource({ loadList(it) }, { invalidate(it) }).toFlow()
is PagedData.Concat ->
ContinuationSource<T, String>({ loadList(it) }, { invalidate(it) }).toFlow()
ContinuationSource({ loadList(it) }, { invalidate(it) }).toFlow()
}
56 changes: 50 additions & 6 deletions app/src/main/java/dev/brahmkshatriya/echo/utils/AnimationUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ package dev.brahmkshatriya.echo.utils
import android.content.Context.MODE_PRIVATE
import android.view.View
import android.view.ViewPropertyAnimator
import androidx.activity.BackEventCompat
import androidx.activity.OnBackPressedCallback
import androidx.core.view.animation.PathInterpolatorCompat
import androidx.core.view.doOnPreDraw
import androidx.core.view.forEach
import androidx.core.view.forEachIndexed
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.google.android.material.R
import com.google.android.material.color.MaterialColors
import com.google.android.material.motion.MotionUtils
import com.google.android.material.navigation.NavigationBarView
import com.google.android.material.transition.MaterialArcMotion
import com.google.android.material.transition.MaterialContainerTransform
import com.google.android.material.transition.MaterialSharedAxis
import dev.brahmkshatriya.echo.R
import dev.brahmkshatriya.echo.ui.settings.LookFragment.Companion.ANIMATIONS_KEY
import dev.brahmkshatriya.echo.ui.settings.LookFragment.Companion.SHARED_ELEMENT_KEY

Expand All @@ -24,7 +27,8 @@ fun startAnimation(
) = view.run {
clearAnimation()
val interpolator = MotionUtils.resolveThemeInterpolator(
context, R.attr.motionEasingStandardInterpolator, FastOutSlowInInterpolator()
context, com.google.android.material.R.attr.motionEasingStandardInterpolator,
FastOutSlowInInterpolator()
)
val duration = animationDuration * durationMultiplier
animation.setInterpolator(interpolator).setDuration(duration.toLong()).start()
Expand Down Expand Up @@ -93,7 +97,7 @@ fun animateTranslation(view: View, old: Int, newHeight: Int) = view.run {
private val View.animationDuration: Long
get() = context.applicationContext.run {
MotionUtils.resolveThemeDuration(
this, R.attr.motionDurationMedium1, 350
this, com.google.android.material.R.attr.motionDurationMedium1, 350
).toLong()
}
private val View.animations
Expand All @@ -106,10 +110,10 @@ private val View.sharedElementTransitions
getSharedPreferences(packageName, MODE_PRIVATE).getBoolean(SHARED_ELEMENT_KEY, true)
}

private val GestureInterpolator = PathInterpolatorCompat.create(0f, 0f, 0f, 1f)

fun Fragment.setupTransition(view: View) {
val color = MaterialColors.getColor(
view, dev.brahmkshatriya.echo.R.attr.echoBackground, 0
)
val color = MaterialColors.getColor(view, R.attr.echoBackground, 0)
view.setBackgroundColor(color)

if (view.animations) {
Expand All @@ -132,5 +136,45 @@ fun Fragment.setupTransition(view: View) {

postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
if(parentFragmentManager.backStackEntryCount < 1) return
val predictiveBackMargin = resources.getDimensionPixelSize(R.dimen.predictive_back_margin)
var initialTouchY = -1f
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
parentFragmentManager.popBackStack()
}

override fun handleOnBackProgressed(backEvent: BackEventCompat) {
val progress = GestureInterpolator.getInterpolation(backEvent.progress)
if(transitionName == null)
view.alpha = 1 - progress
if (initialTouchY < 0f)
initialTouchY = backEvent.touchY
val progressY = GestureInterpolator.getInterpolation(
(backEvent.touchY - initialTouchY) / view.height
)
val maxTranslationX = (view.width / 20) - predictiveBackMargin
view.translationX = progress * maxTranslationX *
(if (backEvent.swipeEdge == BackEventCompat.EDGE_LEFT) -1 else 1)
val maxTranslationY = (view.height / 20) - predictiveBackMargin
view.translationY = progressY * maxTranslationY * -1
val scale = 1f - (0.1f * progress)
view.scaleX = scale
view.scaleY = scale
}

override fun handleOnBackCancelled() {
initialTouchY = -1f
view.run {
translationX = 0f
translationY = 0f
scaleX = 1f
scaleY = 1f
}
}
}
)
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
<dimen name="nav_width">@dimen/collapsed_cover_size</dimen>
<dimen name="nav_height">@dimen/collapsed_cover_size</dimen>
<dimen name="player_info_peek_height">@dimen/nav_height</dimen>
<dimen name="predictive_back_margin">56dp</dimen>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface SearchFeedClient {
/**
* Searches for quick search items.
*
* @param query the query to search for.
* @param query the query to search for, will be empty if the user hasn't typed anything.
* @return the quick search items.
*
* @see QuickSearchItem
Expand All @@ -34,7 +34,7 @@ interface SearchFeedClient {
/**
* Searches for tabs.
*
* @param query the query to search for.
* @param query the query to search for, will be empty if the user hasn't typed anything.
* @return the tabs.
*
* @see Tab
Expand All @@ -44,13 +44,13 @@ interface SearchFeedClient {
/**
* Searches for shelves.
*
* @param query the query to search for.
* @param query the query to search for, will be empty if the user hasn't typed anything.
* @param tab the tab to search in.
* @return the paged shelves.
*
* @see Tab
* @see Shelf
* @see PagedData
*/
fun searchFeed(query: String?, tab: Tab?): PagedData<Shelf>
fun searchFeed(query: String, tab: Tab?): PagedData<Shelf>
}

0 comments on commit 016505f

Please sign in to comment.