@@ -58,8 +58,11 @@ import kotlinx.coroutines.CoroutineScope
5858import kotlinx.coroutines.Dispatchers
5959import kotlinx.coroutines.delay
6060import kotlinx.coroutines.launch
61+ import kotlinx.coroutines.suspendCancellableCoroutine
62+ import kotlinx.coroutines.withTimeoutOrNull
6163import java.io.ByteArrayOutputStream
6264import kotlin.Exception
65+ import kotlin.coroutines.resume
6366
6467@OptIn(
6568 ReactNativeSdkInternal ::class ,
@@ -140,28 +143,42 @@ class PaymentSheetManager(
140143
141144 val paymentOptionCallback =
142145 PaymentOptionResultCallback { paymentOptionResult ->
143- val result =
144- paymentOptionResult.paymentOption?.let {
145- val bitmap = getBitmapFromDrawable(it.icon())
146- val imageString = getBase64FromBitmap(bitmap)
146+ paymentOptionResult.paymentOption?.let { paymentOption ->
147+ // Convert drawable to bitmap asynchronously to avoid shared state issues
148+ CoroutineScope (Dispatchers .Default ).launch {
149+ val imageString =
150+ try {
151+ convertDrawableToBase64(paymentOption.icon())
152+ } catch (e: Exception ) {
153+ val result =
154+ createError(
155+ PaymentSheetErrorType .Failed .toString(),
156+ " Failed to process payment option image: ${e.message} " ,
157+ )
158+ resolvePresentPromise(result)
159+ return @launch
160+ }
161+
147162 val option: WritableMap = Arguments .createMap()
148- option.putString(" label" , it .label)
163+ option.putString(" label" , paymentOption .label)
149164 option.putString(" image" , imageString)
150165 val additionalFields: Map <String , Any > = mapOf (" didCancel" to paymentOptionResult.didCancel)
151- createResult(" paymentOption" , option, additionalFields)
166+ val result = createResult(" paymentOption" , option, additionalFields)
167+ resolvePresentPromise(result)
152168 }
153- ? : run {
154- if (paymentSheetTimedOut) {
155- paymentSheetTimedOut = false
156- createError( PaymentSheetErrorType . Timeout .toString(), " The payment has timed out " )
157- } else {
158- createError(
159- PaymentSheetErrorType . Canceled .toString(),
160- " The payment option selection flow has been canceled " ,
161- )
162- }
169+ } ? : run {
170+ val result =
171+ if ( paymentSheetTimedOut) {
172+ paymentSheetTimedOut = false
173+ createError( PaymentSheetErrorType . Timeout .toString(), " The payment has timed out " )
174+ } else {
175+ createError(
176+ PaymentSheetErrorType . Canceled .toString() ,
177+ " The payment option selection flow has been canceled " ,
178+ )
163179 }
164- resolvePresentPromise(result)
180+ resolvePresentPromise(result)
181+ }
165182 }
166183
167184 val paymentResultCallback =
@@ -413,16 +430,31 @@ class PaymentSheetManager(
413430 private fun configureFlowController () {
414431 val onFlowControllerConfigure =
415432 PaymentSheet .FlowController .ConfigCallback { _, _ ->
416- val result =
417- flowController?.getPaymentOption()?.let {
418- val bitmap = getBitmapFromDrawable(it.icon())
419- val imageString = getBase64FromBitmap(bitmap)
433+ flowController?.getPaymentOption()?.let { paymentOption ->
434+ // Launch async job to convert drawable, but resolve promise synchronously
435+ CoroutineScope (Dispatchers .Default ).launch {
436+ val imageString =
437+ try {
438+ convertDrawableToBase64(paymentOption.icon())
439+ } catch (e: Exception ) {
440+ val result =
441+ createError(
442+ PaymentSheetErrorType .Failed .toString(),
443+ " Failed to process payment option image: ${e.message} " ,
444+ )
445+ initPromise.resolve(result)
446+ return @launch
447+ }
448+
420449 val option: WritableMap = Arguments .createMap()
421- option.putString(" label" , it .label)
450+ option.putString(" label" , paymentOption .label)
422451 option.putString(" image" , imageString)
423- createResult(" paymentOption" , option)
424- } ? : run { Arguments .createMap() }
425- initPromise.resolve(result)
452+ val result = createResult(" paymentOption" , option)
453+ initPromise.resolve(result)
454+ }
455+ } ? : run {
456+ initPromise.resolve(Arguments .createMap())
457+ }
426458 }
427459
428460 if (! paymentIntentClientSecret.isNullOrEmpty()) {
@@ -550,17 +582,86 @@ class PaymentSheetManager(
550582 }
551583}
552584
585+ suspend fun waitForDrawableToLoad (
586+ drawable : Drawable ,
587+ timeoutMs : Long = 3000,
588+ ): Drawable {
589+ // If already loaded, return immediately
590+ if (drawable.intrinsicWidth > 1 && drawable.intrinsicHeight > 1 ) {
591+ return drawable
592+ }
593+
594+ // Use callback to be notified when drawable finishes loading
595+ return withTimeoutOrNull(timeoutMs) {
596+ suspendCancellableCoroutine { continuation ->
597+ val callback =
598+ object : Drawable .Callback {
599+ override fun invalidateDrawable (who : Drawable ) {
600+ // Drawable has changed/loaded - check if it's ready now
601+ if (who.intrinsicWidth > 1 && who.intrinsicHeight > 1 ) {
602+ who.callback = null // Remove callback
603+ if (continuation.isActive) {
604+ continuation.resume(who)
605+ }
606+ }
607+ }
608+
609+ override fun scheduleDrawable (
610+ who : Drawable ,
611+ what : Runnable ,
612+ `when `: Long ,
613+ ) {}
614+
615+ override fun unscheduleDrawable (
616+ who : Drawable ,
617+ what : Runnable ,
618+ ) {}
619+ }
620+
621+ drawable.callback = callback
622+
623+ // Trigger an invalidation to check if it loads immediately
624+ drawable.invalidateSelf()
625+
626+ continuation.invokeOnCancellation { drawable.callback = null }
627+ }
628+ } ? : drawable // Return drawable even if timeout (best effort)
629+ }
630+
631+ suspend fun convertDrawableToBase64 (drawable : Drawable ): String? {
632+ val loadedDrawable = waitForDrawableToLoad(drawable)
633+ val bitmap = getBitmapFromDrawable(loadedDrawable)
634+ return getBase64FromBitmap(bitmap)
635+ }
636+
553637fun getBitmapFromDrawable (drawable : Drawable ): Bitmap ? {
554638 val drawableCompat = DrawableCompat .wrap(drawable).mutate()
555- if (drawableCompat.intrinsicWidth <= 0 || drawableCompat.intrinsicHeight <= 0 ) {
639+
640+ // Determine the size to use - prefer intrinsic size, fall back to bounds
641+ val width =
642+ if (drawableCompat.intrinsicWidth > 0 ) {
643+ drawableCompat.intrinsicWidth
644+ } else {
645+ drawableCompat.bounds.width()
646+ }
647+
648+ val height =
649+ if (drawableCompat.intrinsicHeight > 0 ) {
650+ drawableCompat.intrinsicHeight
651+ } else {
652+ drawableCompat.bounds.height()
653+ }
654+
655+ if (width <= 0 || height <= 0 ) {
556656 return null
557657 }
558- val bitmap =
559- createBitmap(drawableCompat.intrinsicWidth, drawableCompat.intrinsicHeight )
658+
659+ val bitmap = createBitmap(width, height, Bitmap . Config . ARGB_8888 )
560660 bitmap.eraseColor(Color .TRANSPARENT )
561661 val canvas = Canvas (bitmap)
562- drawable.setBounds(0 , 0 , canvas.width, canvas.height)
563- drawable.draw(canvas)
662+ drawableCompat.setBounds(0 , 0 , canvas.width, canvas.height)
663+ drawableCompat.draw(canvas)
664+
564665 return bitmap
565666}
566667
0 commit comments