Generate QR in background thread.
- Moved the logic to generate the QR Code image from ReceiveTransactionFragment to its ViewModel (ReceiveTransactionViewModel), and used coroutines to send that process to a background thread to make sure this does not freeze the UI.
This commit is contained in:
parent
2a88821710
commit
855f47e793
2 changed files with 69 additions and 61 deletions
|
@ -1,10 +1,7 @@
|
||||||
package cy.agorise.bitsybitshareswallet.fragments
|
package cy.agorise.bitsybitshareswallet.fragments
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -17,11 +14,6 @@ import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import com.crashlytics.android.Crashlytics
|
import com.crashlytics.android.Crashlytics
|
||||||
import com.google.common.primitives.UnsignedLong
|
import com.google.common.primitives.UnsignedLong
|
||||||
import com.google.zxing.BarcodeFormat
|
|
||||||
import com.google.zxing.EncodeHintType
|
|
||||||
import com.google.zxing.MultiFormatWriter
|
|
||||||
import com.google.zxing.WriterException
|
|
||||||
import com.google.zxing.common.BitMatrix
|
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import cy.agorise.bitsybitshareswallet.R
|
import cy.agorise.bitsybitshareswallet.R
|
||||||
import cy.agorise.bitsybitshareswallet.adapters.AssetsAdapter
|
import cy.agorise.bitsybitshareswallet.adapters.AssetsAdapter
|
||||||
|
@ -43,6 +35,7 @@ import java.text.DecimalFormatSymbols
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
class ReceiveTransactionFragment : ConnectedFragment() {
|
class ReceiveTransactionFragment : ConnectedFragment() {
|
||||||
|
|
||||||
|
@ -144,6 +137,10 @@ class ReceiveTransactionFragment : ConnectedFragment() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mViewModel.qrCodeBitmap.observe(this, Observer { bitmap ->
|
||||||
|
ivQR.setImageBitmap(bitmap)
|
||||||
|
})
|
||||||
|
|
||||||
spAsset.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
|
spAsset.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
|
||||||
override fun onNothingSelected(parent: AdapterView<*>?) { }
|
override fun onNothingSelected(parent: AdapterView<*>?) { }
|
||||||
|
|
||||||
|
@ -251,7 +248,6 @@ class ReceiveTransactionFragment : ConnectedFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO use coroutines to move this process to a background thread
|
|
||||||
private fun updateQR() {
|
private fun updateQR() {
|
||||||
if (mAsset == null) {
|
if (mAsset == null) {
|
||||||
ivQR.setImageDrawable(null)
|
ivQR.setImageDrawable(null)
|
||||||
|
@ -276,64 +272,14 @@ class ReceiveTransactionFragment : ConnectedFragment() {
|
||||||
asset.symbol.replaceFirst("bit", ""), items, "", "")
|
asset.symbol.replaceFirst("bit", ""), items, "", "")
|
||||||
Log.d(TAG, "invoice: " + invoice.toJsonString())
|
Log.d(TAG, "invoice: " + invoice.toJsonString())
|
||||||
try {
|
try {
|
||||||
val bitmap = encodeAsBitmap(Invoice.toQrCode(invoice), "#139657") // PalmPay green
|
mViewModel.updateInvoice(invoice, min(ivQR.width, ivQR.height))
|
||||||
ivQR.setImageBitmap(bitmap)
|
|
||||||
updateAmountAddressUI(amount, asset.symbol, asset.precision, mUserAccount!!.name)
|
updateAmountAddressUI(amount, asset.symbol, asset.precision, mUserAccount!!.name)
|
||||||
} catch (e: WriterException) {
|
|
||||||
Log.e(TAG, "WriterException. Msg: " + e.message)
|
|
||||||
Crashlytics.logException(e)
|
|
||||||
} catch (e: NullPointerException) {
|
} catch (e: NullPointerException) {
|
||||||
Log.e(TAG, "NullPointerException. Msg: " + e.message)
|
Log.e(TAG, "NullPointerException. Msg: " + e.message)
|
||||||
Crashlytics.logException(e)
|
Crashlytics.logException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes the provided data as a QR-code. Used to provide payment requests.
|
|
||||||
* @param data: Data containing payment request data as the recipient's address and the requested amount.
|
|
||||||
* @param color: The color used for the QR-code
|
|
||||||
* @return Bitmap with the QR-code encoded data
|
|
||||||
* @throws WriterException if QR Code cannot be generated
|
|
||||||
*/
|
|
||||||
@Throws(WriterException::class)
|
|
||||||
internal fun encodeAsBitmap(data: String, color: String): Bitmap? {
|
|
||||||
val result: BitMatrix
|
|
||||||
|
|
||||||
// Get measured width and height of the ImageView where the QR code will be placed and make sure
|
|
||||||
// the final QR code has a squared shape
|
|
||||||
val w = Math.min(ivQR.width, ivQR.height)
|
|
||||||
val h = w
|
|
||||||
|
|
||||||
try {
|
|
||||||
val hints = HashMap<EncodeHintType, Any>()
|
|
||||||
hints[EncodeHintType.MARGIN] = 0
|
|
||||||
result = MultiFormatWriter().encode(
|
|
||||||
data,
|
|
||||||
BarcodeFormat.QR_CODE, w, h, hints
|
|
||||||
)
|
|
||||||
} catch (iae: IllegalArgumentException) {
|
|
||||||
// Unsupported format
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val pixels = IntArray(w * h)
|
|
||||||
for (y in 0 until h) {
|
|
||||||
val offset = y * w
|
|
||||||
for (x in 0 until w) {
|
|
||||||
pixels[offset + x] = if (result.get(x, y)) Color.parseColor(color) else Color.WHITE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
|
||||||
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
|
||||||
bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
|
|
||||||
bitmap
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
Crashlytics.logException(e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the UI to show the amount and account to send the payment
|
* Updates the UI to show the amount and account to send the payment
|
||||||
*/
|
*/
|
||||||
|
@ -369,7 +315,7 @@ class ReceiveTransactionFragment : ConnectedFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyStoragePermission() {
|
private fun verifyStoragePermission() {
|
||||||
if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
if (ContextCompat.checkSelfPermission(activity!!, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
// Permission is not already granted
|
// Permission is not already granted
|
||||||
requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||||
|
|
|
@ -1,18 +1,36 @@
|
||||||
package cy.agorise.bitsybitshareswallet.viewmodels
|
package cy.agorise.bitsybitshareswallet.viewmodels
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import com.google.zxing.EncodeHintType
|
||||||
|
import com.google.zxing.MultiFormatWriter
|
||||||
|
import com.google.zxing.WriterException
|
||||||
import cy.agorise.bitsybitshareswallet.database.entities.Asset
|
import cy.agorise.bitsybitshareswallet.database.entities.Asset
|
||||||
import cy.agorise.bitsybitshareswallet.database.entities.UserAccount
|
import cy.agorise.bitsybitshareswallet.database.entities.UserAccount
|
||||||
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
||||||
import cy.agorise.bitsybitshareswallet.repositories.UserAccountRepository
|
import cy.agorise.bitsybitshareswallet.repositories.UserAccountRepository
|
||||||
|
import cy.agorise.graphenej.Invoice
|
||||||
|
import kotlinx.coroutines.Dispatchers.Default
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
class ReceiveTransactionViewModel(application: Application) : AndroidViewModel(application) {
|
class ReceiveTransactionViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
private var mUserAccountRepository = UserAccountRepository(application)
|
private var mUserAccountRepository = UserAccountRepository(application)
|
||||||
private var mAssetRepository = AssetRepository(application)
|
private var mAssetRepository = AssetRepository(application)
|
||||||
|
|
||||||
|
private val _qrCodeBitmap = MutableLiveData<Bitmap>()
|
||||||
|
val qrCodeBitmap: LiveData<Bitmap>
|
||||||
|
get() = _qrCodeBitmap
|
||||||
|
|
||||||
internal fun getUserAccount(id: String): LiveData<UserAccount> {
|
internal fun getUserAccount(id: String): LiveData<UserAccount> {
|
||||||
return mUserAccountRepository.getUserAccount(id)
|
return mUserAccountRepository.getUserAccount(id)
|
||||||
}
|
}
|
||||||
|
@ -20,4 +38,48 @@ class ReceiveTransactionViewModel(application: Application) : AndroidViewModel(a
|
||||||
internal fun getAllNonZero(): LiveData<List<Asset>> {
|
internal fun getAllNonZero(): LiveData<List<Asset>> {
|
||||||
return mAssetRepository.getAllNonZero()
|
return mAssetRepository.getAllNonZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun updateInvoice(invoice: Invoice, size: Int) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
_qrCodeBitmap.value = encodeAsBitmap(Invoice.toQrCode(invoice), "#139657", size) // PalmPay green
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d("ReceiveTransactionVM", e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the provided data as a QR-code. Used to provide payment requests.
|
||||||
|
* @param data: Data containing payment request data as the recipient's address and the requested amount.
|
||||||
|
* @param color: The color used for the QR-code
|
||||||
|
* @param size: The size in pixels of the QR-code to generate
|
||||||
|
* @return Bitmap with the QR-code encoded data
|
||||||
|
* @throws WriterException if QR Code cannot be generated
|
||||||
|
* @throws IllegalArgumentException IllegalArgumentException
|
||||||
|
*/
|
||||||
|
@Throws(WriterException::class, IllegalArgumentException::class)
|
||||||
|
private suspend fun encodeAsBitmap(data: String, color: String, size: Int): Bitmap? =
|
||||||
|
withContext(Default) {
|
||||||
|
|
||||||
|
val hints = HashMap<EncodeHintType, Any>()
|
||||||
|
hints[EncodeHintType.MARGIN] = 0
|
||||||
|
val result = MultiFormatWriter().encode(
|
||||||
|
data,
|
||||||
|
BarcodeFormat.QR_CODE, size, size, hints
|
||||||
|
)
|
||||||
|
|
||||||
|
val pixels = IntArray(size * size)
|
||||||
|
for (y in 0 until size) {
|
||||||
|
val offset = y * size
|
||||||
|
for (x in 0 until size) {
|
||||||
|
pixels[offset + x] =
|
||||||
|
if (result.get(x, y)) Color.parseColor(color) else Color.WHITE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||||
|
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
|
||||||
|
bitmap
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue