From e46c5c55aabd8b1b1cac36c40e491e367365e0b5 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Wed, 10 Jul 2024 15:28:47 -0700 Subject: [PATCH 1/8] refactor: remove extra code and fix context issues with callbacks --- .../service/platform/PlatformService.kt | 9 +++--- .../wallet/ui/dashpay/PlatformRepo.kt | 28 ------------------- 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/service/platform/PlatformService.kt b/wallet/src/de/schildbach/wallet/service/platform/PlatformService.kt index 03de8fe20..6be91db87 100644 --- a/wallet/src/de/schildbach/wallet/service/platform/PlatformService.kt +++ b/wallet/src/de/schildbach/wallet/service/platform/PlatformService.kt @@ -17,7 +17,6 @@ package de.schildbach.wallet.service.platform import de.schildbach.wallet.Constants -import de.schildbach.wallet.livedata.Resource import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -62,7 +61,7 @@ interface PlatformService { } class PlatformServiceImplementation @Inject constructor( - walletDataProvider: WalletDataProvider + val walletDataProvider: WalletDataProvider ) : PlatformService { override val platform = Platform(Constants.NETWORK_PARAMETERS) override val profiles = Profiles(platform) @@ -73,7 +72,7 @@ class PlatformServiceImplementation @Inject constructor( override val names: Names = platform.names override val client: DapiClient = platform.client override val params: NetworkParameters = platform.params - + private lateinit var masternodeListManager: SimplifiedMasternodeListManager companion object { private val log = LoggerFactory.getLogger(PlatformServiceImplementation::class.java) } @@ -88,7 +87,7 @@ class PlatformServiceImplementation @Inject constructor( var quorumPublicKey: ByteArray? = null log.info("searching for quorum: $quorumType, $quorumHash, $coreChainLockedHeight") Context.propagate(walletDataProvider.wallet!!.context) - Context.get().masternodeListManager.getQuorumListAtTip( + masternodeListManager.getQuorumListAtTip( LLMQParameters.LLMQType.fromValue( quorumType ) @@ -136,11 +135,11 @@ class PlatformServiceImplementation @Inject constructor( } return@withContext success >= 2 - //return@withContext Resource.success(true) } } override fun setMasternodeListManager(masternodeListManager: SimplifiedMasternodeListManager) { + this.masternodeListManager = masternodeListManager platform.setMasternodeListManager(masternodeListManager) } } diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt index c73a115d9..3253dd63e 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt @@ -922,34 +922,6 @@ 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 From 750568f72f4e9b89919e4c94d70f5e349ad6facc Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Wed, 31 Jul 2024 14:18:03 -0700 Subject: [PATCH 2/8] feat: add topup ui warning --- .../ui/enter_amount/EnterAmountFragment.kt | 4 + .../res/drawable/ic_warning_yellow_circle.xml | 12 ++ .../main/res/layout/fragment_enter_amount.xml | 17 ++ wallet/res/navigation/nav_send.xml | 11 + wallet/res/values/strings-extra.xml | 10 + .../src/de/schildbach/wallet/Constants.java | 2 +- .../wallet/data/CreditBalanceInfo.kt | 25 +++ .../wallet/payments/SendCoinsTaskRunner.kt | 1 + .../service/platform/PlatformSyncService.kt | 1 + .../wallet/ui/EditProfileActivity.kt | 43 +++- .../ui/dashpay/CreateIdentityService.kt | 2 +- .../wallet/ui/dashpay/EditProfileViewModel.kt | 10 +- .../wallet/ui/dashpay/PlatformRepo.kt | 14 +- .../wallet/ui/send/BuyCreditsFragment.kt | 192 ++++++++++++++++++ .../wallet/ui/send/SendCoinsActivity.kt | 26 ++- .../wallet/ui/send/SendCoinsFragment.kt | 31 +-- 16 files changed, 377 insertions(+), 24 deletions(-) create mode 100644 common/src/main/res/drawable/ic_warning_yellow_circle.xml create mode 100644 wallet/src/de/schildbach/wallet/data/CreditBalanceInfo.kt create mode 100644 wallet/src/de/schildbach/wallet/ui/send/BuyCreditsFragment.kt diff --git a/common/src/main/java/org/dash/wallet/common/ui/enter_amount/EnterAmountFragment.kt b/common/src/main/java/org/dash/wallet/common/ui/enter_amount/EnterAmountFragment.kt index a7e1c7df2..5fb6129d8 100644 --- a/common/src/main/java/org/dash/wallet/common/ui/enter_amount/EnterAmountFragment.kt +++ b/common/src/main/java/org/dash/wallet/common/ui/enter_amount/EnterAmountFragment.kt @@ -294,4 +294,8 @@ class EnterAmountFragment : Fragment(R.layout.fragment_enter_amount) { binding.networkStatusStub.isVisible = !hasInternet } } + + fun setMessage(message: String) { + binding.messageText.text = message + } } diff --git a/common/src/main/res/drawable/ic_warning_yellow_circle.xml b/common/src/main/res/drawable/ic_warning_yellow_circle.xml new file mode 100644 index 000000000..206233a27 --- /dev/null +++ b/common/src/main/res/drawable/ic_warning_yellow_circle.xml @@ -0,0 +1,12 @@ + + + + diff --git a/common/src/main/res/layout/fragment_enter_amount.xml b/common/src/main/res/layout/fragment_enter_amount.xml index 9914fb8bd..6c82a852a 100644 --- a/common/src/main/res/layout/fragment_enter_amount.xml +++ b/common/src/main/res/layout/fragment_enter_amount.xml @@ -78,6 +78,23 @@ tools:visibility="visible" tools:text="Insufficient funds" /> + + + + + + + + Prices weren\'t retrieved. Fiat values may be incorrect. Prices are at least 30 minutes old. Fiat values may be incorrect. Prices have fluctuated more than 50% since the last update. + + + Your credit balance is low + Your credit balance is fully depleted + "You can continue to use DashPay for payments but you cannot update your profile or add more contacts until you top up your credit balance + Top-up your credits to continue making changes to your profile and adding contacts + Maybe later + Buy Credits + You don’t have enough funds to buy credits + %s - %d contacts / %d profile updates \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/Constants.java b/wallet/src/de/schildbach/wallet/Constants.java index f4a6f80b4..35737d2dc 100644 --- a/wallet/src/de/schildbach/wallet/Constants.java +++ b/wallet/src/de/schildbach/wallet/Constants.java @@ -287,7 +287,7 @@ public final static class Files { public static final boolean SUPPORT_BOTH_BACKUP_WARNINGS = false; // 1,500,000,000 credits - public static final Coin DASH_PAY_FEE = Coin.parseCoin("0.15"); + public static final Coin DASH_PAY_FEE = Coin.parseCoin("0.45"); // 150,000,000 public static final Coin DASH_PAY_INVITE_MIN = DASH_PAY_FEE.div(10); diff --git a/wallet/src/de/schildbach/wallet/data/CreditBalanceInfo.kt b/wallet/src/de/schildbach/wallet/data/CreditBalanceInfo.kt new file mode 100644 index 000000000..bde160117 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/data/CreditBalanceInfo.kt @@ -0,0 +1,25 @@ +package de.schildbach.wallet.data + +import org.bitcoinj.core.Coin + +/* + * Tests in July 2024: Contact Requests cost about 80,000,000 - 90,000,000 + * Profile Updates cost 10,000,000 - 100,000,000 + */ +data class CreditBalanceInfo( + val balance: Long +) { + companion object { + const val CREDITS_PER_DUFF = 1_000 + const val MAX_OPERATION_COST = 100_000_000.toLong() + val MAX_OPERATION_COST_COIN = MAX_OPERATION_COST / CREDITS_PER_DUFF + const val LOW_BALANCE = MAX_OPERATION_COST * 10 + } + fun isBalanceEnough(): Boolean { + return balance >= MAX_OPERATION_COST + } + + fun isBalanceWarning(): Boolean { + return balance <= LOW_BALANCE + } +} diff --git a/wallet/src/de/schildbach/wallet/payments/SendCoinsTaskRunner.kt b/wallet/src/de/schildbach/wallet/payments/SendCoinsTaskRunner.kt index f5bc4191d..894e56ca7 100644 --- a/wallet/src/de/schildbach/wallet/payments/SendCoinsTaskRunner.kt +++ b/wallet/src/de/schildbach/wallet/payments/SendCoinsTaskRunner.kt @@ -268,6 +268,7 @@ class SendCoinsTaskRunner @Inject constructor( return sendRequest } + @VisibleForTesting fun createSendRequest( address: Address, diff --git a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt index e81566a67..2e4479d80 100644 --- a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt +++ b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt @@ -380,6 +380,7 @@ class PlatformSynchronizationService @Inject constructor( val contact = EvolutionContact(userId, contactRequest.toUserId.toString()) try { if (!platformRepo.walletApplication.wallet!!.hasReceivingKeyChain(contact)) { + Context.propagate(walletApplication.wallet!!.context) log.info("adding accepted/send request to wallet: ${contactRequest.toUserId}") val contactIdentity = platform.identities.get(contactRequest.toUserId) var myEncryptionKey = encryptionKey diff --git a/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt b/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt index 7c2eaf2ff..3c4280ade 100644 --- a/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt @@ -43,6 +43,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.net.toUri import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.amulyakhare.textdrawable.TextDrawable import com.bumptech.glide.Glide import com.bumptech.glide.signature.ObjectKey @@ -54,15 +55,23 @@ import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAuthIO import com.google.api.services.drive.Drive import dagger.hilt.android.AndroidEntryPoint import de.schildbach.wallet.Constants +import de.schildbach.wallet.data.PaymentIntent import de.schildbach.wallet.database.entity.DashPayProfile import de.schildbach.wallet.livedata.Status import de.schildbach.wallet.ui.dashpay.* import de.schildbach.wallet.ui.dashpay.utils.GoogleDriveService import de.schildbach.wallet.ui.dashpay.utils.display import de.schildbach.wallet.ui.dashpay.work.UpdateProfileError +import de.schildbach.wallet.ui.send.SendCoinsActivity import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.ActivityEditProfileBinding +import kotlinx.coroutines.launch +import org.bitcoinj.core.Coin import org.bitcoinj.core.Sha256Hash +import org.bitcoinj.script.ScriptBuilder +import org.bitcoinj.wallet.AuthenticationKeyChain +import org.bitcoinj.wallet.AuthenticationKeyChainGroup +import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension import org.dash.wallet.common.ui.avatar.ProfilePictureDisplay import org.dash.wallet.common.ui.avatar.ProfilePictureHelper import org.dash.wallet.common.ui.avatar.ProfilePictureTransformation @@ -172,7 +181,7 @@ class EditProfileActivity : LockScreenActivity() { }) binding.save.setOnClickListener { - save() + saveButton() } binding.profileEditIcon.setOnClickListener { @@ -413,7 +422,37 @@ class EditProfileActivity : LockScreenActivity() { binding.save.isEnabled = !(binding.displayName.text.length > Constants.DISPLAY_NAME_MAX_LENGTH || binding.aboutMe.text.length > Constants.ABOUT_ME_MAX_LENGTH) } - fun save() { + private fun saveButton() { + lifecycleScope.launch { + val enough = editProfileViewModel.hasEnoughCredits() + // TODO: before merging remove this + val shouldWarn = true // enough.isBalanceWarning() + val isEmpty = enough.isBalanceWarning() + + if (shouldWarn || isEmpty) { + val answer = AdaptiveDialog.create( + R.drawable.ic_warning_yellow_circle, + if (isEmpty) getString(R.string.credit_balance_empty_warning_title) else getString(R.string.credit_balance_low_warning_title), + if (isEmpty) getString(R.string.credit_balance_empty_warning_message) else getString(R.string.credit_balance_low_warning_message), + getString(R.string.credit_balance_button_maybe_later), + getString(R.string.credit_balance_button_buy) + ).showAsync(this@EditProfileActivity) + + if (answer == true) { + val authenticationGroupExtension = walletData.wallet!!.getKeyChainExtension(AuthenticationGroupExtension.EXTENSION_ID) as AuthenticationGroupExtension + val pubKeyHash = authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.BLOCKCHAIN_IDENTITY_TOPUP).pubKeyHash + SendCoinsActivity.startBuyCredits(this@EditProfileActivity, pubKeyHash) + } else { + if (shouldWarn) + save() + } + } else { + save() + } + } + } + + private fun save() { showSaveReminderDialog = false val displayName = binding.displayName.text.toString().trim() val publicMessage = binding.aboutMe.text.toString().trim() diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt index d1c993eaa..9d280e6d4 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt @@ -265,7 +265,7 @@ class CreateIdentityService : LifecycleService() { val blockchainIdentityDataTmp = platformRepo.loadBlockchainIdentityData() when { - (blockchainIdentityDataTmp != null && blockchainIdentityDataTmp.restoring) -> { + (blockchainIdentityDataTmp != null && blockchainIdentityDataTmp.restoring && blockchainIdentityDataTmp.creationStateErrorMessage == null) -> { // TODO: handle case when blockchain reset has happened and the cftx was not found yet val cftx = blockchainIdentityDataTmp.findAssetLockTransaction(walletApplication.wallet) ?: throw IllegalStateException("can't find asset lock transaction") diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/EditProfileViewModel.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/EditProfileViewModel.kt index 8a503f30a..2f424d1a1 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/EditProfileViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/EditProfileViewModel.kt @@ -31,6 +31,7 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import dagger.hilt.android.lifecycle.HiltViewModel import de.schildbach.wallet.Constants import de.schildbach.wallet.WalletApplication +import de.schildbach.wallet.data.CreditBalanceInfo import de.schildbach.wallet.data.ImgurUploadResponse import de.schildbach.wallet.database.dao.DashPayProfileDao import de.schildbach.wallet.database.entity.BlockchainIdentityConfig @@ -70,7 +71,8 @@ class EditProfileViewModel @Inject constructor( private val walletApplication: WalletApplication, private val analytics: AnalyticsService, blockchainIdentityDataDao: BlockchainIdentityConfig, - dashPayProfileDao: DashPayProfileDao + dashPayProfileDao: DashPayProfileDao, + val platformRepo: PlatformRepo ) : BaseProfileViewModel(blockchainIdentityDataDao, dashPayProfileDao) { enum class ProfilePictureStorageService { @@ -122,7 +124,7 @@ class EditProfileViewModel @Inject constructor( val updateProfileRequestState = UpdateProfileStatusLiveData(walletApplication) - var lastAttemptedProfile: DashPayProfile? = null + private var lastAttemptedProfile: DashPayProfile? = null fun broadcastUpdateProfile(displayName: String, publicMessage: String, avatarUrl: String, uploadService: String = "", localAvatarUrl: String = "") { @@ -405,4 +407,8 @@ class EditProfileViewModel @Inject constructor( } } } + + suspend fun hasEnoughCredits(): CreditBalanceInfo { + return platformRepo.getIdentityBalance() + } } \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt index 3253dd63e..9b6b79858 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt @@ -48,7 +48,6 @@ 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 @@ -70,7 +69,6 @@ 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.slf4j.LoggerFactory @@ -516,6 +514,14 @@ class PlatformRepo @Inject constructor( } } + suspend fun createTopupTransactionAsync(blockchainIdentity: BlockchainIdentity, topupAmount: Coin, keyParameter: KeyParameter?, useCoinJoin: Boolean) { + withContext(Dispatchers.IO) { + Context.propagate(walletApplication.wallet!!.context) + val cftx = blockchainIdentity.createTopupFundingTransaction(topupAmount, keyParameter, useCoinJoin, true) + blockchainIdentity.initializeAssetLockTransaction(cftx) + } + } + // // Step 2 is to obtain the credit funding transaction for invites // @@ -1192,4 +1198,8 @@ class PlatformRepo @Inject constructor( } return report.toString() } + + suspend fun getIdentityBalance(): CreditBalanceInfo { + return CreditBalanceInfo(platform.client.getIdentityBalance(blockchainIdentity.uniqueIdentifier)) + } } \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/send/BuyCreditsFragment.kt b/wallet/src/de/schildbach/wallet/ui/send/BuyCreditsFragment.kt new file mode 100644 index 000000000..7bc2ea298 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/send/BuyCreditsFragment.kt @@ -0,0 +1,192 @@ +package de.schildbach.wallet.ui.send + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.View +import de.schildbach.wallet.data.CreditBalanceInfo +import de.schildbach.wallet.database.entity.DashPayProfile +import de.schildbach.wallet.integration.android.BitcoinIntegration +import de.schildbach.wallet_test.R +import org.bitcoinj.core.Coin +import org.bitcoinj.core.InsufficientMoneyException +import org.bitcoinj.core.Transaction +import org.bitcoinj.crypto.KeyCrypterException +import org.bitcoinj.utils.ExchangeRate +import org.bitcoinj.utils.MonetaryFormat +import org.bitcoinj.wallet.Wallet +import org.dash.wallet.common.services.LeftoverBalanceException +import org.dash.wallet.common.services.analytics.AnalyticsConstants +import org.dash.wallet.common.ui.dialogs.AdaptiveDialog +import org.dash.wallet.common.ui.dialogs.MinimumBalanceDialog +import org.slf4j.LoggerFactory + +class BuyCreditsFragment : SendCoinsFragment() { + + companion object { + private val log = LoggerFactory.getLogger(BuyCreditsFragment::class.java) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.paymentHeader.setTitle(getString(R.string.credit_balance_button_buy)) + } + + override fun updateView() { + val isReplaying = viewModel.isBlockchainReplaying.value + val dryRunException = viewModel.dryRunException + + if (isReplaying != true && dryRunException != null) { + when (dryRunException) { + is InsufficientMoneyException -> { + val errorMessage = getErrorMessage(R.string.credit_balance_insufficient_error_message) + enterAmountFragment?.setError(errorMessage) + return + } + else -> {} + } + } + + // if there is no value (null) or it is zero, then display the message in the + // enter amount fragment using 0.01 DASH + val amount = enterAmountViewModel.amount.value ?: Coin.CENT + val operations = if (amount.isZero) { + Coin.CENT.value + } else { + amount.value + } / CreditBalanceInfo.MAX_OPERATION_COST_COIN + enterAmountFragment?.setMessage( + getString(R.string.buy_credits_estimated_items, + if (amount.isZero) { Coin.CENT } else { amount }.toFriendlyString(), + operations, + operations + ) + ) + + super.updateView() + } + + override suspend fun showPaymentConfirmation() { + + AdaptiveDialog.create( + null, + "Not Implemented", + "The feature to topup your credits is not completed", + getString(R.string.button_close) + ).showAsync(requireActivity()) + +// val dryRunRequest = viewModel.dryrunSendRequest ?: return +// val address = viewModel.basePaymentIntent.address?.toBase58() ?: return +// +// val txFee = dryRunRequest.tx.fee +// val amount: Coin? +// val total: String? +// +// if (dryRunRequest.emptyWallet) { +// amount = enterAmountViewModel.amount.value?.minus(txFee) +// total = enterAmountViewModel.amount.value?.toPlainString() +// } else { +// amount = enterAmountViewModel.amount.value +// total = amount?.add(txFee ?: Coin.ZERO)?.toPlainString() +// } +// +// val rate = enterAmountViewModel.selectedExchangeRate.value +// val exchangeRate = rate?.let { ExchangeRate(Coin.COIN, rate.fiat) } +// val amountStr = MonetaryFormat.BTC.noCode().format(amount).toString() +// val fee = txFee?.toPlainString() ?: "" +// +// var dashPayProfile: DashPayProfile? = null +// +// if (viewModel.contactData.value?.requestReceived == true) { +// dashPayProfile = viewModel.contactData.value?.dashPayProfile +// } +// +// val isPendingContactRequest = viewModel.contactData.value?.isPendingRequest == true +// val username = dashPayProfile?.username +// val displayName = (dashPayProfile?.displayName ?: "").ifEmpty { username } +// val avatarUrl = dashPayProfile?.avatarUrl +// +// // need to put the conformation for used with Create UserName +// val dialog = ConfirmTransactionDialog() +// val confirmed = dialog.show( +// requireActivity(), +// address, +// amountStr, +// exchangeRate, +// fee, +// total ?: "", +// null, +// null, +// null, +// username, +// displayName, +// avatarUrl, +// isPendingContactRequest +// ) +// +// if (confirmed == true) { +// handleGo(true, dialog.autoAcceptContactRequest) +// } + } + + private suspend fun handleGo(checkBalance: Boolean, autoAcceptContactRequest: Boolean) { + if (viewModel.dryrunSendRequest == null) { + log.error("illegal state dryrunSendRequest == null") + return + } + + val editedAmount = enterAmountViewModel.amount.value + val rate = enterAmountViewModel.selectedExchangeRate.value + + if (editedAmount != null) { + val exchangeRate = rate?.fiat?.let { ExchangeRate(Coin.COIN, it) } + + try { + viewModel.logEvent(AnalyticsConstants.SendReceive.ENTER_AMOUNT_SEND) + + if (enterAmountFragment?.maxSelected == true) { + viewModel.logEvent(AnalyticsConstants.SendReceive.ENTER_AMOUNT_MAX) + } + + // buy do an asset lock transaction. + val tx = viewModel.signAndSendPayment(editedAmount, exchangeRate, checkBalance) + + onSignAndSendPaymentSuccess(tx, autoAcceptContactRequest) + } catch (ex: LeftoverBalanceException) { + val shouldContinue = MinimumBalanceDialog().showAsync(requireActivity()) + + if (shouldContinue == true) { + handleGo(false, autoAcceptContactRequest) + } + } catch (ex: InsufficientMoneyException) { + showInsufficientMoneyDialog(ex.missing ?: Coin.ZERO) + } catch (ex: KeyCrypterException) { + showFailureDialog(ex) + } catch (ex: Wallet.CouldNotAdjustDownwards) { + showEmptyWalletFailedDialog() + } catch (ex: Exception) { + showFailureDialog(ex) + } + + viewModel.resetState() + } + } + + private fun onSignAndSendPaymentSuccess(transaction: Transaction, autoAcceptContactRequest: Boolean) { + viewModel.logSentEvent(enterAmountViewModel.dashToFiatDirection.value ?: true) + val callingActivity = requireActivity().callingActivity + + if (callingActivity != null) { + log.info("returning result to calling activity: {}", callingActivity.flattenToString()) + val resultIntent = Intent() + BitcoinIntegration.transactionHashToResult( + resultIntent, + transaction.txId.toString() + ) + requireActivity().setResult(Activity.RESULT_OK, resultIntent) + } + + showTransactionResult(transaction, autoAcceptContactRequest) + playSentSound() + requireActivity().finish() + } +} \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.kt b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.kt index f9ae4c697..968978dd2 100644 --- a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.kt @@ -41,7 +41,9 @@ import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.ActivitySendCoinsBinding import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine +import org.bitcoinj.core.Coin import org.bitcoinj.protocols.payments.PaymentProtocol +import org.bitcoinj.script.ScriptBuilder import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.util.ResourceString import java.io.FileNotFoundException @@ -53,6 +55,7 @@ open class SendCoinsActivity : LockScreenActivity() { companion object { const val ACTION_SEND_FROM_WALLET_URI = "de.schildbach.wallet.action.SEND_FROM_WALLET_URI" const val INTENT_EXTRA_PAYMENT_INTENT = "paymentIntent" + const val INTENT_EXTRA_BUY_CREDITS = "buyCredits" fun start(context: Context, paymentIntent: PaymentIntent?) { start(context, null, paymentIntent, false) @@ -70,6 +73,23 @@ open class SendCoinsActivity : LockScreenActivity() { context.startActivity(intent) } + fun startBuyCredits(context: Context, pubKeyHash: ByteArray) { + val intent = Intent(context, SendCoinsActivity::class.java) + + val paymentIntent = PaymentIntent( + PaymentIntent.Standard.BIP21, + "topup", + null, + listOf(PaymentIntent.Output(Coin.ZERO, ScriptBuilder.createOpReturnScript(ByteArray(20)))).toTypedArray(), + "topup", null, null, null, null, + null, null, null + ) + intent.putExtra(INTENT_EXTRA_PAYMENT_INTENT, paymentIntent) + intent.putExtra(INTENT_EXTRA_BUY_CREDITS, true) + intent.putExtra(INTENT_EXTRA_KEEP_UNLOCKED, false) + context.startActivity(intent) + } + fun sendFromWalletUri(callingActivity: Activity, requestCode: Int, paymentIntent: PaymentIntent?) { val intent = Intent(callingActivity, SendCoinsActivity::class.java) intent.action = ACTION_SEND_FROM_WALLET_URI @@ -212,10 +232,13 @@ open class SendCoinsActivity : LockScreenActivity() { val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController val navGraph = navController.navInflater.inflate(R.navigation.nav_send) + val buyCredits = intent.extras?.get(INTENT_EXTRA_BUY_CREDITS) ?: false navGraph.setStartDestination( if (paymentIntent.hasPaymentRequestUrl()) { R.id.paymentProtocolFragment + } else if (buyCredits == true) { + R.id.buyCreditsFragment } else { R.id.sendCoinsFragment } @@ -224,7 +247,8 @@ open class SendCoinsActivity : LockScreenActivity() { navController.setGraph( navGraph, bundleOf( - INTENT_EXTRA_PAYMENT_INTENT to paymentIntent + INTENT_EXTRA_PAYMENT_INTENT to paymentIntent, + INTENT_EXTRA_BUY_CREDITS to buyCredits ) ) } diff --git a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.kt b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.kt index 971a75c11..62323e1c8 100644 --- a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.kt @@ -60,21 +60,21 @@ import org.slf4j.LoggerFactory import javax.inject.Inject @AndroidEntryPoint -class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { +open class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { companion object { private val log = LoggerFactory.getLogger(SendCoinsFragment::class.java) private const val SEND_COINS_SOUND = "send_coins_broadcast_1" } - private val binding by viewBinding(SendCoinsFragmentBinding::bind) - private val viewModel by activityViewModels() - private val enterAmountViewModel by activityViewModels() + protected val binding by viewBinding(SendCoinsFragmentBinding::bind) + protected val viewModel by activityViewModels() + protected val enterAmountViewModel by activityViewModels() private val dashPayViewModel by activityViewModels() private val args by navArgs() @Inject lateinit var authManager: AuthenticationManager - private var enterAmountFragment: EnterAmountFragment? = null - private var userAuthorizedDuring: Boolean = false + protected var enterAmountFragment: EnterAmountFragment? = null + protected var userAuthorizedDuring: Boolean = false get() = field || enterAmountFragment?.didAuthorize == true set(value) { field = value @@ -171,7 +171,7 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { } } - private fun updateView() { + protected open fun updateView() { val isReplaying = viewModel.isBlockchainReplaying.value val dryRunException = viewModel.dryRunException val state = viewModel.state.value ?: SendCoinsViewModel.State.INPUT @@ -206,13 +206,13 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { ) } - private fun getErrorMessage(@StringRes errorMessage: Int) = if (!requirePinForBalance || userAuthorizedDuring) { + protected fun getErrorMessage(@StringRes errorMessage: Int) = if (!requirePinForBalance || userAuthorizedDuring) { getString(errorMessage) } else { "" } - private suspend fun authenticateOrConfirm() { + open suspend fun authenticateOrConfirm() { if (!viewModel.everythingPlausible()) { return } @@ -250,6 +250,7 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { } val tx = viewModel.signAndSendPayment(editedAmount, exchangeRate, checkBalance) + onSignAndSendPaymentSuccess(tx, autoAcceptContactRequest) } catch (ex: LeftoverBalanceException) { val shouldContinue = MinimumBalanceDialog().showAsync(requireActivity()) @@ -271,7 +272,7 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { } } - private suspend fun showPaymentConfirmation() { + protected open suspend fun showPaymentConfirmation() { val dryRunRequest = viewModel.dryrunSendRequest ?: return val address = viewModel.basePaymentIntent.address?.toBase58() ?: return @@ -344,7 +345,7 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { requireActivity().finish() } - private fun showTransactionResult(transaction: Transaction, autoAcceptContactRequest: Boolean) { + protected fun showTransactionResult(transaction: Transaction, autoAcceptContactRequest: Boolean) { if (!isAdded) { return } @@ -365,7 +366,7 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { startActivity(transactionResultIntent) } - private fun playSentSound() { + protected fun playSentSound() { if (!viewModel.shouldPlaySounds) { return } @@ -393,7 +394,7 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { binding.paymentHeader.setBalanceValue(balanceText) } - private suspend fun showInsufficientMoneyDialog(missing: Coin) { + protected suspend fun showInsufficientMoneyDialog(missing: Coin) { val msg = StringBuilder( getString( R.string.send_coins_fragment_insufficient_money_msg1, @@ -438,7 +439,7 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { } } - private suspend fun showEmptyWalletFailedDialog() { + protected suspend fun showEmptyWalletFailedDialog() { AdaptiveDialog.create( R.drawable.ic_error, getString(R.string.send_coins_fragment_empty_wallet_failed_title), @@ -448,7 +449,7 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { ).showAsync(requireActivity()) } - private suspend fun showFailureDialog(exception: Exception) { + protected suspend fun showFailureDialog(exception: Exception) { AdaptiveDialog.create( R.drawable.ic_error, getString(R.string.send_coins_error_msg), From 6d73f5894a76d94a4386c6629c6313f3d19e432b Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Thu, 1 Aug 2024 05:40:35 -0700 Subject: [PATCH 3/8] feat: add balance warning dialogs for contact requests --- .../wallet/ui/DashPayUserActivity.kt | 42 ++++++++++++++++++- .../wallet/ui/DashPayUserActivityViewModel.kt | 5 +++ .../wallet/ui/SearchUserActivity.kt | 36 ++++++++++++++++ .../wallet/ui/dashpay/DashPayViewModel.kt | 5 +++ 4 files changed, 86 insertions(+), 2 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt index 66c970578..2065373ca 100644 --- a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt @@ -23,6 +23,7 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import de.schildbach.wallet.data.* @@ -37,9 +38,12 @@ import de.schildbach.wallet.ui.transactions.TransactionDetailsDialogFragment import de.schildbach.wallet.ui.util.InputParser import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.ActivityDashpayUserBinding +import kotlinx.coroutines.launch import org.bitcoinj.core.PrefixedChecksummedBytes import org.bitcoinj.core.Transaction import org.bitcoinj.core.VerificationException +import org.bitcoinj.wallet.AuthenticationKeyChain +import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.data.entity.BlockchainState import org.dash.wallet.common.services.analytics.AnalyticsConstants @@ -156,12 +160,14 @@ class DashPayUserActivity : LockScreenActivity() { override fun onSendContactRequestClick() { dashPayViewModel.logEvent(AnalyticsConstants.UsersContacts.SEND_REQUEST) - viewModel.sendContactRequest() + // check credit balance + sendContactRequest() setResult(RESULT_CODE_CHANGED) } override fun onAcceptClick() { - viewModel.sendContactRequest() + // check credit balance + sendContactRequest() setResult(RESULT_CODE_CHANGED) } @@ -185,6 +191,38 @@ class DashPayUserActivity : LockScreenActivity() { } } + fun sendContactRequest() { + lifecycleScope.launch { + val enough = viewModel.hasEnoughCredits() + // TODO: before merging remove this + val shouldWarn = true // enough.isBalanceWarning() + val isEmpty = enough.isBalanceWarning() + + if (shouldWarn || isEmpty) { + val answer = AdaptiveDialog.create( + R.drawable.ic_warning_yellow_circle, + if (isEmpty) getString(R.string.credit_balance_empty_warning_title) else getString(R.string.credit_balance_low_warning_title), + if (isEmpty) getString(R.string.credit_balance_empty_warning_message) else getString(R.string.credit_balance_low_warning_message), + getString(R.string.credit_balance_button_maybe_later), + getString(R.string.credit_balance_button_buy) + ).showAsync(this@DashPayUserActivity) + + if (answer == true) { + val authenticationGroupExtension = + walletData.wallet!!.getKeyChainExtension(AuthenticationGroupExtension.EXTENSION_ID) as AuthenticationGroupExtension + val pubKeyHash = + authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.BLOCKCHAIN_IDENTITY_TOPUP).pubKeyHash + SendCoinsActivity.startBuyCredits(this@DashPayUserActivity, pubKeyHash) + } else { + if (shouldWarn) + viewModel.sendContactRequest() + } + } else { + viewModel.sendContactRequest() + } + } + } + private fun updateContactRelationUi() { val userData = viewModel.userData val state = viewModel.sendContactRequestState.value diff --git a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivityViewModel.kt b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivityViewModel.kt index 945cf51a5..657b24d37 100644 --- a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivityViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivityViewModel.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import de.schildbach.wallet.WalletApplication +import de.schildbach.wallet.data.CreditBalanceInfo import de.schildbach.wallet.data.UsernameSearchResult import de.schildbach.wallet.database.entity.DashPayProfile import de.schildbach.wallet.livedata.Resource @@ -100,4 +101,8 @@ class DashPayUserActivityViewModel @Inject constructor( platformRepo.addOrUpdateDashPayProfile(dashPayProfile) } } + + suspend fun hasEnoughCredits(): CreditBalanceInfo { + return platformRepo.getIdentityBalance() + } } \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt index d47bc2be9..a45168933 100644 --- a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt @@ -47,9 +47,13 @@ import de.schildbach.wallet.livedata.Status import de.schildbach.wallet.ui.dashpay.DashPayViewModel import de.schildbach.wallet.ui.invite.InviteFriendActivity import de.schildbach.wallet.ui.invite.InvitesHistoryActivity +import de.schildbach.wallet.ui.send.SendCoinsActivity import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.ActivitySearchDashpayProfileRootBinding import kotlinx.coroutines.launch +import org.bitcoinj.wallet.AuthenticationKeyChain +import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension +import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.util.observe @AndroidEntryPoint @@ -301,7 +305,39 @@ class SearchUserActivity : LockScreenActivity(), OnItemClickListener, OnContactR } override fun onAcceptRequest(usernameSearchResult: UsernameSearchResult, position: Int) { + // need to check balance dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) + + lifecycleScope.launch { + val enough = dashPayViewModel.hasEnoughCredits() + // TODO: before merging remove this + val shouldWarn = true // enough.isBalanceWarning() + val isEmpty = enough.isBalanceWarning() + + if (shouldWarn || isEmpty) { + val answer = AdaptiveDialog.create( + R.drawable.ic_warning_yellow_circle, + if (isEmpty) getString(R.string.credit_balance_empty_warning_title) else getString(R.string.credit_balance_low_warning_title), + if (isEmpty) getString(R.string.credit_balance_empty_warning_message) else getString(R.string.credit_balance_low_warning_message), + getString(R.string.credit_balance_button_maybe_later), + getString(R.string.credit_balance_button_buy) + ).showAsync(this@SearchUserActivity) + + if (answer == true) { + val authenticationGroupExtension = + walletData.wallet!!.getKeyChainExtension(AuthenticationGroupExtension.EXTENSION_ID) as AuthenticationGroupExtension + val pubKeyHash = + authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.BLOCKCHAIN_IDENTITY_TOPUP).pubKeyHash + SendCoinsActivity.startBuyCredits(this@SearchUserActivity, pubKeyHash) + } else { + if (shouldWarn) + dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) + } + } else { + dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) + } + } + } override fun onIgnoreRequest(usernameSearchResult: UsernameSearchResult, position: Int) { diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt index eb0c0679b..5541942f3 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.switchMap import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import de.schildbach.wallet.WalletApplication +import de.schildbach.wallet.data.CreditBalanceInfo import de.schildbach.wallet.data.UsernameSearch import de.schildbach.wallet.data.UsernameSortOrderBy import de.schildbach.wallet.database.dao.BlockchainStateDao @@ -310,4 +311,8 @@ open class DashPayViewModel @Inject constructor( val limit: Int = 100, val excludeIds: ArrayList = arrayListOf() ) + + suspend fun hasEnoughCredits(): CreditBalanceInfo { + return platformRepo.getIdentityBalance() + } } From 0bc04dc3d260443dd7dcee3d0edfea5de8214512 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Thu, 1 Aug 2024 05:51:02 -0700 Subject: [PATCH 4/8] feat: add balance warning dialogs for contact requests --- .../wallet/ui/DashPayUserActivity.kt | 6 +--- .../wallet/ui/EditProfileActivity.kt | 4 +-- .../wallet/ui/SearchUserActivity.kt | 6 +--- .../ui/dashpay/NotificationsFragment.kt | 34 ++++++++++++++++++- .../wallet/ui/send/SendCoinsActivity.kt | 2 +- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt index 2065373ca..2c9ebf15b 100644 --- a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt @@ -208,11 +208,7 @@ class DashPayUserActivity : LockScreenActivity() { ).showAsync(this@DashPayUserActivity) if (answer == true) { - val authenticationGroupExtension = - walletData.wallet!!.getKeyChainExtension(AuthenticationGroupExtension.EXTENSION_ID) as AuthenticationGroupExtension - val pubKeyHash = - authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.BLOCKCHAIN_IDENTITY_TOPUP).pubKeyHash - SendCoinsActivity.startBuyCredits(this@DashPayUserActivity, pubKeyHash) + SendCoinsActivity.startBuyCredits(this@DashPayUserActivity) } else { if (shouldWarn) viewModel.sendContactRequest() diff --git a/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt b/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt index 3c4280ade..162548377 100644 --- a/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt @@ -439,9 +439,7 @@ class EditProfileActivity : LockScreenActivity() { ).showAsync(this@EditProfileActivity) if (answer == true) { - val authenticationGroupExtension = walletData.wallet!!.getKeyChainExtension(AuthenticationGroupExtension.EXTENSION_ID) as AuthenticationGroupExtension - val pubKeyHash = authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.BLOCKCHAIN_IDENTITY_TOPUP).pubKeyHash - SendCoinsActivity.startBuyCredits(this@EditProfileActivity, pubKeyHash) + SendCoinsActivity.startBuyCredits(this@EditProfileActivity) } else { if (shouldWarn) save() diff --git a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt index a45168933..8e8e1b932 100644 --- a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt @@ -324,11 +324,7 @@ class SearchUserActivity : LockScreenActivity(), OnItemClickListener, OnContactR ).showAsync(this@SearchUserActivity) if (answer == true) { - val authenticationGroupExtension = - walletData.wallet!!.getKeyChainExtension(AuthenticationGroupExtension.EXTENSION_ID) as AuthenticationGroupExtension - val pubKeyHash = - authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.BLOCKCHAIN_IDENTITY_TOPUP).pubKeyHash - SendCoinsActivity.startBuyCredits(this@SearchUserActivity, pubKeyHash) + SendCoinsActivity.startBuyCredits(this@SearchUserActivity) } else { if (shouldWarn) dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt index 8372bd418..c9269e251 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt @@ -35,12 +35,16 @@ import de.schildbach.wallet.ui.DashPayUserActivity import de.schildbach.wallet.ui.dashpay.notification.NotificationsViewModel import de.schildbach.wallet.ui.invite.InviteFriendActivity import de.schildbach.wallet.ui.invite.InvitesHistoryActivity +import de.schildbach.wallet.ui.send.SendCoinsActivity import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.FragmentNotificationsBinding import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import org.bitcoinj.wallet.AuthenticationKeyChain +import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.services.analytics.AnalyticsConstants +import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.util.observe import org.slf4j.LoggerFactory @@ -254,13 +258,41 @@ class NotificationsFragment : Fragment(R.layout.fragment_notifications) { fun onAcceptRequest(usernameSearchResult: UsernameSearchResult, position: Int) { dashPayViewModel.logEvent(AnalyticsConstants.UsersContacts.NOTIFICATIONS_ACCEPT_REQUEST) - dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) + sendContactRequest(usernameSearchResult) } fun onIgnoreRequest(usernameSearchResult: UsernameSearchResult, position: Int) { // this Ignmore Request function is not currently implemented } + fun sendContactRequest(usernameSearchResult: UsernameSearchResult) { + lifecycleScope.launch { + val enough = dashPayViewModel.hasEnoughCredits() + // TODO: before merging remove this + val shouldWarn = true // enough.isBalanceWarning() + val isEmpty = enough.isBalanceWarning() + + if (shouldWarn || isEmpty) { + val answer = AdaptiveDialog.create( + R.drawable.ic_warning_yellow_circle, + if (isEmpty) getString(R.string.credit_balance_empty_warning_title) else getString(R.string.credit_balance_low_warning_title), + if (isEmpty) getString(R.string.credit_balance_empty_warning_message) else getString(R.string.credit_balance_low_warning_message), + getString(R.string.credit_balance_button_maybe_later), + getString(R.string.credit_balance_button_buy) + ).showAsync(requireActivity()) + + if (answer == true) { + SendCoinsActivity.startBuyCredits(requireActivity()) + } else { + if (shouldWarn) + dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) + } + } else { + dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) + } + } + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == DashPayUserActivity.REQUEST_CODE_DEFAULT && resultCode == DashPayUserActivity.RESULT_CODE_CHANGED) { diff --git a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.kt b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.kt index 968978dd2..15431e2ad 100644 --- a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.kt @@ -73,7 +73,7 @@ open class SendCoinsActivity : LockScreenActivity() { context.startActivity(intent) } - fun startBuyCredits(context: Context, pubKeyHash: ByteArray) { + fun startBuyCredits(context: Context) { val intent = Intent(context, SendCoinsActivity::class.java) val paymentIntent = PaymentIntent( From f3682332d556b5f8eb6e4c80ee95f9a34620511a Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Thu, 1 Aug 2024 06:02:05 -0700 Subject: [PATCH 5/8] style: fix formatting --- .../wallet/ui/SearchUserActivity.kt | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt index 8e8e1b932..7bec2576b 100644 --- a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt @@ -308,32 +308,31 @@ class SearchUserActivity : LockScreenActivity(), OnItemClickListener, OnContactR // need to check balance dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) - lifecycleScope.launch { - val enough = dashPayViewModel.hasEnoughCredits() - // TODO: before merging remove this - val shouldWarn = true // enough.isBalanceWarning() - val isEmpty = enough.isBalanceWarning() - - if (shouldWarn || isEmpty) { - val answer = AdaptiveDialog.create( - R.drawable.ic_warning_yellow_circle, - if (isEmpty) getString(R.string.credit_balance_empty_warning_title) else getString(R.string.credit_balance_low_warning_title), - if (isEmpty) getString(R.string.credit_balance_empty_warning_message) else getString(R.string.credit_balance_low_warning_message), - getString(R.string.credit_balance_button_maybe_later), - getString(R.string.credit_balance_button_buy) - ).showAsync(this@SearchUserActivity) - - if (answer == true) { - SendCoinsActivity.startBuyCredits(this@SearchUserActivity) - } else { - if (shouldWarn) - dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) - } + lifecycleScope.launch { + val enough = dashPayViewModel.hasEnoughCredits() + // TODO: before merging remove this + val shouldWarn = true // enough.isBalanceWarning() + val isEmpty = enough.isBalanceWarning() + + if (shouldWarn || isEmpty) { + val answer = AdaptiveDialog.create( + R.drawable.ic_warning_yellow_circle, + if (isEmpty) getString(R.string.credit_balance_empty_warning_title) else getString(R.string.credit_balance_low_warning_title), + if (isEmpty) getString(R.string.credit_balance_empty_warning_message) else getString(R.string.credit_balance_low_warning_message), + getString(R.string.credit_balance_button_maybe_later), + getString(R.string.credit_balance_button_buy) + ).showAsync(this@SearchUserActivity) + + if (answer == true) { + SendCoinsActivity.startBuyCredits(this@SearchUserActivity) } else { - dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) + if (shouldWarn) + dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) } + } else { + dashPayViewModel.sendContactRequest(usernameSearchResult.fromContactRequest!!.userId) } - + } } override fun onIgnoreRequest(usernameSearchResult: UsernameSearchResult, position: Int) { From 40622ebec9ff9eeef3cd6c0c82d84a451d90f6a1 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 9 Aug 2024 10:24:19 -0700 Subject: [PATCH 6/8] fix: enable balance checking for credits --- wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt | 3 +-- wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt | 3 +-- .../de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt index 2c9ebf15b..a2da17fe3 100644 --- a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt @@ -194,8 +194,7 @@ class DashPayUserActivity : LockScreenActivity() { fun sendContactRequest() { lifecycleScope.launch { val enough = viewModel.hasEnoughCredits() - // TODO: before merging remove this - val shouldWarn = true // enough.isBalanceWarning() + val shouldWarn = enough.isBalanceWarning() val isEmpty = enough.isBalanceWarning() if (shouldWarn || isEmpty) { diff --git a/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt b/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt index 162548377..f479f5f27 100644 --- a/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt @@ -425,8 +425,7 @@ class EditProfileActivity : LockScreenActivity() { private fun saveButton() { lifecycleScope.launch { val enough = editProfileViewModel.hasEnoughCredits() - // TODO: before merging remove this - val shouldWarn = true // enough.isBalanceWarning() + val shouldWarn = enough.isBalanceWarning() val isEmpty = enough.isBalanceWarning() if (shouldWarn || isEmpty) { diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt index c9269e251..f7c032ae2 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt @@ -268,8 +268,7 @@ class NotificationsFragment : Fragment(R.layout.fragment_notifications) { fun sendContactRequest(usernameSearchResult: UsernameSearchResult) { lifecycleScope.launch { val enough = dashPayViewModel.hasEnoughCredits() - // TODO: before merging remove this - val shouldWarn = true // enough.isBalanceWarning() + val shouldWarn = enough.isBalanceWarning() val isEmpty = enough.isBalanceWarning() if (shouldWarn || isEmpty) { From aff521c9fd1303fe7adf2e15d1959c860eb1d49d Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 9 Aug 2024 10:24:44 -0700 Subject: [PATCH 7/8] fix: fix infinite loop if not wanting to save profile --- wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt b/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt index f479f5f27..5f1076a9d 100644 --- a/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt @@ -681,7 +681,7 @@ class EditProfileActivity : LockScreenActivity() { if (it == true) { save() } else { - finish() + super.finish() } } return From 4882e17c7f7a077f1410bab0108014f88652f329 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 9 Aug 2024 10:25:35 -0700 Subject: [PATCH 8/8] fix: fix issues with UserAlerts after app upgrade --- .../database/entity/BlockchainIdentityData.kt | 5 ++++ .../service/platform/PlatformSyncService.kt | 4 ++- .../wallet/ui/SearchUserActivity.kt | 3 +-- .../ui/dashpay/CreateIdentityService.kt | 12 ++++----- .../ui/dashpay/NotificationsLiveData.kt | 2 +- .../schildbach/wallet/ui/dashpay/UserAlert.kt | 25 ++++++++++++++++--- 6 files changed, 38 insertions(+), 13 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/database/entity/BlockchainIdentityData.kt b/wallet/src/de/schildbach/wallet/database/entity/BlockchainIdentityData.kt index 3d496c84f..6d151feac 100644 --- a/wallet/src/de/schildbach/wallet/database/entity/BlockchainIdentityData.kt +++ b/wallet/src/de/schildbach/wallet/database/entity/BlockchainIdentityData.kt @@ -113,6 +113,11 @@ data class BlockchainIdentityData(var creationState: CreationState = CreationSta DONE, DONE_AND_DISMISS // this should always be the last value } + + fun finishRestoration() { + this.restoring = false + this.creationStateErrorMessage = null + } } @Singleton diff --git a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt index 2e4479d80..e59d16252 100644 --- a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt +++ b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt @@ -1075,7 +1075,9 @@ class PlatformSynchronizationService @Inject constructor( } // first check to see if there is a blockchain identity - if (blockchainIdentityDataDao.load() == null) { + // or if the previous restore is incomplete + val identityData = blockchainIdentityDataDao.load() + if (identityData == null || identityData.restoring) { log.info("PreDownloadBlocks: checking for existing associated identity") val identity = platformRepo.getIdentityFromPublicKeyId() diff --git a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt index 7bec2576b..778c93152 100644 --- a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt @@ -310,8 +310,7 @@ class SearchUserActivity : LockScreenActivity(), OnItemClickListener, OnContactR lifecycleScope.launch { val enough = dashPayViewModel.hasEnoughCredits() - // TODO: before merging remove this - val shouldWarn = true // enough.isBalanceWarning() + val shouldWarn = enough.isBalanceWarning() val isEmpty = enough.isBalanceWarning() if (shouldWarn || isEmpty) { diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt index 9d280e6d4..4e4e99cec 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt @@ -19,8 +19,9 @@ import de.schildbach.wallet.security.SecurityFunctions import de.schildbach.wallet.security.SecurityGuard import de.schildbach.wallet.service.CoinJoinMode import de.schildbach.wallet.service.platform.PlatformSyncService +import de.schildbach.wallet.ui.dashpay.UserAlert.Companion.INVITATION_NOTIFICATION_ICON +import de.schildbach.wallet.ui.dashpay.UserAlert.Companion.INVITATION_NOTIFICATION_TEXT import de.schildbach.wallet.ui.dashpay.work.SendContactRequestOperation -import de.schildbach.wallet_test.R import io.grpc.Status import io.grpc.StatusRuntimeException import kotlinx.coroutines.* @@ -626,10 +627,8 @@ class CreateIdentityService : LifecycleService() { // this alert will be shown or not based on the current balance and will be // managed by NotificationsLiveData - val userAlert = UserAlert(R.string.invitation_notification_text, - R.drawable.ic_invitation) + val userAlert = UserAlert(INVITATION_NOTIFICATION_TEXT, INVITATION_NOTIFICATION_ICON) userAlertDao.insert(userAlert) - } } @@ -656,7 +655,7 @@ class CreateIdentityService : LifecycleService() { val creditFundingTransaction: AssetLockTransaction? = cftxs.find { it.identityId.bytes!!.contentEquals(identity) } val existingBlockchainIdentityData = blockchainIdentityDataDao.load() - if (existingBlockchainIdentityData != null) { + if (existingBlockchainIdentityData != null && !(existingBlockchainIdentityData.restoring /*&& existingBlockchainIdentityData.creationStateErrorMessage != null*/)) { log.info("Attempting restore of existing identity and username; save credit funding txid") val blockchainIdentity = platformRepo.blockchainIdentity blockchainIdentity.assetLockTransaction = creditFundingTransaction @@ -732,7 +731,8 @@ class CreateIdentityService : LifecycleService() { // We are finished recovering platformRepo.updateIdentityCreationState(blockchainIdentityData, CreationState.DONE) - + blockchainIdentityData.finishRestoration() + platformRepo.updateBlockchainIdentityData(blockchainIdentityData) // Complete the entire process platformRepo.updateIdentityCreationState(blockchainIdentityData, CreationState.DONE_AND_DISMISS) diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsLiveData.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsLiveData.kt index c609a31aa..891d1020a 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsLiveData.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsLiveData.kt @@ -45,7 +45,7 @@ open class NotificationsLiveData( val userAlert = userAlertDao.load(0L) if (userAlert != null && platformRepo.shouldShowAlert()) { - results.add(NotificationItemUserAlert(userAlert.stringResId, userAlert.iconResId)) + results.add(NotificationItemUserAlert(userAlert.stringResourceId, userAlert.iconResourceId)) } val contactRequests = platformRepo.searchContacts(query, UsernameSortOrderBy.DATE_ADDED) diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/UserAlert.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/UserAlert.kt index d7cec4cd3..21eaa905f 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/UserAlert.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/UserAlert.kt @@ -20,9 +20,28 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.room.Entity import androidx.room.PrimaryKey +import de.schildbach.wallet_test.R import java.util.* @Entity(tableName = "user_alerts") -data class UserAlert(@PrimaryKey @StringRes val stringResId: Int, - @DrawableRes val iconResId: Int, var dismissed: Boolean = false, - val createdAt: Long = Date().time) \ No newline at end of file +data class UserAlert( + @PrimaryKey val stringResId: Int, + @DrawableRes val iconResId: Int, + var dismissed: Boolean = false, + val createdAt: Long = Date().time) +{ + companion object { + const val INVITATION_NOTIFICATION_TEXT = 1 + val textMap = hashMapOf(INVITATION_NOTIFICATION_TEXT to R.string.invitation_notification_text) + + + const val INVITATION_NOTIFICATION_ICON = 1000 + val iconMap = hashMapOf(INVITATION_NOTIFICATION_ICON to R.drawable.ic_invitation) + } + + + val stringResourceId: Int + get() = textMap[stringResId] ?: INVITATION_NOTIFICATION_TEXT + val iconResourceId: Int + get() = iconMap[iconResId] ?: INVITATION_NOTIFICATION_ICON +} \ No newline at end of file