Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paywalls: backwards compatible blurring #1327

Merged

Conversation

aboedo
Copy link
Member

@aboedo aboedo commented Oct 9, 2023

Add backwards compatible blur.

This has 3 different ways of applying blur:

It also adds some logic to benchmark... But I've been having a tough time actually running the benchmarks.
From multiDex to instrumentation tests and roboelectric, it's been fun. Will reach out tomorrow to android devs for a hand in actually running the benchmarks.

@aboedo aboedo self-assigned this Oct 9, 2023
Comment on lines 20 to 25
.matchParentSize()
.conditional(templateConfiguration.configuration.blurredBackgroundImage) {
// TODO-PAYWALLS: backwards compatibility for blurring
blur(BackgroundUIConstants.blurSize, edgeTreatment = BlurredEdgeTreatment.Unbounded)
.alpha(BackgroundUIConstants.blurAlpha)
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't really want to remove the modifier because it's really nice but it was easier to just apply transformations since you can get the image as a bitmap easily

Comment on lines 68 to 54
// max radius supported by RenderScript is 25
val updatedRadius = min(radius.toDouble(), 25.toDouble())
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: figure out whether this is an issue

// useful for testing
val useRenderScript = true
if (useRenderScript) {
return blurUsingRenderScript(context, radius)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renderscript is pretty optimized and I believe it can take better advantage of the hardware, so it should be the best option other than going directly to the native API in 31+

if (useRenderScript) {
return blurUsingRenderScript(context, radius)
} else {
return blurByAveraging(scale = 1f, radius = radius.roundToInt())
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to get actual numbers, but whatever, this one is n * 2 with n = number of pixels on the image, so it's not the fastest implementation.
It does seem to run pretty well on my crappy device on a 1024x1024 image, but numbers would be nice

*
* This sum is then divided by the sum of the divisor values to obtain the average color value for the pixel.
*/
internal suspend fun Bitmap.blurByAveraging(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to make this one more readable but it's still a mouthful.
In a nutshell it goes pixel by pixel, counting the values of the pixels that are in a given radius, averages the number, then sets the average as the color

@aboedo aboedo requested a review from a team October 9, 2023 20:56
@aboedo aboedo added the pr:feat A new feature label Oct 9, 2023
Copy link
Contributor

@NachoSoto NachoSoto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great 👏🏻

@aboedo aboedo force-pushed the andy/pwl-247-backwards-compatible-blurring-renderscript branch from 67d42e0 to 19ef51e Compare October 18, 2023 18:45
@@ -39,6 +54,8 @@ internal fun BoxScope.PaywallBackground(templateConfiguration: TemplateConfigura
urlString = it.toString(),
modifier = modifier,
contentScale = BackgroundUIConstants.contentScale,
transformation = transformation,
alpha = BackgroundUIConstants.blurAlpha
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now applying the alpha even when there's no blur.

@NachoSoto
Copy link
Contributor

Taking over this

@NachoSoto NachoSoto force-pushed the andy/pwl-247-backwards-compatible-blurring-renderscript branch from c3aa923 to 2e2fb06 Compare October 18, 2023 22:17
Comment on lines 32 to 34
transformation?.let {
requestBuilder.transformations(listOf(it))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out this can be simplified:

.transformations(listOfNotNull(transformation))

@NachoSoto NachoSoto force-pushed the andy/pwl-247-backwards-compatible-blurring-renderscript branch from 2e2fb06 to 119c260 Compare October 18, 2023 22:23
@NachoSoto NachoSoto marked this pull request as ready for review October 18, 2023 22:24

@RunWith(AndroidJUnit4::class)
class BlurBenchmarkTest {
@Ignore("Test is for checking performance only")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disabled this so we don't run it every time unnecessarily.

@NachoSoto
Copy link
Contributor

I cleaned this up a bit, it should be ready!

@NachoSoto NachoSoto added pr:RevenueCatUI and removed pr:feat A new feature labels Oct 18, 2023
internal class BlurTransformation(
private val context: Context,
private val radius: Float,
private val scale: Float = 0.5f,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this wasn't used? I'm gonna remove it, but let me know if you meant to use it.

@NachoSoto NachoSoto requested a review from a team October 18, 2023 22:59
@NachoSoto NachoSoto force-pushed the andy/pwl-247-backwards-compatible-blurring-renderscript branch from 4d64141 to 0bb96cc Compare October 18, 2023 23:00
@NachoSoto NachoSoto force-pushed the andy/pwl-247-backwards-compatible-blurring-renderscript branch from 0bb96cc to d5f4c95 Compare October 18, 2023 23:01
@codecov
Copy link

codecov bot commented Oct 18, 2023

Codecov Report

All modified lines are covered by tests ✅

❗ No coverage uploaded for pull request base (paywalls@19ab46b). Click here to learn what that means.

❗ Current head d5f4c95 differs from pull request most recent head 1ef2a52. Consider uploading reports for the commit 1ef2a52 to get more accurate results

Additional details and impacted files
@@             Coverage Diff             @@
##             paywalls    #1327   +/-   ##
===========================================
  Coverage            ?   84.53%           
===========================================
  Files               ?      194           
  Lines               ?     6538           
  Branches            ?      952           
===========================================
  Hits                ?     5527           
  Misses              ?      643           
  Partials            ?      368           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing major, looks good!

// max radius supported by RenderScript
private const val MAX_SUPPORTED_RADIUS = 25

internal fun Bitmap.blur(context: Context, radius: Float = 25f): Bitmap {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe? Or we could also define MAX_SUPPORTED_RADIUS as a float.

Suggested change
internal fun Bitmap.blur(context: Context, radius: Float = 25f): Bitmap {
internal fun Bitmap.blur(context: Context, radius: Float = MAX_SUPPORTED_RADIUS.toFloat()): Bitmap {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh thanks I forgot to use it here too.

return input.blur(context, radius)
}

override fun equals(other: Any?): Boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I'm not sure if we need the equals and hashcode for something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me check

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it uses cacheKey instead.

) {
AsyncImage(
return AsyncImage(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I don't think we need the return here right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, fixed


override val cacheKey: String = "${javaClass.name}-$radius"

override suspend fun transform(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting that this is a suspend function... I guess in case the transformation takes a long time.

@NachoSoto NachoSoto enabled auto-merge (squash) October 19, 2023 15:27
@NachoSoto NachoSoto merged commit 1d79340 into paywalls Oct 19, 2023
5 checks passed
@NachoSoto NachoSoto deleted the andy/pwl-247-backwards-compatible-blurring-renderscript branch October 19, 2023 15:43
tonidero pushed a commit that referenced this pull request Oct 31, 2023
Add backwards compatible blur. 

This has 3 different ways of applying blur: 
- through native blur in API 31+
- through averaging the pixels in the image, based on
https://github.com/T8RIN/BlurTransformation
- through RenderScript in API < 31

It also adds some logic to benchmark... But I've been having a tough
time actually running the benchmarks.
From multiDex to instrumentation tests and roboelectric, it's been fun.
Will reach out tomorrow to android devs for a hand in actually running
the benchmarks.

---------

Co-authored-by: NachoSoto <[email protected]>
NachoSoto added a commit that referenced this pull request Oct 31, 2023
### New Features
#### ✨📱 Introducing Android Paywalls 🐾🧱

RevenueCat's Paywalls allow you to remotely configure your entire
paywall view without any code changes or app updates.
Our paywall templates use native code to deliver smooth, intuitive
experiences to your customers when you’re ready to deliver them an
Offering; and you can use our Dashboard to pick the right template and
configuration to meet your needs.

To use RevenueCat Paywalls on Android, simply:

1. Create a Paywall on the Dashboard for the `Offering` you intend to
serve to your customers
2. Add the `RevenueCatUI` dependency to your project:
```groovy build.gradle
implementation 'com.revenuecat.purchases:purchases:7.1.0'
implementation 'com.revenuecat.purchases:purchases-ui:7.1.0'
```
3. Display a paywall:
```kotlin
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@composable
private fun LockedScreen() {
    YourContent()

    PaywallDialog(
        PaywallDialogOptions.Builder()
            .setRequiredEntitlementIdentifier(Constants.ENTITLEMENT_ID)
            .build()
    )
}
```

> **Note**
> Android paywalls is currently behind an experimental flag:
`ExperimentalPreviewRevenueCatUIPurchasesAPI`.
> It is safe to release app updates with it. We guarantee that paywalls
will continue to work and any changes will always be backwards
compatible.
> They are stable, but migration steps may be required in the future.
We'll do our best to minimize any changes you have to make.

You can find more information in [our
documentation](https://rev.cat/paywalls).

<details>

<summary>List of changes</summary>

* `Paywalls`: Improve PaywallDialog look on tablets (#1419) via Toni
Rico (@tonidero)
* `Paywalls`: disallow Markdown links in PurchaseButton (#1421) via
NachoSoto (@NachoSoto)
* `Paywalls`: implemented template 5 (#1412) via NachoSoto (@NachoSoto)
* `Paywalls`: Fix wrong view model data after configuration changes
(#1417) via Toni Rico (@tonidero)
* `Paywalls`: update template 4 colors (#1410) via NachoSoto
(@NachoSoto)
* `Paywalls`: Add bundled font to paywall tester (#1415) via Toni Rico
(@tonidero)
* `Paywalls`: Fix purchase button text flashing when changing selected
package (#1416) via Toni Rico (@tonidero)
* `Paywalls`: support text3 and accent3 colors (#1409) via NachoSoto
(@NachoSoto)
* `Paywalls`: PaywallData errors shouldn't make Offerings fail to decode
(#1402) via NachoSoto (@NachoSoto)
* `Paywalls`: convert empty strings to null via NachoSoto (@NachoSoto)
* `Paywalls`: footer view should not render background image via
NachoSoto (@NachoSoto)
* `Paywalls`: improve template 4 A11y support via NachoSoto (@NachoSoto)
* `Paywalls`: Add close button option (#1390) via Toni Rico (@tonidero)
* `Paywalls`: fix MarkdownText stealing touches from package buttons
(#1411) via NachoSoto (@NachoSoto)
* Fix onDismiss getting called twice in PaywallDialogs (#1408) via Cesar
de la Vega (@vegaro)
* `Paywalls`: ignore URL deserialization errors (#1399) via NachoSoto
(@NachoSoto)
* Animate transition between loading and loaded paywall (#1404) via
Cesar de la Vega (@vegaro)
* Fix button padding in Loading paywall (#1405) via Cesar de la Vega
(@vegaro)
* Add test for `packageInfo.localizedDiscount` (#1407) via Cesar de la
Vega (@vegaro)
* `Paywalls`: RemoteImage always renders a placeholder on previews via
NachoSoto (@NachoSoto)
* `Paywalls`: decode empty images as null via NachoSoto (@NachoSoto)
* `Paywalls`: Allow trigger manual paywall tester release (#1406) via
Toni Rico (@tonidero)
* Fix navigation after closing paywall in Paywalls screen (#1403) via
Cesar de la Vega (@vegaro)
* `Paywalls`: Add experimental annotation to all public APIs in
RevenueCat UI (#1400) via Toni Rico (@tonidero)
* `Paywalls`: improve template 2 layout (#1396) via NachoSoto
(@NachoSoto)
* `Paywalls`: template 4 (#1349) via NachoSoto (@NachoSoto)
* `Paywalls`: fix template 3 offer details color (#1394) via NachoSoto
(@NachoSoto)
* `Paywalls`: PaywallDialogOptions no longer requires dismissRequest
(#1386) via NachoSoto (@NachoSoto)
* `Paywalls`: fixed accessibility across templates (#1392) via NachoSoto
(@NachoSoto)
* `Paywalls`: increase PaywallBackground blur radius to match iOS via
NachoSoto (@NachoSoto)
* `Paywalls`: use LocalUriHandler for opening links (#1388) via
NachoSoto (@NachoSoto)
* `Paywalls`: support for Markdown links (#1387) via NachoSoto
(@NachoSoto)
* `Paywalls`: display purchase/restore errors (#1384) via NachoSoto
(@NachoSoto)
* `Paywalls`: improve error handling (#1383) via NachoSoto (@NachoSoto)
* `Paywalls`: fixed incorrect background on footer (#1382) via NachoSoto
(@NachoSoto)
* `Paywalls`: fix backwards-compatible blurring of default paywall
background (#1380) via NachoSoto (@NachoSoto)
* `Paywalls`: polish template 2 spacing (#1381) via NachoSoto
(@NachoSoto)
* `Paywalls`: optimize backwards compatible blurring (#1379) via
NachoSoto (@NachoSoto)
* `Paywalls`: Predownload offering images if paywalls sdk exists (#1372)
via Toni Rico (@tonidero)
* `Paywalls`: PurchaseButton supports gradients (#1378) via NachoSoto
(@NachoSoto)
* `Paywalls`: optimize AdaptiveComposable (#1377) via NachoSoto
(@NachoSoto)
* `Paywalls`: improve FooterDialog corner radius (#1374) via NachoSoto
(@NachoSoto)
* `Paywalls`: optimize PurchaseButton (#1376) via NachoSoto (@NachoSoto)
* `Paywalls`: Enable footer modes in paywall tester paywalls tab (#1368)
via Toni Rico (@tonidero)
* `Paywalls`: Support footer in template 3 (#1367) via Toni Rico
(@tonidero)
* `Paywalls`: calculate discountRelativeToMostExpensivePerMonth (#1370)
via NachoSoto (@NachoSoto)
* `Paywalls`: Add offer details in template 2 when in condensed form
(#1371) via Toni Rico (@tonidero)
* `Paywalls`: improve handling of lifetime/custom packages (#1363) via
NachoSoto (@NachoSoto)
* `Paywalls`: Support footer in template 1 (#1366) via Toni Rico
(@tonidero)
* Add `StoreProduct.pricePerMonth` (#1369) via NachoSoto (@NachoSoto)
* `Paywalls`: Support condensed footer presentation in template 2
(#1365) via Toni Rico (@tonidero)
* `Paywalls`: finished localization support (#1362) via NachoSoto
(@NachoSoto)
* `Paywalls`: backwards compatible blurring (#1327) via Andy Boedo
(@AndyBoedo)
* `Paywalls`: PaywallViewModel tests (#1357) via NachoSoto (@NachoSoto)
* `Paywalls`: improve LoadingPaywall (#1364) via NachoSoto (@NachoSoto)
* `Paywalls`: Fix paywall compose previews (#1360) via Toni Rico
(@tonidero)
* `Paywalls`: Fix proguard rules kotlinx serialization (#1356) via Toni
Rico (@tonidero)
* `Paywalls`: Add custom font example to paywalls screen (#1358) via
Toni Rico (@tonidero)
* `Paywalls`: Support Google fonts and font families with multiple fonts
(#1338) via Toni Rico (@tonidero)
* `Paywalls`: Support custom fonts through FontProvider (#1328) via Toni
Rico (@tonidero)
* `Paywalls`: fixed Footer padding (#1354) via NachoSoto (@NachoSoto)
* `Paywalls`: Rename PaywallView to Paywall (#1351) via Toni Rico
(@tonidero)
* `Paywalls`: disable PurchaseButton during purchases (#1352) via
NachoSoto (@NachoSoto)
* `Paywalls`: enable library publishing (#1353) via NachoSoto
(@NachoSoto)
* `Paywalls`: handle "action in progress" state (#1346) via NachoSoto
(@NachoSoto)
* `Paywalls`: support {{ sub_duration_in_months }} (#1348) via NachoSoto
(@NachoSoto)
* `Paywalls`: disallow purchasing currently subscribed products (#1334)
via NachoSoto (@NachoSoto)
* `Paywalls`: new PaywallActivityLauncher.launchIfNeeded methods (#1335)
via NachoSoto (@NachoSoto)
* `Paywalls`: PaywallColor supports RGBA (#1332) via NachoSoto
(@NachoSoto)
* `Paywalls`: Make DialogScaffold private (#1329) via Toni Rico
(@tonidero)
* Add `revenuecatui` `gradle.properties` to specify name of dependency
(#1324) via Toni Rico (@tonidero)
* `Paywalls`: log error when failing to load images (#1321) via
NachoSoto (@NachoSoto)
* Log errors when displaying default paywall (#1318) via Cesar de la
Vega (@vegaro)
* Rename packages to packageIds in `PaywallData` (#1309) via Cesar de la
Vega (@vegaro)
* Add support for multiple intro offers in `IntroEligibilityStateView`
(#1319) via Cesar de la Vega (@vegaro)
* Fix material theme references to use Material3 versions (#1326) via
Toni Rico (@tonidero)
* `Paywalls`: Add support to launch paywall as activity (#1317) via Toni
Rico (@tonidero)
* Parse `{{ sub_offer_price_2 }}` and `{{ sub_offer_duration_2 }}`
variables (#1313) via Cesar de la Vega (@vegaro)
* `Paywalls`: changed PaywallsTester app icon (#1323) via NachoSoto
(@NachoSoto)
* `Paywalls`: fixed PaywallDialog.setRequiredEntitlementIdentifier
(#1322) via NachoSoto (@NachoSoto)
* `Paywalls`: Markdown support (#1312) via NachoSoto (@NachoSoto)
* `PaywallsTester`: added template name to offerings list (#1316) via
NachoSoto (@NachoSoto)
* `Paywalls`: Update paywall tester to be able to display paywall footer
(#1315) via Toni Rico (@tonidero)
* `Paywalls`: Add PaywallFooter composable to present a minified paywall
UI that allows for custom paywalls (#1314) via Toni Rico (@tonidero)
* `Paywalls`: use IntroEligibilityStateView (#1311) via NachoSoto
(@NachoSoto)
* `PaywallData` validation tests (#1310) via Cesar de la Vega (@vegaro)
* `Paywalls`: implemented LoadingPaywallView with placeholder (#1284)
via NachoSoto (@NachoSoto)
* `Paywalls`: created LoadingPaywallView (#1282) via NachoSoto
(@NachoSoto)
* Fix template test data (#1308) via Cesar de la Vega (@vegaro)
</details>

### Other Changes
Add `purchases.setOnesignalUserID` (#1304) via Raquel Diez
(@Raquel10-RevenueCat)

---------

Co-authored-by: revenuecat-ops <[email protected]>
Co-authored-by: NachoSoto <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants