Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-30 18:22:42 +05:00
parent c55274f40f
commit efcb0dea79
20 changed files with 477 additions and 17 deletions

View file

@ -37,6 +37,8 @@ dependencies {
implementation(projects.domain.demo)
implementation(projects.domain.nft)
implementation(projects.domain.notifications.toggles)
implementation(projects.domain.notifications.models)
implementation(projects.domain.notifications)
/* AndroidX */
implementation(deps.androidx.fragment.ktx)

View file

@ -0,0 +1,40 @@
package com.tangem.feature.walletsettings.component.impl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.feature.walletsettings.component.NetworksAvailableForNotificationsComponent
import com.tangem.feature.walletsettings.component.impl.model.NetworksAvailableForNotificationsModel
import com.tangem.feature.walletsettings.ui.NetworksAvailableForNotificationsListBS
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultNetworksAvailableForNotificationsComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: NetworksAvailableForNotificationsComponent.Params,
) : NetworksAvailableForNotificationsComponent, AppComponentContext by context {
private val model: NetworksAvailableForNotificationsModel = getOrCreateModel(params)
override fun dismiss() {
params.onDismiss()
}
@Composable
override fun BottomSheet() {
val state by model.state.collectAsStateWithLifecycle()
NetworksAvailableForNotificationsListBS(state = state, onBack = ::dismiss, onDismiss = ::dismiss)
}
@AssistedFactory
interface Factory : NetworksAvailableForNotificationsComponent.Factory {
override fun create(
context: AppComponentContext,
params: NetworksAvailableForNotificationsComponent.Params,
): DefaultNetworksAvailableForNotificationsComponent
}
}

View file

@ -11,10 +11,13 @@ import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableDialogComponent
import com.tangem.feature.walletsettings.component.NetworksAvailableForNotificationsComponent
import com.tangem.feature.walletsettings.component.RenameWalletComponent
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.feature.walletsettings.entity.DialogConfig
import com.tangem.feature.walletsettings.entity.NetworksAvailableForNotificationBSConfig
import com.tangem.feature.walletsettings.model.WalletSettingsModel
import com.tangem.feature.walletsettings.ui.WalletSettingsScreen
import dagger.assisted.Assisted
@ -25,14 +28,24 @@ internal class DefaultWalletSettingsComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted params: WalletSettingsComponent.Params,
private val renameWalletComponentFactory: RenameWalletComponent.Factory,
private val networksAvailableForNotificationsComponent: NetworksAvailableForNotificationsComponent.Factory,
) : WalletSettingsComponent, AppComponentContext by context {
private val model: WalletSettingsModel = getOrCreateModel(params)
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = null,
handleBackButton = false,
key = "bottomSheetSlot",
childFactory = ::bottomSheetChild,
)
private val dialog = childSlot(
source = model.dialogNavigation,
serializer = DialogConfig.serializer(),
handleBackButton = true,
key = "dialogSlot",
childFactory = ::dialogChild,
)
@ -40,12 +53,15 @@ internal class DefaultWalletSettingsComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
val dialog by dialog.subscribeAsState()
val bottomSheet by bottomSheetSlot.subscribeAsState()
WalletSettingsScreen(
modifier = modifier,
state = state,
dialog = { dialog.child?.instance?.Dialog() },
)
bottomSheet.child?.instance?.BottomSheet()
}
private fun dialogChild(
@ -64,6 +80,18 @@ internal class DefaultWalletSettingsComponent @AssistedInject constructor(
}
}
@Suppress("UnusedPrivateMember")
private fun bottomSheetChild(
config: NetworksAvailableForNotificationBSConfig,
componentContext: ComponentContext,
): ComposableBottomSheetComponent = networksAvailableForNotificationsComponent.create(
context = childByContext(componentContext),
params = NetworksAvailableForNotificationsComponent.Params(
onDismiss = model
.bottomSheetNavigation::dismiss,
),
)
@AssistedFactory
interface Factory : WalletSettingsComponent.Factory {
override fun create(

View file

@ -0,0 +1,40 @@
package com.tangem.feature.walletsettings.component.impl.model
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.domain.notifications.GetNetworksAvailableForNotificationsUseCase
import com.tangem.feature.walletsettings.ui.state.NetworksAvailableForNotificationsUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@ModelScoped
internal class NetworksAvailableForNotificationsModel @Inject constructor(
private val getNetworksAvailableForNotificationsUseCase: GetNetworksAvailableForNotificationsUseCase,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
val state: StateFlow<NetworksAvailableForNotificationsUM> get() = _state
private val _state = MutableStateFlow(
value = NetworksAvailableForNotificationsUM(
networks = persistentListOf(),
isLoading = true,
),
)
init {
modelScope.launch {
getNetworksAvailableForNotificationsUseCase().onRight { networks ->
_state.update {
it.copy(networks = networks.toImmutableList(), isLoading = false)
}
}
}
}
}

View file

@ -2,8 +2,10 @@ package com.tangem.feature.walletsettings.di
import com.tangem.feature.walletsettings.component.RenameWalletComponent
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.feature.walletsettings.component.impl.DefaultNetworksAvailableForNotificationsComponent
import com.tangem.feature.walletsettings.component.impl.DefaultRenameWalletComponent
import com.tangem.feature.walletsettings.component.impl.DefaultWalletSettingsComponent
import com.tangem.feature.walletsettings.component.NetworksAvailableForNotificationsComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@ -23,4 +25,10 @@ internal interface WalletSettingsComponentModule {
@Binds
@Singleton
fun bindRenameWalletComponentFactory(factory: DefaultRenameWalletComponent.Factory): RenameWalletComponent.Factory
@Binds
@Singleton
fun bindNetworksComponentFactory(
factory: DefaultNetworksAvailableForNotificationsComponent.Factory,
): NetworksAvailableForNotificationsComponent.Factory
}

View file

@ -2,6 +2,7 @@ package com.tangem.feature.walletsettings.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.feature.walletsettings.component.impl.model.NetworksAvailableForNotificationsModel
import com.tangem.feature.walletsettings.component.impl.model.RenameWalletModel
import com.tangem.feature.walletsettings.model.WalletSettingsModel
import dagger.Binds
@ -23,4 +24,9 @@ internal interface WalletSettingsModelModule {
@IntoMap
@ClassKey(RenameWalletModel::class)
fun bindRenameWalletModel(model: RenameWalletModel): Model
@Binds
@IntoMap
@ClassKey(NetworksAvailableForNotificationsModel::class)
fun bindNetworksAvailableForNotificationsModel(model: NetworksAvailableForNotificationsModel): Model
}

View file

@ -0,0 +1,6 @@
package com.tangem.feature.walletsettings.entity
import kotlinx.serialization.Serializable
@Serializable
internal object NetworksAvailableForNotificationBSConfig

View file

@ -13,9 +13,7 @@ import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.settings.SettingsManager
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.message.*
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.models.scan.CardDTO
@ -31,6 +29,7 @@ import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.walletsettings.analytics.Settings
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.feature.walletsettings.entity.DialogConfig
import com.tangem.feature.walletsettings.entity.NetworksAvailableForNotificationBSConfig
import com.tangem.feature.walletsettings.entity.WalletSettingsItemUM
import com.tangem.feature.walletsettings.entity.WalletSettingsUM
import com.tangem.feature.walletsettings.impl.R
@ -70,6 +69,7 @@ internal class WalletSettingsModel @Inject constructor(
val params: WalletSettingsComponent.Params = paramsContainer.require()
val dialogNavigation = SlotNavigation<DialogConfig>()
val bottomSheetNavigation: SlotNavigation<NetworksAvailableForNotificationBSConfig> = SlotNavigation()
val state: MutableStateFlow<WalletSettingsUM> = MutableStateFlow(
value = WalletSettingsUM(
@ -215,7 +215,7 @@ internal class WalletSettingsModel @Inject constructor(
}
private fun onNotificationsDescriptionClick() {
// TODO [REDACTED_JIRA]
bottomSheetNavigation.activate(NetworksAvailableForNotificationBSConfig)
}
private fun openPushSystemSettings() {

View file

@ -0,0 +1,171 @@
package com.tangem.feature.walletsettings.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter
import com.tangem.core.ui.components.items.NetworkNameAndSymbolItem
import com.tangem.core.ui.components.items.NetworkNameAndSymbolItemShimmer
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
import com.tangem.feature.walletsettings.ui.state.NetworksAvailableForNotificationsUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun NetworksAvailableForNotificationsListBS(
state: NetworksAvailableForNotificationsUM,
onBack: () -> Unit,
onDismiss: () -> Unit,
) {
TangemModalBottomSheetWithFooter<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onDismiss,
content = TangemBottomSheetConfigContent.Empty,
),
containerColor = TangemTheme.colors.background.tertiary,
onBack = onBack,
title = {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = onDismiss,
)
},
content = {
NetworksAvailableForNotificationsListBottomSheetContent(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
state = state,
)
},
footer = {
SecondaryButton(
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
text = stringResourceSafe(R.string.balance_hidden_got_it_button),
onClick = onDismiss,
)
},
)
}
@Composable
private fun NetworksAvailableForNotificationsListBottomSheetContent(
state: NetworksAvailableForNotificationsUM,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.background(TangemTheme.colors.background.primary),
) {
Header()
SpacerH24()
Networks(networks = state.networks, isLoading = state.isLoading)
}
}
@Composable
private fun ColumnScope.Header() {
Icon(
painter = painterResource(id = R.drawable.ic_notification_48),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.size(TangemTheme.dimens.size56),
)
Text(
text = stringResourceSafe(R.string.push_transactions_notifications_title),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(
start = TangemTheme.dimens.spacing34,
end = TangemTheme.dimens.spacing34,
top = TangemTheme.dimens.spacing28,
),
)
Text(
text = stringResourceSafe(R.string.push_transactions_notifications_description),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(
start = TangemTheme.dimens.spacing34,
end = TangemTheme.dimens.spacing34,
top = TangemTheme.dimens.spacing8,
),
)
}
@Composable
private fun Networks(
networks: ImmutableList<NotificationsEligibleNetwork>,
modifier: Modifier = Modifier,
isLoading: Boolean = false,
) {
Column(modifier = modifier) {
Text(
modifier = Modifier.padding(top = 12.dp, bottom = 4.dp, start = 12.dp, end = 12.dp),
text = stringResourceSafe(R.string.common_supported_networks),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
if (isLoading) {
repeat(SHIMMERS_COUNT) {
NetworkNameAndSymbolItemShimmer()
}
} else {
networks.fastForEach { network ->
key(network.id) {
NetworkNameAndSymbolItem(
icon = network.icon,
name = network.name,
symbol = network.symbol,
)
}
}
}
}
}
private const val SHIMMERS_COUNT = 10
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_CustomTokenNetworkSelectorContent() {
TangemThemePreview {
NetworksAvailableForNotificationsListBS(
state = NetworksAvailableForNotificationsUM(
networks = persistentListOf(),
isLoading = true,
),
onDismiss = {},
onBack = {},
)
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.feature.walletsettings.ui.state
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
import kotlinx.collections.immutable.ImmutableList
data class NetworksAvailableForNotificationsUM(
val networks: ImmutableList<NotificationsEligibleNetwork>,
val isLoading: Boolean,
)