@@ -32,15 +32,14 @@ import androidx.lifecycle.ViewModel
3232import com.getcode.view.LoadingSuccessState
3333import kotlinx.coroutines.flow.MutableStateFlow
3434import kotlinx.coroutines.flow.distinctUntilChangedBy
35- import kotlinx.coroutines.flow.filterNotNull
3635import kotlinx.coroutines.flow.launchIn
37- import kotlinx.coroutines.flow.map
3836import kotlinx.coroutines.flow.onEach
3937import kotlinx.coroutines.flow.take
4038import kotlinx.coroutines.flow.update
4139import kotlinx.coroutines.launch
4240import kotlinx.coroutines.withContext
4341import com.getcode.vendor.Base58
42+ import kotlinx.coroutines.flow.mapNotNull
4443import java.text.DateFormat
4544import java.text.SimpleDateFormat
4645import java.util.Date
@@ -72,8 +71,7 @@ abstract class BaseAccessKeyViewModel(
7271 init {
7372 userManager.state
7473 .distinctUntilChangedBy { it.entropy }
75- .map { it.entropy }
76- .filterNotNull()
74+ .mapNotNull { it.entropy }
7775 .take(1 )
7876 .onEach { initWithEntropy(it) }
7977 .launchIn(viewModelScope)
@@ -127,19 +125,28 @@ abstract class BaseAccessKeyViewModel(
127125 )
128126 }
129127
130- private val targetWidth = 1200
131- private val targetHeight = 2500
132-
133- private val logoWidth = 92.4f
134- private val logoHeight = 132
135- private val qrCodeSize = 360
136-
137- private val bgTopOffset = 550
138- private val logoTopOffset = 770
139- private val qrTopOffset = 980
140- private val keyTextTopOffset = 1600
141- private val topTextTopOffset = 200
142- private val bottomTextTopOffset = 2000
128+ // These layout dimensions are compile-time constants so they are inlined at every
129+ // use site by kotlinc. They must NOT be instance fields: createBitmapForExport runs
130+ // on dispatchers.IO and can be kicked off from the init{} flow before the constructor
131+ // finishes initializing instance fields, so a background thread could read them as 0
132+ // (their default) with no happens-before — surfacing as
133+ // "IllegalArgumentException: width and height must be > 0" on release/R8 builds only.
134+ // Refs Bugsnag 6a457e5e.
135+ private companion object {
136+ const val TARGET_WIDTH = 1200
137+ const val TARGET_HEIGHT = 2500
138+
139+ const val LOGO_WIDTH_RATIO = 92.4f
140+ const val LOGO_HEIGHT = 132
141+ const val QR_CODE_SIZE = 360
142+
143+ const val BG_TOP_OFFSET = 550
144+ const val LOGO_TOP_OFFSET = 770
145+ const val QR_TOP_OFFSET = 980
146+ const val KEY_TEXT_TOP_OFFSET = 1600
147+ const val WARNING_TEXT_TOP_OFFSET = 200
148+ const val HELPER_TEXT_TOP_OFFSET = 2000
149+ }
143150
144151 protected suspend fun saveBitmapToFile (): Result <Boolean > {
145152 uiFlow.update { it.copy(exportState = LoadingSuccessState (loading = true )) }
@@ -159,7 +166,7 @@ abstract class BaseAccessKeyViewModel(
159166 }
160167
161168 private fun renderLogo (resId : Int ): Bitmap ? =
162- resources.getDrawable(resId)?.toBitmap(logoWidth .roundToInt(), logoHeight )
169+ resources.getDrawable(resId)?.toBitmap(LOGO_WIDTH_RATIO .roundToInt(), LOGO_HEIGHT )
163170
164171 private fun createBitmapForExport (
165172 drawBackground : Boolean = true,
@@ -190,7 +197,7 @@ abstract class BaseAccessKeyViewModel(
190197 }
191198 .getOrNull()
192199
193- val imageOut = createBitmap(targetWidth, targetHeight ).applyCanvas {
200+ val imageOut = createBitmap(TARGET_WIDTH , TARGET_HEIGHT ).applyCanvas {
194201 val accessBgActualWidth =
195202 accessKeyBg.getScaledWidth(resources.displayMetrics)
196203
@@ -209,7 +216,7 @@ abstract class BaseAccessKeyViewModel(
209216 topTextChunks.forEachIndexed { index, text ->
210217 drawText(
211218 canvas = this ,
212- y = topTextTopOffset + (60 * (index + 1 )),
219+ y = WARNING_TEXT_TOP_OFFSET + (60 * (index + 1 )),
213220 sizePx = 40 ,
214221 color = Alert .toAGColor(),
215222 text = text
@@ -218,8 +225,8 @@ abstract class BaseAccessKeyViewModel(
218225
219226 drawBitmap(
220227 accessKeyBg,
221- (((targetWidth - accessBgActualWidth) / 2 )).toFloat(),
222- bgTopOffset .toFloat(),
228+ (((TARGET_WIDTH - accessBgActualWidth) / 2 )).toFloat(),
229+ BG_TOP_OFFSET .toFloat(),
223230 null
224231 )
225232
@@ -234,10 +241,10 @@ abstract class BaseAccessKeyViewModel(
234241
235242 val borderInset = spec.borderWidth / 2
236243 val borderRect = RectF (
237- ((targetWidth - accessBgActualWidth) / 2f ) + borderInset,
238- bgTopOffset .toFloat() + borderInset,
239- ((targetWidth + accessBgActualWidth) / 2f ) - borderInset,
240- bgTopOffset .toFloat() + accessKeyBg.height - borderInset
244+ ((TARGET_WIDTH - accessBgActualWidth) / 2f ) + borderInset,
245+ BG_TOP_OFFSET .toFloat() + borderInset,
246+ ((TARGET_WIDTH + accessBgActualWidth) / 2f ) - borderInset,
247+ BG_TOP_OFFSET .toFloat() + accessKeyBg.height - borderInset
241248 )
242249
243250 // Adjust corner radius to match your card
@@ -248,32 +255,32 @@ abstract class BaseAccessKeyViewModel(
248255 imageLogo?.let { logo ->
249256 drawBitmap(
250257 logo,
251- ((targetWidth - logoWidth ) / 2 ),
252- logoTopOffset .toFloat(),
258+ ((TARGET_WIDTH - LOGO_WIDTH_RATIO ) / 2 ),
259+ LOGO_TOP_OFFSET .toFloat(),
253260 null
254261 )
255262 }
256263
257264 getQrCode(entropyB64)?.let { bitmap ->
258265 drawBitmap(
259266 bitmap,
260- ((targetWidth - qrCodeSize ) / 2 ).toFloat(),
261- qrTopOffset .toFloat(),
267+ ((TARGET_WIDTH - QR_CODE_SIZE ) / 2 ).toFloat(),
268+ QR_TOP_OFFSET .toFloat(),
262269 null
263270 )
264271 }
265272
266273 drawText(
267274 canvas = this ,
268- y = keyTextTopOffset ,
275+ y = KEY_TEXT_TOP_OFFSET ,
269276 sizePx = 32 ,
270277 color = White .toAGColor(),
271278 text = accessKeyText[0 ]
272279 )
273280
274281 drawText(
275282 canvas = this ,
276- y = keyTextTopOffset + 40 ,
283+ y = KEY_TEXT_TOP_OFFSET + 40 ,
277284 sizePx = 32 ,
278285 color = White .toAGColor(),
279286 text = accessKeyText[1 ]
@@ -287,7 +294,7 @@ abstract class BaseAccessKeyViewModel(
287294 bottomTextChunks.forEachIndexed { index, text ->
288295 drawText(
289296 canvas = this ,
290- y = bottomTextTopOffset + (60 * (index + 1 )),
297+ y = HELPER_TEXT_TOP_OFFSET + (60 * (index + 1 )),
291298 sizePx = 40 ,
292299 color = White .toAGColor(),
293300 text = text
@@ -335,7 +342,7 @@ abstract class BaseAccessKeyViewModel(
335342 val base58 = Base58 .encode(entropyB64.decodeBase64())
336343 val url = " ${resources.getString(R .string.app_root_url)} /login?data=$base58 "
337344
338- return qrCodeGenerator.generate(url, qrCodeSize )
345+ return qrCodeGenerator.generate(url, QR_CODE_SIZE )
339346 }
340347
341348 private fun drawText (
@@ -356,7 +363,7 @@ abstract class BaseAccessKeyViewModel(
356363
357364 val bounds1 = android.graphics.Rect ()
358365 textPaint.getTextBounds(text, 0 , text.length, bounds1)
359- val xV: Int = x ? : ((targetWidth - bounds1.width()) / 2 )
366+ val xV: Int = x ? : ((TARGET_WIDTH - bounds1.width()) / 2 )
360367 canvas.drawText(text, xV.toFloat(), y.toFloat(), textPaint)
361368 }
362369
0 commit comments