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
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Color
import android.os.Bundle
import android.preference.PreferenceManager
import android.util.Log
@ -17,11 +14,6 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.crashlytics.android.Crashlytics
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 cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.adapters.AssetsAdapter
@ -43,6 +35,7 @@ import java.text.DecimalFormatSymbols
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
import kotlin.math.min
class ReceiveTransactionFragment : ConnectedFragment() {
@ -144,6 +137,10 @@ class ReceiveTransactionFragment : ConnectedFragment() {
}
})
mViewModel.qrCodeBitmap.observe(this, Observer { bitmap ->
ivQR.setImageBitmap(bitmap)
})
spAsset.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
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() {
if (mAsset == null) {
ivQR.setImageDrawable(null)
@ -276,64 +272,14 @@ class ReceiveTransactionFragment : ConnectedFragment() {
asset.symbol.replaceFirst("bit", ""), items, "", "")
Log.d(TAG, "invoice: " + invoice.toJsonString())
try {
val bitmap = encodeAsBitmap(Invoice.toQrCode(invoice), "#139657") // PalmPay green
ivQR.setImageBitmap(bitmap)
mViewModel.updateInvoice(invoice, min(ivQR.width, ivQR.height))
updateAmountAddressUI(amount, asset.symbol, asset.precision, mUserAccount!!.name)
} catch (e: WriterException) {
Log.e(TAG, "WriterException. Msg: " + e.message)
Crashlytics.logException(e)
} catch (e: NullPointerException) {
Log.e(TAG, "NullPointerException. Msg: " + e.message)
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
*/
@ -369,7 +315,7 @@ class ReceiveTransactionFragment : ConnectedFragment() {
}
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) {
// Permission is not already granted
requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE),

View file

@ -1,18 +1,36 @@
package cy.agorise.bitsybitshareswallet.viewmodels
import android.app.Application
import android.graphics.Bitmap
import android.graphics.Color
import android.util.Log
import androidx.lifecycle.AndroidViewModel
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.UserAccount
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
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) {
private var mUserAccountRepository = UserAccountRepository(application)
private var mAssetRepository = AssetRepository(application)
private val _qrCodeBitmap = MutableLiveData<Bitmap>()
val qrCodeBitmap: LiveData<Bitmap>
get() = _qrCodeBitmap
internal fun getUserAccount(id: String): LiveData<UserAccount> {
return mUserAccountRepository.getUserAccount(id)
}
@ -20,4 +38,48 @@ class ReceiveTransactionViewModel(application: Application) : AndroidViewModel(a
internal fun getAllNonZero(): LiveData<List<Asset>> {
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
}
}