diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d53a38d574..d592ad2b98 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -215,6 +215,8 @@ dependencies { implementation(projects.features.walletconnect.impl) implementation(projects.features.usedesk.api) implementation(projects.features.usedesk.impl) + implementation(projects.features.hotWallet.api) + implementation(projects.features.hotWallet.impl) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) diff --git a/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt b/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt index 85e57a4ae2..ade00b0024 100644 --- a/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt @@ -16,6 +16,7 @@ import com.tangem.core.ui.utils.findActivity import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.home.api.HomeComponent import com.tangem.tap.features.home.compose.StoriesScreen +import com.tangem.tap.features.home.compose.StoriesScreenV2 import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.home.redux.HomeState import com.tangem.tap.store @@ -57,12 +58,22 @@ internal class DefaultHomeComponent @AssistedInject constructor( val activity = LocalContext.current.findActivity() BackHandler(onBack = activity::finish) SystemBarsIconsDisposable(darkIcons = false) - StoriesScreen( - homeState = homeState, - onScanButtonClick = model::onScanClick, - onShopButtonClick = model::onShopClick, - onSearchTokensClick = model::onSearchClick, - ) + if (homeState.value.isV2StoriesEnabled) { + StoriesScreenV2( + homeState = homeState, + onCreateNewWalletButtonClick = model::onCreateNewWalletScreen, + onAddExistingWalletButtonClick = model::onAddExistingWalletScreen, + onScanButtonClick = model::onScanClick, + ) + } else { + StoriesScreen( + homeState = homeState, + onScanButtonClick = model::onScanClick, + onShopButtonClick = model::onShopClick, + onSearchTokensClick = model::onSearchClick, + ) + } + ChangeRootBackgroundColorEffect(Color(color = 0xFF010101)) } diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt b/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt index 132cbd1773..8936e89b41 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt @@ -71,6 +71,14 @@ internal class HomeModel @Inject constructor( .launchIn(modelScope) } + fun onCreateNewWalletScreen() { + // TODO implement navigation to create new wallet + } + + fun onAddExistingWalletScreen() { + // TODO implement navigation to add existing wallet + } + fun onScanClick() { analyticsEventHandler.send(IntroductionProcess.ButtonScanCard()) scanCard() diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreenV2.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreenV2.kt new file mode 100644 index 0000000000..a17f707453 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreenV2.kt @@ -0,0 +1,252 @@ +@file:Suppress("MagicNumber") + +package com.tangem.tap.features.home.compose + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.TestTags +import com.tangem.tap.features.home.compose.content.* +import com.tangem.tap.features.home.compose.views.HomeButtonsV2 +import com.tangem.tap.features.home.compose.views.StoriesProgressBar +import com.tangem.tap.features.home.redux.HomeState +import com.tangem.tap.features.home.redux.Stories +import com.tangem.wallet.R +import kotlin.math.max + +@Composable +internal fun StoriesScreenV2( + homeState: MutableState, + onCreateNewWalletButtonClick: () -> Unit, + onAddExistingWalletButtonClick: () -> Unit, + onScanButtonClick: () -> Unit, +) { + val state = homeState.value + + var currentStory by remember { mutableStateOf(state.firstStory) } + val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory)) + + val goToPreviousStory = remember(currentStory, currentStoryIndex) { + { currentStory = state.stories[max(0, currentStoryIndex - 1)] } + } + val goToNextStory = remember(currentStory, currentStoryIndex) { + { + currentStory = if (currentStoryIndex < state.stories.lastIndex) { + state.stories[currentStoryIndex + 1] + } else { + state.firstStory + } + } + } + + // todo refactor [REDACTED_TASK_KEY] + StoriesScreenContentV2( + modifier = Modifier + .fillMaxSize() + .testTag(TestTags.STORIES_SCREEN), + config = StoriesScreenContentV2Config( + storiesSize = state.stories.lastIndex, + currentStoryIndex = currentStoryIndex, + currentStory = currentStory, + isScanInProgress = homeState.value.scanInProgress, + onGoToPreviousStory = goToPreviousStory, + onGoToNextStory = goToNextStory, + onCreateNewWalletButtonClick = onCreateNewWalletButtonClick, + onAddExistingWalletButtonClick = onAddExistingWalletButtonClick, + onScanButtonClick = onScanButtonClick, + ), + ) +} + +@Deprecated("Use StoriesContainer from core/ui") +@Suppress("LongMethod") +@Composable +private fun StoriesScreenContentV2(config: StoriesScreenContentV2Config, modifier: Modifier = Modifier) { + var isPressed by remember { mutableStateOf(value = false) } + + val isPaused = isPressed || config.isScanInProgress + val currentStoryDuration = config.currentStory.duration + + Box( + modifier = modifier.background(Color(0xFF010101)), + ) { + Row( + modifier = Modifier.fillMaxSize(), + ) { + Box( + Modifier + .weight(1f) + .fillMaxHeight() + .pointerInput(Unit) { + detectTapGestures( + onPress = { + val pressStartTime = System.currentTimeMillis() + isPressed = true + this.tryAwaitRelease() + val pressEndTime = System.currentTimeMillis() + val totalPressTime = pressEndTime - pressStartTime + if (totalPressTime < 200) config.onGoToPreviousStory() + isPressed = false + }, + ) + }, + ) + Box( + Modifier + .weight(1f) + .fillMaxHeight() + .pointerInput(Unit) { + detectTapGestures( + onPress = { + val pressStartTime = System.currentTimeMillis() + isPressed = true + this.tryAwaitRelease() + val pressEndTime = System.currentTimeMillis() + val totalPressTime = pressEndTime - pressStartTime + if (totalPressTime < 200) config.onGoToNextStory() + isPressed = false + }, + ) + }, + ) + } + + Column( + modifier = Modifier + .statusBarsPadding() + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + StoriesProgressBar( + steps = config.storiesSize, + currentStep = config.currentStoryIndex, + stepDuration = currentStoryDuration, + paused = isPaused, + onStepFinish = config.onGoToNextStory, + ) + Image( + painter = painterResource(id = R.drawable.ic_tangem_logo), + contentDescription = null, + contentScale = ContentScale.FillHeight, + modifier = Modifier + .padding( + start = TangemTheme.dimens.spacing16, + top = TangemTheme.dimens.spacing16, + ) + .height(TangemTheme.dimens.size18) + .align(Alignment.Start), + ) + when (config.currentStory) { + Stories.TangemIntro -> FirstStoriesContent( + isPaused = isPaused, + duration = currentStoryDuration, + ) + Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet() + Stories.UltraSecureBackup -> StoriesUltraSecureBackup( + isPaused = isPaused, + stepDuration = currentStoryDuration, + ) + Stories.Currencies -> StoriesCurrencies(isPaused, currentStoryDuration) + Stories.Web3 -> StoriesWeb3(isPaused, currentStoryDuration) + Stories.WalletForEveryone -> StoriesWalletForEveryone(currentStoryDuration) + } + } + Column( + modifier = Modifier + .navigationBarsPadding() + .padding(bottom = TangemTheme.dimens.spacing16) + .padding(horizontal = TangemTheme.dimens.spacing16) + .align(Alignment.BottomCenter) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + ) { + HomeButtonsV2( + modifier = Modifier.fillMaxWidth(), + btnScanStateInProgress = config.isScanInProgress, + onScanButtonClick = config.onScanButtonClick, + onCreateNewWalletButtonClick = config.onCreateNewWalletButtonClick, + onAddExistingWalletButtonClick = config.onAddExistingWalletButtonClick, + ) + } + } +} + +private data class StoriesScreenContentV2Config( + val storiesSize: Int, + val currentStoryIndex: Int, + val currentStory: Stories, + val isScanInProgress: Boolean, + val onGoToPreviousStory: () -> Unit = {}, + val onGoToNextStory: () -> Unit = {}, + val onCreateNewWalletButtonClick: () -> Unit = {}, + val onAddExistingWalletButtonClick: () -> Unit = {}, + val onScanButtonClick: () -> Unit = {}, +) + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun StoriesScreenContentV2Preview( + @PreviewParameter(StoriesScreenContentV2ConfigProvider::class) config: StoriesScreenContentV2Config, +) { + TangemThemePreview { + StoriesScreenContentV2(config = config) + } +} + +private class StoriesScreenContentV2ConfigProvider : CollectionPreviewParameterProvider( + collection = listOf( + StoriesScreenContentV2Config( + storiesSize = 6, + currentStoryIndex = 0, + currentStory = Stories.TangemIntro, + isScanInProgress = true, + ), + StoriesScreenContentV2Config( + storiesSize = 6, + currentStoryIndex = 1, + currentStory = Stories.RevolutionaryWallet, + isScanInProgress = false, + ), + StoriesScreenContentV2Config( + storiesSize = 6, + currentStoryIndex = 2, + currentStory = Stories.UltraSecureBackup, + isScanInProgress = false, + ), + StoriesScreenContentV2Config( + storiesSize = 6, + currentStoryIndex = 3, + currentStory = Stories.Currencies, + isScanInProgress = false, + ), + StoriesScreenContentV2Config( + storiesSize = 6, + currentStoryIndex = 4, + currentStory = Stories.Web3, + isScanInProgress = false, + ), + StoriesScreenContentV2Config( + storiesSize = 6, + currentStoryIndex = 5, + currentStory = Stories.WalletForEveryone, + isScanInProgress = false, + ), + ), +) +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtonsV2.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtonsV2.kt new file mode 100644 index 0000000000..6b620da6dc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtonsV2.kt @@ -0,0 +1,124 @@ +package com.tangem.tap.features.home.compose.views + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.TestTags +import com.tangem.wallet.R + +@Composable +internal fun HomeButtonsV2( + btnScanStateInProgress: Boolean, + onScanButtonClick: () -> Unit, + onCreateNewWalletButtonClick: () -> Unit, + onAddExistingWalletButtonClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + CreateNewWalletButton( + modifier = Modifier + .fillMaxWidth() + .testTag(TestTags.STORIES_SCREEN_CREATE_NEW_WALLET_BUTTON), + onClick = onCreateNewWalletButtonClick, + ) + AddExistingWalletButton( + modifier = Modifier + .fillMaxWidth() + .testTag(TestTags.STORIES_SCREEN_ADD_EXISTING_WALLET_BUTTON), + onClick = onAddExistingWalletButtonClick, + ) + ScanCardButton( + modifier = Modifier + .fillMaxWidth() + .testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON), + showProgress = btnScanStateInProgress, + onClick = onScanButtonClick, + ) + } +} + +@Composable +private fun CreateNewWalletButton(onClick: () -> Unit, modifier: Modifier = Modifier) { + StoriesButton( + modifier = modifier, + text = stringResourceSafe(id = R.string.home_button_create_new_wallet), + useDarkerColors = false, + onClick = onClick, + ) +} + +@Composable +private fun AddExistingWalletButton(onClick: () -> Unit, modifier: Modifier = Modifier) { + StoriesButton( + modifier = modifier, + text = stringResourceSafe(id = R.string.home_button_add_existing_wallet), + useDarkerColors = true, + onClick = onClick, + ) +} + +@Composable +private fun ScanCardButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { + StoriesButton( + modifier = modifier, + text = stringResourceSafe(id = R.string.home_button_scan), + useDarkerColors = true, + icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24), + onClick = onClick, + showProgress = showProgress, + ) +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun HomeButtonsV2Preview(@PreviewParameter(HomeButtonsV2ParameterProvider::class) state: HomeButtonsV2State) { + TangemThemePreview { + Box( + modifier = Modifier.background(Color.Black), + ) { + HomeButtonsV2( + btnScanStateInProgress = state.btnScanStateInProgress, + onCreateNewWalletButtonClick = {}, + onAddExistingWalletButtonClick = {}, + onScanButtonClick = {}, + modifier = Modifier.padding(all = TangemTheme.dimens.spacing16), + ) + } + } +} + +private class HomeButtonsV2ParameterProvider : CollectionPreviewParameterProvider( + collection = listOf( + HomeButtonsV2State( + btnScanStateInProgress = false, + ), + HomeButtonsV2State( + btnScanStateInProgress = true, + ), + ), +) + +private data class HomeButtonsV2State( + val btnScanStateInProgress: Boolean, +) +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt index b1d45779ba..d36ea27e67 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt @@ -7,6 +7,7 @@ import org.rekotlin.StateType // todo refactor [REDACTED_TASK_KEY] data class HomeState( val scanInProgress: Boolean = false, + val isV2StoriesEnabled: Boolean = false, val stories: ImmutableList = getRestrictedStories().toImmutableList(), ) : StateType { diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 27c5144b7f..92d6363206 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -383,6 +383,8 @@ Unlimited Order Tangem Scan Tangem + Create New Wallet + Add Existing Wallet to %s On %s network This information was generated with AI.\nTap here, if you find any errors. diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/TestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/TestTags.kt index d7de6de18e..feca4f686c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/TestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/TestTags.kt @@ -4,6 +4,8 @@ object TestTags { const val STORIES_SCREEN = "STORIES_SCREEN_CONTAINER" const val STORIES_SCREEN_SCAN_BUTTON = "STORIES_SCREEN_SCAN_BUTTON" const val STORIES_SCREEN_ORDER_BUTTON = "STORIES_SCREEN_ORDER_BUTTON" + const val STORIES_SCREEN_CREATE_NEW_WALLET_BUTTON = "STORIES_SCREEN_CREATE_NEW_WALLET_BUTTON" + const val STORIES_SCREEN_ADD_EXISTING_WALLET_BUTTON = "STORIES_SCREEN_ADD_EXISTING_WALLET_BUTTON" const val MAIN_SCREEN = "MAIN_SCREEN_CONTAINER" const val MAIN_SCREEN_MORE_BUTTON = "MAIN_SCREEN_MORE_BUTTON"