From 9461b9d765abb0efebf0caf6d17a680d427aab54 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 21 Oct 2024 07:57:23 -0700 Subject: [PATCH 01/58] feat: add firebase events for some dashpay related actions --- .../services/analytics/AnalyticsConstants.kt | 3 ++ .../src/de/schildbach/wallet/di/AppModule.kt | 9 +++- .../wallet/payments/SendCoinsTaskRunner.kt | 53 +++++++++++++++++-- .../wallet/ui/dashpay/EditProfileViewModel.kt | 1 + 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/org/dash/wallet/common/services/analytics/AnalyticsConstants.kt b/common/src/main/java/org/dash/wallet/common/services/analytics/AnalyticsConstants.kt index fbd2b9c358..112899d225 100644 --- a/common/src/main/java/org/dash/wallet/common/services/analytics/AnalyticsConstants.kt +++ b/common/src/main/java/org/dash/wallet/common/services/analytics/AnalyticsConstants.kt @@ -87,6 +87,8 @@ object AnalyticsConstants { } object SendReceive { + const val SEND_TX = "send_tx" // also include amount sent + const val SEND_TX_CONTACT = "send_tx_to_contact" // also include amount sent const val SCAN_TO_SEND = "send_scan_to_send" const val SEND_TO_ADDRESS = "send_to_address" const val SHOW_QR_CODE = "receive_show_qr_code" @@ -175,6 +177,7 @@ object AnalyticsConstants { const val PROFILE_NAME_LENGTH = "profile_display_name_length" const val PROFILE_CHANGE_ABOUT_ME = "profile_change_about_me" const val PROFILE_ABOUT_ME_LENGTH = "profile_about_me_length" + const val PROFILE_CHANGE_PICTURE = "profile_change_picture" const val PROFILE_CHANGE_PICTURE_GRAVATAR = "profile_change_picture_gravatar" const val PROFILE_CHANGE_PICTURE_PUBLIC_URL = "profile_change_picture_public_url" const val PROFILE_CHANGE_PICTURE_CAMERA = "profile_change_picture_camera_photo" diff --git a/wallet/src/de/schildbach/wallet/di/AppModule.kt b/wallet/src/de/schildbach/wallet/di/AppModule.kt index c548e3bd7d..d6578e12e0 100644 --- a/wallet/src/de/schildbach/wallet/di/AppModule.kt +++ b/wallet/src/de/schildbach/wallet/di/AppModule.kt @@ -30,6 +30,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.data.CoinJoinConfig +import de.schildbach.wallet.database.entity.BlockchainIdentityConfig import de.schildbach.wallet.payments.ConfirmTransactionLauncher import de.schildbach.wallet.payments.SendCoinsTaskRunner import de.schildbach.wallet.security.SecurityFunctions @@ -37,6 +38,7 @@ import de.schildbach.wallet.service.* import de.schildbach.wallet.service.AndroidActionsService import de.schildbach.wallet.service.AppRestartService import de.schildbach.wallet.service.RestartService +import de.schildbach.wallet.ui.dashpay.PlatformRepo import de.schildbach.wallet.ui.more.tools.ZenLedgerApi import de.schildbach.wallet.ui.more.tools.ZenLedgerClient import de.schildbach.wallet.ui.notifications.NotificationManagerWrapper @@ -105,9 +107,12 @@ abstract class AppModule { walletApplication: WalletApplication, securityFunctions: SecurityFunctions, packageInfoProvider: PackageInfoProvider, - coinJoinConfig: CoinJoinConfig + analyticsService: AnalyticsService, + identityConfig: BlockchainIdentityConfig, + coinJoinConfig: CoinJoinConfig, + platformRepo: PlatformRepo ): SendPaymentService { - val realService = SendCoinsTaskRunner(walletData, walletApplication, securityFunctions, packageInfoProvider, coinJoinConfig) + val realService = SendCoinsTaskRunner(walletData, walletApplication, securityFunctions, packageInfoProvider, analyticsService, identityConfig, coinJoinConfig, platformRepo) return if (BuildConfig.FLAVOR.lowercase() == "prod") { realService diff --git a/wallet/src/de/schildbach/wallet/payments/SendCoinsTaskRunner.kt b/wallet/src/de/schildbach/wallet/payments/SendCoinsTaskRunner.kt index 894e56ca7d..f3ef41a217 100644 --- a/wallet/src/de/schildbach/wallet/payments/SendCoinsTaskRunner.kt +++ b/wallet/src/de/schildbach/wallet/payments/SendCoinsTaskRunner.kt @@ -17,17 +17,19 @@ package de.schildbach.wallet.payments import androidx.annotation.VisibleForTesting -import androidx.lifecycle.viewModelScope +import com.google.firebase.installations.FirebaseInstallations import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.data.CoinJoinConfig import de.schildbach.wallet.data.PaymentIntent +import de.schildbach.wallet.database.entity.BlockchainIdentityConfig +import de.schildbach.wallet.database.entity.BlockchainIdentityConfig.Companion.IDENTITY_ID import de.schildbach.wallet.payments.parsers.PaymentIntentParser import de.schildbach.wallet.security.SecurityFunctions import de.schildbach.wallet.security.SecurityGuard import de.schildbach.wallet.service.CoinJoinMode import de.schildbach.wallet.service.PackageInfoProvider +import de.schildbach.wallet.ui.dashpay.PlatformRepo import kotlinx.coroutines.* -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -52,6 +54,8 @@ import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.services.DirectPayException import org.dash.wallet.common.services.LeftoverBalanceException import org.dash.wallet.common.services.SendPaymentService +import org.dash.wallet.common.services.analytics.AnalyticsConstants +import org.dash.wallet.common.services.analytics.AnalyticsService import org.dash.wallet.common.transactions.ByAddressCoinSelector import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.call @@ -64,7 +68,10 @@ class SendCoinsTaskRunner @Inject constructor( private val walletApplication: WalletApplication, private val securityFunctions: SecurityFunctions, private val packageInfoProvider: PackageInfoProvider, - coinJoinConfig: CoinJoinConfig + private val analyticsService: AnalyticsService, + private val identityConfig: BlockchainIdentityConfig, + coinJoinConfig: CoinJoinConfig, + private val platformRepo: PlatformRepo ) : SendPaymentService { companion object { private const val WALLET_EXCEPTION_MESSAGE = "this method can't be used before creating the wallet" @@ -72,7 +79,7 @@ class SendCoinsTaskRunner @Inject constructor( } private var coinJoinSend = false private val coroutineScope = CoroutineScope(Dispatchers.IO) - + private var firebaseInstallationId: String = "" init { coinJoinConfig .observeMode() @@ -81,6 +88,10 @@ class SendCoinsTaskRunner @Inject constructor( coinJoinSend = mode != CoinJoinMode.NONE } .launchIn(coroutineScope) + + FirebaseInstallations.getInstance().id.addOnCompleteListener { task -> + firebaseInstallationId = if (task.isSuccessful) task.result else "" + } } @Throws(LeftoverBalanceException::class) @@ -319,7 +330,6 @@ class SendCoinsTaskRunner @Inject constructor( if (checkBalanceConditions) { checkBalanceConditions(wallet, sendRequest.tx) } - signSendRequest(sendRequest) try { @@ -334,6 +344,7 @@ class SendCoinsTaskRunner @Inject constructor( val transaction = sendRequest.tx log.info("send successful, transaction committed: {}", transaction.txId.toString()) walletApplication.broadcastTransaction(transaction) + logSendTxEvent(transaction, wallet) transaction } catch (ex: Exception) { when (ex) { @@ -349,6 +360,38 @@ class SendCoinsTaskRunner @Inject constructor( } } + private suspend fun logSendTxEvent( + transaction: Transaction, + wallet: Wallet + ) { + identityConfig.get(IDENTITY_ID)?.let { + val valueSent: Long = transaction.outputs.filter { + !it.isMine(wallet) + }.sumOf { + it.value.value + } + val isSentToContact = try { + platformRepo.blockchainIdentity.getContactForTransaction(transaction) != null + } catch (e: Exception) { + false + } + analyticsService.logEvent( + AnalyticsConstants.SendReceive.SEND_TX, + mapOf( + AnalyticsConstants.Parameter.VALUE to valueSent + ) + ) + if (isSentToContact) { + analyticsService.logEvent( + AnalyticsConstants.SendReceive.SEND_TX_CONTACT, + mapOf( + AnalyticsConstants.Parameter.VALUE to valueSent + ) + ) + } + } + } + fun signSendRequest(sendRequest: SendRequest) { val wallet = walletData.wallet ?: throw RuntimeException("this method can't be used before creating the wallet") diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/EditProfileViewModel.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/EditProfileViewModel.kt index 2f424d1a13..f657d1217a 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/EditProfileViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/EditProfileViewModel.kt @@ -398,6 +398,7 @@ class EditProfileViewModel @Inject constructor( } if (avatarUrl != dashPayProfile.value!!.avatarUrl) { + analytics.logEvent(AnalyticsConstants.UsersContacts.PROFILE_CHANGE_PICTURE, mapOf()) when (pictureSource) { "gravatar" -> analytics.logEvent(AnalyticsConstants.UsersContacts.PROFILE_CHANGE_PICTURE_GRAVATAR, mapOf()) "public_url" -> analytics.logEvent(AnalyticsConstants.UsersContacts.PROFILE_CHANGE_PICTURE_PUBLIC_URL, mapOf()) From 374e6465ed406aa8b4ad71e6f19c0e08a0475436 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 21 Oct 2024 07:58:00 -0700 Subject: [PATCH 02/58] fix: change order of operands to ensure execution --- .../wallet/service/platform/PlatformSyncService.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt index 74819afe00..5292df3dd4 100644 --- a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt +++ b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt @@ -300,7 +300,7 @@ class PlatformSynchronizationService @Inject constructor( dashPayContactRequestDao.insert(dashPayContactRequest) // add our receiving from this contact keychain if it doesn't exist - addedContact = addedContact || checkAndAddSentRequest(userId, contactRequest) + addedContact = checkAndAddSentRequest(userId, contactRequest) || addedContact log.info("contactRequest: added sent request from ${contactRequest.toUserId}") } } @@ -328,7 +328,7 @@ class PlatformSynchronizationService @Inject constructor( dashPayContactRequestDao.insert(dashPayContactRequest) // add the sending to contact keychain if it doesn't exist - addedContact = addedContact || checkAndAddReceivedRequest(userId, contactRequest) + addedContact = checkAndAddReceivedRequest(userId, contactRequest) || addedContact log.info("contactRequest: added received request from ${contactRequest.ownerId}") } } @@ -477,9 +477,9 @@ class PlatformSynchronizationService @Inject constructor( myEncryptionKey = platformRepo.walletApplication.wallet!!.keyCrypter!!.deriveKey(password) } - platformRepo.blockchainIdentity.addContactPaymentKeyChain( + platformRepo.blockchainIdentity.addPaymentKeyChainToContact( contactIdentity!!, - contactRequest.document, + contactRequest, myEncryptionKey!! ) return true From d41c0d86a053b1e26f1a4440ae414f277d4753f1 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 21 Oct 2024 08:09:21 -0700 Subject: [PATCH 03/58] fix: use dashj 21.1.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b6ce137df4..e1f5061c75 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { kotlin_version = '1.9.23' coroutinesVersion = '1.6.4' ok_http_version = '4.9.1' - dashjVersion = '21.1.1' + dashjVersion = '21.1.2' dppVersion = "1.3-SNAPSHOT" hiltVersion = '2.51' hiltCompilerVersion = '1.2.0' From ab41f9dd3327b65af2e65da7e05893c743e3534d Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 21 Oct 2024 10:40:10 -0700 Subject: [PATCH 04/58] chore: v11.0.1 --- wallet/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wallet/build.gradle b/wallet/build.gradle index a5532ed6ae..68a393e383 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -207,8 +207,8 @@ android { compileSdk 34 minSdkVersion 24 targetSdkVersion 34 - versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 110043 - versionName project.hasProperty('versionName') ? project.property('versionName') : "11.0-beta" + versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 110060 + versionName project.hasProperty('versionName') ? project.property('versionName') : "11.0.1" multiDexEnabled true generatedDensities = ['hdpi', 'xhdpi'] vectorDrawables.useSupportLibrary = true From 582456e2f4ff10b475549a1f58353e37e33c3bdd Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Thu, 24 Oct 2024 18:12:23 -0700 Subject: [PATCH 05/58] feat: update username voting screen --- build.gradle | 2 +- .../org/dash/wallet/common/ui/ResourcesExt.kt | 98 ++++++ common/src/main/res/values/colors.xml | 2 + common/src/main/res/values/strings.xml | 1 + common/src/main/res/values/styles.xml | 81 +++++ wallet/AndroidManifest.xml | 6 + .../res/layout/fragment_username_requests.xml | 12 +- .../layout/fragment_username_voting_info.xml | 3 +- .../layout/username_request_group_view.xml | 84 ++++- wallet/res/layout/username_request_view.xml | 76 ++-- wallet/res/navigation/nav_voting.xml | 8 +- wallet/res/values/strings-dashpay.xml | 14 +- .../wallet/database/dao/UsernameVoteDao.kt | 10 +- .../wallet/database/entity/UsernameRequest.kt | 16 +- .../wallet/database/entity/UsernameVote.kt | 2 +- .../service/platform/PlatformSyncService.kt | 45 ++- .../work/BroadcastUsernameVotesOperation.kt | 95 +++-- .../work/BroadcastUsernameVotesWorker.kt | 30 +- .../ui/username/AddVotingKeysFragment.kt | 16 +- .../UsernameRequestDetailsFragment.kt | 6 +- .../ui/username/UsernameRequestsFragment.kt | 136 +++++-- .../ui/username/UsernameRequestsViewModel.kt | 22 +- .../ui/username/VotingKeyInputFragment.kt | 2 +- .../adapters/UsernameRequestGroupAdapter.kt | 332 +++++++++++++----- .../voting/RequestUserNameViewModel.kt | 3 +- 25 files changed, 853 insertions(+), 249 deletions(-) diff --git a/build.gradle b/build.gradle index e1f5061c75..a6664eb071 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { coroutinesVersion = '1.6.4' ok_http_version = '4.9.1' dashjVersion = '21.1.2' - dppVersion = "1.3-SNAPSHOT" + dppVersion = "1.3.1-SNAPSHOT" hiltVersion = '2.51' hiltCompilerVersion = '1.2.0' hiltWorkVersion = '1.0.0' diff --git a/common/src/main/java/org/dash/wallet/common/ui/ResourcesExt.kt b/common/src/main/java/org/dash/wallet/common/ui/ResourcesExt.kt index 6747bd3b8e..c53504c15e 100644 --- a/common/src/main/java/org/dash/wallet/common/ui/ResourcesExt.kt +++ b/common/src/main/java/org/dash/wallet/common/ui/ResourcesExt.kt @@ -17,7 +17,12 @@ package org.dash.wallet.common.ui +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.RippleDrawable import android.view.View +import android.widget.Button import androidx.annotation.StyleRes import androidx.core.content.res.ResourcesCompat import org.dash.wallet.common.R @@ -41,3 +46,96 @@ fun View.setRoundedRippleBackground(@StyleRes style: Int?) { ) } } + +fun View.applyStyle(@StyleRes style: Int?) { + style?.let { + context.theme.applyStyle(style, true) + } +} + +//fun View.setRoundedRippleBackgroundButtonStyle1(@StyleRes style: Int?) { +// style?.let { +// val theme = this.resources.newTheme().apply { applyStyle(style, true) } +// +// // Extract the cornerRadius attribute +//// val cornerRadiusAttr = intArrayOf(R.attr.cornerRadius) +//// val cornerRadiusArray = theme.obtainStyledAttributes(cornerRadiusAttr) +//// val cornerRadius = cornerRadiusArray.getDimension(0, 0f) // Default to 0 if not set +//// cornerRadiusArray.recycle() +// val cornerRadius = 7.dpToPx(context).toFloat() +// +// // Extract the titleTextColor attribute +// val titleTextColorAttr = intArrayOf(R.attr.titleTextColor) +// val textColorArray = theme.obtainStyledAttributes(titleTextColorAttr) +// val titleTextColor = textColorArray.getColor(0, Color.BLACK) // Default to black if not set +// textColorArray.recycle() +// +// // Apply cornerRadius and titleTextColor as needed (e.g., to a button) +// val drawable = ResourcesCompat.getDrawable( +// this.resources, +// R.drawable.rounded_ripple_background, +// theme +// ) +// +// // Assuming rounded_ripple_background is a shape drawable that supports corner radius +// if (drawable is RippleDrawable) { +// // For the mask +// val maskDrawable = drawable.findDrawableByLayerId(android.R.id.mask) as? GradientDrawable +// maskDrawable?.cornerRadius = cornerRadius +// +// // For the content shape (background) +// val contentDrawable = drawable.getDrawable(0) as? GradientDrawable +// contentDrawable?.cornerRadius = cornerRadius +// } +// +// // Apply drawable as background and set titleTextColor +// this.background = drawable +// (this as? Button)?.setTextColor(titleTextColor) +// } +//} +// +fun View.setRoundedRippleBackgroundButtonStyle(@StyleRes style: Int?, defaultCornerRadius: Int = 7) { + style?.let { + val theme = this.resources.newTheme().apply { applyStyle(style, true) } + val defaultCornerRadius = defaultCornerRadius.dpToPx(this.context) + + // Resolve the cornerRadius and rippleColor attributes from the applied style + val attrs = intArrayOf( + R.attr.backgroundColor, + R.attr.titleTextColor + ) + + val typedArray = theme.obtainStyledAttributes(style, attrs) + + // Extract the attributes from the typed array + val backgroundColor = typedArray.getColor(0, Color.TRANSPARENT) + val titleTextColor = typedArray.getColor(1, Color.BLACK) // Default to black if not set + typedArray.recycle() + + // Apply the ripple drawable from your XML + val rippleDrawable = ResourcesCompat.getDrawable( + this.resources, + R.drawable.rounded_ripple_background, // Your ripple drawable + theme + ) as? RippleDrawable + + rippleDrawable?.let { rd -> + + // Modify the background color, corner radius, and stroke in the ripple's shape + val contentDrawable = rd.getDrawable(1) as? GradientDrawable + contentDrawable?.apply { + setColor(backgroundColor) + cornerRadius = defaultCornerRadius.dpToPx(context).toFloat() + } + + // Set the ripple drawable as the background + this.background = rd + } + (this as? Button)?.setTextColor(titleTextColor) + } +} + +// Utility function to convert dp to px for stroke width +fun Int.dpToPx(context: Context): Int { + return (this * context.resources.displayMetrics.density).toInt() +} diff --git a/common/src/main/res/values/colors.xml b/common/src/main/res/values/colors.xml index 307611426c..635ba80aa9 100644 --- a/common/src/main/res/values/colors.xml +++ b/common/src/main/res/values/colors.xml @@ -122,4 +122,6 @@ #AAAEB3 #5D5F61 + #EA3943 + #0DEA3943 \ No newline at end of file diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index e58879fff7..1130033d9b 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -112,4 +112,5 @@ %d%% + %d\n%s \ No newline at end of file diff --git a/common/src/main/res/values/styles.xml b/common/src/main/res/values/styles.xml index da01902211..46f5b161d7 100644 --- a/common/src/main/res/values/styles.xml +++ b/common/src/main/res/values/styles.xml @@ -961,6 +961,87 @@ @style/PrimaryButtonTheme.Large.LightBlue + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - -