mirror of
https://github.com/Ashinch/ReadYou.git
synced 2025-02-08 16:18:40 +01:00
feat(ui): use forward and backward transition (#540)
This commit is contained in:
parent
867b9fc942
commit
34e6648323
@ -16,36 +16,67 @@ import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavDeepLink
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import com.google.accompanist.navigation.animation.composable
|
||||
import me.ash.reader.ui.motion.materialSharedAxisXIn
|
||||
import me.ash.reader.ui.motion.materialSharedAxisXOut
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
fun NavGraphBuilder.animatedComposable(
|
||||
route: String,
|
||||
arguments: List<NamedNavArgument> = emptyList(),
|
||||
deepLinks: List<NavDeepLink> = emptyList(),
|
||||
content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit,
|
||||
) = composable(
|
||||
route = route,
|
||||
arguments = arguments,
|
||||
deepLinks = deepLinks,
|
||||
enterTransition = {
|
||||
fadeIn(animationSpec = tween(220, delayMillis = 90)) +
|
||||
scaleIn(
|
||||
initialScale = 0.92f,
|
||||
animationSpec = tween(220, delayMillis = 90)
|
||||
)
|
||||
},
|
||||
exitTransition = {
|
||||
fadeOut(animationSpec = tween(90))
|
||||
},
|
||||
popEnterTransition = {
|
||||
fadeIn(animationSpec = tween(220, delayMillis = 90)) +
|
||||
scaleIn(
|
||||
initialScale = 0.92f,
|
||||
animationSpec = tween(220, delayMillis = 90)
|
||||
)
|
||||
},
|
||||
popExitTransition = {
|
||||
fadeOut(animationSpec = tween(90))
|
||||
},
|
||||
content = content
|
||||
@Deprecated(message = "Migrate to Forward and backward transition", replaceWith = ReplaceWith("forwardAndBackwardComposable(route = route, arguments = arguments, deepLinks = deepLinks) { content() }")
|
||||
)
|
||||
fun NavGraphBuilder.animatedComposable(
|
||||
route: String,
|
||||
arguments: List<NamedNavArgument> = emptyList(),
|
||||
deepLinks: List<NavDeepLink> = emptyList(),
|
||||
content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit,
|
||||
) = composable(
|
||||
route = route,
|
||||
arguments = arguments,
|
||||
deepLinks = deepLinks,
|
||||
enterTransition = {
|
||||
fadeIn(animationSpec = tween(220, delayMillis = 90)) +
|
||||
scaleIn(
|
||||
initialScale = 0.92f,
|
||||
animationSpec = tween(220, delayMillis = 90)
|
||||
)
|
||||
},
|
||||
exitTransition = {
|
||||
fadeOut(animationSpec = tween(90))
|
||||
},
|
||||
popEnterTransition = {
|
||||
fadeIn(animationSpec = tween(220, delayMillis = 90)) +
|
||||
scaleIn(
|
||||
initialScale = 0.92f,
|
||||
animationSpec = tween(220, delayMillis = 90)
|
||||
)
|
||||
},
|
||||
popExitTransition = {
|
||||
fadeOut(animationSpec = tween(90))
|
||||
},
|
||||
content = content
|
||||
)
|
||||
|
||||
private const val INITIAL_OFFSET_FACTOR = 0.10f
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
fun NavGraphBuilder.forwardAndBackwardComposable(
|
||||
route: String,
|
||||
arguments: List<NamedNavArgument> = emptyList(),
|
||||
deepLinks: List<NavDeepLink> = emptyList(),
|
||||
content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
|
||||
) = composable(
|
||||
route = route,
|
||||
arguments = arguments,
|
||||
deepLinks = deepLinks,
|
||||
enterTransition = {
|
||||
materialSharedAxisXIn(initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() })
|
||||
},
|
||||
exitTransition = {
|
||||
materialSharedAxisXOut(targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() })
|
||||
},
|
||||
popEnterTransition = {
|
||||
materialSharedAxisXIn(initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() })
|
||||
},
|
||||
popExitTransition = {
|
||||
materialSharedAxisXOut(targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() })
|
||||
},
|
||||
content = content
|
||||
)
|
||||
|
116
app/src/main/java/me/ash/reader/ui/motion/MaterialSharedAxis.kt
Normal file
116
app/src/main/java/me/ash/reader/ui/motion/MaterialSharedAxis.kt
Normal file
@ -0,0 +1,116 @@
|
||||
package me.ash.reader.ui.motion
|
||||
|
||||
/*
|
||||
* Copyright 2021 SOUP
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import androidx.compose.animation.ContentTransform
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
|
||||
/**
|
||||
* Returns the provided [Dp] as an [Int] value by the [LocalDensity].
|
||||
*
|
||||
* @param slideDistance Value to the slide distance dimension, 30dp by default.
|
||||
*/
|
||||
@Composable
|
||||
public fun rememberSlideDistance(
|
||||
slideDistance: Dp = MotionConstants.DefaultSlideDistance,
|
||||
): Int {
|
||||
val density = LocalDensity.current
|
||||
return remember(density, slideDistance) {
|
||||
with(density) { slideDistance.roundToPx() }
|
||||
}
|
||||
}
|
||||
|
||||
private const val ProgressThreshold = 0.35f
|
||||
|
||||
private val Int.ForOutgoing: Int
|
||||
get() = (this * ProgressThreshold).toInt()
|
||||
|
||||
private val Int.ForIncoming: Int
|
||||
get() = this - this.ForOutgoing
|
||||
|
||||
/**
|
||||
* [materialSharedAxisX] allows to switch a layout with shared X-axis transition.
|
||||
*
|
||||
*/
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
public fun materialSharedAxisX(
|
||||
initialOffsetX: (fullWidth: Int) -> Int,
|
||||
targetOffsetX: (fullWidth: Int) -> Int,
|
||||
durationMillis: Int = MotionConstants.DefaultMotionDuration,
|
||||
): ContentTransform = ContentTransform(materialSharedAxisXIn(
|
||||
initialOffsetX = initialOffsetX,
|
||||
durationMillis = durationMillis
|
||||
), materialSharedAxisXOut(
|
||||
targetOffsetX = targetOffsetX,
|
||||
durationMillis = durationMillis
|
||||
))
|
||||
|
||||
/**
|
||||
* [materialSharedAxisXIn] allows to switch a layout with shared X-axis enter transition.
|
||||
*/
|
||||
public fun materialSharedAxisXIn(
|
||||
initialOffsetX: (fullWidth: Int) -> Int,
|
||||
durationMillis: Int = MotionConstants.DefaultMotionDuration,
|
||||
): EnterTransition = slideInHorizontally(
|
||||
animationSpec = tween(
|
||||
durationMillis = durationMillis,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
initialOffsetX = initialOffsetX
|
||||
) + fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = durationMillis.ForIncoming,
|
||||
delayMillis = durationMillis.ForOutgoing,
|
||||
easing = LinearOutSlowInEasing
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* [materialSharedAxisXOut] allows to switch a layout with shared X-axis exit transition.
|
||||
*
|
||||
*/
|
||||
public fun materialSharedAxisXOut(
|
||||
targetOffsetX: (fullWidth: Int) -> Int,
|
||||
durationMillis: Int = MotionConstants.DefaultMotionDuration,
|
||||
): ExitTransition = slideOutHorizontally(
|
||||
animationSpec = tween(
|
||||
durationMillis = durationMillis,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
targetOffsetX = targetOffsetX
|
||||
) + fadeOut(
|
||||
animationSpec = tween(
|
||||
durationMillis = durationMillis.ForOutgoing,
|
||||
delayMillis = 0,
|
||||
easing = FastOutLinearInEasing
|
||||
)
|
||||
)
|
28
app/src/main/java/me/ash/reader/ui/motion/MotionConstants.kt
Normal file
28
app/src/main/java/me/ash/reader/ui/motion/MotionConstants.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package me.ash.reader.ui.motion
|
||||
|
||||
/*
|
||||
* Copyright 2021 SOUP
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
public object MotionConstants {
|
||||
public const val DefaultMotionDuration: Int = 300
|
||||
public const val DefaultFadeInDuration: Int = 150
|
||||
public const val DefaultFadeOutDuration: Int = 75
|
||||
public val DefaultSlideDistance: Dp = 30.dp
|
||||
}
|
@ -120,86 +120,86 @@ fun HomeEntry(
|
||||
startDestination = if (context.isFirstLaunch) RouteName.STARTUP else RouteName.FEEDS,
|
||||
) {
|
||||
// Startup
|
||||
animatedComposable(route = RouteName.STARTUP) {
|
||||
forwardAndBackwardComposable(route = RouteName.STARTUP) {
|
||||
StartupPage(navController)
|
||||
}
|
||||
|
||||
// Home
|
||||
animatedComposable(route = RouteName.FEEDS) {
|
||||
forwardAndBackwardComposable(route = RouteName.FEEDS) {
|
||||
FeedsPage(navController = navController, homeViewModel = homeViewModel)
|
||||
}
|
||||
animatedComposable(route = RouteName.FLOW) {
|
||||
forwardAndBackwardComposable(route = RouteName.FLOW) {
|
||||
FlowPage(
|
||||
navController = navController,
|
||||
homeViewModel = homeViewModel,
|
||||
)
|
||||
}
|
||||
animatedComposable(route = "${RouteName.READING}/{articleId}") {
|
||||
forwardAndBackwardComposable(route = "${RouteName.READING}/{articleId}") {
|
||||
ReadingPage(navController = navController, homeViewModel = homeViewModel)
|
||||
}
|
||||
|
||||
// Settings
|
||||
animatedComposable(route = RouteName.SETTINGS) {
|
||||
forwardAndBackwardComposable(route = RouteName.SETTINGS) {
|
||||
SettingsPage(navController)
|
||||
}
|
||||
|
||||
// Accounts
|
||||
animatedComposable(route = RouteName.ACCOUNTS) {
|
||||
forwardAndBackwardComposable(route = RouteName.ACCOUNTS) {
|
||||
AccountsPage(navController)
|
||||
}
|
||||
|
||||
animatedComposable(route = "${RouteName.ACCOUNT_DETAILS}/{accountId}") {
|
||||
forwardAndBackwardComposable(route = "${RouteName.ACCOUNT_DETAILS}/{accountId}") {
|
||||
AccountDetailsPage(navController)
|
||||
}
|
||||
|
||||
animatedComposable(route = RouteName.ADD_ACCOUNTS) {
|
||||
forwardAndBackwardComposable(route = RouteName.ADD_ACCOUNTS) {
|
||||
AddAccountsPage(navController)
|
||||
}
|
||||
|
||||
// Color & Style
|
||||
animatedComposable(route = RouteName.COLOR_AND_STYLE) {
|
||||
forwardAndBackwardComposable(route = RouteName.COLOR_AND_STYLE) {
|
||||
ColorAndStylePage(navController)
|
||||
}
|
||||
animatedComposable(route = RouteName.DARK_THEME) {
|
||||
forwardAndBackwardComposable(route = RouteName.DARK_THEME) {
|
||||
DarkThemePage(navController)
|
||||
}
|
||||
animatedComposable(route = RouteName.FEEDS_PAGE_STYLE) {
|
||||
forwardAndBackwardComposable(route = RouteName.FEEDS_PAGE_STYLE) {
|
||||
FeedsPageStylePage(navController)
|
||||
}
|
||||
animatedComposable(route = RouteName.FLOW_PAGE_STYLE) {
|
||||
forwardAndBackwardComposable(route = RouteName.FLOW_PAGE_STYLE) {
|
||||
FlowPageStylePage(navController)
|
||||
}
|
||||
animatedComposable(route = RouteName.READING_PAGE_STYLE) {
|
||||
forwardAndBackwardComposable(route = RouteName.READING_PAGE_STYLE) {
|
||||
ReadingStylePage(navController)
|
||||
}
|
||||
animatedComposable(route = RouteName.READING_DARK_THEME) {
|
||||
forwardAndBackwardComposable(route = RouteName.READING_DARK_THEME) {
|
||||
ReadingDarkThemePage(navController)
|
||||
}
|
||||
animatedComposable(route = RouteName.READING_PAGE_TITLE) {
|
||||
forwardAndBackwardComposable(route = RouteName.READING_PAGE_TITLE) {
|
||||
ReadingTitlePage(navController)
|
||||
}
|
||||
animatedComposable(route = RouteName.READING_PAGE_TEXT) {
|
||||
forwardAndBackwardComposable(route = RouteName.READING_PAGE_TEXT) {
|
||||
ReadingTextPage(navController)
|
||||
}
|
||||
animatedComposable(route = RouteName.READING_PAGE_IMAGE) {
|
||||
forwardAndBackwardComposable(route = RouteName.READING_PAGE_IMAGE) {
|
||||
ReadingImagePage(navController)
|
||||
}
|
||||
animatedComposable(route = RouteName.READING_PAGE_VIDEO) {
|
||||
forwardAndBackwardComposable(route = RouteName.READING_PAGE_VIDEO) {
|
||||
ReadingVideoPage(navController)
|
||||
}
|
||||
|
||||
// Interaction
|
||||
animatedComposable(route = RouteName.INTERACTION) {
|
||||
forwardAndBackwardComposable(route = RouteName.INTERACTION) {
|
||||
InteractionPage(navController)
|
||||
}
|
||||
|
||||
// Languages
|
||||
animatedComposable(route = RouteName.LANGUAGES) {
|
||||
forwardAndBackwardComposable(route = RouteName.LANGUAGES) {
|
||||
LanguagesPage(navController = navController)
|
||||
}
|
||||
|
||||
// Tips & Support
|
||||
animatedComposable(route = RouteName.TIPS_AND_SUPPORT) {
|
||||
forwardAndBackwardComposable(route = RouteName.TIPS_AND_SUPPORT) {
|
||||
TipsAndSupportPage(navController)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user