From 479ccd8ad2400d07501c411be202c50dfd8aaecf Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Wed, 20 Dec 2023 12:48:42 -0800 Subject: [PATCH] fix(dashpay): update image permissions for Android 13 and fix camera (#1235) * fix: update permissions for images on Android 13, refactor to use ActivityResultLauncher * fix(dashpay): show the Join DashPay item on the home screen * fix(dashpay): rename Username Voting Period Active --- wallet/AndroidManifest.xml | 3 +- wallet/build.gradle | 2 +- wallet/res/layout/activity_settings.xml | 2 +- wallet/res/values/strings.xml | 2 +- .../wallet/service/BlockchainServiceImpl.java | 2 +- .../wallet/service/CoinJoinService.kt | 66 +++--- .../wallet/ui/EditProfileActivity.kt | 221 +++++++++--------- .../schildbach/wallet/ui/SettingsViewModel.kt | 11 +- .../wallet/ui/dashpay/CropImageActivity.kt | 1 + .../wallet/ui/dashpay/HistoryHeaderAdapter.kt | 2 +- .../wallet/ui/main/MainViewModel.kt | 6 +- .../wallet/ui/main/WalletFragment.kt | 4 +- 12 files changed, 174 insertions(+), 148 deletions(-) diff --git a/wallet/AndroidManifest.xml b/wallet/AndroidManifest.xml index b28639f407..e5e632ee65 100644 --- a/wallet/AndroidManifest.xml +++ b/wallet/AndroidManifest.xml @@ -17,7 +17,8 @@ - + + diff --git a/wallet/build.gradle b/wallet/build.gradle index a234c6092d..354e2efc4f 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -214,7 +214,7 @@ android { compileSdk 33 minSdkVersion 23 targetSdkVersion 33 - versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 80133 + versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 90000 versionName project.hasProperty('versionName') ? project.property('versionName') : "5.3-dashpay" multiDexEnabled true generatedDensities = ['hdpi', 'xhdpi'] diff --git a/wallet/res/layout/activity_settings.xml b/wallet/res/layout/activity_settings.xml index b127014eba..ed5f89858c 100644 --- a/wallet/res/layout/activity_settings.xml +++ b/wallet/res/layout/activity_settings.xml @@ -161,7 +161,7 @@ Quick Voting By tapping the "Vote for All" button, you will automatically vote for all of the filtered usernames (%d) that were submitted first Vote for All - Voting Dash Pay + Username Voting Period Active There was a network error, you can try again at no extra cost Requested · Voting: 1 Mar – 15 Mar Cancel Request diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java index b4227afc98..7d12ea9564 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java @@ -1025,7 +1025,7 @@ void initViewModel() { blockchainStateDao.load().observe(this, (blockchainState) -> handleBlockchainStateNotification(blockchainState, mixingStatus)); registerCrowdNodeConfirmedAddressFilter(); - FlowExtKt.observe(coinJoinService.getMixingState(), this, (mixingStatus, continuation) -> { + FlowExtKt.observe(coinJoinService.observeMixingState(), this, (mixingStatus, continuation) -> { handleBlockchainStateNotification(blockchainState, mixingStatus); return null; }); diff --git a/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt b/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt index 9e81dac383..c32e5da60a 100644 --- a/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt +++ b/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt @@ -43,11 +43,11 @@ import org.bitcoinj.coinjoin.PoolStatus import org.bitcoinj.coinjoin.callbacks.RequestDecryptedKey import org.bitcoinj.coinjoin.callbacks.RequestKeyParameter import org.bitcoinj.coinjoin.listeners.CoinJoinTransactionListener -import org.bitcoinj.coinjoin.utils.CoinJoinTransactionType import org.bitcoinj.coinjoin.listeners.MixingCompleteListener import org.bitcoinj.coinjoin.listeners.SessionCompleteListener import org.bitcoinj.coinjoin.progress.MixingProgressTracker import org.bitcoinj.coinjoin.utils.CoinJoinManager +import org.bitcoinj.coinjoin.utils.CoinJoinTransactionType import org.bitcoinj.core.AbstractBlockChain import org.bitcoinj.core.Coin import org.bitcoinj.core.Context @@ -81,9 +81,9 @@ enum class CoinJoinMode { * Monitor the status of the CoinJoin Mixing Service */ interface CoinJoinService { - val mixingStatus: MixingStatus - val mixingState: Flow - val mixingProgress: Flow + suspend fun getMixingState(): MixingStatus + fun observeMixingState(): Flow + fun observeMixingProgress(): Flow } enum class MixingStatus { @@ -122,11 +122,11 @@ class CoinJoinMixingService @Inject constructor( private var sessionCompleteListeners: ArrayList = arrayListOf() var mode: CoinJoinMode = CoinJoinMode.NONE - override var mixingStatus: MixingStatus = MixingStatus.NOT_STARTED - private set - val _mixingState = MutableStateFlow(MixingStatus.NOT_STARTED) - override val mixingState: Flow - get() = _mixingState + private val _mixingState = MutableStateFlow(MixingStatus.NOT_STARTED) + private val _progressFlow = MutableStateFlow(0.00) + + override suspend fun getMixingState(): MixingStatus = _mixingState.value + override fun observeMixingState(): Flow = _mixingState private val coroutineScope = CoroutineScope( Executors.newFixedThreadPool(2, ContextPropagatingThreadFactory("coinjoin-pool")).asCoroutineDispatcher() @@ -138,6 +138,7 @@ class CoinJoinMixingService @Inject constructor( private val isBlockChainSet: Boolean get() = blockChain != null private var networkStatus: NetworkStatus = NetworkStatus.UNKNOWN + private var isSynced = false private var hasAnonymizableBalance: Boolean = false // https://stackoverflow.com/questions/55421710/how-to-suspend-kotlin-coroutine-until-notified @@ -145,9 +146,7 @@ class CoinJoinMixingService @Inject constructor( private val updateMixingStateMutex = Mutex(locked = false) private var exception: Throwable? = null - override val mixingProgress: Flow - get() = _progressFlow - private val _progressFlow = MutableStateFlow(0.00) + override fun observeMixingProgress(): Flow = _progressFlow init { blockchainStateProvider.observeNetworkStatus() @@ -167,7 +166,12 @@ class CoinJoinMixingService @Inject constructor( .filterNotNull() .distinctUntilChanged() .onEach { blockChainState -> - if (blockChainState.isSynced()) { + val isSynced = blockChainState.isSynced() + if (isSynced != this.isSynced) { + updateState(config.getMode(), hasAnonymizableBalance, networkStatus, isSynced, blockChain) + } + // this will trigger mixing as new blocks are mined and received tx's are confirmed + if (isSynced) { log.info("coinjoin: new block: ${blockChainState.bestChainHeight}") updateBalance(walletDataProvider.getWalletBalance()) } @@ -204,24 +208,34 @@ class CoinJoinMixingService @Inject constructor( val hasAnonymizableBalance = anonBalance.isGreaterThan(CoinJoin.getSmallestDenomination()) log.info("coinjoin: mixing can occur: $hasAnonymizableBalance") - updateState(config.getMode(), hasAnonymizableBalance, networkStatus, blockchainStateProvider.getBlockChain()) + updateState( + config.getMode(), + hasAnonymizableBalance, + networkStatus, + isSynced, + blockchainStateProvider.getBlockChain() + ) } private suspend fun updateState( mode: CoinJoinMode, hasAnonymizableBalance: Boolean, networkStatus: NetworkStatus, + isSynced: Boolean, blockChain: AbstractBlockChain? ) { updateMutex.lock() - log.info("coinjoin-updateState: ${this.mode}, ${this.hasAnonymizableBalance}, ${this.networkStatus}, ${blockChain != null}") + log.info( + "coinjoin-updateState: ${this.mode}, ${this.hasAnonymizableBalance}, ${this.networkStatus}, ${this.isSynced} ${blockChain != null}" + ) try { setBlockchain(blockChain) - log.info("coinjoin-updateState: $mode, $hasAnonymizableBalance, $networkStatus, ${blockChain != null}") + log.info( + "coinjoin-updateState: $mode, $hasAnonymizableBalance, $networkStatus, $isSynced, ${blockChain != null}" + ) this.networkStatus = networkStatus - this.mixingStatus = mixingStatus - _mixingState.value = mixingStatus this.hasAnonymizableBalance = hasAnonymizableBalance + this.isSynced = isSynced this.mode = mode if (mode == CoinJoinMode.NONE) { @@ -229,7 +243,7 @@ class CoinJoinMixingService @Inject constructor( } else { configureMixing() if (hasAnonymizableBalance) { - if (networkStatus == NetworkStatus.CONNECTED && isBlockChainSet) { + if (networkStatus == NetworkStatus.CONNECTED && isBlockChainSet && isSynced) { updateMixingState(MixingStatus.MIXING) } else { updateMixingState(MixingStatus.PAUSED) @@ -250,9 +264,8 @@ class CoinJoinMixingService @Inject constructor( ) { updateMixingStateMutex.lock() try { - - val previousMixingStatus = this.mixingStatus - this.mixingStatus = mixingStatus + val previousMixingStatus = _mixingState.value + _mixingState.value = mixingStatus log.info("coinjoin-updateMixingState: $previousMixingStatus -> $mixingStatus") when { @@ -273,11 +286,11 @@ class CoinJoinMixingService @Inject constructor( } private suspend fun updateBlockChain(blockChain: AbstractBlockChain?) { - updateState(mode, hasAnonymizableBalance, networkStatus, blockChain) + updateState(mode, hasAnonymizableBalance, networkStatus, isSynced, blockChain) } private suspend fun updateNetworkStatus(networkStatus: NetworkStatus) { - updateState(mode, hasAnonymizableBalance, networkStatus, blockChain) + updateState(mode, hasAnonymizableBalance, networkStatus, isSynced, blockChain) } private suspend fun updateMode(mode: CoinJoinMode) { @@ -286,7 +299,7 @@ class CoinJoinMixingService @Inject constructor( configureMixing() updateBalance(walletDataProvider.wallet!!.getBalance(Wallet.BalanceType.AVAILABLE)) } - updateState(mode, hasAnonymizableBalance, networkStatus, blockChain) + updateState(mode, hasAnonymizableBalance, networkStatus, isSynced, blockChain) } private var mixingProgressTracker: MixingProgressTracker = object : MixingProgressTracker() { @@ -347,6 +360,7 @@ class CoinJoinMixingService @Inject constructor( private fun configureMixing() { configureMixing(walletDataProvider.getWalletBalance()) } + /** set CoinJoinClientOptions based on CoinJoinMode */ private fun configureMixing(amount: Coin) { when (mode) { @@ -389,7 +403,7 @@ class CoinJoinMixingService @Inject constructor( clientManager = CoinJoinClientManager(wallet) coinJoinClientManagers[wallet.description] = clientManager // this allows mixing to wait for the last transaction to be confirmed - //clientManager.addContinueMixingOnError(PoolStatus.ERR_NO_INPUTS) + // clientManager.addContinueMixingOnError(PoolStatus.ERR_NO_INPUTS) // wait until the masternode sync system fixes itself clientManager.addContinueMixingOnError(PoolStatus.ERR_NO_MASTERNODES_DETECTED) clientManager.setStopOnNothingToDo(true) diff --git a/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt b/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt index 33ba6ba885..7c2eaf2ffb 100644 --- a/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/EditProfileActivity.kt @@ -17,10 +17,11 @@ package de.schildbach.wallet.ui import android.Manifest +import android.Manifest.permission.READ_EXTERNAL_STORAGE +import android.Manifest.permission.READ_MEDIA_IMAGES import android.app.Activity -import android.content.ClipData import android.content.Intent -import android.content.pm.PackageManager +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.database.Cursor import android.graphics.Bitmap import android.graphics.BitmapFactory @@ -34,8 +35,10 @@ import android.text.TextWatcher import android.view.View import android.view.WindowManager import android.widget.Toast +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels -import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.net.toUri @@ -56,12 +59,12 @@ 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 org.dash.wallet.common.ui.avatar.ProfilePictureHelper import de.schildbach.wallet.ui.dashpay.work.UpdateProfileError import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.ActivityEditProfileBinding import org.bitcoinj.core.Sha256Hash import org.dash.wallet.common.ui.avatar.ProfilePictureDisplay +import org.dash.wallet.common.ui.avatar.ProfilePictureHelper import org.dash.wallet.common.ui.avatar.ProfilePictureTransformation import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.slf4j.LoggerFactory @@ -71,19 +74,12 @@ import java.io.IOException import java.io.InputStream import java.math.BigInteger + @AndroidEntryPoint class EditProfileActivity : LockScreenActivity() { companion object { - const val REQUEST_CODE_URI = 0 - const val REQUEST_CODE_IMAGE = 1 - const val REQUEST_CODE_CHOOSE_PICTURE_PERMISSION = 2 - const val REQUEST_CODE_TAKE_PICTURE_PERMISSION = 3 - const val REQUEST_CODE_CROP_IMAGE = 4 - const val REQUEST_CODE_GOOGLE_DRIVE_SIGN_IN = 5 - - protected val log = LoggerFactory.getLogger(EditProfileActivity::class.java) - + private val log = LoggerFactory.getLogger(EditProfileActivity::class.java) } private lateinit var binding: ActivityEditProfileBinding @@ -96,6 +92,12 @@ class EditProfileActivity : LockScreenActivity() { private var profilePictureChanged = false private var uploadProfilePictureStateDialog: UploadProfilePictureStateDialog? = null + private lateinit var takePicturePermissionLauncher: ActivityResultLauncher + private lateinit var takePictureLauncher: ActivityResultLauncher + private lateinit var choosePicturePermissionLauncher: ActivityResultLauncher + private lateinit var chooseImageLauncher: ActivityResultLauncher + private lateinit var cropImageLauncher: ActivityResultLauncher + private lateinit var googleDriveSignInLauncher: ActivityResultLauncher private var mDrive: Drive? = null private var showSaveReminderDialog = false @@ -207,6 +209,81 @@ class EditProfileActivity : LockScreenActivity() { imitateUserInteraction() cropProfilePicture() } + + takePicturePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean -> + if (isGranted) { + takePicture() + } else { + // Handle the case where permission is denied + } + } + + takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) { isSuccess: Boolean -> + if (isSuccess) { + editProfileViewModel.onTmpPictureReadyForEditEvent.postValue(editProfileViewModel.tmpPictureFile) + } + turnOnAutoLogout() + } + + choosePicturePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean -> + if (isGranted) { + choosePicture() + } else { + // Handle the case where permission is denied + } + } + // Initialize the launchers + chooseImageLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { selectedImage: Uri? -> + // Handle the picked image + if (selectedImage != null) { + // your code to handle the image + turnOnAutoLogout() + + @Suppress("DEPRECATION") + val filePathColumn = arrayOf(MediaStore.Images.Media.DATA) + val cursor: Cursor? = contentResolver.query( + selectedImage, + filePathColumn, null, null, null + ) + if (cursor != null) { + cursor.moveToFirst() + val columnIndex: Int = cursor.getColumnIndex(filePathColumn[0]) + val picturePath: String? = cursor.getString(columnIndex) + if (picturePath != null) { + editProfileViewModel.saveAsProfilePictureTmp(picturePath) + } else { + saveImageWithAuthority(selectedImage) + } + } + } + } + + cropImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode == Activity.RESULT_OK) { + if (externalUrlSharedViewModel.externalUrl != null) { + saveUrl(CropImageActivity.extractZoomedRect(result.data!!)) + } else { + showProfilePictureServiceDialog() + } + } else if (result.resultCode == Activity.RESULT_CANCELED) { + // if crop was canceled, then return the externalUrl to its original state + if (externalUrlSharedViewModel.externalUrl != null) { + externalUrlSharedViewModel.externalUrl = + if (editProfileViewModel.dashPayProfile.value!!.avatarUrl == "") { + null + } else { + Uri.parse(editProfileViewModel.dashPayProfile.value!!.avatarUrl) + } + } + } + } + + googleDriveSignInLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode == Activity.RESULT_OK) { + // Handle Google Drive Sign In Result + handleGdriveSigninResult(result.data!!) + } + } } private fun pictureFromGravatar() { @@ -373,18 +450,28 @@ class EditProfileActivity : LockScreenActivity() { } private fun takePictureWithPermission() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PERMISSION_GRANTED) { takePicture() } else { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_TAKE_PICTURE_PERMISSION) + takePicturePermissionLauncher.launch(Manifest.permission.CAMERA) } } private fun choosePictureWithPermission() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + // TODO: see https://android-developers.googleblog.com/2023/08/choosing-right-storage-experience.html + // for android 14 changes + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && + (ContextCompat.checkSelfPermission(this, READ_MEDIA_IMAGES) == PERMISSION_GRANTED) + ) { + // Full access on Android 13+ + choosePicture() + } else if (ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED) { + // Full access up to Android 12 choosePicture() } else { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE_CHOOSE_PICTURE_PERMISSION) + // Access denied, so ask for permission + val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) READ_MEDIA_IMAGES else READ_EXTERNAL_STORAGE + choosePicturePermissionLauncher.launch(permission) } } @@ -395,9 +482,8 @@ class EditProfileActivity : LockScreenActivity() { private fun choosePicture() { if (editProfileViewModel.createTmpPictureFile()) { - val pickPhoto = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) turnOffAutoLogout() - startActivityForResult(pickPhoto, REQUEST_CODE_URI) + chooseImageLauncher.launch("image/*") } else { Toast.makeText(this, "Unable to create temporary file", Toast.LENGTH_LONG).show() } @@ -413,74 +499,8 @@ class EditProfileActivity : LockScreenActivity() { } private fun dispatchTakePictureIntent() { - Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> - // Ensure that there's a camera activity to handle the intent - takePictureIntent.resolveActivity(packageManager)?.also { - val tmpFileUri = getFileUri(editProfileViewModel.tmpPictureFile) - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, tmpFileUri) - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) { - // to avoid 'SecurityException: Permission Denial' on KitKat - takePictureIntent.clipData = ClipData.newRawUri("", tmpFileUri) - takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - startActivityForResult(takePictureIntent, REQUEST_CODE_IMAGE) - } - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - REQUEST_CODE_IMAGE -> { - turnOnAutoLogout() - if (resultCode == RESULT_OK) { - // picture saved in editProfileViewModel.profilePictureTmpFile - editProfileViewModel.onTmpPictureReadyForEditEvent.postValue(editProfileViewModel.tmpPictureFile) - } - } - REQUEST_CODE_URI -> { - turnOnAutoLogout() - if (resultCode == RESULT_OK && data != null) { - val selectedImage: Uri? = data.data - if (selectedImage != null) { - @Suppress("DEPRECATION") - val filePathColumn = arrayOf(MediaStore.Images.Media.DATA) - val cursor: Cursor? = contentResolver.query(selectedImage, - filePathColumn, null, null, null) - if (cursor != null) { - cursor.moveToFirst() - val columnIndex: Int = cursor.getColumnIndex(filePathColumn[0]) - val picturePath: String? = cursor.getString(columnIndex) - if (picturePath != null) { - editProfileViewModel.saveAsProfilePictureTmp(picturePath) - } else { - saveImageWithAuthority(selectedImage) - } - } - } - } - } - REQUEST_CODE_CROP_IMAGE -> { - if (resultCode == Activity.RESULT_OK) { - if (externalUrlSharedViewModel.externalUrl != null) { - saveUrl(CropImageActivity.extractZoomedRect(data!!)) - } else { - showProfilePictureServiceDialog() - } - } else if (resultCode == Activity.RESULT_CANCELED) { - // if crop was canceled, then return the externalUrl to its original state - if (externalUrlSharedViewModel.externalUrl != null) - externalUrlSharedViewModel.externalUrl = if (editProfileViewModel.dashPayProfile.value!!.avatarUrl == "") { - null - } else { - Uri.parse(editProfileViewModel.dashPayProfile.value!!.avatarUrl) - } - } - } - REQUEST_CODE_GOOGLE_DRIVE_SIGN_IN -> { - handleGdriveSigninResult(data!!) - } - } + val tmpFileUri = getFileUri(editProfileViewModel.tmpPictureFile) + takePictureLauncher.launch(tmpFileUri) } private fun saveImageWithAuthority(uri: Uri) { @@ -540,33 +560,18 @@ class EditProfileActivity : LockScreenActivity() { private fun cropProfilePicture() { if (externalUrlSharedViewModel.shouldCrop) { val tmpPictureUri = editProfileViewModel.tmpPictureFile.toUri() + val profilePictureUri = editProfileViewModel.profilePictureFile!!.toUri() val initZoomedRect = ProfilePictureHelper.extractZoomedRect(externalUrlSharedViewModel.externalUrl) val intent = CropImageActivity.createIntent(this, tmpPictureUri, profilePictureUri, initZoomedRect) - startActivityForResult(intent, REQUEST_CODE_CROP_IMAGE) + + // Use the launcher to start the activity + cropImageLauncher.launch(intent) } else { saveUrl(RectF(0.0f, 0.0f, 1.0f, 1.0f)) } } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - when (requestCode) { - REQUEST_CODE_TAKE_PICTURE_PERMISSION -> { - when { - grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED -> takePicture() - else -> takePictureWithPermission() - } - } - REQUEST_CODE_CHOOSE_PICTURE_PERMISSION -> { - when { - grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED -> choosePicture() - else -> choosePictureWithPermission() - } - } - else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - } - private fun getFileUri(file: File): Uri { return FileProvider.getUriForFile(walletApplication, "${walletApplication.packageName}.file_attachment", file) } @@ -592,10 +597,10 @@ class EditProfileActivity : LockScreenActivity() { val signInAccount = GoogleDriveService.getSigninAccount(applicationContext) val googleSignInClient = GoogleSignIn.getClient(this, GoogleDriveService.getGoogleSigninOptions()) if (signInAccount == null) { - startActivityForResult(googleSignInClient.signInIntent, REQUEST_CODE_GOOGLE_DRIVE_SIGN_IN) + googleDriveSignInLauncher.launch(googleSignInClient.signInIntent) } else { googleSignInClient.revokeAccess() - .addOnSuccessListener { startActivityForResult(googleSignInClient.signInIntent, REQUEST_CODE_GOOGLE_DRIVE_SIGN_IN) } + .addOnSuccessListener { googleDriveSignInLauncher.launch(googleSignInClient.signInIntent) } .addOnFailureListener { e: java.lang.Exception? -> log.error("could not revoke access to drive: ", e) applyGdriveAccessDenied() diff --git a/wallet/src/de/schildbach/wallet/ui/SettingsViewModel.kt b/wallet/src/de/schildbach/wallet/ui/SettingsViewModel.kt index 47fc6c4da8..66d4e394d3 100644 --- a/wallet/src/de/schildbach/wallet/ui/SettingsViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/SettingsViewModel.kt @@ -8,8 +8,9 @@ import de.schildbach.wallet.service.CoinJoinMode import de.schildbach.wallet.service.CoinJoinService import de.schildbach.wallet.service.MixingStatus import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import org.bitcoinj.utils.MonetaryFormat import org.bitcoinj.wallet.Wallet import org.bitcoinj.wallet.WalletEx import org.dash.wallet.common.WalletDataProvider @@ -29,8 +30,12 @@ class SettingsViewModel @Inject constructor( val coinJoinMixingMode: Flow get() = coinJoinConfig.observeMode() - val coinJoinMixingStatus: MixingStatus - get() = coinJoinService.mixingStatus + var coinJoinMixingStatus: MixingStatus = MixingStatus.NOT_STARTED + init { + coinJoinService.observeMixingState() + .onEach { coinJoinMixingStatus = it } + .launchIn(viewModelScope) + } var decimalFormat: DecimalFormat = DecimalFormat("0.000") val walletBalance: String diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/CropImageActivity.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/CropImageActivity.kt index 1680ebe038..36d1c28efc 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/CropImageActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/CropImageActivity.kt @@ -73,6 +73,7 @@ class CropImageActivity : LockScreenActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityCropImageBinding.inflate(layoutInflater) + setContentView(binding.root) val tempFile = intent.getParcelableExtra(TEMP_FILE) val destinationFile = intent.getParcelableExtra(DESTINATION_FILE) diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/HistoryHeaderAdapter.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/HistoryHeaderAdapter.kt index 44f84fbf4f..43b31b8d88 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/HistoryHeaderAdapter.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/HistoryHeaderAdapter.kt @@ -199,6 +199,6 @@ class HistoryHeaderAdapter( private fun shouldShowJoinDashPay(canJoin: Boolean): Boolean { val hideJoinDashPay = preferences.getBoolean(PREFS_KEY_HIDE_JOIN_DASHPAY_CARD, false) - return blockchainIdentityData == null && canJoin && !hideJoinDashPay + return blockchainIdentityData?.creationState == BlockchainIdentityData.CreationState.NONE && canJoin && !hideJoinDashPay } } \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt index 9ec6f13a32..2d9f5e3922 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt @@ -101,8 +101,6 @@ import org.dash.wallet.common.util.head import org.dash.wallet.common.util.toBigDecimal import org.dash.wallet.integrations.crowdnode.transactions.FullCrowdNodeSignUpTxSet import org.slf4j.LoggerFactory -import java.math.MathContext -import java.math.RoundingMode import java.text.DecimalFormat import java.util.Currency import java.util.Locale @@ -210,9 +208,9 @@ class MainViewModel @Inject constructor( val coinJoinMode: Flow get() = coinJoinConfig.observeMode() val mixingState: Flow - get() = coinJoinService.mixingState + get() = coinJoinService.observeMixingState() val mixingProgress: Flow - get() = coinJoinService.mixingProgress + get() = coinJoinService.observeMixingProgress() var decimalFormat: DecimalFormat = DecimalFormat("0.000") val walletBalance: String diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt b/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt index 00110356a1..8b7a904b94 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt @@ -172,7 +172,9 @@ class WalletFragment : Fragment(R.layout.home_content) { viewModel.mixingState.observe(viewLifecycleOwner) { mixingState -> mixingBinding.root.isVisible = when (mixingState) { - MixingStatus.NOT_STARTED, MixingStatus.FINISHED -> false + MixingStatus.NOT_STARTED, + MixingStatus.ERROR, + MixingStatus.FINISHED -> false else -> true } }