Updated on 2026-08-14
This commit is contained in:
commit
0159b2f1ff
53 changed files with 650 additions and 462 deletions
|
|
@ -48,7 +48,6 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
|
|||
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.features.tester.api.TesterMenuLauncher
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.google.GoogleServicesHelper
|
||||
|
|
@ -447,12 +446,11 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
} else {
|
||||
lifecycleScope.launch {
|
||||
val shouldShowTos = !cardRepository.isTangemTOSAccepted()
|
||||
val shouldShowInitialPush = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false }
|
||||
|
||||
val route = when {
|
||||
shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false)
|
||||
shouldShowInitialPush -> AppRoute.PushNotification
|
||||
else -> AppRoute.Home
|
||||
val route = if (shouldShowTos) {
|
||||
AppRoute.Disclaimer(isTosAccepted = false)
|
||||
} else {
|
||||
AppRoute.Home
|
||||
}
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(route) }
|
||||
|
|
|
|||
|
|
@ -3,10 +3,16 @@ package com.tangem.tap.data
|
|||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class FirebasePushNotificationsTokenProvider @Inject constructor() : PushNotificationsTokenProvider {
|
||||
override suspend fun getToken(): String {
|
||||
return FirebaseMessaging.getInstance().token.await()
|
||||
return try {
|
||||
FirebaseMessaging.getInstance().token.await()
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,26 +4,35 @@ import com.tangem.common.services.secure.SecureStorage
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultSelectedUserWalletRepository(
|
||||
private val secureStorage: SecureStorage,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SelectedUserWalletRepository {
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
override suspend fun get(): UserWalletId? = withContext(dispatchers.io) {
|
||||
secureStorage.get(StorageKey.SelectedWalletId.name)
|
||||
?.decodeToString(throwOnInvalidSequence = true)
|
||||
?.let { UserWalletId(it) }
|
||||
mutex.withLock {
|
||||
secureStorage.get(StorageKey.SelectedWalletId.name)
|
||||
?.decodeToString(throwOnInvalidSequence = true)
|
||||
?.let { UserWalletId(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun set(walletId: UserWalletId?) = withContext(dispatchers.io) {
|
||||
if (walletId == null) {
|
||||
secureStorage.delete(StorageKey.SelectedWalletId.name)
|
||||
} else {
|
||||
secureStorage.store(
|
||||
data = walletId.stringValue.encodeToByteArray(throwOnInvalidSequence = true),
|
||||
account = StorageKey.SelectedWalletId.name,
|
||||
)
|
||||
mutex.withLock {
|
||||
if (walletId == null) {
|
||||
secureStorage.delete(StorageKey.SelectedWalletId.name)
|
||||
} else {
|
||||
secureStorage.store(
|
||||
data = walletId.stringValue.encodeToByteArray(throwOnInvalidSequence = true),
|
||||
account = StorageKey.SelectedWalletId.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import com.tangem.features.nft.component.NFTComponent
|
|||
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
|
||||
import com.tangem.features.onramp.component.*
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacksStub
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||
import com.tangem.features.send.v2.api.NFTSendComponent
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.SendEntryPointComponent
|
||||
|
|
@ -380,7 +382,9 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.PushNotification -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = PushNotificationsComponent.Params.Route(AppRoute.Home),
|
||||
params = PushNotificationsParams(
|
||||
modelCallbacks = PushNotificationsModelCallbacksStub(),
|
||||
),
|
||||
componentFactory = pushNotificationsComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,4 +21,7 @@ internal class AndroidAppInfoProvider @Inject constructor(
|
|||
get() = TimeZone.getDefault().id
|
||||
override val appVersion: String
|
||||
get() = appVersionProvider.versionName
|
||||
override val isHuaweiDevice: Boolean
|
||||
get() = Build.MANUFACTURER.equals("HUAWEI", ignoreCase = true) ||
|
||||
Build.BRAND.equals("HUAWEI", ignoreCase = true)
|
||||
}
|
||||
|
|
@ -134,6 +134,18 @@ object PreferencesKeys {
|
|||
|
||||
val NOTIFICATIONS_ENABLED_STATES_KEY by lazy { stringPreferencesKey(name = "notificationsEnabledStates") }
|
||||
|
||||
val NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY by lazy {
|
||||
stringPreferencesKey(
|
||||
name = "notificationsAutomaticallyEnabledStates",
|
||||
)
|
||||
}
|
||||
|
||||
val NOTIFICATIONS_USER_ALLOW_SEND_ADDRESSES_KEY by lazy {
|
||||
booleanPreferencesKey(
|
||||
name = "userAllowSendAddresses",
|
||||
)
|
||||
}
|
||||
|
||||
val TRON_NETWORK_FEE_NOTIFICATION_SHOW_COUNT_KEY by lazy {
|
||||
intPreferencesKey(name = "tronNetworkFeeNotificationShowCount")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ fun Showcase(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ShowcaseButtons(
|
||||
fun ShowcaseButtons(
|
||||
primaryButtonText: TextReference,
|
||||
secondaryButtonText: TextReference,
|
||||
onPrimaryClick: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
|
||||
/**
|
||||
* Returns push permission requester.
|
||||
* Handles granting permission from app settings.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun requestPermission(permission: String, onAllow: () -> Unit, onDeny: () -> Unit): () -> Unit {
|
||||
val permissionState = rememberPermissionState(
|
||||
permission = permission,
|
||||
onPermissionResult = { isGranted ->
|
||||
if (isGranted) {
|
||||
onAllow()
|
||||
} else {
|
||||
onDeny()
|
||||
}
|
||||
},
|
||||
)
|
||||
return when {
|
||||
permissionState.status.isGranted == false -> {
|
||||
if (isRequirePushPermission) {
|
||||
permissionState::launchPermissionRequest
|
||||
} else {
|
||||
onDeny // on versions below Tiramisu call onDeny directly and then open settings
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
onAllow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
|
||||
private val isRequirePushPermission = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
|
||||
/**
|
||||
* Returns push permission requester.
|
||||
* Handles granting permission from app settings.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun requestPushPermission(pushPermission: String?, onAllow: () -> Unit, onDeny: () -> Unit): () -> Unit {
|
||||
val permissionState = pushPermission?.let { permission ->
|
||||
rememberPermissionState(
|
||||
permission = permission,
|
||||
onPermissionResult = { isGranted ->
|
||||
if (isGranted) {
|
||||
onAllow()
|
||||
} else {
|
||||
onDeny()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return if (permissionState == null) {
|
||||
{}
|
||||
} else {
|
||||
permissionState::launchPermissionRequest
|
||||
}
|
||||
}
|
||||
|
|
@ -7,4 +7,5 @@ interface AppInfoProvider {
|
|||
val language: String
|
||||
val timezone: String
|
||||
val appVersion: String
|
||||
val isHuaweiDevice: Boolean
|
||||
}
|
||||
|
|
@ -10,7 +10,9 @@ import com.tangem.domain.wallets.repository.WalletsRepository
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class UserTokensResponseAddressesEnricher @Inject constructor(
|
||||
private val notificationsFeatureToggles: NotificationsFeatureToggles,
|
||||
|
|
@ -28,7 +30,10 @@ class UserTokensResponseAddressesEnricher @Inject constructor(
|
|||
|
||||
return withContext(dispatchers.default) {
|
||||
val networksStatuses = if (isNotificationsEnabled) {
|
||||
multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first()
|
||||
withTimeoutOrNull(
|
||||
FETCH_TIMEOUT_SECONDS.seconds,
|
||||
{ multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() },
|
||||
) ?: emptySet()
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
|
|
@ -61,4 +66,8 @@ class UserTokensResponseAddressesEnricher @Inject constructor(
|
|||
response.copy(tokens = enrichedTokens, notifyStatus = isNotificationsEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FETCH_TIMEOUT_SECONDS = 3
|
||||
}
|
||||
}
|
||||
|
|
@ -82,4 +82,36 @@ internal class DefaultNotificationsRepository @Inject constructor(
|
|||
NotificationsEligibleNetworkConverter.convert(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun shouldShowSubscribeOnNotificationsAfterUpdate(): Boolean {
|
||||
return appPreferencesStore.getSyncOrNull(
|
||||
key = PreferencesKeys.NOTIFICATIONS_USER_ALLOW_SEND_ADDRESSES_KEY,
|
||||
) == null
|
||||
}
|
||||
|
||||
override suspend fun isUserAllowToSubscribeOnPushNotifications(): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.NOTIFICATIONS_USER_ALLOW_SEND_ADDRESSES_KEY,
|
||||
default = false,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun setUserAllowToSubscribeOnPushNotifications(value: Boolean) {
|
||||
appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_USER_ALLOW_SEND_ADDRESSES_KEY, value)
|
||||
}
|
||||
|
||||
override suspend fun getWalletAutomaticallyEnabledList(): List<String> = appPreferencesStore
|
||||
.getObjectMapSync<Boolean>(PreferencesKeys.NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY).map {
|
||||
it.key
|
||||
}
|
||||
|
||||
override suspend fun setNotificationsWasEnabledAutomatically(userWalletId: String) {
|
||||
appPreferencesStore.editData {
|
||||
it.setObjectMap(
|
||||
key = PreferencesKeys.NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY,
|
||||
value = it.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY)
|
||||
.plus(userWalletId to true),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.notifications
|
||||
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetIsHuaweiDeviceWithoutGoogleServicesUseCase @Inject constructor(
|
||||
private val appInfoProvider: AppInfoProvider,
|
||||
private val pushNotificationsTokenProvider: PushNotificationsTokenProvider,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Boolean {
|
||||
return appInfoProvider.isHuaweiDevice && pushNotificationsTokenProvider.getToken().isEmpty()
|
||||
}
|
||||
}
|
||||
|
|
@ -21,4 +21,14 @@ interface NotificationsRepository {
|
|||
|
||||
@Throws
|
||||
suspend fun getEligibleNetworks(): List<NotificationsEligibleNetwork>
|
||||
|
||||
suspend fun shouldShowSubscribeOnNotificationsAfterUpdate(): Boolean
|
||||
|
||||
suspend fun isUserAllowToSubscribeOnPushNotifications(): Boolean
|
||||
|
||||
suspend fun setUserAllowToSubscribeOnPushNotifications(value: Boolean)
|
||||
|
||||
suspend fun getWalletAutomaticallyEnabledList(): List<String>
|
||||
|
||||
suspend fun setNotificationsWasEnabledAutomatically(userWalletId: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetIsBiometricsEnabledUseCase @Inject constructor(
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
) {
|
||||
|
||||
operator fun invoke(): Boolean = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() ?: false
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Use case for retrieving wallets where automatically enabling push notifications was not applied.
|
||||
* * This use case filters out wallets that have already had push notifications automatically enabled
|
||||
* from the complete list of user wallets, returning only those wallets that still need to have
|
||||
* push notifications automatically enabled.
|
||||
* * @property userWalletsListManager Manager for user wallets list operations
|
||||
* @property dispatchers Coroutine dispatcher provider for background operations
|
||||
*/
|
||||
class GetWalletsForAutomaticallyPushEnablingUseCase @Inject constructor(
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(walletsListWherePushWasEnabled: List<UserWalletId>): List<UserWalletId> =
|
||||
withContext(dispatchers.default) {
|
||||
val allLocalWallets = userWalletsListManager.userWalletsSync.map { it.walletId }
|
||||
allLocalWallets - walletsListWherePushWasEnabled.toSet()
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ dependencies {
|
|||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.notifications)
|
||||
|
||||
/** Feature modules */
|
||||
implementation(projects.features.disclaimer.api)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.disclaimer.impl.entity
|
|||
internal data class DisclaimerUM(
|
||||
val url: String,
|
||||
val isTosAccepted: Boolean,
|
||||
val onAccept: (Boolean) -> Unit,
|
||||
val onAccept: () -> Unit,
|
||||
val popBack: () -> Unit,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.finisher.AppFinisher
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.settings.NeverRequestPermissionUseCase
|
||||
import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase
|
||||
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
||||
|
|
@ -26,6 +28,9 @@ internal class DisclaimerModel @Inject constructor(
|
|||
private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase,
|
||||
private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase,
|
||||
private val appFinisher: AppFinisher,
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
|
||||
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -40,13 +45,14 @@ internal class DisclaimerModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
|
||||
private fun onAccept(shouldAskPushPermission: Boolean) = modelScope.launch {
|
||||
private fun onAccept() = modelScope.launch {
|
||||
if (params.isTosAccepted) {
|
||||
router.pop()
|
||||
} else {
|
||||
cardRepository.acceptTangemTOS()
|
||||
|
||||
if (shouldAskPushPermission) {
|
||||
val shouldAskPushPermission = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
|
||||
val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase()
|
||||
if (shouldAskPushPermission && !isHuaweiDevice) {
|
||||
router.push(AppRoute.PushNotification)
|
||||
} else {
|
||||
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
|
||||
|
|
|
|||
|
|
@ -15,9 +15,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.google.accompanist.web.WebView
|
||||
import com.google.accompanist.web.WebViewNavigator
|
||||
import com.google.accompanist.web.rememberWebViewNavigator
|
||||
|
|
@ -38,7 +35,6 @@ import com.tangem.features.disclaimer.impl.R
|
|||
import com.tangem.features.disclaimer.impl.entity.DisclaimerUM
|
||||
import com.tangem.features.disclaimer.impl.entity.DummyDisclaimer
|
||||
import com.tangem.features.disclaimer.impl.local.localTermsOfServices
|
||||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.withDebounce
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
|
@ -166,15 +162,11 @@ private fun WebViewNavigator.loadLocalToS() {
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
private fun BoxScope.DisclaimerButton(onAccept: (Boolean) -> Unit) {
|
||||
val isPermissionGranted = getPushPermissionOrNull()?.let { permission ->
|
||||
rememberPermissionState(permission = permission).status.isGranted
|
||||
} ?: true
|
||||
private fun BoxScope.DisclaimerButton(onAccept: () -> Unit) {
|
||||
PrimaryButton(
|
||||
text = stringResourceSafe(id = R.string.common_accept),
|
||||
onClick = { onAccept(!isPermissionGranted) },
|
||||
onClick = onAccept,
|
||||
colors = ButtonColors(
|
||||
containerColor = TangemColorPalette.Light4,
|
||||
contentColor = TangemColorPalette.Dark6,
|
||||
|
|
|
|||
|
|
@ -9,13 +9,12 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.domain.settings.ShouldAskPermissionUseCase
|
||||
import com.tangem.features.hotwallet.setaccesscode.SetAccessCodeComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.routing.AddExistingWalletRoute
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent
|
||||
import com.tangem.features.hotwallet.setaccesscode.SetAccessCodeComponent
|
||||
import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -32,7 +31,6 @@ internal class AddExistingWalletModel @Inject constructor(
|
|||
val addExistingWalletImportModelCallbacks = AddExistingWalletImportModelCallbacks()
|
||||
val manualBackupCompletedComponentModelCallbacks = ManualBackupCompletedComponentModelCallbacks()
|
||||
val accessCodeModelCallbacks = AccessCodeModelCallbacks()
|
||||
val pushNotificationsComponentModelCallbacks = PushNotificationsComponentModelCallbacks()
|
||||
val mobileWalletSetupFinishedComponentModelCallbacks = MobileWalletSetupFinishedComponentModelCallbacks()
|
||||
|
||||
val stackNavigation = StackNavigation<AddExistingWalletRoute>()
|
||||
|
|
@ -89,12 +87,6 @@ internal class AddExistingWalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
inner class PushNotificationsComponentModelCallbacks : PushNotificationsComponent.ModelCallbacks {
|
||||
override fun onResult() {
|
||||
stackNavigation.replaceCurrent(AddExistingWalletRoute.SetupFinished)
|
||||
}
|
||||
}
|
||||
|
||||
inner class MobileWalletSetupFinishedComponentModelCallbacks : MobileWalletSetupFinishedComponent.ModelCallbacks {
|
||||
override fun onContinueClick() {
|
||||
router.replaceAll(AppRoute.Wallet)
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ package com.tangem.features.hotwallet.addexistingwallet.root.routing
|
|||
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.setaccesscode.SetAccessCodeComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.AddExistingWalletModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent
|
||||
import com.tangem.features.hotwallet.setaccesscode.SetAccessCodeComponent
|
||||
import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacksStub
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AddExistingWalletChildFactory @Inject constructor(
|
||||
|
|
@ -47,8 +49,8 @@ internal class AddExistingWalletChildFactory @Inject constructor(
|
|||
)
|
||||
is AddExistingWalletRoute.PushNotifications -> pushNotificationsComponent.create(
|
||||
context = childContext,
|
||||
params = PushNotificationsComponent.Params.Callbacks(
|
||||
callbacks = model.pushNotificationsComponentModelCallbacks,
|
||||
params = PushNotificationsParams(
|
||||
modelCallbacks = PushNotificationsModelCallbacksStub(),
|
||||
),
|
||||
)
|
||||
AddExistingWalletRoute.SetupFinished -> MobileWalletSetupFinishedComponent(
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWall
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.Wallet1ChooseOptionComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.MultiWalletFinalizeComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.Wallet1ScanPrimaryComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.MultiWalletScanPrimaryComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.MultiWalletSeedPhraseComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
||||
|
|
@ -159,7 +159,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
|||
backButtonClickFlow = backButtonClickFlow,
|
||||
onBack = { stackNavigation.pop() },
|
||||
)
|
||||
ScanPrimary -> Wallet1ScanPrimaryComponent(
|
||||
ScanPrimary -> MultiWalletScanPrimaryComponent(
|
||||
context = childContext,
|
||||
params = childParams,
|
||||
onDone = { handleNavigationEvent(AddBackupDevice) },
|
||||
|
|
|
|||
|
|
@ -84,6 +84,15 @@ class MultiWalletBackupModel @Inject constructor(
|
|||
}
|
||||
|
||||
analyticsEventHandler.send(OnboardingEvent.Backup.Started)
|
||||
|
||||
// Clear any saved backup before starting the backup process
|
||||
// also clears the primary card if it was set
|
||||
backupService.discardSavedBackup()
|
||||
|
||||
// Primary card from ScanTask or from the ScanPrimaryModel or from MultiWalletCreateWalletModel
|
||||
// always not null for this step
|
||||
val primaryCard = requireNotNull(scanResponse.primaryCard)
|
||||
backupService.setPrimaryCard(primaryCard)
|
||||
}
|
||||
|
||||
private fun getInitState(): MultiWalletBackupUM {
|
||||
|
|
@ -93,24 +102,11 @@ class MultiWalletBackupModel @Inject constructor(
|
|||
finalizeButtonEnabled = false,
|
||||
addBackupButtonEnabled = true,
|
||||
addBackupButtonLoading = false,
|
||||
onAddBackupClick = ::startBackupWallet,
|
||||
onAddBackupClick = ::addBackupCardWithService,
|
||||
onFinalizeButtonClick = ::onFinalizeClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun startBackupWallet() {
|
||||
if (state.value.numberOfBackupCards == 0 && scanResponse.primaryCard != null) {
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
|
||||
val primaryCard = scanResponse.primaryCard
|
||||
if (primaryCard != null) {
|
||||
backupService.setPrimaryCard(primaryCard)
|
||||
}
|
||||
|
||||
addBackupCardWithService()
|
||||
}
|
||||
|
||||
private fun setNumberOfBackupCards(number: Int) {
|
||||
// set state for adding backup cards and disable button if there is more than 2 backup cards
|
||||
_uiState.update { st ->
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.Wallet1ScanPrimaryModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.ui.Wallet1ScanPrimary
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.MultiWalletScanPrimaryModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.ui.MultiWalletScanPrimary
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class Wallet1ScanPrimaryComponent(
|
||||
internal class MultiWalletScanPrimaryComponent(
|
||||
context: AppComponentContext,
|
||||
params: MultiWalletChildParams,
|
||||
onDone: () -> Unit,
|
||||
) : AppComponentContext by context, ComposableContentComponent {
|
||||
|
||||
private val model: Wallet1ScanPrimaryModel = getOrCreateModel(params)
|
||||
private val model: MultiWalletScanPrimaryModel = getOrCreateModel(params)
|
||||
|
||||
init {
|
||||
params.innerNavigation.update {
|
||||
|
|
@ -38,7 +38,7 @@ internal class Wallet1ScanPrimaryComponent(
|
|||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
Wallet1ScanPrimary(
|
||||
MultiWalletScanPrimary(
|
||||
isRing = model.isRing,
|
||||
onScanPrimaryClick = model::onScanPrimaryClick,
|
||||
)
|
||||
|
|
@ -5,21 +5,24 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.sdk.api.BackupServiceHolder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class Wallet1ScanPrimaryModel @Inject constructor(
|
||||
internal class MultiWalletScanPrimaryModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val backupServiceHolder: BackupServiceHolder,
|
||||
private val cardRepository: CardRepository,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<MultiWalletChildParams>()
|
||||
|
|
@ -31,9 +34,20 @@ internal class Wallet1ScanPrimaryModel @Inject constructor(
|
|||
fun onScanPrimaryClick() {
|
||||
val backupService = backupServiceHolder.backupService.get() ?: return
|
||||
val iconScanRes = R.drawable.img_hand_scan_ring.takeIf { isRing }
|
||||
|
||||
backupService.readPrimaryCard(iconScanRes = iconScanRes, cardId = scanResponse.card.cardId) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
modelScope.launch {
|
||||
cardRepository.startCardActivation(cardId = scanResponse.card.cardId)
|
||||
}
|
||||
params.multiWalletState.update {
|
||||
it.copy(
|
||||
currentScanResponse = scanResponse.copy(
|
||||
primaryCard = result.data,
|
||||
),
|
||||
)
|
||||
}
|
||||
modelScope.launch { onDone.emit(Unit) }
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
|
|
@ -17,7 +17,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
import com.tangem.features.onboarding.v2.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun Wallet1ScanPrimary(isRing: Boolean, onScanPrimaryClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
internal fun MultiWalletScanPrimary(isRing: Boolean, onScanPrimaryClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -66,7 +66,7 @@ internal fun Wallet1ScanPrimary(isRing: Boolean, onScanPrimaryClick: () -> Unit,
|
|||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
Wallet1ScanPrimary(
|
||||
MultiWalletScanPrimary(
|
||||
isRing = true,
|
||||
onScanPrimaryClick = {},
|
||||
)
|
||||
|
|
@ -9,7 +9,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.Mul
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.model.Wallet1ChooseOptionModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model.MultiWalletCreateWalletModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.model.MultiWalletFinalizeModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.Wallet1ScanPrimaryModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.MultiWalletScanPrimaryModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.MultiWalletSeedPhraseModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel
|
||||
import dagger.Binds
|
||||
|
|
@ -70,6 +70,6 @@ internal interface ModelModule {
|
|||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(Wallet1ScanPrimaryModel::class)
|
||||
fun provideModel8(model: Wallet1ScanPrimaryModel): Model
|
||||
@ClassKey(MultiWalletScanPrimaryModel::class)
|
||||
fun provideModel8(model: MultiWalletScanPrimaryModel): Model
|
||||
}
|
||||
|
|
@ -24,6 +24,9 @@ data class OnboardingMultiWalletState(
|
|||
* -> [Done]
|
||||
*
|
||||
* Wallet2/Ring
|
||||
*
|
||||
* ScanPrimary -> | (BackupService is cleared, no PrimaryCard)
|
||||
* |
|
||||
* CreateWallet -> SeedPhrase -> AddBackupDevice -> Finalize -> [Done]
|
||||
* |
|
||||
* -> AddBackupDevice -> Finalize -> [Done]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.pushnotifications.api
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
|
||||
interface PushNotificationsBottomSheetComponent : ComposableBottomSheetComponent {
|
||||
|
||||
interface Factory : ComponentFactory<PushNotificationsParams, PushNotificationsBottomSheetComponent>
|
||||
}
|
||||
|
|
@ -1,19 +1,9 @@
|
|||
package com.tangem.features.pushnotifications.api
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface PushNotificationsComponent : ComposableContentComponent {
|
||||
|
||||
interface Factory : ComponentFactory<Params, PushNotificationsComponent>
|
||||
|
||||
interface ModelCallbacks {
|
||||
fun onResult()
|
||||
}
|
||||
|
||||
sealed class Params {
|
||||
data class Callbacks(val callbacks: ModelCallbacks) : Params()
|
||||
data class Route(val route: AppRoute) : Params()
|
||||
}
|
||||
interface Factory : ComponentFactory<PushNotificationsParams, PushNotificationsComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.pushnotifications.api
|
||||
|
||||
interface PushNotificationsModelCallbacks {
|
||||
fun onAllowSystemPermission()
|
||||
fun onDenySystemPermission()
|
||||
fun onDismiss()
|
||||
}
|
||||
|
||||
class PushNotificationsModelCallbacksStub(
|
||||
val onAllowSystemPermission: () -> Unit = {},
|
||||
val onDenySystemPermission: () -> Unit = {},
|
||||
val onDismiss: () -> Unit = {},
|
||||
) : PushNotificationsModelCallbacks {
|
||||
override fun onAllowSystemPermission() = onAllowSystemPermission.invoke()
|
||||
override fun onDenySystemPermission() = onDenySystemPermission.invoke()
|
||||
override fun onDismiss() = onDismiss.invoke()
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.features.pushnotifications.api
|
||||
|
||||
data class PushNotificationsParams(
|
||||
val isBottomSheet: Boolean = false,
|
||||
val modelCallbacks: PushNotificationsModelCallbacks,
|
||||
)
|
||||
|
|
@ -16,6 +16,8 @@ dependencies {
|
|||
implementation(deps.androidx.activity.compose)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.accompanist.permission)
|
||||
|
|
@ -39,6 +41,7 @@ dependencies {
|
|||
/** Domain module */
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.notifications.toggles)
|
||||
implementation(projects.domain.notifications)
|
||||
|
||||
/** Feature modules */
|
||||
implementation(projects.features.pushNotifications.api)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.features.pushnotifications.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||
import com.tangem.features.pushnotifications.impl.model.PushNotificationsModel
|
||||
import com.tangem.features.pushnotifications.impl.presentation.ui.PushNotificationsBottomSheet
|
||||
import com.tangem.features.pushnotifications.impl.presentation.ui.PushNotificationsContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultPushNotificationsBottomSheetComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: PushNotificationsParams,
|
||||
) : PushNotificationsBottomSheetComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: PushNotificationsModel = getOrCreateModel(params)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : PushNotificationsBottomSheetComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: PushNotificationsParams,
|
||||
): DefaultPushNotificationsBottomSheetComponent
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
params.modelCallbacks.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.state.collectAsState()
|
||||
val bottomSheetConfig = remember(key1 = this) {
|
||||
TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
)
|
||||
}
|
||||
|
||||
PushNotificationsBottomSheet(
|
||||
config = bottomSheetConfig,
|
||||
) {
|
||||
PushNotificationsContent(
|
||||
onAllowClick = model::onAllowClick,
|
||||
onLaterClick = model::onLaterClick,
|
||||
onAllowPermission = model::onAllowPermission,
|
||||
onDenyPermission = model::onDenyPermission,
|
||||
showNotificationsInfo = state.showInfoAboutNotifications,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
|
||||
import com.tangem.core.ui.utils.findActivity
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||
import com.tangem.features.pushnotifications.impl.model.PushNotificationsModel
|
||||
import com.tangem.features.pushnotifications.impl.presentation.ui.PushNotificationsScreen
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -19,7 +20,7 @@ import dagger.assisted.AssistedInject
|
|||
|
||||
internal class DefaultPushNotificationsComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: PushNotificationsComponent.Params,
|
||||
@Assisted private val params: PushNotificationsParams,
|
||||
) : PushNotificationsComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: PushNotificationsModel = getOrCreateModel(params)
|
||||
|
|
@ -31,8 +32,8 @@ internal class DefaultPushNotificationsComponent @AssistedInject constructor(
|
|||
BackHandler(onBack = { activity.finish() })
|
||||
NavigationBar3ButtonsScrim()
|
||||
PushNotificationsScreen(
|
||||
onRequest = model::onRequest,
|
||||
onNeverRequest = model::onNeverRequest,
|
||||
onAllowClick = model::onAllowClick,
|
||||
onLaterClick = model::onLaterClick,
|
||||
onAllowPermission = model::onAllowPermission,
|
||||
onDenyPermission = model::onDenyPermission,
|
||||
showNotificationsInfo = state.showInfoAboutNotifications,
|
||||
|
|
@ -43,7 +44,7 @@ internal class DefaultPushNotificationsComponent @AssistedInject constructor(
|
|||
interface Factory : PushNotificationsComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: PushNotificationsComponent.Params,
|
||||
params: PushNotificationsParams,
|
||||
): DefaultPushNotificationsComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.features.pushnotifications.impl.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.features.pushnotifications.impl.DefaultPushNotificationsBottomSheetComponent
|
||||
import com.tangem.features.pushnotifications.impl.DefaultPushNotificationsComponent
|
||||
import com.tangem.features.pushnotifications.impl.model.PushNotificationsModel
|
||||
import dagger.Binds
|
||||
|
|
@ -15,6 +17,11 @@ import dagger.multibindings.IntoMap
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal interface PushNotificationsModule {
|
||||
|
||||
@Binds
|
||||
fun bindBottomSheetComponentFactory(
|
||||
impl: DefaultPushNotificationsBottomSheetComponent.Factory,
|
||||
): PushNotificationsBottomSheetComponent.Factory
|
||||
|
||||
@Binds
|
||||
fun bindComponentFactory(impl: DefaultPushNotificationsComponent.Factory): PushNotificationsComponent.Factory
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.features.pushnotifications.impl.model
|
||||
|
||||
internal interface PushNotificationsClickIntents {
|
||||
fun onRequest()
|
||||
fun onAllowClick()
|
||||
|
||||
fun onNeverRequest()
|
||||
fun onLaterClick()
|
||||
|
||||
fun onAllowPermission()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
package com.tangem.features.pushnotifications.impl.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||
import com.tangem.domain.settings.NeverRequestPermissionUseCase
|
||||
import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.features.pushnotifications.impl.presentation.state.PushNotificationsUM
|
||||
|
|
@ -20,9 +22,9 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
@Suppress("LongParameterList")
|
||||
internal class PushNotificationsModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -30,10 +32,11 @@ internal class PushNotificationsModel @Inject constructor(
|
|||
private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase,
|
||||
private val appRouter: AppRouter,
|
||||
private val analyticHandler: AnalyticsEventHandler,
|
||||
notificationsFeatureToggles: NotificationsFeatureToggles,
|
||||
private val notificationsFeatureToggles: NotificationsFeatureToggles,
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
) : Model(), PushNotificationsClickIntents {
|
||||
|
||||
private val params: PushNotificationsComponent.Params = paramsContainer.require()
|
||||
val params: PushNotificationsParams = paramsContainer.require()
|
||||
|
||||
private val _state = MutableStateFlow(
|
||||
PushNotificationsUM(
|
||||
|
|
@ -43,20 +46,33 @@ internal class PushNotificationsModel @Inject constructor(
|
|||
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
override fun onRequest() {
|
||||
override fun onAllowClick() {
|
||||
if (notificationsFeatureToggles.isNotificationsEnabled) {
|
||||
modelScope.launch {
|
||||
notificationsRepository.setUserAllowToSubscribeOnPushNotifications(true)
|
||||
}
|
||||
}
|
||||
analyticHandler.send(
|
||||
PushNotificationAnalyticEvents.ButtonAllow(AnalyticsParam.ScreensSources.Stories),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onNeverRequest() {
|
||||
override fun onLaterClick() {
|
||||
if (notificationsFeatureToggles.isNotificationsEnabled) {
|
||||
modelScope.launch {
|
||||
notificationsRepository.setUserAllowToSubscribeOnPushNotifications(false)
|
||||
}
|
||||
}
|
||||
analyticHandler.send(
|
||||
PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Stories),
|
||||
)
|
||||
modelScope.launch {
|
||||
neverRequestPermissionUseCase(PUSH_PERMISSION)
|
||||
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
|
||||
onResult()
|
||||
params.modelCallbacks.onDenySystemPermission()
|
||||
if (!params.isBottomSheet) {
|
||||
appRouter.push(AppRoute.Home)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +83,10 @@ internal class PushNotificationsModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
neverRequestPermissionUseCase(PUSH_PERMISSION)
|
||||
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
|
||||
onResult()
|
||||
params.modelCallbacks.onAllowSystemPermission()
|
||||
if (!params.isBottomSheet) {
|
||||
appRouter.push(AppRoute.Home)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,17 +97,9 @@ internal class PushNotificationsModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
neverRequestPermissionUseCase(PUSH_PERMISSION)
|
||||
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
|
||||
onResult()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onResult() {
|
||||
when (params) {
|
||||
is PushNotificationsComponent.Params.Callbacks -> {
|
||||
params.callbacks.onResult()
|
||||
}
|
||||
is PushNotificationsComponent.Params.Route -> {
|
||||
appRouter.push(params.route)
|
||||
params.modelCallbacks.onDenySystemPermission()
|
||||
if (!params.isBottomSheet) {
|
||||
appRouter.push(AppRoute.Home)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.features.pushnotifications.impl.presentation.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH28
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
|
||||
import com.tangem.core.ui.components.showcase.ShowcaseButtons
|
||||
import com.tangem.core.ui.components.showcase.ShowcaseContent
|
||||
import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.requestPermission
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun PushNotificationsBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) {
|
||||
TangemModalBottomSheet<TangemBottomSheetConfigContent>(
|
||||
config = config,
|
||||
title = {
|
||||
TangemModalBottomSheetTitle(
|
||||
endIconRes = R.drawable.ic_close_24,
|
||||
onEndClick = config.onDismissRequest,
|
||||
)
|
||||
},
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun PushNotificationsContent(
|
||||
onAllowClick: () -> Unit,
|
||||
onLaterClick: () -> Unit,
|
||||
onAllowPermission: () -> Unit,
|
||||
onDenyPermission: () -> Unit,
|
||||
showNotificationsInfo: Boolean,
|
||||
) {
|
||||
val requestPushPermission = requestPermission(
|
||||
onAllow = onAllowPermission,
|
||||
onDeny = onDenyPermission,
|
||||
permission = PUSH_PERMISSION,
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
ShowcaseContent(
|
||||
headerIconRes = R.drawable.ic_notifications_unread_24,
|
||||
headerText = resourceReference(R.string.user_push_notification_agreement_header),
|
||||
showcaseItems = persistentListOf(
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_rocket_launch_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_one),
|
||||
),
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_storefront_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_two),
|
||||
),
|
||||
).let { baseItems ->
|
||||
if (showNotificationsInfo) {
|
||||
baseItems.add(
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_notifications_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_three),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
baseItems
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing40),
|
||||
)
|
||||
SpacerH28()
|
||||
ShowcaseButtons(
|
||||
primaryButtonText = resourceReference(R.string.common_allow),
|
||||
onPrimaryClick = {
|
||||
onAllowClick()
|
||||
requestPushPermission()
|
||||
},
|
||||
secondaryButtonText = resourceReference(R.string.common_later),
|
||||
onSecondaryClick = {
|
||||
onLaterClick()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview_PushNotificationsBottomSheet() {
|
||||
TangemThemePreview {
|
||||
PushNotificationsBottomSheet(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = {},
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
) {
|
||||
PushNotificationsContent(
|
||||
onAllowClick = {},
|
||||
onLaterClick = {},
|
||||
onAllowPermission = {},
|
||||
onDenyPermission = {},
|
||||
showNotificationsInfo = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +1,29 @@
|
|||
package com.tangem.features.pushnotifications.impl.presentation.ui
|
||||
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.showcase.Showcase
|
||||
import com.tangem.core.ui.components.showcase.model.ShowcaseButtonModel
|
||||
import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.utils.requestPushPermission
|
||||
import com.tangem.core.ui.utils.requestPermission
|
||||
import com.tangem.feature.pushnotifications.impl.R
|
||||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun PushNotificationsScreen(
|
||||
onRequest: () -> Unit,
|
||||
onNeverRequest: () -> Unit,
|
||||
onAllowClick: () -> Unit,
|
||||
onLaterClick: () -> Unit,
|
||||
onAllowPermission: () -> Unit,
|
||||
onDenyPermission: () -> Unit,
|
||||
showNotificationsInfo: Boolean,
|
||||
) {
|
||||
val requestPushPermission = requestPushPermission(
|
||||
val requestPushPermission = requestPermission(
|
||||
onAllow = onAllowPermission,
|
||||
onDeny = onDenyPermission,
|
||||
pushPermission = getPushPermissionOrNull(),
|
||||
permission = PUSH_PERMISSION,
|
||||
)
|
||||
|
||||
Showcase(
|
||||
|
|
@ -53,13 +53,15 @@ internal fun PushNotificationsScreen(
|
|||
primaryButton = ShowcaseButtonModel(
|
||||
buttonText = resourceReference(R.string.common_allow),
|
||||
onClick = {
|
||||
onRequest()
|
||||
onAllowClick()
|
||||
requestPushPermission()
|
||||
},
|
||||
),
|
||||
secondaryButton = ShowcaseButtonModel(
|
||||
buttonText = resourceReference(R.string.common_later),
|
||||
onClick = onNeverRequest,
|
||||
onClick = {
|
||||
onLaterClick()
|
||||
},
|
||||
),
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.nft.DisableWalletNFTUseCase
|
||||
import com.tangem.domain.nft.EnableWalletNFTUseCase
|
||||
import com.tangem.domain.nft.GetWalletNFTEnabledUseCase
|
||||
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||
import com.tangem.domain.settings.repositories.PermissionRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -74,6 +76,8 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
private val settingsManager: SettingsManager,
|
||||
private val permissionsRepository: PermissionRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
|
||||
) : Model() {
|
||||
|
||||
val params: WalletSettingsComponent.Params = paramsContainer.require()
|
||||
|
|
@ -97,6 +101,8 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
) { maybeWallet, nftEnabled, notificationsEnabled ->
|
||||
val wallet = maybeWallet.getOrNull() ?: return@combine
|
||||
val isRenameWalletAvailable = getShouldSaveUserWalletsSyncUseCase()
|
||||
val isNeedShowNotifications = notificationsToggles.isNotificationsEnabled &&
|
||||
!getIsHuaweiDeviceWithoutGoogleServicesUseCase()
|
||||
state.update { value ->
|
||||
value.copy(
|
||||
items = buildItems(
|
||||
|
|
@ -105,7 +111,7 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
isRenameWalletAvailable = isRenameWalletAvailable,
|
||||
isNFTEnabled = nftEnabled,
|
||||
isNotificationsEnabled = notificationsEnabled,
|
||||
isNotificationsFeatureEnabled = notificationsToggles.isNotificationsEnabled,
|
||||
isNotificationsFeatureEnabled = isNeedShowNotifications,
|
||||
isNotificationsPermissionGranted = isNotificationsPermissionGranted(),
|
||||
isHotWalletEnabled = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
),
|
||||
|
|
@ -231,6 +237,10 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
private fun onCheckedNotificationsChange(isChecked: Boolean) {
|
||||
modelScope.launch {
|
||||
if (isChecked) {
|
||||
if (getIsHuaweiDeviceWithoutGoogleServicesUseCase()) {
|
||||
showHuaweiDialog()
|
||||
return@launch
|
||||
}
|
||||
state.update { value ->
|
||||
value.copy(
|
||||
requestPushNotificationsPermission = true,
|
||||
|
|
@ -243,6 +253,21 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun showHuaweiDialog() {
|
||||
val message = DialogMessage(
|
||||
message = resourceReference(R.string.wallet_settings_push_notifications_huawei_warning),
|
||||
firstActionBuilder = {
|
||||
EventMessageAction(
|
||||
title = resourceReference(R.string.common_ok),
|
||||
warning = true,
|
||||
onClick = {},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
private fun onNotificationsDescriptionClick() {
|
||||
bottomSheetNavigation.activate(NetworksAvailableForNotificationBSConfig)
|
||||
}
|
||||
|
|
@ -261,6 +286,7 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
if (isGranted) {
|
||||
modelScope.launch {
|
||||
setNotificationsEnabledUseCase(params.userWalletId, true).onRight {
|
||||
notificationsRepository.setNotificationsWasEnabledAutomatically(params.userWalletId.stringValue)
|
||||
analyticsEventHandler.send(PushNotificationAnalyticEvents.NotificationsEnabled(true))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
|
|
@ -28,12 +29,12 @@ import com.tangem.core.ui.extensions.stringResourceSafe
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.WalletSettingsScreenTestTags
|
||||
import com.tangem.core.ui.utils.requestPushPermission
|
||||
import com.tangem.core.ui.utils.requestPermission
|
||||
import com.tangem.feature.walletsettings.component.preview.PreviewWalletSettingsComponent
|
||||
import com.tangem.feature.walletsettings.entity.WalletSettingsItemUM
|
||||
import com.tangem.feature.walletsettings.entity.WalletSettingsUM
|
||||
import com.tangem.feature.walletsettings.impl.R
|
||||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
|
||||
@Composable
|
||||
internal fun WalletSettingsScreen(
|
||||
|
|
@ -119,14 +120,16 @@ private fun Content(state: WalletSettingsUM, modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
val requestPushPermission = requestPushPermission(
|
||||
val requestPushPermission = requestPermission(
|
||||
onAllow = { state.onPushNotificationPermissionGranted(true) },
|
||||
onDeny = { state.onPushNotificationPermissionGranted(false) },
|
||||
pushPermission = getPushPermissionOrNull(),
|
||||
permission = PUSH_PERMISSION,
|
||||
)
|
||||
|
||||
if (state.requestPushNotificationsPermission) {
|
||||
requestPushPermission()
|
||||
LaunchedEffect(Unit) {
|
||||
requestPushPermission()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ dependencies {
|
|||
implementation(projects.domain.visa)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.notifications)
|
||||
implementation(projects.domain.notifications.toggles)
|
||||
|
||||
/** Feature Apis */
|
||||
implementation(projects.features.details.api)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
|
|||
import com.tangem.feature.walletsettings.component.RenameWalletComponent
|
||||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.markets.entry.MarketsEntryComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -32,6 +34,7 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
private val renameWalletComponentFactory: RenameWalletComponent.Factory,
|
||||
private val marketsEntryComponentFactory: MarketsEntryComponent.Factory,
|
||||
private val askBiometryComponentFactory: AskBiometryComponent.Factory,
|
||||
private val pushNotificationsBottomSheetComponent: PushNotificationsBottomSheetComponent.Factory,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: WalletModel = getOrCreateModel()
|
||||
|
|
@ -66,6 +69,13 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
WalletDialogConfig.AskForPushNotifications -> pushNotificationsBottomSheetComponent.create(
|
||||
context = childByContext(componentContext),
|
||||
params = PushNotificationsParams(
|
||||
isBottomSheet = true,
|
||||
modelCallbacks = model.askForPushNotificationsModelCallbacks,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,16 +11,18 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
|
||||
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
|
|
@ -28,7 +30,6 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWal
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.DemonstrateWalletsScrollPreview.Direction
|
||||
|
|
@ -37,8 +38,8 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.*
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
|
||||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
|
|
@ -79,11 +80,18 @@ internal class WalletModel @Inject constructor(
|
|||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
private val observeAndClearNFTCacheIfNeedUseCase: ObserveAndClearNFTCacheIfNeedUseCase,
|
||||
private val walletDeepLinkActionListener: WalletDeepLinkActionListener,
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase,
|
||||
private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase,
|
||||
private val notificationsFeatureToggles: NotificationsFeatureToggles,
|
||||
private val getIsBiometryIsEnabledUseCase: GetIsBiometricsEnabledUseCase,
|
||||
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
|
||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||
val innerWalletRouter: InnerWalletRouter,
|
||||
) : Model() {
|
||||
|
||||
val askBiometryModelCallbacks = AskBiometryModelCallbacks()
|
||||
val askForPushNotificationsModelCallbacks = AskForPushNotificationsCallbacks()
|
||||
val uiState: StateFlow<WalletScreenState> = stateHolder.uiState
|
||||
|
||||
private val walletsUpdateJobHolder = JobHolder()
|
||||
|
|
@ -106,6 +114,7 @@ internal class WalletModel @Inject constructor(
|
|||
subscribeOnSelectedWalletFlow()
|
||||
subscribeToScreenBackgroundState()
|
||||
subscribeOnPushNotificationsPermission()
|
||||
enableNotificationsIfNeeded()
|
||||
|
||||
clickIntents.initialize(innerWalletRouter, modelScope)
|
||||
}
|
||||
|
|
@ -201,20 +210,19 @@ internal class WalletModel @Inject constructor(
|
|||
|
||||
private fun subscribeOnPushNotificationsPermission() {
|
||||
modelScope.launch {
|
||||
val shouldRequestPush = shouldAskPermissionUseCase(PUSH_PERMISSION)
|
||||
val isPushPermissionAvailable = getPushPermissionOrNull() != null
|
||||
if (!shouldRequestPush || !isPushPermissionAvailable) return@launch
|
||||
val shouldAskPermission = shouldAskPermissionUseCase(PUSH_PERMISSION)
|
||||
val afterUpdate = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
|
||||
val isBiometricsEnabled = getIsBiometryIsEnabledUseCase()
|
||||
val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase()
|
||||
val shouldShowBottomSheet = shouldAskPermission || afterUpdate
|
||||
if (!isBiometricsEnabled) return@launch
|
||||
if (isHuaweiDevice) return@launch
|
||||
if (!shouldShowBottomSheet) return@launch
|
||||
|
||||
delay(timeMillis = 1_800)
|
||||
|
||||
stateHolder.showBottomSheet(
|
||||
content = PushNotificationsBottomSheetConfig(
|
||||
onRequest = clickIntents::onRequestPushPermission,
|
||||
onNeverRequest = { clickIntents.onNeverAskPushPermission(false) },
|
||||
onAllow = clickIntents::onAllowPushPermission,
|
||||
onDeny = clickIntents::onDenyPushPermission,
|
||||
),
|
||||
onDismiss = { clickIntents.onNeverAskPushPermission(true) },
|
||||
innerWalletRouter.dialogNavigation.activate(
|
||||
configuration = WalletDialogConfig.AskForPushNotifications,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -435,6 +443,8 @@ internal class WalletModel @Inject constructor(
|
|||
it.copy(selectedWalletIndex = action.selectedWalletIndex)
|
||||
}
|
||||
}
|
||||
|
||||
enableNotificationsIfNeeded()
|
||||
}
|
||||
|
||||
private fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) {
|
||||
|
|
@ -552,6 +562,26 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun enableNotificationsIfNeeded() {
|
||||
if (!notificationsFeatureToggles.isNotificationsEnabled) return
|
||||
modelScope.launch {
|
||||
val isUserAllowToEnableNotifications = notificationsRepository.isUserAllowToSubscribeOnPushNotifications()
|
||||
if (isUserAllowToEnableNotifications) {
|
||||
val alreadyEnabledWallets = notificationsRepository.getWalletAutomaticallyEnabledList().map {
|
||||
UserWalletId(it)
|
||||
}
|
||||
val walletsListWhichShouldBeEnabled = getWalletsListForEnablingUseCase(alreadyEnabledWallets)
|
||||
walletsListWhichShouldBeEnabled.forEach { userWalletId ->
|
||||
setNotificationsEnabledUseCase(userWalletId, true).onRight {
|
||||
notificationsRepository.setNotificationsWasEnabledAutomatically(userWalletId.stringValue)
|
||||
}.onLeft {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class AskBiometryModelCallbacks : AskBiometryComponent.ModelCallbacks {
|
||||
override fun onAllowed() {
|
||||
analyticsEventsHandler.send(MainScreenAnalyticsEvent.EnableBiometrics(AnalyticsParam.OnOffState.On))
|
||||
|
|
@ -564,6 +594,23 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
inner class AskForPushNotificationsCallbacks : PushNotificationsModelCallbacks {
|
||||
|
||||
override fun onAllowSystemPermission() {
|
||||
innerWalletRouter.dialogNavigation.dismiss()
|
||||
enableNotificationsIfNeeded()
|
||||
}
|
||||
|
||||
override fun onDenySystemPermission() {
|
||||
innerWalletRouter.dialogNavigation.dismiss()
|
||||
enableNotificationsIfNeeded()
|
||||
}
|
||||
|
||||
override fun onDismiss() {
|
||||
innerWalletRouter.dialogNavigation.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val REFRESH_WALLET_BACKGROUND_TIMER_MILLIS = 10000L
|
||||
const val EXPRESS_STATUS_UPDATE_DELAY = 10000L
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ internal enum class Wallet2CobrandImage(
|
|||
USA(
|
||||
cards2ResId = R.drawable.ill_usa_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_usa_card3_120_106,
|
||||
batchIds = setOf("AF91"),
|
||||
batchIds = setOf("AF91", "AF990017"),
|
||||
),
|
||||
|
||||
VeChain(
|
||||
|
|
|
|||
|
|
@ -16,4 +16,7 @@ internal sealed interface WalletDialogConfig {
|
|||
|
||||
@Serializable
|
||||
data object AskForBiometry : WalletDialogConfig
|
||||
|
||||
@Serializable
|
||||
data object AskForPushNotifications : WalletDialogConfig
|
||||
}
|
||||
|
|
@ -75,7 +75,6 @@ import com.tangem.feature.wallet.impl.R
|
|||
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.walletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.PushNotificationsBottomSheet
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
|
||||
|
|
@ -709,7 +708,6 @@ private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
|
|||
is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig)
|
||||
is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig)
|
||||
is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig)
|
||||
is PushNotificationsBottomSheetConfig -> PushNotificationsBottomSheet(config = bottomSheetConfig)
|
||||
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = bottomSheetConfig)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.showcase.ShowcaseContent
|
||||
import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.requestPushPermission
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig
|
||||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun PushNotificationsBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet<PushNotificationsBottomSheetConfig>(config = config) {
|
||||
PushNotificationsSheetContent(content = it, onDismiss = config.onDismissRequest)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetConfig, onDismiss: () -> Unit) {
|
||||
val requestPushPermission = requestPushPermission(
|
||||
pushPermission = getPushPermissionOrNull(),
|
||||
onAllow = {
|
||||
content.onAllow()
|
||||
onDismiss()
|
||||
},
|
||||
onDeny = {
|
||||
content.onDeny()
|
||||
onDismiss()
|
||||
},
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
ShowcaseContent(
|
||||
headerIconRes = R.drawable.ic_notifications_unread_24,
|
||||
headerText = resourceReference(R.string.user_push_notification_agreement_header),
|
||||
showcaseItems = persistentListOf(
|
||||
ShowcaseItemModel(
|
||||
iconRes = R.drawable.ic_rocket_launch_24,
|
||||
text = resourceReference(R.string.user_push_notification_agreement_argument_one),
|
||||
),
|
||||
ShowcaseItemModel(
|
||||
iconRes = R.drawable.ic_storefront_24,
|
||||
text = resourceReference(R.string.user_push_notification_agreement_argument_two),
|
||||
),
|
||||
),
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing40),
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
modifier = Modifier.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
top = TangemTheme.dimens.spacing40,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
SecondaryButton(
|
||||
text = stringResourceSafe(R.string.common_later),
|
||||
onClick = {
|
||||
content.onNeverRequest()
|
||||
onDismiss()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
PrimaryButton(
|
||||
text = stringResourceSafe(R.string.common_allow),
|
||||
onClick = {
|
||||
content.onRequest()
|
||||
requestPushPermission()
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PushNotificationsSheetContent_Preview() {
|
||||
TangemThemePreview {
|
||||
PushNotificationsSheetContent(
|
||||
PushNotificationsBottomSheetConfig(
|
||||
onRequest = {},
|
||||
onNeverRequest = {},
|
||||
onAllow = {},
|
||||
onDeny = {},
|
||||
),
|
||||
onDismiss = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
tangemBlockchainSdk = "releases-5.27.0-1126"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "releases-5.27.0-507"
|
||||
tangemCardSdk = "releases-5.27.0-510"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
tangemVico = "2.0.0-alpha.25-tangem12"
|
||||
#tangemVico = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
|
|
@ -1,169 +0,0 @@
|
|||
{
|
||||
"amplitudeApiKey": "place_your_key_here_if_needed",
|
||||
"appsFlyer": {
|
||||
"appsFlyerDevKey": "place_your_key_here_if_needed",
|
||||
"appsFlyerAppID": "place_your_key_here_if_needed"
|
||||
},
|
||||
"blockchairApiKeys": ["place_your_key_here_if_needed"],
|
||||
"blockchairAuthorizationToken": "",
|
||||
"blockcypherTokens": [
|
||||
"place_your_key_here_if_needed",
|
||||
"place_your_key_here_if_needed",
|
||||
"place_your_key_here_if_needed"
|
||||
],
|
||||
"bscQuiknodeApiKey": "",
|
||||
"bscQuiknodeSubdomain": "place_your_data_here",
|
||||
"getBlockAccessTokens": {
|
||||
"avalanche": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"ethereum": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"ethereumClassic": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"fantom": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"rsk": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"bsc": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"polygon": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"xdai": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"cronos": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"solana": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"ton": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"tron": {
|
||||
"rest": "place_your_key_here_if_needed"
|
||||
},
|
||||
"cosmos-hub": {
|
||||
"rest": "place_your_key_here_if_needed"
|
||||
},
|
||||
"near": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"xrp": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"cardano": {
|
||||
"rosetta": "place_your_key_here_if_needed"
|
||||
},
|
||||
"dogecoin": {
|
||||
"blockBookRest": "place_your_key_here_if_needed",
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"litecoin": {
|
||||
"blockBookRest": "place_your_key_here_if_needed",
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"dash": {
|
||||
"blockBookRest": "place_your_key_here_if_needed",
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"bitcoin": {
|
||||
"blockBookRest": "place_your_key_here_if_needed",
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"aptos": {
|
||||
"rest": "place_your_key_here_if_needed"
|
||||
},
|
||||
"algorand": {
|
||||
"rest": "place_your_key_here_if_needed"
|
||||
},
|
||||
"polygon-zkevm": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"zksync": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"base": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"blast": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"filecoin": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"arbitrum-one": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"bitcoinCash": {
|
||||
"blockBookRest": "place_your_key_here_if_needed",
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"kusama": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"moonbeam": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"optimism": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"polkadot": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"shibarium": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"sui": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"telos": {
|
||||
"jsonRpc": "place_your_key_here_if_needed"
|
||||
},
|
||||
"tezos": {
|
||||
"rest": "place_your_key_here_if_needed"
|
||||
}
|
||||
},
|
||||
"kaspaSecondaryApiUrl": "place_your_kaspa_api_here",
|
||||
"infuraProjectId": "place_your_key_here_if_needed",
|
||||
"mercuryoSecret": "place_your_key_here_if_needed",
|
||||
"mercuryoWidgetId": "place_your_key_here_if_needed",
|
||||
"moonPayApiKey": "place_your_key_here_if_needed",
|
||||
"moonPayApiSecretKey": "place_your_key_here_if_needed",
|
||||
"nowNodesApiKey": "place_your_key_here_if_needed",
|
||||
"tonCenterApiKey": {
|
||||
"mainnet": "place_your_key_here_if_needed",
|
||||
"testnet": "place_your_key_here_if_needed"
|
||||
},
|
||||
"quiknodeApiKey": "",
|
||||
"quiknodeSubdomain": "place_your_data_here_if_needed",
|
||||
"tronGridApiKey": "place_your_key_here_if_needed",
|
||||
"walletConnectProjectId": "place_your_key_here_if_needed",
|
||||
"chiaFireAcademyApiKey": "place_your_key_here_if_needed",
|
||||
"chiaTangemApiKey": "place_your_key_here_if_needed",
|
||||
"express": {
|
||||
"apiKey": "place_your_key_here_if_needed",
|
||||
"signVerifierPublicKey": "place_your_key_here_if_needed"
|
||||
},
|
||||
"devExpress": {
|
||||
"apiKey": "place_your_key_here_if_needed",
|
||||
"signVerifierPublicKey": "place_your_key_here_if_needed"
|
||||
},
|
||||
"hederaArkhiaKey": "place_your_key_here_if_needed",
|
||||
"polygonScanApiKey": "place_your_key_here_if_needed",
|
||||
"koinosProApiKey": "place_your_key_here_if_needed",
|
||||
"stakeKitApiKey": "place_your_key_here_if_needed",
|
||||
"bittensorDwellirKey": "place_your_key_here_if_needed",
|
||||
"bittensorOnfinalityKey": "place_your_key_here_if_needed",
|
||||
"alephiumTangemApiKey": "place_your_key_here_if_needed",
|
||||
"moralisApiKey": "place_your_key_here_if_needed",
|
||||
"nftScanApiKey": "place_your_key_here_if_needed",
|
||||
"blockaidApiKey": "place_your_key_here_if_needed"
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 35539d4e63d1f3ec4ec4f625f7875684ed5033c4
|
||||
Subproject commit bc4cd430853ca794614b8d5163c9b28b9ca26112
|
||||
Loading…
Add table
Add a link
Reference in a new issue