Skip to content

Commit

Permalink
Merge pull request #1321 from radixdlt/sp/apply-shield-ABW-4055
Browse files Browse the repository at this point in the history
[ABW-4055] - Apply security shield to entities
  • Loading branch information
sergiupuhalschi-rdx authored Feb 5, 2025
2 parents 8a07034 + 784ce3a commit 4935bfb
Show file tree
Hide file tree
Showing 34 changed files with 1,551 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.babylon.wallet.android.domain.usecases.assets

import com.babylon.wallet.android.data.repository.state.StateRepository
import com.radixdlt.sargon.Decimal192
import com.radixdlt.sargon.extensions.orZero
import com.radixdlt.sargon.extensions.sumOf
import rdx.works.core.sargon.activeAccountsOnCurrentNetwork
import rdx.works.profile.domain.GetProfileUseCase
import javax.inject.Inject

class GetAllAccountsXrdBalanceUseCase @Inject constructor(
private val getProfileUseCase: GetProfileUseCase,
private val stateRepository: StateRepository
) {

suspend operator fun invoke(): Decimal192 {
val allAccounts = getProfileUseCase().activeAccountsOnCurrentNetwork
return stateRepository.getOwnedXRD(accounts = allAccounts)
.getOrNull()?.values?.sumOf { it }.orZero()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.babylon.wallet.android.presentation.settings.securitycenter.applyshield

import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptionsBuilder
import androidx.navigation.navigation
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.accounts.ROUTE_CHOOSE_ACCOUNTS
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.accounts.chooseAccounts
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.apply.applyShield
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.personas.choosePersonas
import com.radixdlt.sargon.SecurityStructureId

private const val DESTINATION_APPLY_SHIELD_GRAPH = "apply_shield_graph"
private const val ARG_SECURITY_STRUCTURE_ID = "arg_security_structure_id"

const val ROUTE_APPLY_SHIELD_GRAPH = "$DESTINATION_APPLY_SHIELD_GRAPH/{$ARG_SECURITY_STRUCTURE_ID}"

internal class ApplyShieldArgs(
val securityStructureId: SecurityStructureId
) {

constructor(savedStateHandle: SavedStateHandle) : this(
securityStructureId = SecurityStructureId.fromString(requireNotNull(savedStateHandle.get<String>(ARG_SECURITY_STRUCTURE_ID)))
)
}

fun NavController.applyShieldNavGraph(
id: SecurityStructureId,
navOptionsBuilder: NavOptionsBuilder.() -> Unit = {}
) {
navigate("$DESTINATION_APPLY_SHIELD_GRAPH/$id", navOptionsBuilder)
}

fun NavGraphBuilder.applyShieldNavGraph(
navController: NavController
) {
navigation(
startDestination = ROUTE_CHOOSE_ACCOUNTS,
route = ROUTE_APPLY_SHIELD_GRAPH,
) {
chooseAccounts(navController)

choosePersonas(navController)

applyShield(navController)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.accounts

import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.runtime.remember
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.ROUTE_APPLY_SHIELD_GRAPH
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.common.ApplyShieldSharedViewModel
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.personas.choosePersonas

const val ROUTE_CHOOSE_ACCOUNTS = "choose_accounts"

fun NavController.chooseAccounts() {
navigate(ROUTE_CHOOSE_ACCOUNTS)
}

fun NavGraphBuilder.chooseAccounts(
navController: NavController
) {
composable(
route = ROUTE_CHOOSE_ACCOUNTS,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left) },
exitTransition = { ExitTransition.None },
popEnterTransition = { EnterTransition.None },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Right) }
) {
val parentEntry = remember(it) { navController.getBackStackEntry(ROUTE_APPLY_SHIELD_GRAPH) }
val sharedVM = hiltViewModel<ApplyShieldSharedViewModel>(parentEntry)

ChooseAccountsScreen(
viewModel = hiltViewModel(),
onDismiss = { navController.popBackStack() },
onSelected = { addresses ->
sharedVM.onAccountsSelected(addresses)
navController.choosePersonas(mustSelectAtLeastOne = sharedVM.mustSelectAtLeastOnePersona)
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.accounts

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.babylon.wallet.android.R
import com.babylon.wallet.android.designsystem.theme.RadixTheme
import com.babylon.wallet.android.designsystem.theme.gradient
import com.babylon.wallet.android.domain.model.Selectable
import com.babylon.wallet.android.presentation.dapp.authorized.account.AccountSelectionCard
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.common.composables.ChooseEntityContent
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.common.models.ChooseEntityEvent
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.common.models.ChooseEntityUiState
import com.babylon.wallet.android.presentation.ui.RadixWalletPreviewTheme
import com.radixdlt.sargon.Account
import com.radixdlt.sargon.AddressOfAccountOrPersona
import com.radixdlt.sargon.annotation.UsesSampleValues
import com.radixdlt.sargon.samples.sampleMainnet
import com.radixdlt.sargon.samples.sampleStokenet

@Composable
fun ChooseAccountsScreen(
modifier: Modifier = Modifier,
viewModel: ChooseAccountsViewModel,
onDismiss: () -> Unit,
onSelected: (List<AddressOfAccountOrPersona>) -> Unit
) {
val state by viewModel.state.collectAsStateWithLifecycle()

ChooseAccountsContent(
modifier = modifier,
state = state,
onDismiss = onDismiss,
onSelectAllToggleClick = viewModel::onSelectAllToggleClick,
onSelectAccount = viewModel::onSelectItem,
onContinueClick = viewModel::onContinueClick,
onSkipClick = viewModel::onSkipClick
)

LaunchedEffect(Unit) {
viewModel.oneOffEvent.collect { event ->
when (event) {
is ChooseEntityEvent.EntitiesSelected -> onSelected(event.addresses)
}
}
}
}

@Composable
private fun ChooseAccountsContent(
modifier: Modifier = Modifier,
state: ChooseEntityUiState<Account>,
onDismiss: () -> Unit,
onSelectAllToggleClick: () -> Unit,
onSelectAccount: (Account) -> Unit,
onContinueClick: () -> Unit,
onSkipClick: () -> Unit
) {
ChooseEntityContent(
modifier = modifier.fillMaxSize(),
title = stringResource(id = R.string.shieldWizardApplyShield_chooseAccounts_title),
subtitle = stringResource(id = R.string.shieldWizardApplyShield_chooseAccounts_subtitle),
isButtonEnabled = state.isButtonEnabled,
isSelectAllVisible = !state.isEmpty,
selectedAll = state.selectedAll,
hasSkipButton = true,
onContinueClick = onContinueClick,
onDismiss = onDismiss,
onSelectAllToggleClick = onSelectAllToggleClick,
onSkipClick = onSkipClick
) {
items(state.items) { account ->
AccountSelectionCard(
modifier = Modifier
.padding(
horizontal = RadixTheme.dimensions.paddingLarge,
vertical = RadixTheme.dimensions.paddingXSmall
)
.background(
brush = account.data.appearanceId.gradient(),
shape = RadixTheme.shapes.roundedRectSmall
)
.clickable { onSelectAccount(account.data) },
accountName = account.data.displayName.value,
address = account.data.address,
checked = account.selected,
isSingleChoice = false,
radioButtonClicked = { onSelectAccount(account.data) }
)
}
}
}

@Composable
@Preview
@UsesSampleValues
private fun ChooseAccountsPreview(
@PreviewParameter(ChooseAccountsPreviewProvider::class) state: ChooseEntityUiState<Account>
) {
RadixWalletPreviewTheme {
ChooseAccountsContent(
state = state,
onDismiss = {},
onSelectAllToggleClick = {},
onSelectAccount = {},
onContinueClick = {},
onSkipClick = {}
)
}
}

@UsesSampleValues
class ChooseAccountsPreviewProvider : PreviewParameterProvider<ChooseEntityUiState<Account>> {

override val values: Sequence<ChooseEntityUiState<Account>>
get() = sequenceOf(
ChooseEntityUiState(
items = listOf(
Selectable(Account.sampleMainnet(), true),
Selectable(Account.sampleStokenet(), false)
)
),
ChooseEntityUiState(
items = listOf(
Selectable(Account.sampleMainnet(), true),
Selectable(Account.sampleStokenet(), true)
)
),
ChooseEntityUiState()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.accounts

import androidx.lifecycle.viewModelScope
import com.babylon.wallet.android.domain.model.Selectable
import com.babylon.wallet.android.presentation.common.OneOffEventHandler
import com.babylon.wallet.android.presentation.common.OneOffEventHandlerImpl
import com.babylon.wallet.android.presentation.common.StateViewModel
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.common.delegates.ChooseEntityDelegate
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.common.delegates.ChooseEntityDelegateImpl
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.common.models.ChooseEntityEvent
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.common.models.ChooseEntityUiState
import com.radixdlt.sargon.Account
import com.radixdlt.sargon.AddressOfAccountOrPersona
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import rdx.works.core.sargon.activeAccountsOnCurrentNetwork
import rdx.works.profile.domain.GetProfileUseCase
import javax.inject.Inject

@HiltViewModel
class ChooseAccountsViewModel @Inject constructor(
private val getProfileUseCase: GetProfileUseCase,
private val chooseEntityDelegate: ChooseEntityDelegateImpl<Account>
) : StateViewModel<ChooseEntityUiState<Account>>(),
ChooseEntityDelegate<Account> by chooseEntityDelegate,
OneOffEventHandler<ChooseEntityEvent> by OneOffEventHandlerImpl() {

init {
chooseEntityDelegate(viewModelScope, _state)
initAccounts()
}

override fun initialState(): ChooseEntityUiState<Account> = ChooseEntityUiState(mustSelectAtLeastOne = true)

fun onContinueClick() {
viewModelScope.launch {
sendEvent(
ChooseEntityEvent.EntitiesSelected(
addresses = chooseEntityDelegate.getSelectedItems().map { AddressOfAccountOrPersona.Account(it.address) }
)
)
}
}

private fun initAccounts() {
viewModelScope.launch {
val accounts = getProfileUseCase().activeAccountsOnCurrentNetwork
_state.update { state -> state.copy(items = accounts.map { Selectable(it) }) }
}
}

fun onSkipClick() {
viewModelScope.launch {
sendEvent(
ChooseEntityEvent.EntitiesSelected(
addresses = emptyList()
)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.apply

import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.ROUTE_APPLY_SHIELD_GRAPH
import com.babylon.wallet.android.presentation.settings.securitycenter.applyshield.common.ApplyShieldSharedViewModel

private const val ROUTE_APPLY_SHIELD = "apply_shield"

fun NavController.applyShield() {
navigate(ROUTE_APPLY_SHIELD)
}

fun NavGraphBuilder.applyShield(
navController: NavController
) {
composable(
route = ROUTE_APPLY_SHIELD,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left) },
exitTransition = { ExitTransition.None },
popEnterTransition = { EnterTransition.None },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Right) }
) {
val parentEntry = remember(it) { navController.getBackStackEntry(ROUTE_APPLY_SHIELD_GRAPH) }
val sharedVM = hiltViewModel<ApplyShieldSharedViewModel>(parentEntry)
val sharedState by sharedVM.state.collectAsStateWithLifecycle()

ApplyShieldScreen(
viewModel = hiltViewModel(),
securityStructureId = sharedState.securityStructureId,
entityAddresses = sharedState.allAddresses,
onDismiss = { navController.popBackStack() },
onShieldApplied = { navController.popBackStack(ROUTE_APPLY_SHIELD_GRAPH, true) }
)
}
}
Loading

0 comments on commit 4935bfb

Please sign in to comment.