-
Notifications
You must be signed in to change notification settings - Fork 53
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 V2] Adds ImageComponentView
#1959
Conversation
📸 Snapshot Test3 added, 110 unchanged
🛸 Powered by Emerge Tools |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #1959 +/- ##
=======================================
Coverage 81.87% 81.87%
=======================================
Files 260 260
Lines 8493 8493
Branches 1226 1226
=======================================
Hits 6954 6954
Misses 1041 1041
Partials 498 498 ☔ View full report in Codecov by Sentry. |
.../main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/image/ImageComponentView.kt
Outdated
Show resolved
Hide resolved
modifier: Modifier = Modifier, | ||
) { | ||
if (style.visible) { | ||
Box(modifier = modifier.applyIfNotNull(style.shape) { clip(it) }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to apply the clipping in an outer box... If I applied it to the image itself, it didn't work together with the gradient overlays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can change the drawWithCache
modifier to draw an Outline
instead of a Rect
. That way we don't need the Box
. Might be nice to turn it into a custom modifier (for readability and reusability)? Something like:
@JvmSynthetic
@Stable
internal fun Modifier.overlay(
color: ColorStyle,
shape: Shape = RectangleShape,
): Modifier = this then drawWithCache {
val outline = shape.createOutline(size, layoutDirection, this)
onDrawWithContent {
drawContent()
when (color) {
is ColorStyle.Solid -> drawOutline(outline, color.color)
is ColorStyle.Gradient -> drawOutline(outline, color.brush)
}
}
}
Which can then be used as:
.applyIfNotNull(style.colorStyle) { colorStyle ->
overlay(colorStyle, style.shape ?: RectangleShape)
}
// This is now needed, as the Box is no longer clipping the image itself.
.applyIfNotNull(style.shape) { clip(it) }
.../main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/image/ImageComponentView.kt
Outdated
Show resolved
Hide resolved
...nuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/composables/RemoteImage.kt
Outdated
Show resolved
Hide resolved
@get:JvmSynthetic | ||
val darkGradientColors: ColorInfo.Gradient?, | ||
@get:JvmSynthetic | ||
val contentScale: ContentScale, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this might require some transformations from the ImageComponent
to this ImageComponentStyle
. For example, this expects a ContentScale
but in ImageComponent
we have our own FitMode
. I believe this translation should be done separately and in advance though.
d56f0c1
to
47a2d2e
Compare
...main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/ImageComponentStyle.kt
Outdated
Show resolved
Hide resolved
colorStyle: ColorStyle? = null, | ||
) = ImageComponentStyle( | ||
visible = visible, | ||
themeImageUrls = ThemeImageUrls(light = ImageUrls(url, url, lowResURL, 200u, 200u)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if the sizes of the images are affecting anything... Not according to my tests 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use the width and height to already give the image view the appropriate size before the image is downloaded. That way the layout won't "jump" when the image arrives.
Since width and height are part of ThemeImageUrls
, which is provided by ImageComponentStyle
, I think we can remove the separate size
from ImageComponentStyle
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhh ok that makes a lot of sense 😅 Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm as I was thinking about this, how would that work if we want the image to "Fill" or "Fit" the screen? In the ThemeImageUrls
there is no way to pass if we want one of those two right? I think passing the specific width/height only makes sense if we want a "fixed" size...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of offline conversation:
- We cannot remove the
size
as that indicates the size of the image view, not the image itself. - The
ImageUrls
width/height come into play when the image view is set toFit
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice job on this! Just a few minor comments.
colorStyle: ColorStyle? = null, | ||
) = ImageComponentStyle( | ||
visible = visible, | ||
themeImageUrls = ThemeImageUrls(light = ImageUrls(url, url, lowResURL, 200u, 200u)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use the width and height to already give the image view the appropriate size before the image is downloaded. That way the layout won't "jump" when the image arrives.
Since width and height are part of ThemeImageUrls
, which is provided by ImageComponentStyle
, I think we can remove the separate size
from ImageComponentStyle
.
placeholder = placeholderSource?.let { | ||
rememberAsyncImagePainter( | ||
model = it.data, | ||
placeholder = if (isInPreviewMode()) painterResource(R.drawable.android) else null, | ||
imageLoader = imageLoader, | ||
contentScale = contentScale, | ||
onError = { errorState -> | ||
Logger.e("Error loading placeholder image", errorState.result.throwable) | ||
}, | ||
) | ||
} ?: if (isInPreviewMode()) painterResource(R.drawable.android) else null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
...main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/ImageComponentStyle.kt
Outdated
Show resolved
Hide resolved
.../main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/image/ImageComponentView.kt
Outdated
Show resolved
Hide resolved
modifier: Modifier = Modifier, | ||
) { | ||
if (style.visible) { | ||
Box(modifier = modifier.applyIfNotNull(style.shape) { clip(it) }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can change the drawWithCache
modifier to draw an Outline
instead of a Rect
. That way we don't need the Box
. Might be nice to turn it into a custom modifier (for readability and reusability)? Something like:
@JvmSynthetic
@Stable
internal fun Modifier.overlay(
color: ColorStyle,
shape: Shape = RectangleShape,
): Modifier = this then drawWithCache {
val outline = shape.createOutline(size, layoutDirection, this)
onDrawWithContent {
drawContent()
when (color) {
is ColorStyle.Solid -> drawOutline(outline, color.color)
is ColorStyle.Gradient -> drawOutline(outline, color.brush)
}
}
}
Which can then be used as:
.applyIfNotNull(style.colorStyle) { colorStyle ->
overlay(colorStyle, style.shape ?: RectangleShape)
}
// This is now needed, as the Box is no longer clipping the image itself.
.applyIfNotNull(style.shape) { clip(it) }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤏 this close! Just a comment on px vs dp, and a suggestion.
imageUrls: ImageUrls? = null, | ||
): Modifier { | ||
val widthModifier = when (val width = size.width) { | ||
is Fit -> Modifier.wrapContentWidth(align = horizontalAlignment ?: Alignment.CenterHorizontally) | ||
is Fit -> if (imageUrls == null) { | ||
Modifier.wrapContentWidth(align = horizontalAlignment ?: Alignment.CenterHorizontally) | ||
} else { | ||
Modifier.width(imageUrls.width.toInt().dp) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is nice! 2 comments:
- The values in
ImageUrls
are pixels, so we should transform them to dp, instead of interpreting them as dp. - Maybe we can adjust the
Size
, taking into account theImageUrls
values before it gets to this modifier? That way this modifier doesn't need to know aboutImageUrls
.
To illustrate, both of those combined could look something like this:
@JvmSynthetic
@Composable
internal fun Size.adjustForImage(imageUrls: ImageUrls): Size {
val density = LocalDensity.current
return adjustForImage(imageUrls, density)
}
@JvmSynthetic
internal fun Size.adjustForImage(imageUrls: ImageUrls, density: Density): Size =
Size(
width = when (width) {
is Fit -> Fixed(with(density) { imageUrls.width.toInt().toDp().value.toUInt() })
is Fill,
is Fixed,
-> width
},
height = when (height) {
is Fit -> Fixed(with(density) { imageUrls.height.toInt().toDp().value.toUInt() })
is Fill,
is Fixed,
-> height
},
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that 👍 Thanks!
import com.revenuecat.purchases.paywalls.components.properties.ImageUrls | ||
import com.revenuecat.purchases.paywalls.components.properties.ThemeImageUrls | ||
|
||
internal val ThemeImageUrls.urlsForCurrentTheme: ImageUrls | ||
@Composable get() = if (isSystemInDarkTheme()) dark ?: light else light | ||
@ReadOnlyComposable @Composable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not important, but I think we can make some composables like this read only composables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea probably indeed, good call!
@JvmSynthetic | ||
@ReadOnlyComposable | ||
@Composable | ||
fun adjustedSize(): Size { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this to the ImageComponentStyle
since it felt like a better place to put it than as simple extensions of the Size
class, since here is where we actually have the data to adjust the size. Not sure if there are other components that would require a similar adjustment, in which case, I think we can extract it to extension functions as suggested. Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super nice!
@JvmSynthetic | ||
@ReadOnlyComposable | ||
@Composable | ||
fun adjustedSize(): Size { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this!
7498eff
to
7f632f6
Compare
**This is an automatic release.** ## RevenueCatUI SDK ### 🐞 Bugfixes * Fix multi-tier template to allow optional header image (#1971) via Josh Holtz (@joshdholtz) ### 🔄 Other Changes * [Paywalls V2] Adds first version of `LoadedPaywallComponents` (#1970) via JayShortway (@JayShortway) * [Paywalls V2] Add `ButtonComponentView` (#1963) via Toni Rico (@tonidero) * [Paywalls V2] Adds `PaywallState.Loaded.Components` (#1969) via JayShortway (@JayShortway) * [Paywalls V2] Adds image background tests (#1967) via JayShortway (@JayShortway) * [Paywalls V2] `TextComponentView` updates when the theme changes (#1966) via JayShortway (@JayShortway) * [Paywalls V2] Adds a `StyleFactory` (#1965) via JayShortway (@JayShortway) * [EXTERNAL] fix: update polish translations (#1919) via @radko93 (#1964) via JayShortway (@JayShortway) * [Paywalls V2] Adds `ImageComponentView` (#1959) via Toni Rico (@tonidero) * WebPurchaseRedemption: Rename `AlreadyRedeemed` result to `PurchaseBelongsToOtherUser` (#1962) via Toni Rico (@tonidero) * [Paywalls V2] Extends support for blurred shadows to all Android versions (#1957) via JayShortway (@JayShortway) Co-authored-by: revenuecat-ops <ops@revenuecat.com>
Description
This adds a new component
ImageComponentView
and the correspondingImageComponentStyle
. It doesn't include transformations fromImageComponent
toImageComponentStyle
.Untitled2.mp4