diff --git a/.github/workflows/android_test.yml b/.github/workflows/android_test.yml index 7a10ff9f9a..c16a75afa6 100644 --- a/.github/workflows/android_test.yml +++ b/.github/workflows/android_test.yml @@ -18,7 +18,7 @@ jobs: TERM: dumb steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: 'true' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4a2c24bc1a..89290edb7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 40 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: 'true' @@ -55,7 +55,7 @@ jobs: timeout-minutes: 40 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: 'true' @@ -93,7 +93,7 @@ jobs: timeout-minutes: 40 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: 'true' @@ -134,7 +134,7 @@ jobs: TERM: dumb steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: 'true' @@ -160,7 +160,7 @@ jobs: timeout-minutes: 40 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: 'true' diff --git a/.github/workflows/build_nightly.yml b/.github/workflows/build_nightly.yml index 28559c4320..b97e8f5e88 100644 --- a/.github/workflows/build_nightly.yml +++ b/.github/workflows/build_nightly.yml @@ -11,7 +11,7 @@ jobs: timeout-minutes: 40 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: 'true' @@ -27,7 +27,7 @@ jobs: - name: Generate cache key run: ./checksum.sh checksum.txt - - uses: actions/cache@v5 + - uses: actions/cache@v6 with: path: | ~/.gradle/caches/modules-* diff --git a/.github/workflows/fixup.yml b/.github/workflows/fixup.yml index 261567889f..f3db17a4c7 100644 --- a/.github/workflows/fixup.yml +++ b/.github/workflows/fixup.yml @@ -9,7 +9,7 @@ jobs: timeout-minutes: 40 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: 'true' diff --git a/.github/workflows/macrobenchmark.yml b/.github/workflows/macrobenchmark.yml index 458fd0909f..70f1e196c3 100644 --- a/.github/workflows/macrobenchmark.yml +++ b/.github/workflows/macrobenchmark.yml @@ -11,7 +11,7 @@ jobs: TERM: dumb steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: 'true' @@ -27,7 +27,7 @@ jobs: - name: Generate cache key run: ./checksum.sh checksum.txt - - uses: actions/cache@v5 + - uses: actions/cache@v6 with: path: | ~/.gradle/caches/modules-* diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 81b82a31e5..625341a833 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -15,7 +15,7 @@ jobs: TERM: dumb steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: 'true' @@ -31,7 +31,7 @@ jobs: - name: Generate cache key run: ./checksum.sh checksum.txt - - uses: actions/cache@v5 + - uses: actions/cache@v6 with: path: | ~/.gradle/caches/modules-* @@ -40,7 +40,7 @@ jobs: key: gradle-${{ hashFiles('checksum.txt') }} - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.x' diff --git a/README.md b/README.md index eddc061e2f..a34d4d26eb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The currently maintained branches of Horologist are. | Version | Branch | Min SDK (Wear) | Kotlin/JDK | Description | |---------|---------------|----------------|------------|-------------------------------------------------------------------------------------| | 0.7.x | release-0.7.x | 26 | 2.2.0 / 17 | Wear Compose 1.5.x, Compose 1.8.x and generally latest stable Androidx. | -| 0.8.x | main | 26 | 2.x / 17 | Wear Compose 1.6.x, Compose 1.9.x and generally latest relevant alphas of Androidx. | +| 0.8.x | main | 26 | 2.2 / 17 | Wear Compose 1.6.x, Compose 1.9.x and generally latest relevant alphas of Androidx. | Maintenance branches will not delete existing APIs and they should remain stable. However the main branch will actively update to incorporate new API guidance, removing or changing diff --git a/auth/composables-material3/build.gradle.kts b/auth/composables-material3/build.gradle.kts index 4926a0271e..3336a22c66 100644 --- a/auth/composables-material3/build.gradle.kts +++ b/auth/composables-material3/build.gradle.kts @@ -84,6 +84,7 @@ dependencies { implementation(libs.compose.material.iconsext) implementation(libs.compose.material3) implementation(libs.compose.ui.graphics) + implementation(libs.androidx.graphics.shapes) implementation(libs.compose.ui.text) implementation(libs.compose.ui.unit) implementation(libs.kotlin.stdlib) diff --git a/auth/composables-material3/src/main/java/com/google/android/horologist/auth/composables/material3/screens/PillWorkaround.kt b/auth/composables-material3/src/main/java/com/google/android/horologist/auth/composables/material3/screens/PillWorkaround.kt new file mode 100644 index 0000000000..6e25fb9759 --- /dev/null +++ b/auth/composables-material3/src/main/java/com/google/android/horologist/auth/composables/material3/screens/PillWorkaround.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2026 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.google.android.horologist.auth.composables.material3.screens + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.center +import androidx.compose.ui.graphics.Matrix +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.asComposePath +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import androidx.graphics.shapes.CornerRounding +import androidx.graphics.shapes.RoundedPolygon +import androidx.graphics.shapes.toPath +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.sin + +internal data class PointNRound(val o: Offset, val r: CornerRounding = CornerRounding.Unrounded) + +private fun Float.toRadians(): Float = (this * Math.PI / 180f).toFloat() + +private fun Offset.angleDegrees(): Float = + atan2(y, x) * 180f / Math.PI.toFloat() + +private fun Offset.rotateDegrees(angle: Float, center: Offset): Offset { + val rad = angle.toRadians() + val cos = cos(rad) + val sin = sin(rad) + val dx = x - center.x + val dy = y - center.y + return Offset( + dx * cos - dy * sin + center.x, + dx * sin + dy * cos + center.y + ) +} + +internal fun doRepeat(points: List, reps: Int, center: Offset, mirroring: Boolean) = + if (mirroring) { + buildList { + val angles = points.map { (it.o - center).angleDegrees() } + val distances = points.map { (it.o - center).getDistance() } + val actualReps = reps * 2 + val sectionAngle = 360f / actualReps + repeat(actualReps) { + points.indices.forEach { index -> + val i = if (it % 2 == 0) index else points.lastIndex - index + if (i > 0 || it % 2 == 0) { + val a = + (sectionAngle * it + + if (it % 2 == 0) angles[i] else sectionAngle - angles[i] + 2 * angles[0]) + .toRadians() + val finalPoint = Offset(cos(a), sin(a)) * distances[i] + center + add(PointNRound(finalPoint, points[i].r)) + } + } + } + } + } else { + points.size.let { np -> + (0 until np * reps).map { + val point = points[it % np].o.rotateDegrees((it / np) * 360f / reps, center) + PointNRound(point, points[it % np].r) + } + } + } + +internal fun customPolygon( + points: List, + reps: Int, + mirroring: Boolean, + center: Offset = Offset(0.5f, 0.5f) +): RoundedPolygon { + val repeatedPoints = doRepeat(points, reps, center, mirroring) + val vertices = FloatArray(repeatedPoints.size * 2) + val roundings = mutableListOf() + for (i in repeatedPoints.indices) { + vertices[2 * i] = repeatedPoints[i].o.x + vertices[2 * i + 1] = repeatedPoints[i].o.y + roundings.add(repeatedPoints[i].r) + } + return RoundedPolygon( + vertices = vertices, + perVertexRounding = roundings, + centerX = center.x, + centerY = center.y + ) +} + +internal fun pill(): RoundedPolygon { + return customPolygon( + listOf( + PointNRound(Offset(0.961f, 0.039f), CornerRounding(0.426f)), + PointNRound(Offset(1.001f, 0.428f)), + PointNRound(Offset(1.000f, 0.609f), CornerRounding(1.000f)), + ), + reps = 2, + mirroring = true, + ) +} + +@Composable +internal fun RoundedPolygon.toShape(): Shape { + return remember(this) { + object : Shape { + private val shapePath: Path = toPath().asComposePath() + private val workPath: Path = Path() + + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density, + ): Outline { + workPath.rewind() + workPath.addPath(shapePath) + val scaleMatrix = Matrix().apply { scale(x = size.width, y = size.height) } + workPath.transform(scaleMatrix) + workPath.translate(size.center - workPath.getBounds().center) + return Outline.Generic(workPath) + } + } + } +} diff --git a/auth/composables-material3/src/main/java/com/google/android/horologist/auth/composables/material3/screens/SignedInConfirmationDialog.kt b/auth/composables-material3/src/main/java/com/google/android/horologist/auth/composables/material3/screens/SignedInConfirmationDialog.kt index 2bd07b2fb3..0c24d32173 100644 --- a/auth/composables-material3/src/main/java/com/google/android/horologist/auth/composables/material3/screens/SignedInConfirmationDialog.kt +++ b/auth/composables-material3/src/main/java/com/google/android/horologist/auth/composables/material3/screens/SignedInConfirmationDialog.kt @@ -29,8 +29,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.outlined.AccountCircle import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialShapes -import androidx.compose.material3.toShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -161,11 +159,15 @@ internal fun SignedInConfirmationDialogContent( horizontalAlignment = Alignment.CenterHorizontally, ) { val hasName = !name.isNullOrEmpty() + // TODO: Revert to MaterialShapes.Pill.toShape() once we can upgrade compose-material3 version. + // Using graphics-shapes directly as a workaround to avoid compileSdk 37. + val pillPolygon = remember { pill() } + val pillShape = pillPolygon.toShape() Box( modifier = Modifier .padding(4.dp) .size(96.dp) - .clip(MaterialShapes.Pill.toShape()) + .clip(pillShape) .background(MaterialTheme.colorScheme.surfaceContainer), contentAlignment = Alignment.Center, ) { diff --git a/build.gradle.kts b/build.gradle.kts index b59a75f200..de25d1d835 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -102,12 +102,12 @@ allprojects { exclude(group = "com.google.protobuf", module = "protobuf-java") resolutionStrategy { dependencySubstitution { - substitute(module("com.google.protobuf:protobuf-java")).using(module("com.google.protobuf:protobuf-javalite:4.34.1")) + substitute(module("com.google.protobuf:protobuf-java")).using(module("com.google.protobuf:protobuf-javalite:4.35.1")) } - force("io.grpc:grpc-stub:1.80.0") - force("io.grpc:grpc-protobuf-lite:1.80.0") - force("io.grpc:grpc-android:1.80.0") - force("io.grpc:grpc-binder:1.80.0") + force("io.grpc:grpc-stub:1.82.2") + force("io.grpc:grpc-protobuf-lite:1.82.2") + force("io.grpc:grpc-android:1.82.2") + force("io.grpc:grpc-binder:1.82.2") } } @@ -142,6 +142,7 @@ subprojects { allWarningsAsErrors = true } jvmTarget.set(JvmTarget.JVM_17) + languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) freeCompilerArgs.addAll( listOf( // Allow use of @OptIn diff --git a/compose-material/build.gradle.kts b/compose-material/build.gradle.kts index f571374f51..74de7b97ec 100644 --- a/compose-material/build.gradle.kts +++ b/compose-material/build.gradle.kts @@ -44,8 +44,6 @@ android { packaging { resources { excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") } } - sourceSets.getByName("main") { assets.srcDir("src/main/assets") } - testOptions { unitTests { isIncludeAndroidResources = true } animationsDisabled = true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1e24355199..21343dbadd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,70 +7,70 @@ androidx-concurrent = "1.3.0" androidx-constraintlayout-compose = "1.1.1" androidx-datastore = "1.2.1" androidx-graphics = "1.1.0" -androidx-health-services = "1.1.0-rc01" +androidx-health-services = "1.1.0-rc02" androidx-hilt = "1.3.0" -androidx-media3 = "1.10.0" +androidx-media3 = "1.10.1" androidx-test-espresso = "3.7.0" androidx-test-ext = "1.3.0" androidx-test-runner = "1.7.0" androidx-wear-watchface = "1.3.0" androidx-wear-watchface-rendering = "1.2.1" androidxActivity = "1.13.0" -androidxComposeBom = "2026.03.01" +androidxComposeBom = "2026.04.01" androidxComposeRemote = "1.0.0-alpha15" androidxCore = "1.18.0" androidxLifecycle = "2.10.0" androidxNavigation = "2.9.8" -androidxPhoneInteractions = "1.1.0" +androidxPhoneInteractions = "1.1.1" androidxRemoteInteractions = "1.2.0" androidxStartup = "1.2.0" androidxTracing = "1.3.0" androidxWear = "1.4.0" androidxWork = "2.11.2" -androidxprotolayout = "1.4.0" -androidxtiles = "1.6.0" +androidxprotolayout = "1.4.1" +androidxtiles = "1.6.1" annotation = "1.0.1" app-cash-turbine = "1.2.1" appcompat = "1.7.1" -com-squareup-okhttp3 = "5.3.2" +com-squareup-okhttp3 = "5.4.0" com-squareup-retrofit2 = "3.0.0" -compose-animation = "1.11.4" -compose-material3 = "1.5.0-alpha18" -composeAiTools = "0.16.48" +compose-animation = "1.11.0" +compose-material3 = "1.4.0" +composeAiTools = "0.17.2" composesnapshot = "-" -dependencyAnalysis = "3.7.0" +dependencyAnalysis = "3.17.0" desugar_jdk_libs = "2.1.5" dokka = "2.2.0" -googledagger = "2.59.2" -gradlePublishPlugin = "0.36.0" -grpcStub = "1.80.0" +googledagger = "2.60.1" +gradlePublishPlugin = "0.37.0" +grpcStub = "1.82.2" io-coil-kt = "2.7.0" junit = "4.13.2" -kotlin = "2.3.21" +kotlin = "2.4.10" kotlinxCoroutine = "1.11.0" kotlinxSerialization = "1.11.0" -ksp = "2.3.9" +ksp = "2.3.10" ktlint = "1.8.0" -material = "1.13.0" +material = "1.14.0" metalava = "0.5.0" moshi = "1.15.2" okio = "3.17.0" org-robolectric = "4.16.1" -ossLicensesPlugin = "0.11.0" +ossLicensesPlugin = "0.13.0" osslicenses = "0.10.0" -playServicesAuth = "21.5.1" +playServicesAuth = "21.6.0" playServicesOssLicenses = "17.5.1" # Stay on 4.26.1 due to https://github.com/firebase/firebase-android-sdk/issues/5997 protobuf = "4.34.1" -protobuf-gen-grpc-java = "1.80.0" +protobuf-gen-grpc-java = "1.82.2" protobuf-gen-grpc-kotlin = "1.5.0:jdk8@jar" protobuf-gen-javalite = "3.0.0" -roborazzi = "1.59.0" +roborazzi = "1.70.0" room = "2.8.4" -runtimeTracing = "1.11.0" -spotless = "8.4.0" +runtimeTracing = "1.11.4" +spotless = "8.8.0" ktfmt = "0.26.0" -tiles-tooling-preview = "1.6.0" +tiles-tooling-preview = "1.6.1" truth = "1.4.5" wearInput = "1.2.0" wearToolingPreview = "1.0.0" @@ -78,7 +78,7 @@ wearcompose = "1.6.2" glanceWear = "1.0.0-alpha14" composeRemote = "1.0.0-alpha15" wearComposeRemote = "1.0.0-alpha07" -agp = "9.1.0" +agp = "9.3.0" [libraries] accessibility-test-framework = { module = "com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework", version.ref = "accessibilityTestFramework" } @@ -129,7 +129,7 @@ androidx-metrics-performance = "androidx.metrics:metrics-performance:1.0.0" androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidxNavigation" } androidx-navigation-runtime = { module = "androidx.navigation:navigation-runtime", version.ref = "androidxNavigation" } androidx-navigation-testing = { module = "androidx.navigation:navigation-testing", version.ref = "androidxNavigation" } -androidx-paging = "androidx.paging:paging-compose:3.4.2" +androidx-paging = "androidx.paging:paging-compose:3.5.0" androidx-palette-ktx = "androidx.palette:palette-ktx:1.0.0" androidx-runtime-tracing = { module = "androidx.compose.runtime:runtime-tracing", version.ref = "runtimeTracing" } androidx-startup = { module = "androidx.startup:startup-runtime", version.ref = "androidxStartup" } @@ -138,7 +138,7 @@ androidx-test-ext = { module = "androidx.test.ext:junit", version.ref = "android androidx-test-ext-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx-test-ext" } androidx-test-rules = "androidx.test:rules:1.7.0" androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidx-test-runner" } -androidx-test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0" +androidx-test-uiautomator = "androidx.test.uiautomator:uiautomator:2.4.0" androidx-tracing-ktx = { module = "androidx.tracing:tracing-ktx", version.ref = "androidxTracing" } androidx-wear = { module = "androidx.wear:wear", version.ref = "androidxWear" } androidx-wear-compose-material3 = { module = "androidx.wear.compose:compose-material3", version.ref = "wearcompose" } @@ -198,16 +198,16 @@ dagger-hiltandroidplugin = { module = "com.google.dagger:hilt-android-gradle-plu dagger-hiltandroidtesting = { module = "com.google.dagger:hilt-android-testing", version.ref = "googledagger" } desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" } dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } -google-genai = "com.google.genai:google-genai:1.51.0" +google-genai = "com.google.genai:google-genai:1.62.0" gradleMavenPublishPlugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "gradlePublishPlugin" } grpc-stub = { module = "io.grpc:grpc-stub", version.ref = "grpcStub" } hilt-ext-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "androidx-hilt" } hilt-ext-work = { module = "androidx.hilt:hilt-work", version.ref = "androidx-hilt" } hilt-navigationcompose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "androidx-hilt" } -io-grpc-grpc-android = "io.grpc:grpc-android:1.80.0" -io-grpc-grpc-binder = "io.grpc:grpc-binder:1.80.0" +io-grpc-grpc-android = "io.grpc:grpc-android:1.82.2" +io-grpc-grpc-binder = "io.grpc:grpc-binder:1.82.2" io-grpc-grpc-kotlin = "io.grpc:grpc-kotlin-stub:1.5.0" -io-grpc-protobuf-lite = "io.grpc:grpc-protobuf-lite:1.80.0" +io-grpc-protobuf-lite = "io.grpc:grpc-protobuf-lite:1.82.2" junit = { module = "junit:junit", version.ref = "junit" } kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } @@ -231,7 +231,7 @@ osslicenses-wear-compose-material = { module = "io.github.droibit.oss-licenses-a play-services-oss-licenses = { module = "com.google.android.gms:play-services-oss-licenses", version.ref = "playServicesOssLicenses" } playservices-auth = { module = "com.google.android.gms:play-services-auth", version.ref = "playServicesAuth" } playservices-base = "com.google.android.gms:play-services-base:18.10.0" -playservices-wearable = "com.google.android.gms:play-services-wearable:19.0.0" +playservices-wearable = "com.google.android.gms:play-services-wearable:20.0.1" protobuf-kotlin-lite = { module = "com.google.protobuf:protobuf-kotlin-lite", version.ref = "protobuf" } protobuf-protoc-gen-grpc-java = { module = "io.grpc:protoc-gen-grpc-java", version.ref = "protobuf-gen-grpc-java"} protobuf-protoc-gen-grpc-kotlin = { module = "io.grpc:protoc-gen-grpc-kotlin", version.ref = "protobuf-gen-grpc-kotlin"} @@ -255,8 +255,8 @@ wearcompose-foundation = { module = "androidx.wear.compose:compose-foundation", wearcompose-material = { module = "androidx.wear.compose:compose-material", version.ref = "wearcompose" } wearcompose-navigation = { module = "androidx.wear.compose:compose-navigation", version.ref = "wearcompose" } wearcompose-navigation3 = { module = "androidx.wear.compose:compose-navigation3", version = "1.6.2" } -navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version = "1.1.1" } -navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version = "1.1.1" } +navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version = "1.1.4" } +navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version = "1.1.4" } wearcompose-tooling = { module = "androidx.wear.compose:compose-ui-tooling", version.ref = "wearcompose" } [plugins] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c82ad3ff01..1adfd2e54c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/images/base/build.gradle.kts b/images/base/build.gradle.kts index d1699d062e..889afca296 100644 --- a/images/base/build.gradle.kts +++ b/images/base/build.gradle.kts @@ -39,8 +39,6 @@ android { packaging { resources { excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") } } - sourceSets.getByName("main") { assets.srcDir("src/main/assets") } - testOptions { unitTests { isIncludeAndroidResources = true } animationsDisabled = true diff --git a/images/coil/build.gradle.kts b/images/coil/build.gradle.kts index 0585c4274a..41ca580e99 100644 --- a/images/coil/build.gradle.kts +++ b/images/coil/build.gradle.kts @@ -40,8 +40,6 @@ android { packaging { resources { excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") } } - sourceSets.getByName("main") { assets.srcDir("src/main/assets") } - testOptions { unitTests { isIncludeAndroidResources = true } animationsDisabled = true diff --git a/media/audio-ui-material3/build.gradle.kts b/media/audio-ui-material3/build.gradle.kts index 904605b512..b96eb0735d 100644 --- a/media/audio-ui-material3/build.gradle.kts +++ b/media/audio-ui-material3/build.gradle.kts @@ -44,8 +44,6 @@ android { animationsDisabled = true } - sourceSets.getByName("main") { assets.srcDir("src/main/assets") } - lint { disable += listOf("MissingTranslation", "ExtraTranslation") checkReleaseBuilds = false diff --git a/media/audio-ui-model/build.gradle.kts b/media/audio-ui-model/build.gradle.kts index e30aae8b61..7ea10c3ec2 100644 --- a/media/audio-ui-model/build.gradle.kts +++ b/media/audio-ui-model/build.gradle.kts @@ -44,8 +44,6 @@ android { animationsDisabled = true } - sourceSets.getByName("main") { assets.srcDir("src/main/assets") } - lint { disable += listOf("MissingTranslation", "ExtraTranslation") checkReleaseBuilds = false diff --git a/media/audio-ui/build.gradle.kts b/media/audio-ui/build.gradle.kts index daa618ba29..cbe5478c57 100644 --- a/media/audio-ui/build.gradle.kts +++ b/media/audio-ui/build.gradle.kts @@ -45,8 +45,6 @@ android { animationsDisabled = true } - sourceSets.getByName("main") { assets.srcDir("src/main/assets") } - lint { disable += listOf("MissingTranslation", "ExtraTranslation") checkReleaseBuilds = false diff --git a/media/audio/build.gradle.kts b/media/audio/build.gradle.kts index e68e3d23de..ee26885cfc 100644 --- a/media/audio/build.gradle.kts +++ b/media/audio/build.gradle.kts @@ -39,14 +39,6 @@ android { testOptions { unitTests { isIncludeAndroidResources = true } } - sourceSets.getByName("test") { - java.srcDir("src/sharedTest/kotlin") - res.srcDir("src/sharedTest/res") - } - sourceSets.getByName("androidTest") { - java.srcDir("src/sharedTest/kotlin") - res.srcDir("src/sharedTest/res") - } lint { checkReleaseBuilds = false textReport = true @@ -54,6 +46,27 @@ android { namespace = "com.google.android.horologist.audio" } +// Configured outside the `android { }` block to use the public-DSL +// LibraryExtension type, whose AndroidSourceSet implementation is +// compatible with AGP 9.2+. +project.extensions + .getByType(com.android.build.api.dsl.LibraryExtension::class.java) + .sourceSets + .named("test") + .configure { + java.srcDir("src/sharedTest/kotlin") + res.srcDir("src/sharedTest/res") + } + +project.extensions + .getByType(com.android.build.api.dsl.LibraryExtension::class.java) + .sourceSets + .named("androidTest") + .configure { + java.srcDir("src/sharedTest/kotlin") + res.srcDir("src/sharedTest/res") + } + project.tasks.withType().configureEach { // Workaround for https://youtrack.jetbrains.com/issue/KT-37652 if (!this.name.endsWith("TestKotlin") && !this.name.startsWith("compileDebug")) { diff --git a/media/sync/build.gradle.kts b/media/sync/build.gradle.kts index c9bedda40c..01a2a7bea9 100644 --- a/media/sync/build.gradle.kts +++ b/media/sync/build.gradle.kts @@ -39,8 +39,6 @@ android { packaging { resources { excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") } } - sourceSets.getByName("main") { assets.srcDir("src/main/assets") } - testOptions { unitTests { isIncludeAndroidResources = true } animationsDisabled = true diff --git a/media/ui-material3/build.gradle.kts b/media/ui-material3/build.gradle.kts index f4984cd771..18131df67c 100644 --- a/media/ui-material3/build.gradle.kts +++ b/media/ui-material3/build.gradle.kts @@ -41,8 +41,6 @@ android { packaging { resources { excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") } } - sourceSets.getByName("main") { assets.srcDir("src/main/assets") } - testOptions { unitTests { isIncludeAndroidResources = true } animationsDisabled = true diff --git a/media/ui-model/build.gradle.kts b/media/ui-model/build.gradle.kts index dcc7336f8c..fba248d4b9 100644 --- a/media/ui-model/build.gradle.kts +++ b/media/ui-model/build.gradle.kts @@ -41,8 +41,6 @@ android { packaging { resources { excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") } } - sourceSets.getByName("main") { assets.srcDir("src/main/assets") } - testOptions { unitTests { isIncludeAndroidResources = true } animationsDisabled = true diff --git a/media/ui/build.gradle.kts b/media/ui/build.gradle.kts index ba85f2fd68..a0c245d256 100644 --- a/media/ui/build.gradle.kts +++ b/media/ui/build.gradle.kts @@ -42,8 +42,6 @@ android { packaging { resources { excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") } } - sourceSets.getByName("main") { assets.srcDir("src/main/assets") } - testOptions { unitTests { isIncludeAndroidResources = true } animationsDisabled = true diff --git a/network-awareness/okhttp/src/main/java/com/google/android/horologist/networks/okhttp/impl/FailedCall.kt b/network-awareness/okhttp/src/main/java/com/google/android/horologist/networks/okhttp/impl/FailedCall.kt index 55a17c84fd..1e7237d384 100644 --- a/network-awareness/okhttp/src/main/java/com/google/android/horologist/networks/okhttp/impl/FailedCall.kt +++ b/network-awareness/okhttp/src/main/java/com/google/android/horologist/networks/okhttp/impl/FailedCall.kt @@ -21,8 +21,10 @@ import com.google.android.horologist.networks.okhttp.impl.RequestTypeHolder.Comp import com.google.android.horologist.networks.okhttp.requestType import okhttp3.Call import okhttp3.Callback +import okhttp3.EventListener import okhttp3.Request import okhttp3.Response +import okhttp3.Route import okio.IOException import okio.Timeout import kotlin.reflect.KClass @@ -38,6 +40,11 @@ public class FailedCall( ) : Call { private var cancelled = false private var executed = false + private val eventListeners = mutableListOf() + + override fun addEventListener(eventListener: EventListener) { + eventListeners.add(eventListener) + } override fun cancel() { cancelled = true @@ -47,7 +54,11 @@ public class FailedCall( val request = request() // Remove network and lease from new request val cleanRequest = request.newBuilder().requestType(request.requestType).build() - return callFactory.newCall(cleanRequest) + val newCall = callFactory.newCall(cleanRequest) + for (listener in eventListeners) { + newCall.addEventListener(listener) + } + return newCall } override fun enqueue(responseCallback: Callback) { diff --git a/network-awareness/okhttp/src/main/java/com/google/android/horologist/networks/okhttp/impl/HighBandwidthCall.kt b/network-awareness/okhttp/src/main/java/com/google/android/horologist/networks/okhttp/impl/HighBandwidthCall.kt index bdd2b8819d..618fe6850b 100644 --- a/network-awareness/okhttp/src/main/java/com/google/android/horologist/networks/okhttp/impl/HighBandwidthCall.kt +++ b/network-awareness/okhttp/src/main/java/com/google/android/horologist/networks/okhttp/impl/HighBandwidthCall.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import okhttp3.Call import okhttp3.Callback +import okhttp3.EventListener import okhttp3.Request import okhttp3.Response import okio.AsyncTimeout @@ -52,6 +53,19 @@ internal class HighBandwidthCall( @GuardedBy("this") private var call: Call? = null + private val eventListeners = mutableListOf() + + override fun addEventListener(eventListener: EventListener) { + synchronized(this) { + val currentCall = call + if (currentCall != null) { + currentCall.addEventListener(eventListener) + } else { + eventListeners.add(eventListener) + } + } + } + override fun cancel() { synchronized(this) { cancelled = true @@ -63,7 +77,11 @@ internal class HighBandwidthCall( override fun clone(): Call { // Remove network and lease from new request val cleanRequest = request.newBuilder().requestType(request.requestType).build() - return callFactory.newCall(cleanRequest) + val newCall = callFactory.newCall(cleanRequest) + for (listener in eventListeners) { + newCall.addEventListener(listener) + } + return newCall } override fun enqueue(responseCallback: Callback) { @@ -76,7 +94,12 @@ internal class HighBandwidthCall( if (cancelled) { request.highBandwidthConnectionLease?.close() } else { - callFactory.newDirectCall(request).enqueue(object : Callback { + val directCall = callFactory.newDirectCall(request) + call = directCall + for (listener in eventListeners) { + directCall.addEventListener(listener) + } + directCall.enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { responseCallback.onFailure(this@HighBandwidthCall, e) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 5063bed01e..bda50d8929 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ */ plugins { - id("com.gradle.develocity") version "4.4.1" + id("com.gradle.develocity") version "4.5.0" } develocity {