diff --git a/common/src/main/java/org/dash/wallet/common/services/BlockchainStateProvider.kt b/common/src/main/java/org/dash/wallet/common/services/BlockchainStateProvider.kt index 8cfe18ec0b..232bf69557 100644 --- a/common/src/main/java/org/dash/wallet/common/services/BlockchainStateProvider.kt +++ b/common/src/main/java/org/dash/wallet/common/services/BlockchainStateProvider.kt @@ -19,6 +19,7 @@ package org.dash.wallet.common.services import kotlinx.coroutines.flow.Flow import org.bitcoinj.core.AbstractBlockChain +import org.bitcoinj.core.PeerGroup import org.dash.wallet.common.data.entity.BlockchainState import org.dash.wallet.common.data.NetworkStatus @@ -43,4 +44,6 @@ interface BlockchainStateProvider { fun getBlockChain(): AbstractBlockChain? fun observeBlockChain(): Flow + + fun observeSyncStage(): Flow } diff --git a/wallet/build.gradle b/wallet/build.gradle index 7c382246e8..21695b72ca 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -51,13 +51,10 @@ dependencies { implementation 'org.bouncycastle:bcprov-jdk15to18:1.74' implementation "org.dashj:dashj-core:$dashjVersion" - implementation 'org.dashj.platform:dashpay:0.24-MOCK-SNAPSHOT' - implementation 'org.dashj.platform:platform-core:0.24-MOCK-SNAPSHOT' - implementation 'org.dashj.platform:dpp:0.24-SNAPSHOT' - implementation 'org.dashj.platform:dapi-client:0.24-SNAPSHOT' - implementation 'org.dashj:dashj-merk:0.22-SNAPSHOT' - implementation 'org.dashj.android:dashj-merk:0.22-SNAPSHOT' - implementation 'io.grpc:grpc-stub:1.28.0' // CURRENT_GRPC_VERSION + implementation 'org.dashj.platform:dash-sdk-java:1.0-SNAPSHOT' + implementation 'org.dashj.platform:dash-sdk-kotlin:1.0-SNAPSHOT' + implementation 'org.dashj.platform:dash-sdk-android:1.0-SNAPSHOT' + implementation 'io.grpc:grpc-stub:1.54.0' // CURRENT_GRPC_VERSION implementation "org.dashj.android:dashj-bls-android:1.0.0" implementation "org.dashj.android:dashj-x11-android:0.17.5" implementation "org.dashj.android:dashj-scrypt-android:0.17.5" @@ -208,8 +205,8 @@ android { compileSdk 33 minSdkVersion 24 targetSdkVersion 33 - versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 90002 - versionName project.hasProperty('versionName') ? project.property('versionName') : "5.4-dashpay" + versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 90008 + versionName project.hasProperty('versionName') ? project.property('versionName') : "5.5-dashpay" multiDexEnabled true generatedDensities = ['hdpi', 'xhdpi'] vectorDrawables.useSupportLibrary = true diff --git a/wallet/proguard.cfg b/wallet/proguard.cfg index 12d537bb65..574b1fdc82 100644 --- a/wallet/proguard.cfg +++ b/wallet/proguard.cfg @@ -68,6 +68,9 @@ -dontnote com.squareup.okhttp.internal.Platform -dontwarn org.bitcoinj.store.LevelDBFullPrunedBlockStore** +# dash-sdk +-keep class org.dashj.platform.sdk.** { *; } + # zxing -dontwarn com.google.zxing.common.BitMatrix diff --git a/wallet/src/de/schildbach/wallet/Constants.java b/wallet/src/de/schildbach/wallet/Constants.java index a7c22c15c8..f4a6f80b4c 100644 --- a/wallet/src/de/schildbach/wallet/Constants.java +++ b/wallet/src/de/schildbach/wallet/Constants.java @@ -128,7 +128,7 @@ public final class Constants { /** Bitcoinj global context. */ public static final Context CONTEXT = new Context(NETWORK_PARAMETERS); - public static final boolean DASHPAY_DISABLED = true; + public static final boolean DASHPAY_DISABLED = false; public final static class Files { diff --git a/wallet/src/de/schildbach/wallet/database/BlockchainStateRoomConverters.kt b/wallet/src/de/schildbach/wallet/database/BlockchainStateRoomConverters.kt index 1411d1a45e..23d8bf046b 100644 --- a/wallet/src/de/schildbach/wallet/database/BlockchainStateRoomConverters.kt +++ b/wallet/src/de/schildbach/wallet/database/BlockchainStateRoomConverters.kt @@ -21,13 +21,12 @@ import android.net.Uri import androidx.room.TypeConverter import de.schildbach.wallet.database.entity.BlockchainIdentityData import de.schildbach.wallet.data.InvitationLinkData -import de.schildbach.wallet.ui.dashpay.PlatformRepo import org.dashj.platform.dashpay.BlockchainIdentity import org.dashj.platform.dpp.identity.Identity -import org.dashj.platform.dpp.identity.IdentityPublicKey import org.dash.wallet.common.data.entity.BlockchainState import org.bitcoinj.core.Coin import org.bitcoinj.core.Sha256Hash +import org.dashj.platform.sdk.KeyType import java.util.* import kotlin.collections.ArrayList @@ -99,12 +98,12 @@ class BlockchainStateRoomConverters { } @TypeConverter - fun toCurrentMainKeyType(value: Int): IdentityPublicKey.Type? { - return if (value > -1) IdentityPublicKey.Type.values()[value] else null + fun toCurrentMainKeyType(value: Int): KeyType? { + return if (value > -1) KeyType.entries[value] else null } @TypeConverter - fun fromCurrentMainKeyType(currentMainKeyType: IdentityPublicKey.Type?): Int { + fun fromCurrentMainKeyType(currentMainKeyType: KeyType?): Int { return currentMainKeyType?.ordinal ?: -1 } diff --git a/wallet/src/de/schildbach/wallet/database/entity/BlockchainIdentityData.kt b/wallet/src/de/schildbach/wallet/database/entity/BlockchainIdentityData.kt index 63891ba6b7..3d496c84f7 100644 --- a/wallet/src/de/schildbach/wallet/database/entity/BlockchainIdentityData.kt +++ b/wallet/src/de/schildbach/wallet/database/entity/BlockchainIdentityData.kt @@ -38,9 +38,9 @@ import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.data.BaseConfig import org.dashj.platform.dashpay.BlockchainIdentity import org.dashj.platform.dpp.identity.Identity -import org.dashj.platform.dpp.identity.IdentityPublicKey import org.dashj.platform.dpp.toHex import org.dashj.platform.dpp.util.Converters +import org.dashj.platform.sdk.KeyType import javax.inject.Inject import javax.inject.Singleton @@ -62,7 +62,7 @@ data class BlockchainIdentityData(var creationState: CreationState = CreationSta var totalKeyCount: Int? = null, var keysCreated: Long? = null, var currentMainKeyIndex: Int? = null, - var currentMainKeyType: IdentityPublicKey.Type? = null) { + var currentMainKeyType: KeyType? = null) { var id = 1 set(@Suppress("UNUSED_PARAMETER") value) { @@ -73,7 +73,11 @@ data class BlockchainIdentityData(var creationState: CreationState = CreationSta fun findAssetLockTransaction(wallet: Wallet?): AssetLockTransaction? { if (creditFundingTxId == null) { - return null + val authExtension = + wallet!!.getKeyChainExtension(AuthenticationGroupExtension.EXTENSION_ID) as AuthenticationGroupExtension + val list = authExtension.assetLockTransactions + list.sortBy { it.updateTime } + return if (list.isEmpty()) null else list.first() } if (wallet != null) { creditFundingTransactionCache = wallet.getTransaction(creditFundingTxId)?.run { @@ -141,7 +145,7 @@ open class BlockchainIdentityConfig @Inject constructor( val BALANCE = longPreferencesKey("identity_balance") } - val identityData: Flow = data + private val identityData: Flow = data .map { prefs -> BlockchainIdentityData( creationState = BlockchainIdentityData.CreationState.valueOf(prefs[CREATION_STATE] ?: "NONE"), @@ -161,7 +165,7 @@ open class BlockchainIdentityConfig @Inject constructor( ) } - val identityBaseData: Flow = data + private val identityBaseData: Flow = data .map { prefs -> BlockchainIdentityBaseData( 1, diff --git a/wallet/src/de/schildbach/wallet/database/entity/DashPayContactRequest.kt b/wallet/src/de/schildbach/wallet/database/entity/DashPayContactRequest.kt index 1daeb5493b..a7d73409c7 100644 --- a/wallet/src/de/schildbach/wallet/database/entity/DashPayContactRequest.kt +++ b/wallet/src/de/schildbach/wallet/database/entity/DashPayContactRequest.kt @@ -43,14 +43,14 @@ data class DashPayContactRequest(val userId: String, companion object { fun fromDocument(document: Document): DashPayContactRequest { val timestamp: Long = if (document.createdAt != null) document.createdAt!! else 0L - val toUserId = Base58.encode(document.data["toUserId"] as ByteArray) + val toUserId = Base58.encode((document.data["toUserId"] as Identifier).toBuffer()) val encryptedAccountLabel: ByteArray? = if (document.data.containsKey("encryptedAccountLabel")) document.data["encryptedAccountLabel"] as ByteArray else null val accountReference: Int = if (document.data.containsKey("accountReference")) - document.data["accountReference"] as Int + (document.data["accountReference"] as Long).toInt() else 0 val autoAcceptProof: ByteArray? = if (document.data.containsKey("autoAcceptProof")) @@ -60,8 +60,8 @@ data class DashPayContactRequest(val userId: String, return DashPayContactRequest(document.ownerId.toString(), toUserId, accountReference, document.data["encryptedPublicKey"] as ByteArray, - document.data["senderKeyIndex"] as Int, - document.data["recipientKeyIndex"] as Int, + (document.data["senderKeyIndex"] as Long).toInt(), + (document.data["recipientKeyIndex"] as Long).toInt(), timestamp, encryptedAccountLabel, autoAcceptProof) diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java index 91b3bd3710..8e5a93c000 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java @@ -53,7 +53,6 @@ import org.bitcoinj.core.CheckpointManager; import org.bitcoinj.core.Coin; import org.bitcoinj.core.FilteredBlock; -import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Peer; import org.bitcoinj.core.PeerGroup; import org.bitcoinj.core.Sha256Hash; @@ -192,7 +191,7 @@ public class BlockchainServiceImpl extends LifecycleService implements Blockchai private BlockchainState blockchainState = new BlockchainState(null, 0, false, impediments, 0, 0, 0); private int notificationCount = 0; private Coin notificationAccumulatedAmount = Coin.ZERO; - private final List
notificationAddresses = new LinkedList
(); + private final List
notificationAddresses = new LinkedList<>(); private AtomicInteger transactionsReceived = new AtomicInteger(); private AtomicInteger mnListDiffsReceived = new AtomicInteger(); private long serviceCreatedAt; @@ -1302,7 +1301,10 @@ private void updateBlockchainStateImpediments() { } private void updateBlockchainState() { - blockchainStateDataProvider.updateBlockchainState(blockChain, impediments, percentageSync()); + blockchainStateDataProvider.updateBlockchainState( + blockChain, impediments, percentageSync(), + peerGroup != null ? peerGroup.getSyncStage() : null + ); } @Override diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt b/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt index 5080a3d3d2..381d276f83 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt +++ b/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt @@ -34,6 +34,7 @@ import org.bitcoinj.core.BlockChain import org.bitcoinj.core.CheckpointManager import org.bitcoinj.core.Coin import org.bitcoinj.core.NetworkParameters +import org.bitcoinj.core.PeerGroup import org.bitcoinj.core.StoredBlock import org.bitcoinj.store.BlockStoreException import org.dash.wallet.common.Configuration @@ -42,7 +43,6 @@ import org.dash.wallet.common.data.entity.BlockchainState import org.dash.wallet.common.data.NetworkStatus import org.dash.wallet.common.data.entity.BlockchainState.Impediment import org.dash.wallet.common.services.BlockchainStateProvider -import org.slf4j.LoggerFactory import java.io.IOException import java.io.InputStream import java.math.BigInteger @@ -86,6 +86,7 @@ class BlockchainStateDataProvider @Inject constructor( private val networkStatusFlow = MutableStateFlow(NetworkStatus.UNKNOWN) private val blockchainFlow = MutableStateFlow(null) + private val syncStageFlow = MutableStateFlow(null) override suspend fun getState(): BlockchainState? { return blockchainStateDao.getState() @@ -106,7 +107,7 @@ class BlockchainStateDataProvider @Inject constructor( } } - fun updateBlockchainState(blockChain: BlockChain, impediments: Set, percentageSync: Int) { + fun updateBlockchainState(blockChain: BlockChain, impediments: Set, percentageSync: Int, syncStage: PeerGroup.SyncStage?) { coroutineScope.launch { var blockchainState = blockchainStateDao.getState() if (blockchainState == null) { @@ -123,6 +124,7 @@ class BlockchainStateDataProvider @Inject constructor( blockchainState.mnlistHeight = mnListHeight blockchainState.percentageSync = percentageSync blockchainStateDao.saveState(blockchainState) + syncStageFlow.value = syncStage } } @@ -182,7 +184,11 @@ class BlockchainStateDataProvider @Inject constructor( } override fun observeBlockChain(): Flow { - return blockchainFlow; + return blockchainFlow + } + + override fun observeSyncStage(): Flow { + return syncStageFlow } override fun getMasternodeAPY(): Double { diff --git a/wallet/src/de/schildbach/wallet/service/platform/PlatformBroadcastService.kt b/wallet/src/de/schildbach/wallet/service/platform/PlatformBroadcastService.kt index 5cdca170e2..4a51a2bd41 100644 --- a/wallet/src/de/schildbach/wallet/service/platform/PlatformBroadcastService.kt +++ b/wallet/src/de/schildbach/wallet/service/platform/PlatformBroadcastService.kt @@ -28,10 +28,8 @@ import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.services.analytics.AnalyticsService import org.dash.wallet.common.services.analytics.AnalyticsTimer -import org.dashj.platform.dashpay.RetryDelayType import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.util.concurrent.TimeoutException import javax.inject.Inject interface PlatformBroadcastService { @@ -127,20 +125,11 @@ class PlatformDocumentBroadcastService @Inject constructor( timer.logTiming() log.info("profile broadcast") - //Verify that the Contact Request was seen on the network - val updatedProfile = blockchainIdentity.watchProfile(100, 5000, RetryDelayType.LINEAR) + // TODO: Verify that the Contact Request was seen on the network? - if (createdProfile != updatedProfile) { - log.warn("Created profile doesn't match profile from network $createdProfile != $updatedProfile") - } - - log.info("updated profile: $updatedProfile") - if (updatedProfile != null) { - val updatedDashPayProfile = DashPayProfile.fromDocument(updatedProfile, dashPayProfile.username) - platformRepo.updateDashPayProfile(updatedDashPayProfile!!) //update the database since the cr was accepted - return updatedDashPayProfile - } else { - throw TimeoutException("timeout when updating profile") - } + log.info("updated profile: $createdProfile") + val updatedDashPayProfile = DashPayProfile.fromDocument(createdProfile, dashPayProfile.username) + platformRepo.updateDashPayProfile(updatedDashPayProfile!!) //update the database since the cr was accepted + return updatedDashPayProfile } } diff --git a/wallet/src/de/schildbach/wallet/service/platform/PlatformService.kt b/wallet/src/de/schildbach/wallet/service/platform/PlatformService.kt index 201d9f725d..03de8fe20d 100644 --- a/wallet/src/de/schildbach/wallet/service/platform/PlatformService.kt +++ b/wallet/src/de/schildbach/wallet/service/platform/PlatformService.kt @@ -22,16 +22,23 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.withContext +import org.bitcoinj.core.Context import org.bitcoinj.core.NetworkParameters +import org.bitcoinj.core.Sha256Hash import org.bitcoinj.evolution.SimplifiedMasternodeListManager +import org.bitcoinj.quorums.LLMQParameters +import org.dash.wallet.common.WalletDataProvider import org.dashj.platform.dapiclient.DapiClient import org.dashj.platform.dashpay.ContactRequests import org.dashj.platform.dashpay.Profiles import org.dashj.platform.dpp.DashPlatformProtocol +import org.dashj.platform.dpp.toHex +import org.dashj.platform.sdk.callbacks.ContextProvider import org.dashj.platform.sdk.platform.Identities import org.dashj.platform.sdk.platform.Names import org.dashj.platform.sdk.platform.Platform import org.dashj.platform.sdk.platform.PlatformStateRepository +import org.slf4j.LoggerFactory import javax.inject.Inject /** @@ -49,13 +56,13 @@ interface PlatformService { val client: DapiClient val params: NetworkParameters - suspend fun isPlatformAvailable(): Resource + suspend fun isPlatformAvailable(): Boolean fun hasApp(app: String): Boolean fun setMasternodeListManager(masternodeListManager: SimplifiedMasternodeListManager) } class PlatformServiceImplementation @Inject constructor( - + walletDataProvider: WalletDataProvider ) : PlatformService { override val platform = Platform(Constants.NETWORK_PARAMETERS) override val profiles = Profiles(platform) @@ -67,6 +74,40 @@ class PlatformServiceImplementation @Inject constructor( override val client: DapiClient = platform.client override val params: NetworkParameters = platform.params + companion object { + private val log = LoggerFactory.getLogger(PlatformServiceImplementation::class.java) + } + init { + val contextProvider = object : ContextProvider() { + override fun getQuorumPublicKey( + quorumType: Int, + quorumHashBytes: ByteArray?, + coreChainLockedHeight: Int + ): ByteArray? { + val quorumHash = Sha256Hash.wrap(quorumHashBytes) + var quorumPublicKey: ByteArray? = null + log.info("searching for quorum: $quorumType, $quorumHash, $coreChainLockedHeight") + Context.propagate(walletDataProvider.wallet!!.context) + Context.get().masternodeListManager.getQuorumListAtTip( + LLMQParameters.LLMQType.fromValue( + quorumType + ) + ).forEachQuorum(true) { + if (it.llmqType.value == quorumType && it.quorumHash == quorumHash) { + quorumPublicKey = it.quorumPublicKey.serialize(false) + } + } + log.info("searching for quorum: result: ${quorumPublicKey?.toHex()}") + return quorumPublicKey + } + + override fun getDataContract(identifier: org.dashj.platform.sdk.Identifier?): ByteArray { + TODO("Not yet implemented") + } + } + platform.client.contextProvider = contextProvider + } + override fun hasApp(app: String): Boolean { return platform.hasApp(app) } @@ -76,23 +117,26 @@ class PlatformServiceImplementation @Inject constructor( * * @return true if platform is available */ - override suspend fun isPlatformAvailable(): Resource { + override suspend fun isPlatformAvailable(): Boolean { return withContext(Dispatchers.IO) { var success = 0 val checks = arrayListOf>() for (i in 0 until 3) { - checks.add(async { platform.check() }) + checks.add(async { + try { + platform.check() + } catch (e: Exception) { + return@async false + } + }) } for (check in checks) { success += if (check.await()) 1 else 0 } - return@withContext if (success >= 2) { - Resource.success(true) - } else { - Resource.error("Platform is not available") - } + return@withContext success >= 2 + //return@withContext Resource.success(true) } } diff --git a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt index 1464386825..e81566a67e 100644 --- a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt +++ b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt @@ -69,6 +69,7 @@ import org.dash.wallet.common.util.TickerFlow import org.dashj.platform.contracts.wallet.TxMetadataItem import org.dashj.platform.dashpay.ContactRequest import org.dashj.platform.dpp.identifier.Identifier +import org.dashj.platform.sdk.platform.DomainDocument import org.slf4j.Logger import org.slf4j.LoggerFactory import java.util.HashMap @@ -84,6 +85,7 @@ import kotlin.time.Duration.Companion.seconds interface PlatformSyncService { fun init() + fun initSync() fun resume() fun shutdown() @@ -104,14 +106,14 @@ interface PlatformSyncService { } class PlatformSynchronizationService @Inject constructor( - val platform: PlatformService, - val platformRepo: PlatformRepo, + private val platform: PlatformService, + private val platformRepo: PlatformRepo, val analytics: AnalyticsService, - val config: DashPayConfig, - val walletApplication: WalletApplication, - val transactionMetadataProvider: TransactionMetadataProvider, - val transactionMetadataChangeCacheDao: TransactionMetadataChangeCacheDao, - val transactionMetadataDocumentDao: TransactionMetadataDocumentDao, + private val config: DashPayConfig, + private val walletApplication: WalletApplication, + private val transactionMetadataProvider: TransactionMetadataProvider, + private val transactionMetadataChangeCacheDao: TransactionMetadataChangeCacheDao, + private val transactionMetadataDocumentDao: TransactionMetadataDocumentDao, private val blockchainIdentityDataDao: BlockchainIdentityConfig, private val dashPayProfileDao: DashPayProfileDao, private val dashPayContactRequestDao: DashPayContactRequestDao, @@ -128,7 +130,7 @@ class PlatformSynchronizationService @Inject constructor( val CUTOFF_MAX = if (BuildConfig.DEBUG) 6.minutes else 6.hours } - private lateinit var platformSyncJob: Job + private var platformSyncJob: Job? = null private val updatingContacts = AtomicBoolean(false) private val preDownloadBlocks = AtomicBoolean(false) private var preDownloadBlocksFuture: SettableFuture? = null @@ -144,17 +146,13 @@ class PlatformSynchronizationService @Inject constructor( override fun init() { syncScope.launch { platformRepo.init() } log.info("Starting the platform sync job") - initSync() } override fun resume() { - if (!platformSyncJob.isActive && platformRepo.hasIdentity) { - log.info("Resuming the platform sync job") - initSync() - } + // This method may not be required. initSync must be called by PreBlockDownload handler } - private fun initSync() { + override fun initSync() { platformSyncJob = TickerFlow(UPDATE_TIMER_DELAY) .onEach { updateContactRequests() } .launchIn(syncScope) @@ -176,10 +174,12 @@ class PlatformSynchronizationService @Inject constructor( } override fun shutdown() { - if (platformRepo.hasIdentity) { - Preconditions.checkState(platformSyncJob.isActive) + if (platformSyncJob != null && platformRepo.hasIdentity) { + Preconditions.checkState(platformSyncJob!!.isActive) log.info("Shutting down the platform sync job") syncScope.coroutineContext.cancelChildren(CancellationException("shutdown the platform sync")) + platformSyncJob!!.cancel(null) + platformSyncJob = null } } @@ -344,9 +344,7 @@ class PlatformSynchronizationService @Inject constructor( log.error(platformRepo.formatExceptionMessage("error updating contacts", e)) } finally { updatingContacts.set(false) - if (preDownloadBlocks.get()) { - finishPreBlockDownload() - } + counterForReport++ if (counterForReport % 8 == 0) { @@ -354,6 +352,10 @@ class PlatformSynchronizationService @Inject constructor( log.info(platform.client.reportNetworkStatus()) } } + // This needs to be here to ensure that the pre-block download stage always completes + if (preDownloadBlocks.get()) { + finishPreBlockDownload() + } } override fun updateSyncStatus(stage: PreBlockStage) { @@ -525,15 +527,14 @@ class PlatformSynchronizationService @Inject constructor( ) val profileById = profileDocuments.associateBy({ it.ownerId }, { it }) - val nameDocuments = platform.names.getList(identifierList) + val nameDocuments = platform.names.getList(identifierList).map { DomainDocument(it) } val nameById = nameDocuments.associateBy({ platformRepo.getIdentityForName(it) }, { it }) for (id in profileById.keys) { if (nameById.containsKey(id)) { - val nameDocument = - nameById[id] // what happens if there is no username for the identity? crash - val username = nameDocument!!.data["normalizedLabel"] as String + val nameDocument = nameById[id]!! // what happens if there is no username for the identity? crash + val username = nameDocument.label val identityId = platformRepo.getIdentityForName(nameDocument) val profileDocument = profileById[id] @@ -564,7 +565,7 @@ class PlatformSynchronizationService @Inject constructor( val nameDocument = nameById[Identifier.from(identityId)] // what happens if there is no username for the identity? crash if (nameDocument != null) { - val username = nameDocument.data["normalizedLabel"] as String + val username = nameDocument.label val identityIdForName = platformRepo.getIdentityForName(nameDocument) dashPayProfileDao.insert( DashPayProfile( @@ -1039,7 +1040,7 @@ class PlatformSynchronizationService @Inject constructor( // Nevertheless, platformSyncJob should be inactive when the BlockchainService is destroyed // Perhaps the updateContactRequests method is being run while the job is canceled - if (platformSyncJob.isActive) { + if (platformSyncJob?.isActive == true) { if (isRunningInForeground()) { log.info("attempting to update bloom filters when the app is in the foreground") val intent = Intent( @@ -1056,7 +1057,10 @@ class PlatformSynchronizationService @Inject constructor( } /** - * Called before DashJ starts synchronizing the blockchain + * Called before DashJ starts synchronizing the blockchain, + * Platform DAPI calls should be delayed until this function + * is called because an updated Masternode and Quorun List is + * required for proof verification */ override fun preBlockDownload(future: SettableFuture) { syncScope.launch(Dispatchers.IO) { @@ -1097,6 +1101,7 @@ class PlatformSynchronizationService @Inject constructor( else if (!updatingContacts.get()) { updateContactRequests() } + initSync() } } diff --git a/wallet/src/de/schildbach/wallet/ui/ContactViewHolder.kt b/wallet/src/de/schildbach/wallet/ui/ContactViewHolder.kt index 8e3f17932e..60d155d75e 100644 --- a/wallet/src/de/schildbach/wallet/ui/ContactViewHolder.kt +++ b/wallet/src/de/schildbach/wallet/ui/ContactViewHolder.kt @@ -28,23 +28,22 @@ import de.schildbach.wallet.data.UsernameSearchResult import de.schildbach.wallet.livedata.Resource import de.schildbach.wallet.ui.dashpay.utils.display import de.schildbach.wallet_test.R -import de.schildbach.wallet_test.databinding.ContactRowBinding import de.schildbach.wallet_test.databinding.DashpayContactRowBinding import de.schildbach.wallet_test.databinding.DashpayContactRowContentBinding import de.schildbach.wallet_test.databinding.DashpayContactSuggestionRowBinding import org.dash.wallet.common.ui.avatar.ProfilePictureDisplay -class ContactViewHolder(val binding: DashpayContactRowBinding, val isSuggestion: Boolean = false, val useFriendsIcon: Boolean = true) - : RecyclerView.ViewHolder(binding.root) { +interface OnItemClickListener { + fun onItemClicked(view: View, usernameSearchResult: UsernameSearchResult) +} - interface OnItemClickListener { - fun onItemClicked(view: View, usernameSearchResult: UsernameSearchResult) - } +interface OnContactRequestButtonClickListener { + fun onAcceptRequest(usernameSearchResult: UsernameSearchResult, position: Int) + fun onIgnoreRequest(usernameSearchResult: UsernameSearchResult, position: Int) +} - interface OnContactRequestButtonClickListener { - fun onAcceptRequest(usernameSearchResult: UsernameSearchResult, position: Int) - fun onIgnoreRequest(usernameSearchResult: UsernameSearchResult, position: Int) - } +class ContactViewHolder(val binding: DashpayContactRowBinding, val isSuggestion: Boolean = false, val useFriendsIcon: Boolean = true) + : RecyclerView.ViewHolder(binding.root) { val contactRowBinding = DashpayContactRowContentBinding.bind(binding.contactRow) @@ -121,94 +120,85 @@ class ContactViewHolder(val binding: DashpayContactRowBinding, val isSuggestion: } } -class ContactSuggestionViewHolder(val binding: DashpayContactSuggestionRowBinding, val isSuggestion: Boolean = false, val useFriendsIcon: Boolean = true) - : RecyclerView.ViewHolder(binding.root) { - - interface OnItemClickListener { - fun onItemClicked(view: View, usernameSearchResult: UsernameSearchResult) - } - - interface OnContactRequestButtonClickListener { - fun onAcceptRequest(usernameSearchResult: UsernameSearchResult, position: Int) - fun onIgnoreRequest(usernameSearchResult: UsernameSearchResult, position: Int) - } - - val contactRowBinding = DashpayContactRowContentBinding.bind(binding.contactRow) - - fun bind( - usernameSearchResult: UsernameSearchResult, - sendContactRequestWorkState: Resource?, listener: OnItemClickListener?, - contactRequestButtonClickListener: OnContactRequestButtonClickListener?, - networkAvailable: Boolean = true - ) { - - val dashPayProfile = usernameSearchResult.dashPayProfile - if (dashPayProfile.displayName.isEmpty()) { - contactRowBinding.displayName.text = dashPayProfile.username - contactRowBinding.username.text = "" - } else { - contactRowBinding.displayName.text = dashPayProfile.displayName - contactRowBinding.username.text = usernameSearchResult.username - } - - ProfilePictureDisplay.display(contactRowBinding.avatar, dashPayProfile) - - itemView.setOnClickListener { - listener?.onItemClicked(itemView, usernameSearchResult) - } - - if (!isSuggestion) { - val isPendingRequest = usernameSearchResult.isPendingRequest - itemView.setBackgroundResource(if (isPendingRequest) R.drawable.selectable_round_corners_white else R.drawable.selectable_round_corners) - } - - ContactRelation.process( - usernameSearchResult.type, - sendContactRequestWorkState, - object : ContactRelation.RelationshipCallback { - - override fun none() { - contactRowBinding.relationState.visibility = View.GONE - } - - override fun inviting() { - contactRowBinding.relationState.displayedChild = 3 - contactRowBinding.relationState.visibility = View.VISIBLE - contactRowBinding.pendingWorkText.setText(R.string.sending_contact_request_short) - (contactRowBinding.ignoreContactRequest.drawable as AnimationDrawable).start() - } - - override fun invited() { - contactRowBinding.relationState.displayedChild = 1 - } - - override fun inviteReceived() { - contactRowBinding.relationState.displayedChild = 2 - contactRowBinding.relationState.visibility = View.VISIBLE - contactRowBinding.acceptContactRequest.isEnabled = networkAvailable - contactRowBinding.acceptContactRequest.setOnClickListener { - contactRequestButtonClickListener?.onAcceptRequest(usernameSearchResult, adapterPosition) - } - contactRowBinding.ignoreContactRequest.setOnClickListener { - contactRequestButtonClickListener?.onIgnoreRequest(usernameSearchResult, adapterPosition) - } - } - - override fun acceptingInvite() { - contactRowBinding.relationState.displayedChild = 3 - contactRowBinding.relationState.visibility = View.VISIBLE - contactRowBinding.pendingWorkText.setText(R.string.accepting_contact_request_short) - (contactRowBinding.pendingWorkIcon.drawable as AnimationDrawable).start() - } - - override fun friends() { - if (useFriendsIcon) { - contactRowBinding.relationState.visibility = View.VISIBLE - contactRowBinding.relationState.displayedChild = 0 - } else { - none() - } - } - }) - } -} \ No newline at end of file +//class ContactSuggestionViewHolder(val binding: DashpayContactSuggestionRowBinding, val isSuggestion: Boolean = false, val useFriendsIcon: Boolean = true) +// : RecyclerView.ViewHolder(binding.root) { +// +// val contactRowBinding = ContactnRowBinding.bind(binding.contactRow) +// +// fun bind( +// usernameSearchResult: UsernameSearchResult, +// sendContactRequestWorkState: Resource?, listener: OnItemClickListener?, +// contactRequestButtonClickListener: OnContactRequestButtonClickListener?, +// networkAvailable: Boolean = true +// ) { +// +// val dashPayProfile = usernameSearchResult.dashPayProfile +// if (dashPayProfile.displayName.isEmpty()) { +// contactRowBinding.displayName.text = dashPayProfile.username +// contactRowBinding.username.text = "" +// } else { +// contactRowBinding.displayName.text = dashPayProfile.displayName +// contactRowBinding.username.text = usernameSearchResult.username +// } +// +// ProfilePictureDisplay.display(contactRowBinding.avatar, dashPayProfile) +// +// itemView.setOnClickListener { +// listener?.onItemClicked(itemView, usernameSearchResult) +// } +// +// if (!isSuggestion) { +// val isPendingRequest = usernameSearchResult.isPendingRequest +// itemView.setBackgroundResource(if (isPendingRequest) R.drawable.selectable_round_corners_white else R.drawable.selectable_round_corners) +// } +// +// ContactRelation.process( +// usernameSearchResult.type, +// sendContactRequestWorkState, +// object : ContactRelation.RelationshipCallback { +// +// override fun none() { +// contactRowBinding.relationState.visibility = View.GONE +// } +// +// override fun inviting() { +// contactRowBinding.relationState.displayedChild = 3 +// contactRowBinding.relationState.visibility = View.VISIBLE +// contactRowBinding.pendingWorkText.setText(R.string.sending_contact_request_short) +// (contactRowBinding.ignoreContactRequest.drawable as AnimationDrawable).start() +// } +// +// override fun invited() { +// contactRowBinding.relationState.displayedChild = 1 +// } +// +// override fun inviteReceived() { +// contactRowBinding.relationState.displayedChild = 2 +// contactRowBinding.relationState.visibility = View.VISIBLE +// contactRowBinding.acceptContactRequest.isEnabled = networkAvailable +// contactRowBinding.acceptContactRequest.setOnClickListener { +// contactRequestButtonClickListener?.onAcceptRequest(usernameSearchResult, adapterPosition) +// } +// contactRowBinding.ignoreContactRequest.setOnClickListener { +// contactRequestButtonClickListener?.onIgnoreRequest(usernameSearchResult, adapterPosition) +// } +// } +// +// override fun acceptingInvite() { +// contactRowBinding.relationState.displayedChild = 3 +// contactRowBinding.relationState.visibility = View.VISIBLE +// contactRowBinding.pendingWorkText.setText(R.string.accepting_contact_request_short) +// (contactRowBinding.pendingWorkIcon.drawable as AnimationDrawable).start() +// } +// +// override fun friends() { +// if (useFriendsIcon) { +// contactRowBinding.relationState.visibility = View.VISIBLE +// contactRowBinding.relationState.displayedChild = 0 +// } else { +// none() +// } +// } +// }) +// } +//} \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/CreateUsernameActivity.kt b/wallet/src/de/schildbach/wallet/ui/CreateUsernameActivity.kt index 004c8ec471..8f8fc5fc2c 100644 --- a/wallet/src/de/schildbach/wallet/ui/CreateUsernameActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/CreateUsernameActivity.kt @@ -33,6 +33,7 @@ import de.schildbach.wallet.ui.username.CreateUsernameArgs import de.schildbach.wallet.ui.username.CreateUsernameFragment import de.schildbach.wallet.ui.username.voting.RequestUserNameViewModel import de.schildbach.wallet_test.R +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.dash.wallet.common.InteractionAwareActivity import org.slf4j.LoggerFactory @@ -88,6 +89,7 @@ class CreateUsernameActivity : InteractionAwareActivity() { } } + @OptIn(ExperimentalCoroutinesApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt index 2a7f50f656..d47bc2be98 100644 --- a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt @@ -53,8 +53,7 @@ import kotlinx.coroutines.launch import org.dash.wallet.common.util.observe @AndroidEntryPoint -class SearchUserActivity : LockScreenActivity(), ContactViewHolder.OnItemClickListener, - ContactViewHolder.OnContactRequestButtonClickListener { +class SearchUserActivity : LockScreenActivity(), OnItemClickListener, OnContactRequestButtonClickListener { companion object { diff --git a/wallet/src/de/schildbach/wallet/ui/UsernameSearchResultsAdapter.kt b/wallet/src/de/schildbach/wallet/ui/UsernameSearchResultsAdapter.kt index 1f3e3bafa1..c8cbc09d84 100644 --- a/wallet/src/de/schildbach/wallet/ui/UsernameSearchResultsAdapter.kt +++ b/wallet/src/de/schildbach/wallet/ui/UsernameSearchResultsAdapter.kt @@ -23,13 +23,11 @@ import androidx.recyclerview.widget.RecyclerView import androidx.work.WorkInfo import de.schildbach.wallet.data.UsernameSearchResult import de.schildbach.wallet.livedata.Resource -import de.schildbach.wallet_test.R -import de.schildbach.wallet_test.databinding.ContactRowBinding import de.schildbach.wallet_test.databinding.DashpayContactRowBinding -class UsernameSearchResultsAdapter(private val onContactRequestButtonClickListener: ContactViewHolder.OnContactRequestButtonClickListener) : RecyclerView.Adapter() { +class UsernameSearchResultsAdapter(private val onContactRequestButtonClickListener: OnContactRequestButtonClickListener) : RecyclerView.Adapter() { - var itemClickListener: ContactViewHolder.OnItemClickListener? = null + var itemClickListener: OnItemClickListener? = null var results: List = arrayListOf() set(value) { field = value diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/BaseProfileViewModel.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/BaseProfileViewModel.kt index 5f215c50ca..eb19d8467f 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/BaseProfileViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/BaseProfileViewModel.kt @@ -47,8 +47,8 @@ open class BaseProfileViewModel( blockchainIdentityDataDao.observeBase() .distinctUntilChanged() .onEach(_blockchainIdentity::postValue) - .filter { it?.userId != null } - .flatMapLatest { dashPayProfileDao.observeByUserId(it?.userId!!) } + .filter { it.userId != null } + .flatMapLatest { dashPayProfileDao.observeByUserId(it.userId!!) } .distinctUntilChanged() .onEach(_dashPayProfile::postValue) .launchIn(viewModelScope) diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/ContactSearchResultsAdapter.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/ContactSearchResultsAdapter.kt index 571fe763dc..025ed96e43 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/ContactSearchResultsAdapter.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/ContactSearchResultsAdapter.kt @@ -29,14 +29,13 @@ import androidx.work.WorkInfo import de.schildbach.wallet.data.UsernameSearchResult import de.schildbach.wallet.data.UsernameSortOrderBy import de.schildbach.wallet.livedata.Resource -import de.schildbach.wallet.ui.ContactSuggestionViewHolder import de.schildbach.wallet.ui.ContactViewHolder +import de.schildbach.wallet.ui.OnContactRequestButtonClickListener +import de.schildbach.wallet.ui.OnItemClickListener import de.schildbach.wallet.util.PlatformUtils import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.ContactHeaderRowBinding import de.schildbach.wallet_test.databinding.ContactRequestHeaderRowBinding -import de.schildbach.wallet_test.databinding.ContactRequestRowBinding -import de.schildbach.wallet_test.databinding.ContactRowBinding import de.schildbach.wallet_test.databinding.ContactsSuggestionsHeaderBinding import de.schildbach.wallet_test.databinding.DashpayContactRowBinding import de.schildbach.wallet_test.databinding.DashpayContactSuggestionRowBinding @@ -64,7 +63,7 @@ class ContactSearchResultsAdapter(private val listener: Listener, setHasStableIds(true) } - var itemClickListener: ContactViewHolder.OnItemClickListener? = null + var itemClickListener: OnItemClickListener? = null var results: ArrayList = arrayListOf() set(value) { field = value @@ -92,7 +91,7 @@ class ContactSearchResultsAdapter(private val listener: Listener, ContactViewHolder(binding, useFriendsIcon = false) } CONTACT_NO_RESULTS -> { - val binding = NoContactsResultsBinding.inflate(inflater) + val binding = NoContactsResultsBinding.inflate(inflater, parent, false) ContactsNoResultsViewHolder(binding) } CONTACTS_SUGGESTIONS_HEADER -> { @@ -100,8 +99,8 @@ class ContactSearchResultsAdapter(private val listener: Listener, ContactsSuggestionsHeaderViewHolder(binding) } CONTACT_SUGGESTION_ROW -> { - val binding = DashpayContactSuggestionRowBinding.inflate(inflater) - ContactSuggestionViewHolder(binding, isSuggestion = true) + val binding = DashpayContactRowBinding.inflate(inflater, parent, false) + ContactViewHolder(binding, isSuggestion = true) } else -> throw IllegalArgumentException("Invalid viewType $viewType") } @@ -148,7 +147,10 @@ class ContactSearchResultsAdapter(private val listener: Listener, CONTACT_HEADER -> (holder as ContactHeaderViewHolder).bind() CONTACT_NO_RESULTS -> (holder as ContactsNoResultsViewHolder).bind() CONTACTS_SUGGESTIONS_HEADER -> (holder as ContactsSuggestionsHeaderViewHolder).bind(query) - CONTACT_SUGGESTION_ROW -> (holder as ContactViewHolder).bind(item.usernameSearchResult!!, null, itemClickListener, listener) + CONTACT_SUGGESTION_ROW -> { + val sendContactRequestWorkState = sendContactRequestWorkStateMap[item.usernameSearchResult!!.dashPayProfile.userId] + (holder as ContactViewHolder).bind(item.usernameSearchResult!!, sendContactRequestWorkState, itemClickListener, listener) + } else -> throw IllegalArgumentException("Invalid viewType ${item.viewType}") } } @@ -236,7 +238,7 @@ class ContactSearchResultsAdapter(private val listener: Listener, } } - interface Listener : ContactViewHolder.OnContactRequestButtonClickListener { + interface Listener : OnContactRequestButtonClickListener { fun onSortOrderChanged(direction: UsernameSortOrderBy) fun onSearchUser() } diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt index 806f53a79b..2a0d1b6f07 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt @@ -70,7 +70,7 @@ enum class ContactsScreenMode { @AndroidEntryPoint class ContactsFragment : Fragment(), ContactSearchResultsAdapter.Listener, - ContactViewHolder.OnItemClickListener { + OnItemClickListener { private val binding by viewBinding(FragmentContactsRootBinding::bind) private val dashPayViewModel by viewModels() diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt index 21cec3e4f7..d1c993eaa4 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt @@ -31,6 +31,7 @@ import org.bitcoinj.evolution.AssetLockTransaction import org.bitcoinj.wallet.Wallet import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension import org.bouncycastle.crypto.params.KeyParameter +import org.dash.wallet.common.Configuration import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.services.analytics.AnalyticsService import org.dash.wallet.common.services.analytics.AnalyticsTimer @@ -130,6 +131,7 @@ class CreateIdentityService : LifecycleService() { } private val walletApplication by lazy { application as WalletApplication } + @Inject lateinit var configuration: Configuration @Inject lateinit var platformRepo: PlatformRepo @Inject lateinit var platformSyncService: PlatformSyncService @Inject lateinit var userAlertDao: UserAlertDao @@ -266,7 +268,7 @@ class CreateIdentityService : LifecycleService() { (blockchainIdentityDataTmp != null && blockchainIdentityDataTmp.restoring) -> { // TODO: handle case when blockchain reset has happened and the cftx was not found yet val cftx = blockchainIdentityDataTmp.findAssetLockTransaction(walletApplication.wallet) - ?: throw IllegalStateException() + ?: throw IllegalStateException("can't find asset lock transaction") restoreIdentity(cftx.identityId.bytes) return @@ -307,6 +309,8 @@ class CreateIdentityService : LifecycleService() { blockchainIdentityData.creationState = CreationState.NONE blockchainIdentityData.creditFundingTxId = null isRetry = true + } else if (blockchainIdentityData.creationState >= CreationState.IDENTITY_REGISTERED) { + isRetry = true } } @@ -348,6 +352,7 @@ class CreateIdentityService : LifecycleService() { platformRepo.updateIdentityCreationState(blockchainIdentityData, CreationState.CREDIT_FUNDING_TX_SENDING) val timerIsLock = AnalyticsTimer(analytics, log, AnalyticsConstants.Process.PROCESS_USERNAME_CREATE_ISLOCK) // check to see if the funding transaction has been sent previously + org.bitcoinj.core.Context.propagate(wallet.context) val sent = blockchainIdentity.assetLockTransaction!!.confidence?.let { it.isSent || it.isIX || it.numBroadcastPeers() > 0 || it.confidenceType == TransactionConfidence.ConfidenceType.BUILDING } ?: false @@ -372,13 +377,11 @@ class CreateIdentityService : LifecycleService() { // // Step 3: Register the identity // - if(isRetry) { - val existingIdentity = platformRepo.getIdentityFromPublicKeyId() - if (existingIdentity != null) { - val encryptionKey = platformRepo.getWalletEncryptionKey() - val firstIdentityKey = platformRepo.getBlockchainIdentityKey(0, encryptionKey)!! - platformRepo.recoverIdentityAsync(blockchainIdentity, firstIdentityKey.pubKey) - } + val existingIdentity = platformRepo.getIdentityFromPublicKeyId() + if (existingIdentity != null) { + //val encryptionKey = platformRepo.getWalletEncryptionKey() + val firstIdentityKey = platformRepo.getBlockchainIdentityKey(0, encryptionKey)!! + platformRepo.recoverIdentityAsync(blockchainIdentity, firstIdentityKey.pubKeyHash) } else { platformRepo.registerIdentityAsync(blockchainIdentity, encryptionKey) } @@ -498,13 +501,11 @@ class CreateIdentityService : LifecycleService() { // Step 3: Register the identity // try { - if(isRetry) { - val existingIdentity = platformRepo.getIdentityFromPublicKeyId() - if (existingIdentity != null) { - val encryptionKey = platformRepo.getWalletEncryptionKey() - val firstIdentityKey = platformRepo.getBlockchainIdentityKey(0, encryptionKey)!! - platformRepo.recoverIdentityAsync(blockchainIdentity, firstIdentityKey.pubKey) - } + val existingIdentity = platformRepo.getIdentityFromPublicKeyId() + if (existingIdentity != null) { + val encryptionKey = platformRepo.getWalletEncryptionKey() + val firstIdentityKey = platformRepo.getBlockchainIdentityKey(0, encryptionKey)!! + platformRepo.recoverIdentityAsync(blockchainIdentity, firstIdentityKey.pubKeyHash) } else { platformRepo.registerIdentityAsync(blockchainIdentity, encryptionKey) } @@ -523,7 +524,7 @@ class CreateIdentityService : LifecycleService() { throw IllegalStateException("Invite has already been used", exception) } - log.error(e.toString()); + log.error(e.toString()) throw e } throw e @@ -540,7 +541,7 @@ class CreateIdentityService : LifecycleService() { SendContactRequestOperation(walletApplication) .create(inviterUserId) .enqueue() - walletApplication.configuration.apply { + configuration.apply { inviter = inviterUserId inviterContactRequestSentInfoShown = false } @@ -551,6 +552,8 @@ class CreateIdentityService : LifecycleService() { } private suspend fun finishRegistration(blockchainIdentity: BlockchainIdentity, encryptionKey: KeyParameter) { + // TODO: let's delay to make sure that the identity is propagated + delay(20000) // This Step is obsolete, verification is handled by the previous block, lets leave it in for now if (blockchainIdentityData.creationState <= CreationState.IDENTITY_REGISTERED) { @@ -581,7 +584,7 @@ class CreateIdentityService : LifecycleService() { // platformRepo.updateBlockchainIdentityData(blockchainIdentityData, blockchainIdentity) } - + delay(30000) // 20 sec delay for platform sync if (blockchainIdentityData.creationState <= CreationState.USERNAME_REGISTERING) { platformRepo.updateIdentityCreationState(blockchainIdentityData, CreationState.USERNAME_REGISTERING) // @@ -610,6 +613,7 @@ class CreateIdentityService : LifecycleService() { addInviteUserAlert(walletApplication.wallet!!) platformRepo.init() + platformSyncService.initSync() timerStep3.logTiming() // aaaand we're done :) @@ -734,6 +738,7 @@ class CreateIdentityService : LifecycleService() { platformSyncService.updateSyncStatus(PreBlockStage.RecoveryComplete) platformRepo.init() + platformSyncService.initSync() } /** diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt index e242dc3bca..eb0c0679b4 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt @@ -37,7 +37,6 @@ import de.schildbach.wallet.service.platform.PlatformSyncService import de.schildbach.wallet.ui.dashpay.utils.DashPayConfig import de.schildbach.wallet.ui.dashpay.work.SendContactRequestOperation import de.schildbach.wallet.ui.username.CreateUsernameArgs -import io.grpc.StatusRuntimeException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.distinctUntilChanged @@ -287,7 +286,7 @@ open class DashPayViewModel @Inject constructor( analytics.logEvent(event, mapOf()) } - protected fun formatExceptionMessage(description: String, e: Exception): String { + private fun formatExceptionMessage(description: String, e: Exception): String { var msg = if (e.localizedMessage != null) { e.localizedMessage } else { @@ -297,9 +296,6 @@ open class DashPayViewModel @Inject constructor( msg = "Unknown error - ${e.javaClass.simpleName}" } log.error("$description: $msg", e) - if (e is StatusRuntimeException) { - log.error("---> ${e.trailers}") - } e.printStackTrace() return msg } diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt index 816a1c105c..c73a115d95 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt @@ -48,6 +48,7 @@ import org.bitcoinj.core.* import org.bitcoinj.crypto.IDeterministicKey import org.bitcoinj.evolution.AssetLockTransaction import org.bitcoinj.quorums.InstantSendLock +import org.bitcoinj.quorums.LLMQParameters import org.bitcoinj.wallet.AuthenticationKeyChain import org.bitcoinj.wallet.DeterministicSeed import org.bitcoinj.wallet.Wallet @@ -69,9 +70,9 @@ import org.dashj.platform.dpp.errors.concensus.basic.identity.InvalidInstantAsse import org.dashj.platform.dpp.identifier.Identifier import org.dashj.platform.dpp.identity.Identity import org.dashj.platform.dpp.toHex +import org.dashj.platform.sdk.callbacks.ContextProvider import org.dashj.platform.sdk.platform.DomainDocument import org.dashj.platform.sdk.platform.Names -import org.dashj.platform.sdk.platform.multicall.MulticallQuery import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.TimeoutException @@ -136,6 +137,8 @@ class PlatformRepo @Inject constructor( } suspend fun init() { + // load the dash-sdk library + System.loadLibrary("sdklib") authenticationGroupExtension = walletApplication.wallet?.getKeyChainExtension(AuthenticationGroupExtension.EXTENSION_ID) as AuthenticationGroupExtension blockchainIdentityDataDao.load()?.let { blockchainIdentity = initBlockchainIdentity(it, walletApplication.wallet!!) @@ -247,7 +250,7 @@ class PlatformRepo @Inject constructor( val nameDocuments = if (!onlyExactUsername) { platform.names.search(text, Names.DEFAULT_PARENT_DOMAIN, retrieveAll = false, limit = limit) } else { - val nameDocument = platform.names.get(text, Names.DEFAULT_PARENT_DOMAIN, MulticallQuery.Companion.CallType.UNTIL_FOUND) + val nameDocument = platform.names.get(text, Names.DEFAULT_PARENT_DOMAIN) if (nameDocument != null) { listOf(nameDocument) } else { @@ -257,7 +260,7 @@ class PlatformRepo @Inject constructor( val userIds = if (onlyExactUsername) { val result = mutableListOf() val exactNameDoc = try { - nameDocuments.first { text == it.data["normalizedLabel"] } + DomainDocument(nameDocuments.first { text == it.data["normalizedLabel"] }) } catch (e: NoSuchElementException) { null } @@ -266,8 +269,8 @@ class PlatformRepo @Inject constructor( } result } else { - nameDocuments.map { getIdentityForName(it) } - } + nameDocuments.map { getIdentityForName(DomainDocument(it)) } + }.toSet().toList() val profileById: Map = if (userIds.isNotEmpty()) { val profileDocuments = platform.profiles.getList(userIds) @@ -278,16 +281,19 @@ class PlatformRepo @Inject constructor( } val toContactDocuments = dashPayContactRequestDao.loadToOthers(userIdString) - ?: arrayListOf() // Get all contact requests where toUserId == userId val fromContactDocuments = dashPayContactRequestDao.loadFromOthers(userIdString) - ?: arrayListOf() val usernameSearchResults = ArrayList() - for (nameDoc in nameDocuments) { - //Remove own user document from result + for (domainDoc in nameDocuments) { + val nameDoc = DomainDocument(domainDoc) + if (nameDoc.dashAliasIdentityId != null) { + continue // skip aliases + } + + //Remove own user document from result val nameDocIdentityId = getIdentityForName(nameDoc) if (nameDocIdentityId == userId) { continue @@ -309,20 +315,20 @@ class PlatformRepo @Inject constructor( } } - val username = nameDoc.data["normalizedLabel"] as String + val username = nameDoc.label val profileDoc = profileById[nameDocIdentityId] val dashPayProfile = if (profileDoc != null) DashPayProfile.fromDocument(profileDoc, username)!! else DashPayProfile(nameDocIdentityId.toString(), username) - usernameSearchResults.add(UsernameSearchResult(nameDoc.data["normalizedLabel"] as String, + usernameSearchResults.add(UsernameSearchResult(username, dashPayProfile, toContact, fromContact)) } // TODO: this is only needed when Proofs don't sort results // This was added in v0.20 - usernameSearchResults.sortBy { it.username } + usernameSearchResults.sortBy { Names.normalizeString(it.username) } return usernameSearchResults } @@ -342,14 +348,14 @@ class PlatformRepo @Inject constructor( val toContactDocuments = dashPayContactRequestDao.loadToOthers(userId) val toContactMap = HashMap() - toContactDocuments!!.forEach { + toContactDocuments.forEach { userIdList.add(it.toUserId) toContactMap[it.toUserId] = it } // Get all contact requests where toUserId == userId, the users who have added me val fromContactDocuments = dashPayContactRequestDao.loadFromOthers(userId) val fromContactMap = HashMap() - fromContactDocuments!!.forEach { + fromContactDocuments.forEach { userIdList.add(it.userId) // It is possible for a contact to send multiple requests that differ by account @@ -373,7 +379,7 @@ class PlatformRepo @Inject constructor( } val usernameSearchResults = ArrayList() - val searchText = text.toLowerCase() + val searchText = text.lowercase() for (profile in profiles) { if (profile.value == null) { @@ -408,11 +414,11 @@ class PlatformRepo @Inject constructor( when (orderBy) { UsernameSortOrderBy.DISPLAY_NAME -> usernameSearchResults.sortBy { if (it.dashPayProfile.displayName.isNotEmpty()) - it.dashPayProfile.displayName.toLowerCase() - else it.dashPayProfile.username.toLowerCase() + it.dashPayProfile.displayName.lowercase() + else it.dashPayProfile.username.lowercase() } UsernameSortOrderBy.USERNAME -> usernameSearchResults.sortBy { - it.dashPayProfile.username.toLowerCase() + it.dashPayProfile.username.lowercase() } UsernameSortOrderBy.DATE_ADDED -> usernameSearchResults.sortByDescending { it.date @@ -438,9 +444,6 @@ class PlatformRepo @Inject constructor( msg = "Unknown error - ${e.javaClass.simpleName}" } log.error("$description: $msg", e) - if (e is StatusRuntimeException) { - log.error("---> ${e.trailers}") - } return msg } @@ -486,53 +489,6 @@ class PlatformRepo @Inject constructor( return this::blockchainIdentity.isInitialized } -/* - @Throws(Exception::class) - suspend fun sendContactRequest(toUserId: String): DashPayContactRequest { - if (walletApplication.wallet!!.isEncrypted) { - // always create a SecurityGuard when it is required - val securityGuard = SecurityGuard() - val password = securityGuard.retrievePassword() - // Don't bother with DeriveKeyTask here, just call deriveKey - val encryptionKey = walletApplication.wallet!!.keyCrypter!!.deriveKey(password) - return sendContactRequest(toUserId, encryptionKey) - } - throw IllegalStateException("sendContactRequest doesn't support non-encrypted wallets") - } - - @Throws(Exception::class) - suspend fun sendContactRequest(toUserId: String, encryptionKey: KeyParameter): DashPayContactRequest { - val potentialContactIdentity = platform.identities.get(toUserId) - log.info("potential contact identity: $potentialContactIdentity") - - //Create Contact Request - val timer = AnalyticsTimer(analytics, log, AnalyticsConstants.Process.PROCESS_CONTACT_REQUEST_SEND) - val cr = contactRequests.create(blockchainIdentity, potentialContactIdentity!!, encryptionKey) - timer.logTiming() - log.info("contact request sent") - - // add our receiving from this contact keychain if it doesn't exist - val contact = EvolutionContact(blockchainIdentity.uniqueIdString, toUserId) - - if (!walletApplication.wallet!!.hasReceivingKeyChain(contact)) { - Context.propagate(walletApplication.wallet!!.context) - blockchainIdentity.addPaymentKeyChainFromContact(potentialContactIdentity, cr, encryptionKey) - - // update bloom filters now on main thread - mainHandler.post { - updateBloomFilters() - } - } - - log.info("contact request: $cr") - val dashPayContactRequest = DashPayContactRequest.fromDocument(cr!!) - updateDashPayContactRequest(dashPayContactRequest) //update the database since the cr was accepted - updateDashPayProfile(toUserId) // update the profile - fireContactsUpdatedListeners() // trigger listeners - return dashPayContactRequest - } -*/ - // // Step 1 is to upgrade the wallet to support authentication keys // @@ -570,7 +526,7 @@ class PlatformRepo @Inject constructor( //TODO: remove when iOS uses big endian if (cftxData == null) cftxData = platform.client.getTransaction(Sha256Hash.wrap(invite.cftx).reversedBytes.toHex()) - val assetLockTx = AssetLockTransaction(platform.params, cftxData!!.transaction) + val assetLockTx = AssetLockTransaction(platform.params, cftxData!!) val privateKey = DumpedPrivateKey.fromBase58(platform.params, invite.privateKey).key assetLockTx.addAssetLockPublicKey(privateKey) @@ -591,7 +547,7 @@ class PlatformRepo @Inject constructor( // suspend fun registerIdentityAsync(blockchainIdentity: BlockchainIdentity, keyParameter: KeyParameter?) { withContext(Dispatchers.IO) { - Context.getOrCreate(walletApplication.wallet!!.params) + Context.propagate(walletApplication.wallet!!.context) for (i in 0 until 3) { try { val timer = AnalyticsTimer(analytics, log, AnalyticsConstants.Process.PROCESS_USERNAME_IDENTITY_CREATE) @@ -607,17 +563,6 @@ class PlatformRepo @Inject constructor( } } - // - // Step 3: Verify that the identity is registered - // - @Deprecated("watch* functions should no longer be used") - suspend fun verifyIdentityRegisteredAsync(blockchainIdentity: BlockchainIdentity) { - withContext(Dispatchers.IO) { - blockchainIdentity.watchIdentity(100, 1000, RetryDelayType.SLOW20) - ?: throw TimeoutException("the identity was not found to be registered in the allotted amount of time") - } - } - // // Step 3: Find the identity in the case of recovery // @@ -629,6 +574,7 @@ class PlatformRepo @Inject constructor( suspend fun recoverIdentityAsync(blockchainIdentity: BlockchainIdentity, publicKeyHash: ByteArray) { withContext(Dispatchers.IO) { + blockchainIdentity.registrationStatus = BlockchainIdentity.RegistrationStatus.UNKNOWN blockchainIdentity.recoverIdentity(publicKeyHash) } } @@ -667,7 +613,7 @@ class PlatformRepo @Inject constructor( withContext(Dispatchers.IO) { val names = blockchainIdentity.preorderedUsernames() val timer = AnalyticsTimer(analytics, log, AnalyticsConstants.Process.PROCESS_USERNAME_DOMAIN_CREATE) - blockchainIdentity.registerUsernameDomainsForUsernames(names, keyParameter) + blockchainIdentity.registerUsernameDomainsForUsernames(names, keyParameter, false) timer.logTiming() } } @@ -686,6 +632,7 @@ class PlatformRepo @Inject constructor( } //Step 6: Create DashPay Profile + @Deprecated("Don't need this function when creating an identity") suspend fun createDashPayProfile(blockchainIdentity: BlockchainIdentity, keyParameter: KeyParameter) { withContext(Dispatchers.IO) { val username = blockchainIdentity.currentUsername!! @@ -725,9 +672,11 @@ class PlatformRepo @Inject constructor( } blockchainIdentity } + // TODO: needs to check against Platform to see if values exist. Check after + // Syncing complete return blockchainIdentity.apply { currentUsername = blockchainIdentityData.username - registrationStatus = blockchainIdentityData.registrationStatus!! + registrationStatus = blockchainIdentityData.registrationStatus ?: BlockchainIdentity.RegistrationStatus.NOT_REGISTERED val usernameStatus = HashMap() // usernameStatus, usernameSalts are not set if preorder hasn't started if (blockchainIdentityData.creationState >= BlockchainIdentityData.CreationState.PREORDER_REGISTERING) { @@ -739,16 +688,12 @@ class PlatformRepo @Inject constructor( usernameStatus[BLOCKCHAIN_USERNAME_STATUS] = blockchainIdentityData.usernameStatus!! } usernameStatus[BLOCKCHAIN_USERNAME_UNIQUE] = true - usernameStatuses[currentUsername!!] = usernameStatus + currentUsername ?.let { + usernameStatuses[it] = usernameStatus + } } creditBalance = blockchainIdentityData.creditBalance ?: Coin.ZERO - //activeKeyCount = blockchainIdentityData.activeKeyCount ?: 0 - //totalKeyCount = blockchainIdentityData.totalKeyCount ?: 0 - //keysCreated = blockchainIdentityData.keysCreated ?: 0 - //currentMainKeyIndex = blockchainIdentityData.currentMainKeyIndex ?: 0 - //currentMainKeyType = blockchainIdentityData.currentMainKeyType - // ?: IdentityPublicKey.Type.ECDSA_SECP256K1 } } @@ -829,14 +774,14 @@ class PlatformRepo @Inject constructor( val nameDocuments = platform.names.getByOwnerId(userId) if (nameDocuments.isNotEmpty()) { - val username = nameDocuments[0].data["normalizedLabel"] as String + val username = DomainDocument(nameDocuments[0]).label val profile = DashPayProfile.fromDocument(profileDocument, username) dashPayProfileDao.insert(profile!!) return true } return false - } catch (e: StatusRuntimeException) { + } catch (e: Exception) { formatExceptionMessage("update profile failure", e) return false } @@ -911,11 +856,9 @@ class PlatformRepo @Inject constructor( * obtains the identity associated with the username (domain document) * @throws NullPointerException if neither the unique id or alias exists */ - fun getIdentityForName(nameDocument: Document): Identifier { - val domainDocument = DomainDocument(nameDocument) - + fun getIdentityForName(nameDocument: DomainDocument): Identifier { // look at the unique identity first, followed by the alias - return domainDocument.dashUniqueIdentityId ?: domainDocument.dashAliasIdentityId!! + return nameDocument.dashUniqueIdentityId ?: nameDocument.dashAliasIdentityId!! } suspend fun getLocalUserProfile(): DashPayProfile? { @@ -979,6 +922,34 @@ class PlatformRepo @Inject constructor( } + val contextProvider = object : ContextProvider() { + override fun getQuorumPublicKey( + quorumType: Int, + quorumHashBytes: ByteArray?, + coreChainLockedHeight: Int + ): ByteArray? { + val quorumHash = Sha256Hash.wrap(quorumHashBytes) + var quorumPublicKey: ByteArray? = null + log.info("searching for quorum: $quorumType, $quorumHash, $coreChainLockedHeight") + Context.propagate(walletApplication.wallet!!.context) + Context.get().masternodeListManager.getQuorumListAtTip( + LLMQParameters.LLMQType.fromValue( + quorumType + ) + ).forEachQuorum(true) { + if (it.llmqType.value == quorumType && it.quorumHash == quorumHash) { + quorumPublicKey = it.quorumPublicKey.serialize(false) + } + } + log.info("searching for quorum: result: ${quorumPublicKey?.toHex()}") + return quorumPublicKey + } + + override fun getDataContract(identifier: org.dashj.platform.sdk.Identifier?): ByteArray { + TODO("Not yet implemented") + } + } + fun getIdentityFromPublicKeyId(): Identity? { val encryptionKey = getWalletEncryptionKey() val firstIdentityKey = getBlockchainIdentityKey(0, encryptionKey) ?: return null @@ -1121,7 +1092,7 @@ class PlatformRepo @Inject constructor( platform.client.getTransaction(Sha256Hash.wrap(invite.cftx).reversedBytes.toHex()) } if (tx != null) { - val cfTx = AssetLockTransaction(Constants.NETWORK_PARAMETERS, tx.transaction) + val cfTx = AssetLockTransaction(Constants.NETWORK_PARAMETERS, tx) val identity = platform.identities.get(cfTx.identityId.toStringBase58()) if (identity == null) { // determine if the invite has enough credits diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/work/BaseWorker.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/work/BaseWorker.kt index 8329a39f74..15ea4092c2 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/work/BaseWorker.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/work/BaseWorker.kt @@ -5,7 +5,6 @@ import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.WorkerParameters import androidx.work.workDataOf -import io.grpc.StatusRuntimeException import kotlinx.coroutines.delay import org.slf4j.LoggerFactory @@ -57,9 +56,6 @@ abstract class BaseWorker(context: Context, parameters: WorkerParameters) msg = "Unknown error - ${e.javaClass.simpleName}" } log.error("$description: $msg", e) - if (e is StatusRuntimeException) { - log.error("---> ${e.trailers}") - } return msg } } \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/work/SendInviteOperation.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/work/SendInviteOperation.kt index c064e97c33..0b2c82a542 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/work/SendInviteOperation.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/work/SendInviteOperation.kt @@ -144,9 +144,11 @@ class SendInviteOperation(val application: Application) { val password = SecurityGuard().retrievePassword() val sendInviteWorker = OneTimeWorkRequestBuilder() - .setInputData(workDataOf( - SendInviteWorker.KEY_PASSWORD to password, - SendInviteWorker.KEY_INVITE_ID to id)) + .setInputData( + workDataOf( + SendInviteWorker.KEY_PASSWORD to password + ) + ) .addTag("invite:$id") .build() diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/work/SendInviteWorker.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/work/SendInviteWorker.kt index 25cfd21252..df9a40d138 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/work/SendInviteWorker.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/work/SendInviteWorker.kt @@ -31,7 +31,6 @@ import de.schildbach.wallet.database.entity.DashPayProfile import de.schildbach.wallet.data.InvitationLinkData import de.schildbach.wallet.ui.dashpay.PlatformRepo import de.schildbach.wallet_test.R -import org.bitcoinj.core.Address import org.bitcoinj.crypto.KeyCrypterException import org.bitcoinj.evolution.AssetLockTransaction import org.bouncycastle.crypto.params.KeyParameter @@ -60,7 +59,6 @@ class SendInviteWorker @AssistedInject constructor( const val KEY_PASSWORD = "SendInviteWorker.PASSWORD" const val KEY_TX_ID = "SendInviteWorker.KEY_TX_ID" const val KEY_USER_ID = "SendInviteWorker.KEY_USER_ID" - const val KEY_INVITE_ID = "SendInviteWorker.KEY_INVITE_ID" const val KEY_DYNAMIC_LINK = "SendInviteWorker.KEY_DYNAMIC_LINK" const val KEY_SHORT_DYNAMIC_LINK = "SendInviteWorker.KEY_SHORT_DYNAMIC_LINK" } @@ -70,8 +68,6 @@ class SendInviteWorker @AssistedInject constructor( get() = data.getByteArray(KEY_TX_ID)!! val userId get() = data.getString(KEY_USER_ID)!! - val inviteId - get() = data.getString(KEY_INVITE_ID)!! val dynamicLink get() = data.getString(KEY_DYNAMIC_LINK)!! val shortDynamicLink @@ -107,7 +103,6 @@ class SendInviteWorker @AssistedInject constructor( Result.success(workDataOf( KEY_TX_ID to cftx.txId.bytes, KEY_USER_ID to cftx.identityId.toStringBase58(), - KEY_INVITE_ID to Address.fromPubKeyHash(wallet.params, cftx.identityId.bytes).toBase58(), KEY_DYNAMIC_LINK to dynamicLink.uri.toString(), KEY_SHORT_DYNAMIC_LINK to shortDynamicLink.shortLink.toString() )) diff --git a/wallet/src/de/schildbach/wallet/ui/invite/InviteFriendFragment.kt b/wallet/src/de/schildbach/wallet/ui/invite/InviteFriendFragment.kt index 5ea3840cf8..f7ebbb268c 100644 --- a/wallet/src/de/schildbach/wallet/ui/invite/InviteFriendFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/invite/InviteFriendFragment.kt @@ -22,14 +22,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint import de.schildbach.wallet.Constants import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.livedata.Status -import de.schildbach.wallet.service.CoinJoinMode import de.schildbach.wallet.ui.dashpay.PlatformPaymentConfirmDialog import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.FragmentInviteFriendBinding @@ -79,17 +76,9 @@ class InviteFriendFragment() : walletApplication = requireActivity().application as WalletApplication binding.createInvitationButton.setOnClickListener { viewModel.logEvent(AnalyticsConstants.Invites.INVITE_FRIEND) - safeNavigate(InviteFriendFragmentDirections.inviteFriendFragmentToUsernamePrivacy()) - } - - findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData("mode")?.observe( - viewLifecycleOwner, - ) { result -> - // Do something with the result. - result?.let { - showConfirmationDialog() - } + showConfirmationDialog() } + initViewModel() } @@ -106,39 +95,36 @@ class InviteFriendFragment() : private fun confirmButtonClick(startedByHistory: Boolean) { showProgress() viewModel.sendInviteTransaction() - viewModel.sendInviteStatusLiveData.observe( - viewLifecycleOwner, - Observer { - if (it.status != Status.LOADING) { - dismissProgress() - } - when (it.status) { - Status.SUCCESS -> { - if (it.data != null) { - safeNavigate( - InviteFriendFragmentDirections - .inviteFriendFragmentToInviteCreatedFragment(identityId = it.data.userId, startedFromHistory = startedByHistory), - ) - } - } - Status.LOADING -> { - // sending has begun - } - else -> { - // there was an error sending - val errorDialog = FancyAlertDialog.newInstance( - R.string.invitation_creating_error_title, - R.string.invitation_creating_error_message, - R.drawable.ic_error_creating_invitation, - R.string.okay, - 0, + viewModel.sendInviteStatusLiveData.observe(viewLifecycleOwner) { + if (it.status != Status.LOADING) { + dismissProgress() + } + when (it.status) { + Status.SUCCESS -> { + if (it.data != null) { + safeNavigate( + InviteFriendFragmentDirections + .inviteFriendFragmentToInviteCreatedFragment(identityId = it.data.userId, startedFromHistory = startedByHistory), ) - errorDialog.show(childFragmentManager, null) - viewModel.logEvent(AnalyticsConstants.Invites.ERROR_CREATE) } } - }, - ) + Status.LOADING -> { + // sending has begun + } + else -> { + // there was an error sending + val errorDialog = FancyAlertDialog.newInstance( + R.string.invitation_creating_error_title, + R.string.invitation_creating_error_message, + R.drawable.ic_error_creating_invitation, + R.string.okay, + 0, + ) + errorDialog.show(childFragmentManager, null) + viewModel.logEvent(AnalyticsConstants.Invites.ERROR_CREATE) + } + } + } } private fun showConfirmationDialog() { diff --git a/wallet/src/de/schildbach/wallet/ui/invite/OnboardFromInviteActivity.kt b/wallet/src/de/schildbach/wallet/ui/invite/OnboardFromInviteActivity.kt index e155a68fc0..c6242f8d96 100644 --- a/wallet/src/de/schildbach/wallet/ui/invite/OnboardFromInviteActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/invite/OnboardFromInviteActivity.kt @@ -109,7 +109,7 @@ class OnboardFromInviteActivity : AppCompatActivity() { finish() } } - mode = Mode.values()[intent.getIntExtra(EXTRA_MODE, 0)] + mode = Mode.entries.toTypedArray()[intent.getIntExtra(EXTRA_MODE, 0)] OnboardingState.add() } @@ -135,6 +135,7 @@ class OnboardFromInviteActivity : AppCompatActivity() { overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) } + @Deprecated("Deprecated in Java") override fun onBackPressed() { if (mode == Mode.STEP_3) { startActivity(goNextIntent) diff --git a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt index 36d8a27e82..6029b76de3 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt @@ -25,7 +25,6 @@ import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData -import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import de.schildbach.wallet.Constants @@ -38,9 +37,7 @@ import de.schildbach.wallet.database.dao.UserAlertDao import de.schildbach.wallet.database.entity.BlockchainIdentityConfig import de.schildbach.wallet.database.entity.BlockchainIdentityData import de.schildbach.wallet.database.entity.DashPayProfile -import de.schildbach.wallet.livedata.Resource import de.schildbach.wallet.livedata.SeriousErrorLiveData -import de.schildbach.wallet.livedata.Status import de.schildbach.wallet.security.BiometricHelper import de.schildbach.wallet.service.CoinJoinMode import de.schildbach.wallet.service.CoinJoinService @@ -76,6 +73,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.bitcoinj.core.Coin +import org.bitcoinj.core.PeerGroup import org.bitcoinj.core.Sha256Hash import org.bitcoinj.core.Transaction import org.bitcoinj.utils.MonetaryFormat @@ -144,6 +142,7 @@ class MainViewModel @Inject constructor( } private val workerJob = SupervisorJob() + @VisibleForTesting val viewModelWorkerScope = CoroutineScope(Dispatchers.IO + workerJob) @@ -239,22 +238,11 @@ class MainViewModel @Inject constructor( get() = decimalFormat.format((walletData.wallet as WalletEx).coinJoinBalance.toBigDecimal()) // DashPay + private val isPlatformAvailable = MutableStateFlow(false) - private val isPlatformAvailableData = liveData(Dispatchers.IO) { - val status = if (Constants.SUPPORTS_PLATFORM) { - platformService.isPlatformAvailable() - } else { - Resource.success(false) - } - if (status.status == Status.SUCCESS && status.data != null) { - emit(status.data) - } else { - emit(false) - } - } val isAbleToCreateIdentityLiveData = MediatorLiveData().apply { - addSource(isPlatformAvailableData) { + addSource(isPlatformAvailable.asLiveData()) { value = combineLatestData() } addSource(_isBlockchainSynced) { @@ -355,6 +343,19 @@ class MainViewModel @Inject constructor( } } .launchIn(viewModelScope) + + blockchainStateProvider.observeSyncStage() + .distinctUntilChanged() + .onEach { syncStage -> + if (syncStage == PeerGroup.SyncStage.PREBLOCKS || syncStage == PeerGroup.SyncStage.BLOCKS && !isPlatformAvailable.value) { + isPlatformAvailable.value = if (Constants.SUPPORTS_PLATFORM) { + platformService.isPlatformAvailable() + } else { + false + } + } + } + .launchIn(viewModelScope) } fun logEvent(event: String) { @@ -669,7 +670,7 @@ class MainViewModel @Inject constructor( suspend fun dismissUsernameCreatedCardIfDone(): Boolean { val data = blockchainIdentityDataDao.loadBase() - if (data?.creationState == BlockchainIdentityData.CreationState.DONE) { + if (data.creationState == BlockchainIdentityData.CreationState.DONE) { platformRepo.doneAndDismiss() return true } @@ -703,7 +704,7 @@ class MainViewModel @Inject constructor( return if (Constants.DASHPAY_DISABLED) { false } else { - val isPlatformAvailable = isPlatformAvailableData.value ?: false + val isPlatformAvailable = isPlatformAvailable.value val isSynced = _isBlockchainSynced.value ?: false val noIdentityCreatedOrInProgress = (blockchainIdentity.value == null) || blockchainIdentity.value!!.creationState == BlockchainIdentityData.CreationState.NONE diff --git a/wallet/src/de/schildbach/wallet/ui/username/CreateUsernameFragment.kt b/wallet/src/de/schildbach/wallet/ui/username/CreateUsernameFragment.kt index 27b06a3f05..0988b36dd3 100644 --- a/wallet/src/de/schildbach/wallet/ui/username/CreateUsernameFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/username/CreateUsernameFragment.kt @@ -68,7 +68,7 @@ class CreateUsernameFragment : Fragment(R.layout.fragment_create_username), Text private val binding by viewBinding(FragmentCreateUsernameBinding::bind) private val dashPayViewModel: DashPayViewModel by activityViewModels() - val confirmTransactionSharedViewModel: PlatformPaymentConfirmDialog.SharedViewModel by activityViewModels() + private val confirmTransactionSharedViewModel: PlatformPaymentConfirmDialog.SharedViewModel by activityViewModels() private lateinit var walletApplication: WalletApplication private var reuseTransaction: Boolean = false @@ -106,6 +106,10 @@ class CreateUsernameFragment : Fragment(R.layout.fragment_create_username), Text walletApplication = requireActivity().application as WalletApplication createUsernameArgs = arguments?.getParcelable(CREATE_USER_NAME_ARGS) + // why are the args not passed via the nav graph? + // TODO: fix the passing of arguments or just use the dashPayViewModel + if (createUsernameArgs == null) + createUsernameArgs = dashPayViewModel.createUsernameArgs when (createUsernameArgs?.actions) { CreateUsernameActions.DISPLAY_COMPLETE -> { this.completeUsername = createUsernameArgs?.userName!!