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:
Severiano Jaramillo 2019-08-27 13:47:25 -05:00
parent 2a88821710
commit 855f47e793
2 changed files with 69 additions and 61 deletions

View file

@ -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),

View file

@ -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
}
} }