-
Notifications
You must be signed in to change notification settings - Fork 38
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
Add support for heightDp, widthDp, showBackground, backgroundColor #576
Changes from all commits
fa0a375
9b5068d
e254ec0
b4ff8cf
be5c99a
e514e45
d1cac8c
e57d40f
ea218db
a10f240
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package com.github.takahirom.roborazzi | ||
|
||
import android.app.Activity | ||
import android.graphics.Color | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.foundation.layout.width | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.unit.dp | ||
import androidx.test.core.app.ActivityScenario | ||
import org.robolectric.Shadows.shadowOf | ||
import org.robolectric.shadows.ShadowDisplay.getDefaultDisplay | ||
import kotlin.math.roundToInt | ||
|
||
fun ActivityScenario<out Activity>.setBackgroundColor( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These 2 Methods together with the maybe worth documenting a bit more about these options to arise awareness? |
||
showBackground: Boolean, | ||
backgroundColor: Long, | ||
) { | ||
when (showBackground) { | ||
false -> { | ||
onActivity { activity -> | ||
activity.window.decorView.setBackgroundColor(Color.TRANSPARENT) | ||
} | ||
} | ||
|
||
true -> { | ||
val color = when (backgroundColor != 0L) { | ||
true -> backgroundColor.toInt() | ||
false -> Color.WHITE | ||
} | ||
onActivity { activity -> | ||
activity.window.decorView.setBackgroundColor(color) | ||
} | ||
} | ||
} | ||
} | ||
|
||
fun ActivityScenario<out Activity>.createSizedPreview( | ||
widthDp: Int, | ||
heightDp: Int, | ||
preview: @Composable () -> Unit | ||
): @Composable () -> Unit { | ||
var result: (@Composable () -> Unit)? = null | ||
onActivity { activity -> | ||
activity.setDisplaySize(widthDp = widthDp, heightDp = heightDp) | ||
result = preview.size(widthDp = widthDp, heightDp = heightDp) | ||
} | ||
return result | ||
?: throw IllegalStateException("The preview could not be sucessfully sized to widthDp = $widthDp and heightDp = $heightDp") | ||
} | ||
|
||
|
||
internal fun Activity.setDisplaySize( | ||
widthDp: Int, | ||
heightDp: Int | ||
) { | ||
if (widthDp <= 0 && heightDp <= 0) return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe desirable to log a warning or debug message if it returns here? |
||
|
||
val display = shadowOf(getDefaultDisplay()) | ||
val density = resources.displayMetrics.density | ||
if (widthDp > 0) { | ||
val widthPx = (widthDp * density).roundToInt() | ||
display.setWidth(widthPx) | ||
} | ||
if (heightDp > 0) { | ||
val heightPx = (heightDp * density).roundToInt() | ||
display.setHeight(heightPx) | ||
} | ||
recreate() | ||
} | ||
|
||
/** | ||
* WARNING: | ||
* For this to work, it requires that the Display is within the widthDp and heightDp dimensions | ||
* You can ensure that by calling [Activity.setDisplaySize] before | ||
*/ | ||
internal fun (@Composable () -> Unit).size( | ||
widthDp: Int, | ||
heightDp: Int, | ||
): @Composable () -> Unit { | ||
val resizedPreview = @Composable { | ||
val modifier = when { | ||
widthDp > 0 && heightDp > 0 -> Modifier.size(widthDp.dp, heightDp.dp) | ||
widthDp > 0 -> Modifier.width(widthDp.dp) | ||
heightDp > 0 -> Modifier.height(heightDp.dp) | ||
else -> Modifier | ||
} | ||
Box(modifier = modifier) { | ||
this@size() | ||
} | ||
} | ||
return resizedPreview | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.github.takahirom.roborazzi | ||
|
||
import android.app.Application | ||
import android.content.ComponentName | ||
import androidx.test.core.app.ApplicationProvider | ||
import org.robolectric.Shadows | ||
|
||
/** | ||
* Workaround for https://github.com/takahirom/roborazzi/issues/100 | ||
*/ | ||
internal fun registerActivityToRobolectricIfNeeded() { | ||
try { | ||
val appContext: Application = ApplicationProvider.getApplicationContext() | ||
Shadows.shadowOf(appContext.packageManager).addActivityIfNotPresent( | ||
ComponentName( | ||
appContext.packageName, | ||
RoborazziTransparentActivity::class.java.name, | ||
) | ||
) | ||
} catch (e: ClassNotFoundException) { | ||
// Configured to run even without Robolectric | ||
e.printStackTrace() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,13 @@ | ||
package com.github.takahirom.roborazzi | ||
|
||
import android.app.Application | ||
import android.content.ComponentName | ||
import android.app.Activity | ||
import android.view.ViewGroup | ||
import androidx.activity.ComponentActivity | ||
import androidx.activity.compose.setContent | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.platform.ComposeView | ||
import androidx.compose.ui.platform.ViewRootForTest | ||
import androidx.test.core.app.ActivityScenario | ||
import androidx.test.core.app.ApplicationProvider | ||
import org.robolectric.Shadows | ||
import java.io.File | ||
|
||
fun captureRoboImage( | ||
|
@@ -30,16 +28,12 @@ fun captureRoboImage( | |
content: @Composable () -> Unit, | ||
) { | ||
if (!roborazziOptions.taskType.isEnabled()) return | ||
registerRoborazziActivityToRobolectricIfNeeded() | ||
registerActivityToRobolectricIfNeeded() | ||
|
||
val activityScenario = ActivityScenario.launch(RoborazziTransparentActivity::class.java) | ||
activityScenario.use { | ||
activityScenario.onActivity { activity -> | ||
activity.setContent(content = content) | ||
val composeView = activity.window.decorView | ||
.findViewById<ViewGroup>(android.R.id.content) | ||
.getChildAt(0) as ComposeView | ||
val viewRootForTest = composeView.getChildAt(0) as ViewRootForTest | ||
viewRootForTest.view.captureRoboImage(file, roborazziOptions) | ||
activityScenario.captureRoboImage(file, roborazziOptions){ | ||
content() | ||
} | ||
|
||
// Closing the activity is necessary to prevent memory leaks. | ||
|
@@ -48,20 +42,55 @@ fun captureRoboImage( | |
} | ||
} | ||
|
||
/** | ||
* Workaround for https://github.com/takahirom/roborazzi/issues/100 | ||
*/ | ||
private fun registerRoborazziActivityToRobolectricIfNeeded() { | ||
try { | ||
val appContext: Application = ApplicationProvider.getApplicationContext() | ||
Shadows.shadowOf(appContext.packageManager).addActivityIfNotPresent( | ||
ComponentName( | ||
appContext.packageName, | ||
RoborazziTransparentActivity::class.java.name, | ||
) | ||
) | ||
} catch (e: ClassNotFoundException) { | ||
// Configured to run even without Robolectric | ||
e.printStackTrace() | ||
fun captureRoboImageWithActivityScenarioSetup( | ||
filePath: String, | ||
roborazziOptions: RoborazziOptions = provideRoborazziContext().options, | ||
content: (ActivityScenario<out Activity>) -> @Composable () -> Unit, | ||
) { | ||
if (!roborazziOptions.taskType.isEnabled()) return | ||
registerActivityToRobolectricIfNeeded() | ||
|
||
val activityScenario = ActivityScenario.launch(RoborazziTransparentActivity::class.java) | ||
|
||
activityScenario.use { | ||
val sizedPreview = content(activityScenario) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better to name it "preview" instead of "sizedPreview". That is a leftover of the clean up... |
||
activityScenario.captureRoboImage(filePath, roborazziOptions){ | ||
sizedPreview() | ||
} | ||
|
||
// Closing the activity is necessary to prevent memory leaks. | ||
// If multiple captureRoboImage calls occur in a single test, | ||
// they can lead to an activity leak. | ||
} | ||
} | ||
|
||
|
||
private fun ActivityScenario<out ComponentActivity>.captureRoboImage( | ||
filePath: String, | ||
roborazziOptions: RoborazziOptions = provideRoborazziContext().options, | ||
content: @Composable () -> Unit, | ||
) { | ||
captureRoboImage( | ||
file = fileWithRecordFilePathStrategy(filePath), | ||
roborazziOptions = roborazziOptions, | ||
content = content | ||
) | ||
} | ||
|
||
private fun ActivityScenario<out ComponentActivity>.captureRoboImage( | ||
file: File, | ||
roborazziOptions: RoborazziOptions = provideRoborazziContext().options, | ||
content: @Composable () -> Unit, | ||
) { | ||
|
||
onActivity { activity -> | ||
activity.setContent(content = { content() }) | ||
|
||
val composeView = activity.window.decorView | ||
.findViewById<ViewGroup>(android.R.id.content) | ||
.getChildAt(0) as ComposeView | ||
|
||
val viewRootForTest = composeView.getChildAt(0) as ViewRootForTest | ||
viewRootForTest.view.captureRoboImage(file, roborazziOptions) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
package com.github.takahirom.roborazzi | ||
|
||
import android.os.Bundle | ||
import android.os.PersistableBundle | ||
import androidx.activity.ComponentActivity | ||
|
||
class RoborazziTransparentActivity: ComponentActivity() { | ||
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { | ||
super.onCreate(savedInstanceState, persistentState) | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
setTheme(android.R.style.Theme_Translucent_NoTitleBar_Fullscreen) | ||
super.onCreate(savedInstanceState) | ||
} | ||
} |
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 think it was practical to add such a captureRoboImage method to allow users to set properties through the activityScenario. Open to other opinions
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.
Thank you. Designing these APIs is a bit challenging. I'm giving a talk at a technology conference this Saturday, so I'm a little busy at the moment. I apologize for the delay, but I’d like to take some time over the weekend to consider this. I’ll get back to you soon.