diff --git a/.github/workflows/apply_spotless.yml b/.github/workflows/apply_spotless.yml index db01dad5..5ba1f6fe 100644 --- a/.github/workflows/apply_spotless.yml +++ b/.github/workflows/apply_spotless.yml @@ -44,10 +44,13 @@ jobs: - name: Run spotlessApply run: ./gradlew :compose:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace + - name: Run spotlessApply for Wear + run: ./gradlew :wear:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace + + - name: Run spotlessApply for Misc + run: ./gradlew :misc: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 with: - commit_message: Apply Spotless - - - name: Run spotlessApply for Wear - run: ./gradlew :wear:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace + commit_message: Apply Spotless \ No newline at end of file diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/images/AnimateImageSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/images/AnimateImageSnippets.kt new file mode 100644 index 00000000..2eab0220 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/images/AnimateImageSnippets.kt @@ -0,0 +1,118 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.images + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.painter.ColorPainter +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp + +// [START android_compose_images_imageresizeonscrollexample] +@Composable +fun ImageResizeOnScrollExample( + modifier: Modifier = Modifier, + maxImageSize: Dp = 300.dp, + minImageSize: Dp = 100.dp +) { + var currentImageSize by remember { mutableStateOf(maxImageSize) } + var imageScale by remember { mutableFloatStateOf(1f) } + + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + // Calculate the change in image size based on scroll delta + val delta = available.y + val newImageSize = currentImageSize + delta.dp + val previousImageSize = currentImageSize + + // Constrain the image size within the allowed bounds + currentImageSize = newImageSize.coerceIn(minImageSize, maxImageSize) + val consumed = currentImageSize - previousImageSize + + // Calculate the scale for the image + imageScale = currentImageSize / maxImageSize + + // Return the consumed scroll amount + return Offset(0f, consumed.value) + } + } + } + + Box(Modifier.nestedScroll(nestedScrollConnection)) { + LazyColumn( + Modifier + .fillMaxWidth() + .padding(15.dp) + .offset { + IntOffset(0, currentImageSize.roundToPx()) + } + ) { + // Placeholder list items + items(100, key = { it }) { + Text( + text = "Item: $it", + style = MaterialTheme.typography.bodyLarge + ) + } + } + + Image( + painter = ColorPainter(Color.Red), + contentDescription = "Red color image", + Modifier + .size(maxImageSize) + .align(Alignment.TopCenter) + .graphicsLayer { + scaleX = imageScale + scaleY = imageScale + // Center the image vertically as it scales + translationY = -(maxImageSize.toPx() - currentImageSize.toPx()) / 2f + } + ) + } +} +// [END android_compose_images_imageresizeonscrollexample] + +@Preview(showBackground = true) +@Composable +private fun ImageSizeOnScrollScreenPreview() { + ImageResizeOnScrollExample() +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/HtmlStyling.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/HtmlStyling.kt new file mode 100644 index 00000000..607fdf46 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/HtmlStyling.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.text + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.fromHtml +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview + +// [START android_compose_text_annotatedhtmlstringwithlink] +@Composable +fun AnnotatedHtmlStringWithLink( + modifier: Modifier = Modifier, + htmlText: String = """ +
+ Build better apps faster with Jetpack Compose +
+ """.trimIndent() +) { + Text( + AnnotatedString.fromHtml( + htmlText, + linkStyles = TextLinkStyles( + style = SpanStyle( + textDecoration = TextDecoration.Underline, + fontStyle = FontStyle.Italic, + color = Color.Blue + ) + ) + ), + modifier + ) +} +// [END android_compose_text_annotatedhtmlstringwithlink] + +@Preview(showBackground = true) +@Composable +private fun AnnotatedHtmlStringWithLinkPreview() { + AnnotatedHtmlStringWithLink() +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7d649bdc..6d03c099 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,20 +1,20 @@ [versions] accompanist = "0.36.0" -androidGradlePlugin = "8.7.0" -androidx-activity-compose = "1.9.2" +androidGradlePlugin = "8.7.2" +androidx-activity-compose = "1.9.3" androidx-appcompat = "1.7.0" -androidx-compose-bom = "2024.09.03" +androidx-compose-bom = "2024.10.01" androidx-compose-ui-test = "1.7.0-alpha08" -androidx-constraintlayout = "2.1.4" -androidx-constraintlayout-compose = "1.0.1" +androidx-constraintlayout = "2.2.0" +androidx-constraintlayout-compose = "1.1.0" androidx-coordinator-layout = "1.2.0" -androidx-corektx = "1.13.1" +androidx-corektx = "1.15.0" androidx-emoji2-views = "1.5.0" -androidx-fragment-ktx = "1.8.4" +androidx-fragment-ktx = "1.8.5" androidx-glance-appwidget = "1.1.1" -androidx-lifecycle-compose = "2.8.6" -androidx-lifecycle-runtime-compose = "2.8.6" -androidx-navigation = "2.8.2" +androidx-lifecycle-compose = "2.8.7" +androidx-lifecycle-runtime-compose = "2.8.7" +androidx-navigation = "2.8.3" androidx-paging = "3.3.2" androidx-test = "1.6.1" androidx-test-espresso = "3.6.1" @@ -22,8 +22,8 @@ androidx-window = "1.3.0" androidxHiltNavigationCompose = "1.2.0" coil = "2.7.0" # @keep -compileSdk = "34" -compose-latest = "1.7.3" +compileSdk = "35" +compose-latest = "1.7.5" composeUiTooling = "1.4.0" coreSplashscreen = "1.0.1" coroutines = "1.9.0" @@ -33,13 +33,13 @@ gradle-versions = "0.51.0" hilt = "2.52" horologist = "0.6.20" junit = "4.13.2" -kotlin = "2.0.20" +kotlin = "2.0.21" kotlinxSerializationJson = "1.7.3" -ksp = "2.0.20-1.0.25" -maps-compose = "6.1.2" -material = "1.13.0-alpha06" +ksp = "2.0.21-1.0.26" +maps-compose = "6.2.0" +material = "1.13.0-alpha07" material3-adaptive = "1.0.0" -material3-adaptive-navigation-suite = "1.3.0" +material3-adaptive-navigation-suite = "1.3.1" media3 = "1.4.1" # @keep minSdk = "21" @@ -47,7 +47,7 @@ playServicesWearable = "18.2.0" recyclerview = "1.3.2" # @keep targetSdk = "34" -version-catalog-update = "0.8.4" +version-catalog-update = "0.8.5" wearComposeFoundation = "1.4.0" wearComposeMaterial = "1.4.0" @@ -108,7 +108,7 @@ androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-te androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" } androidx-test-runner = "androidx.test:runner:1.6.2" androidx-window-core = { module = "androidx.window:window-core", version.ref = "androidx-window" } -androidx-work-runtime-ktx = "androidx.work:work-runtime-ktx:2.9.1" +androidx-work-runtime-ktx = "androidx.work:work-runtime-ktx:2.10.0" coil-kt-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "wearComposeFoundation" } compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "wearComposeMaterial" } diff --git a/misc/src/main/java/com/example/snippets/BroadcastReceiverJavaSnippets.java b/misc/src/main/java/com/example/snippets/BroadcastReceiverJavaSnippets.java index bd06de80..08363995 100644 --- a/misc/src/main/java/com/example/snippets/BroadcastReceiverJavaSnippets.java +++ b/misc/src/main/java/com/example/snippets/BroadcastReceiverJavaSnippets.java @@ -4,7 +4,9 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Bundle; +import androidx.activity.ComponentActivity; import androidx.core.content.ContextCompat; import java.util.Objects; @@ -94,5 +96,25 @@ public void sendBroadcast(String newData, boolean usePermission) { } } } -} + /** @noinspection InnerClassMayBeStatic*/ + // [START android_broadcast_receiver_13_activity_java] + class MyActivity extends ComponentActivity { + MyBroadcastReceiver myBroadcastReceiver; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // [START_EXCLUDE] + IntentFilter filter = new IntentFilter("com.example.snippets.ACTION_UPDATE_DATA"); + boolean listenToBroadcastsFromOtherApps = false; + int receiverFlags = listenToBroadcastsFromOtherApps + ? ContextCompat.RECEIVER_EXPORTED + : ContextCompat.RECEIVER_NOT_EXPORTED; + // [END_EXCLUDE] + ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags); + // Set content + } + } + // [END android_broadcast_receiver_13_activity_java] +} diff --git a/misc/src/main/java/com/example/snippets/BroadcastReceiverSnippets.kt b/misc/src/main/java/com/example/snippets/BroadcastReceiverSnippets.kt index 8c4e003f..9c4c5f9c 100644 --- a/misc/src/main/java/com/example/snippets/BroadcastReceiverSnippets.kt +++ b/misc/src/main/java/com/example/snippets/BroadcastReceiverSnippets.kt @@ -4,6 +4,9 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -24,6 +27,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.hilt.navigation.compose.hiltViewModel @@ -190,3 +194,62 @@ class MyBroadcastReceiverWithPermission : BroadcastReceiver() { // no-op, only used to demonstrate manifest registration of receiver with permission } } + +// Ignore following code - it's only used to demonstrate best practices, not part of the sample +@Suppress("unused") +// [START android_broadcast_receiver_13_activity] +class MyActivity : ComponentActivity() { + private val myBroadcastReceiver = MyBroadcastReceiver() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // [START_EXCLUDE] + val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA") + val listenToBroadcastsFromOtherApps = false + val receiverFlags = if (listenToBroadcastsFromOtherApps) { + ContextCompat.RECEIVER_EXPORTED + } else { + ContextCompat.RECEIVER_NOT_EXPORTED + } + // [END_EXCLUDE] + ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags) + setContent { MyApp() } + } + + override fun onDestroy() { + super.onDestroy() + // When you forget to unregister your receiver here, you're causing a leak! + this.unregisterReceiver(myBroadcastReceiver) + } +} +// [END android_broadcast_receiver_13_activity] + +@Composable +fun MyApp() {} + +@Suppress("unused") +// [START android_broadcast_receiver_14_stateless] +@Composable +fun MyStatefulScreen() { + val myBroadcastReceiver = remember { MyBroadcastReceiver()} + val context = LocalContext.current + LifecycleStartEffect(true) { + // [START_EXCLUDE] + val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA") + val listenToBroadcastsFromOtherApps = false + val flags = if (listenToBroadcastsFromOtherApps) { + ContextCompat.RECEIVER_EXPORTED + } else { + ContextCompat.RECEIVER_NOT_EXPORTED + } + // [END_EXCLUDE] + ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, flags) + onStopOrDispose { context.unregisterReceiver(myBroadcastReceiver) } + } + MyStatelessScreen() +} + +@Composable +fun MyStatelessScreen() { + // Implement your screen +} diff --git a/misc/src/main/java/com/example/snippets/MainActivity.kt b/misc/src/main/java/com/example/snippets/MainActivity.kt index 3ddc00e4..f2c660fa 100644 --- a/misc/src/main/java/com/example/snippets/MainActivity.kt +++ b/misc/src/main/java/com/example/snippets/MainActivity.kt @@ -15,12 +15,9 @@ import com.example.snippets.navigation.Destination import com.example.snippets.navigation.LandingScreen import com.example.snippets.ui.theme.SnippetsTheme import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { - @Inject - lateinit var dataRepository: DataRepository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts index 9725e539..13fff794 100644 --- a/wear/build.gradle.kts +++ b/wear/build.gradle.kts @@ -6,7 +6,7 @@ plugins { android { namespace = "com.example.wear" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "com.example.wear"