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

Add an example of seeking between shared element states manually using SeekableTransitionState #273

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
d71778c
Add snippets for Indication and Ripple migration (#191)
arriolac Jan 24, 2024
0bad025
Convert NeonIndication to data class. (#193)
arriolac Jan 24, 2024
ef49816
Merge branch 'main' into latest
arriolac Jan 25, 2024
5cb7dd2
Adding pip snippets to latest branch (#196)
MagicalMeghan Jan 30, 2024
5152ffc
Merge branch 'main' into latest
arriolac Feb 6, 2024
940a400
Delete pictureInPicture. (#213)
arriolac Feb 9, 2024
41acfd7
Update activity compose level and align log tag with main branch (#215)
MagicalMeghan Feb 14, 2024
aa8c5fe
Merge remote-tracking branch 'origin/main' into latest
compose-devrel-github-bot Feb 28, 2024
3637c7f
Merge pull request #229 from android/bot-sync-main
arriolac Feb 28, 2024
d9dbbf7
Merge remote-tracking branch 'origin/main' into latest
compose-devrel-github-bot Feb 29, 2024
c616bd2
Merge pull request #231 from android/bot-sync-main
arriolac Feb 29, 2024
fc0a273
Merge remote-tracking branch 'origin/main' into latest
compose-devrel-github-bot Mar 6, 2024
67f4f28
Merge pull request #235 from android/bot-sync-main
arriolac Mar 6, 2024
74e4d28
Merge remote-tracking branch 'origin/main' into latest
compose-devrel-github-bot Mar 6, 2024
72e3789
Merge pull request #237 from android/bot-sync-main
arriolac Mar 6, 2024
8242f9e
Migrate recomposeHighlighter to Modifier.Node (#197) (#238)
compose-devrel-github-bot Mar 11, 2024
bf83206
Adding samples for ContextualFlowRow (#234)
riggaroo Mar 12, 2024
6d2b166
Merge remote-tracking branch 'origin/main' into latest
compose-devrel-github-bot Mar 20, 2024
2441cae
Merge pull request #245 from android/bot-sync-main
arriolac Mar 20, 2024
2e672f9
Update libs.versions.toml (#247) (#248)
compose-devrel-github-bot Apr 8, 2024
c8a6c3b
Merge remote-tracking branch 'origin/main' into latest
compose-devrel-github-bot Apr 12, 2024
a9f1084
Merge pull request #250 from android/bot-sync-main
arriolac Apr 12, 2024
21a691c
Update Bitmap to image snippet to use new Graphics Layers (#254)
riggaroo Apr 23, 2024
b365d6d
add shared element snippets
riggaroo Apr 29, 2024
7c2c698
Apply Spotless
riggaroo Apr 29, 2024
f45f8ad
Split snippets into different files.
riggaroo Apr 29, 2024
d6e029f
Merge branch 'riggaroo/shared-element-snippets' of https://github.com…
riggaroo Apr 29, 2024
8e22485
Apply Spotless
riggaroo Apr 29, 2024
ee796a9
Shared element snippets (#256)
riggaroo Apr 30, 2024
90bcd56
Update libs.versions.toml (#251) (#252)
compose-devrel-github-bot Apr 30, 2024
664855e
Snippet updates based on peer feedback.
riggaroo May 1, 2024
4b60d08
Merge branch 'latest' into riggaroo/shared-element-snippets
riggaroo May 1, 2024
2c46281
Shared element snippets (#258)
riggaroo May 1, 2024
23d2ab2
Unique key snippet
riggaroo May 1, 2024
0349cd8
Merge branch 'latest' into riggaroo/shared-element-snippets
riggaroo May 1, 2024
fb5f982
Update SharedElementsWithNavigationSnippets.kt
riggaroo May 1, 2024
d6d77eb
Update AsyncImage snippet
riggaroo May 2, 2024
5a44e5e
Add placeholder size example
riggaroo May 2, 2024
d918044
Add comment to snippet about unmatched bounds
riggaroo May 3, 2024
77e9ab1
🤖 Sync main to latest (#259)
compose-devrel-github-bot May 7, 2024
3f7adbe
Update to new lazy list snippets. (#265)
riggaroo May 9, 2024
8abe5cc
Compose - beta01 (#270)
riggaroo May 14, 2024
0f4ace6
Added AnimatedVisibility shared element examples.
riggaroo May 20, 2024
112345c
Add an example of Seeking between shared element states manually.
riggaroo May 20, 2024
e484b05
Add an example of Seeking between shared element states manually.
riggaroo May 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions .github/dependabot.yml

This file was deleted.

6 changes: 3 additions & 3 deletions .github/workflows/apply_spotless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ jobs:

steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
token: ${{ secrets.PAT || github.token }}

- name: set up Java 17
uses: actions/setup-java@v4
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: '17'
Expand All @@ -44,6 +44,6 @@ jobs:
run: ./gradlew :compose:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace

- name: Auto-commit if spotlessApply has changes
uses: stefanzweifel/git-auto-commit-action@v5
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Apply Spotless
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ jobs:
timeout-minutes: 30

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
token: ${{ secrets.PAT || github.token }}
- name: set up Java 17
uses: actions/setup-java@v4
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: '17'
Expand Down
12 changes: 4 additions & 8 deletions .github/workflows/sync_main_latest.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
name: Sync main and latest
on:
workflow_dispatch:
push:
branches:
- main
Expand All @@ -11,17 +10,14 @@ jobs:
name: Syncing branches
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set git config user
run: git config user.email "[email protected]" && git config user.name "compose-devrel-github-bot"
uses: actions/checkout@v3

- name: Merge main into latest
run: git fetch && git switch latest && git merge -s ours origin/main --allow-unrelated-histories
run: git checkout latest && git merge main

- name: Create pull request
id: cpr
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.PAT }}
commit-message: 🤖 Sync main to latest
Expand All @@ -31,5 +27,5 @@ jobs:
branch: bot-sync-main
delete-branch: true
title: '🤖 Sync main to latest'
body: 'Update `latest` with `main`'
body: Updated dependencies
reviewers: ${{ github.actor }}
6 changes: 3 additions & 3 deletions .github/workflows/update_deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'zulu'
Expand All @@ -19,7 +19,7 @@ jobs:
run: ./gradlew versionCatalogUpdate
- name: Create pull request
id: cpr
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.PAT }}
commit-message: 🤖 Update Dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
Expand All @@ -43,7 +43,7 @@ class MainActivity : ComponentActivity() {

@Composable
private fun Content() {
var counter by remember { mutableIntStateOf(0) }
var counter by remember { mutableStateOf(0) }
Column(
Modifier
.fillMaxSize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,140 +16,100 @@

package com.example.android.compose.recomposehighlighter

import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.dp
import java.util.Objects
import kotlin.math.min
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
* A [Modifier] that draws a border around elements that are recomposing. The border increases in
* size and interpolates from red to green as more recompositions occur before a timeout.
*/
@Stable
fun Modifier.recomposeHighlighter(): Modifier = this.then(RecomposeHighlighterElement())

private class RecomposeHighlighterElement : ModifierNodeElement<RecomposeHighlighterModifier>() {

override fun InspectorInfo.inspectableProperties() {
debugInspectorInfo { name = "recomposeHighlighter" }
}

override fun create(): RecomposeHighlighterModifier = RecomposeHighlighterModifier()

override fun update(node: RecomposeHighlighterModifier) {
node.incrementCompositions()
}

// It's never equal, so that every recomposition triggers the update function.
override fun equals(other: Any?): Boolean = false

override fun hashCode(): Int = Objects.hash(this)
}

private class RecomposeHighlighterModifier : Modifier.Node(), DrawModifierNode {

private var timerJob: Job? = null

/**
* The total number of compositions that have occurred.
*/
private var totalCompositions: Long = 0
set(value) {
if (field == value) return
restartTimer()
field = value
invalidateDraw()
}

fun incrementCompositions() {
totalCompositions++
}

override fun onAttach() {
super.onAttach()
restartTimer()
}

override val shouldAutoInvalidate: Boolean = false

override fun onDetach() {
timerJob?.cancel()
}

/**
* Start the timeout, and reset everytime there's a recomposition.
*/
private fun restartTimer() {
if (!isAttached) return

timerJob?.cancel()
timerJob = coroutineScope.launch {
fun Modifier.recomposeHighlighter(): Modifier = this.then(recomposeModifier)

// Use a single instance + @Stable to ensure that recompositions can enable skipping optimizations
// Modifier.composed will still remember unique data per call site.
private val recomposeModifier =
Modifier.composed(inspectorInfo = debugInspectorInfo { name = "recomposeHighlighter" }) {
// The total number of compositions that have occurred. We're not using a State<> here be
// able to read/write the value without invalidating (which would cause infinite
// recomposition).
val totalCompositions = remember { arrayOf(0L) }
totalCompositions[0]++

// The value of totalCompositions at the last timeout.
val totalCompositionsAtLastTimeout = remember { mutableStateOf(0L) }

// Start the timeout, and reset everytime there's a recomposition. (Using totalCompositions
// as the key is really just to cause the timer to restart every composition).
LaunchedEffect(totalCompositions[0]) {
delay(3000)
totalCompositions = 0
invalidateDraw()
totalCompositionsAtLastTimeout.value = totalCompositions[0]
}
}

override fun ContentDrawScope.draw() {
// Draw actual content.
drawContent()
Modifier.drawWithCache {
onDrawWithContent {
// Draw actual content.
drawContent()

// Below is to draw the highlight, if necessary. A lot of the logic is copied from Modifier.border
// Below is to draw the highlight, if necessary. A lot of the logic is copied from
// Modifier.border
val numCompositionsSinceTimeout =
totalCompositions[0] - totalCompositionsAtLastTimeout.value

val hasValidBorderParams = size.minDimension > 0f
if (!hasValidBorderParams || totalCompositions <= 0) {
return
}

val (color, strokeWidthPx) =
when (totalCompositions) {
// We need at least one composition to draw, so draw the smallest border
// color in blue.
1L -> Color.Blue to 1f
// 2 compositions is _probably_ okay.
2L -> Color.Green to 2.dp.toPx()
// 3 or more compositions before timeout may indicate an issue. lerp the
// color from yellow to red, and continually increase the border size.
else -> {
lerp(
Color.Yellow.copy(alpha = 0.8f),
Color.Red.copy(alpha = 0.5f),
min(1f, (totalCompositions - 1).toFloat() / 100f),
) to totalCompositions.toInt().dp.toPx()
val hasValidBorderParams = size.minDimension > 0f
if (!hasValidBorderParams || numCompositionsSinceTimeout <= 0) {
return@onDrawWithContent
}
}

val halfStroke = strokeWidthPx / 2
val topLeft = Offset(halfStroke, halfStroke)
val borderSize = Size(size.width - strokeWidthPx, size.height - strokeWidthPx)

val fillArea = (strokeWidthPx * 2) > size.minDimension
val rectTopLeft = if (fillArea) Offset.Zero else topLeft
val size = if (fillArea) size else borderSize
val style = if (fillArea) Fill else Stroke(strokeWidthPx)

drawRect(
brush = SolidColor(color),
topLeft = rectTopLeft,
size = size,
style = style,
)
val (color, strokeWidthPx) =
when (numCompositionsSinceTimeout) {
// We need at least one composition to draw, so draw the smallest border
// color in blue.
1L -> Color.Blue to 1f
// 2 compositions is _probably_ okay.
2L -> Color.Green to 2.dp.toPx()
// 3 or more compositions before timeout may indicate an issue. lerp the
// color from yellow to red, and continually increase the border size.
else -> {
lerp(
Color.Yellow.copy(alpha = 0.8f),
Color.Red.copy(alpha = 0.5f),
min(1f, (numCompositionsSinceTimeout - 1).toFloat() / 100f)
) to numCompositionsSinceTimeout.toInt().dp.toPx()
}
}

val halfStroke = strokeWidthPx / 2
val topLeft = Offset(halfStroke, halfStroke)
val borderSize = Size(size.width - strokeWidthPx, size.height - strokeWidthPx)

val fillArea = (strokeWidthPx * 2) > size.minDimension
val rectTopLeft = if (fillArea) Offset.Zero else topLeft
val size = if (fillArea) size else borderSize
val style = if (fillArea) Fill else Stroke(strokeWidthPx)

drawRect(
brush = SolidColor(color),
topLeft = rectTopLeft,
size = size,
style = style
)
}
}
}
}
4 changes: 2 additions & 2 deletions compose/snippets/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ dependencies {
implementation(composeBom)
androidTestImplementation(composeBom)

implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.util)
implementation(libs.androidx.compose.ui.graphics)
Expand All @@ -93,8 +94,7 @@ dependencies {

implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.material3.adaptive.layout)
implementation(libs.androidx.compose.material3.adaptive.navigation)
implementation(libs.androidx.compose.material3.adaptive.navigation.suite)
implementation(libs.androidx.compose.material)

implementation(libs.androidx.compose.runtime)
Expand Down
Loading