mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-22 05:38:02 +00:00
Initial commit: Pezkuwi Wallet Android
Security hardened release: - Code obfuscation enabled (minifyEnabled=true, shrinkResources=true) - Sensitive files excluded (google-services.json, keystores) - Branch.io key moved to BuildConfig placeholder - Updated dependencies: OkHttp 4.12.0, Gson 2.10.1, BouncyCastle 1.77 - Comprehensive ProGuard rules for crypto wallet - Navigation 2.7.7, Lifecycle 2.7.0, ConstraintLayout 2.1.4
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,55 @@
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
|
||||
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
namespace 'io.novafoundation.nova.feature_ledger_impl'
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":feature-ledger-api")
|
||||
implementation project(":feature-ledger-core")
|
||||
implementation project(':feature-account-api')
|
||||
implementation project(':feature-wallet-api')
|
||||
implementation project(":common")
|
||||
implementation project(":runtime")
|
||||
|
||||
implementation materialDep
|
||||
|
||||
implementation substrateSdkDep
|
||||
|
||||
implementation bleDep
|
||||
implementation bleKotlinDep
|
||||
|
||||
implementation kotlinDep
|
||||
|
||||
implementation androidDep
|
||||
|
||||
implementation permissionsDep
|
||||
|
||||
implementation coroutinesDep
|
||||
implementation coroutinesAndroidDep
|
||||
implementation lifeCycleKtxDep
|
||||
|
||||
implementation project(":core-db")
|
||||
|
||||
implementation daggerDep
|
||||
ksp daggerCompiler
|
||||
|
||||
testImplementation jUnitDep
|
||||
testImplementation mockitoDep
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.data.repository
|
||||
|
||||
import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2
|
||||
import io.novafoundation.nova.feature_ledger_api.data.repository.LedgerDerivationPath
|
||||
import io.novafoundation.nova.feature_ledger_api.data.repository.LedgerRepository
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
class RealLedgerRepository(
|
||||
private val secretStoreV2: SecretStoreV2,
|
||||
) : LedgerRepository {
|
||||
|
||||
override suspend fun getChainAccountDerivationPath(metaId: Long, chainId: ChainId): String {
|
||||
val key = LedgerDerivationPath.legacyDerivationPathSecretKey(chainId)
|
||||
|
||||
return secretStoreV2.getAdditionalMetaAccountSecret(metaId, key)
|
||||
?: throw IllegalStateException("Cannot find Ledger derivation path for chain $chainId in meta account $metaId")
|
||||
}
|
||||
|
||||
override suspend fun getGenericDerivationPath(metaId: Long): String {
|
||||
val key = LedgerDerivationPath.genericDerivationPathSecretKey()
|
||||
|
||||
return secretStoreV2.getAdditionalMetaAccountSecret(metaId, key)
|
||||
?: throw IllegalStateException("Cannot find Ledger generic derivation path for meta account $metaId")
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.di
|
||||
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import io.novafoundation.nova.common.di.CommonApi
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.core_db.di.DbApi
|
||||
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator
|
||||
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
|
||||
import io.novafoundation.nova.feature_ledger_core.di.LedgerCoreApi
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.modules.LedgerBindsModule
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress.di.AddEvmGenericLedgerAccountSelectAddressComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectLedger.di.AddEvmAccountSelectGenericLedgerComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress.di.AddLedgerChainAccountSelectAddressComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectLedger.di.AddChainAccountSelectLedgerComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.di.FinishImportGenericLedgerComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.di.PreviewImportGenericLedgerComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectAddress.di.SelectAddressImportGenericLedgerComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger.di.SelectLedgerGenericImportComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.start.di.StartImportGenericLedgerComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.SelectLedgerAddressInterScreenCommunicator
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.fillWallet.di.FillWalletImportLedgerComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.finish.di.FinishImportLedgerComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.selectAddress.di.SelectAddressImportLedgerLegacyComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.selectLedger.di.SelectLedgerImportLedgerComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.start.di.StartImportLegacyLedgerComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.sign.di.SignLedgerComponent
|
||||
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
|
||||
import io.novafoundation.nova.runtime.di.RuntimeApi
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
LedgerFeatureDependencies::class,
|
||||
],
|
||||
modules = [
|
||||
LedgerFeatureModule::class,
|
||||
LedgerBindsModule::class
|
||||
]
|
||||
)
|
||||
@FeatureScope
|
||||
interface LedgerFeatureComponent : LedgerFeatureApi {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
deps: LedgerFeatureDependencies,
|
||||
@BindsInstance router: LedgerRouter,
|
||||
@BindsInstance selectLedgerAddressInterScreenCommunicator: SelectLedgerAddressInterScreenCommunicator,
|
||||
@BindsInstance signInterScreenCommunicator: LedgerSignCommunicator,
|
||||
): LedgerFeatureComponent
|
||||
}
|
||||
|
||||
fun startImportLegacyLedgerComponentFactory(): StartImportLegacyLedgerComponent.Factory
|
||||
fun fillWalletImportLedgerComponentFactory(): FillWalletImportLedgerComponent.Factory
|
||||
fun selectLedgerImportComponentFactory(): SelectLedgerImportLedgerComponent.Factory
|
||||
fun selectAddressImportLedgerLegacyComponentFactory(): SelectAddressImportLedgerLegacyComponent.Factory
|
||||
fun selectAddressImportLedgerGenericComponentFactory(): SelectAddressImportGenericLedgerComponent.Factory
|
||||
fun finishImportLedgerComponentFactory(): FinishImportLedgerComponent.Factory
|
||||
|
||||
fun signLedgerComponentFactory(): SignLedgerComponent.Factory
|
||||
|
||||
fun addChainAccountSelectLedgerComponentFactory(): AddChainAccountSelectLedgerComponent.Factory
|
||||
fun addChainAccountSelectAddressComponentFactory(): AddLedgerChainAccountSelectAddressComponent.Factory
|
||||
|
||||
// New generic app flow
|
||||
|
||||
fun startImportGenericLedgerComponentFactory(): StartImportGenericLedgerComponent.Factory
|
||||
fun selectLedgerGenericImportComponentFactory(): SelectLedgerGenericImportComponent.Factory
|
||||
fun previewImportGenericLedgerComponentFactory(): PreviewImportGenericLedgerComponent.Factory
|
||||
fun finishGenericImportLedgerComponentFactory(): FinishImportGenericLedgerComponent.Factory
|
||||
|
||||
// Generic import EVM account
|
||||
fun addEvmAccountSelectGenericLedgerComponentFactory(): AddEvmAccountSelectGenericLedgerComponent.Factory
|
||||
fun addEvmGenericLedgerAccountSelectAddressComponentFactory(): AddEvmGenericLedgerAccountSelectAddressComponent.Factory
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
CommonApi::class,
|
||||
RuntimeApi::class,
|
||||
WalletFeatureApi::class,
|
||||
AccountFeatureApi::class,
|
||||
LedgerCoreApi::class,
|
||||
DbApi::class,
|
||||
]
|
||||
)
|
||||
interface LedgerFeatureDependenciesComponent : LedgerFeatureDependencies
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.di
|
||||
|
||||
import coil.ImageLoader
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.address.format.AddressSchemeFormatter
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2
|
||||
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
|
||||
import io.novafoundation.nova.common.resources.ContextManager
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager
|
||||
import io.novafoundation.nova.common.utils.location.LocationManager
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory
|
||||
import io.novafoundation.nova.core_db.dao.MetaAccountDao
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.GenericLedgerAddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LegacyLedgerAddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.AddressActionsMixin
|
||||
import io.novafoundation.nova.feature_ledger_core.domain.LedgerMigrationTracker
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TokenRepository
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
|
||||
import io.novafoundation.nova.runtime.extrinsic.ExtrinsicValidityUseCase
|
||||
import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataShortenerService
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.network.rpc.RpcCalls
|
||||
|
||||
interface LedgerFeatureDependencies {
|
||||
|
||||
val amountFormatter: AmountFormatter
|
||||
|
||||
val chainRegistry: ChainRegistry
|
||||
|
||||
val appLinksProvider: AppLinksProvider
|
||||
|
||||
val imageLoader: ImageLoader
|
||||
|
||||
val addressIconGenerator: AddressIconGenerator
|
||||
|
||||
val resourceManager: ResourceManager
|
||||
|
||||
val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory
|
||||
|
||||
val bluetoothManager: BluetoothManager
|
||||
|
||||
val locationManager: LocationManager
|
||||
|
||||
val permissionAskerFactory: PermissionsAskerFactory
|
||||
|
||||
val contextManager: ContextManager
|
||||
|
||||
val assetSourceRegistry: AssetSourceRegistry
|
||||
|
||||
val tokenRepository: TokenRepository
|
||||
|
||||
val metaAccountDao: MetaAccountDao
|
||||
|
||||
val accountInteractor: AccountInteractor
|
||||
|
||||
val accountRepository: AccountRepository
|
||||
|
||||
val secretStoreV2: SecretStoreV2
|
||||
|
||||
val signSharedState: SigningSharedState
|
||||
|
||||
val extrinsicValidityUseCase: ExtrinsicValidityUseCase
|
||||
|
||||
val selectedAccountUseCase: SelectedAccountUseCase
|
||||
|
||||
val legacyLedgerAddAccountRepository: LegacyLedgerAddAccountRepository
|
||||
|
||||
val genericLegacyLedgerAddAccountRepository: GenericLedgerAddAccountRepository
|
||||
|
||||
val apiCreator: NetworkApiCreator
|
||||
|
||||
val rpcCalls: RpcCalls
|
||||
|
||||
val metadataShortenerService: MetadataShortenerService
|
||||
|
||||
val ledgerMigrationTracker: LedgerMigrationTracker
|
||||
|
||||
val externalActions: ExternalActions.Presentation
|
||||
|
||||
val addressActionsMixinFactory: AddressActionsMixin.Factory
|
||||
|
||||
val addressSchemeFormatter: AddressSchemeFormatter
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.di
|
||||
|
||||
import io.novafoundation.nova.common.di.FeatureApiHolder
|
||||
import io.novafoundation.nova.common.di.FeatureContainer
|
||||
import io.novafoundation.nova.common.di.scope.ApplicationScope
|
||||
import io.novafoundation.nova.core_db.di.DbApi
|
||||
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator
|
||||
import io.novafoundation.nova.feature_ledger_core.di.LedgerCoreApi
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.SelectLedgerAddressInterScreenCommunicator
|
||||
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
|
||||
import io.novafoundation.nova.runtime.di.RuntimeApi
|
||||
import javax.inject.Inject
|
||||
|
||||
@ApplicationScope
|
||||
class LedgerFeatureHolder @Inject constructor(
|
||||
featureContainer: FeatureContainer,
|
||||
private val router: LedgerRouter,
|
||||
private val selectLedgerAddressInterScreenCommunicator: SelectLedgerAddressInterScreenCommunicator,
|
||||
private val signInterScreenCommunicator: LedgerSignCommunicator,
|
||||
) : FeatureApiHolder(featureContainer) {
|
||||
|
||||
override fun initializeDependencies(): Any {
|
||||
val accountFeatureDependencies = DaggerLedgerFeatureComponent_LedgerFeatureDependenciesComponent.builder()
|
||||
.commonApi(commonApi())
|
||||
.dbApi(getFeature(DbApi::class.java))
|
||||
.runtimeApi(getFeature(RuntimeApi::class.java))
|
||||
.walletFeatureApi(getFeature(WalletFeatureApi::class.java))
|
||||
.accountFeatureApi(getFeature(AccountFeatureApi::class.java))
|
||||
.ledgerCoreApi(getFeature(LedgerCoreApi::class.java))
|
||||
.build()
|
||||
|
||||
return DaggerLedgerFeatureComponent.factory()
|
||||
.create(
|
||||
accountFeatureDependencies,
|
||||
router,
|
||||
selectLedgerAddressInterScreenCommunicator,
|
||||
signInterScreenCommunicator
|
||||
)
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.address.format.AddressSchemeFormatter
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.resources.ContextManager
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager
|
||||
import io.novafoundation.nova.feature_ledger_api.data.repository.LedgerRepository
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.transport.LedgerTransport
|
||||
import io.novafoundation.nova.feature_ledger_core.domain.LedgerMigrationTracker
|
||||
import io.novafoundation.nova.feature_ledger_impl.data.repository.RealLedgerRepository
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.modules.GenericLedgerModule
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.RealSelectAddressLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.migration.LedgerMigrationUseCase
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.migration.RealLedgerMigrationUseCase
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessagePresentable
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatterFactory
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.SingleSheetLedgerMessagePresentable
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers.LedgerDeviceFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatterFactory
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.application.substrate.legacyApp.LegacySubstrateLedgerApplication
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.application.substrate.newApp.GenericSubstrateLedgerApplication
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.application.substrate.newApp.MigrationSubstrateLedgerApplication
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.connection.ble.LedgerBleManager
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.discovery.CompoundLedgerDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.discovery.ble.BleLedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.discovery.usb.UsbLedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.transport.ChunkedLedgerTransport
|
||||
import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataShortenerService
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
@Module(includes = [GenericLedgerModule::class])
|
||||
class LedgerFeatureModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideLedgerTransport(): LedgerTransport = ChunkedLedgerTransport()
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideSubstrateLedgerApplication(
|
||||
transport: LedgerTransport,
|
||||
ledgerRepository: LedgerRepository,
|
||||
) = LegacySubstrateLedgerApplication(transport, ledgerRepository)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMigrationLedgerApplication(
|
||||
transport: LedgerTransport,
|
||||
chainRegistry: ChainRegistry,
|
||||
ledgerRepository: LedgerRepository,
|
||||
metadataShortenerService: MetadataShortenerService
|
||||
) = MigrationSubstrateLedgerApplication(
|
||||
transport = transport,
|
||||
chainRegistry = chainRegistry,
|
||||
metadataShortenerService = metadataShortenerService,
|
||||
ledgerRepository = ledgerRepository
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideGenericLedgerApplication(
|
||||
transport: LedgerTransport,
|
||||
chainRegistry: ChainRegistry,
|
||||
ledgerRepository: LedgerRepository,
|
||||
metadataShortenerService: MetadataShortenerService
|
||||
) = GenericSubstrateLedgerApplication(
|
||||
transport = transport,
|
||||
metadataShortenerService = metadataShortenerService,
|
||||
ledgerRepository = ledgerRepository,
|
||||
chainRegistry = chainRegistry
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideLedgerMessageFormatterFactory(
|
||||
resourceManager: ResourceManager,
|
||||
migrationTracker: LedgerMigrationTracker,
|
||||
chainRegistry: ChainRegistry,
|
||||
appLinksProvider: AppLinksProvider,
|
||||
): LedgerMessageFormatterFactory {
|
||||
return LedgerMessageFormatterFactory(resourceManager, migrationTracker, chainRegistry, appLinksProvider)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideLedgerMigrationUseCase(
|
||||
ledgerMigrationTracker: LedgerMigrationTracker,
|
||||
migrationApp: MigrationSubstrateLedgerApplication,
|
||||
legacyApp: LegacySubstrateLedgerApplication,
|
||||
genericApp: GenericSubstrateLedgerApplication,
|
||||
): LedgerMigrationUseCase {
|
||||
return RealLedgerMigrationUseCase(ledgerMigrationTracker, migrationApp, legacyApp, genericApp)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideLedgerBleManager(
|
||||
contextManager: ContextManager
|
||||
) = LedgerBleManager(contextManager)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideLedgerDeviceDiscoveryService(
|
||||
bluetoothManager: BluetoothManager,
|
||||
ledgerBleManager: LedgerBleManager
|
||||
) = BleLedgerDeviceDiscoveryService(
|
||||
bluetoothManager = bluetoothManager,
|
||||
ledgerBleManager = ledgerBleManager
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideUsbDeviceDiscoveryService(
|
||||
contextManager: ContextManager
|
||||
) = UsbLedgerDeviceDiscoveryService(contextManager)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideDeviceDiscoveryService(
|
||||
bleLedgerDeviceDiscoveryService: BleLedgerDeviceDiscoveryService,
|
||||
usbLedgerDeviceDiscoveryService: UsbLedgerDeviceDiscoveryService
|
||||
): LedgerDeviceDiscoveryService = CompoundLedgerDiscoveryService(
|
||||
bleLedgerDeviceDiscoveryService,
|
||||
usbLedgerDeviceDiscoveryService
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideRepository(
|
||||
secretStoreV2: SecretStoreV2
|
||||
): LedgerRepository = RealLedgerRepository(secretStoreV2)
|
||||
|
||||
@Provides
|
||||
fun provideLedgerMessagePresentable(): LedgerMessagePresentable = SingleSheetLedgerMessagePresentable()
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideSelectAddressInteractor(
|
||||
migrationUseCase: LedgerMigrationUseCase,
|
||||
ledgerDeviceDiscoveryService: LedgerDeviceDiscoveryService,
|
||||
): SelectAddressLedgerInteractor {
|
||||
return RealSelectAddressLedgerInteractor(
|
||||
migrationUseCase = migrationUseCase,
|
||||
ledgerDeviceDiscoveryService = ledgerDeviceDiscoveryService,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideLedgerDeviceMapper(resourceManager: ResourceManager): LedgerDeviceFormatter {
|
||||
return LedgerDeviceFormatter(resourceManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMessageCommandFormatterFactory(
|
||||
resourceManager: ResourceManager,
|
||||
deviceMapper: LedgerDeviceFormatter,
|
||||
addressSchemeFormatter: AddressSchemeFormatter
|
||||
) = MessageCommandFormatterFactory(resourceManager, deviceMapper, addressSchemeFormatter)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.di.annotations
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class GenericLedger
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.di.modules
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.annotations.GenericLedger
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatterFactory
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatterFactory
|
||||
|
||||
@Module
|
||||
class GenericLedgerModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
@GenericLedger
|
||||
fun provideMessageFormatter(factory: LedgerMessageFormatterFactory): LedgerMessageFormatter = factory.createGeneric()
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
@GenericLedger
|
||||
fun provideMessageCommandFormatter(
|
||||
@GenericLedger messageFormatter: LedgerMessageFormatter,
|
||||
messageCommandFormatterFactory: MessageCommandFormatterFactory
|
||||
): MessageCommandFormatter = messageCommandFormatterFactory.create(messageFormatter)
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.di.modules
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.generic.GenericLedgerEvmAlertFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.generic.RealGenericLedgerEvmAlertFormatter
|
||||
|
||||
@Module
|
||||
interface LedgerBindsModule {
|
||||
|
||||
@Binds
|
||||
fun bindEvmUpdateFormatter(real: RealGenericLedgerEvmAlertFormatter): GenericLedgerEvmAlertFormatter
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.domain.account.addChain.generic
|
||||
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.common.utils.coerceToUnit
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.GenericLedgerAddAccountRepository
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerEvmAccount
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
interface AddEvmAccountToGenericLedgerInteractor {
|
||||
|
||||
suspend fun addEvmAccount(metaId: Long, account: LedgerEvmAccount): Result<Unit>
|
||||
}
|
||||
|
||||
@ScreenScope
|
||||
class RealAddEvmAccountToGenericLedgerInteractor @Inject constructor(
|
||||
private val genericLedgerAddAccountRepository: GenericLedgerAddAccountRepository
|
||||
) : AddEvmAccountToGenericLedgerInteractor {
|
||||
|
||||
override suspend fun addEvmAccount(metaId: Long, account: LedgerEvmAccount): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val payload = GenericLedgerAddAccountRepository.Payload.AddEvmAccount(metaId, account)
|
||||
genericLedgerAddAccountRepository.addAccount(payload)
|
||||
}.coerceToUnit()
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.domain.account.addChain.legacy
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LegacyLedgerAddAccountRepository
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount
|
||||
|
||||
interface AddLedgerChainAccountInteractor {
|
||||
|
||||
suspend fun addChainAccount(metaId: Long, chainId: String, account: LedgerSubstrateAccount): Result<Unit>
|
||||
}
|
||||
|
||||
class RealAddLedgerChainAccountInteractor(
|
||||
private val legacyLedgerAddAccountRepository: LegacyLedgerAddAccountRepository
|
||||
) : AddLedgerChainAccountInteractor {
|
||||
|
||||
override suspend fun addChainAccount(metaId: Long, chainId: String, account: LedgerSubstrateAccount): Result<Unit> = kotlin.runCatching {
|
||||
legacyLedgerAddAccountRepository.addAccount(
|
||||
LegacyLedgerAddAccountRepository.Payload.ChainAccount(
|
||||
metaId,
|
||||
chainId,
|
||||
account
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress
|
||||
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LedgerVariant
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerEvmAccount
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.findDeviceOrThrow
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.migration.LedgerMigrationUseCase
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
class LedgerAccount(
|
||||
val index: Int,
|
||||
val substrate: LedgerSubstrateAccount,
|
||||
val evm: LedgerEvmAccount?,
|
||||
)
|
||||
|
||||
interface SelectAddressLedgerInteractor {
|
||||
|
||||
suspend fun getDevice(deviceId: String): LedgerDevice
|
||||
|
||||
suspend fun loadLedgerAccount(substrateChain: Chain, deviceId: String, accountIndex: Int, ledgerVariant: LedgerVariant): Result<LedgerAccount>
|
||||
|
||||
suspend fun verifyLedgerAccount(
|
||||
substrateChain: Chain,
|
||||
deviceId: String,
|
||||
accountIndex: Int,
|
||||
ledgerVariant: LedgerVariant,
|
||||
addressSchemes: List<AddressScheme>
|
||||
): Result<Unit>
|
||||
}
|
||||
|
||||
class RealSelectAddressLedgerInteractor(
|
||||
private val migrationUseCase: LedgerMigrationUseCase,
|
||||
private val ledgerDeviceDiscoveryService: LedgerDeviceDiscoveryService,
|
||||
) : SelectAddressLedgerInteractor {
|
||||
|
||||
override suspend fun getDevice(deviceId: String): LedgerDevice {
|
||||
return ledgerDeviceDiscoveryService.findDeviceOrThrow(deviceId)
|
||||
}
|
||||
|
||||
override suspend fun loadLedgerAccount(
|
||||
substrateChain: Chain,
|
||||
deviceId: String,
|
||||
accountIndex: Int,
|
||||
ledgerVariant: LedgerVariant,
|
||||
) = runCatching {
|
||||
val device = ledgerDeviceDiscoveryService.findDeviceOrThrow(deviceId)
|
||||
val app = migrationUseCase.determineLedgerApp(substrateChain.id, ledgerVariant)
|
||||
|
||||
val substrateAccount = app.getSubstrateAccount(device, substrateChain.id, accountIndex, confirmAddress = false)
|
||||
val evmAccount = app.getEvmAccount(device, accountIndex, confirmAddress = false)
|
||||
|
||||
LedgerAccount(accountIndex, substrateAccount, evmAccount)
|
||||
}
|
||||
|
||||
override suspend fun verifyLedgerAccount(
|
||||
substrateChain: Chain,
|
||||
deviceId: String,
|
||||
accountIndex: Int,
|
||||
ledgerVariant: LedgerVariant,
|
||||
addressSchemes: List<AddressScheme>
|
||||
): Result<Unit> = runCatching {
|
||||
val device = ledgerDeviceDiscoveryService.findDeviceOrThrow(deviceId)
|
||||
val app = migrationUseCase.determineLedgerApp(substrateChain.id, ledgerVariant)
|
||||
|
||||
val verificationPerScheme = mapOf(
|
||||
AddressScheme.SUBSTRATE to suspend { app.getSubstrateAccount(device, substrateChain.id, accountIndex, confirmAddress = true) },
|
||||
AddressScheme.EVM to suspend { app.getEvmAccount(device, accountIndex, confirmAddress = true) }
|
||||
)
|
||||
|
||||
addressSchemes.forEach {
|
||||
verificationPerScheme.getValue(it).invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.domain.account.connect.generic.finish
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.addAccountWithSingleChange
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.GenericLedgerAddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerEvmAccount
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount
|
||||
|
||||
interface FinishImportGenericLedgerInteractor {
|
||||
|
||||
suspend fun createWallet(
|
||||
name: String,
|
||||
substrateAccount: LedgerSubstrateAccount,
|
||||
evmAccount: LedgerEvmAccount?,
|
||||
): Result<Unit>
|
||||
}
|
||||
|
||||
class RealFinishImportGenericLedgerInteractor(
|
||||
private val genericLedgerAddAccountRepository: GenericLedgerAddAccountRepository,
|
||||
private val accountRepository: AccountRepository,
|
||||
) : FinishImportGenericLedgerInteractor {
|
||||
|
||||
override suspend fun createWallet(
|
||||
name: String,
|
||||
substrateAccount: LedgerSubstrateAccount,
|
||||
evmAccount: LedgerEvmAccount?,
|
||||
) = runCatching {
|
||||
val payload = GenericLedgerAddAccountRepository.Payload.NewWallet(
|
||||
name = name,
|
||||
substrateAccount = substrateAccount,
|
||||
evmAccount = evmAccount
|
||||
)
|
||||
|
||||
val addAccountResult = genericLedgerAddAccountRepository.addAccountWithSingleChange(payload)
|
||||
|
||||
accountRepository.selectMetaAccount(addAccountResult.metaId)
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.domain.account.connect.generic.preview
|
||||
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
import io.novafoundation.nova.common.list.GroupedList
|
||||
import io.novafoundation.nova.common.utils.mapValuesNotNull
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.chain.preview.model.ChainAccountPreview
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.findDeviceOrThrow
|
||||
import io.novafoundation.nova.feature_ledger_core.domain.LedgerMigrationTracker
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.application.substrate.newApp.GenericSubstrateLedgerApplication
|
||||
import io.novafoundation.nova.runtime.ext.addressScheme
|
||||
import io.novafoundation.nova.runtime.ext.defaultComparator
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
interface PreviewImportGenericLedgerInteractor {
|
||||
|
||||
suspend fun getDevice(deviceId: String): LedgerDevice
|
||||
|
||||
suspend fun availableChainAccounts(
|
||||
substrateAccountId: AccountId,
|
||||
evmAccountId: AccountId?,
|
||||
): GroupedList<AddressScheme, ChainAccountPreview>
|
||||
|
||||
suspend fun verifyAddressOnLedger(accountIndex: Int, deviceId: String): Result<Unit>
|
||||
}
|
||||
|
||||
class RealPreviewImportGenericLedgerInteractor(
|
||||
private val ledgerMigrationTracker: LedgerMigrationTracker,
|
||||
private val genericSubstrateLedgerApplication: GenericSubstrateLedgerApplication,
|
||||
private val ledgerDiscoveryService: LedgerDeviceDiscoveryService
|
||||
) : PreviewImportGenericLedgerInteractor {
|
||||
|
||||
override suspend fun getDevice(deviceId: String): LedgerDevice {
|
||||
return ledgerDiscoveryService.findDeviceOrThrow(deviceId)
|
||||
}
|
||||
|
||||
override suspend fun availableChainAccounts(
|
||||
substrateAccountId: AccountId,
|
||||
evmAccountId: AccountId?,
|
||||
): GroupedList<AddressScheme, ChainAccountPreview> {
|
||||
return ledgerMigrationTracker.supportedChainsByGenericApp()
|
||||
.groupBy(Chain::addressScheme)
|
||||
.mapValuesNotNull { (scheme, chains) ->
|
||||
val accountId = when (scheme) {
|
||||
AddressScheme.EVM -> evmAccountId ?: return@mapValuesNotNull null
|
||||
AddressScheme.SUBSTRATE -> substrateAccountId
|
||||
}
|
||||
|
||||
chains
|
||||
.sortedWith(Chain.defaultComparator())
|
||||
.map { chain -> ChainAccountPreview(chain, accountId) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun verifyAddressOnLedger(accountIndex: Int, deviceId: String): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val device = ledgerDiscoveryService.findDeviceOrThrow(deviceId)
|
||||
|
||||
genericSubstrateLedgerApplication.getUniversalSubstrateAccount(device, accountIndex, confirmAddress = true)
|
||||
genericSubstrateLedgerApplication.getEvmAccount(device, accountIndex, confirmAddress = true)
|
||||
|
||||
Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.domain.account.connect.legacy.fillWallet
|
||||
|
||||
import io.novafoundation.nova.common.utils.mapToSet
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.SubstrateApplicationConfig
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.enabledChainById
|
||||
|
||||
interface FillWalletImportLedgerInteractor {
|
||||
|
||||
suspend fun availableLedgerChains(): List<Chain>
|
||||
}
|
||||
|
||||
class RealFillWalletImportLedgerInteractor(
|
||||
private val chainRegistry: ChainRegistry
|
||||
) : FillWalletImportLedgerInteractor {
|
||||
|
||||
override suspend fun availableLedgerChains(): List<Chain> {
|
||||
val supportedLedgerApps = SubstrateApplicationConfig.all()
|
||||
val supportedChainIds = supportedLedgerApps.mapToSet { it.chainId }
|
||||
|
||||
return chainRegistry.enabledChainById()
|
||||
.filterKeys { it in supportedChainIds }
|
||||
.values
|
||||
.toList()
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.domain.account.connect.legacy.finish
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.addAccountWithSingleChange
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LegacyLedgerAddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
interface FinishImportLedgerInteractor {
|
||||
|
||||
suspend fun createWallet(
|
||||
name: String,
|
||||
ledgerChainAccounts: Map<ChainId, LedgerSubstrateAccount>,
|
||||
): Result<Unit>
|
||||
}
|
||||
|
||||
class RealFinishImportLedgerInteractor(
|
||||
private val legacyLedgerAddAccountRepository: LegacyLedgerAddAccountRepository,
|
||||
private val accountRepository: AccountRepository,
|
||||
) : FinishImportLedgerInteractor {
|
||||
|
||||
override suspend fun createWallet(name: String, ledgerChainAccounts: Map<ChainId, LedgerSubstrateAccount>) = runCatching {
|
||||
val addAccountResult = legacyLedgerAddAccountRepository.addAccountWithSingleChange(
|
||||
LegacyLedgerAddAccountRepository.Payload.MetaAccount(
|
||||
name,
|
||||
ledgerChainAccounts
|
||||
)
|
||||
)
|
||||
|
||||
accountRepository.selectMetaAccount(addAccountResult.metaId)
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.domain.account.sign
|
||||
|
||||
import io.novafoundation.nova.common.utils.chainId
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SeparateFlowSignerState
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.chainId
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.signaturePayload
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LedgerVariant
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.migration.LedgerMigrationUseCase
|
||||
import io.novafoundation.nova.runtime.ext.verifyMultiChain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novasama.substrate_sdk_android.encrypt.SignatureVerifier
|
||||
import io.novasama.substrate_sdk_android.encrypt.SignatureWrapper
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.InheritedImplication
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
interface SignLedgerInteractor {
|
||||
|
||||
suspend fun getSignature(
|
||||
device: LedgerDevice,
|
||||
metaId: Long,
|
||||
payload: InheritedImplication,
|
||||
): SignatureWrapper
|
||||
|
||||
suspend fun verifySignature(
|
||||
payload: SeparateFlowSignerState,
|
||||
signature: SignatureWrapper
|
||||
): Boolean
|
||||
}
|
||||
|
||||
class RealSignLedgerInteractor(
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val usedVariant: LedgerVariant,
|
||||
private val migrationUseCase: LedgerMigrationUseCase
|
||||
) : SignLedgerInteractor {
|
||||
|
||||
override suspend fun getSignature(
|
||||
device: LedgerDevice,
|
||||
metaId: Long,
|
||||
payload: InheritedImplication
|
||||
): SignatureWrapper = withContext(Dispatchers.Default) {
|
||||
val chainId = payload.chainId
|
||||
val app = migrationUseCase.determineLedgerApp(chainId, usedVariant)
|
||||
|
||||
app.getSignature(device, metaId, chainId, payload)
|
||||
}
|
||||
|
||||
override suspend fun verifySignature(
|
||||
payload: SeparateFlowSignerState,
|
||||
signature: SignatureWrapper
|
||||
): Boolean = runCatching {
|
||||
val payloadBytes = payload.payload.signaturePayload()
|
||||
val chainId = payload.payload.chainId()
|
||||
val chain = chainRegistry.getChain(chainId)
|
||||
|
||||
val publicKey = payload.metaAccount.publicKeyIn(chain) ?: throw IllegalStateException("No public key for chain $chainId")
|
||||
|
||||
SignatureVerifier.verifyMultiChain(chain, signature, payloadBytes, publicKey)
|
||||
}.getOrDefault(false)
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.domain.migration
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LedgerVariant
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.SubstrateLedgerApplication
|
||||
import io.novafoundation.nova.feature_ledger_core.domain.LedgerMigrationTracker
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.application.substrate.legacyApp.LegacySubstrateLedgerApplication
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.application.substrate.newApp.GenericSubstrateLedgerApplication
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.application.substrate.newApp.MigrationSubstrateLedgerApplication
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
interface LedgerMigrationUseCase {
|
||||
|
||||
suspend fun determineLedgerApp(chainId: ChainId, ledgerVariant: LedgerVariant): SubstrateLedgerApplication
|
||||
}
|
||||
|
||||
suspend fun LedgerMigrationUseCase.determineAppForLegacyAccount(chainId: ChainId): SubstrateLedgerApplication {
|
||||
return determineLedgerApp(chainId, LedgerVariant.LEGACY)
|
||||
}
|
||||
|
||||
class RealLedgerMigrationUseCase(
|
||||
private val ledgerMigrationTracker: LedgerMigrationTracker,
|
||||
private val migrationApp: MigrationSubstrateLedgerApplication,
|
||||
private val legacyApp: LegacySubstrateLedgerApplication,
|
||||
private val genericApp: GenericSubstrateLedgerApplication,
|
||||
) : LedgerMigrationUseCase {
|
||||
|
||||
override suspend fun determineLedgerApp(chainId: ChainId, ledgerVariant: LedgerVariant): SubstrateLedgerApplication {
|
||||
return when {
|
||||
ledgerVariant == LedgerVariant.GENERIC -> genericApp
|
||||
ledgerMigrationTracker.shouldUseMigrationApp(chainId) -> migrationApp
|
||||
else -> legacyApp
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation
|
||||
|
||||
import io.novafoundation.nova.common.navigation.ReturnableRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress.AddEvmGenericLedgerAccountSelectAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress.AddLedgerChainAccountSelectAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.FinishImportGenericLedgerPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.PreviewImportGenericLedgerPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger.SelectLedgerGenericPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.fillWallet.FillWalletImportLedgerLegacyPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.finish.FinishImportLedgerPayload
|
||||
|
||||
interface LedgerRouter : ReturnableRouter {
|
||||
|
||||
fun openImportFillWallet(payload: FillWalletImportLedgerLegacyPayload)
|
||||
|
||||
fun returnToImportFillWallet()
|
||||
|
||||
fun openSelectImportAddress(payload: SelectLedgerAddressPayload)
|
||||
|
||||
fun openCreatePincode()
|
||||
|
||||
fun openMain()
|
||||
|
||||
fun openFinishImportLedger(payload: FinishImportLedgerPayload)
|
||||
|
||||
fun finishSignFlow()
|
||||
|
||||
fun openAddChainAccountSelectAddress(payload: AddLedgerChainAccountSelectAddressPayload)
|
||||
|
||||
// Generic app flows
|
||||
|
||||
fun openSelectLedgerGeneric(payload: SelectLedgerGenericPayload)
|
||||
|
||||
fun openSelectAddressGenericLedger(payload: SelectLedgerAddressPayload)
|
||||
|
||||
fun openPreviewLedgerAccountsGeneric(payload: PreviewImportGenericLedgerPayload)
|
||||
|
||||
fun openFinishImportLedgerGeneric(payload: FinishImportGenericLedgerPayload)
|
||||
|
||||
fun openAddGenericEvmAddressSelectAddress(payload: AddEvmGenericLedgerAccountSelectAddressPayload)
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress
|
||||
|
||||
import android.os.Bundle
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.utils.makeGone
|
||||
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.LedgerFeatureComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectAddressLedgerFragment
|
||||
|
||||
class AddEvmGenericLedgerAccountSelectAddressFragment : SelectAddressLedgerFragment<AddEvmGenericLedgerAccountSelectAddressViewModel>() {
|
||||
|
||||
companion object {
|
||||
private const val PAYLOAD_KEY = "AddEvmGenericLedgerAccountSelectAddressFragment.Payload"
|
||||
|
||||
fun getBundle(payload: AddEvmGenericLedgerAccountSelectAddressPayload): Bundle {
|
||||
return Bundle().apply {
|
||||
putParcelable(PAYLOAD_KEY, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
super.initViews()
|
||||
|
||||
binder.ledgerSelectAddressChain.makeGone()
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<LedgerFeatureComponent>(requireContext(), LedgerFeatureApi::class.java)
|
||||
.addEvmGenericLedgerAccountSelectAddressComponentFactory()
|
||||
.create(this, argument(PAYLOAD_KEY))
|
||||
.inject(this)
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class AddEvmGenericLedgerAccountSelectAddressPayload(
|
||||
val metaId: Long,
|
||||
val deviceId: String,
|
||||
) : Parcelable
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress
|
||||
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LedgerVariant
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.AddressActionsMixin
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.addChain.generic.AddEvmAccountToGenericLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.LedgerAccount
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.generic.GenericLedgerEvmAlertFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectAddressLedgerViewModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.model.AddressVerificationMode
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AddEvmGenericLedgerAccountSelectAddressViewModel(
|
||||
private val router: LedgerRouter,
|
||||
private val payload: AddEvmGenericLedgerAccountSelectAddressPayload,
|
||||
private val addAccountInteractor: AddEvmAccountToGenericLedgerInteractor,
|
||||
private val selectAddressLedgerInteractor: SelectAddressLedgerInteractor,
|
||||
private val evmAlertFormatter: GenericLedgerEvmAlertFormatter,
|
||||
addressIconGenerator: AddressIconGenerator,
|
||||
resourceManager: ResourceManager,
|
||||
chainRegistry: ChainRegistry,
|
||||
selectLedgerAddressPayload: SelectLedgerAddressPayload,
|
||||
messageCommandFormatter: MessageCommandFormatter,
|
||||
addressActionsMixinFactory: AddressActionsMixin.Factory
|
||||
) : SelectAddressLedgerViewModel(
|
||||
router = router,
|
||||
interactor = selectAddressLedgerInteractor,
|
||||
addressIconGenerator = addressIconGenerator,
|
||||
resourceManager = resourceManager,
|
||||
payload = selectLedgerAddressPayload,
|
||||
chainRegistry = chainRegistry,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
addressActionsMixinFactory = addressActionsMixinFactory
|
||||
) {
|
||||
|
||||
override val ledgerVariant: LedgerVariant = LedgerVariant.GENERIC
|
||||
|
||||
override val addressVerificationMode = AddressVerificationMode.Enabled(addressSchemesToVerify = listOf(AddressScheme.EVM))
|
||||
|
||||
override suspend fun loadLedgerAccount(
|
||||
substratePreviewChain: Chain,
|
||||
deviceId: String,
|
||||
accountIndex: Int,
|
||||
ledgerVariant: LedgerVariant
|
||||
): Result<LedgerAccount?> {
|
||||
return selectAddressLedgerInteractor.loadLedgerAccount(substratePreviewChain, deviceId, accountIndex, ledgerVariant).map { ledgerAccount ->
|
||||
if (ledgerAccount.evm != null) {
|
||||
ledgerAccount
|
||||
} else {
|
||||
_alertFlow.emit(evmAlertFormatter.createUpdateAppToGetEvmAddressAlert())
|
||||
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAccountVerified(account: LedgerAccount) {
|
||||
launch {
|
||||
val result = addAccountInteractor.addEvmAccount(payload.metaId, account.evm!!)
|
||||
|
||||
result
|
||||
.onSuccess { router.openMain() }
|
||||
.onFailure(::showError)
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress.AddEvmGenericLedgerAccountSelectAddressFragment
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress.AddEvmGenericLedgerAccountSelectAddressPayload
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
AddEvmGenericLedgerAccountSelectAddressModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface AddEvmGenericLedgerAccountSelectAddressComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: AddEvmGenericLedgerAccountSelectAddressPayload,
|
||||
): AddEvmGenericLedgerAccountSelectAddressComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: AddEvmGenericLedgerAccountSelectAddressFragment)
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.AddressActionsMixin
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.annotations.GenericLedger
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.addChain.generic.AddEvmAccountToGenericLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.addChain.generic.RealAddEvmAccountToGenericLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress.AddEvmGenericLedgerAccountSelectAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress.AddEvmGenericLedgerAccountSelectAddressViewModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress.di.AddEvmGenericLedgerAccountSelectAddressModule.BindsModule
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.generic.GenericLedgerEvmAlertFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload
|
||||
import io.novafoundation.nova.runtime.ext.Geneses
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
@Module(includes = [ViewModelModule::class, BindsModule::class])
|
||||
class AddEvmGenericLedgerAccountSelectAddressModule {
|
||||
|
||||
@Module
|
||||
interface BindsModule {
|
||||
|
||||
@Binds
|
||||
fun bindInteractor(real: RealAddEvmAccountToGenericLedgerInteractor): AddEvmAccountToGenericLedgerInteractor
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(AddEvmGenericLedgerAccountSelectAddressViewModel::class)
|
||||
fun provideViewModel(
|
||||
router: LedgerRouter,
|
||||
payload: AddEvmGenericLedgerAccountSelectAddressPayload,
|
||||
addAccountInteractor: AddEvmAccountToGenericLedgerInteractor,
|
||||
selectAddressLedgerInteractor: SelectAddressLedgerInteractor,
|
||||
addressIconGenerator: AddressIconGenerator,
|
||||
resourceManager: ResourceManager,
|
||||
chainRegistry: ChainRegistry,
|
||||
@GenericLedger messageCommandFormatter: MessageCommandFormatter,
|
||||
evmAlertFormatter: GenericLedgerEvmAlertFormatter,
|
||||
addressActionsMixinFactory: AddressActionsMixin.Factory
|
||||
): ViewModel {
|
||||
val selectLedgerAddressPayload = SelectLedgerAddressPayload(payload.deviceId, substrateChainId = Chain.Geneses.POLKADOT)
|
||||
|
||||
return AddEvmGenericLedgerAccountSelectAddressViewModel(
|
||||
router = router,
|
||||
payload = payload,
|
||||
selectAddressLedgerInteractor = selectAddressLedgerInteractor,
|
||||
addressIconGenerator = addressIconGenerator,
|
||||
resourceManager = resourceManager,
|
||||
chainRegistry = chainRegistry,
|
||||
selectLedgerAddressPayload = selectLedgerAddressPayload,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
addAccountInteractor = addAccountInteractor,
|
||||
evmAlertFormatter = evmAlertFormatter,
|
||||
addressActionsMixinFactory = addressActionsMixinFactory
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(fragment: Fragment, viewModelFactory: ViewModelProvider.Factory): AddEvmGenericLedgerAccountSelectAddressViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(AddEvmGenericLedgerAccountSelectAddressViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectLedger
|
||||
|
||||
import android.os.Bundle
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.LedgerFeatureComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerFragment
|
||||
|
||||
class AddEvmAccountSelectGenericLedgerFragment : SelectLedgerFragment<AddEvmAccountSelectGenericLedgerViewModel>() {
|
||||
|
||||
companion object {
|
||||
private const val KEY_ADD_ACCOUNT_PAYLOAD = "AddEvmAccountSelectGenericLedgerFragment.Payload"
|
||||
|
||||
fun getBundle(payload: AddEvmAccountSelectGenericLedgerPayload): Bundle {
|
||||
return Bundle().apply {
|
||||
putParcelable(KEY_ADD_ACCOUNT_PAYLOAD, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<LedgerFeatureComponent>(requireContext(), LedgerFeatureApi::class.java)
|
||||
.addEvmAccountSelectGenericLedgerComponentFactory()
|
||||
.create(this, argument(KEY_ADD_ACCOUNT_PAYLOAD))
|
||||
.inject(this)
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectLedger
|
||||
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerPayload
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class AddEvmAccountSelectGenericLedgerPayload(val metaId: Long) : SelectLedgerPayload {
|
||||
|
||||
@IgnoredOnParcel
|
||||
override val connectionMode: SelectLedgerPayload.ConnectionMode = SelectLedgerPayload.ConnectionMode.ALL
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectLedger
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager
|
||||
import io.novafoundation.nova.common.utils.event
|
||||
import io.novafoundation.nova.common.utils.location.LocationManager
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectAddress.AddEvmGenericLedgerAccountSelectAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers.LedgerDeviceFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerViewModel
|
||||
|
||||
class AddEvmAccountSelectGenericLedgerViewModel(
|
||||
private val router: LedgerRouter,
|
||||
private val payload: AddEvmAccountSelectGenericLedgerPayload,
|
||||
private val messageCommandFormatter: MessageCommandFormatter,
|
||||
discoveryService: LedgerDeviceDiscoveryService,
|
||||
permissionsAsker: PermissionsAsker.Presentation,
|
||||
bluetoothManager: BluetoothManager,
|
||||
locationManager: LocationManager,
|
||||
resourceManager: ResourceManager,
|
||||
messageFormatter: LedgerMessageFormatter,
|
||||
ledgerDeviceFormatter: LedgerDeviceFormatter
|
||||
) : SelectLedgerViewModel(
|
||||
discoveryService = discoveryService,
|
||||
permissionsAsker = permissionsAsker,
|
||||
bluetoothManager = bluetoothManager,
|
||||
locationManager = locationManager,
|
||||
router = router,
|
||||
resourceManager = resourceManager,
|
||||
messageFormatter = messageFormatter,
|
||||
ledgerDeviceFormatter = ledgerDeviceFormatter,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
payload = payload
|
||||
) {
|
||||
|
||||
override suspend fun verifyConnection(device: LedgerDevice) {
|
||||
ledgerMessageCommands.value = messageCommandFormatter.hideCommand().event()
|
||||
|
||||
val payload = AddEvmGenericLedgerAccountSelectAddressPayload(payload.metaId, device.id)
|
||||
router.openAddGenericEvmAddressSelectAddress(payload)
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectLedger.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectLedger.AddEvmAccountSelectGenericLedgerFragment
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectLedger.AddEvmAccountSelectGenericLedgerPayload
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
AddEvmAccountSelectGenericLedgerModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface AddEvmAccountSelectGenericLedgerComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: AddEvmAccountSelectGenericLedgerPayload,
|
||||
): AddEvmAccountSelectGenericLedgerComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: AddEvmAccountSelectGenericLedgerFragment)
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectLedger.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.di.modules.shared.PermissionAskerForFragmentModule
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager
|
||||
import io.novafoundation.nova.common.utils.location.LocationManager
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.annotations.GenericLedger
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectLedger.AddEvmAccountSelectGenericLedgerPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.generic.selectLedger.AddEvmAccountSelectGenericLedgerViewModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers.LedgerDeviceFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter
|
||||
|
||||
@Module(includes = [ViewModelModule::class, PermissionAskerForFragmentModule::class])
|
||||
class AddEvmAccountSelectGenericLedgerModule {
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(AddEvmAccountSelectGenericLedgerViewModel::class)
|
||||
fun provideViewModel(
|
||||
payload: AddEvmAccountSelectGenericLedgerPayload,
|
||||
discoveryService: LedgerDeviceDiscoveryService,
|
||||
permissionsAsker: PermissionsAsker.Presentation,
|
||||
bluetoothManager: BluetoothManager,
|
||||
locationManager: LocationManager,
|
||||
router: LedgerRouter,
|
||||
resourceManager: ResourceManager,
|
||||
ledgerDeviceFormatter: LedgerDeviceFormatter,
|
||||
@GenericLedger messageFormatter: LedgerMessageFormatter,
|
||||
@GenericLedger messageCommandFormatter: MessageCommandFormatter
|
||||
): ViewModel {
|
||||
return AddEvmAccountSelectGenericLedgerViewModel(
|
||||
discoveryService = discoveryService,
|
||||
permissionsAsker = permissionsAsker,
|
||||
bluetoothManager = bluetoothManager,
|
||||
locationManager = locationManager,
|
||||
router = router,
|
||||
resourceManager = resourceManager,
|
||||
payload = payload,
|
||||
messageFormatter = messageFormatter,
|
||||
ledgerDeviceFormatter = ledgerDeviceFormatter,
|
||||
messageCommandFormatter = messageCommandFormatter
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(fragment: Fragment, viewModelFactory: ViewModelProvider.Factory): AddEvmAccountSelectGenericLedgerViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(AddEvmAccountSelectGenericLedgerViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress
|
||||
|
||||
import android.os.Bundle
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.LedgerFeatureComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectAddressLedgerFragment
|
||||
|
||||
class AddLedgerChainAccountSelectAddressFragment : SelectAddressLedgerFragment<AddLedgerChainAccountSelectAddressViewModel>() {
|
||||
|
||||
companion object {
|
||||
private const val PAYLOAD_KEY = "AddChainAccountSelectAddressLedgerFragment.Payload"
|
||||
|
||||
fun getBundle(payload: AddLedgerChainAccountSelectAddressPayload): Bundle {
|
||||
return Bundle().apply {
|
||||
putParcelable(PAYLOAD_KEY, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<LedgerFeatureComponent>(requireContext(), LedgerFeatureApi::class.java)
|
||||
.addChainAccountSelectAddressComponentFactory()
|
||||
.create(this, argument(PAYLOAD_KEY))
|
||||
.inject(this)
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class AddLedgerChainAccountSelectAddressPayload(
|
||||
val chainId: ChainId,
|
||||
val metaId: Long,
|
||||
val deviceId: String,
|
||||
) : Parcelable
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress
|
||||
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LedgerVariant
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.AddressActionsMixin
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.addChain.legacy.AddLedgerChainAccountInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.LedgerAccount
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectAddressLedgerViewModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.model.AddressVerificationMode
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class AddLedgerChainAccountSelectAddressViewModel(
|
||||
private val router: LedgerRouter,
|
||||
private val payload: AddLedgerChainAccountSelectAddressPayload,
|
||||
private val addChainAccountInteractor: AddLedgerChainAccountInteractor,
|
||||
selectAddressLedgerInteractor: SelectAddressLedgerInteractor,
|
||||
addressIconGenerator: AddressIconGenerator,
|
||||
resourceManager: ResourceManager,
|
||||
chainRegistry: ChainRegistry,
|
||||
selectLedgerAddressPayload: SelectLedgerAddressPayload,
|
||||
messageCommandFormatter: MessageCommandFormatter,
|
||||
addressActionsMixinFactory: AddressActionsMixin.Factory
|
||||
) : SelectAddressLedgerViewModel(
|
||||
router = router,
|
||||
interactor = selectAddressLedgerInteractor,
|
||||
addressIconGenerator = addressIconGenerator,
|
||||
resourceManager = resourceManager,
|
||||
payload = selectLedgerAddressPayload,
|
||||
chainRegistry = chainRegistry,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
addressActionsMixinFactory = addressActionsMixinFactory
|
||||
) {
|
||||
|
||||
override val ledgerVariant: LedgerVariant = LedgerVariant.LEGACY
|
||||
|
||||
override val addressVerificationMode = AddressVerificationMode.Enabled(addressSchemesToVerify = listOf(AddressScheme.SUBSTRATE))
|
||||
|
||||
override fun onAccountVerified(account: LedgerAccount) {
|
||||
launch {
|
||||
val result = withContext(Dispatchers.Default) {
|
||||
addChainAccountInteractor.addChainAccount(payload.metaId, payload.chainId, account.substrate)
|
||||
}
|
||||
|
||||
result.onSuccess {
|
||||
router.openMain()
|
||||
}.onFailure(::showError)
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress.AddLedgerChainAccountSelectAddressFragment
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress.AddLedgerChainAccountSelectAddressPayload
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
AddLedgerChainAccountSelectAddressModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface AddLedgerChainAccountSelectAddressComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: AddLedgerChainAccountSelectAddressPayload,
|
||||
): AddLedgerChainAccountSelectAddressComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: AddLedgerChainAccountSelectAddressFragment)
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LegacyLedgerAddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.AddressActionsMixin
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.addChain.legacy.AddLedgerChainAccountInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.addChain.legacy.RealAddLedgerChainAccountInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress.AddLedgerChainAccountSelectAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress.AddLedgerChainAccountSelectAddressViewModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatterFactory
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatterFactory
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class AddLedgerChainAccountSelectAddressModule {
|
||||
|
||||
@Provides
|
||||
@ScreenScope
|
||||
fun provideInteractor(
|
||||
legacyLedgerAddAccountRepository: LegacyLedgerAddAccountRepository
|
||||
): AddLedgerChainAccountInteractor = RealAddLedgerChainAccountInteractor(legacyLedgerAddAccountRepository)
|
||||
|
||||
@Provides
|
||||
@ScreenScope
|
||||
fun provideSelectLedgerAddressPayload(
|
||||
screenPayload: AddLedgerChainAccountSelectAddressPayload
|
||||
): SelectLedgerAddressPayload = SelectLedgerAddressPayload(
|
||||
deviceId = screenPayload.deviceId,
|
||||
substrateChainId = screenPayload.chainId
|
||||
)
|
||||
|
||||
@Provides
|
||||
@ScreenScope
|
||||
fun provideMessageFormatter(
|
||||
screenPayload: AddLedgerChainAccountSelectAddressPayload,
|
||||
factory: LedgerMessageFormatterFactory,
|
||||
): LedgerMessageFormatter = factory.createLegacy(screenPayload.chainId, showAlerts = false)
|
||||
|
||||
@Provides
|
||||
@ScreenScope
|
||||
fun provideMessageCommandFormatter(
|
||||
messageFormatter: LedgerMessageFormatter,
|
||||
messageCommandFormatterFactory: MessageCommandFormatterFactory
|
||||
): MessageCommandFormatter = messageCommandFormatterFactory.create(messageFormatter)
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(AddLedgerChainAccountSelectAddressViewModel::class)
|
||||
fun provideViewModel(
|
||||
router: LedgerRouter,
|
||||
payload: AddLedgerChainAccountSelectAddressPayload,
|
||||
addChainAccountInteractor: AddLedgerChainAccountInteractor,
|
||||
selectAddressLedgerInteractor: SelectAddressLedgerInteractor,
|
||||
addressIconGenerator: AddressIconGenerator,
|
||||
resourceManager: ResourceManager,
|
||||
chainRegistry: ChainRegistry,
|
||||
selectLedgerAddressPayload: SelectLedgerAddressPayload,
|
||||
messageCommandFormatter: MessageCommandFormatter,
|
||||
addressActionsMixinFactory: AddressActionsMixin.Factory
|
||||
): ViewModel {
|
||||
return AddLedgerChainAccountSelectAddressViewModel(
|
||||
router = router,
|
||||
payload = payload,
|
||||
addChainAccountInteractor = addChainAccountInteractor,
|
||||
selectAddressLedgerInteractor = selectAddressLedgerInteractor,
|
||||
addressIconGenerator = addressIconGenerator,
|
||||
resourceManager = resourceManager,
|
||||
chainRegistry = chainRegistry,
|
||||
selectLedgerAddressPayload = selectLedgerAddressPayload,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
addressActionsMixinFactory = addressActionsMixinFactory
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(fragment: Fragment, viewModelFactory: ViewModelProvider.Factory): AddLedgerChainAccountSelectAddressViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(
|
||||
AddLedgerChainAccountSelectAddressViewModel::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectLedger
|
||||
|
||||
import android.os.Bundle
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.LedgerFeatureComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerFragment
|
||||
|
||||
class AddChainAccountSelectLedgerFragment : SelectLedgerFragment<AddChainAccountSelectLedgerViewModel>() {
|
||||
|
||||
companion object {
|
||||
private const val KEY_ADD_ACCOUNT_PAYLOAD = "AddChainAccountSelectLedgerFragment.Payload"
|
||||
|
||||
fun getBundle(payload: AddChainAccountSelectLedgerPayload): Bundle {
|
||||
return Bundle().apply {
|
||||
putParcelable(KEY_ADD_ACCOUNT_PAYLOAD, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<LedgerFeatureComponent>(requireContext(), LedgerFeatureApi::class.java)
|
||||
.addChainAccountSelectLedgerComponentFactory()
|
||||
.create(this, argument(KEY_ADD_ACCOUNT_PAYLOAD))
|
||||
.inject(this)
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectLedger
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.add.AddAccountPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerPayload
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class AddChainAccountSelectLedgerPayload(
|
||||
val addAccountPayload: AddAccountPayload.ChainAccount,
|
||||
override val connectionMode: SelectLedgerPayload.ConnectionMode
|
||||
) : SelectLedgerPayload
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectLedger
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager
|
||||
import io.novafoundation.nova.common.utils.event
|
||||
import io.novafoundation.nova.common.utils.location.LocationManager
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.migration.LedgerMigrationUseCase
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.migration.determineAppForLegacyAccount
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectAddress.AddLedgerChainAccountSelectAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers.LedgerDeviceFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerViewModel
|
||||
|
||||
class AddChainAccountSelectLedgerViewModel(
|
||||
private val migrationUseCase: LedgerMigrationUseCase,
|
||||
private val router: LedgerRouter,
|
||||
private val payload: AddChainAccountSelectLedgerPayload,
|
||||
private val messageCommandFormatter: MessageCommandFormatter,
|
||||
discoveryService: LedgerDeviceDiscoveryService,
|
||||
permissionsAsker: PermissionsAsker.Presentation,
|
||||
bluetoothManager: BluetoothManager,
|
||||
locationManager: LocationManager,
|
||||
resourceManager: ResourceManager,
|
||||
messageFormatter: LedgerMessageFormatter,
|
||||
ledgerDeviceFormatter: LedgerDeviceFormatter
|
||||
) : SelectLedgerViewModel(
|
||||
discoveryService = discoveryService,
|
||||
permissionsAsker = permissionsAsker,
|
||||
bluetoothManager = bluetoothManager,
|
||||
locationManager = locationManager,
|
||||
router = router,
|
||||
resourceManager = resourceManager,
|
||||
messageFormatter = messageFormatter,
|
||||
ledgerDeviceFormatter = ledgerDeviceFormatter,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
payload = payload
|
||||
) {
|
||||
|
||||
private val addAccountPayload = payload.addAccountPayload
|
||||
|
||||
override suspend fun verifyConnection(device: LedgerDevice) {
|
||||
ledgerMessageCommands.value = messageCommandFormatter.hideCommand().event()
|
||||
|
||||
val app = migrationUseCase.determineAppForLegacyAccount(addAccountPayload.chainId)
|
||||
|
||||
// ensure that address loads successfully
|
||||
app.getSubstrateAccount(device, addAccountPayload.chainId, accountIndex = 0, confirmAddress = false)
|
||||
|
||||
val payload = AddLedgerChainAccountSelectAddressPayload(addAccountPayload.chainId, addAccountPayload.metaId, device.id)
|
||||
router.openAddChainAccountSelectAddress(payload)
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectLedger.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectLedger.AddChainAccountSelectLedgerPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectLedger.AddChainAccountSelectLedgerFragment
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
AddChainAccountSelectLedgerModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface AddChainAccountSelectLedgerComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: AddChainAccountSelectLedgerPayload,
|
||||
): AddChainAccountSelectLedgerComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: AddChainAccountSelectLedgerFragment)
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectLedger.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.di.modules.shared.PermissionAskerForFragmentModule
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager
|
||||
import io.novafoundation.nova.common.utils.location.LocationManager
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.migration.LedgerMigrationUseCase
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectLedger.AddChainAccountSelectLedgerPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.legacy.selectLedger.AddChainAccountSelectLedgerViewModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatterFactory
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers.LedgerDeviceFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatterFactory
|
||||
|
||||
@Module(includes = [ViewModelModule::class, PermissionAskerForFragmentModule::class])
|
||||
class AddChainAccountSelectLedgerModule {
|
||||
|
||||
@Provides
|
||||
@ScreenScope
|
||||
fun provideMessageFormatter(
|
||||
payload: AddChainAccountSelectLedgerPayload,
|
||||
factory: LedgerMessageFormatterFactory,
|
||||
): LedgerMessageFormatter = factory.createLegacy(payload.addAccountPayload.chainId, showAlerts = false)
|
||||
|
||||
@Provides
|
||||
@ScreenScope
|
||||
fun provideMessageCommandFormatter(
|
||||
messageFormatter: LedgerMessageFormatter,
|
||||
messageCommandFormatterFactory: MessageCommandFormatterFactory
|
||||
): MessageCommandFormatter = messageCommandFormatterFactory.create(messageFormatter)
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(AddChainAccountSelectLedgerViewModel::class)
|
||||
fun provideViewModel(
|
||||
migrationUseCase: LedgerMigrationUseCase,
|
||||
payload: AddChainAccountSelectLedgerPayload,
|
||||
discoveryService: LedgerDeviceDiscoveryService,
|
||||
permissionsAsker: PermissionsAsker.Presentation,
|
||||
bluetoothManager: BluetoothManager,
|
||||
locationManager: LocationManager,
|
||||
router: LedgerRouter,
|
||||
resourceManager: ResourceManager,
|
||||
messageFormatter: LedgerMessageFormatter,
|
||||
ledgerDeviceFormatter: LedgerDeviceFormatter,
|
||||
messageCommandFormatter: MessageCommandFormatter
|
||||
): ViewModel {
|
||||
return AddChainAccountSelectLedgerViewModel(
|
||||
migrationUseCase = migrationUseCase,
|
||||
discoveryService = discoveryService,
|
||||
permissionsAsker = permissionsAsker,
|
||||
bluetoothManager = bluetoothManager,
|
||||
locationManager = locationManager,
|
||||
router = router,
|
||||
resourceManager = resourceManager,
|
||||
payload = payload,
|
||||
messageFormatter = messageFormatter,
|
||||
ledgerDeviceFormatter = ledgerDeviceFormatter,
|
||||
messageCommandFormatter = messageCommandFormatter
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(fragment: Fragment, viewModelFactory: ViewModelProvider.Factory): AddChainAccountSelectLedgerViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(
|
||||
AddChainAccountSelectLedgerViewModel::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import io.novafoundation.nova.common.utils.formatting.TimerValue
|
||||
import io.novafoundation.nova.common.utils.setTextColorRes
|
||||
import io.novafoundation.nova.common.utils.setVisible
|
||||
import io.novafoundation.nova.common.view.AlertModel
|
||||
import io.novafoundation.nova.common.view.bottomSheet.BaseBottomSheet
|
||||
import io.novafoundation.nova.common.view.setModelOrHide
|
||||
import io.novafoundation.nova.common.view.startTimer
|
||||
import io.novafoundation.nova.common.view.stopTimer
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.databinding.FragmentLedgerMessageBinding
|
||||
|
||||
sealed class LedgerMessageCommand {
|
||||
|
||||
companion object
|
||||
|
||||
object Hide : LedgerMessageCommand()
|
||||
|
||||
sealed class Show(
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
val graphics: Graphics,
|
||||
val alert: AlertModel?,
|
||||
val onCancel: () -> Unit,
|
||||
) : LedgerMessageCommand() {
|
||||
|
||||
sealed class Error(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
graphics: Graphics,
|
||||
alert: AlertModel?,
|
||||
onCancel: () -> Unit,
|
||||
) : Show(title, subtitle, graphics, alert, onCancel) {
|
||||
|
||||
class RecoverableError(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
graphics: Graphics,
|
||||
alert: AlertModel?,
|
||||
onCancel: () -> Unit,
|
||||
val onRetry: () -> Unit
|
||||
) : Error(title, subtitle, graphics, alert, onCancel)
|
||||
|
||||
class FatalError(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
graphics: Graphics,
|
||||
alert: AlertModel?,
|
||||
val onConfirm: () -> Unit,
|
||||
onCancel: () -> Unit = onConfirm, // when error is fatal, confirm is the same as hide by default
|
||||
) : Error(title, subtitle, graphics, alert, onCancel)
|
||||
}
|
||||
|
||||
class Info(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
graphics: Graphics,
|
||||
onCancel: () -> Unit,
|
||||
alert: AlertModel?,
|
||||
val footer: Footer
|
||||
) : Show(title, subtitle, graphics, alert, onCancel)
|
||||
}
|
||||
|
||||
sealed class Footer {
|
||||
|
||||
class Timer(
|
||||
val timerValue: TimerValue,
|
||||
val closeToExpire: (TimerValue) -> Boolean,
|
||||
val timerFinished: () -> Unit,
|
||||
@StringRes val messageFormat: Int
|
||||
) : Footer()
|
||||
|
||||
class Value(val value: String) : Footer()
|
||||
|
||||
class Rows(
|
||||
val first: Column,
|
||||
val second: Column
|
||||
) : Footer() {
|
||||
|
||||
class Column(
|
||||
val label: String,
|
||||
val value: String
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Graphics(@DrawableRes val ledgerImageRes: Int)
|
||||
}
|
||||
|
||||
class LedgerMessageBottomSheet(
|
||||
context: Context,
|
||||
) : BaseBottomSheet<FragmentLedgerMessageBinding>(context) {
|
||||
|
||||
override val binder = FragmentLedgerMessageBinding.inflate(LayoutInflater.from(context))
|
||||
|
||||
val container: View
|
||||
get() = binder.ledgerMessageContainer
|
||||
|
||||
fun receiveCommand(command: LedgerMessageCommand) {
|
||||
binder.ledgerMessageActions.setVisible(command is LedgerMessageCommand.Show.Error)
|
||||
binder.ledgerMessageCancel.setVisible(command is LedgerMessageCommand.Show.Error.RecoverableError)
|
||||
setupFooterVisibility(command is LedgerMessageCommand.Show.Info)
|
||||
|
||||
when (command) {
|
||||
LedgerMessageCommand.Hide -> dismiss()
|
||||
|
||||
is LedgerMessageCommand.Show.Error.FatalError -> {
|
||||
setupBaseShow(command)
|
||||
binder.ledgerMessageConfirm.setOnClickListener { command.onConfirm() }
|
||||
binder.ledgerMessageConfirm.setText(R.string.common_ok_back)
|
||||
}
|
||||
|
||||
is LedgerMessageCommand.Show.Error.RecoverableError -> {
|
||||
setupBaseShow(command)
|
||||
binder.ledgerMessageConfirm.setOnClickListener { command.onRetry() }
|
||||
binder.ledgerMessageConfirm.setText(R.string.common_retry)
|
||||
binder.ledgerMessageCancel.setOnClickListener { command.onCancel() }
|
||||
}
|
||||
|
||||
is LedgerMessageCommand.Show.Info -> {
|
||||
setupBaseShow(command)
|
||||
showFooter(command.footer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFooterVisibility(visible: Boolean) {
|
||||
binder.ledgerMessageFooterMessage.setVisible(visible)
|
||||
binder.ledgerMessageFooterColumns.setVisible(visible)
|
||||
|
||||
if (!visible) {
|
||||
binder.ledgerMessageFooterMessage.stopTimer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFooter(footer: LedgerMessageCommand.Footer) {
|
||||
binder.ledgerMessageFooterMessage.setVisible(footer !is LedgerMessageCommand.Footer.Rows)
|
||||
binder.ledgerMessageFooterColumns.setVisible(footer is LedgerMessageCommand.Footer.Rows)
|
||||
|
||||
when (footer) {
|
||||
is LedgerMessageCommand.Footer.Value -> {
|
||||
binder.ledgerMessageFooterMessage.text = footer.value
|
||||
}
|
||||
|
||||
is LedgerMessageCommand.Footer.Timer -> {
|
||||
binder.ledgerMessageFooterMessage.startTimer(
|
||||
value = footer.timerValue,
|
||||
customMessageFormat = footer.messageFormat,
|
||||
onTick = { view, _ ->
|
||||
val textColorRes = if (footer.closeToExpire(footer.timerValue)) R.color.text_negative else R.color.text_secondary
|
||||
|
||||
view.setTextColorRes(textColorRes)
|
||||
},
|
||||
onFinish = { footer.timerFinished() }
|
||||
)
|
||||
}
|
||||
|
||||
is LedgerMessageCommand.Footer.Rows -> {
|
||||
binder.ledgerMessageFooterTitle1.text = footer.first.label
|
||||
binder.ledgerMessageFooterMessage1.text = footer.first.value
|
||||
|
||||
binder.ledgerMessageFooterTitle2.text = footer.second.label
|
||||
binder.ledgerMessageFooterMessage2.text = footer.second.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupBaseShow(command: LedgerMessageCommand.Show) {
|
||||
binder.ledgerMessageTitle.text = command.title
|
||||
binder.ledgerMessageSubtitle.text = command.subtitle
|
||||
binder.ledgerMessageImage.setImageResource(command.graphics.ledgerImageRes)
|
||||
binder.ledgerMessageAlert.setModelOrHide(command.alert)
|
||||
|
||||
setOnCancelListener { command.onCancel() }
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
|
||||
interface LedgerMessagePresentable {
|
||||
|
||||
fun presentCommand(command: LedgerMessageCommand, context: Context)
|
||||
}
|
||||
|
||||
interface LedgerMessageCommands {
|
||||
|
||||
val ledgerMessageCommands: MutableLiveData<Event<LedgerMessageCommand>>
|
||||
}
|
||||
|
||||
class SingleSheetLedgerMessagePresentable : LedgerMessagePresentable {
|
||||
|
||||
private var bottomSheet: LedgerMessageBottomSheet? = null
|
||||
|
||||
override fun presentCommand(command: LedgerMessageCommand, context: Context) {
|
||||
when {
|
||||
bottomSheet == null && command is LedgerMessageCommand.Show -> {
|
||||
bottomSheet = LedgerMessageBottomSheet(context)
|
||||
bottomSheet?.receiveCommand(command)
|
||||
bottomSheet?.show()
|
||||
}
|
||||
|
||||
bottomSheet != null && command is LedgerMessageCommand.Show -> {
|
||||
bottomSheet?.container?.stateChangeTransition {
|
||||
bottomSheet?.receiveCommand(command)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
bottomSheet?.receiveCommand(command)
|
||||
bottomSheet = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun View.stateChangeTransition(onChangeState: () -> Unit) {
|
||||
animate()
|
||||
.alpha(0f)
|
||||
.withEndAction {
|
||||
onChangeState()
|
||||
|
||||
animate()
|
||||
.alpha(1f)
|
||||
.start()
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
fun <F, V> F.setupLedgerMessages(presentable: LedgerMessagePresentable)
|
||||
where F : BaseFragment<V, *>, V : LedgerMessageCommands {
|
||||
viewModel.ledgerMessageCommands.observeEvent {
|
||||
presentable.presentCommand(it, requireContext())
|
||||
}
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet
|
||||
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
import io.novafoundation.nova.common.address.format.AddressSchemeFormatter
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.second
|
||||
import io.novafoundation.nova.common.view.AlertModel
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerApplicationResponse
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand.Footer.Rows.Column
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand.Show.Error.RecoverableError
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers.LedgerDeviceFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter.MessageKind
|
||||
import io.novafoundation.nova.runtime.extrinsic.ValidityPeriod
|
||||
import io.novafoundation.nova.runtime.extrinsic.closeToExpire
|
||||
|
||||
class MessageCommandFormatterFactory(
|
||||
private val resourceManager: ResourceManager,
|
||||
private val deviceMapper: LedgerDeviceFormatter,
|
||||
private val addressSchemeFormatter: AddressSchemeFormatter
|
||||
) {
|
||||
|
||||
fun create(messageFormatter: LedgerMessageFormatter): MessageCommandFormatter {
|
||||
return MessageCommandFormatter(resourceManager, deviceMapper, messageFormatter, addressSchemeFormatter)
|
||||
}
|
||||
}
|
||||
|
||||
class MessageCommandFormatter(
|
||||
private val resourceManager: ResourceManager,
|
||||
private val deviceMapper: LedgerDeviceFormatter,
|
||||
private val messageFormatter: LedgerMessageFormatter,
|
||||
private val addressSchemeFormatter: AddressSchemeFormatter,
|
||||
) {
|
||||
|
||||
context(Browserable.Presentation)
|
||||
suspend fun unknownError(
|
||||
device: LedgerDevice,
|
||||
onRetry: () -> Unit,
|
||||
onCancel: () -> Unit
|
||||
): LedgerMessageCommand {
|
||||
return retryCommand(
|
||||
title = resourceManager.getString(R.string.ledger_error_general_title),
|
||||
subtitle = resourceManager.getString(R.string.ledger_error_general_message),
|
||||
alertModel = messageFormatter.alertForKind(MessageKind.OTHER),
|
||||
device = device,
|
||||
onRetry = onRetry,
|
||||
onCancel = onCancel
|
||||
)
|
||||
}
|
||||
|
||||
context(Browserable.Presentation)
|
||||
suspend fun substrateApplicationError(
|
||||
reason: LedgerApplicationResponse,
|
||||
device: LedgerDevice,
|
||||
onCancel: () -> Unit,
|
||||
onRetry: () -> Unit
|
||||
): LedgerMessageCommand {
|
||||
val errorTitle: String
|
||||
val errorMessage: String
|
||||
val alert: AlertModel?
|
||||
|
||||
when (reason) {
|
||||
LedgerApplicationResponse.APP_NOT_OPEN, LedgerApplicationResponse.WRONG_APP_OPEN -> {
|
||||
val appName = messageFormatter.appName()
|
||||
|
||||
errorTitle = resourceManager.getString(R.string.ledger_error_app_not_launched_title, appName)
|
||||
errorMessage = resourceManager.getString(R.string.ledger_error_app_not_launched_message, appName)
|
||||
alert = messageFormatter.alertForKind(MessageKind.APP_NOT_OPEN)
|
||||
}
|
||||
|
||||
LedgerApplicationResponse.TRANSACTION_REJECTED -> {
|
||||
errorTitle = resourceManager.getString(R.string.ledger_error_app_cancelled_title)
|
||||
errorMessage = resourceManager.getString(R.string.ledger_error_app_cancelled_message)
|
||||
alert = messageFormatter.alertForKind(MessageKind.OTHER)
|
||||
}
|
||||
|
||||
else -> {
|
||||
errorTitle = resourceManager.getString(R.string.ledger_error_general_title)
|
||||
errorMessage = resourceManager.getString(R.string.ledger_error_general_message)
|
||||
alert = messageFormatter.alertForKind(MessageKind.OTHER)
|
||||
}
|
||||
}
|
||||
|
||||
return retryCommand(errorTitle, errorMessage, device, alert, onCancel, onRetry)
|
||||
}
|
||||
|
||||
fun retryCommand(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
device: LedgerDevice,
|
||||
alertModel: AlertModel?,
|
||||
onCancel: () -> Unit,
|
||||
onRetry: () -> Unit
|
||||
): LedgerMessageCommand {
|
||||
val deviceMapper = deviceMapper.createDelegate(device)
|
||||
return RecoverableError(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
alert = alertModel,
|
||||
onCancel = onCancel,
|
||||
onRetry = onRetry,
|
||||
graphics = deviceMapper.getErrorImage()
|
||||
)
|
||||
}
|
||||
|
||||
context(Browserable.Presentation)
|
||||
suspend fun fatalErrorCommand(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
device: LedgerDevice,
|
||||
onCancel: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
): LedgerMessageCommand {
|
||||
val deviceMapper = deviceMapper.createDelegate(device)
|
||||
|
||||
return LedgerMessageCommand.Show.Error.FatalError(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
alert = messageFormatter.alertForKind(MessageKind.OTHER),
|
||||
graphics = deviceMapper.getErrorImage(),
|
||||
onCancel = onCancel,
|
||||
onConfirm = onConfirm
|
||||
)
|
||||
}
|
||||
|
||||
context(Browserable.Presentation)
|
||||
suspend fun signCommand(
|
||||
validityPeriod: ValidityPeriod,
|
||||
device: LedgerDevice,
|
||||
onTimeFinished: () -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
): LedgerMessageCommand {
|
||||
val deviceMapper = deviceMapper.createDelegate(device)
|
||||
|
||||
return LedgerMessageCommand.Show.Info(
|
||||
title = resourceManager.getString(R.string.ledger_review_approve_title),
|
||||
subtitle = deviceMapper.getSignMessage(),
|
||||
onCancel = onCancel,
|
||||
alert = messageFormatter.alertForKind(MessageKind.OTHER),
|
||||
graphics = deviceMapper.getSignImage(),
|
||||
footer = LedgerMessageCommand.Footer.Timer(
|
||||
timerValue = validityPeriod.period,
|
||||
closeToExpire = { validityPeriod.closeToExpire() },
|
||||
timerFinished = { onTimeFinished() },
|
||||
messageFormat = R.string.ledger_sign_transaction_validity_format
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun reviewAddressCommand(
|
||||
addresses: List<Pair<AddressScheme, String>>,
|
||||
device: LedgerDevice,
|
||||
onCancel: () -> Unit,
|
||||
): LedgerMessageCommand {
|
||||
val deviceMapper = deviceMapper.createDelegate(device)
|
||||
|
||||
val footer: LedgerMessageCommand.Footer
|
||||
val subtitle: String
|
||||
|
||||
when (addresses.size) {
|
||||
0 -> error("At least one address should be not null")
|
||||
|
||||
1 -> {
|
||||
footer = LedgerMessageCommand.Footer.Value(
|
||||
value = addresses.single().second.toTwoLinesAddress(),
|
||||
)
|
||||
subtitle = deviceMapper.getReviewAddressMessage()
|
||||
}
|
||||
|
||||
2 -> {
|
||||
footer = LedgerMessageCommand.Footer.Rows(
|
||||
first = rowFor(addresses.first()),
|
||||
second = rowFor(addresses.second())
|
||||
)
|
||||
subtitle = deviceMapper.getReviewAddressesMessage()
|
||||
}
|
||||
|
||||
else -> error("Too many addresses passed: ${addresses.size}")
|
||||
}
|
||||
|
||||
return LedgerMessageCommand.Show.Info(
|
||||
title = resourceManager.getString(R.string.ledger_review_approve_title),
|
||||
subtitle = subtitle,
|
||||
onCancel = onCancel,
|
||||
alert = null,
|
||||
graphics = deviceMapper.getApproveImage(),
|
||||
footer = footer
|
||||
)
|
||||
}
|
||||
|
||||
fun hideCommand(): LedgerMessageCommand {
|
||||
return LedgerMessageCommand.Hide
|
||||
}
|
||||
|
||||
private fun String.toTwoLinesAddress(): String {
|
||||
val middle = length / 2
|
||||
return substring(0, middle) + "\n" + substring(middle)
|
||||
}
|
||||
|
||||
private fun rowFor(addressWithScheme: Pair<AddressScheme, String>): Column {
|
||||
val label = addressSchemeFormatter.addressLabel(addressWithScheme.first)
|
||||
return Column(label, addressWithScheme.second.toTwoLinesAddress())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun createLedgerReviewAddresses(
|
||||
allowedAddressSchemes: List<AddressScheme>,
|
||||
vararg allAddresses: Pair<AddressScheme, String?>
|
||||
): List<Pair<AddressScheme, String>> {
|
||||
return allAddresses.filter { it.first in allowedAddressSchemes && it.second != null } as List<Pair<AddressScheme, String>>
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDeviceType
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
|
||||
class LedgerDeviceFormatter(private val resourceManager: ResourceManager) {
|
||||
|
||||
fun formatName(device: LedgerDevice): String = createDelegate(device).getName()
|
||||
|
||||
fun createDelegate(device: LedgerDevice): LedgerDeviceMapperDelegate {
|
||||
return when (device.deviceType) {
|
||||
LedgerDeviceType.STAX -> LedgerStaxMapperDelegate(resourceManager, device)
|
||||
LedgerDeviceType.FLEX -> LedgerFlexMapperDelegate(resourceManager, device)
|
||||
LedgerDeviceType.NANO_X -> LedgerNanoXUIMapperDelegate(resourceManager, device)
|
||||
LedgerDeviceType.NANO_S_PLUS -> LedgerNanoSPlusMapperDelegate(resourceManager, device)
|
||||
LedgerDeviceType.NANO_S -> LedgerNanoSMapperDelegate(resourceManager, device)
|
||||
LedgerDeviceType.NANO_GEN5 -> LedgerNanoGen5UIMapperDelegate(resourceManager, device)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface LedgerDeviceMapperDelegate {
|
||||
|
||||
fun getName(): String
|
||||
|
||||
fun getApproveImage(): LedgerMessageCommand.Graphics
|
||||
|
||||
fun getErrorImage(): LedgerMessageCommand.Graphics
|
||||
|
||||
fun getSignImage(): LedgerMessageCommand.Graphics
|
||||
|
||||
fun getReviewAddressMessage(): String
|
||||
|
||||
fun getReviewAddressesMessage(): String
|
||||
|
||||
fun getSignMessage(): String
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
|
||||
class LedgerFlexMapperDelegate(
|
||||
private val resourceManager: ResourceManager,
|
||||
private val device: LedgerDevice
|
||||
) : LedgerDeviceMapperDelegate {
|
||||
|
||||
override fun getName(): String {
|
||||
return device.name ?: resourceManager.getString(R.string.ledger_device_flex)
|
||||
}
|
||||
|
||||
override fun getApproveImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_flex_approve)
|
||||
}
|
||||
|
||||
override fun getSignImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_flex_sign)
|
||||
}
|
||||
|
||||
override fun getErrorImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_flex_error)
|
||||
}
|
||||
|
||||
override fun getReviewAddressMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_address_message_confirm_button, getName())
|
||||
}
|
||||
|
||||
override fun getReviewAddressesMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_addresses_message_confirm_button, getName())
|
||||
}
|
||||
|
||||
override fun getSignMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_hold_to_sign, getName())
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
|
||||
class LedgerNanoGen5UIMapperDelegate(
|
||||
private val resourceManager: ResourceManager,
|
||||
private val device: LedgerDevice
|
||||
) : LedgerDeviceMapperDelegate {
|
||||
|
||||
override fun getName(): String {
|
||||
return device.name ?: resourceManager.getString(R.string.ledger_device_nano_gen5)
|
||||
}
|
||||
|
||||
override fun getApproveImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_nano_gen5_approve)
|
||||
}
|
||||
|
||||
override fun getSignImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_nano_gen5_sign)
|
||||
}
|
||||
|
||||
override fun getErrorImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_nano_gen5_error)
|
||||
}
|
||||
|
||||
override fun getReviewAddressMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_address_message_confirm_button, getName())
|
||||
}
|
||||
|
||||
override fun getReviewAddressesMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_addresses_message_confirm_button, getName())
|
||||
}
|
||||
|
||||
override fun getSignMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_hold_to_sign, getName())
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
|
||||
class LedgerNanoSMapperDelegate(
|
||||
private val resourceManager: ResourceManager,
|
||||
private val device: LedgerDevice
|
||||
) : LedgerDeviceMapperDelegate {
|
||||
|
||||
override fun getName(): String {
|
||||
return device.name ?: resourceManager.getString(R.string.ledger_device_nano_s)
|
||||
}
|
||||
|
||||
override fun getApproveImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_nano_s_approve)
|
||||
}
|
||||
|
||||
override fun getSignImage(): LedgerMessageCommand.Graphics {
|
||||
return getApproveImage()
|
||||
}
|
||||
|
||||
override fun getErrorImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_nano_s_error)
|
||||
}
|
||||
|
||||
override fun getReviewAddressMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_address_message_both_buttons, getName())
|
||||
}
|
||||
|
||||
override fun getReviewAddressesMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_addresses_message_both_buttons, getName())
|
||||
}
|
||||
|
||||
override fun getSignMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_sign_approve_message, getName())
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
|
||||
class LedgerNanoSPlusMapperDelegate(
|
||||
private val resourceManager: ResourceManager,
|
||||
private val device: LedgerDevice
|
||||
) : LedgerDeviceMapperDelegate {
|
||||
|
||||
override fun getName(): String {
|
||||
return device.name ?: resourceManager.getString(R.string.ledger_device_nano_s_plus)
|
||||
}
|
||||
|
||||
override fun getApproveImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_nano_s_approve)
|
||||
}
|
||||
|
||||
override fun getSignImage(): LedgerMessageCommand.Graphics {
|
||||
return getApproveImage()
|
||||
}
|
||||
|
||||
override fun getErrorImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_nano_s_error)
|
||||
}
|
||||
|
||||
override fun getReviewAddressMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_address_message_both_buttons, getName())
|
||||
}
|
||||
|
||||
override fun getReviewAddressesMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_addresses_message_both_buttons, getName())
|
||||
}
|
||||
|
||||
override fun getSignMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_sign_approve_message, getName())
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
|
||||
class LedgerNanoXUIMapperDelegate(
|
||||
private val resourceManager: ResourceManager,
|
||||
private val device: LedgerDevice
|
||||
) : LedgerDeviceMapperDelegate {
|
||||
|
||||
override fun getName(): String {
|
||||
return device.name ?: resourceManager.getString(R.string.ledger_device_nano_x)
|
||||
}
|
||||
|
||||
override fun getApproveImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_nano_x_approve)
|
||||
}
|
||||
|
||||
override fun getSignImage(): LedgerMessageCommand.Graphics {
|
||||
return getApproveImage()
|
||||
}
|
||||
|
||||
override fun getErrorImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_nano_x_error)
|
||||
}
|
||||
|
||||
override fun getReviewAddressMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_address_message_both_buttons, getName())
|
||||
}
|
||||
|
||||
override fun getReviewAddressesMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_addresses_message_both_buttons, getName())
|
||||
}
|
||||
|
||||
override fun getSignMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_sign_approve_message, getName())
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
|
||||
class LedgerStaxMapperDelegate(
|
||||
private val resourceManager: ResourceManager,
|
||||
private val device: LedgerDevice
|
||||
) : LedgerDeviceMapperDelegate {
|
||||
|
||||
override fun getName(): String {
|
||||
return device.name ?: resourceManager.getString(R.string.ledger_device_stax)
|
||||
}
|
||||
|
||||
override fun getApproveImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_stax_approve)
|
||||
}
|
||||
|
||||
override fun getSignImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_stax_sign)
|
||||
}
|
||||
|
||||
override fun getErrorImage(): LedgerMessageCommand.Graphics {
|
||||
return LedgerMessageCommand.Graphics(R.drawable.ic_ledger_stax_error)
|
||||
}
|
||||
|
||||
override fun getReviewAddressMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_address_message_confirm_button, getName())
|
||||
}
|
||||
|
||||
override fun getReviewAddressesMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_verify_addresses_message_confirm_button, getName())
|
||||
}
|
||||
|
||||
override fun getSignMessage(): String {
|
||||
return resourceManager.getString(R.string.ledger_hold_to_sign, getName())
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.errors
|
||||
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.utils.event
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.SubstrateLedgerApplicationError
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommands
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
fun <V> V.handleLedgerError(
|
||||
reason: Throwable,
|
||||
device: LedgerDevice,
|
||||
commandFormatter: MessageCommandFormatter,
|
||||
onRetry: () -> Unit
|
||||
) where V : BaseViewModel, V : LedgerMessageCommands, V : Browserable.Presentation {
|
||||
reason.printStackTrace()
|
||||
|
||||
launch {
|
||||
when (reason) {
|
||||
is CancellationException -> {
|
||||
// do nothing on coroutines cancellation
|
||||
}
|
||||
|
||||
is SubstrateLedgerApplicationError.Response -> {
|
||||
ledgerMessageCommands.value = commandFormatter.substrateApplicationError(
|
||||
reason = reason.response,
|
||||
device = device,
|
||||
onCancel = ::hide,
|
||||
onRetry = onRetry
|
||||
).event()
|
||||
}
|
||||
|
||||
else -> {
|
||||
ledgerMessageCommands.value = commandFormatter.unknownError(
|
||||
device = device,
|
||||
onRetry = onRetry,
|
||||
onCancel = ::hide
|
||||
).event()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LedgerMessageCommands.hide() {
|
||||
ledgerMessageCommands.value = LedgerMessageCommand.Hide.event()
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters
|
||||
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.view.AlertModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
|
||||
class GenericLedgerMessageFormatter(
|
||||
private val resourceManager: ResourceManager,
|
||||
) : LedgerMessageFormatter {
|
||||
|
||||
override suspend fun appName(): String {
|
||||
return resourceManager.getString(R.string.account_ledger_migration_generic)
|
||||
}
|
||||
|
||||
context(Browserable.Presentation)
|
||||
override suspend fun alertForKind(messageKind: LedgerMessageFormatter.MessageKind): AlertModel? {
|
||||
// We do not show any alerts for new ledger app
|
||||
return null
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters
|
||||
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.view.AlertModel
|
||||
|
||||
interface LedgerMessageFormatter {
|
||||
|
||||
enum class MessageKind {
|
||||
APP_NOT_OPEN, OTHER
|
||||
}
|
||||
|
||||
suspend fun appName(): String
|
||||
|
||||
context(Browserable.Presentation)
|
||||
suspend fun alertForKind(
|
||||
messageKind: MessageKind,
|
||||
): AlertModel?
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters
|
||||
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_ledger_core.domain.LedgerMigrationTracker
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
class LedgerMessageFormatterFactory(
|
||||
private val resourceManager: ResourceManager,
|
||||
private val migrationTracker: LedgerMigrationTracker,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val appLinksProvider: AppLinksProvider,
|
||||
) {
|
||||
|
||||
fun createLegacy(chainId: ChainId, showAlerts: Boolean): LedgerMessageFormatter {
|
||||
return LegacyLedgerMessageFormatter(migrationTracker, resourceManager, chainRegistry, appLinksProvider, chainId, showAlerts)
|
||||
}
|
||||
|
||||
fun createGeneric(): LedgerMessageFormatter {
|
||||
return GenericLedgerMessageFormatter(resourceManager)
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters
|
||||
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.view.AlertModel
|
||||
import io.novafoundation.nova.common.view.AlertModel.ActionModel
|
||||
import io.novafoundation.nova.common.view.AlertView.StylePreset
|
||||
import io.novafoundation.nova.common.view.asStyle
|
||||
import io.novafoundation.nova.feature_ledger_core.domain.LedgerMigrationTracker
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
class LegacyLedgerMessageFormatter(
|
||||
private val migrationTracker: LedgerMigrationTracker,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val appLinksProvider: AppLinksProvider,
|
||||
private val chainId: ChainId,
|
||||
private val showAlerts: Boolean
|
||||
) : LedgerMessageFormatter {
|
||||
|
||||
private var shouldUseMigrationApp: Boolean? = null
|
||||
private val cacheMutex = Mutex()
|
||||
|
||||
override suspend fun appName(): String {
|
||||
return if (shouldUseMigrationApp()) {
|
||||
resourceManager.getString(R.string.account_ledger_migration_app)
|
||||
} else {
|
||||
val chain = chainRegistry.getChain(chainId)
|
||||
chain.name
|
||||
}
|
||||
}
|
||||
|
||||
context(Browserable.Presentation)
|
||||
override suspend fun alertForKind(messageKind: LedgerMessageFormatter.MessageKind): AlertModel? {
|
||||
val shouldShowAlert = showAlerts && shouldUseMigrationApp()
|
||||
if (!shouldShowAlert) return null
|
||||
|
||||
return when (messageKind) {
|
||||
LedgerMessageFormatter.MessageKind.APP_NOT_OPEN -> AlertModel(
|
||||
style = StylePreset.INFO.asStyle(),
|
||||
message = resourceManager.getString(R.string.account_ledger_legacy_warning_title),
|
||||
subMessage = resourceManager.getString(R.string.account_ledger_legacy_warning_message),
|
||||
linkAction = ActionModel(
|
||||
text = resourceManager.getString(R.string.common_find_out_more),
|
||||
listener = { showBrowser(appLinksProvider.ledgerMigrationArticle) }
|
||||
)
|
||||
)
|
||||
|
||||
LedgerMessageFormatter.MessageKind.OTHER -> AlertModel(
|
||||
style = StylePreset.INFO.asStyle(),
|
||||
message = resourceManager.getString(R.string.account_ledger_legacy_warning_title),
|
||||
subMessage = resourceManager.getString(R.string.account_ledger_migration_deprecation_message),
|
||||
linkAction = ActionModel(
|
||||
text = resourceManager.getString(R.string.common_find_out_more),
|
||||
listener = { showBrowser(appLinksProvider.ledgerMigrationArticle) }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun shouldUseMigrationApp(): Boolean = cacheMutex.withLock {
|
||||
if (shouldUseMigrationApp == null) {
|
||||
shouldUseMigrationApp = migrationTracker.shouldUseMigrationApp(chainId)
|
||||
}
|
||||
|
||||
shouldUseMigrationApp!!
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.generic
|
||||
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.view.AlertModel
|
||||
import io.novafoundation.nova.common.view.AlertView
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import javax.inject.Inject
|
||||
|
||||
interface GenericLedgerEvmAlertFormatter {
|
||||
|
||||
fun createUpdateAppToGetEvmAddressAlert(): AlertModel
|
||||
}
|
||||
|
||||
@FeatureScope
|
||||
class RealGenericLedgerEvmAlertFormatter @Inject constructor(
|
||||
private val resourceManager: ResourceManager,
|
||||
) : GenericLedgerEvmAlertFormatter {
|
||||
|
||||
override fun createUpdateAppToGetEvmAddressAlert(): AlertModel {
|
||||
return AlertModel(
|
||||
style = AlertView.Style.fromPreset(AlertView.StylePreset.WARNING),
|
||||
message = resourceManager.getString(R.string.ledger_select_address_update_for_evm_title),
|
||||
subMessage = resourceManager.getString(R.string.ledger_select_address_update_for_evm_message)
|
||||
)
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress
|
||||
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
|
||||
import coil.ImageLoader
|
||||
import io.novafoundation.nova.common.address.AddressModel
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.view.setModelOrHide
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.setupAddressActions
|
||||
import io.novafoundation.nova.feature_ledger_impl.databinding.FragmentImportLedgerSelectAddressBinding
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessagePresentable
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.setupLedgerMessages
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.list.LedgerAccountAdapter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.list.LedgerSelectAddressLoadMoreAdapter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.model.LedgerAccountRvItem
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class SelectAddressLedgerFragment<V : SelectAddressLedgerViewModel> :
|
||||
BaseFragment<V, FragmentImportLedgerSelectAddressBinding>(),
|
||||
LedgerSelectAddressLoadMoreAdapter.Handler,
|
||||
LedgerAccountAdapter.Handler {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PAYLOAD_KEY = "SelectAddressImportLedgerFragment.PAYLOAD_KEY"
|
||||
|
||||
fun getBundle(payload: SelectLedgerAddressPayload) = bundleOf(PAYLOAD_KEY to payload)
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var imageLoader: ImageLoader
|
||||
|
||||
override fun createBinding() = FragmentImportLedgerSelectAddressBinding.inflate(layoutInflater)
|
||||
|
||||
private val addressesAdapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
LedgerAccountAdapter(this)
|
||||
}
|
||||
private val loadMoreAdapter = LedgerSelectAddressLoadMoreAdapter(handler = this, lifecycleOwner = this)
|
||||
|
||||
@Inject
|
||||
lateinit var ledgerMessagePresentable: LedgerMessagePresentable
|
||||
|
||||
protected fun payload(): SelectLedgerAddressPayload = argument(PAYLOAD_KEY)
|
||||
|
||||
override fun initViews() {
|
||||
binder.ledgerSelectAddressToolbar.setHomeButtonListener {
|
||||
viewModel.backClicked()
|
||||
}
|
||||
onBackPressed { viewModel.backClicked() }
|
||||
|
||||
binder.ledgerSelectAddressContent.setHasFixedSize(true)
|
||||
binder.ledgerSelectAddressContent.adapter = ConcatAdapter(addressesAdapter, loadMoreAdapter)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: V) {
|
||||
viewModel.loadMoreState.observe(loadMoreAdapter::setState)
|
||||
viewModel.loadedAccountModels.observe(addressesAdapter::submitList)
|
||||
|
||||
viewModel.chainUi.observe(binder.ledgerSelectAddressChain::setChain)
|
||||
|
||||
viewModel.alertFlow.observe(binder.ledgerSelectAddressAlert::setModelOrHide)
|
||||
|
||||
setupLedgerMessages(ledgerMessagePresentable)
|
||||
|
||||
viewModel.addressActionsMixin.setupAddressActions()
|
||||
}
|
||||
|
||||
override fun loadMoreClicked() {
|
||||
viewModel.loadMoreClicked()
|
||||
}
|
||||
|
||||
override fun itemClicked(item: LedgerAccountRvItem) {
|
||||
viewModel.accountClicked(item)
|
||||
}
|
||||
|
||||
override fun addressInfoClicked(addressModel: AddressModel, addressScheme: AddressScheme) {
|
||||
viewModel.addressInfoClicked(addressModel, addressScheme)
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.address.AddressModel
|
||||
import io.novafoundation.nova.common.address.format.AddressFormat
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.presentation.DescriptiveButtonState
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
import io.novafoundation.nova.common.utils.GENERIC_ADDRESS_PREFIX
|
||||
import io.novafoundation.nova.common.utils.added
|
||||
import io.novafoundation.nova.common.utils.event
|
||||
import io.novafoundation.nova.common.utils.flowOf
|
||||
import io.novafoundation.nova.common.utils.formatting.format
|
||||
import io.novafoundation.nova.common.utils.invoke
|
||||
import io.novafoundation.nova.common.utils.launchUnit
|
||||
import io.novafoundation.nova.common.utils.lazyAsync
|
||||
import io.novafoundation.nova.common.utils.mapList
|
||||
import io.novafoundation.nova.common.view.AlertModel
|
||||
import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LedgerVariant
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.icon.createAccountAddressModel
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.AddressActionsMixin
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.showAddressActions
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.address
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.LedgerAccount
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommands
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.createLedgerReviewAddresses
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.errors.handleLedgerError
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.model.AddressVerificationMode
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.model.LedgerAccountRvItem
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.ss58.SS58Encoder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
abstract class SelectAddressLedgerViewModel(
|
||||
private val router: LedgerRouter,
|
||||
protected val interactor: SelectAddressLedgerInteractor,
|
||||
private val addressIconGenerator: AddressIconGenerator,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val payload: SelectLedgerAddressPayload,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val messageCommandFormatter: MessageCommandFormatter,
|
||||
private val addressActionsMixinFactory: AddressActionsMixin.Factory
|
||||
) : BaseViewModel(),
|
||||
LedgerMessageCommands,
|
||||
Browserable.Presentation by Browserable() {
|
||||
|
||||
abstract val ledgerVariant: LedgerVariant
|
||||
|
||||
abstract val addressVerificationMode: AddressVerificationMode
|
||||
|
||||
override val ledgerMessageCommands: MutableLiveData<Event<LedgerMessageCommand>> = MutableLiveData()
|
||||
|
||||
private val substratePreviewChain by lazyAsync { chainRegistry.getChain(payload.substrateChainId) }
|
||||
|
||||
private val loadingState = MutableStateFlow(AccountLoadingState.CAN_LOAD)
|
||||
protected val loadedAccounts: MutableStateFlow<List<LedgerAccount>> = MutableStateFlow(emptyList())
|
||||
|
||||
private var verifyAddressJob: Job? = null
|
||||
|
||||
val loadedAccountModels = loadedAccounts.mapList { it.toUi() }
|
||||
.shareInBackground()
|
||||
|
||||
val chainUi = flowOf { mapChainToUi(substratePreviewChain()) }
|
||||
.shareInBackground()
|
||||
|
||||
val loadMoreState = loadingState.map { loadingState ->
|
||||
when (loadingState) {
|
||||
AccountLoadingState.CAN_LOAD -> DescriptiveButtonState.Enabled(resourceManager.getString(R.string.ledger_import_select_address_load_more))
|
||||
|
||||
AccountLoadingState.LOADING -> DescriptiveButtonState.Loading
|
||||
AccountLoadingState.NOTHING_TO_LOAD -> DescriptiveButtonState.Gone
|
||||
}
|
||||
}.shareInBackground()
|
||||
|
||||
protected val _alertFlow = MutableStateFlow<AlertModel?>(null)
|
||||
val alertFlow: Flow<AlertModel?> = _alertFlow
|
||||
|
||||
val device = flowOf {
|
||||
interactor.getDevice(payload.deviceId)
|
||||
}
|
||||
|
||||
val addressActionsMixin = addressActionsMixinFactory.create(this)
|
||||
|
||||
init {
|
||||
loadNewAccount()
|
||||
}
|
||||
|
||||
abstract fun onAccountVerified(account: LedgerAccount)
|
||||
|
||||
/**
|
||||
* Loads ledger account. Can return Success(null) to indicate there is nothing to load and "load more" button should be hidden
|
||||
*/
|
||||
protected open suspend fun loadLedgerAccount(
|
||||
substratePreviewChain: Chain,
|
||||
deviceId: String,
|
||||
accountIndex: Int,
|
||||
ledgerVariant: LedgerVariant
|
||||
): Result<LedgerAccount?> {
|
||||
return interactor.loadLedgerAccount(substratePreviewChain, deviceId, accountIndex, ledgerVariant)
|
||||
}
|
||||
|
||||
fun loadMoreClicked() {
|
||||
if (loadingState.value != AccountLoadingState.CAN_LOAD) return
|
||||
|
||||
loadNewAccount()
|
||||
}
|
||||
|
||||
fun backClicked() {
|
||||
router.back()
|
||||
}
|
||||
|
||||
fun accountClicked(accountUi: LedgerAccountRvItem) {
|
||||
verifyAccount(accountUi.id)
|
||||
}
|
||||
|
||||
fun addressInfoClicked(addressModel: AddressModel, addressScheme: AddressScheme) {
|
||||
addressActionsMixin.showAddressActions(addressModel.address, AddressFormat.defaultForScheme(addressScheme, SS58Encoder.GENERIC_ADDRESS_PREFIX))
|
||||
}
|
||||
|
||||
private fun verifyAccount(id: Int) {
|
||||
verifyAddressJob?.cancel()
|
||||
verifyAddressJob = launch {
|
||||
val account = loadedAccounts.value.first { it.index == id }
|
||||
val verificationMode = addressVerificationMode
|
||||
|
||||
if (verificationMode is AddressVerificationMode.Enabled) {
|
||||
verifyAccountInternal(account, verificationMode.addressSchemesToVerify)
|
||||
} else {
|
||||
onAccountVerified(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun verifyAccountInternal(account: LedgerAccount, reviewAddressSchemes: List<AddressScheme>) {
|
||||
val device = device.first()
|
||||
|
||||
ledgerMessageCommands.value = messageCommandFormatter.reviewAddressCommand(
|
||||
addresses = createLedgerReviewAddresses(
|
||||
allowedAddressSchemes = reviewAddressSchemes,
|
||||
AddressScheme.SUBSTRATE to account.substrate.address,
|
||||
AddressScheme.EVM to account.evm?.address()
|
||||
),
|
||||
device = device,
|
||||
onCancel = ::verifyAddressCancelled,
|
||||
).event()
|
||||
|
||||
val result = withContext(Dispatchers.Default) {
|
||||
interactor.verifyLedgerAccount(substratePreviewChain(), payload.deviceId, account.index, ledgerVariant, reviewAddressSchemes)
|
||||
}
|
||||
|
||||
result.onFailure {
|
||||
handleLedgerError(it, device) { verifyAccount(account.index) }
|
||||
}.onSuccess {
|
||||
ledgerMessageCommands.value = messageCommandFormatter.hideCommand().event()
|
||||
|
||||
onAccountVerified(account)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLedgerError(error: Throwable, device: LedgerDevice, retry: () -> Unit) {
|
||||
handleLedgerError(
|
||||
reason = error,
|
||||
device = device,
|
||||
commandFormatter = messageCommandFormatter,
|
||||
onRetry = retry
|
||||
)
|
||||
}
|
||||
|
||||
private fun verifyAddressCancelled() {
|
||||
ledgerMessageCommands.value = messageCommandFormatter.hideCommand().event()
|
||||
verifyAddressJob?.cancel()
|
||||
verifyAddressJob = null
|
||||
}
|
||||
|
||||
private fun loadNewAccount(): Unit = launchUnit(Dispatchers.Default) {
|
||||
ledgerMessageCommands.postValue(messageCommandFormatter.hideCommand().event())
|
||||
|
||||
loadingState.value = AccountLoadingState.LOADING
|
||||
val nextAccountIndex = loadedAccounts.value.size
|
||||
|
||||
loadLedgerAccount(substratePreviewChain(), payload.deviceId, nextAccountIndex, ledgerVariant)
|
||||
.onSuccess { newAccount ->
|
||||
if (newAccount != null) {
|
||||
loadedAccounts.value = loadedAccounts.value.added(newAccount)
|
||||
loadingState.value = AccountLoadingState.CAN_LOAD
|
||||
} else {
|
||||
loadingState.value = AccountLoadingState.NOTHING_TO_LOAD
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("Ledger", "Failed to load Ledger account", it)
|
||||
handleLedgerError(it, device.first()) { loadNewAccount() }
|
||||
loadingState.value = AccountLoadingState.CAN_LOAD
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun LedgerAccount.toUi(): LedgerAccountRvItem {
|
||||
return LedgerAccountRvItem(
|
||||
id = index,
|
||||
label = resourceManager.getString(R.string.ledger_select_address_account_label, (index + 1).format()),
|
||||
substrate = addressIconGenerator.createAccountAddressModel(substratePreviewChain(), substrate.address),
|
||||
evm = evm?.let { addressIconGenerator.createAccountAddressModel(AddressFormat.evm(), it.accountId) }
|
||||
)
|
||||
}
|
||||
|
||||
enum class AccountLoadingState {
|
||||
CAN_LOAD, LOADING, NOTHING_TO_LOAD
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class SelectLedgerAddressPayload(
|
||||
val deviceId: String,
|
||||
val substrateChainId: ChainId
|
||||
) : Parcelable
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.list
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import io.novafoundation.nova.common.address.AddressModel
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
import io.novafoundation.nova.common.list.BaseListAdapter
|
||||
import io.novafoundation.nova.common.list.BaseViewHolder
|
||||
import io.novafoundation.nova.common.utils.inflater
|
||||
import io.novafoundation.nova.common.utils.setTextColorRes
|
||||
import io.novafoundation.nova.common.view.setExtraInfoAvailable
|
||||
import io.novafoundation.nova.common.view.shape.addRipple
|
||||
import io.novafoundation.nova.common.view.shape.getRoundedCornerDrawable
|
||||
import io.novafoundation.nova.feature_account_api.view.showAddress
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.databinding.ItemLedgerAccountBinding
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.model.LedgerAccountRvItem
|
||||
|
||||
class LedgerAccountAdapter(
|
||||
private val handler: Handler
|
||||
) : BaseListAdapter<LedgerAccountRvItem, SelectLedgerHolder>(DiffCallback()) {
|
||||
|
||||
interface Handler {
|
||||
|
||||
fun itemClicked(item: LedgerAccountRvItem)
|
||||
|
||||
fun addressInfoClicked(addressModel: AddressModel, addressScheme: AddressScheme)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SelectLedgerHolder {
|
||||
return SelectLedgerHolder(ItemLedgerAccountBinding.inflate(parent.inflater(), parent, false), handler)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SelectLedgerHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<LedgerAccountRvItem>() {
|
||||
override fun areItemsTheSame(oldItem: LedgerAccountRvItem, newItem: LedgerAccountRvItem): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: LedgerAccountRvItem, newItem: LedgerAccountRvItem): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
class SelectLedgerHolder(
|
||||
private val viewBinding: ItemLedgerAccountBinding,
|
||||
private val eventHandler: LedgerAccountAdapter.Handler
|
||||
) : BaseViewHolder(viewBinding.root) {
|
||||
init {
|
||||
viewBinding.root.background = with(containerView.context) {
|
||||
addRipple(getRoundedCornerDrawable(R.color.block_background))
|
||||
}
|
||||
|
||||
viewBinding.itemLedgerAccountSubstrate.setExtraInfoAvailable(true)
|
||||
}
|
||||
|
||||
fun bind(model: LedgerAccountRvItem) = with(viewBinding) {
|
||||
viewBinding.root.setOnClickListener { eventHandler.itemClicked(model) }
|
||||
|
||||
itemLedgerAccountLabel.text = model.label
|
||||
itemLedgerAccountIcon.setImageDrawable(model.substrate.image)
|
||||
|
||||
itemLedgerAccountSubstrate.showAddress(model.substrate)
|
||||
itemLedgerAccountSubstrate.setOnClickListener { eventHandler.addressInfoClicked(model.substrate, AddressScheme.SUBSTRATE) }
|
||||
|
||||
if (model.evm != null) {
|
||||
itemLedgerAccountEvm.valuePrimary.setTextColorRes(R.color.text_primary)
|
||||
itemLedgerAccountEvm.setPrimaryValueStartIcon(null)
|
||||
itemLedgerAccountEvm.showAddress(model.evm)
|
||||
|
||||
itemLedgerAccountEvm.setOnClickListener { eventHandler.addressInfoClicked(model.evm, AddressScheme.EVM) }
|
||||
itemLedgerAccountEvm.setExtraInfoAvailable(true)
|
||||
} else {
|
||||
itemLedgerAccountEvm.valuePrimary.setTextColorRes(R.color.text_secondary)
|
||||
itemLedgerAccountEvm.showValue(context.getString(R.string.ledger_select_address_not_found))
|
||||
|
||||
itemLedgerAccountEvm.setOnClickListener(null)
|
||||
itemLedgerAccountEvm.setExtraInfoAvailable(false)
|
||||
|
||||
itemLedgerAccountEvm.setPrimaryValueStartIcon(R.drawable.ic_warning_filled)
|
||||
}
|
||||
}
|
||||
|
||||
override fun unbind() {}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.list
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.novafoundation.nova.common.list.BaseViewHolder
|
||||
import io.novafoundation.nova.common.presentation.DescriptiveButtonState
|
||||
import io.novafoundation.nova.common.utils.inflateChild
|
||||
import io.novafoundation.nova.common.view.PrimaryButton
|
||||
import io.novafoundation.nova.common.view.setState
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
|
||||
class LedgerSelectAddressLoadMoreAdapter(
|
||||
private val handler: Handler,
|
||||
private val lifecycleOwner: LifecycleOwner,
|
||||
) : RecyclerView.Adapter<LedgerSelectAddressLoadMoreViewHolder>() {
|
||||
|
||||
interface Handler {
|
||||
|
||||
fun loadMoreClicked()
|
||||
}
|
||||
|
||||
private var state: DescriptiveButtonState? = null
|
||||
|
||||
fun setState(newState: DescriptiveButtonState) {
|
||||
state = newState
|
||||
|
||||
notifyItemChanged(0)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LedgerSelectAddressLoadMoreViewHolder {
|
||||
val containerView = parent.inflateChild(R.layout.item_select_address_load_more) as PrimaryButton
|
||||
|
||||
return LedgerSelectAddressLoadMoreViewHolder(containerView, handler, lifecycleOwner)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: LedgerSelectAddressLoadMoreViewHolder, position: Int) {
|
||||
state?.let(holder::bind)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1
|
||||
}
|
||||
|
||||
class LedgerSelectAddressLoadMoreViewHolder(
|
||||
override val containerView: PrimaryButton,
|
||||
handler: LedgerSelectAddressLoadMoreAdapter.Handler,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
) : BaseViewHolder(containerView) {
|
||||
|
||||
init {
|
||||
containerView.prepareForProgress(lifecycleOwner)
|
||||
containerView.setOnClickListener { handler.loadMoreClicked() }
|
||||
}
|
||||
|
||||
fun bind(state: DescriptiveButtonState) {
|
||||
containerView.setState(state)
|
||||
}
|
||||
|
||||
override fun unbind() {}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.model
|
||||
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
|
||||
sealed class AddressVerificationMode {
|
||||
|
||||
data object Disabled : AddressVerificationMode()
|
||||
|
||||
class Enabled(val addressSchemesToVerify: List<AddressScheme>) : AddressVerificationMode()
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.model
|
||||
|
||||
import io.novafoundation.nova.common.address.AddressModel
|
||||
|
||||
data class LedgerAccountRvItem(
|
||||
val id: Int,
|
||||
val label: String,
|
||||
val substrate: AddressModel,
|
||||
val evm: AddressModel?
|
||||
)
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import io.novafoundation.nova.common.list.BaseListAdapter
|
||||
import io.novafoundation.nova.common.list.BaseViewHolder
|
||||
import io.novafoundation.nova.common.utils.inflater
|
||||
import io.novafoundation.nova.common.view.shape.addRipple
|
||||
import io.novafoundation.nova.common.view.shape.getRoundedCornerDrawable
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.databinding.ItemLedgerBinding
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.model.SelectLedgerModel
|
||||
|
||||
class SelectLedgerAdapter(
|
||||
private val handler: Handler
|
||||
) : BaseListAdapter<SelectLedgerModel, SelectLedgerHolder>(DiffCallback()) {
|
||||
|
||||
interface Handler {
|
||||
|
||||
fun itemClicked(item: SelectLedgerModel)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SelectLedgerHolder {
|
||||
return SelectLedgerHolder(ItemLedgerBinding.inflate(parent.inflater(), parent, false), handler)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SelectLedgerHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<SelectLedgerModel>() {
|
||||
override fun areItemsTheSame(oldItem: SelectLedgerModel, newItem: SelectLedgerModel): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: SelectLedgerModel, newItem: SelectLedgerModel): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
class SelectLedgerHolder(
|
||||
private val binder: ItemLedgerBinding,
|
||||
private val eventHandler: SelectLedgerAdapter.Handler
|
||||
) : BaseViewHolder(binder.root) {
|
||||
init {
|
||||
binder.itemLedger.background = with(containerView.context) {
|
||||
addRipple(getRoundedCornerDrawable(R.color.block_background))
|
||||
}
|
||||
|
||||
binder.itemLedger.setProgressTint(R.color.icon_secondary)
|
||||
}
|
||||
|
||||
fun bind(model: SelectLedgerModel) = with(binder) {
|
||||
itemLedger.title.text = model.name
|
||||
|
||||
bindConnecting(model)
|
||||
}
|
||||
|
||||
fun bindConnecting(model: SelectLedgerModel) = with(binder) {
|
||||
itemLedger.setInProgress(model.isConnecting)
|
||||
|
||||
if (model.isConnecting) {
|
||||
root.setOnClickListener(null)
|
||||
} else {
|
||||
root.setOnClickListener { eventHandler.itemClicked(model) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun unbind() {}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.location.LocationManager
|
||||
import android.os.Bundle
|
||||
import androidx.core.view.isVisible
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.mixin.impl.observeBrowserEvents
|
||||
import io.novafoundation.nova.common.utils.permissions.setupPermissionAsker
|
||||
import io.novafoundation.nova.common.utils.setVisible
|
||||
import io.novafoundation.nova.common.view.dialog.dialog
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.databinding.FragmentSelectLedgerBinding
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessagePresentable
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.setupLedgerMessages
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.model.SelectLedgerModel
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class SelectLedgerFragment<V : SelectLedgerViewModel> : BaseFragment<V, FragmentSelectLedgerBinding>(), SelectLedgerAdapter.Handler {
|
||||
|
||||
override fun createBinding() = FragmentSelectLedgerBinding.inflate(layoutInflater)
|
||||
|
||||
@Inject
|
||||
lateinit var ledgerMessagePresentable: LedgerMessagePresentable
|
||||
|
||||
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SelectLedgerAdapter(this)
|
||||
}
|
||||
|
||||
private val bluetoothConnectivityReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
|
||||
BluetoothAdapter.STATE_OFF -> viewModel.bluetoothStateChanged(BluetoothState.OFF)
|
||||
BluetoothAdapter.STATE_ON -> viewModel.bluetoothStateChanged(BluetoothState.ON)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val locationStateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
val action = intent.action
|
||||
if (action != null && action == LocationManager.PROVIDERS_CHANGED_ACTION) {
|
||||
viewModel.locationStateChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
binder.selectLedgerToolbar.setHomeButtonListener { viewModel.backClicked() }
|
||||
onBackPressed { viewModel.backClicked() }
|
||||
|
||||
binder.selectLedgerGrantPermissions.setOnClickListener { viewModel.allowAvailabilityRequests() }
|
||||
|
||||
binder.selectLedgerDevices.setHasFixedSize(true)
|
||||
binder.selectLedgerDevices.adapter = adapter
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: V) {
|
||||
viewModel.deviceModels.observe {
|
||||
adapter.submitList(it)
|
||||
|
||||
binder.selectLedgerDevices.setVisible(it.isNotEmpty())
|
||||
binder.selectLedgerProgress.setVisible(it.isEmpty())
|
||||
}
|
||||
|
||||
viewModel.showRequestLocationDialog.observe {
|
||||
dialog(requireContext(), R.style.AccentAlertDialogTheme) {
|
||||
setTitle(R.string.select_ledger_location_enable_request_title)
|
||||
setMessage(getString(R.string.select_ledger_location_enable_request_message))
|
||||
setPositiveButton(R.string.common_enable) { _, _ -> viewModel.enableLocationAcknowledged() }
|
||||
setNegativeButton(R.string.common_cancel, null)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.hints.observe(binder.selectLedgerHints::setText)
|
||||
viewModel.showPermissionsButton.observe { binder.selectLedgerGrantPermissions.isVisible = it }
|
||||
|
||||
setupPermissionAsker(viewModel)
|
||||
setupLedgerMessages(ledgerMessagePresentable)
|
||||
observeBrowserEvents(viewModel)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
enableBluetoothConnectivityTracker()
|
||||
enableLocationStateTracker()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
disableBluetoothConnectivityTracker()
|
||||
disableLocationStateTracker()
|
||||
}
|
||||
|
||||
private fun enableLocationStateTracker() {
|
||||
val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
|
||||
|
||||
requireActivity().registerReceiver(locationStateReceiver, filter)
|
||||
}
|
||||
|
||||
private fun disableLocationStateTracker() {
|
||||
try {
|
||||
requireActivity().unregisterReceiver(locationStateReceiver)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// Receiver not registered
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableBluetoothConnectivityTracker() {
|
||||
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
|
||||
|
||||
requireActivity().registerReceiver(bluetoothConnectivityReceiver, filter)
|
||||
}
|
||||
|
||||
private fun disableBluetoothConnectivityTracker() {
|
||||
try {
|
||||
requireActivity().unregisterReceiver(bluetoothConnectivityReceiver)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// Receiver not registered
|
||||
}
|
||||
}
|
||||
|
||||
override fun itemClicked(item: SelectLedgerModel) {
|
||||
viewModel.deviceClicked(item)
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger
|
||||
|
||||
import android.os.Parcelable
|
||||
|
||||
interface SelectLedgerPayload : Parcelable {
|
||||
|
||||
val connectionMode: ConnectionMode
|
||||
|
||||
enum class ConnectionMode {
|
||||
BLUETOOTH, USB, ALL
|
||||
}
|
||||
}
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.navigation.ReturnableRouter
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager
|
||||
import io.novafoundation.nova.common.utils.flowOf
|
||||
import io.novafoundation.nova.common.utils.location.LocationManager
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.common.utils.permissions.checkPermissions
|
||||
import io.novafoundation.nova.common.utils.stateMachine.StateMachine
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryMethods
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryRequirement
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryRequirementAvailability
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.discoveryRequirements
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.findDevice
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommands
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers.LedgerDeviceFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.errors.handleLedgerError
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.model.SelectLedgerModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SelectLedgerEvent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SideEffect
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.states.DevicesFoundState
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.states.DiscoveringState
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.states.SelectLedgerState
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.discovery.ble.BleScanFailed
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class BluetoothState {
|
||||
ON, OFF
|
||||
}
|
||||
|
||||
abstract class SelectLedgerViewModel(
|
||||
private val discoveryService: LedgerDeviceDiscoveryService,
|
||||
private val permissionsAsker: PermissionsAsker.Presentation,
|
||||
private val bluetoothManager: BluetoothManager,
|
||||
private val locationManager: LocationManager,
|
||||
private val router: ReturnableRouter,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val messageFormatter: LedgerMessageFormatter,
|
||||
private val payload: SelectLedgerPayload,
|
||||
private val ledgerDeviceFormatter: LedgerDeviceFormatter,
|
||||
private val messageCommandFormatter: MessageCommandFormatter,
|
||||
) : BaseViewModel(),
|
||||
PermissionsAsker by permissionsAsker,
|
||||
LedgerMessageCommands,
|
||||
Browserable.Presentation by Browserable() {
|
||||
|
||||
private val discoveryMethods = payload.connectionMode.toDiscoveryMethod()
|
||||
|
||||
private val stateMachine = StateMachine(createInitialState(), coroutineScope = this)
|
||||
|
||||
val deviceModels = stateMachine.state.map(::mapStateToUi)
|
||||
.shareInBackground()
|
||||
|
||||
val hints = flowOf {
|
||||
when (payload.connectionMode) {
|
||||
SelectLedgerPayload.ConnectionMode.BLUETOOTH -> resourceManager.getString(
|
||||
R.string.account_ledger_select_device_description,
|
||||
messageFormatter.appName()
|
||||
)
|
||||
|
||||
SelectLedgerPayload.ConnectionMode.USB -> resourceManager.getString(
|
||||
R.string.account_ledger_select_device_usb_description,
|
||||
messageFormatter.appName()
|
||||
)
|
||||
|
||||
SelectLedgerPayload.ConnectionMode.ALL -> resourceManager.getString(R.string.account_ledger_select_device_all_description)
|
||||
}
|
||||
}.shareInBackground()
|
||||
|
||||
val showPermissionsButton = flowOf { payload.connectionMode == SelectLedgerPayload.ConnectionMode.ALL }
|
||||
.shareInBackground()
|
||||
|
||||
override val ledgerMessageCommands = MutableLiveData<Event<LedgerMessageCommand>>()
|
||||
|
||||
private val _showRequestLocationDialog = MutableLiveData<Boolean>()
|
||||
val showRequestLocationDialog: LiveData<Boolean> = _showRequestLocationDialog
|
||||
|
||||
init {
|
||||
handleSideEffects()
|
||||
setupDiscoveryObserving()
|
||||
}
|
||||
|
||||
abstract suspend fun verifyConnection(device: LedgerDevice)
|
||||
|
||||
open suspend fun handleLedgerError(reason: Throwable, device: LedgerDevice) {
|
||||
handleLedgerError(
|
||||
reason = reason,
|
||||
device = device,
|
||||
commandFormatter = messageCommandFormatter,
|
||||
onRetry = { stateMachine.onEvent(SelectLedgerEvent.DeviceChosen(device)) }
|
||||
)
|
||||
}
|
||||
|
||||
open fun backClicked() {
|
||||
router.back()
|
||||
}
|
||||
|
||||
fun allowAvailabilityRequests() {
|
||||
stateMachine.onEvent(SelectLedgerEvent.AvailabilityRequestsAllowed)
|
||||
}
|
||||
|
||||
fun deviceClicked(item: SelectLedgerModel) = launch {
|
||||
discoveryService.findDevice(item.id)?.let { device ->
|
||||
stateMachine.onEvent(SelectLedgerEvent.DeviceChosen(device))
|
||||
}
|
||||
}
|
||||
|
||||
fun bluetoothStateChanged(state: BluetoothState) {
|
||||
when (state) {
|
||||
BluetoothState.ON -> stateMachine.onEvent(SelectLedgerEvent.DiscoveryRequirementSatisfied(DiscoveryRequirement.BLUETOOTH))
|
||||
BluetoothState.OFF -> stateMachine.onEvent(SelectLedgerEvent.DiscoveryRequirementMissing(DiscoveryRequirement.BLUETOOTH))
|
||||
}
|
||||
}
|
||||
|
||||
fun locationStateChanged() {
|
||||
emitLocationState()
|
||||
}
|
||||
|
||||
fun enableLocationAcknowledged() {
|
||||
locationManager.enableLocation()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
discoveryService.stopDiscovery()
|
||||
}
|
||||
|
||||
private fun emitLocationState() {
|
||||
when (locationManager.isLocationEnabled()) {
|
||||
true -> stateMachine.onEvent(SelectLedgerEvent.DiscoveryRequirementSatisfied(DiscoveryRequirement.LOCATION))
|
||||
false -> stateMachine.onEvent(SelectLedgerEvent.DiscoveryRequirementMissing(DiscoveryRequirement.LOCATION))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSideEffects() {
|
||||
launch {
|
||||
for (effect in stateMachine.sideEffects) {
|
||||
handleSideEffect(effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun performConnectionVerification(device: LedgerDevice) = launch {
|
||||
runCatching { verifyConnection(device) }
|
||||
.onSuccess { stateMachine.onEvent(SelectLedgerEvent.ConnectionVerified) }
|
||||
.onFailure { stateMachine.onEvent(SelectLedgerEvent.VerificationFailed(it)) }
|
||||
}
|
||||
|
||||
private suspend fun handleSideEffect(effect: SideEffect) {
|
||||
when (effect) {
|
||||
is SideEffect.PresentLedgerFailure -> launch { handleLedgerError(effect.reason, effect.device) }
|
||||
|
||||
is SideEffect.VerifyConnection -> performConnectionVerification(effect.device)
|
||||
|
||||
is SideEffect.StartDiscovery -> discoveryService.startDiscovery(effect.methods)
|
||||
|
||||
is SideEffect.RequestPermissions -> requestPermissions(effect.requirements, effect.shouldExitUponDenial)
|
||||
|
||||
is SideEffect.RequestSatisfyRequirement -> requestSatisfyRequirement(effect.requirements)
|
||||
|
||||
is SideEffect.StopDiscovery -> discoveryService.stopDiscovery(effect.methods)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestSatisfyRequirement(requirements: List<DiscoveryRequirement>) {
|
||||
val (awaitable, fireAndForget) = requirements.map { it.createRequest() }
|
||||
.partition { it.awaitable }
|
||||
|
||||
// Do awaitable requests first to reduce the change of overlapping requests happening
|
||||
// With this logic overlapping requests may only happen if there are more than one `fireAndForget` requirement
|
||||
// Important: permissions are handled separately and state machine ensures permissions are always requested first
|
||||
awaitable.forEach { it.requestAction() }
|
||||
fireAndForget.forEach { it.requestAction() }
|
||||
}
|
||||
|
||||
private suspend fun requestPermissions(
|
||||
discoveryRequirements: List<DiscoveryRequirement>,
|
||||
shouldExitUponDenial: Boolean
|
||||
): Boolean {
|
||||
val permissions = discoveryRequirements.requiredPermissions()
|
||||
|
||||
val granted = permissionsAsker.requirePermissions(*permissions.toTypedArray())
|
||||
|
||||
if (granted) {
|
||||
stateMachine.onEvent(SelectLedgerEvent.PermissionsGranted)
|
||||
} else {
|
||||
onPermissionsNotGranted(shouldExitUponDenial)
|
||||
}
|
||||
|
||||
return granted
|
||||
}
|
||||
|
||||
private fun setupDiscoveryObserving() {
|
||||
discoveryService.errors.onEach(::discoveryError)
|
||||
|
||||
discoveryService.discoveredDevices.onEach {
|
||||
stateMachine.onEvent(SelectLedgerEvent.DiscoveredDevicesListChanged(it))
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
private fun onPermissionsNotGranted(shouldExitUponDenial: Boolean) {
|
||||
if (shouldExitUponDenial) {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
private fun discoveryError(error: Throwable) {
|
||||
when (error) {
|
||||
is BleScanFailed -> {
|
||||
val event = SelectLedgerEvent.DiscoveryRequirementMissing(DiscoveryRequirement.BLUETOOTH)
|
||||
stateMachine.onEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapStateToUi(state: SelectLedgerState): List<SelectLedgerModel> {
|
||||
return when (state) {
|
||||
is DevicesFoundState -> mapDevicesToUi(state.devices, connectingTo = state.verifyingDevice)
|
||||
is DiscoveringState -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapDevicesToUi(devices: List<LedgerDevice>, connectingTo: LedgerDevice?): List<SelectLedgerModel> {
|
||||
return devices.map {
|
||||
SelectLedgerModel(
|
||||
id = it.id,
|
||||
name = ledgerDeviceFormatter.formatName(it),
|
||||
isConnecting = it.id == connectingTo?.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SelectLedgerPayload.ConnectionMode.toDiscoveryMethod(): DiscoveryMethods {
|
||||
return when (this) {
|
||||
SelectLedgerPayload.ConnectionMode.BLUETOOTH -> DiscoveryMethods(DiscoveryMethods.Method.BLE)
|
||||
SelectLedgerPayload.ConnectionMode.USB -> DiscoveryMethods(DiscoveryMethods.Method.USB)
|
||||
SelectLedgerPayload.ConnectionMode.ALL -> DiscoveryMethods(DiscoveryMethods.Method.BLE, DiscoveryMethods.Method.USB)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<DiscoveryRequirement>.requiredPermissions() = flatMap { it.requiredPermissions() }
|
||||
|
||||
private fun DiscoveryRequirement.requiredPermissions() = when (this) {
|
||||
DiscoveryRequirement.BLUETOOTH -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
listOf(
|
||||
Manifest.permission.BLUETOOTH_CONNECT,
|
||||
Manifest.permission.BLUETOOTH_SCAN
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
DiscoveryRequirement.LOCATION -> listOf(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
}
|
||||
|
||||
private fun createInitialState(): SelectLedgerState {
|
||||
val allRequirements = discoveryMethods.discoveryRequirements()
|
||||
val permissionsGranted = permissionsAsker.checkPermissions(allRequirements.requiredPermissions())
|
||||
|
||||
val satisfiedDiscoverRequirements = setOfNotNull(
|
||||
DiscoveryRequirement.BLUETOOTH.takeIf { bluetoothManager.isBluetoothEnabled() },
|
||||
DiscoveryRequirement.LOCATION.takeIf { locationManager.isLocationEnabled() }
|
||||
)
|
||||
|
||||
val availability = DiscoveryRequirementAvailability(satisfiedDiscoverRequirements, permissionsGranted)
|
||||
|
||||
return DiscoveringState.initial(discoveryMethods, availability)
|
||||
}
|
||||
|
||||
private fun DiscoveryRequirement.createRequest(): DiscoveryRequirementRequest {
|
||||
return when (this) {
|
||||
DiscoveryRequirement.BLUETOOTH -> DiscoveryRequirementRequest.awaitable { bluetoothManager.enableBluetoothAndAwait() }
|
||||
DiscoveryRequirement.LOCATION -> DiscoveryRequirementRequest.fireAndForget { requestLocation() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestLocation() {
|
||||
_showRequestLocationDialog.value = true
|
||||
}
|
||||
|
||||
private class DiscoveryRequirementRequest(
|
||||
val requestAction: suspend () -> Unit,
|
||||
val awaitable: Boolean
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun awaitable(requestAction: suspend () -> Unit): DiscoveryRequirementRequest {
|
||||
return DiscoveryRequirementRequest(requestAction, awaitable = true)
|
||||
}
|
||||
|
||||
fun fireAndForget(requestAction: () -> Unit): DiscoveryRequirementRequest {
|
||||
return DiscoveryRequirementRequest(requestAction, awaitable = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.model
|
||||
|
||||
data class SelectLedgerModel(
|
||||
val name: String,
|
||||
val id: String,
|
||||
val isConnecting: Boolean,
|
||||
)
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine
|
||||
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryMethods
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryRequirement
|
||||
|
||||
sealed class SideEffect {
|
||||
|
||||
data class RequestPermissions(val requirements: List<DiscoveryRequirement>, val shouldExitUponDenial: Boolean) : SideEffect()
|
||||
|
||||
data class RequestSatisfyRequirement(val requirements: List<DiscoveryRequirement>) : SideEffect()
|
||||
|
||||
data class PresentLedgerFailure(val reason: Throwable, val device: LedgerDevice) : SideEffect()
|
||||
|
||||
data class VerifyConnection(val device: LedgerDevice) : SideEffect()
|
||||
|
||||
data class StartDiscovery(val methods: Set<DiscoveryMethods.Method>) : SideEffect()
|
||||
|
||||
data class StopDiscovery(val methods: Set<DiscoveryMethods.Method>) : SideEffect()
|
||||
}
|
||||
|
||||
sealed class SelectLedgerEvent {
|
||||
|
||||
data class DiscoveryRequirementSatisfied(val requirement: DiscoveryRequirement) : SelectLedgerEvent()
|
||||
|
||||
data class DiscoveryRequirementMissing(val requirement: DiscoveryRequirement) : SelectLedgerEvent()
|
||||
|
||||
object PermissionsGranted : SelectLedgerEvent() {
|
||||
override fun toString(): String {
|
||||
return "PermissionsGranted"
|
||||
}
|
||||
}
|
||||
|
||||
object AvailabilityRequestsAllowed : SelectLedgerEvent() {
|
||||
override fun toString(): String {
|
||||
return "AvailabilityRequestsAllowed"
|
||||
}
|
||||
}
|
||||
|
||||
data class DiscoveredDevicesListChanged(val newDevices: List<LedgerDevice>) : SelectLedgerEvent()
|
||||
|
||||
data class DeviceChosen(val device: LedgerDevice) : SelectLedgerEvent()
|
||||
|
||||
data class VerificationFailed(val reason: Throwable) : SelectLedgerEvent()
|
||||
|
||||
object ConnectionVerified : SelectLedgerEvent() {
|
||||
override fun toString(): String {
|
||||
return "ConnectionVerified"
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.states
|
||||
|
||||
import io.novafoundation.nova.common.utils.stateMachine.StateMachine
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryMethods
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryRequirementAvailability
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SelectLedgerEvent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SelectLedgerEvent.ConnectionVerified
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SelectLedgerEvent.DeviceChosen
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SelectLedgerEvent.DiscoveredDevicesListChanged
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SelectLedgerEvent.VerificationFailed
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SideEffect
|
||||
|
||||
data class DevicesFoundState(
|
||||
val devices: List<LedgerDevice>,
|
||||
val verifyingDevice: LedgerDevice?,
|
||||
private val discoveryMethods: DiscoveryMethods,
|
||||
private val discoveryRequirementAvailability: DiscoveryRequirementAvailability,
|
||||
private val usedAllowedRequirementAvailabilityRequests: Boolean
|
||||
) : SelectLedgerState() {
|
||||
|
||||
context(StateMachine.Transition<SelectLedgerState, SideEffect>)
|
||||
override suspend fun performTransition(event: SelectLedgerEvent) {
|
||||
when (event) {
|
||||
is SelectLedgerEvent.AvailabilityRequestsAllowed -> userAllowedAvailabilityRequests(
|
||||
discoveryMethods = discoveryMethods,
|
||||
discoveryRequirementAvailability = discoveryRequirementAvailability,
|
||||
nextState = copy(usedAllowedRequirementAvailabilityRequests = true)
|
||||
)
|
||||
|
||||
is VerificationFailed -> verifyingDevice?.let { device ->
|
||||
emitState(copy(verifyingDevice = null))
|
||||
emitSideEffect(SideEffect.PresentLedgerFailure(event.reason, device))
|
||||
}
|
||||
|
||||
is ConnectionVerified -> verifyingDevice?.let {
|
||||
emitState(
|
||||
DevicesFoundState(
|
||||
devices = devices,
|
||||
verifyingDevice = null,
|
||||
discoveryMethods = discoveryMethods,
|
||||
discoveryRequirementAvailability = discoveryRequirementAvailability,
|
||||
usedAllowedRequirementAvailabilityRequests = usedAllowedRequirementAvailabilityRequests
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is DeviceChosen -> {
|
||||
emitState(copy(verifyingDevice = event.device))
|
||||
emitSideEffect(SideEffect.VerifyConnection(event.device))
|
||||
}
|
||||
|
||||
is DiscoveredDevicesListChanged -> emitState(copy(devices = event.newDevices))
|
||||
|
||||
else -> updateActiveDiscoveryMethodsByEvent(
|
||||
allDiscoveryMethods = discoveryMethods,
|
||||
previousRequirementsAvailability = discoveryRequirementAvailability,
|
||||
event = event,
|
||||
userAllowedRequirementAvailabilityRequests = usedAllowedRequirementAvailabilityRequests
|
||||
) { newSatisfiedRequirements ->
|
||||
DevicesFoundState(devices, verifyingDevice, discoveryMethods, newSatisfiedRequirements, usedAllowedRequirementAvailabilityRequests)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.states
|
||||
|
||||
import io.novafoundation.nova.common.utils.stateMachine.StateMachine
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryMethods
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryRequirementAvailability
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SelectLedgerEvent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SelectLedgerEvent.DiscoveredDevicesListChanged
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SideEffect
|
||||
|
||||
data class DiscoveringState(
|
||||
private val discoveryMethods: DiscoveryMethods,
|
||||
private val discoveryRequirementAvailability: DiscoveryRequirementAvailability,
|
||||
private val usedAllowedRequirementAvailabilityRequests: Boolean
|
||||
) : SelectLedgerState() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun initial(discoveryMethods: DiscoveryMethods, initialRequirementAvailability: DiscoveryRequirementAvailability): DiscoveringState {
|
||||
return DiscoveringState(discoveryMethods, initialRequirementAvailability, usedAllowedRequirementAvailabilityRequests = false)
|
||||
}
|
||||
}
|
||||
|
||||
context(StateMachine.Transition<SelectLedgerState, SideEffect>)
|
||||
override suspend fun performTransition(event: SelectLedgerEvent) {
|
||||
when (event) {
|
||||
is SelectLedgerEvent.AvailabilityRequestsAllowed -> userAllowedAvailabilityRequests(
|
||||
discoveryMethods = discoveryMethods,
|
||||
discoveryRequirementAvailability = discoveryRequirementAvailability,
|
||||
nextState = copy(usedAllowedRequirementAvailabilityRequests = true)
|
||||
)
|
||||
|
||||
is DiscoveredDevicesListChanged -> if (event.newDevices.isNotEmpty()) {
|
||||
val newState = DevicesFoundState(
|
||||
devices = event.newDevices,
|
||||
verifyingDevice = null,
|
||||
discoveryMethods = discoveryMethods,
|
||||
discoveryRequirementAvailability = discoveryRequirementAvailability,
|
||||
usedAllowedRequirementAvailabilityRequests = usedAllowedRequirementAvailabilityRequests
|
||||
)
|
||||
emitState(newState)
|
||||
}
|
||||
|
||||
else -> updateActiveDiscoveryMethodsByEvent(
|
||||
allDiscoveryMethods = discoveryMethods,
|
||||
previousRequirementsAvailability = discoveryRequirementAvailability,
|
||||
event = event,
|
||||
userAllowedRequirementAvailabilityRequests = usedAllowedRequirementAvailabilityRequests
|
||||
) { newDiscoveryRequirementAvailability ->
|
||||
DiscoveringState(discoveryMethods, newDiscoveryRequirementAvailability, usedAllowedRequirementAvailabilityRequests)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context(StateMachine.Transition<SelectLedgerState, SideEffect>)
|
||||
override suspend fun bootstrap() {
|
||||
// Start discovery for all methods available from the start
|
||||
// I.e. those that do not have any requirements or already have all permissions / requirements satisfied
|
||||
updateActiveDiscoveryMethods(
|
||||
allDiscoveryMethods = discoveryMethods,
|
||||
previousActiveMethods = emptySet(),
|
||||
newRequirementsAvailability = discoveryRequirementAvailability
|
||||
)
|
||||
|
||||
// Perform initial automatic requests to requirements and permissions if needed
|
||||
// It's important to request permissions firstly since bluetooth request will crash application without permissions after android 12
|
||||
requestPermissions(discoveryMethods, discoveryRequirementAvailability, usedAllowedRequirementAvailabilityRequests)
|
||||
requestMissingDiscoveryRequirements(discoveryMethods, discoveryRequirementAvailability, usedAllowedRequirementAvailabilityRequests)
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.states
|
||||
|
||||
import io.novafoundation.nova.common.utils.stateMachine.StateMachine
|
||||
import io.novafoundation.nova.common.utils.stateMachine.StateMachine.Transition
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryMethods
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryRequirement
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryRequirementAvailability
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.discoveryRequirements
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.filterBySatisfiedRequirements
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.grantPermissions
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.missRequirement
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.satisfyRequirement
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SelectLedgerEvent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.stateMachine.SideEffect
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.DiscoveryMethods.Method as DiscoveryMethod
|
||||
|
||||
sealed class SelectLedgerState : StateMachine.State<SelectLedgerState, SideEffect, SelectLedgerEvent> {
|
||||
|
||||
context(Transition<SelectLedgerState, SideEffect>)
|
||||
protected suspend fun updateActiveDiscoveryMethodsByEvent(
|
||||
allDiscoveryMethods: DiscoveryMethods,
|
||||
previousRequirementsAvailability: DiscoveryRequirementAvailability,
|
||||
event: SelectLedgerEvent,
|
||||
userAllowedRequirementAvailabilityRequests: Boolean,
|
||||
newState: (newRequirementsAvailability: DiscoveryRequirementAvailability) -> SelectLedgerState
|
||||
) {
|
||||
val newPreviousSatisfiedRequirements = updateDiscoveryRequirementAvailabilityByEvent(previousRequirementsAvailability, event) ?: return
|
||||
|
||||
emitState(newState(newPreviousSatisfiedRequirements))
|
||||
|
||||
updateActiveDiscoveryMethods(
|
||||
allDiscoveryMethods,
|
||||
previousRequirementsAvailability,
|
||||
newPreviousSatisfiedRequirements,
|
||||
)
|
||||
|
||||
if (event is SelectLedgerEvent.DiscoveryRequirementMissing) {
|
||||
requestSatisfyDiscoveryRequirement(allDiscoveryMethods, event.requirement, userAllowedRequirementAvailabilityRequests)
|
||||
}
|
||||
}
|
||||
|
||||
context(Transition<SelectLedgerState, SideEffect>)
|
||||
protected suspend fun userAllowedAvailabilityRequests(
|
||||
discoveryMethods: DiscoveryMethods,
|
||||
discoveryRequirementAvailability: DiscoveryRequirementAvailability,
|
||||
nextState: SelectLedgerState,
|
||||
) {
|
||||
emitState(nextState)
|
||||
|
||||
requestPermissions(discoveryMethods, discoveryRequirementAvailability, usedAllowedRequirementAvailabilityRequests = true)
|
||||
requestMissingDiscoveryRequirements(discoveryMethods, discoveryRequirementAvailability, usedAllowedRequirementAvailabilityRequests = true)
|
||||
}
|
||||
|
||||
context(Transition<SelectLedgerState, SideEffect>)
|
||||
protected suspend fun updateActiveDiscoveryMethods(
|
||||
allDiscoveryMethods: DiscoveryMethods,
|
||||
previousRequirementsAvailability: DiscoveryRequirementAvailability,
|
||||
newRequirementsAvailability: DiscoveryRequirementAvailability,
|
||||
) {
|
||||
val previousActiveDiscoveryMethods = allDiscoveryMethods.filterBySatisfiedRequirements(previousRequirementsAvailability)
|
||||
updateActiveDiscoveryMethods(
|
||||
allDiscoveryMethods,
|
||||
previousActiveDiscoveryMethods,
|
||||
newRequirementsAvailability,
|
||||
)
|
||||
}
|
||||
|
||||
context(Transition<SelectLedgerState, SideEffect>)
|
||||
protected suspend fun updateActiveDiscoveryMethods(
|
||||
allDiscoveryMethods: DiscoveryMethods,
|
||||
previousActiveMethods: Set<DiscoveryMethod>,
|
||||
newRequirementsAvailability: DiscoveryRequirementAvailability,
|
||||
) {
|
||||
val newActiveMethods = allDiscoveryMethods.filterBySatisfiedRequirements(newRequirementsAvailability)
|
||||
|
||||
val methodsToStart = newActiveMethods - previousActiveMethods
|
||||
val methodsToStop = previousActiveMethods - newActiveMethods
|
||||
|
||||
startDiscovery(methodsToStart)
|
||||
stopDiscovery(methodsToStop)
|
||||
}
|
||||
|
||||
context(Transition<SelectLedgerState, SideEffect>)
|
||||
protected suspend fun requestPermissions(
|
||||
allDiscoveryMethods: DiscoveryMethods,
|
||||
newRequirementsAvailability: DiscoveryRequirementAvailability,
|
||||
usedAllowedRequirementAvailabilityRequests: Boolean
|
||||
) {
|
||||
val allDiscoveryRequirements = allDiscoveryMethods.discoveryRequirements()
|
||||
|
||||
val canRequestPermissions = canPerformAvailabilityRequests(allDiscoveryMethods, usedAllowedRequirementAvailabilityRequests)
|
||||
|
||||
// We only need permissions when there is at least one requirement (assuming each requirement requires at least one permission)
|
||||
// and permissions has not been granted yet
|
||||
val permissionsNeeded = allDiscoveryRequirements.isNotEmpty() && !newRequirementsAvailability.permissionsGranted
|
||||
|
||||
if (canRequestPermissions && permissionsNeeded) {
|
||||
val shouldExitUponDenial = allDiscoveryMethods.methods.size == 1
|
||||
|
||||
emitSideEffect(SideEffect.RequestPermissions(allDiscoveryRequirements, shouldExitUponDenial))
|
||||
}
|
||||
}
|
||||
|
||||
context(Transition<SelectLedgerState, SideEffect>)
|
||||
protected suspend fun requestMissingDiscoveryRequirements(
|
||||
discoveryMethods: DiscoveryMethods,
|
||||
discoveryRequirementAvailability: DiscoveryRequirementAvailability,
|
||||
usedAllowedRequirementAvailabilityRequests: Boolean
|
||||
) {
|
||||
val canRequestRequirement = canPerformAvailabilityRequests(discoveryMethods, usedAllowedRequirementAvailabilityRequests)
|
||||
if (!canRequestRequirement) return
|
||||
|
||||
val allRequirements = discoveryMethods.discoveryRequirements()
|
||||
val missingRequirements = allRequirements - discoveryRequirementAvailability.satisfiedRequirements
|
||||
|
||||
if (missingRequirements.isNotEmpty()) {
|
||||
emitSideEffect(SideEffect.RequestSatisfyRequirement(missingRequirements))
|
||||
}
|
||||
}
|
||||
|
||||
context(Transition<SelectLedgerState, SideEffect>)
|
||||
private suspend fun requestSatisfyDiscoveryRequirement(
|
||||
allDiscoveryMethods: DiscoveryMethods,
|
||||
requirement: DiscoveryRequirement,
|
||||
usedAllowedRequirementAvailabilityRequests: Boolean
|
||||
) {
|
||||
val canRequestRequirement = canPerformAvailabilityRequests(allDiscoveryMethods, usedAllowedRequirementAvailabilityRequests)
|
||||
|
||||
if (canRequestRequirement) {
|
||||
emitSideEffect(SideEffect.RequestSatisfyRequirement(listOf(requirement)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun canPerformAvailabilityRequests(
|
||||
allDiscoveryMethods: DiscoveryMethods,
|
||||
usedAllowedRequirementAvailabilityRequests: Boolean
|
||||
): Boolean {
|
||||
// We can only perform availability requests if there is a single method or a user explicitly pressed a button to allow such requests
|
||||
// Otherwise we don't bother them with the automatic requests as there might be methods that do not need any requirements at all
|
||||
return allDiscoveryMethods.methods.size == 1 || usedAllowedRequirementAvailabilityRequests
|
||||
}
|
||||
|
||||
private fun updateDiscoveryRequirementAvailabilityByEvent(
|
||||
availability: DiscoveryRequirementAvailability,
|
||||
event: SelectLedgerEvent
|
||||
): DiscoveryRequirementAvailability? {
|
||||
return when (event) {
|
||||
is SelectLedgerEvent.DiscoveryRequirementMissing -> availability.missRequirement(event.requirement)
|
||||
is SelectLedgerEvent.DiscoveryRequirementSatisfied -> availability.satisfyRequirement(event.requirement)
|
||||
is SelectLedgerEvent.PermissionsGranted -> availability.grantPermissions()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
context(Transition<SelectLedgerState, SideEffect>)
|
||||
private suspend fun startDiscovery(methods: Set<DiscoveryMethod>) {
|
||||
if (methods.isNotEmpty()) {
|
||||
emitSideEffect(SideEffect.StartDiscovery(methods))
|
||||
}
|
||||
}
|
||||
|
||||
context(Transition<SelectLedgerState, SideEffect>)
|
||||
private suspend fun stopDiscovery(methods: Set<DiscoveryMethod>) {
|
||||
if (methods.isNotEmpty()) {
|
||||
emitSideEffect(SideEffect.StopDiscovery(methods))
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import io.novafoundation.nova.common.utils.WithContextExtensions
|
||||
import io.novafoundation.nova.common.utils.setDrawableEnd
|
||||
import io.novafoundation.nova.common.utils.setTextColorRes
|
||||
import io.novafoundation.nova.common.utils.updatePadding
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
|
||||
class ItemLedgerView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : AppCompatTextView(context, attrs, defStyleAttr), WithContextExtensions by WithContextExtensions(context) {
|
||||
|
||||
init {
|
||||
setTextAppearance(R.style.TextAppearance_NovaFoundation_Regular_SubHeadline)
|
||||
setTextColorRes(R.color.text_primary)
|
||||
|
||||
setDrawableEnd(R.drawable.ic_chevron_right, widthInDp = 24, paddingInDp = 4, tint = R.color.icon_secondary)
|
||||
|
||||
updatePadding(
|
||||
top = 14.dp,
|
||||
bottom = 14.dp,
|
||||
start = 12.dp,
|
||||
end = 12.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.start
|
||||
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.list.instruction.InstructionItem
|
||||
import io.novafoundation.nova.common.mixin.impl.observeBrowserEvents
|
||||
import io.novafoundation.nova.common.utils.formatting.spannable.highlightedText
|
||||
import io.novafoundation.nova.common.utils.setupWithViewPager2
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.databinding.FragmentImportLedgerStartBinding
|
||||
|
||||
private const val BLUETOOTH_PAGE_INDEX = 0
|
||||
private const val USB_PAGE_INDEX = 1
|
||||
|
||||
abstract class StartImportLedgerFragment<VM : StartImportLedgerViewModel> :
|
||||
BaseFragment<VM, FragmentImportLedgerStartBinding>(),
|
||||
StartImportLedgerPagerAdapter.Handler {
|
||||
|
||||
protected val pageAdapter by lazy(LazyThreadSafetyMode.NONE) { StartImportLedgerPagerAdapter(createPages(), this) }
|
||||
|
||||
override fun createBinding() = FragmentImportLedgerStartBinding.inflate(layoutInflater)
|
||||
|
||||
override fun initViews() {
|
||||
binder.startImportLedgerToolbar.setHomeButtonListener { viewModel.backClicked() }
|
||||
|
||||
binder.startImportLedgerContinue.setOnClickListener {
|
||||
when (binder.startImportLedgerConnectionModePages.currentItem) {
|
||||
BLUETOOTH_PAGE_INDEX -> viewModel.continueWithBluetooth()
|
||||
USB_PAGE_INDEX -> viewModel.continueWithUsb()
|
||||
}
|
||||
}
|
||||
|
||||
binder.startImportLedgerConnectionModePages.adapter = pageAdapter
|
||||
binder.startImportLedgerConnectionMode.setupWithViewPager2(binder.startImportLedgerConnectionModePages, pageAdapter::getPageTitle)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: VM) {
|
||||
observeBrowserEvents(viewModel)
|
||||
}
|
||||
|
||||
override fun guideLinkClicked() {
|
||||
viewModel.guideClicked()
|
||||
}
|
||||
|
||||
private fun createPages(): List<ConnectionModePageModel> {
|
||||
return buildList {
|
||||
add(BLUETOOTH_PAGE_INDEX, createBluetoothPage())
|
||||
add(USB_PAGE_INDEX, createUSBPage())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBluetoothPage(): ConnectionModePageModel {
|
||||
return ConnectionModePageModel(
|
||||
modeName = getString(R.string.start_import_ledger_connection_mode_bluetooth),
|
||||
guideItems = listOf(
|
||||
InstructionItem.Step(1, networkAppIsInstalledStep()),
|
||||
InstructionItem.Step(2, openingNetworkAppStep()),
|
||||
InstructionItem.Step(3, enableBluetoothStep()),
|
||||
InstructionItem.Step(4, selectAccountStep())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun createUSBPage(): ConnectionModePageModel {
|
||||
return ConnectionModePageModel(
|
||||
modeName = getString(R.string.start_import_ledger_connection_mode_usb),
|
||||
guideItems = listOf(
|
||||
InstructionItem.Step(1, networkAppIsInstalledStep()),
|
||||
InstructionItem.Step(2, openingNetworkAppStep()),
|
||||
InstructionItem.Step(3, enableOTGSetting()),
|
||||
InstructionItem.Step(4, selectAccountStep())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
abstract fun networkAppIsInstalledStep(): CharSequence
|
||||
|
||||
abstract fun openingNetworkAppStep(): CharSequence
|
||||
|
||||
private fun enableBluetoothStep() = requireContext().highlightedText(
|
||||
R.string.account_ledger_import_start_step_3,
|
||||
R.string.account_ledger_import_start_step_3_highlighted
|
||||
)
|
||||
|
||||
private fun enableOTGSetting() = requireContext().highlightedText(
|
||||
R.string.account_ledger_import_start_step_otg,
|
||||
R.string.account_ledger_import_start_step_otg_highlighted
|
||||
)
|
||||
|
||||
private fun selectAccountStep() = requireContext().highlightedText(
|
||||
R.string.account_ledger_import_start_step_4,
|
||||
R.string.account_ledger_import_start_step_4_highlighted
|
||||
)
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.start
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import io.novafoundation.nova.common.list.instruction.InstructionAdapter
|
||||
import io.novafoundation.nova.common.list.instruction.InstructionItem
|
||||
import io.novafoundation.nova.common.utils.inflater
|
||||
import io.novafoundation.nova.common.view.AlertModel
|
||||
import io.novafoundation.nova.common.view.setModelOrHide
|
||||
import io.novafoundation.nova.feature_ledger_impl.databinding.ItemImportLedgerStartPageBinding
|
||||
|
||||
class ConnectionModePageModel(
|
||||
val modeName: String,
|
||||
val guideItems: List<InstructionItem>
|
||||
)
|
||||
|
||||
class StartImportLedgerPagerAdapter(
|
||||
private val pages: List<ConnectionModePageModel>,
|
||||
private val handler: Handler
|
||||
) : RecyclerView.Adapter<StartImportLedgerPageViewHolder>() {
|
||||
|
||||
interface Handler {
|
||||
fun guideLinkClicked()
|
||||
}
|
||||
|
||||
private var alertModel: AlertModel? = null
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return pages.size
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StartImportLedgerPageViewHolder {
|
||||
val binder = ItemImportLedgerStartPageBinding.inflate(parent.inflater(), parent, false)
|
||||
return StartImportLedgerPageViewHolder(binder, handler)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: StartImportLedgerPageViewHolder, position: Int) {
|
||||
holder.bind(pages[position], alertModel)
|
||||
}
|
||||
|
||||
fun showWarning(alertModel: AlertModel?) {
|
||||
this.alertModel = alertModel
|
||||
repeat(pages.size) { notifyItemChanged(it) }
|
||||
}
|
||||
|
||||
fun getPageTitle(position: Int): CharSequence {
|
||||
return pages[position].modeName
|
||||
}
|
||||
}
|
||||
|
||||
class StartImportLedgerPageViewHolder(
|
||||
private val binder: ItemImportLedgerStartPageBinding,
|
||||
private val handler: StartImportLedgerPagerAdapter.Handler
|
||||
) : ViewHolder(binder.root) {
|
||||
|
||||
private val adapter = InstructionAdapter()
|
||||
|
||||
init {
|
||||
binder.startImportLedgerInstructions.adapter = adapter
|
||||
binder.startImportLedgerGuideLink.setOnClickListener { handler.guideLinkClicked() }
|
||||
}
|
||||
|
||||
fun bind(page: ConnectionModePageModel, alertModel: AlertModel?) {
|
||||
adapter.submitList(page.guideItems)
|
||||
binder.startImportLedgerWarning.setModelOrHide(alertModel)
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.common.start
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
import io.novafoundation.nova.common.utils.event
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
|
||||
abstract class StartImportLedgerViewModel(
|
||||
private val router: LedgerRouter,
|
||||
private val appLinksProvider: AppLinksProvider
|
||||
) : BaseViewModel(), Browserable {
|
||||
|
||||
override val openBrowserEvent = MutableLiveData<Event<String>>()
|
||||
|
||||
fun backClicked() {
|
||||
router.back()
|
||||
}
|
||||
|
||||
abstract fun continueWithBluetooth()
|
||||
|
||||
abstract fun continueWithUsb()
|
||||
|
||||
fun guideClicked() {
|
||||
openBrowserEvent.value = appLinksProvider.ledgerConnectionGuide.event()
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.common.payload
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerEvmAccount
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class LedgerGenericEvmAccountParcel(
|
||||
val publicKey: ByteArray,
|
||||
val accountId: AccountId,
|
||||
) : Parcelable
|
||||
|
||||
fun LedgerEvmAccount.toParcel(): LedgerGenericEvmAccountParcel {
|
||||
return LedgerGenericEvmAccountParcel(publicKey = publicKey, accountId = accountId)
|
||||
}
|
||||
|
||||
fun LedgerGenericEvmAccountParcel.toDomain(): LedgerEvmAccount {
|
||||
return LedgerEvmAccount(accountId = accountId, publicKey = publicKey)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.common.payload
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount
|
||||
import io.novasama.substrate_sdk_android.encrypt.EncryptionType
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class LedgerGenericSubstrateAccountParcel(
|
||||
val address: String,
|
||||
val publicKey: ByteArray,
|
||||
val encryptionType: EncryptionType,
|
||||
val derivationPath: String,
|
||||
) : Parcelable
|
||||
|
||||
fun LedgerSubstrateAccount.toGenericParcel(): LedgerGenericSubstrateAccountParcel {
|
||||
return LedgerGenericSubstrateAccountParcel(address, publicKey, encryptionType, derivationPath)
|
||||
}
|
||||
|
||||
fun LedgerGenericSubstrateAccountParcel.toDomain(): LedgerSubstrateAccount {
|
||||
return LedgerSubstrateAccount(address, publicKey, encryptionType, derivationPath)
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish
|
||||
|
||||
import android.os.Bundle
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.createName.CreateWalletNameFragment
|
||||
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.LedgerFeatureComponent
|
||||
|
||||
class FinishImportGenericLedgerFragment : CreateWalletNameFragment<FinishImportGenericLedgerViewModel>() {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PAYLOAD_KEY = "FinishImportLedgerFragment.Payload"
|
||||
|
||||
fun getBundle(payload: FinishImportGenericLedgerPayload): Bundle {
|
||||
return Bundle().apply {
|
||||
putParcelable(PAYLOAD_KEY, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<LedgerFeatureComponent>(requireContext(), LedgerFeatureApi::class.java)
|
||||
.finishGenericImportLedgerComponentFactory()
|
||||
.create(this, argument(PAYLOAD_KEY))
|
||||
.inject(this)
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.common.payload.LedgerGenericEvmAccountParcel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.common.payload.LedgerGenericSubstrateAccountParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class FinishImportGenericLedgerPayload(
|
||||
val substrateAccount: LedgerGenericSubstrateAccountParcel,
|
||||
val evmAccount: LedgerGenericEvmAccountParcel?,
|
||||
) : Parcelable
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.createName.CreateWalletNameViewModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.connect.generic.finish.FinishImportGenericLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.common.payload.toDomain
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class FinishImportGenericLedgerViewModel(
|
||||
private val router: LedgerRouter,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val payload: FinishImportGenericLedgerPayload,
|
||||
private val accountInteractor: AccountInteractor,
|
||||
private val interactor: FinishImportGenericLedgerInteractor
|
||||
) : CreateWalletNameViewModel(router, resourceManager) {
|
||||
|
||||
override fun proceed(name: String) {
|
||||
launch {
|
||||
interactor.createWallet(name, payload.substrateAccount.toDomain(), payload.evmAccount?.toDomain())
|
||||
.onSuccess { continueBasedOnCodeStatus() }
|
||||
.onFailure(::showError)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun continueBasedOnCodeStatus() {
|
||||
if (accountInteractor.isCodeSet()) {
|
||||
router.openMain()
|
||||
} else {
|
||||
router.openCreatePincode()
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.FinishImportGenericLedgerFragment
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.FinishImportGenericLedgerPayload
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
FinishImportGenericLedgerModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface FinishImportGenericLedgerComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: FinishImportGenericLedgerPayload,
|
||||
): FinishImportGenericLedgerComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: FinishImportGenericLedgerFragment)
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.GenericLedgerAddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.connect.generic.finish.FinishImportGenericLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.connect.generic.finish.RealFinishImportGenericLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.FinishImportGenericLedgerPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.FinishImportGenericLedgerViewModel
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class FinishImportGenericLedgerModule {
|
||||
|
||||
@Provides
|
||||
@ScreenScope
|
||||
fun provideInteractor(
|
||||
genericLedgerAddAccountRepository: GenericLedgerAddAccountRepository,
|
||||
accountRepository: AccountRepository,
|
||||
): FinishImportGenericLedgerInteractor = RealFinishImportGenericLedgerInteractor(
|
||||
genericLedgerAddAccountRepository = genericLedgerAddAccountRepository,
|
||||
accountRepository = accountRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(FinishImportGenericLedgerViewModel::class)
|
||||
fun provideViewModel(
|
||||
router: LedgerRouter,
|
||||
resourceManager: ResourceManager,
|
||||
payload: FinishImportGenericLedgerPayload,
|
||||
accountInteractor: AccountInteractor,
|
||||
interactor: FinishImportGenericLedgerInteractor
|
||||
): ViewModel {
|
||||
return FinishImportGenericLedgerViewModel(
|
||||
router = router,
|
||||
resourceManager = resourceManager,
|
||||
payload = payload,
|
||||
accountInteractor = accountInteractor,
|
||||
interactor = interactor
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(fragment: Fragment, viewModelFactory: ViewModelProvider.Factory): FinishImportGenericLedgerViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(FinishImportGenericLedgerViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview
|
||||
|
||||
import android.os.Bundle
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.chain.ChainAccountsAdapter
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.chain.preview.BaseChainAccountsPreviewFragment
|
||||
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.LedgerFeatureComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessagePresentable
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.setupLedgerMessages
|
||||
import javax.inject.Inject
|
||||
|
||||
class PreviewImportGenericLedgerFragment : BaseChainAccountsPreviewFragment<PreviewImportGenericLedgerViewModel>(), ChainAccountsAdapter.Handler {
|
||||
|
||||
@Inject
|
||||
lateinit var ledgerMessagePresentable: LedgerMessagePresentable
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PAYLOAD_KEY = "PreviewImportGenericLedgerFragment.Payload"
|
||||
|
||||
fun getBundle(payload: PreviewImportGenericLedgerPayload): Bundle {
|
||||
return Bundle().apply {
|
||||
putParcelable(PAYLOAD_KEY, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<LedgerFeatureComponent>(
|
||||
requireContext(),
|
||||
LedgerFeatureApi::class.java
|
||||
)
|
||||
.previewImportGenericLedgerComponentFactory()
|
||||
.create(this, argument(PAYLOAD_KEY))
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: PreviewImportGenericLedgerViewModel) {
|
||||
super.subscribe(viewModel)
|
||||
|
||||
setupLedgerMessages(ledgerMessagePresentable)
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.common.payload.LedgerGenericEvmAccountParcel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.common.payload.LedgerGenericSubstrateAccountParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class PreviewImportGenericLedgerPayload(
|
||||
val accountIndex: Int,
|
||||
val substrateAccount: LedgerGenericSubstrateAccountParcel,
|
||||
val evmAccount: LedgerGenericEvmAccountParcel?,
|
||||
val deviceId: String
|
||||
) : Parcelable
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
import io.novafoundation.nova.common.address.format.AddressSchemeFormatter
|
||||
import io.novafoundation.nova.common.list.toListWithHeaders
|
||||
import io.novafoundation.nova.common.presentation.DescriptiveButtonState
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
import io.novafoundation.nova.common.utils.event
|
||||
import io.novafoundation.nova.common.utils.flowOf
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.chain.model.ChainAccountGroupUi
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.chain.preview.BaseChainAccountsPreviewViewModel
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
|
||||
import io.novafoundation.nova.feature_ledger_impl.R
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.connect.generic.preview.PreviewImportGenericLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommand
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessageCommands
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.createLedgerReviewAddresses
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.errors.handleLedgerError
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.FinishImportGenericLedgerPayload
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novasama.substrate_sdk_android.extensions.asEthereumAccountId
|
||||
import io.novasama.substrate_sdk_android.extensions.toAddress
|
||||
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class PreviewImportGenericLedgerViewModel(
|
||||
private val interactor: PreviewImportGenericLedgerInteractor,
|
||||
private val router: LedgerRouter,
|
||||
private val iconGenerator: AddressIconGenerator,
|
||||
private val payload: PreviewImportGenericLedgerPayload,
|
||||
private val externalActions: ExternalActions.Presentation,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val messageCommandFormatter: MessageCommandFormatter,
|
||||
private val addressSchemeFormatter: AddressSchemeFormatter,
|
||||
) : BaseChainAccountsPreviewViewModel(
|
||||
iconGenerator = iconGenerator,
|
||||
externalActions = externalActions,
|
||||
chainRegistry = chainRegistry,
|
||||
router = router
|
||||
),
|
||||
LedgerMessageCommands {
|
||||
|
||||
override val ledgerMessageCommands: MutableLiveData<Event<LedgerMessageCommand>> = MutableLiveData()
|
||||
|
||||
override val chainAccountProjections = flowOf {
|
||||
interactor.availableChainAccounts(
|
||||
substrateAccountId = payload.substrateAccount.address.toAccountId(),
|
||||
evmAccountId = payload.evmAccount?.accountId
|
||||
).toListWithHeaders(
|
||||
keyMapper = { scheme, _ -> formatGroupHeader(scheme) },
|
||||
valueMapper = { account -> mapChainAccountPreviewToUi(account) }
|
||||
)
|
||||
}
|
||||
.shareInBackground()
|
||||
|
||||
override val buttonState: Flow<DescriptiveButtonState> = flowOf {
|
||||
DescriptiveButtonState.Enabled(resourceManager.getString(R.string.common_continue))
|
||||
}
|
||||
|
||||
val device = flowOf {
|
||||
interactor.getDevice(payload.deviceId)
|
||||
}
|
||||
|
||||
private var verifyAddressJob: Job? = null
|
||||
|
||||
override fun continueClicked() {
|
||||
verifyAddressJob?.cancel()
|
||||
verifyAddressJob = launch {
|
||||
verifyAccount()
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatGroupHeader(addressScheme: AddressScheme): ChainAccountGroupUi {
|
||||
return ChainAccountGroupUi(
|
||||
id = addressScheme.name,
|
||||
title = addressSchemeFormatter.accountsLabel(addressScheme),
|
||||
action = null
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun verifyAccount() {
|
||||
val device = device.first()
|
||||
|
||||
ledgerMessageCommands.value = messageCommandFormatter.reviewAddressCommand(
|
||||
addresses = createLedgerReviewAddresses(
|
||||
allowedAddressSchemes = AddressScheme.entries,
|
||||
AddressScheme.SUBSTRATE to payload.substrateAccount.address,
|
||||
AddressScheme.EVM to payload.evmAccount?.accountId?.asEthereumAccountId()?.toAddress()?.value
|
||||
),
|
||||
device = device,
|
||||
onCancel = ::verifyAddressCancelled,
|
||||
).event()
|
||||
val result = withContext(Dispatchers.Default) {
|
||||
interactor.verifyAddressOnLedger(payload.accountIndex, payload.deviceId)
|
||||
}
|
||||
|
||||
result.onFailure {
|
||||
handleLedgerError(
|
||||
reason = it,
|
||||
device = device,
|
||||
commandFormatter = messageCommandFormatter,
|
||||
onRetry = ::continueClicked
|
||||
)
|
||||
}.onSuccess {
|
||||
ledgerMessageCommands.value = messageCommandFormatter.hideCommand().event()
|
||||
|
||||
onAccountVerified()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAccountVerified() {
|
||||
val nextPayload = FinishImportGenericLedgerPayload(payload.substrateAccount, payload.evmAccount)
|
||||
router.openFinishImportLedgerGeneric(nextPayload)
|
||||
}
|
||||
|
||||
private fun verifyAddressCancelled() {
|
||||
ledgerMessageCommands.value = messageCommandFormatter.hideCommand().event()
|
||||
verifyAddressJob?.cancel()
|
||||
verifyAddressJob = null
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.PreviewImportGenericLedgerFragment
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.PreviewImportGenericLedgerPayload
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
PreviewImportGenericLedgerModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface PreviewImportGenericLedgerComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: PreviewImportGenericLedgerPayload
|
||||
): PreviewImportGenericLedgerComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: PreviewImportGenericLedgerFragment)
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.address.format.AddressSchemeFormatter
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_core.domain.LedgerMigrationTracker
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.annotations.GenericLedger
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.connect.generic.preview.PreviewImportGenericLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.connect.generic.preview.RealPreviewImportGenericLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.PreviewImportGenericLedgerPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.PreviewImportGenericLedgerViewModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.sdk.application.substrate.newApp.GenericSubstrateLedgerApplication
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class PreviewImportGenericLedgerModule {
|
||||
|
||||
@Provides
|
||||
@ScreenScope
|
||||
fun provideInteractor(
|
||||
ledgerMigrationTracker: LedgerMigrationTracker,
|
||||
genericSubstrateLedgerApplication: GenericSubstrateLedgerApplication,
|
||||
ledgerDiscoveryService: LedgerDeviceDiscoveryService
|
||||
): PreviewImportGenericLedgerInteractor {
|
||||
return RealPreviewImportGenericLedgerInteractor(ledgerMigrationTracker, genericSubstrateLedgerApplication, ledgerDiscoveryService)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(PreviewImportGenericLedgerViewModel::class)
|
||||
fun provideViewModel(
|
||||
interactor: PreviewImportGenericLedgerInteractor,
|
||||
router: LedgerRouter,
|
||||
iconGenerator: AddressIconGenerator,
|
||||
payload: PreviewImportGenericLedgerPayload,
|
||||
externalActions: ExternalActions.Presentation,
|
||||
chainRegistry: ChainRegistry,
|
||||
resourceManager: ResourceManager,
|
||||
@GenericLedger messageCommandFormatter: MessageCommandFormatter,
|
||||
addressSchemeFormatter: AddressSchemeFormatter
|
||||
): ViewModel {
|
||||
return PreviewImportGenericLedgerViewModel(
|
||||
interactor = interactor,
|
||||
router = router,
|
||||
iconGenerator = iconGenerator,
|
||||
payload = payload,
|
||||
externalActions = externalActions,
|
||||
chainRegistry = chainRegistry,
|
||||
resourceManager = resourceManager,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
addressSchemeFormatter = addressSchemeFormatter
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(
|
||||
fragment: Fragment,
|
||||
viewModelFactory: ViewModelProvider.Factory
|
||||
): PreviewImportGenericLedgerViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(PreviewImportGenericLedgerViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectAddress
|
||||
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.utils.makeGone
|
||||
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.LedgerFeatureComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectAddressLedgerFragment
|
||||
|
||||
class SelectAddressImportGenericLedgerFragment : SelectAddressLedgerFragment<SelectAddressImportGenericLedgerViewModel>() {
|
||||
|
||||
override fun initViews() {
|
||||
super.initViews()
|
||||
|
||||
binder.ledgerSelectAddressChain.makeGone()
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<LedgerFeatureComponent>(requireContext(), LedgerFeatureApi::class.java)
|
||||
.selectAddressImportLedgerGenericComponentFactory()
|
||||
.create(this, payload())
|
||||
.inject(this)
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectAddress
|
||||
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.view.AlertModel
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LedgerVariant
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.AddressActionsMixin
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.LedgerAccount
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.generic.GenericLedgerEvmAlertFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectAddressLedgerViewModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.model.AddressVerificationMode
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.common.payload.toGenericParcel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.common.payload.toParcel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.PreviewImportGenericLedgerPayload
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SelectAddressImportGenericLedgerViewModel(
|
||||
private val router: LedgerRouter,
|
||||
private val payload: SelectLedgerAddressPayload,
|
||||
interactor: SelectAddressLedgerInteractor,
|
||||
addressIconGenerator: AddressIconGenerator,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val evmUpdateFormatter: GenericLedgerEvmAlertFormatter,
|
||||
chainRegistry: ChainRegistry,
|
||||
messageCommandFormatter: MessageCommandFormatter,
|
||||
addressActionsMixinFactory: AddressActionsMixin.Factory
|
||||
) : SelectAddressLedgerViewModel(
|
||||
router = router,
|
||||
interactor = interactor,
|
||||
addressIconGenerator = addressIconGenerator,
|
||||
resourceManager = resourceManager,
|
||||
payload = payload,
|
||||
chainRegistry = chainRegistry,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
addressActionsMixinFactory = addressActionsMixinFactory
|
||||
) {
|
||||
|
||||
override val ledgerVariant: LedgerVariant = LedgerVariant.GENERIC
|
||||
|
||||
override val addressVerificationMode = AddressVerificationMode.Disabled
|
||||
|
||||
init {
|
||||
loadedAccounts.onEach { accounts ->
|
||||
val needsUpdateToSupportEvm = accounts.any { it.evm == null }
|
||||
val model = createAlertModel(needsUpdateToSupportEvm)
|
||||
_alertFlow.emit(model)
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
override fun onAccountVerified(account: LedgerAccount) {
|
||||
launch {
|
||||
val payload = PreviewImportGenericLedgerPayload(
|
||||
accountIndex = account.index,
|
||||
substrateAccount = account.substrate.toGenericParcel(),
|
||||
evmAccount = account.evm?.toParcel(),
|
||||
deviceId = payload.deviceId
|
||||
)
|
||||
|
||||
router.openPreviewLedgerAccountsGeneric(payload)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAlertModel(needsUpdateToSupportEvm: Boolean): AlertModel? {
|
||||
return if (needsUpdateToSupportEvm) {
|
||||
evmUpdateFormatter.createUpdateAppToGetEvmAddressAlert()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectAddress.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectAddress.SelectAddressImportGenericLedgerFragment
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
SelectAddressImportGenericLedgerModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface SelectAddressImportGenericLedgerComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: SelectLedgerAddressPayload,
|
||||
): SelectAddressImportGenericLedgerComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: SelectAddressImportGenericLedgerFragment)
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectAddress.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.AddressActionsMixin
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.annotations.GenericLedger
|
||||
import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.generic.GenericLedgerEvmAlertFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectAddress.SelectAddressImportGenericLedgerViewModel
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class SelectAddressImportGenericLedgerModule {
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(SelectAddressImportGenericLedgerViewModel::class)
|
||||
fun provideViewModel(
|
||||
router: LedgerRouter,
|
||||
interactor: SelectAddressLedgerInteractor,
|
||||
addressIconGenerator: AddressIconGenerator,
|
||||
resourceManager: ResourceManager,
|
||||
payload: SelectLedgerAddressPayload,
|
||||
chainRegistry: ChainRegistry,
|
||||
@GenericLedger messageCommandFormatter: MessageCommandFormatter,
|
||||
evmAlertFormatter: GenericLedgerEvmAlertFormatter,
|
||||
addressActionsMixinFactory: AddressActionsMixin.Factory
|
||||
): ViewModel {
|
||||
return SelectAddressImportGenericLedgerViewModel(
|
||||
router = router,
|
||||
interactor = interactor,
|
||||
addressIconGenerator = addressIconGenerator,
|
||||
resourceManager = resourceManager,
|
||||
payload = payload,
|
||||
chainRegistry = chainRegistry,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
evmUpdateFormatter = evmAlertFormatter,
|
||||
addressActionsMixinFactory = addressActionsMixinFactory
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(fragment: Fragment, viewModelFactory: ViewModelProvider.Factory): SelectAddressImportGenericLedgerViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(SelectAddressImportGenericLedgerViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.LedgerFeatureComponent
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerFragment
|
||||
|
||||
class SelectLedgerGenericImportFragment : SelectLedgerFragment<SelectLedgerGenericImportViewModel>() {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PAYLOAD_KEY = "SelectLedgerGenericImportFragment.PAYLOAD_KEY"
|
||||
|
||||
fun getBundle(payload: SelectLedgerGenericPayload): Bundle = bundleOf(PAYLOAD_KEY to payload)
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<LedgerFeatureComponent>(requireContext(), LedgerFeatureApi::class.java)
|
||||
.selectLedgerGenericImportComponentFactory()
|
||||
.create(this, argument(PAYLOAD_KEY))
|
||||
.inject(this)
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger
|
||||
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager
|
||||
import io.novafoundation.nova.common.utils.event
|
||||
import io.novafoundation.nova.common.utils.location.LocationManager
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers.LedgerDeviceFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerPayload
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerViewModel
|
||||
import io.novafoundation.nova.runtime.ext.ChainGeneses
|
||||
|
||||
class SelectLedgerGenericImportViewModel(
|
||||
private val router: LedgerRouter,
|
||||
private val messageCommandFormatter: MessageCommandFormatter,
|
||||
discoveryService: LedgerDeviceDiscoveryService,
|
||||
permissionsAsker: PermissionsAsker.Presentation,
|
||||
bluetoothManager: BluetoothManager,
|
||||
locationManager: LocationManager,
|
||||
resourceManager: ResourceManager,
|
||||
messageFormatter: LedgerMessageFormatter,
|
||||
payload: SelectLedgerPayload,
|
||||
deviceMapperFactory: LedgerDeviceFormatter,
|
||||
) : SelectLedgerViewModel(
|
||||
discoveryService = discoveryService,
|
||||
permissionsAsker = permissionsAsker,
|
||||
bluetoothManager = bluetoothManager,
|
||||
locationManager = locationManager,
|
||||
router = router,
|
||||
resourceManager = resourceManager,
|
||||
messageFormatter = messageFormatter,
|
||||
ledgerDeviceFormatter = deviceMapperFactory,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
payload = payload
|
||||
) {
|
||||
|
||||
override suspend fun verifyConnection(device: LedgerDevice) {
|
||||
ledgerMessageCommands.value = messageCommandFormatter.hideCommand().event()
|
||||
|
||||
val payload = SelectLedgerAddressPayload(
|
||||
deviceId = device.id,
|
||||
substrateChainId = getPreviewBalanceChainId()
|
||||
)
|
||||
|
||||
router.openSelectAddressGenericLedger(payload)
|
||||
}
|
||||
|
||||
private fun getPreviewBalanceChainId() = ChainGeneses.POLKADOT
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger
|
||||
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerPayload
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class SelectLedgerGenericPayload(override val connectionMode: SelectLedgerPayload.ConnectionMode) : SelectLedgerPayload
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger.SelectLedgerGenericImportFragment
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger.SelectLedgerGenericPayload
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
SelectLedgerGenericImportModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface SelectLedgerGenericImportComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: SelectLedgerGenericPayload,
|
||||
): SelectLedgerGenericImportComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: SelectLedgerGenericImportFragment)
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.di.modules.shared.PermissionAskerForFragmentModule
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager
|
||||
import io.novafoundation.nova.common.utils.location.LocationManager
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService
|
||||
import io.novafoundation.nova.feature_ledger_impl.di.annotations.GenericLedger
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.MessageCommandFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.mappers.LedgerDeviceFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.formatters.LedgerMessageFormatter
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger.SelectLedgerGenericImportViewModel
|
||||
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.selectLedger.SelectLedgerGenericPayload
|
||||
|
||||
@Module(includes = [ViewModelModule::class, PermissionAskerForFragmentModule::class])
|
||||
class SelectLedgerGenericImportModule {
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(SelectLedgerGenericImportViewModel::class)
|
||||
fun provideViewModel(
|
||||
discoveryService: LedgerDeviceDiscoveryService,
|
||||
permissionsAsker: PermissionsAsker.Presentation,
|
||||
bluetoothManager: BluetoothManager,
|
||||
locationManager: LocationManager,
|
||||
router: LedgerRouter,
|
||||
resourceManager: ResourceManager,
|
||||
@GenericLedger messageFormatter: LedgerMessageFormatter,
|
||||
payload: SelectLedgerGenericPayload,
|
||||
deviceMapperFactory: LedgerDeviceFormatter,
|
||||
@GenericLedger messageCommandFormatter: MessageCommandFormatter
|
||||
): ViewModel {
|
||||
return SelectLedgerGenericImportViewModel(
|
||||
discoveryService = discoveryService,
|
||||
permissionsAsker = permissionsAsker,
|
||||
bluetoothManager = bluetoothManager,
|
||||
locationManager = locationManager,
|
||||
router = router,
|
||||
resourceManager = resourceManager,
|
||||
messageFormatter = messageFormatter,
|
||||
deviceMapperFactory = deviceMapperFactory,
|
||||
messageCommandFormatter = messageCommandFormatter,
|
||||
payload = payload
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(fragment: Fragment, viewModelFactory: ViewModelProvider.Factory): SelectLedgerGenericImportViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(SelectLedgerGenericImportViewModel::class.java)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user