From 242cc58a7981319c9bfc71ae8b2926ca7b397594 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Mon, 2 Sep 2024 16:29:44 +0200 Subject: [PATCH] Update project to Material Adaptive Library 3 v 1.1.0-alpha01 --- .../list-detail-compose/app/build.gradle | 29 +-- .../listdetailcompose/ui/ListDetailSample.kt | 194 ++++++++++++++---- .../app/src/main/res/drawable/ic_food.xml | 5 + .../app/src/main/res/drawable/ic_no_food.xml | 5 + .../list-detail-compose/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 6 files changed, 177 insertions(+), 62 deletions(-) create mode 100644 CanonicalLayouts/list-detail-compose/app/src/main/res/drawable/ic_food.xml create mode 100644 CanonicalLayouts/list-detail-compose/app/src/main/res/drawable/ic_no_food.xml diff --git a/CanonicalLayouts/list-detail-compose/app/build.gradle b/CanonicalLayouts/list-detail-compose/app/build.gradle index 1f3218b5e..4314c4f8d 100644 --- a/CanonicalLayouts/list-detail-compose/app/build.gradle +++ b/CanonicalLayouts/list-detail-compose/app/build.gradle @@ -20,12 +20,12 @@ plugins { android { namespace 'com.example.listdetailcompose' - compileSdk 34 + compileSdk 35 defaultConfig { applicationId "com.example.listdetailcompose" minSdk 21 - targetSdk 34 + targetSdk 35 versionCode 1 versionName "1.0" @@ -62,21 +62,22 @@ android { } dependencies { - def composeBom = platform('androidx.compose:compose-bom:2024.03.00') + def composeBom = platform('androidx.compose:compose-bom:2024.08.00') implementation(composeBom) implementation "com.google.accompanist:accompanist-adaptive:0.32.0" - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' - implementation 'androidx.activity:activity-compose:1.8.2' - implementation "androidx.compose.foundation:foundation:1.6.4" - implementation "androidx.compose.ui:ui:1.6.4" + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.4' + implementation 'androidx.activity:activity-compose:1.9.1' + implementation "androidx.compose.foundation:foundation:1.6.8" + implementation "androidx.compose.ui:ui:1.6.8" implementation "androidx.compose.ui:ui-tooling-preview" - implementation "androidx.window:window:1.2.0" - implementation 'androidx.compose.material3:material3:1.3.0-alpha03' - implementation 'androidx.compose.material3.adaptive:adaptive:1.0.0-alpha09' - implementation 'androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha09' - implementation 'androidx.compose.material3.adaptive:adaptive-navigation:1.0.0-alpha09' - implementation "androidx.compose.material3:material3-window-size-class:1.3.0-alpha03" + implementation "androidx.window:window:1.3.0" + implementation 'androidx.compose.material3:material3:1.3.0-rc01' + implementation 'androidx.compose.material3.adaptive:adaptive:1.1.0-alpha01' + implementation 'androidx.compose.material3.adaptive:adaptive-layout:1.1.0-alpha01' + implementation 'androidx.compose.material3.adaptive:adaptive-navigation:1.1.0-alpha01' + implementation "androidx.compose.material3:material3-window-size-class:1.3.0-rc01" + implementation "androidx.compose.animation:animation:1.7.0-rc01" testImplementation 'junit:junit:4.13.2' } diff --git a/CanonicalLayouts/list-detail-compose/app/src/main/java/com/example/listdetailcompose/ui/ListDetailSample.kt b/CanonicalLayouts/list-detail-compose/app/src/main/java/com/example/listdetailcompose/ui/ListDetailSample.kt index cc17b4eb5..73669cebc 100644 --- a/CanonicalLayouts/list-detail-compose/app/src/main/java/com/example/listdetailcompose/ui/ListDetailSample.kt +++ b/CanonicalLayouts/list-detail-compose/app/src/main/java/com/example/listdetailcompose/ui/ListDetailSample.kt @@ -14,14 +14,24 @@ * limitations under the License. */ +@file:OptIn(ExperimentalSharedTransitionApi::class) + package com.example.listdetailcompose.ui +import android.annotation.SuppressLint import androidx.activity.compose.BackHandler +import androidx.annotation.DrawableRes +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -35,9 +45,13 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.adaptive.layout.AnimatedPane import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole import androidx.compose.material3.adaptive.layout.PaneAdaptedValue +import androidx.compose.material3.adaptive.layout.PaneExpansionDragHandle +import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -45,8 +59,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.window.core.layout.WindowWidthSizeClass import com.example.listdetailcompose.R // Create some simple sample data @@ -56,59 +73,84 @@ private val loremIpsum = """ |Tempus quam pellentesque nec nam aliquam. Praesent semper feugiat nibh sed. Adipiscing elit duis tristique sollicitudin nibh sit. Netus et malesuada fames ac turpis egestas sed tempus urna. Quis varius quam quisque id diam vel quam. Urna duis convallis convallis tellus id interdum velit laoreet. Id eu nisl nunc mi ipsum. Fermentum dui faucibus in ornare. Nunc lobortis mattis aliquam faucibus. Vulputate mi sit amet mauris commodo quis. Porta nibh venenatis cras sed. Vitae tortor condimentum lacinia quis vel eros donec. Eu non diam phasellus vestibulum. """.trimMargin() private val sampleWords = listOf( - "Apple" to loremIpsum, - "Banana" to loremIpsum, - "Cherry" to loremIpsum, - "Date" to loremIpsum, - "Elderberry" to loremIpsum, - "Fig" to loremIpsum, - "Grape" to loremIpsum, - "Honeydew" to loremIpsum, -).map { (word, definition) -> DefinedWord(word, definition) } + "Apple" to R.drawable.ic_food, + "Banana" to R.drawable.ic_no_food, + "Cherry" to R.drawable.ic_food, + "Date" to R.drawable.ic_no_food, + "Elderberry" to R.drawable.ic_food, + "Fig" to R.drawable.ic_no_food, + "Grape" to R.drawable.ic_food, + "Honeydew" to R.drawable.ic_no_food, +).map { (word, icon) -> DefinedWord(word, icon) } private data class DefinedWord( val word: String, - val definition: String + @DrawableRes val icon: Int, + val definition: String = loremIpsum ) +@SuppressLint("UnusedContentLambdaTargetStateParameter") @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable fun ListDetailSample() { var selectedWordIndex: Int? by rememberSaveable { mutableStateOf(null) } val navigator = rememberListDetailPaneScaffoldNavigator() + val isCompact = + currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass != WindowWidthSizeClass.EXPANDED BackHandler(enabled = navigator.canNavigateBack()) { navigator.navigateBack() } - ListDetailPaneScaffold( - directive = navigator.scaffoldDirective, - value = navigator.scaffoldValue, - listPane = { - val currentSelectedWordIndex = selectedWordIndex - val isDetailVisible = - navigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded - - ListContent( - words = sampleWords.map(DefinedWord::word), - selectionState = if (isDetailVisible && currentSelectedWordIndex != null) { - SelectionVisibilityState.ShowSelection(currentSelectedWordIndex) - } else { - SelectionVisibilityState.NoSelection - }, - onIndexClick = { index -> - selectedWordIndex = index - navigator.navigateTo(ListDetailPaneScaffoldRole.Detail) + SharedTransitionLayout { +// AnimatedContent(targetState = isCompact, label = "simple sample") { + ListDetailPaneScaffold( + directive = navigator.scaffoldDirective, + value = navigator.scaffoldValue, + listPane = { + val currentSelectedWordIndex = selectedWordIndex + val isDetailVisible = + navigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded + AnimatedPane { + ListContent( + words = sampleWords, + selectionState = if (isDetailVisible && currentSelectedWordIndex != null) { + SelectionVisibilityState.ShowSelection(currentSelectedWordIndex) + } else { + SelectionVisibilityState.NoSelection + }, + onIndexClick = { index -> + selectedWordIndex = index + navigator.navigateTo(ListDetailPaneScaffoldRole.Detail) + }, + isCompact = isCompact, + isDetailVisible = isDetailVisible, + animatedVisibilityScope = this@AnimatedPane, + sharedTransitionScope = this@SharedTransitionLayout + ) } - ) - }, - detailPane = { - val definedWord = selectedWordIndex?.let(sampleWords::get) - DetailContent( - definedWord = definedWord - ) - } - ) + }, + detailPane = { + val definedWord = selectedWordIndex?.let(sampleWords::get) + val isDetailVisible = + navigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded + AnimatedPane { + DetailContent( + definedWord = definedWord, + isCompact = isCompact, + isDetailVisible = isDetailVisible, + animatedVisibilityScope = this@AnimatedPane, + sharedTransitionScope = this@SharedTransitionLayout + ) + } + }, + paneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue), + paneExpansionDragHandle = { state -> + PaneExpansionDragHandle(state, Color.Red) + } + ) + } +// } } /** @@ -137,10 +179,14 @@ sealed interface SelectionVisibilityState { */ @Composable private fun ListContent( - words: List, + words: List, selectionState: SelectionVisibilityState, onIndexClick: (index: Int) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + isCompact: Boolean, + isDetailVisible: Boolean, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope ) { LazyColumn( contentPadding = PaddingValues(vertical = 16.dp), @@ -204,12 +250,40 @@ private fun ListContent( .then(interactionModifier) .fillMaxWidth() ) { - Text( - text = word, - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - ) + Row { + if (isCompact && !isDetailVisible) { + with(sharedTransitionScope) { + val state = rememberSharedContentState(key = word.word) + + println("list-" + word.word + "-" + state.isMatchFound) + + Image( + painter = painterResource(id = word.icon), + contentDescription = word.word, + modifier = Modifier + .padding(horizontal = 8.dp) + .sharedElement( + state, + animatedVisibilityScope = animatedVisibilityScope + ) + ) + } + } else { + Image( + painter = painterResource(id = word.icon), + contentDescription = word.word, + modifier = Modifier + .padding(horizontal = 8.dp) + ) + } + Text( + text = word.word, + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + ) + } + } } } @@ -222,6 +296,10 @@ private fun ListContent( private fun DetailContent( definedWord: DefinedWord?, modifier: Modifier = Modifier, + isCompact: Boolean, + isDetailVisible: Boolean, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope ) { Column( modifier = modifier @@ -229,6 +307,32 @@ private fun DetailContent( .padding(vertical = 16.dp) ) { if (definedWord != null) { + + if (isCompact && isDetailVisible) { + with(sharedTransitionScope) { + val state = rememberSharedContentState(key = definedWord.word) + + println("detail-" + definedWord.word + "-" + state.isMatchFound) + + Image( + painter = painterResource(id = definedWord.icon), + contentDescription = definedWord.word, + modifier = Modifier + .padding(horizontal = 8.dp) + .sharedElement( + state, + animatedVisibilityScope = animatedVisibilityScope + ) + ) + } + } else { + Image( + painter = painterResource(id = definedWord.icon), + contentDescription = definedWord.word, + modifier = Modifier + .padding(horizontal = 8.dp) + ) + } Text( text = definedWord.word, style = MaterialTheme.typography.headlineMedium diff --git a/CanonicalLayouts/list-detail-compose/app/src/main/res/drawable/ic_food.xml b/CanonicalLayouts/list-detail-compose/app/src/main/res/drawable/ic_food.xml new file mode 100644 index 000000000..68632b468 --- /dev/null +++ b/CanonicalLayouts/list-detail-compose/app/src/main/res/drawable/ic_food.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/CanonicalLayouts/list-detail-compose/app/src/main/res/drawable/ic_no_food.xml b/CanonicalLayouts/list-detail-compose/app/src/main/res/drawable/ic_no_food.xml new file mode 100644 index 000000000..543584121 --- /dev/null +++ b/CanonicalLayouts/list-detail-compose/app/src/main/res/drawable/ic_no_food.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/CanonicalLayouts/list-detail-compose/build.gradle b/CanonicalLayouts/list-detail-compose/build.gradle index 525604a47..1cfdadc2d 100644 --- a/CanonicalLayouts/list-detail-compose/build.gradle +++ b/CanonicalLayouts/list-detail-compose/build.gradle @@ -14,7 +14,7 @@ * limitations under the License. */ plugins { - id 'com.android.application' version '8.3.2' apply false - id 'com.android.library' version '8.3.2' apply false + id 'com.android.application' version '8.6.0' apply false + id 'com.android.library' version '8.6.0' apply false id 'org.jetbrains.kotlin.android' version '1.9.22' apply false } diff --git a/CanonicalLayouts/list-detail-compose/gradle/wrapper/gradle-wrapper.properties b/CanonicalLayouts/list-detail-compose/gradle/wrapper/gradle-wrapper.properties index 98ab018bc..f9f3c8cbd 100644 --- a/CanonicalLayouts/list-detail-compose/gradle/wrapper/gradle-wrapper.properties +++ b/CanonicalLayouts/list-detail-compose/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed May 25 14:11:15 UTC 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME