Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-29 13:12:57 +03:00
parent ecb494f839
commit 42b8de7909
5 changed files with 216 additions and 2 deletions

View file

@ -22,6 +22,12 @@ dependencies {
implementation(projects.core.utils)
implementation(projects.core.ui)
implementation(projects.core.res)
implementation(projects.core.decompose)
/** Domain */
implementation(projects.domain.models)
implementation(projects.domain.feedback)
implementation(projects.domain.core)
/** Tangem libraries */
implementation(deps.tangem.card.core)
@ -31,8 +37,6 @@ dependencies {
/** AndroidX libraries */
implementation(deps.androidx.core.ktx)
implementation(deps.androidx.appCompat)
implementation(deps.androidx.constraintLayout)
implementation(deps.lifecycle.runtime.ktx)
/** Compose libraries */

View file

@ -0,0 +1,31 @@
package com.tangem.features.onboarding.v2.stepper.api
import androidx.annotation.IntRange
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.scan.ScanResponse
import kotlinx.coroutines.flow.StateFlow
interface OnboardingStepperComponent : ComposableContentComponent {
data class StepperState(
@IntRange(from = 0) val currentStep: Int,
@IntRange(from = 0) val steps: Int,
val title: TextReference,
)
class Params(
val initState: StepperState,
val popBack: () -> Unit,
val scanResponse: ScanResponse,
)
val state: StateFlow<StepperState>
fun changeStep(step: Int)
fun nextStep()
fun changeTitle(title: TextReference)
interface Factory : ComponentFactory<Params, OnboardingStepperComponent>
}

View file

@ -0,0 +1,76 @@
package com.tangem.features.onboarding.v2.stepper.impl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.essenty.instancekeeper.getOrCreateSimple
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent
import com.tangem.features.onboarding.v2.stepper.impl.ui.OnboardingStepper
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
internal class DefaultOnboardingStepperComponent @AssistedInject constructor(
@Assisted val context: AppComponentContext,
@Assisted val params: OnboardingStepperComponent.Params,
private val getCardInfoUseCase: GetCardInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
) : OnboardingStepperComponent, AppComponentContext by context {
override val state = instanceKeeper.getOrCreateSimple { MutableStateFlow(params.initState) }
override fun changeStep(step: Int) {
state.update { it.copy(currentStep = step.coerceAtLeast(minimumValue = 0)) }
}
override fun nextStep() {
state.update { state ->
if (state.currentStep < state.steps - 1) {
state.copy(currentStep = state.currentStep + 1)
} else {
state
}
}
}
override fun changeTitle(title: TextReference) {
state.update { it.copy(title = title) }
}
private fun openSupport() {
componentScope.launch {
val cardInfo = getCardInfoUseCase(params.scanResponse).getOrNull() ?: return@launch
sendFeedbackEmailUseCase(FeedbackEmailType.DirectUserRequest(cardInfo))
}
}
@Composable
override fun Content(modifier: Modifier) {
val state by state.collectAsStateWithLifecycle()
OnboardingStepper(
state = state,
onBackClick = remember(this) { params.popBack },
onSupportButtonClick = remember(this) { ::openSupport },
modifier = modifier,
)
}
@AssistedFactory
interface Factory : OnboardingStepperComponent.Factory {
override fun create(
context: AppComponentContext,
params: OnboardingStepperComponent.Params,
): DefaultOnboardingStepperComponent
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.onboarding.v2.stepper.impl.di
import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent
import com.tangem.features.onboarding.v2.stepper.impl.DefaultOnboardingStepperComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ComponentModule {
@Binds
@Singleton
fun bindOnboardingStepperComponent(
factory: DefaultOnboardingStepperComponent.Factory,
): OnboardingStepperComponent.Factory
}

View file

@ -0,0 +1,83 @@
package com.tangem.features.onboarding.v2.stepper.impl.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.progressbar.LinearProgressIndicator
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemAnimations
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent
@Composable
internal fun OnboardingStepper(
state: OnboardingStepperComponent.StepperState,
onBackClick: () -> Unit,
onSupportButtonClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val fraction = state.currentStep.toFloat() / state.steps.coerceAtLeast(1)
val animatedIndicatorFraction by TangemAnimations.horizontalIndicatorAsState(targetFraction = fraction)
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
TangemTopAppBar(
startButton = TopAppBarButtonUM.Back(onBackClick),
endButton = TopAppBarButtonUM(iconRes = R.drawable.ic_chat_24, onIconClicked = onSupportButtonClick)
.takeIf { state.steps != state.currentStep },
title = if (state.steps == state.currentStep) {
resourceReference(R.string.common_done)
} else {
state.title
},
containerColor = TangemTheme.colors.background.primary,
modifier = modifier,
)
LinearProgressIndicator(
modifier = Modifier
.padding(horizontal = 16.dp)
.height(4.dp)
.fillMaxWidth(),
progress = { animatedIndicatorFraction },
color = TangemTheme.colors.icon.primary1,
backgroundColor = TangemTheme.colors.background.tertiary,
strokeCap = StrokeCap.Round,
)
}
}
@Preview
@Composable
private fun Preview() {
TangemThemePreview {
Box(
Modifier
.fillMaxSize()
.background(TangemTheme.colors.background.primary),
) {
OnboardingStepper(
modifier = Modifier.align(Alignment.TopCenter),
state = OnboardingStepperComponent.StepperState(
currentStep = 2,
steps = 3,
title = resourceReference(R.string.common_done),
),
onBackClick = {},
onSupportButtonClick = {},
)
}
}
}