diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 13d09bd76f..7185ff029d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,7 +138,9 @@ android:name="com.tangem.tap.features.sprinklr.ui.SprinklrActivity" android:theme="@style/AppTheme" /> - + { private var homeState: MutableState = mutableStateOf(store.state.homeState) - private val learn2earnViewModel by viewModels() + private val learn2earnViewModel by activityViewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) store.dispatch(HomeAction.OnCreate) store.dispatch(HomeAction.Init) - if (learn2earnViewModel.isNeedToShowViewOnStoriesScreen()) { + if (learn2earnViewModel.uiState.storyScreenState.isVisible) { store.dispatch(HomeAction.InsertStory(position = 0, Stories.OneInchPromo)) // re init homeState after inserting learn2earn story homeState = mutableStateOf(store.state.homeState) @@ -90,10 +90,8 @@ class HomeFragment : Fragment(), StoreSubscriber { @Composable private fun ScreenContent() { StoriesScreen( - homeState, - onLearn2earnClick = { - learn2earnViewModel.onStoriesClick() - }, + homeState = homeState, + onLearn2earnClick = learn2earnViewModel.uiState.storyScreenState.onClick, onScanButtonClick = { Analytics.send(IntroductionProcess.ButtonScanCard()) store.dispatch(HomeAction.ReadCard()) diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index 46ef676001..fa3e832436 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.learn2earn.presentation.ui.StoriesScreen +import com.tangem.feature.learn2earn.presentation.ui.Learn2earnStoriesScreen import com.tangem.tap.features.home.compose.content.* import com.tangem.tap.features.home.compose.views.HomeButtons import com.tangem.tap.features.home.compose.views.StoriesProgressBar @@ -54,8 +54,11 @@ fun StoriesScreen( currentStory = state.stories[max(0, currentStep() - 1)] } val goToNextScreen = { - currentStory = - if (currentStep() < state.stories.lastIndex) state.stories[currentStep() + 1] else state.firstStory + currentStory = if (currentStep() < state.stories.lastIndex) { + state.stories[currentStep() + 1] + } else { + state.firstStory + } } val isPressed = remember { mutableStateOf(false) } @@ -153,7 +156,7 @@ fun StoriesScreen( colorFilter = if (currentStory.isDarkBackground) null else ColorFilter.tint(Color.Black), ) when (currentStory) { - Stories.OneInchPromo -> StoriesScreen(onLearn2earnClick) + Stories.OneInchPromo -> Learn2earnStoriesScreen(onLearn2earnClick) Stories.TangemIntro -> FirstStoriesContent(isPaused, currentStory.duration) { hideContent.value = it } Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet(currentStory.duration) Stories.UltraSecureBackup -> StoriesUltraSecureBackup(isPaused, currentStory.duration) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 47d07b91b0..6540470882 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -8,7 +8,9 @@ import android.view.View import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle @@ -22,12 +24,16 @@ import coil.size.Scale import com.badoo.mvicore.modelWatcher import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.setStatusBarColor +import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.feature.learn2earn.presentation.Learn2earnViewModel +import com.tangem.feature.learn2earn.presentation.ui.Learn2earnMainPageScreen import com.tangem.feature.swap.api.SwapFeatureToggleManager import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.events.Portfolio +import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.show import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.global.GlobalAction @@ -73,6 +79,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber() private val viewModel by viewModels() private val totalBalanceWatcher = modelWatcher { @@ -102,6 +109,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber = if (credentials == null) { - mapOf() - } else { - mapOf("Authorization" to "Basic $credentials") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt index 6ccf6b29b1..6e9eabd1f1 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt @@ -1,10 +1,7 @@ package com.tangem.tap.network.auth.di -import com.tangem.datasource.config.ConfigManager import com.tangem.lib.auth.AuthProvider -import com.tangem.lib.auth.BasicAuthProvider import com.tangem.tap.network.auth.AuthProviderImpl -import com.tangem.tap.network.auth.TangemBasicAuthProvider import com.tangem.tap.proxy.AppStateHolder import dagger.Module import dagger.Provides @@ -21,10 +18,4 @@ class AuthModule { fun provideAuthProvider(appStateHolder: AppStateHolder): AuthProvider { return AuthProviderImpl(appStateHolder) } - - @Provides - @Singleton - fun provideTangemBasicAuthProvider(configManager: ConfigManager): BasicAuthProvider { - return TangemBasicAuthProvider(configManager.config.tangemComAuthorization) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index 1189c0895d..371f1e21bf 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -1,6 +1,8 @@ package com.tangem.tap.proxy.di +import androidx.compose.ui.text.intl.Locale import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager @@ -48,4 +50,20 @@ class ProxyModule { appStateHolder = appStateHolder, ) } + + // regions FeatureConsumers + @Provides + @Singleton + fun provideLear2earnDependencies(appStateHolder: AppStateHolder): Learn2earnDependencyProvider { + return object : Learn2earnDependencyProvider { + override fun getUserCountryCodeProvider(): () -> String = { + appStateHolder.mainStore?.state?.globalState?.userCountryCode ?: Locale.current.language + } + + override fun getWebViewAuthCredentialsProvider(): () -> String? = { + appStateHolder.mainStore?.state?.globalState?.configManager?.config?.tangemComAuthorization + } + } + } + // endregion FeatureConsumers } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_wallet_trade.xml b/app/src/main/res/layout/dialog_wallet_trade.xml index b2e6a5e45b..dea867e05a 100644 --- a/app/src/main/res/layout/dialog_wallet_trade.xml +++ b/app/src/main/res/layout/dialog_wallet_trade.xml @@ -23,7 +23,7 @@ android:focusable="true" android:gravity="center_vertical" android:padding="16dp" - android:text="@string/wallet_button_buy" + android:text="@string/common_buy" android:textColor="@color/darkGray3" android:textSize="14sp" android:textStyle="bold" @@ -38,7 +38,7 @@ android:focusable="true" android:gravity="center_vertical" android:padding="16dp" - android:text="@string/wallet_button_sell" + android:text="@string/common_sell" android:textColor="@color/darkGray3" android:textSize="14sp" android:textStyle="bold" diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml index fe0eebf2a3..8ebe4b5488 100644 --- a/app/src/main/res/layout/fragment_send.xml +++ b/app/src/main/res/layout/fragment_send.xml @@ -22,7 +22,7 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:navigationIcon="@drawable/ic_baseline_arrow_back_24" - app:title="@string/send_title" /> + app:title="@string/common_send" /> @@ -115,7 +115,7 @@ style="@style/TapPrimaryIconButton" android:layout_width="match_parent" android:fontFamily="@font/saira_semi_condensed_regular" - android:text="@string/send_title" + android:text="@string/common_send" app:icon="@drawable/ic_arrow_right" /> - + android:visibility="gone" /> @@ -51,7 +51,7 @@ style="@style/TapPrimaryIconButton" android:layout_width="0dp" android:layout_weight="1" - android:text="@string/wallet_button_send" + android:text="@string/common_send" app:icon="@drawable/ic_send" /> diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/PromotionApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/PromotionApi.kt index dce9e044b7..d71061c21a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/PromotionApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/PromotionApi.kt @@ -24,6 +24,6 @@ interface PromotionApi { @POST("promotion/validate") suspend fun validate(@Body request: ValidateRequestBody): ValidateResponse - @GET("promotion/award") + @POST("promotion/award") suspend fun requestAward(@Body request: AwardRequestBody): AwardResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/AbstractPromotionResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/AbstractPromotionResponse.kt index 064dc71a96..edfc8e990d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/AbstractPromotionResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/AbstractPromotionResponse.kt @@ -13,6 +13,6 @@ abstract class AbstractPromotionResponse { data class Error( @Json(name = "code") val code: Int, - @Json(name = "description") val description: String, + @Json(name = "message") val message: String, ) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/AwardRequestBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/AwardRequestBody.kt index 4ce66c7d76..c8ac7faed1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/AwardRequestBody.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/AwardRequestBody.kt @@ -7,5 +7,6 @@ import com.squareup.moshi.Json */ data class AwardRequestBody( @Json(name = "walletId") val walletId: String, + @Json(name = "address") val address: String, @Json(name = "programName") val programName: String, ) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromotionInfoResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromotionInfoResponse.kt index 2caaaacf14..ed7089f9e4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromotionInfoResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromotionInfoResponse.kt @@ -7,9 +7,9 @@ import com.squareup.moshi.Json */ data class PromotionInfoResponse( @Json(name = "status") val status: Status?, - @Json(name = "awardForNewCard") val awardForNewCard: Float?, - @Json(name = "awardForOldCard") val awardForOldCard: Float?, - @Json(name = "awardPaymentToken") val awardPaymentToken: TokenInfo?, + @Json(name = "awardForNewCard") val awardForNewCard: Double?, + @Json(name = "awardForOldCard") val awardForOldCard: Double?, + @Json(name = "awardPaymentToken") val awardPaymentToken: TokenData?, @Json(name = "error") override val error: Error? = null, ) : AbstractPromotionResponse() { @@ -24,18 +24,13 @@ data class PromotionInfoResponse( FINISHED("finished"), } - data class TokenInfo( + data class TokenData( @Json(name = "id") val id: String, @Json(name = "name") val name: String, @Json(name = "symbol") val symbol: String, @Json(name = "active") val active: Boolean, - @Json(name = "networks") val networks: List, - ) { - data class Network( - @Json(name = "networkId") val networkId: String, - @Json(name = "exchangeable") val exchangeable: Boolean, - @Json(name = "contractAddress") val contractAddress: String, - @Json(name = "decimalCount") val decimalCount: Int, - ) - } + @Json(name = "networkId") val networkId: String, + @Json(name = "contractAddress") val contractAddress: String, + @Json(name = "decimalCount") val decimalCount: Int, + ) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 905d1373d9..c8ae82faa9 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -8,6 +8,7 @@ import com.tangem.datasource.utils.RequestHeader.* import com.tangem.datasource.utils.addHeaders import com.tangem.datasource.utils.allowLogging import com.tangem.lib.auth.AuthProvider +import com.tangem.lib.auth.BuildConfig import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -77,7 +78,7 @@ class NetworkModule { private fun createBasePromotionRetrofit(okHttpClient: OkHttpClient, moshi: Moshi): PromotionApi { return Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create(moshi)) - .baseUrl(PROMOTION_BASE_URL) + .baseUrl(if (BuildConfig.DEBUG) DEV_TANGEM_TECH_BASE_URL else PROD_TANGEM_TECH_BASE_URL) .client(okHttpClient) .build() .create(PromotionApi::class.java) @@ -87,8 +88,6 @@ class NetworkModule { const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" - const val PROMOTION_BASE_URL = "https://tangem.com/v1/" - const val PAYMENTOLOGY_BASE_URL: String = "https://paymentologygate.oa.r.appspot.com/" const val API_ONE_INCH_TIMEOUT_MS = 5000L } diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt index dffc98c05a..4fd16a0d1d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt @@ -13,7 +13,7 @@ internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeade val request = chain.request().newBuilder().apply { requestHeaders .flatMap(RequestHeader::values) - .forEach { addHeader(it.first, it.second) } + .forEach { addHeader(it.first, it.second.invoke()) } }.build() chain.proceed(request) diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt index 66d6c0f90b..5e5067746f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt @@ -7,15 +7,15 @@ import com.tangem.lib.auth.AuthProvider * * @param pairs header name and header value pairs */ -sealed class RequestHeader(vararg pairs: Pair) { +sealed class RequestHeader(vararg pairs: Pair String>) { /** Header list */ - val values: List> = pairs.toList() + val values: List String>> = pairs.toList() - object CacheControlHeader : RequestHeader("Cache-Control" to "max-age=600") + object CacheControlHeader : RequestHeader("Cache-Control" to { "max-age=600" }) class AuthenticationHeader(authProvider: AuthProvider) : RequestHeader( - "card_public_key" to authProvider.getCardPublicKey(), - "card_id" to authProvider.getCardId(), + "card_id" to { authProvider.getCardId() }, + "card_public_key" to { authProvider.getCardPublicKey() }, ) } \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index f2b6457174..6c3dddbebf 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -39,7 +39,7 @@ Восстановление кода доступа Смена кода доступа Код доступа будет изменен только на данной карте - Сброс к заводским настройкам + Заводские настройки Тип безопасности Настройки карты Tangem Bot @@ -66,13 +66,17 @@ Включить Включено Обменять + Посмотреть историю транзакций + Обозреватель Нравится + Основная сеть Нет Нет данных - Ок + OK Основная карта Получить Отклонить + Перезагрузить Сохранить изменения Искать Секретная фраза @@ -156,11 +160,22 @@ Приложите карту Внутренняя ошибка: не удается найти менеджер кошельков Вы обновили данные биометрии, отсканируйте свою карту для входа + Вы завершили обучение и можете получить свои токены 1inch + Получите бонус + + Пройдите обучение и получите %d токен 1inch на свой кошелек + Пройдите обучение и получите %d токена 1inch на свой кошелек + Пройдите обучение и получите %d токена 1inch на свой кошелек + Пройдите обучение и получите %d токенов 1inch на свой кошелек + + Бонус за обучение Управление токенами Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру Бэкап кошелька не был произведен Баланс В сумме учтены не все монеты + Токены 1inch будут зачислены на адрес вашего кошелька в течение 2 дней + По вашему промокоду не было покупки кошелька, а значит вы не можете получить бонус. Купите кошелек Tangem, отсканируйте его в приложении и получите бонус. Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту Отсканируйте карту Вам необходимо установить единый код доступа для защиты всех ваших карт @@ -184,11 +199,8 @@ Запросить Перейти к моему кошельку Завершение бэкапа - Верифицировать (Utorg) - Обновить Установить Код доступа Получить криптовалюту - Зарегистрироваться Сканировать основную карту Пропустить Как это работает? @@ -203,19 +215,9 @@ В этом случае вам будет необходимо начать процесс заново. Вы хотите выйти из процесса активации? Подготовка - Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Хотите сбросить его и использовать карту для бэкапа? - Подтвердите свою личность - Верификация клиента Код доступа Подключиться Резервная копия - Приложите SaltPay карту - Для начала процесса бэкапа вам необходимо добавить Tangem карту - Завершите процесс бэкапа создав код доступа - Приложите Tangem карту - Бэкап карта не добавлена - Бэкап карта создана - Приготовьте SaltPay карту Читать про секретную фразу Запишите эти 12 слов в порядке, указанном ниже, и сохраните их в надежном месте. Ваша секретная фраза @@ -231,13 +233,8 @@ Итак, проверим Для начала работы просто запросите начисление wxDAI на свой кошелек Это займет несколько секунд - Более подробная информация отправлена на ваш адрес электронной почты. - Для начала работы с картой вам необходимо завершить процесс подтверждения личности - Пожалуйста дождитесь завершения процесса подтверждения личности. Вы будете уведомлены через e-mail. Обычно это занимает не более часа. Вы можете закрыть приложение и вернуться позже. Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. Вы можете добавить еще одну карту или завершить процесс резервного копирования - Установите 4-х значный код.\nОн будет использован для платежей. - Подключите вашу карту к децентрализованной платежной системе Подготовьте резервную карту с номером %s Подготовьте основную карту Подготовьте основную карту с номером %s @@ -248,13 +245,9 @@ Резервная карта #%d Запросить %s Запрашивается - Что-то пошло не так - Подтвердите свою личность - Подтверждение личности в процессе Нет резервных карт Добавлена ​​одна резервная карта Код доступа - Подключите свою карту Подготовьте свою карту Добавлены две резервные карты Пополните кошелек на любую сумму, чтобы начать пользоваться картой @@ -275,6 +268,9 @@ Группировка По балансу Сортировка токенов + Разгруппировать + %1$s %2$s адрес в сети %3$s + %1$s (%2$s) в сети %3$s Участвовать Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. @@ -306,12 +302,6 @@ Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек. У вас есть карта банка другой страны или платежной системы UnionPay? Карты банков РФ в данный момент не принимаются - Приложите карту с логотипом Visa - Внимание - Пожалуйста обратитесь в службу поддержки - Недостаточно средств для активации - Данный Код доступа может быть легко взломан - Ввод одинаковых цифр является не безопасным Войдите в приложение и следите за своим балансом без сканирования карты Доступ в приложение Использовать биометрию @@ -344,7 +334,6 @@ Приоритетная Максимальная сумма Сетевая комиссия - Отправить Отправка %s Всего %1$s и %2$s будет отправлено @@ -361,6 +350,7 @@ Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. Революционный аппаратный кошелек + До **трех карт** с одним кошельком До трех карт с одним кошельком @@ -369,6 +359,9 @@ Тысячи криптовалют Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. Кошелек для каждого + Пройдите обучение и получите возможность купить кошелек Tangem со скидкой и токены 1inch в качестве бонуса + Пройти обучение + Получите свой бонус Занимайте Покупайте Обменивайте @@ -421,6 +414,7 @@ Скрыть токен %1$s — это токен в сети %2$s. Чтобы отправить транзакцию %3$s, необходимо пополнить баланс %4$s (%5$s) для оплаты комиссии сети. Пожалуйста, дождитесь завершения транзакции %s, чтобы иметь возможность отправить средства + %1$s токен в сети %%image%% %2$s Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. Невозможно скрыть %s Нет цены @@ -435,6 +429,7 @@ Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе. от: %s + на: %s В процессе… Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. @@ -465,9 +460,6 @@ Транзакция подтверждается… Подтвержденный баланс Действия - Купить - Продать - Отправить Вы хотите купить или продать криптовалюту? Запрос на подпись сообщения.\n\n%s Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index 8b25c3a89d..59649e3ed6 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -171,11 +171,8 @@ 獲取 繼續至我的錢包 完成備份過程 - 通過Utorg驗證 - 刷新 設置PIN碼 接收貨幣 - 註冊 掃描主卡 暫時略過 它是如何運作的? @@ -190,18 +187,9 @@ 這此情況,您必須要重新開始 您想要離開啟用程序嗎? 開始 - 驗證身分 - KYC PIN 碼 連接 創建備份 - 點擊 SaltPay 卡 - 要開始備份過程,您必須添加 Tangem 卡片作為備份 - 通過創建訪問密碼完成備份 - 點擊 Tangem 卡片 - 沒有備份卡片 - 備份卡片已準備完成 - 準備SaltPay卡 閱讀更多關於助記詞的訊息 按照下面給出的順序寫下這 12 個單詞,並將它們存放在安全秘密的地方。 您的助記詞 @@ -217,13 +205,8 @@ 那麼,讓我們檢查一下 要開始,只需將 wxDAI 獲取到您的錢包 這會花上幾秒 - 請查看Email已獲得更多指示 - 要開始使用您的卡,您必須通過 KYC驗證 - 請等待驗證完成,您將收到電子郵件通知。通常最多需要 1 小時。您可以關閉該應用程序,稍後再回來 要開始備份過程,最多可添加兩張備份卡。 您可以再添加一張卡或完成備份過程 - 設置一個 4 位密碼。 \n它將用在付款時使用。 - 將您的卡片連接到去中心化支付系統 準備編號為 %s 的備份卡 準備主卡 準備編號為 %s 的主卡 @@ -234,13 +217,9 @@ 備份卡 #%d 獲取%s 獲取中 - 有東西出錯了 - 確認你的身分 - KYC 認證中 沒有備用卡 添加了一張備用卡 PIN 碼 - 連接你的錢包 準備好你的卡片 添加了兩張備用卡 要開始使用,只需為錢包充值任意金額 @@ -286,12 +265,6 @@ 恢復原廠設置將從所選卡中完全刪除錢包並將其從應用程序中刪除。您將無法恢復當前錢包 您有其他國家的銀行卡或銀聯卡嗎? 目前不接受俄羅斯銀行卡 - 輕觸帶有 visa 標誌的卡片 - 注意 - 請聯繫客服 - 沒有激活資金 - 這樣的 PIN 很容易被暴力破解 - 四個相同的數字不安全 登錄應用程序並在不掃描卡片的情況下檢查您的資產 訪問應用程序 允許使用生物辨識 @@ -339,6 +312,7 @@ Solana 網絡每 2 天收取 %1$s 的費用。無法付此費用的帳戶將從網絡中清除。向您的帳戶存入超過 %2$s 即可免費使用 安全地存儲您的加密貨幣,同時將私鑰保存在您的卡中 創新式的硬體錢包 + 最多 **3張實體卡片** 到一個錢包 最多 3張實體卡片 到一個錢包 @@ -430,9 +404,6 @@ 交易進行中 檢視餘額 動作 - 購買 - 銷售 - 發送 您想要購買或賣出交易貨幣? 請求籤署消息。%s Dapp %1$s,請求\n簽署 BNB 交易。\n%2$s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index aa3dd033f5..7f18cfaf1b 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -64,13 +64,17 @@ Enable Enabled Exchange + Explore transaction history + Explorer Like + Main network No No data OK Primary Card Receive Reject + Reload Save changes Search Seed phrase @@ -154,13 +158,20 @@ Tap the card Internal error: wallet manager not found You have updated biometrics, scan your card to enter - You have completed the training and can get your 1inch tokens - Get a bonus + You have completed the training and can get your 1inch tokens + Get a bonus + + Complete the training and get %d 1inch token on your wallet + Complete the training and get %d 1inch tokens on your wallet + + Learn & Earn Manage tokens To protect your assets, we advise you to carry out this procedure Your wallet has not been backed up Total balance The amount does not include some of your funds + 1inch tokens will be credited to your wallet address within 2 days + There was no purchase of a wallet using your promo code, which means you cannot receive a bonus. Buy Tangem wallet, scan it in the app, and get the bonus. To access all the networks you need to scan the card Scan your card You have to set up a single access code to protect all your wallets @@ -184,11 +195,8 @@ Claim Continue to my wallet Finalize the backup - Verify via Utorg - Refresh Set PIN code Receive crypto - Register Scan primary card Skip for later How does it work? @@ -204,18 +212,9 @@ Do you want to exit the activation process? Getting started Another wallet has already been created on the card you\'re trying to add. Do you want to reset it and use the card for a new wallet? - Verify your identity - KYC Pin code Connect Creating a backup - Tap the SaltPay card - To start the backup process you have to add the Tangem card as your backup - Finalize the backup process by creating an access code - Tap the Tangem card - No backup card - Backup card ready - Prepare the SaltPay card Read more about seed phrases Write these 12 words down in the order given below and store them in a safe and secret place. Your secret phrase @@ -231,13 +230,8 @@ So, let’s check To get started, simply claim wxDAI to your wallet It will take a few seconds - Please check your email for further instructions - To start using your card you have to pass the KYC process - Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. To start the backup process add up to two backup cards. You can add one more card or finalize the backup process - Set up a 4-digit code.\nIt will be used for payments. - Connect your card to the decentralized payment system Prepare the backup card with number %s Prepare the primary card Prepare the primary card with number %s @@ -248,13 +242,9 @@ Backup card #%d Claim %s Claiming - Something went wrong - Verify your identity - KYC is in progress No backup cards One backup card added PIN Code - Connect your card Prepare your card Two backup cards added To get started, simply top up the wallet with any amount @@ -275,6 +265,10 @@ Group By balance Organize tokens + Ungroup + %1$s %2$s address on %3$s network + %1$s (%2$s) on %3$s network + Send only %s to this address. Sending any other currency will result in its irreversible loss. Participate Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Error code: %s. Please try again later. @@ -304,12 +298,6 @@ Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. Do you have a bank card of another country or a UnionPay card? Russian bank cards are not accepted at the moment - Tap the card with the visa logo - Attention - Please contact support - No funds for activation - Such a PIN can be brute-forced easily - Four identical digits isn\'t safe Log into the app and check your balance without scanning the card Access the app Allow to use biometrics @@ -342,7 +330,6 @@ Priority Maximum amount Network fee - Send Sending %s Total %1$s and %2$s will be sent @@ -354,13 +341,12 @@ I have a promo code… Tangem Wallet Other payment methods - Due to the high volume of orders we are receiving, Shipping and Local Delivery orders may be delayed. - Pre-order now Due to the high volume of orders we are receiving shipping may be delayed up to 5 weeks depending on your location Total Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. Store your crypto assets secure while keeping private keys contained in your card Revolutionary Hardware Wallet + Up to **3 physical cards** to one wallet Up to 3 physical cards to one wallet @@ -422,6 +408,7 @@ Hide token %1$s is a token in the %2$s network. To make a %3$s transaction you need to deposit some %4$s (%5$s) to cover the network fee. Please wait for %s transaction to complete to be able to send funds + %1$s token in %%image%% %2$s network The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list. Unable to hide %s No rate @@ -465,9 +452,6 @@ Transaction is in progress… Verified Balance Actions - Buy - Sell - Send Do you want to buy or sell crypto? Requesting to sign a message.\n\n%s Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt index a27dcb58c4..92df3daeb9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt @@ -1,9 +1,11 @@ package com.tangem.core.ui.extensions +import androidx.annotation.PluralsRes import androidx.annotation.StringRes import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource /** @@ -24,6 +26,8 @@ sealed interface TextReference { */ data class Res(@StringRes val id: Int, val formatArgs: WrappedList = WrappedList(emptyList())) : TextReference + data class PluralRes(@PluralsRes val id: Int, val count: Int, val formatArgs: WrappedList) : TextReference + /** * Text string * @@ -38,6 +42,7 @@ sealed interface TextReference { fun TextReference.resolveReference(): String { return when (this) { is TextReference.Res -> stringResource(id, *formatArgs.toTypedArray()) + is TextReference.PluralRes -> pluralStringResource(id, count, *formatArgs.toTypedArray()) is TextReference.Str -> value } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt index 37f01870cc..f22a14aa64 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt @@ -36,6 +36,7 @@ private class DomainGlobalReducer : ReStoreReducer { return when (action) { is DomainGlobalAction.SaveScanNoteResponse -> { val card = action.scanResponse.card + // TODO: AuthHeaders: try to remove it, because now we can use headers with dynamic values state.networkServices.tangemTechService.addAuthenticationHeader( RequestHeader.AuthenticationHeader( object : AuthProvider { diff --git a/features/learn2earn/impl/build.gradle.kts b/features/learn2earn/impl/build.gradle.kts index bd20fe9bf6..b1c4ef9a5a 100644 --- a/features/learn2earn/impl/build.gradle.kts +++ b/features/learn2earn/impl/build.gradle.kts @@ -17,31 +17,28 @@ dependencies { implementation(project(":core:res")) implementation(project(":data:source:preferences")) implementation(project(":libs:auth")) + implementation(project(":libs:crypto")) implementation(deps.material) /** AndroidX */ implementation(deps.androidx.core.ktx) implementation(deps.androidx.appCompat) + implementation(deps.androidx.fragment.ktx) implementation(deps.androidx.activity.compose) - implementation(deps.androidx.constraintLayout) implementation(deps.androidx.browser) /** Compose */ implementation(deps.compose.material) + implementation(deps.compose.material3) implementation(deps.compose.foundation) implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) - /** Tangem libraries */ - // TODO: 1inch: delete if later it doesn't needed - // implementation(deps.tangem.card.core) - // implementation(deps.tangem.card.android) { - // exclude(module = "joda-time") - // } + /** Preferences */ + implementation(deps.krateSharedPref) /** Network */ - implementation(deps.krateSharedPref) implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.retrofit) diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultLearn2earnRepository.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultLearn2earnRepository.kt index 3275265a73..9b4f0ffd8c 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultLearn2earnRepository.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultLearn2earnRepository.kt @@ -5,7 +5,7 @@ import com.tangem.datasource.api.promotion.PromotionApi import com.tangem.datasource.api.promotion.models.* import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage import com.tangem.feature.learn2earn.data.api.Learn2earnRepository -import com.tangem.feature.learn2earn.data.api.UsedCardsPreferenceStorage +import com.tangem.feature.learn2earn.data.models.PromoUserData import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import kotlinx.coroutines.withContext import timber.log.Timber @@ -15,38 +15,32 @@ import timber.log.Timber */ internal class DefaultLearn2earnRepository( private val preferencesStorage: Learn2earnPreferenceStorage, - private val usedCardsPreferenceStorage: UsedCardsPreferenceStorage, - private val moshi: Moshi, private val api: PromotionApi, private val dispatchers: AppCoroutineDispatcherProvider, + moshi: Moshi, ) : Learn2earnRepository { private val promotionInfoAdapter = moshi.adapter(PromotionInfoResponse::class.java) + private val userDataAdapter = moshi.adapter(PromoUserData::class.java) - override fun isHadActivatedCards(): Boolean { - return usedCardsPreferenceStorage.isHadActivatedCards() + private var userData: PromoUserData = restoreUserData() + + override fun getUserData(): PromoUserData { + return userData } - override fun getPromoCode(): String? { - return preferencesStorage.promoCode - } - - override fun savePromoCode(code: String) { - preferencesStorage.promoCode = code + override fun updateUserData(userData: PromoUserData) { + this.userData = userData + saveUserData(userData) } override fun getProgramName(): String { return PROGRAM_NAME } - override fun isAlreadyReceivedAward(): Boolean { - return preferencesStorage.alreadyReceivedAward - } - override suspend fun getPromotionInfo(): Result { return withContext(dispatchers.io) { val info = restorePromotionInfo() - if (info?.status == PromotionInfoResponse.Status.FINISHED) { Result.success(info) } else { @@ -68,9 +62,9 @@ internal class DefaultLearn2earnRepository( } } - override suspend fun requestAward(walletId: String): AwardResponse { + override suspend fun requestAward(walletId: String, address: String): AwardResponse { return withContext(dispatchers.io) { - api.requestAward(AwardRequestBody(walletId, getProgramName())) + api.requestAward(AwardRequestBody(walletId, address, getProgramName())) } } @@ -104,6 +98,30 @@ internal class DefaultLearn2earnRepository( } } + private fun restoreUserData(): PromoUserData { + return try { + preferencesStorage.userData?.let { userDataAdapter.fromJson(it) } + ?: createEmptyUserData().apply { saveUserData(this) } + } catch (ex: Exception) { + Timber.e(ex) + createEmptyUserData().apply { saveUserData(this) } + } + } + + private fun saveUserData(userData: PromoUserData) { + try { + preferencesStorage.userData = userDataAdapter.toJson(userData) + } catch (ex: Exception) { + Timber.e(ex) + } + } + + private fun createEmptyUserData(): PromoUserData = PromoUserData( + promoCode = null, + isRegisteredInPromotion = false, + isAlreadyReceivedAward = false, + ) + private companion object { const val PROGRAM_NAME: String = "1inch" } diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultPreferenceStorage.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultPreferenceStorage.kt index 1df706746e..4c3e39b9ae 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultPreferenceStorage.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultPreferenceStorage.kt @@ -3,7 +3,6 @@ package com.tangem.feature.learn2earn.data import android.content.Context import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage import hu.autsoft.krate.SimpleKrate -import hu.autsoft.krate.booleanPref import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.stringPref @@ -14,9 +13,7 @@ internal class DefaultPreferenceStorage( context: Context, ) : SimpleKrate(context = context, name = "Lear2earnPromotion"), Learn2earnPreferenceStorage { - override var promoCode: String? by stringPref().withDefault(null) - override var promotionInfo: String? by stringPref().withDefault(null) - override var alreadyReceivedAward: Boolean by booleanPref().withDefault(false) + override var userData: String? by stringPref().withDefault(null) } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultUsedCardsPreferenceStorage.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultUsedCardsPreferenceStorage.kt deleted file mode 100644 index a2f3a8cab4..0000000000 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultUsedCardsPreferenceStorage.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.feature.learn2earn.data - -import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage -import com.tangem.feature.learn2earn.data.api.UsedCardsPreferenceStorage - -/** -[REDACTED_AUTHOR] - */ -class DefaultUsedCardsPreferenceStorage( - private val usedCardsPrefStorage: UsedCardsPrefStorage, -) : UsedCardsPreferenceStorage { - - override fun isHadActivatedCards(): Boolean = usedCardsPrefStorage.hadFinishedActivation() -} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/Learn2earnPreferenceStorage.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/Learn2earnPreferenceStorage.kt index b1a251712c..8c8840bb74 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/Learn2earnPreferenceStorage.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/Learn2earnPreferenceStorage.kt @@ -5,9 +5,7 @@ package com.tangem.feature.learn2earn.data.api */ interface Learn2earnPreferenceStorage { - var promoCode: String? - var promotionInfo: String? - var alreadyReceivedAward: Boolean + var userData: String? } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/Learn2earnRepository.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/Learn2earnRepository.kt index 2a8d8811dd..06e95628d7 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/Learn2earnRepository.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/Learn2earnRepository.kt @@ -1,27 +1,24 @@ package com.tangem.feature.learn2earn.data.api import com.tangem.datasource.api.promotion.models.* +import com.tangem.feature.learn2earn.data.models.PromoUserData /** [REDACTED_AUTHOR] */ interface Learn2earnRepository { - fun isHadActivatedCards(): Boolean + fun getUserData(): PromoUserData - fun getPromoCode(): String? - - fun savePromoCode(code: String) + fun updateUserData(userData: PromoUserData) fun getProgramName(): String - fun isAlreadyReceivedAward(): Boolean - suspend fun getPromotionInfo(): Result suspend fun validate(walletId: String): ValidateResponse - suspend fun requestAward(walletId: String): AwardResponse + suspend fun requestAward(walletId: String, address: String): AwardResponse suspend fun validateCode(walletId: String, code: String?): CodeValidateResponse diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/UsedCardsPreferenceStorage.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/UsedCardsPreferenceStorage.kt deleted file mode 100644 index c7d1174496..0000000000 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/api/UsedCardsPreferenceStorage.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.feature.learn2earn.data.api - -/** -[REDACTED_AUTHOR] - * An interface that encapsulates access to deprecated PreferencesDataSource.usedCardsPrefStorage. - */ -interface UsedCardsPreferenceStorage { - fun isHadActivatedCards(): Boolean -} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/di/Learn2earnDataModule.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/di/Learn2earnDataModule.kt index b6b32a203b..98cb9f1b9c 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/di/Learn2earnDataModule.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/di/Learn2earnDataModule.kt @@ -2,16 +2,13 @@ package com.tangem.feature.learn2earn.data.di import android.content.Context import com.squareup.moshi.Moshi -import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.datasource.api.promotion.PromotionApi import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.di.PromotionOneInch import com.tangem.feature.learn2earn.data.DefaultLearn2earnRepository import com.tangem.feature.learn2earn.data.DefaultPreferenceStorage -import com.tangem.feature.learn2earn.data.DefaultUsedCardsPreferenceStorage import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage import com.tangem.feature.learn2earn.data.api.Learn2earnRepository -import com.tangem.feature.learn2earn.data.api.UsedCardsPreferenceStorage import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -30,29 +27,19 @@ class Learn2earnDataModule { return DefaultPreferenceStorage(context) } - @Provides - @Singleton - fun provideDeprecatedPreferenceDataSource( - preferenceDataSource: PreferencesDataSource, - ): UsedCardsPreferenceStorage { - return DefaultUsedCardsPreferenceStorage(preferenceDataSource.usedCardsPrefStorage) - } - @Provides @Singleton fun provideRepository( preferenceStorage: Learn2earnPreferenceStorage, - usedCardsPreferenceStorage: UsedCardsPreferenceStorage, @NetworkMoshi moshi: Moshi, @PromotionOneInch promotionApi: PromotionApi, dispatchers: AppCoroutineDispatcherProvider, ): Learn2earnRepository { return DefaultLearn2earnRepository( preferencesStorage = preferenceStorage, - usedCardsPreferenceStorage = usedCardsPreferenceStorage, - moshi = moshi, api = promotionApi, dispatchers = dispatchers, + moshi = moshi, ) } } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/models/PromoUserData.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/models/PromoUserData.kt new file mode 100644 index 0000000000..7edf1bc122 --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/models/PromoUserData.kt @@ -0,0 +1,10 @@ +package com.tangem.feature.learn2earn.data.models + +/** +[REDACTED_AUTHOR] + */ +data class PromoUserData( + val promoCode: String?, + val isRegisteredInPromotion: Boolean, + val isAlreadyReceivedAward: Boolean, +) \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/DefaultLearn2earnInteractor.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/DefaultLearn2earnInteractor.kt index e6446305d0..7da36f0d40 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/DefaultLearn2earnInteractor.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/DefaultLearn2earnInteractor.kt @@ -1,77 +1,192 @@ package com.tangem.feature.learn2earn.domain import android.net.Uri +import com.tangem.datasource.api.promotion.models.AbstractPromotionResponse import com.tangem.datasource.api.promotion.models.PromotionInfoResponse import com.tangem.feature.learn2earn.data.api.Learn2earnRepository -import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor -import com.tangem.feature.learn2earn.domain.models.CardType +import com.tangem.feature.learn2earn.data.models.PromoUserData +import com.tangem.feature.learn2earn.domain.api.* import com.tangem.feature.learn2earn.domain.models.Promotion import com.tangem.feature.learn2earn.domain.models.PromotionError -import com.tangem.feature.learn2earn.domain.models.PromotionError.Companion.toDomainError -import com.tangem.feature.learn2earn.domain.models.RedirectConsequences -import com.tangem.lib.auth.BasicAuthProvider +import com.tangem.feature.learn2earn.domain.models.toDomainError +import com.tangem.lib.crypto.DerivationManager +import com.tangem.lib.crypto.UserWalletManager +import com.tangem.lib.crypto.models.Currency /** [REDACTED_AUTHOR] */ class DefaultLearn2earnInteractor( private val repository: Learn2earnRepository, - basicAuthProvider: BasicAuthProvider, - userCountryCodeProvider: () -> String, + private val userWalletManager: UserWalletManager, + private val derivationManager: DerivationManager, + dependencyProvider: Learn2earnDependencyProvider, ) : Learn2earnInteractor { - private val webViewUriBuilder = WebViewUriBuilder( - basicAuthProvider = basicAuthProvider, - userCountryCodeProvider = userCountryCodeProvider, - promoCodeProvider = { repository.getPromoCode() }, - promoNameProvider = { repository.getProgramName() }, - ) + override var webViewResultHandler: WebViewResultHandler? = null private lateinit var promotion: Promotion + private val webViewUriBuilder: WebViewUriBuilder = WebViewUriBuilder( + authCredentialsProvider = dependencyProvider.getWebViewAuthCredentialsProvider(), + userCountryCodeProvider = dependencyProvider.getUserCountryCodeProvider(), + promoCodeProvider = { repository.getUserData().promoCode }, + ) + override suspend fun init() { initPromotionInfo() } + override fun isUserHadPromoCode(): Boolean { + return repository.getUserData().promoCode != null + } + override fun isNeedToShowViewOnStoriesScreen(): Boolean { return promotionIsActive() } - override fun getBasicAuthHeaders(): Map { - return webViewUriBuilder.getBasicAuthHeaders() + override suspend fun isNeedToShowViewOnMainScreen(): Boolean { + if (!promotionIsActive()) return false + + val response = repository.validate(userWalletManager.getWalletId()) + return response.valid == true } - override suspend fun isNeedToShowViewOnWalletScreen(walletId: String): Boolean { - val response = repository.validate(walletId) + override fun isUserRegisteredInPromotion(): Boolean { + return repository.getUserData().isRegisteredInPromotion + } - return if (response.valid == false && response.isError()) { - false + override fun getAwardAmount(): Int { + val userInfo = repository.getUserData() + val promotionInfo = promotion.getPromotionInfo() + + return if (userInfo.promoCode == null) { + promotionInfo.awardForOldCard } else { - promotionIsActive() + promotionInfo.awardForNewCard + }.toInt() + } + + @Throws(IllegalArgumentException::class) + override suspend fun requestAward(): Result { + val awardCurrency = getCurrencyForAward() + ?: return Result.failure(PromotionError.UnknownError("Currency for award is null")) + + val walletId = userWalletManager.getWalletId() + val promoCode = repository.getUserData().promoCode + + val error = if (promoCode == null) { + requestAward(walletId, awardCurrency) + } else { + requestAwardWithPromoCode(walletId, awardCurrency, promoCode) + } + + return if (error == null) { + Result.success(Unit) + } else { + val domainError = error.toDomainError() + handlePromotionError(domainError) + Result.failure(domainError) } } - override fun buildUriForStories(): Uri { - val type = if (repository.isHadActivatedCards()) { - CardType.NEW + private suspend fun requestAward(walletId: String, awardCurrency: Currency): AbstractPromotionResponse.Error? { + val validateResponse = repository.validate(walletId) + return if (validateResponse.valid == true) { + val address = getWalletAddressForAward(awardCurrency) + val awardResponse = repository.requestAward(walletId, address) + if (awardResponse.status == true) { + updateUserData { it.copy(isAlreadyReceivedAward = true) } + null + } else { + awardResponse.error + } } else { - CardType.EXISTED + validateResponse.error } - - return webViewUriBuilder.buildUriForStories(type) } - override fun buildUriForMainPage(walletId: String, cardId: String, cardPubKey: String): Uri { - return webViewUriBuilder.buildUriForMainPage(walletId, cardId, cardPubKey) + private suspend fun requestAwardWithPromoCode( + walletId: String, + awardCurrency: Currency, + promoCode: String, + ): AbstractPromotionResponse.Error? { + val codeValidateResponse = repository.validateCode(walletId, promoCode) + return if (codeValidateResponse.valid == true) { + val address = getWalletAddressForAward(awardCurrency) + val awardWithCodeResponse = repository.requestAwardByCode(walletId, address, promoCode) + if (awardWithCodeResponse.status == true) { + updateUserData { it.copy(isAlreadyReceivedAward = true) } + null + } else { + awardWithCodeResponse.error + } + } else { + codeValidateResponse.error + } } - override fun handleWebViewRedirect(uri: Uri): RedirectConsequences { + private suspend fun getWalletAddressForAward(currency: Currency): String { + val derivationPath = deriveOrAddTokens(currency) + return userWalletManager.getWalletAddress(currency.networkId, derivationPath) + } + + private suspend fun deriveOrAddTokens(currency: Currency): String { + val derivationPath = derivationManager.getDerivationPathForBlockchain(currency.networkId) + if (derivationPath.isNullOrEmpty()) error("derivationPath shouldn't be empty") + + if (!derivationManager.hasDerivation(currency.networkId, derivationPath)) { + derivationManager.deriveMissingBlockchains(currency) + } + if (!userWalletManager.isTokenAdded(currency, derivationPath)) { + userWalletManager.addToken(currency, derivationPath) + } + return derivationPath + } + + private fun handlePromotionError(error: PromotionError) { + when (error) { + is PromotionError.CodeNotFound -> { + updateUserData { it.copy(promoCode = null) } + } + is PromotionError.CardAlreadyHasAward, is PromotionError.WalletAlreadyHasAward, + is PromotionError.CodeWasAlreadyUsed, + -> { + updateUserData { it.copy(isAlreadyReceivedAward = true) } + } + else -> Unit + } + } + + override fun buildUriForNewUser(): Uri { + return webViewUriBuilder.buildUriForNewUser() + } + + override fun buildUriForOldUser(): Uri { + return webViewUriBuilder.buildUriForOldUser() + } + + override fun getBasicAuthHeaders(): ArrayList { + return HeadersConverter().convert(webViewUriBuilder.getBasicAuthHeaders()) + } + + override fun handleRedirect(uri: Uri): RedirectConsequences { if (webViewUriBuilder.isReadyForExistedCardAwardRedirect(uri)) { + updateUserData { it.copy(isRegisteredInPromotion = true) } + webViewResultHandler?.handleResult(WebViewResult.ReadyForAward) return RedirectConsequences.FINISH_SESSION } return if (webViewUriBuilder.isPromoCodeRedirect(uri)) { - webViewUriBuilder.extractPromoCode(uri)?.let { repository.savePromoCode(it) } + webViewUriBuilder.extractPromoCode(uri)?.let { code -> + updateUserData { + it.copy( + promoCode = code, + isRegisteredInPromotion = true, + ) + } + webViewResultHandler?.handleResult(WebViewResult.PromoCodeReceived) + } RedirectConsequences.NOTHING } else { RedirectConsequences.PROCEED @@ -80,9 +195,9 @@ class DefaultLearn2earnInteractor( private fun promotionIsActive(): Boolean { val isActive = when { - repository.isAlreadyReceivedAward() -> false + repository.getUserData().isAlreadyReceivedAward -> false promotion.isError() -> false - else -> promotion.getInfo().status == PromotionInfoResponse.Status.ACTIVE + else -> promotion.getPromotionInfo().status == PromotionInfoResponse.Status.ACTIVE } return isActive @@ -118,4 +233,23 @@ class DefaultLearn2earnInteractor( }, ) } + + private fun getCurrencyForAward(): Currency? { + val token = promotion.info?.awardPaymentToken ?: return null + + return Currency.NonNativeToken( + id = token.id, + name = token.name, + symbol = token.symbol, + networkId = token.networkId, + contractAddress = token.contractAddress, + decimalCount = token.decimalCount, + ) + } + + private fun updateUserData(updateBlock: (PromoUserData) -> PromoUserData): PromoUserData { + return updateBlock(repository.getUserData()).apply { + repository.updateUserData(this) + } + } } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/HeadersConverter.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/HeadersConverter.kt new file mode 100644 index 0000000000..439091ba1a --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/HeadersConverter.kt @@ -0,0 +1,28 @@ +package com.tangem.feature.learn2earn.domain + +import com.tangem.utils.converter.TwoWayConverter + +/** + * A converter that helps prepare a list of headers to be added to the Bundle. + * ArrayList is used to prevent cast exceptions when the convert result is placed in a Bundle. + * @see Learn2earnRouter#openWebView() putStringArrayListExtra + * +[REDACTED_AUTHOR] + */ +internal class HeadersConverter : TwoWayConverter, ArrayList> { + + override fun convert(value: Map): ArrayList { + val list = value.toList().map { + "${it.first}$SPLIT_SIGN${it.second}" + } + return ArrayList(list) + } + + override fun convertBack(value: ArrayList): Map { + return value.map { it.split(SPLIT_SIGN) }.associate { it[0] to it[1] } + } + + private companion object { + const val SPLIT_SIGN = ":" + } +} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriBuilder.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriBuilder.kt index 17361859ea..5af98601aa 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriBuilder.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriBuilder.kt @@ -1,23 +1,20 @@ package com.tangem.feature.learn2earn.domain import android.net.Uri -import com.tangem.feature.learn2earn.domain.models.CardType import com.tangem.feature.learn2earn.impl.BuildConfig -import com.tangem.lib.auth.BasicAuthProvider /** [REDACTED_AUTHOR] */ -class WebViewUriBuilder( - private val basicAuthProvider: BasicAuthProvider, +internal class WebViewUriBuilder( + private val authCredentialsProvider: () -> String?, private val userCountryCodeProvider: () -> String, private val promoCodeProvider: () -> String?, - private val promoNameProvider: () -> String, ) { - fun buildUriForStories(type: CardType): Uri { + fun buildUriForNewUser(): Uri { val builder = makeWebViewUriBuilder() - .appendQueryParameter("type", type.typeName) + .appendQueryParameter("type", QUERY_NEW_CARD) promoCodeProvider.invoke()?.let { builder.appendQueryParameter("code", it) @@ -26,23 +23,20 @@ class WebViewUriBuilder( return builder.build() } - fun buildUriForMainPage(walletId: String, cardId: String, cardPubKey: String): Uri { + fun buildUriForOldUser(): Uri { val builder = makeWebViewUriBuilder() - .appendQueryParameter("type", CardType.EXISTED.typeName) - .appendQueryParameter("cardPublicKey", cardPubKey) - .appendQueryParameter("cardId", cardId) - .appendQueryParameter("walletId", walletId) - .appendQueryParameter("programName", promoNameProvider.invoke()) - - promoCodeProvider.invoke()?.let { - builder.appendQueryParameter("code", it) - } + .appendQueryParameter("type", QUERY_EXISTED_CARD) return builder.build() } fun getBasicAuthHeaders(): Map { - return basicAuthProvider.getCredentials() + val credentials = authCredentialsProvider.invoke() + return if (credentials == null) { + mapOf() + } else { + mapOf("Authorization" to "Basic $credentials") + } } fun isPromoCodeRedirect(uri: Uri): Boolean { @@ -67,30 +61,34 @@ class WebViewUriBuilder( private fun makeWebViewUriBuilder(): Uri.Builder { val builder = Uri.Builder() - builder.scheme("https") + builder.scheme(SCHEME) if (BuildConfig.DEBUG) { - builder.authority(DEV_WEB_VIEW_BASE_URL) + builder.authority(DEV_BASE_URL) builder.appendPath(userCountryCodeProvider.invoke()) - builder.appendPath("promotion-test") + builder.appendPath(DEV_PATH_PROMOTION) } else { - builder.authority(WEB_VIEW_BASE_URL) + builder.authority(BASE_URL) builder.appendPath(userCountryCodeProvider.invoke()) - builder.appendPath("promotion") + builder.appendPath(PATH_PROMOTION) } return builder } private companion object { - const val WEB_VIEW_BASE_URL = "tangem.com" + const val SCHEME = "https" - const val DEV_WEB_VIEW_BASE_URL = "devweb.tangem.com" - const val DEV_WEB_VIEW_BASIC_AUTH = "Basic dGFuZ2VtOnRhbmdlbWRldjc=" - // TODO: 1inch: replace by invalid auth - // const val DEV_WEB_VIEW_BASIC_AUTH = "Basic paste valid value" + const val BASE_URL = "tangem.com" + const val PATH_PROMOTION = "promotion" + + const val QUERY_NEW_CARD = "new-card" + const val QUERY_EXISTED_CARD = "existed-card" + + const val DEV_BASE_URL = "devweb.tangem.com" + const val DEV_PATH_PROMOTION = "promotion-test" const val SUFFIX_CODE_CREATED = "/code-created?code=" - const val SUFFIX_READY_FOR_AWARD = "/ready-for-exsited-card-award" + const val SUFFIX_READY_FOR_AWARD = "/ready-for-existed-card-award" } } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnDependencyProvider.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnDependencyProvider.kt new file mode 100644 index 0000000000..b0261884ec --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnDependencyProvider.kt @@ -0,0 +1,13 @@ +package com.tangem.feature.learn2earn.domain.api + +/** + * Interface, a wrapper that allows us to get values from the AppStateHolder located in the app module + * +[REDACTED_AUTHOR] + */ +interface Learn2earnDependencyProvider { + + fun getUserCountryCodeProvider(): () -> String + + fun getWebViewAuthCredentialsProvider(): () -> String? +} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnInteractor.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnInteractor.kt index 432d4b001f..2cdadbb236 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnInteractor.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnInteractor.kt @@ -1,24 +1,32 @@ package com.tangem.feature.learn2earn.domain.api import android.net.Uri -import com.tangem.feature.learn2earn.domain.models.RedirectConsequences /** [REDACTED_AUTHOR] */ -interface Learn2earnInteractor { +interface Learn2earnInteractor : WebViewRedirectHandler { + + var webViewResultHandler: WebViewResultHandler? suspend fun init() + fun isUserHadPromoCode(): Boolean + fun isNeedToShowViewOnStoriesScreen(): Boolean - suspend fun isNeedToShowViewOnWalletScreen(walletId: String): Boolean + suspend fun isNeedToShowViewOnMainScreen(): Boolean - fun getBasicAuthHeaders(): Map + fun isUserRegisteredInPromotion(): Boolean - fun buildUriForStories(): Uri + fun getAwardAmount(): Int - fun buildUriForMainPage(walletId: String, cardId: String, cardPubKey: String): Uri + @Throws(IllegalArgumentException::class) + suspend fun requestAward(): Result - fun handleWebViewRedirect(uri: Uri): RedirectConsequences + fun buildUriForNewUser(): Uri + + fun buildUriForOldUser(): Uri + + fun getBasicAuthHeaders(): ArrayList } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewRedirectHandler.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewRedirectHandler.kt new file mode 100644 index 0000000000..efca9c182a --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewRedirectHandler.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.learn2earn.domain.api + +import android.net.Uri + +/** + * Handler that helps determine what actions to take inside the WebView of the Learn2earnWebViewActivity after + * a redirect has been processed by WebViewRedirectHandler + * +[REDACTED_AUTHOR] + */ +interface WebViewRedirectHandler { + fun handleRedirect(uri: Uri): RedirectConsequences +} + +enum class RedirectConsequences { + NOTHING, + PROCEED, + FINISH_SESSION, +} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt new file mode 100644 index 0000000000..08a749f327 --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt @@ -0,0 +1,15 @@ +package com.tangem.feature.learn2earn.domain.api + +/** + * Handler that helps determine a result of the Learn2earnWebViewActivity webView actions. + * +[REDACTED_AUTHOR] + */ +interface WebViewResultHandler { + fun handleResult(result: WebViewResult) +} + +sealed class WebViewResult { + object PromoCodeReceived : WebViewResult() + object ReadyForAward : WebViewResult() +} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/di/Learn2earnDomainModule.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/di/Learn2earnDomainModule.kt index 07d5687c5d..7ae76d3a19 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/di/Learn2earnDomainModule.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/di/Learn2earnDomainModule.kt @@ -3,9 +3,12 @@ package com.tangem.feature.learn2earn.domain.di import android.content.Context import com.tangem.feature.learn2earn.data.api.Learn2earnRepository import com.tangem.feature.learn2earn.domain.DefaultLearn2earnInteractor +import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor +import com.tangem.feature.learn2earn.domain.api.WebViewRedirectHandler import com.tangem.feature.learn2earn.presentation.Learn2earnRouter -import com.tangem.lib.auth.BasicAuthProvider +import com.tangem.lib.crypto.DerivationManager +import com.tangem.lib.crypto.UserWalletManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -27,13 +30,21 @@ class Learn2earnDomainModule { @Singleton fun provideInteractor( repository: Learn2earnRepository, - basicAuthProvider: BasicAuthProvider, + dependencyProvider: Learn2earnDependencyProvider, + userWalletManager: UserWalletManager, + derivationManager: DerivationManager, ): Learn2earnInteractor { return DefaultLearn2earnInteractor( repository = repository, - basicAuthProvider = basicAuthProvider, - // TODO: 1inch: paste locale provider instead of hardcode - userCountryCodeProvider = { "ru" }, + userWalletManager = userWalletManager, + derivationManager = derivationManager, + dependencyProvider = dependencyProvider, ) } + + @Provides + @Singleton + fun provideWebViewHandler(interactor: Learn2earnInteractor): WebViewRedirectHandler { + return interactor + } } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/CardType.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/CardType.kt deleted file mode 100644 index 1e05115373..0000000000 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/CardType.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.feature.learn2earn.domain.models - -/** -[REDACTED_AUTHOR] - */ -enum class CardType(val typeName: String) { - NEW("new-card"), - EXISTED("existed-card"), -} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/MockModels.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/MockModels.kt deleted file mode 100644 index 340de5e151..0000000000 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/MockModels.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.tangem.feature.learn2earn.domain.models - -import com.tangem.datasource.api.promotion.models.PromotionInfoResponse - -/** -[REDACTED_AUTHOR] - */ -internal fun Promotion.Companion.dummyPending(): Promotion { - return Promotion( - info = Promotion.PromotionInfo.dummy().copy( - status = PromotionInfoResponse.Status.PENDING, - ), - error = null, - ) -} - -internal fun Promotion.Companion.dummyActive(): Promotion { - return Promotion( - info = Promotion.PromotionInfo.dummy().copy( - status = PromotionInfoResponse.Status.ACTIVE, - ), - error = null, - ) -} - -internal fun Promotion.Companion.dummyFinished(): Promotion { - return Promotion( - info = Promotion.PromotionInfo.dummy().copy( - status = PromotionInfoResponse.Status.FINISHED, - ), - error = null, - ) -} - -internal fun Promotion.Companion.dummyError(): Promotion { - return Promotion( - info = null, - error = PromotionError.Error( - code = 105, - description = "any", - ), - ) -} - -internal fun Promotion.Companion.dummyErrorUnreachable(): Promotion { - return Promotion( - info = null, - error = PromotionError.NetworkUnreachable, - ) -} - -private fun Promotion.PromotionInfo.Companion.dummy(): Promotion.PromotionInfo { - return Promotion.PromotionInfo( - status = PromotionInfoResponse.Status.ACTIVE, - awardForNewCard = 10f, - awardForOldCard = 5.5f, - awardPaymentToken = Promotion.tokenInfo(), - ) -} - -private fun Promotion.Companion.tokenInfo(): PromotionInfoResponse.TokenInfo { - return PromotionInfoResponse.TokenInfo( - id = "1inch", - name = "1inch", - symbol = "1INCH", - active = true, - networks = listOf( - PromotionInfoResponse.TokenInfo.Network( - networkId = "polygon-pos", - exchangeable = false, - contractAddress = "0x9c2c5fd7b07e95ee044ddeba0e97a665f142394f", - decimalCount = 1, - ), - ), - ) -} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/Promotion.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/Promotion.kt index e5b071a15d..a79a7dfba4 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/Promotion.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/Promotion.kt @@ -11,7 +11,7 @@ data class Promotion( ) { @Throws(NullPointerException::class) - fun getInfo(): PromotionInfo = info!! + fun getPromotionInfo(): PromotionInfo = info!! fun isError(): Boolean = error != null @@ -19,9 +19,9 @@ data class Promotion( data class PromotionInfo( val status: PromotionInfoResponse.Status, - val awardForNewCard: Float, - val awardForOldCard: Float, - val awardPaymentToken: PromotionInfoResponse.TokenInfo, + val awardForNewCard: Double, + val awardForOldCard: Double, + val awardPaymentToken: PromotionInfoResponse.TokenData, ) { companion object } diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/PromotionError.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/PromotionError.kt index 8cd24cfa2c..18d8e79293 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/PromotionError.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/PromotionError.kt @@ -5,11 +5,31 @@ import com.tangem.datasource.api.promotion.models.AbstractPromotionResponse /** [REDACTED_AUTHOR] */ -sealed class PromotionError(val code: Int, val description: String) { - object NetworkUnreachable : PromotionError(-1, "Network or service is unreachable") - class Error(code: Int, description: String) : PromotionError(code, description) +sealed class PromotionError(val code: Int, val description: String) : Throwable( + message = "code: $code, description: $description", +) { - companion object { - fun AbstractPromotionResponse.Error.toDomainError(): PromotionError = Error(code, description) - } + object NetworkUnreachable : PromotionError(code = 0, "Network or service not available") + + class CodeNotFound(code: Int, description: String) : PromotionError(code, description) + class CodeWasNotAppliedInShop(code: Int, description: String) : PromotionError(code, description) + class CodeWasAlreadyUsed(code: Int, description: String) : PromotionError(code, description) + class WalletAlreadyHasAward(code: Int, description: String) : PromotionError(code, description) + class CardAlreadyHasAward(code: Int, description: String) : PromotionError(code, description) + class ProgramNotFound(code: Int, description: String) : PromotionError(code, description) + class ProgramWasEnd(code: Int, description: String) : PromotionError(code, description) + + class UnknownError(description: String) : PromotionError(code = -1, description) +} + +@Suppress("MagicNumber") +internal fun AbstractPromotionResponse.Error.toDomainError(): PromotionError = when (code) { + 101 -> PromotionError.CodeNotFound(code, message) + 102 -> PromotionError.CodeWasNotAppliedInShop(code, message) + 103 -> PromotionError.CodeWasAlreadyUsed(code, message) + 104 -> PromotionError.WalletAlreadyHasAward(code, message) + 105 -> PromotionError.CardAlreadyHasAward(code, message) + 106 -> PromotionError.ProgramNotFound(code, message) + 107 -> PromotionError.ProgramWasEnd(code, message) + else -> PromotionError.UnknownError("$code:$message") } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/WebViewHelper.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/WebViewHelper.kt deleted file mode 100644 index 0075b17435..0000000000 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/WebViewHelper.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.feature.learn2earn.domain.models - -import android.net.Uri - -/** -[REDACTED_AUTHOR] - */ -interface WebViewHelper { - fun handleWebViewRedirect(uri: Uri): RedirectConsequences - fun getWebViewHeaders(): Map -} - -enum class RedirectConsequences { - NOTHING, - PROCEED, - FINISH_SESSION, -} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnRouter.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnRouter.kt index a02d4be651..c845f2a50e 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnRouter.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnRouter.kt @@ -2,6 +2,8 @@ package com.tangem.feature.learn2earn.presentation import android.content.Context import android.content.Intent +import android.net.Uri +import com.tangem.feature.learn2earn.presentation.webView.Learn2earnWebViewActivity /** [REDACTED_AUTHOR] @@ -10,7 +12,12 @@ class Learn2earnRouter( private val context: Context, ) { - fun openWebView() { - context.startActivity(Intent(context, Learn2earnWebViewActivity::class.java)) + fun openWebView(uri: Uri, rawHeaders: ArrayList) { + val intent = Intent(context, Learn2earnWebViewActivity::class.java) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(Learn2earnWebViewActivity.EXTRA_WEB_URI, uri.toString()) + .putStringArrayListExtra(Learn2earnWebViewActivity.EXTRA_WEB_HEADERS, rawHeaders) + + context.startActivity(intent) } } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnViewModel.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnViewModel.kt index f39aee6032..cb99ddef44 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnViewModel.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnViewModel.kt @@ -1,11 +1,16 @@ package com.tangem.feature.learn2earn.presentation -import android.net.Uri +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor -import com.tangem.feature.learn2earn.domain.models.RedirectConsequences -import com.tangem.feature.learn2earn.domain.models.WebViewHelper +import com.tangem.feature.learn2earn.domain.api.WebViewResult +import com.tangem.feature.learn2earn.domain.api.WebViewResultHandler +import com.tangem.feature.learn2earn.domain.models.PromotionError +import com.tangem.feature.learn2earn.presentation.ui.state.* +import com.tangem.lib.crypto.models.errors.UserCancelledException import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -20,39 +25,156 @@ class Learn2earnViewModel @Inject constructor( private val interactor: Learn2earnInteractor, private val router: Learn2earnRouter, private val dispatchers: AppCoroutineDispatcherProvider, -) : ViewModel(), WebViewHelper { +) : ViewModel() { - var webViewUri: Uri = Uri.parse("https://localhost") + var uiState: Learn2earnState by mutableStateOf( + Learn2earnState.init( + uiActions = Learn2earnUiActions( + onButtonStoryClick = ::onButtonStoryClick, + onButtonMainClick = ::onButtonMainClick, + ), + ), + ) private set - fun isNeedToShowViewOnStoriesScreen(): Boolean = interactor.isNeedToShowViewOnStoriesScreen() - - fun onStoriesClick() { - webViewUri = interactor.buildUriForStories() - router.openWebView() + init { + uiState = uiState.updateStoriesVisibility(interactor.isNeedToShowViewOnStoriesScreen()) } - fun isNeedToShowViewOnWalletScreen(walletId: String, callback: (Boolean) -> Unit) { + fun onMainScreenCreated() { + updateMainScreenViews() + } + + private fun updateMainScreenViews() { viewModelScope.launch(dispatchers.io) { - val isNeedToShow = interactor.isNeedToShowViewOnWalletScreen(walletId) - withContext(dispatchers.main) { - callback(isNeedToShow) + if (interactor.isNeedToShowViewOnMainScreen()) { + updateUi { + uiState.changeGetBounsDescription(getBonusDescription()) + .updateGetBonusVisibility(isVisible = true) + } + } else { + updateUi { uiState.updateGetBonusVisibility(isVisible = false) } } } } - fun onMainPageClick(walletId: String, cardId: String, cardPubKey: String) { - webViewUri = interactor.buildUriForMainPage(walletId, cardId, cardPubKey) - router.openWebView() + private fun onButtonStoryClick() { + router.openWebView(interactor.buildUriForNewUser(), interactor.getBasicAuthHeaders()) } - // region WebViewHelper - override fun handleWebViewRedirect(uri: Uri): RedirectConsequences { - return interactor.handleWebViewRedirect(uri) + private fun onButtonMainClick() { + if (!interactor.isUserHadPromoCode() && !interactor.isUserRegisteredInPromotion()) { + subscribeToWebViewResultEvents() + router.openWebView(interactor.buildUriForOldUser(), interactor.getBasicAuthHeaders()) + return + } + + viewModelScope.launch(dispatchers.io) { + updateUi { uiState.updateProgress(showProgress = true) } + + runCatching { interactor.requestAward() } + .onSuccess { requestAwardResult -> + requestAwardResult.fold( + onSuccess = { + val onHideDialog = { + uiState = uiState.updateGetBonusVisibility(isVisible = false) + .hideDialog() + } + val successDialog = MainScreenState.Dialog.Claimed( + onOk = onHideDialog, + onDismissRequest = onHideDialog, + ) + updateUi { uiState.showDialog(successDialog) } + }, + onFailure = { + val errorDialog = createErrorDialog(it.toDomainError()) + updateUi { + uiState.updateProgress(showProgress = false) + .showDialog(errorDialog) + } + }, + ) + } + .onFailure { exception -> + updateUi { uiState.updateProgress(showProgress = false) } + if (exception is UserCancelledException) return@launch + + val errorDialog = createErrorDialog(exception.toDomainError()) + updateUi { uiState.showDialog(errorDialog) } + } + } } - override fun getWebViewHeaders(): Map { - return interactor.getBasicAuthHeaders() + private fun getBonusDescription(): MainScreenState.Description { + return if (interactor.isUserRegisteredInPromotion()) { + MainScreenState.Description.GetBonus + } else { + MainScreenState.Description.Learn(interactor.getAwardAmount()) + } } - // endregion WebViewHelper + + private fun createErrorDialog(error: PromotionError): MainScreenState.Dialog { + return when (error) { + is PromotionError.CodeWasNotAppliedInShop -> { + val onHideDialog = { uiState = uiState.hideDialog() } + MainScreenState.Dialog.PromoCodeNotRegistered( + onOk = { + uiState = uiState.hideDialog() + subscribeToWebViewResultEvents() + router.openWebView(interactor.buildUriForNewUser(), interactor.getBasicAuthHeaders()) + }, + onCancel = onHideDialog, + onDismissRequest = onHideDialog, + ) + } + is PromotionError.CodeNotFound, + is PromotionError.CodeWasAlreadyUsed, + is PromotionError.CardAlreadyHasAward, + is PromotionError.WalletAlreadyHasAward, + is PromotionError.ProgramNotFound, + is PromotionError.ProgramWasEnd, + -> { + val onHideDialog = { + uiState = uiState.hideDialog() + .updateGetBonusVisibility(isVisible = false) + } + MainScreenState.Dialog.Error( + error = error, + onOk = onHideDialog, + onDismissRequest = onHideDialog, + ) + } + is PromotionError.UnknownError, PromotionError.NetworkUnreachable -> { + val onHideDialog = { uiState = uiState.hideDialog() } + MainScreenState.Dialog.Error( + error = error, + onOk = onHideDialog, + onDismissRequest = onHideDialog, + ) + } + } + } + + private fun subscribeToWebViewResultEvents() { + interactor.webViewResultHandler = object : WebViewResultHandler { + override fun handleResult(result: WebViewResult) { + interactor.webViewResultHandler = null + when (result) { + WebViewResult.ReadyForAward -> updateMainScreenViews() + WebViewResult.PromoCodeReceived -> Unit + } + } + } + } + + private suspend fun updateUi(updateBlock: suspend () -> Learn2earnState) { + withContext(dispatchers.io) { + val newState = updateBlock.invoke() + withContext(dispatchers.main) { uiState = newState } + } + } +} + +private fun Throwable.toDomainError(): PromotionError { + return this as? PromotionError ?: PromotionError.UnknownError(message ?: "Unknown error") } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/GetBonusAlert.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/GetBonusView.kt similarity index 54% rename from features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/GetBonusAlert.kt rename to features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/GetBonusView.kt index 0079c24f11..5c4aff6b96 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/GetBonusAlert.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/GetBonusView.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -13,22 +14,30 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource 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.SpacerH4 +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTypography import com.tangem.feature.learn2earn.impl.R import com.tangem.feature.learn2earn.presentation.ui.component.GradientCircle +import com.tangem.feature.learn2earn.presentation.ui.state.MainScreenState /** [REDACTED_AUTHOR] */ @Composable -fun GetBonusAlert(onClick: () -> Unit, modifier: Modifier = Modifier) { +internal fun GetBonusView(state: MainScreenState, modifier: Modifier = Modifier) { + if (!state.isVisible) return + val shape = TangemTheme.shapes.roundedCornersMedium Box( modifier = modifier + .clickable(onClick = state.onClick) .height(TangemTheme.dimens.size96) .background( color = TangemTheme.colors.background.action, @@ -40,21 +49,33 @@ fun GetBonusAlert(onClick: () -> Unit, modifier: Modifier = Modifier) { modifier = Modifier.fillMaxHeight(), verticalAlignment = Alignment.CenterVertically, ) { - Image( - modifier = Modifier.padding(horizontal = TangemTheme.dimens.size16), - painter = painterResource(id = R.drawable.img_1inch_logo_42_40), - contentDescription = null, - ) + Box(modifier = Modifier.padding(horizontal = TangemTheme.dimens.size16)) { + Image( + painter = painterResource(id = R.drawable.img_1inch_logo_42_40), + alpha = state.logoState.alpha, + contentDescription = null, + ) + if (state.showProgress) { + CircularProgressIndicator( + modifier = Modifier + .align(Alignment.Center) + .size(size = TangemTheme.dimens.size28), + color = TangemColorPalette.White, + strokeWidth = TangemTheme.dimens.size2, + ) + } + } Column( modifier = Modifier.padding(end = TangemTheme.dimens.size40), ) { Text( - text = stringResource(id = R.string.main_learn_title), + text = state.description.title.resolveReference(), style = TangemTypography.body1, color = TangemTheme.colors.text.primary2, ) + SpacerH4() Text( - text = stringResource(id = R.string.main_learn_subtitle), + text = state.description.subtitle.resolveReference(), style = TangemTypography.caption, color = TangemTheme.colors.text.secondary, ) @@ -64,8 +85,7 @@ fun GetBonusAlert(onClick: () -> Unit, modifier: Modifier = Modifier) { modifier = Modifier .size(TangemTheme.dimens.size40) .padding(end = TangemTheme.dimens.size16) - .align(Alignment.CenterEnd) - .clickable(onClick = onClick), + .align(Alignment.CenterEnd), painter = painterResource(id = R.drawable.ic_chevron_right_24), contentDescription = null, colorFilter = ColorFilter.tint(Color.Gray), @@ -108,24 +128,46 @@ private fun ContentBackground(shape: Shape) { @Preview @Composable -private fun OneInchStoriesContentPreview_Light() { +private fun GetBonusViewPreview_Light(@PreviewParameter(GetBonusViewPreviewProvider::class) state: MainScreenState) { TangemTheme( isDark = false, ) { - GetBonusAlert( - onClick = {}, - ) + GetBonusView(state) } } @Preview @Composable -private fun OneInchStoriesContentPreview_Dark() { +private fun GetBonusViewPreview_Dark(@PreviewParameter(GetBonusViewPreviewProvider::class) state: MainScreenState) { TangemTheme( isDark = true, ) { - GetBonusAlert( - onClick = {}, - ) + GetBonusView(state) } -} \ No newline at end of file +} + +private class GetBonusViewPreviewProvider : CollectionPreviewParameterProvider( + collection = listOf( + MainScreenState( + isVisible = true, + onClick = {}, + description = MainScreenState.Description.Learn(1), + logoState = MainScreenState.LogoState.Idle, + showProgress = false, + ), + MainScreenState( + isVisible = true, + onClick = {}, + description = MainScreenState.Description.Learn(2), + logoState = MainScreenState.LogoState.InProgress, + showProgress = true, + ), + MainScreenState( + isVisible = true, + onClick = {}, + description = MainScreenState.Description.GetBonus, + logoState = MainScreenState.LogoState.Idle, + showProgress = false, + ), + ), +) \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnDialogs.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnDialogs.kt new file mode 100644 index 0000000000..2f752ea0fd --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnDialogs.kt @@ -0,0 +1,85 @@ +package com.tangem.feature.learn2earn.presentation.ui + +import androidx.compose.material.AlertDialog +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.components.TextButton +import com.tangem.feature.learn2earn.impl.R +import com.tangem.feature.learn2earn.presentation.ui.state.MainScreenState + +/** +[REDACTED_AUTHOR] + */ +@Composable +internal fun Learn2earnDialogs(dialog: MainScreenState.Dialog?) { + when (dialog) { + is MainScreenState.Dialog.Claimed -> ClaimedDialog(dialog) + is MainScreenState.Dialog.PromoCodeNotRegistered -> PromoCodeNotRegisteredDialog(dialog) + is MainScreenState.Dialog.Error -> ErrorDialog(dialog) + else -> Unit + } +} + +@Composable +private fun ClaimedDialog(dialog: MainScreenState.Dialog.Claimed) { + AlertDialog( + title = { + Text(text = stringResource(id = R.string.common_success)) + }, + text = { + Text(text = stringResource(id = R.string.main_promotion_credited)) + }, + confirmButton = { + TextButton( + text = stringResource(id = R.string.common_ok), + onClick = dialog.onOk, + ) + }, + onDismissRequest = dialog.onDismissRequest, + ) +} + +@Composable +private fun PromoCodeNotRegisteredDialog(dialog: MainScreenState.Dialog.PromoCodeNotRegistered) { + AlertDialog( + title = { + Text(text = stringResource(id = R.string.common_error)) + }, + text = { + Text(text = stringResource(id = R.string.main_promotion_no_purchase)) + }, + dismissButton = { + TextButton( + text = stringResource(id = R.string.common_cancel), + onClick = dialog.onCancel, + ) + }, + confirmButton = { + TextButton( + text = stringResource(id = R.string.common_buy), + onClick = dialog.onOk, + ) + }, + onDismissRequest = dialog.onDismissRequest, + ) +} + +@Composable +private fun ErrorDialog(dialog: MainScreenState.Dialog.Error) { + AlertDialog( + title = { + Text(text = stringResource(id = R.string.common_error)) + }, + text = { + Text(text = dialog.error.description) + }, + confirmButton = { + TextButton( + text = stringResource(id = R.string.common_ok), + onClick = dialog.onOk, + ) + }, + onDismissRequest = dialog.onDismissRequest, + ) +} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnMainPageScreen.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnMainPageScreen.kt new file mode 100644 index 0000000000..be9b02a7d7 --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnMainPageScreen.kt @@ -0,0 +1,21 @@ +package com.tangem.feature.learn2earn.presentation.ui + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.learn2earn.presentation.ui.state.Learn2earnState + +/** +[REDACTED_AUTHOR] + */ +@Composable +fun Learn2earnMainPageScreen(state: Learn2earnState, modifier: Modifier = Modifier) { + GetBonusView( + state = state.mainScreenState, + modifier = modifier + .padding(horizontal = TangemTheme.dimens.size16) + .padding(vertical = TangemTheme.dimens.size8), + ) + Learn2earnDialogs(state.mainScreenState.dialog) +} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/StoriesScreen.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnStoriesScreen.kt similarity index 92% rename from features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/StoriesScreen.kt rename to features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnStoriesScreen.kt index 74f3584e6b..4793bcfbc4 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/StoriesScreen.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnStoriesScreen.kt @@ -24,9 +24,8 @@ import com.tangem.feature.learn2earn.presentation.ui.component.GradientCircle /** [REDACTED_AUTHOR] */ -// TODO: 1inch: make function as internal after adding feature interface @Composable -fun StoriesScreen(onLearnClick: () -> Unit, modifier: Modifier = Modifier) { +fun Learn2earnStoriesScreen(onLearnClick: () -> Unit, modifier: Modifier = Modifier) { Box(modifier = modifier.fillMaxSize()) { ContentBackground( modifier = Modifier @@ -107,11 +106,11 @@ private fun StoryDescription(headerText: String, bodyText: String, modifier: Mod @Preview @Composable -private fun OneInchStoriesContentPreview_Light() { +private fun Learn2earnStoriesScreenPreview_Light() { TangemTheme( isDark = false, ) { - StoriesScreen( + Learn2earnStoriesScreen( onLearnClick = {}, ) } @@ -119,11 +118,11 @@ private fun OneInchStoriesContentPreview_Light() { @Preview @Composable -private fun OneInchStoriesContentPreview_Dark() { +private fun Learn2earnStoriesScreenPreview_Dark() { TangemTheme( isDark = true, ) { - StoriesScreen( + Learn2earnStoriesScreen( onLearnClick = {}, ) } diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/Learn2earnState.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/Learn2earnState.kt new file mode 100644 index 0000000000..7c1ba34c55 --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/Learn2earnState.kt @@ -0,0 +1,74 @@ +package com.tangem.feature.learn2earn.presentation.ui.state + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.WrappedList +import com.tangem.feature.learn2earn.domain.models.PromotionError +import com.tangem.feature.learn2earn.impl.R + +/** +[REDACTED_AUTHOR] + */ +data class Learn2earnState( + val storyScreenState: StoryScreenState, + val mainScreenState: MainScreenState, +) { + companion object +} + +data class StoryScreenState( + val isVisible: Boolean, + val onClick: () -> Unit, +) + +data class MainScreenState( + val isVisible: Boolean, + val onClick: () -> Unit, + val description: Description, + val showProgress: Boolean, + val logoState: LogoState, + val dialog: Dialog? = null, +) { + + sealed class LogoState(val alpha: Float) { + object Idle : LogoState(alpha = 1.0f) + object InProgress : LogoState(alpha = 0.2f) + } + + sealed class Description(val title: TextReference, val subtitle: TextReference) { + + class Learn(award: Int) : Description( + TextReference.Res(R.string.main_learn_title), + TextReference.PluralRes(R.plurals.main_learn_subtitle, award, WrappedList(listOf(award))), + ) + + object GetBonus : Description( + TextReference.Res(R.string.main_get_bonus_title), + TextReference.Res(R.string.main_get_bonus_subtitle), + ) + } + + sealed class Dialog { + + data class Claimed( + val onOk: () -> Unit, + val onDismissRequest: () -> Unit, + ) : Dialog() + + data class PromoCodeNotRegistered( + val onOk: () -> Unit, + val onCancel: () -> Unit, + val onDismissRequest: () -> Unit, + ) : Dialog() + + data class Error( + val error: PromotionError, + val onOk: () -> Unit, + val onDismissRequest: () -> Unit, + ) : Dialog() + } +} + +class Learn2earnUiActions( + val onButtonStoryClick: () -> Unit, + val onButtonMainClick: () -> Unit, +) \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/StateBuilder.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/StateBuilder.kt new file mode 100644 index 0000000000..90e41829ab --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/StateBuilder.kt @@ -0,0 +1,97 @@ +package com.tangem.feature.learn2earn.presentation.ui.state + +/** +[REDACTED_AUTHOR] + */ +internal fun Learn2earnState.Companion.init(uiActions: Learn2earnUiActions): Learn2earnState { + return Learn2earnState( + storyScreenState = StoryScreenState( + isVisible = false, + onClick = uiActions.onButtonStoryClick, + ), + mainScreenState = MainScreenState( + isVisible = false, + onClick = uiActions.onButtonMainClick, + description = MainScreenState.Description.Learn(0), + logoState = MainScreenState.LogoState.Idle, + showProgress = false, + ), + ) +} + +internal fun Learn2earnState.updateStoriesVisibility(isVisible: Boolean): Learn2earnState { + return if (storyScreenState.isVisible == isVisible) { + this + } else { + copy( + storyScreenState = storyScreenState.copy( + isVisible = isVisible, + ), + ) + } +} + +internal fun Learn2earnState.updateGetBonusVisibility(isVisible: Boolean): Learn2earnState { + return if (mainScreenState.isVisible == isVisible) { + this + } else { + copy( + mainScreenState = mainScreenState.copy( + isVisible = isVisible, + ), + ) + } +} + +internal fun Learn2earnState.changeGetBounsDescription(description: MainScreenState.Description): Learn2earnState { + return if (mainScreenState.description == description) { + this + } else { + copy( + mainScreenState = mainScreenState.copy( + description = description, + ), + ) + } +} + +internal fun Learn2earnState.showDialog(dialog: MainScreenState.Dialog): Learn2earnState { + return if (mainScreenState.dialog == dialog) { + this + } else { + copy( + mainScreenState = mainScreenState.copy( + dialog = dialog, + ), + ) + } +} + +internal fun Learn2earnState.hideDialog(): Learn2earnState { + return if (mainScreenState.dialog == null) { + this + } else { + copy( + mainScreenState = mainScreenState.copy( + dialog = null, + ), + ) + } +} + +internal fun Learn2earnState.updateProgress(showProgress: Boolean): Learn2earnState { + return if (mainScreenState.showProgress == showProgress) { + this + } else { + copy( + mainScreenState = mainScreenState.copy( + showProgress = showProgress, + logoState = if (showProgress) { + MainScreenState.LogoState.InProgress + } else { + MainScreenState.LogoState.Idle + }, + ), + ) + } +} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnWebViewActivity.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/Learn2earnWebViewActivity.kt similarity index 55% rename from features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnWebViewActivity.kt rename to features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/Learn2earnWebViewActivity.kt index c054b21ef0..cea693607e 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnWebViewActivity.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/Learn2earnWebViewActivity.kt @@ -1,15 +1,11 @@ -package com.tangem.feature.learn2earn.presentation +package com.tangem.feature.learn2earn.presentation.webView import android.os.Bundle import android.view.MenuItem -import android.webkit.WebResourceRequest import android.webkit.WebView -import android.webkit.WebViewClient import androidx.activity.viewModels import androidx.appcompat.app.ActionBar import androidx.appcompat.app.AppCompatActivity -import com.tangem.feature.learn2earn.domain.models.RedirectConsequences -import com.tangem.feature.learn2earn.domain.models.WebViewHelper import com.tangem.feature.learn2earn.impl.R import dagger.hilt.android.AndroidEntryPoint @@ -19,7 +15,7 @@ import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class Learn2earnWebViewActivity : AppCompatActivity() { - private val learn2earnViewModel by viewModels() + private val viewModel by viewModels() private lateinit var actionBar: ActionBar private lateinit var webView: WebView @@ -28,24 +24,27 @@ class Learn2earnWebViewActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_web_view) + val webViewData = viewModel.extractData(intent) + setSupportActionBar(findViewById(R.id.toolbar)) actionBar = supportActionBar!! actionBar.setDisplayHomeAsUpEnabled(true) actionBar.setDisplayShowHomeEnabled(true) - actionBar.title = learn2earnViewModel.webViewUri.authority - + actionBar.title = webViewData.uri.authority webView = findViewById(R.id.web_view) + webView.settings.javaScriptEnabled = true webView.settings.domStorageEnabled = true webView.settings.loadsImagesAutomatically = true webView.webViewClient = Learn2earnWebViewClient( - helper = learn2earnViewModel, + helper = viewModel, + headers = webViewData.headers, finishSessionHandler = { finish() }, ) webView.loadUrl( - learn2earnViewModel.webViewUri.toString(), - learn2earnViewModel.getWebViewHeaders(), + webViewData.uri.toString(), + webViewData.headers, ) } @@ -66,25 +65,9 @@ class Learn2earnWebViewActivity : AppCompatActivity() { super.onDestroy() } -} -private class Learn2earnWebViewClient( - private val helper: WebViewHelper, - private val finishSessionHandler: () -> Unit, -) : WebViewClient() { - - override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { - when (helper.handleWebViewRedirect(request.url)) { - RedirectConsequences.NOTHING -> Unit - RedirectConsequences.PROCEED -> { - val headers = helper.getWebViewHeaders().toMutableMap() - request.requestHeaders?.let { headers.putAll(it) } - view.loadUrl(request.url.toString(), headers) - } - RedirectConsequences.FINISH_SESSION -> { - finishSessionHandler.invoke() - } - } - return true + companion object { + const val EXTRA_WEB_URI = "webViewUri" + const val EXTRA_WEB_HEADERS = "webViewHeaders" } } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/Learn2earnWebViewClient.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/Learn2earnWebViewClient.kt new file mode 100644 index 0000000000..5957d80a32 --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/Learn2earnWebViewClient.kt @@ -0,0 +1,31 @@ +package com.tangem.feature.learn2earn.presentation.webView + +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import com.tangem.feature.learn2earn.domain.api.RedirectConsequences +import com.tangem.feature.learn2earn.domain.api.WebViewRedirectHandler +import timber.log.Timber + +internal class Learn2earnWebViewClient( + private val helper: WebViewRedirectHandler, + private val headers: Map, + private val finishSessionHandler: () -> Unit, +) : WebViewClient() { + + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + Timber.d("url: ${request.url}") + when (helper.handleRedirect(request.url)) { + RedirectConsequences.PROCEED -> { + val mergedHeaders = headers.toMutableMap() + request.requestHeaders?.let { mergedHeaders.putAll(it) } + view.loadUrl(request.url.toString(), mergedHeaders) + } + RedirectConsequences.FINISH_SESSION -> { + finishSessionHandler.invoke() + } + else -> Unit + } + return true + } +} \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/WebViewViewModel.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/WebViewViewModel.kt new file mode 100644 index 0000000000..3b25e78a58 --- /dev/null +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/WebViewViewModel.kt @@ -0,0 +1,40 @@ +package com.tangem.feature.learn2earn.presentation.webView + +import android.content.Intent +import android.net.Uri +import androidx.lifecycle.ViewModel +import com.tangem.feature.learn2earn.domain.HeadersConverter +import com.tangem.feature.learn2earn.domain.api.RedirectConsequences +import com.tangem.feature.learn2earn.domain.api.WebViewRedirectHandler +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +/** +[REDACTED_AUTHOR] + */ +@HiltViewModel +internal class WebViewViewModel @Inject constructor( + private val handler: WebViewRedirectHandler, +) : ViewModel(), WebViewRedirectHandler { + + override fun handleRedirect(uri: Uri): RedirectConsequences { + return handler.handleRedirect(uri) + } + + fun extractData(intent: Intent): WebViewData { + val uriString = requireNotNull(intent.getStringExtra(Learn2earnWebViewActivity.EXTRA_WEB_URI)) { + "The Intent property EXTRA_WEB_URI is null" + } + val rawHeaders = intent.getStringArrayListExtra(Learn2earnWebViewActivity.EXTRA_WEB_HEADERS) ?: ArrayList() + + return WebViewData( + uri = Uri.parse(uriString), + headers = HeadersConverter().convertBack(rawHeaders), + ) + } +} + +data class WebViewData( + val uri: Uri, + val headers: Map, +) \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/res/layout/activity_web_view.xml b/features/learn2earn/impl/src/main/res/layout/activity_web_view.xml index 46968b0a14..d4020201c8 100644 --- a/features/learn2earn/impl/src/main/res/layout/activity_web_view.xml +++ b/features/learn2earn/impl/src/main/res/layout/activity_web_view.xml @@ -10,6 +10,7 @@ android:id="@+id/app_bar" style="@style/Widget.MaterialComponents.Toolbar.Surface" android:layout_width="match_parent" + app:liftOnScroll="true" android:layout_height="wrap_content"> -} \ No newline at end of file