Updated on 2026-08-14
This commit is contained in:
parent
12fc61bbdc
commit
9334609daf
19 changed files with 297 additions and 206 deletions
|
|
@ -43,6 +43,7 @@
|
|||
android:resizeableActivity="@bool/resizeable_activity"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:allowBackup, android:fullBackupContent, android:label">
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.features.details.ui.common
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
|
|
@ -29,8 +28,6 @@ internal fun SettingsScreensScaffold(
|
|||
) {
|
||||
val backgroundColor = TangemTheme.colors.background.secondary
|
||||
|
||||
BackHandler(onBack = onBackClick)
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
topBar = {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,18 @@ package com.tangem.tap.features.home
|
|||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.arkivanov.essenty.lifecycle.subscribe
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.SystemBarsIconsDisposable
|
||||
import com.tangem.core.ui.res.LocalRootBackgroundColor
|
||||
import com.tangem.core.ui.utils.findActivity
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.home.api.HomeComponent
|
||||
|
|
@ -61,6 +65,17 @@ internal class DefaultHomeComponent @AssistedInject constructor(
|
|||
onShopButtonClick = model::onShopClick,
|
||||
onSearchTokensClick = model::onSearchClick,
|
||||
)
|
||||
|
||||
val rootBackgroundColor = LocalRootBackgroundColor.current
|
||||
val previousColor = remember { rootBackgroundColor.value }
|
||||
val storiesBackgroundColor = Color(color = 0xFF010101)
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
rootBackgroundColor.value = storiesBackgroundColor
|
||||
onDispose {
|
||||
rootBackgroundColor.value = previousColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun newState(state: HomeState) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.routing
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -8,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -17,25 +17,31 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.core.content.ContextCompat.startActivity
|
||||
import com.arkivanov.decompose.ExperimentalDecomposeApi
|
||||
import com.arkivanov.decompose.extensions.compose.stack.Children
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.fade
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.androidPredictiveBackAnimatable
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.predictiveBackAnimation
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
|
||||
import com.arkivanov.decompose.router.stack.ChildStack
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.arkivanov.essenty.backhandler.BackHandler
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
|
||||
import com.tangem.core.ui.message.EventMessageEffect
|
||||
import com.tangem.core.ui.res.LocalIsNavigationRefactoringEnabled
|
||||
import com.tangem.core.ui.res.LocalRootBackgroundColor
|
||||
import com.tangem.core.ui.res.LocalSnackbarHostState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.routing.component.RoutingComponent
|
||||
import com.tangem.tap.routing.transitions.RoutingTransitionAnimationFactory
|
||||
|
||||
@OptIn(ExperimentalDecomposeApi::class)
|
||||
@Composable
|
||||
internal fun RootContent(
|
||||
stack: Value<ChildStack<AppRoute, RoutingComponent.Child>>,
|
||||
wcContent: @Composable (modifier: Modifier) -> Unit,
|
||||
backHandler: BackHandler,
|
||||
uiDependencies: UiDependencies,
|
||||
wcContent: @Composable (modifier: Modifier) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
|
@ -44,45 +50,62 @@ internal fun RootContent(
|
|||
activity = context as Activity,
|
||||
uiDependencies = uiDependencies,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalIsNavigationRefactoringEnabled provides true,
|
||||
) {
|
||||
val snackbarHostState = LocalSnackbarHostState.current
|
||||
val snackbarHostState = LocalSnackbarHostState.current
|
||||
|
||||
Box(Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
Children(
|
||||
modifier = modifier,
|
||||
animation = stackAnimation(fade()),
|
||||
stack = stack,
|
||||
) { child ->
|
||||
when (val instance = child.instance) {
|
||||
is RoutingComponent.Child.Initial -> Unit
|
||||
is RoutingComponent.Child.ComposableComponent -> {
|
||||
instance.component.Content(Modifier.fillMaxSize())
|
||||
Box(Modifier.background(LocalRootBackgroundColor.current.value)) {
|
||||
Children(
|
||||
modifier = modifier,
|
||||
animation = childrenAnimation(backHandler = backHandler, onBack = onBack),
|
||||
stack = stack,
|
||||
) { child ->
|
||||
when (val instance = child.instance) {
|
||||
is RoutingComponent.Child.Initial -> Unit
|
||||
is RoutingComponent.Child.ComposableComponent -> {
|
||||
instance.component.Content(Modifier.fillMaxSize())
|
||||
}
|
||||
is RoutingComponent.Child.LegacyIntent -> {
|
||||
// TODO: Remove and use it's own router: [REDACTED_JIRA]
|
||||
LaunchedEffect(instance) {
|
||||
startActivity(context, instance.intent, Bundle.EMPTY)
|
||||
}
|
||||
is RoutingComponent.Child.LegacyIntent -> {
|
||||
// TODO: Remove and use it's own router: [REDACTED_JIRA]
|
||||
LaunchedEffect(instance) {
|
||||
startActivity(context, instance.intent, Bundle.EMPTY)
|
||||
}
|
||||
}
|
||||
is RoutingComponent.Child.LegacyFragment,
|
||||
-> error("Unsupported child: $instance")
|
||||
}
|
||||
}
|
||||
|
||||
wcContent(Modifier.fillMaxSize())
|
||||
|
||||
TangemSnackbarHost(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.navigationBarsPadding()
|
||||
.padding(all = 16.dp),
|
||||
hostState = snackbarHostState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
EventMessageEffect()
|
||||
wcContent(Modifier.fillMaxSize())
|
||||
|
||||
TangemSnackbarHost(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.navigationBarsPadding()
|
||||
.padding(all = 16.dp),
|
||||
hostState = snackbarHostState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
EventMessageEffect()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalDecomposeApi::class)
|
||||
private fun childrenAnimation(
|
||||
backHandler: BackHandler,
|
||||
onBack: () -> Unit,
|
||||
): StackAnimation<AppRoute, RoutingComponent.Child> {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
predictiveBackAnimation(
|
||||
backHandler = backHandler,
|
||||
onBack = onBack,
|
||||
selector = { backEvent, _, _ ->
|
||||
androidPredictiveBackAnimatable(backEvent)
|
||||
},
|
||||
fallbackAnimation = stackAnimation {
|
||||
RoutingTransitionAnimationFactory.create(it.configuration)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
stackAnimation {
|
||||
RoutingTransitionAnimationFactory.create(it.configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,9 @@ package com.tangem.tap.routing.component
|
|||
|
||||
import android.content.Intent
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.utils.Provider
|
||||
|
||||
internal interface RoutingComponent : ComposableContentComponent {
|
||||
|
||||
|
|
@ -16,11 +14,6 @@ internal interface RoutingComponent : ComposableContentComponent {
|
|||
// TODO: Remove and find initial intent in RoutingComponent: [REDACTED_JIRA]
|
||||
data object Initial : Child()
|
||||
|
||||
data class LegacyFragment(
|
||||
val name: String,
|
||||
val fragmentProvider: Provider<Fragment>,
|
||||
) : Child()
|
||||
|
||||
data class LegacyIntent(val intent: Intent) : Child()
|
||||
|
||||
data class ComposableComponent(
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
stack = stack,
|
||||
uiDependencies = uiDependencies,
|
||||
wcContent = { wcRoutingComponent.Content(it) },
|
||||
backHandler = backHandler,
|
||||
onBack = router::pop,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
package com.tangem.tap.routing.transitions
|
||||
|
||||
import androidx.compose.animation.core.CubicBezierEasing
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.layout
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.*
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import kotlin.compareTo
|
||||
import kotlin.times
|
||||
|
||||
object RoutingTransitionAnimationFactory {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun create(appRoute: AppRoute): StackAnimator {
|
||||
return when (appRoute) {
|
||||
is AppRoute.Onboarding,
|
||||
is AppRoute.Welcome,
|
||||
is AppRoute.Home,
|
||||
-> fade(tween(400)).plus(scale(tween(400)))
|
||||
is AppRoute.Wallet,
|
||||
-> slideAndFade(directions = setOf(Direction.ENTER_BACK, Direction.EXIT_BACK))
|
||||
.plus(
|
||||
scaleWithDirection(
|
||||
directions = setOf(Direction.ENTER_FRONT, Direction.EXIT_FRONT),
|
||||
animationSpec = tween(400),
|
||||
),
|
||||
)
|
||||
else -> slideAndFade()
|
||||
}
|
||||
}
|
||||
|
||||
private fun scaleWithDirection(
|
||||
directions: Set<Direction>,
|
||||
animationSpec: FiniteAnimationSpec<Float> = tween(),
|
||||
frontFactor: Float = 1.15F,
|
||||
backFactor: Float = 0.95F,
|
||||
): StackAnimator = stackAnimator(animationSpec = animationSpec) { factor, direction, content ->
|
||||
content(
|
||||
Modifier.graphicsLayer {
|
||||
val scaleFactor = if (directions.contains(direction)) {
|
||||
if (factor >= 0F) {
|
||||
factor * (frontFactor - 1F) + 1F
|
||||
} else {
|
||||
factor * (1F - backFactor) + 1F
|
||||
}
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
scaleX = scaleFactor
|
||||
scaleY = scaleFactor
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun slideAndFade(directions: Set<Direction>? = null): StackAnimator {
|
||||
val easing = CubicBezierEasing(0.55f, 0.0f, 0.0f, 1f)
|
||||
|
||||
return stackAnimator(
|
||||
animationSpec = tween(durationMillis = 400, easing = easing),
|
||||
) { factor, direction, content ->
|
||||
content(
|
||||
if (directions == null || directions.contains(direction)) {
|
||||
Modifier.offsetXFactor(factor)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
}.plus(
|
||||
fade(
|
||||
animationSpec = tween(
|
||||
delayMillis = 50,
|
||||
durationMillis = 300,
|
||||
easing = easing,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun Modifier.offsetXFactor(factor: Float): Modifier = layout { measurable, constraints ->
|
||||
val placeable = measurable.measure(constraints)
|
||||
|
||||
layout(placeable.width, placeable.height) {
|
||||
placeable.placeRelative(x = (placeable.width.toFloat() * factor * 0.15f).toInt(), y = 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue