From 016505f4ed0a6953cf0a3ac5d40402361b329507 Mon Sep 17 00:00:00 2001 From: brahmkshatriya <69040506+brahmkshatriya@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:41:45 +0530 Subject: [PATCH] Add predictive back animation for all fragments --- .../echo/offline/OfflineExtension.kt | 4 +- .../echo/ui/paging/ContinuationSource.kt | 14 ++--- .../echo/ui/paging/ErrorPagingSource.kt | 2 + .../echo/ui/paging/PagedDataExt.kt | 4 +- .../echo/utils/AnimationUtils.kt | 56 +++++++++++++++++-- app/src/main/res/values/dimens.xml | 1 + .../echo/common/clients/SearchFeedClient.kt | 8 +-- 7 files changed, 68 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/dev/brahmkshatriya/echo/offline/OfflineExtension.kt b/app/src/main/java/dev/brahmkshatriya/echo/offline/OfflineExtension.kt index e7ab44d2..4f70d98f 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/offline/OfflineExtension.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/offline/OfflineExtension.kt @@ -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 } diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/ContinuationSource.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/ContinuationSource.kt index 8b3fd15f..e4fdc48d 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/ContinuationSource.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/ContinuationSource.kt @@ -4,13 +4,13 @@ import androidx.paging.PagingConfig import androidx.paging.PagingState import dev.brahmkshatriya.echo.common.helpers.Page -class ContinuationSource( - private val load: suspend (token: P?) -> Page, - private val invalidate: (token: P?) -> Unit -) : ErrorPagingSource() { +class ContinuationSource( + private val load: suspend (token: String?) -> Page, + private val invalidate: (token: String?) -> Unit +) : ErrorPagingSource() { override val config = PagingConfig(pageSize = 10, enablePlaceholders = false) - override suspend fun loadData(params: LoadParams

): LoadResult.Page { + override suspend fun loadData(params: LoadParams): LoadResult.Page { val token = params.key val page = load(token) return LoadResult.Page( @@ -20,8 +20,8 @@ class ContinuationSource( ) } - override fun getRefreshKey(state: PagingState) = state.anchorPosition?.let { position -> - state.closestPageToPosition(position)?.nextKey?.let { key -> + override fun getRefreshKey(state: PagingState) = state.anchorPosition?.let { pos -> + state.closestPageToPosition(pos)?.nextKey?.let { key -> invalidate(key) key } diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/ErrorPagingSource.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/ErrorPagingSource.kt index a92153d6..7ba5a7da 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/ErrorPagingSource.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/ErrorPagingSource.kt @@ -10,6 +10,8 @@ abstract class ErrorPagingSource : PagingSource): LoadResult { diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/PagedDataExt.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/PagedDataExt.kt index 8a81ea35..8c60be69 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/PagedDataExt.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/paging/PagedDataExt.kt @@ -5,7 +5,7 @@ import dev.brahmkshatriya.echo.common.helpers.PagedData fun PagedData.toFlow() = when (this) { is PagedData.Single -> SingleSource({ loadList() }, { clear() }).toFlow() is PagedData.Continuous -> - ContinuationSource({ loadList(it) }, { invalidate(it) }).toFlow() + ContinuationSource({ loadList(it) }, { invalidate(it) }).toFlow() is PagedData.Concat -> - ContinuationSource({ loadList(it) }, { invalidate(it) }).toFlow() + ContinuationSource({ loadList(it) }, { invalidate(it) }).toFlow() } \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/utils/AnimationUtils.kt b/app/src/main/java/dev/brahmkshatriya/echo/utils/AnimationUtils.kt index bc78ec52..f9307fc6 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/utils/AnimationUtils.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/utils/AnimationUtils.kt @@ -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 @@ -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() @@ -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 @@ -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) { @@ -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 + } + } + } + ) } } \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 3d1815e0..163a79fb 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -6,4 +6,5 @@ @dimen/collapsed_cover_size @dimen/collapsed_cover_size @dimen/nav_height + 56dp \ No newline at end of file diff --git a/common/src/main/java/dev/brahmkshatriya/echo/common/clients/SearchFeedClient.kt b/common/src/main/java/dev/brahmkshatriya/echo/common/clients/SearchFeedClient.kt index df44871e..dbeecc74 100644 --- a/common/src/main/java/dev/brahmkshatriya/echo/common/clients/SearchFeedClient.kt +++ b/common/src/main/java/dev/brahmkshatriya/echo/common/clients/SearchFeedClient.kt @@ -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 @@ -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 @@ -44,7 +44,7 @@ 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. * @@ -52,5 +52,5 @@ interface SearchFeedClient { * @see Shelf * @see PagedData */ - fun searchFeed(query: String?, tab: Tab?): PagedData + fun searchFeed(query: String, tab: Tab?): PagedData } \ No newline at end of file