diff --git a/.github/workflows/build-snapshot.yml b/.github/workflows/build-snapshot.yml index 08a9b1b04..2cc334a54 100644 --- a/.github/workflows/build-snapshot.yml +++ b/.github/workflows/build-snapshot.yml @@ -31,7 +31,7 @@ jobs: - name: set up JDK uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 - name: Decrypt secrets run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }} @@ -52,7 +52,7 @@ jobs: ./gradlew --scan --stacktrace \ spotlessCheck \ assemble \ - metalavaCheckCompatibility \ + metalavaCheckCompatibilityRelease \ lintDebug - name: Unit Tests @@ -102,7 +102,7 @@ jobs: - name: set up JDK uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 - name: Decrypt secrets run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }} @@ -177,7 +177,7 @@ jobs: - name: set up JDK uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 - name: Decrypt secrets run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d5ab7aa2..2382501e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,7 @@ on: - compose-1.1 - compose-1.2 - compose-1.3 + - compose-1.4 paths-ignore: - '**.md' pull_request: @@ -18,7 +19,7 @@ jobs: if: "!contains(github.event.head_commit.message, 'skip ci')" runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 45 steps: - uses: actions/checkout@v2 @@ -29,31 +30,24 @@ jobs: - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: set up JDK - uses: actions/setup-java@v1 + - name: Setup java + uses: actions/setup-java@v3 with: - java-version: 11 + distribution: temurin + java-version: 17 - name: Decrypt secrets run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }} - - name: Generate cache key - run: ./checksum.sh checksum.txt - - - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches/modules-* - ~/.gradle/caches/jars-* - ~/.gradle/caches/build-cache-* - key: gradle-${{ hashFiles('checksum.txt') }} - + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Build run: | ./gradlew --scan --stacktrace \ spotlessCheck \ assemble \ - metalavaCheckCompatibility \ + metalavaCheckCompatibilityRelease \ lintDebug - name: Unit Tests @@ -100,24 +94,17 @@ jobs: - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: set up JDK - uses: actions/setup-java@v1 + - name: Setup java + uses: actions/setup-java@v3 with: - java-version: 11 + distribution: temurin + java-version: 17 - name: Decrypt secrets run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }} - - name: Generate cache key - run: ./checksum.sh checksum.txt - - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches/modules-* - ~/.gradle/caches/jars-* - ~/.gradle/caches/build-cache-* - key: gradle-${{ hashFiles('checksum.txt') }} + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 # Determine what emulator image to use. We run all API 28+ emulators using # the google_apis image @@ -185,24 +172,17 @@ jobs: - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: set up JDK - uses: actions/setup-java@v1 + - name: Setup java + uses: actions/setup-java@v3 with: - java-version: 11 + distribution: temurin + java-version: 17 - name: Decrypt secrets run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }} - - name: Generate cache key - run: ./checksum.sh checksum.txt - - - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches/modules-* - ~/.gradle/caches/jars-* - ~/.gradle/caches/build-cache-* - key: gradle-${{ hashFiles('checksum.txt') }} + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 - name: Deploy to Sonatype run: ./gradlew publish --no-parallel --stacktrace diff --git a/.github/workflows/device-tests.yml b/.github/workflows/device-tests.yml index 416b380bf..e459ed086 100644 --- a/.github/workflows/device-tests.yml +++ b/.github/workflows/device-tests.yml @@ -32,7 +32,7 @@ jobs: - name: set up JDK uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 - name: Decrypt secrets run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }} diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 7d27134d6..28482fadf 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -18,32 +18,24 @@ jobs: - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: set up JDK - uses: actions/setup-java@v1 + - name: Setup java + uses: actions/setup-java@v3 with: - java-version: 11 + distribution: temurin + java-version: 17 - - name: Generate cache key - run: ./checksum.sh checksum.txt - - - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches/modules-* - ~/.gradle/caches/jars-* - ~/.gradle/caches/build-cache-* - key: gradle-${{ hashFiles('checksum.txt') }} + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies run: | python3 -m pip install --upgrade pip - python3 -m pip install mkdocs - python3 -m pip install mkdocs-material + python3 -m pip install mkdocs-material=="9.*" - name: Generate docs run: ./generate_docs.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b8a677cfa..5e21369fa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,3 +29,13 @@ All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. + +## API Changes + +If you are changing any public APIs, you need to run `./gradlew metalavaGenerateSignatureRelease` which will +update the API signatures. + +## Formatting + +To apply formatting, we use spotless. Run `./gradlew :pager:spotlessApply` to format the code according +to the spec. diff --git a/README.md b/README.md index c311fe701..8adac668a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,10 @@ Each [release](https://github.com/google/accompanist/releases) outlines what ver Compose UI 1.3 (1.3.x)Maven Central - Compose UI 1.4 (1.4.x)Maven Central + Compose UI 1.4 (1.4.x)Maven Central + + + Compose UI 1.5 (1.5.x)Maven Central @@ -46,17 +49,14 @@ A library that enables the reuse of [MDC-Android][mdc] Material 2 XML themes, fo ### 🎨 [Material 3 Theme Adapter](./themeadapter-material3/) A library that enables the reuse of [MDC-Android][mdc] Material 3 XML themes, for theming in Jetpack Compose. -### 📖 [Pager](./pager/) -A library that provides utilities for building paginated layouts in Jetpack Compose, similar to Android's [ViewPager][viewpager]. - ### 📫 [Permissions](./permissions/) A library that provides [Android runtime permissions][runtimepermissions] support for Jetpack Compose. ### ⏳ [Placeholder](./placeholder/) A library that provides easy-to-use modifiers for displaying a placeholder UI while content is loading. -### 🌊 [Flow Layouts](./flowlayout/) -A library that adds Flexbox-like layout components to Jetpack Compose. +### 🌊 [Flow Layouts](./flowlayout/) (Deprecated) +See our [Migration Guide](https://google.github.io/accompanist/flowlayout/) for migrating to FlowLayout in Compose. ### 🧭✨[Navigation-Animation](./navigation-animation/) A library which provides [Compose Animation](https://developer.android.com/jetpack/compose/animation) support for Jetpack Navigation Compose. @@ -85,6 +85,9 @@ See our [Migration Guide](https://google.github.io/accompanist/swiperefresh/) fo ### 🎨 [AppCompat Theme Adapter](./appcompat-theme/) (Deprecated) See our [Migration Guide](https://google.github.io/accompanist/appcompat-theme/) for migrating to the new artifact in Accompanist. +### 📖 [Pager](./pager/) (Deprecated) +See our [Migration Guide](https://google.github.io/accompanist/pager/) for migrating to Pager in Compose. + --- ## Future? diff --git a/adaptive/api/current.api b/adaptive/api/current.api index 1d9b3f34f..e9fb10b61 100644 --- a/adaptive/api/current.api +++ b/adaptive/api/current.api @@ -5,7 +5,7 @@ package com.google.accompanist.adaptive { method @androidx.compose.runtime.Composable public static java.util.List calculateDisplayFeatures(android.app.Activity activity); } - @kotlin.jvm.JvmInline public final class FoldAwareConfiguration { + @kotlin.jvm.JvmInline public final value class FoldAwareConfiguration { field public static final com.google.accompanist.adaptive.FoldAwareConfiguration.Companion Companion; } diff --git a/adaptive/build.gradle b/adaptive/build.gradle deleted file mode 100644 index 82dff668b..000000000 --- a/adaptive/build.gradle +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2022 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. - */ - -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jetbrains.dokka' -} - -kotlin { - explicitApi() -} - -android { - namespace "com.google.accompanist.adaptive" - - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 21 - // targetSdkVersion has no effect for libraries. This is only used for the test APK - targetSdkVersion 32 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - buildFeatures { - buildConfig false - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion libs.versions.composeCompiler.get() - } - - lintOptions { - textReport true - textOutput 'stdout' - // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks - checkReleaseBuilds false - } - - packagingOptions { - // Some of the META-INF files conflict with coroutines-test. Exclude them to enable - // our test APK to build (has no effect on our AARs) - excludes += "/META-INF/AL2.0" - excludes += "/META-INF/LGPL2.1" - } - - testOptions { - unitTests { - includeAndroidResources = true - } - animationsDisabled true - } - - sourceSets { - test { - java.srcDirs += 'src/sharedTest/kotlin' - res.srcDirs += 'src/sharedTest/res' - } - androidTest { - java.srcDirs += 'src/sharedTest/kotlin' - res.srcDirs += 'src/sharedTest/res' - } - } -} - -dependencies { - api libs.compose.foundation.foundation - api libs.compose.ui.ui - api libs.androidx.window - - implementation libs.napier - implementation libs.kotlin.coroutines.android - - // ====================== - // Test dependencies - // ====================== - - androidTestImplementation project(':internal-testutils') - testImplementation project(':internal-testutils') - - androidTestImplementation project(':testharness') - testImplementation project(':testharness') - - androidTestImplementation libs.junit - testImplementation libs.junit - - androidTestImplementation libs.truth - testImplementation libs.truth - - androidTestImplementation libs.compose.ui.test.junit4 - testImplementation libs.compose.ui.test.junit4 - - androidTestImplementation libs.compose.ui.test.manifest - testImplementation libs.compose.ui.test.manifest - - androidTestImplementation libs.androidx.test.runner - testImplementation libs.androidx.test.runner - - androidTestImplementation libs.androidx.window.testing - testImplementation libs.androidx.window.testing - - testImplementation libs.robolectric -} - -apply plugin: "com.vanniktech.maven.publish" diff --git a/adaptive/build.gradle.kts b/adaptive/build.gradle.kts new file mode 100644 index 000000000..45d8e440a --- /dev/null +++ b/adaptive/build.gradle.kts @@ -0,0 +1,133 @@ +/* + * Copyright 2023 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. + */ +@file:Suppress("UnstableApiUsage") + +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.android.kotlin.get().pluginId) + id(libs.plugins.jetbrains.dokka.get().pluginId) + id(libs.plugins.gradle.metalava.get().pluginId) + id(libs.plugins.vanniktech.maven.publish.get().pluginId) +} + +kotlin { + explicitApi() +} + +android { + namespace = "com.google.accompanist.adaptive" + + compileSdk = 33 + + defaultConfig { + minSdk = 21 + // targetSdkVersion has no effect for libraries. This is only used for the test APK + targetSdk = 33 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig = false + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + + lint { + textReport = true + textOutput = File("stdout") + // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks + checkReleaseBuilds = false + } + + packagingOptions { + // Some of the META-INF files conflict with coroutines-test. Exclude them to enable + // our test APK to build (has no effect on our AARs) + resources { + excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") + } + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + animationsDisabled = true + } + + sourceSets { + named("test") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + named("androidTest") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + } +} + +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + +dependencies { + api(libs.compose.foundation.foundation) + api(libs.compose.ui.ui) + api(libs.androidx.window) + + implementation(libs.napier) + implementation(libs.kotlin.coroutines.android) + + // ====================== + // Test dependencies + // ====================== + + androidTestImplementation(project(":internal-testutils")) + testImplementation(project(":internal-testutils")) + + androidTestImplementation(project(":testharness")) + testImplementation(project(":testharness")) + + androidTestImplementation(libs.junit) + testImplementation(libs.junit) + + androidTestImplementation(libs.truth) + testImplementation(libs.truth) + + androidTestImplementation(libs.compose.ui.test.junit4) + testImplementation(libs.compose.ui.test.junit4) + + androidTestImplementation(libs.compose.ui.test.manifest) + testImplementation(libs.compose.ui.test.manifest) + + androidTestImplementation(libs.androidx.test.runner) + testImplementation(libs.androidx.test.runner) + + androidTestImplementation(libs.androidx.window.testing) + testImplementation(libs.androidx.window.testing) + + testImplementation(libs.robolectric) +} diff --git a/adaptive/src/main/java/com/google/accompanist/adaptive/DisplayFeatures.kt b/adaptive/src/main/java/com/google/accompanist/adaptive/DisplayFeatures.kt index f440f816c..431298b5d 100644 --- a/adaptive/src/main/java/com/google/accompanist/adaptive/DisplayFeatures.kt +++ b/adaptive/src/main/java/com/google/accompanist/adaptive/DisplayFeatures.kt @@ -29,12 +29,13 @@ import androidx.window.layout.WindowInfoTracker */ @Composable public fun calculateDisplayFeatures(activity: Activity): List { - val windowInfoTracker = remember(activity) { WindowInfoTracker.getOrCreate(activity) } - val windowLayoutInfo = remember(windowInfoTracker, activity) { - windowInfoTracker.windowLayoutInfo(activity) + val windowLayoutInfo = remember(activity) { + WindowInfoTracker.getOrCreate(activity).windowLayoutInfo(activity) } - - val displayFeatures by produceState(initialValue = emptyList()) { + val displayFeatures by produceState( + initialValue = emptyList(), + key1 = windowLayoutInfo + ) { windowLayoutInfo.collect { info -> value = info.displayFeatures } diff --git a/adaptive/src/sharedTest/kotlin/com/google/accompanist/adaptive/TwoPaneTest.kt b/adaptive/src/sharedTest/kotlin/com/google/accompanist/adaptive/TwoPaneTest.kt index af51819c5..a24c83bdd 100644 --- a/adaptive/src/sharedTest/kotlin/com/google/accompanist/adaptive/TwoPaneTest.kt +++ b/adaptive/src/sharedTest/kotlin/com/google/accompanist/adaptive/TwoPaneTest.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toAndroidRect import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.platform.LocalDensity @@ -1578,7 +1577,12 @@ private fun fakeDisplayFeatures( } FoldingFeature( - windowBounds = bounds.toAndroidRect(), + windowBounds = android.graphics.Rect( + bounds.left.toInt(), + bounds.top.toInt(), + bounds.right.toInt(), + bounds.bottom.toInt() + ), center = center, size = size, state = localFoldingFeature.state, diff --git a/appcompat-theme/build.gradle b/appcompat-theme/build.gradle deleted file mode 100644 index c6bbe232d..000000000 --- a/appcompat-theme/build.gradle +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2020 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. - */ - -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jetbrains.dokka' -} - -kotlin { - explicitApi() -} - -android { - namespace "com.google.accompanist.appcompattheme" - - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 21 - // targetSdkVersion has no effect for libraries. This is only used for the test APK - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - buildFeatures { - compose true - buildConfig false - } - - composeOptions { - kotlinCompilerExtensionVersion libs.versions.composeCompiler.get() - } - - lintOptions { - textReport true - textOutput 'stdout' - // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks - checkReleaseBuilds false - } - - packagingOptions { - // Multiple dependencies bring these files in. Exclude them to enable - // our test APK to build (has no effect on our AARs) - excludes += "/META-INF/AL2.0" - excludes += "/META-INF/LGPL2.1" - } - - testOptions { - unitTests { - includeAndroidResources = true - } - animationsDisabled true - } - - sourceSets { - test { - java.srcDirs += 'src/sharedTest/kotlin' - res.srcDirs += 'src/sharedTest/res' - } - androidTest { - java.srcDirs += 'src/sharedTest/kotlin' - res.srcDirs += 'src/sharedTest/res' - } - } -} - -dependencies { - implementation libs.androidx.core - implementation libs.compose.material.material - implementation libs.kotlin.coroutines.android - - api libs.androidx.appcompat - - // ====================== - // Test dependencies - // ====================== - - androidTestImplementation project(':internal-testutils') - testImplementation project(':internal-testutils') - - androidTestImplementation libs.junit - testImplementation libs.junit - - androidTestImplementation libs.compose.ui.test.junit4 - testImplementation libs.compose.ui.test.junit4 - - androidTestImplementation libs.androidx.test.runner - testImplementation libs.androidx.test.runner - - androidTestImplementation libs.androidx.test.espressoCore - testImplementation libs.androidx.test.espressoCore - - testImplementation libs.robolectric -} - -apply plugin: 'com.vanniktech.maven.publish' diff --git a/appcompat-theme/build.gradle.kts b/appcompat-theme/build.gradle.kts new file mode 100644 index 000000000..709e62985 --- /dev/null +++ b/appcompat-theme/build.gradle.kts @@ -0,0 +1,121 @@ +/* + * Copyright 2023 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. + */ +@file:Suppress("UnstableApiUsage") + +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.android.kotlin.get().pluginId) + id(libs.plugins.jetbrains.dokka.get().pluginId) + id(libs.plugins.gradle.metalava.get().pluginId) + id(libs.plugins.vanniktech.maven.publish.get().pluginId) +} + +kotlin { + explicitApi() +} + +android { + namespace = "com.google.accompanist.appcompattheme" + + compileSdk = 33 + + defaultConfig { + minSdk = 21 + // targetSdkVersion has no effect for libraries. This is only used for the test APK + targetSdk = 33 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + compose = true + buildConfig = false + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + + lint { + textReport = true + textOutput = File("stdout") + // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks + checkReleaseBuilds = false + } + + packagingOptions { + // Multiple dependencies bring these files in. Exclude them to enable + // our test APK to build (has no effect on our AARs) + excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + animationsDisabled = true + } + + sourceSets { + named("test") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + named("androidTest") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + } +} + +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + +dependencies { + implementation(libs.androidx.core) + implementation(libs.compose.material.material) + implementation(libs.kotlin.coroutines.android) + + api(libs.androidx.appcompat) + + // ====================== + // Test dependencies + // ====================== + + androidTestImplementation(project(":internal-testutils")) + testImplementation(project(":internal-testutils")) + + androidTestImplementation(libs.junit) + testImplementation(libs.junit) + + androidTestImplementation(libs.compose.ui.test.junit4) + testImplementation(libs.compose.ui.test.junit4) + + androidTestImplementation(libs.androidx.test.runner) + testImplementation(libs.androidx.test.runner) + + androidTestImplementation(libs.androidx.test.espressoCore) + testImplementation(libs.androidx.test.espressoCore) + + testImplementation(libs.robolectric) +} diff --git a/build.gradle b/build.gradle index dc661879c..c1c80c0b7 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ buildscript { repositories { google() mavenCentral() + gradlePluginPortal() } dependencies { @@ -27,8 +28,6 @@ buildscript { classpath libs.gradleMavenPublishPlugin - classpath libs.dokka - classpath libs.metalavaGradle classpath libs.affectedmoduledetector @@ -37,10 +36,11 @@ buildscript { plugins { id "com.diffplug.spotless" version "6.5.2" + alias(libs.plugins.jetbrains.dokka) } -apply plugin: 'org.jetbrains.dokka' apply plugin: 'com.dropbox.affectedmoduledetector' +apply plugin: 'com.diffplug.spotless' tasks.withType(org.jetbrains.dokka.gradle.DokkaMultiModuleTask).configureEach { outputDirectory = rootProject.file('docs/api') @@ -149,16 +149,6 @@ subprojects { } } - // If we have a POM Artifact ID, we should generate API files - if (project.hasProperty('POM_ARTIFACT_ID')) { - apply plugin: 'me.tylerbwong.gradle.metalava' - - metalava { - filename = "api/current.api" - reportLintsAsErrors = true - } - } - // Must be afterEvaluate or else com.vanniktech.maven.publish will overwrite our // dokka and version configuration. afterEvaluate { diff --git a/docs/flowlayout.md b/docs/flowlayout.md index 767c248ea..165f6918e 100644 --- a/docs/flowlayout.md +++ b/docs/flowlayout.md @@ -2,12 +2,17 @@ [![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-flowlayout)](https://search.maven.org/search?q=g:com.google.accompanist) -Flow layouts adapted from the versions which were available in [Jetpack Compose][compose] until they were removed. +Flow Layouts in Accompanist is now deprecated. Please see the migration guide below to begin using +Flow Layouts in Androidx. -Unlike the standard `Row` and `Column` composables, these layout children across multiple rows/columns if they exceed the available space. +The official `androidx.compose.foundation` FlowLayouts support is very similar to accompanist/flowlayouts, with a few changes. -## Usage +It is most similar to `Row` and `Column` and shares similar modifiers and the scopes. +Unlike the standard `Row` and `Column` composables, if it runs out of space on the current row, +the children are placed in the next line, and this repeats until the children are fully placed. +## Usage + ``` kotlin FlowRow { // row contents @@ -18,7 +23,108 @@ FlowColumn { } ``` -For examples, refer to the [samples](https://github.com/google/accompanist/tree/main/sample/src/main/java/com/google/accompanist/sample/flowlayout). +## Migration Guide to the official FlowLayouts + +1. Replace import packages to point to Androidx.Compose +``` kotlin +import androidx.compose.foundation.layout.FlowColumn +``` + +``` kotlin +import androidx.compose.foundation.layout.FlowRow +``` + +For `FlowColumn`: + +2. Replace Modifier `mainAxisAlignment` with `verticalArrangement` + +3. Replace Modifier `crossAxisAlignment` with `horizontalArrangement` + + +For `FlowRow` + +4. `mainAxisAlignment` is now `horizontalArrangement` + +5. `crossAxisAlignment` is now `verticalArrangement` + +``` kotlin +FlowColumn( + modifier = Modifier, + verticalArrangement = Arrangement.Top, + horizontalArrangement = Arrangement.Start, + content = { // columns } +) +``` + +``` kotlin +FlowRow( + modifier = Modifier, + horizontalArrangement = Arrangement.Start, + verticalArrangement = Arrangement.Top, + content = { // rows } +) +``` + +6. Replace `mainAxisSpacing` with `HorizontalArrangement.spacedBy(50.dp, Alignment.*)` in `FlowRow` and `VerticalArrangement.spacedBy(50.dp, Alignment.*)` in `FlowColumn`. + +7. Replace `crossAxisSpacing` with `VerticalArrangement.spacedBy(50.dp, Alignment.*)` in `FlowRow` and `HorizontalArrangement.spacedBy(50.dp)` in `FlowColumn`. + +Here `Alignment.*` is the Alignment you wish to use such as `Alignment.Start`, `Alignment.Top` etc + + +``` kotlin +FlowRow( + modifier = Modifier, + horizontalArrangement = HorizontalArrangement.spacedBy(50.dp, Alignment.Start), + verticalArrangement = VerticalArrangement.spacedBy(50.dp, Alignment.Top), + content = { // rows } +) +``` + +``` kotlin +FlowColumn( + modifier = Modifier, + verticalArrangement = VerticalArrangement.spacedBy(50.dp, Alignment.Top), + horizontalArrangement = HorizontalArrangement.spacedBy(50.dp, Alignment.Start), + content = { // columns } +) +``` + +8. `lastLineMainAxisAlignment` is currently not supported in Compose Flow Layouts. + +### New Features: +#### Add weights to each child +To scale an item based on the size of its parent and the space available, adding weights is helpful. +Adding a weight in `FlowRow` and `FlowColumn` is different than in `Row` and `Column`. + +In `FlowLayout`, it is based on the number of items placed on the row it falls on and their weights. +First we check to see if an item can fit in the current row or column based on its intrinsic size. +If it fits and has a weight, its final size is expanded based on the available space and the number of items +with weights placed on the row or column it falls on. + +Because of the nature of `FlowLayouts` an item only expands and does not shrink in size. Its width in `FlowRow` +or height in `FlowColumn` determines it minimum width or height, and then expands based on its weight and +the available space remaining after row items' width/height have been determined. + +``` kotlin +FlowRow() { + repeat(20) { Box(Modifier.size(20.dp).weight(1f, true) } +} +``` + +#### Create a maximum number of items in row or column +You may choose to limit the number of items that appear in each row in `FlowRow` or column in `FlowColumn` +This can be configured using `maxItemsInEachRow` or `maxItemsInEachColumn`: +``` kotlin +FlowRow(maxItemsInEachRow = 3) { + repeat(10) { Box(Modifier.size(20.dp).weight(1f, true) } +} +``` + +## Examples + +For examples, refer to the [Flow Row samples](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-main/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowRowSample.kt) +and the [Flow Column samples](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-main/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowColumnSample.kt). ## Download @@ -30,11 +136,11 @@ repositories { } dependencies { - implementation "com.google.accompanist:accompanist-flowlayout:" + implementation "androidx.compose.foundation:foundation:" } ``` Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. These are updated on every commit. [compose]: https://developer.android.com/jetpack/compose -[snap]: https://oss.sonatype.org/content/repositories/snapshots/com/google/accompanist/accompanist-flowlayout/ \ No newline at end of file +[snap]: https://oss.sonatype.org/content/repositories/snapshots/com/google/accompanist/accompanist-flowlayout/ diff --git a/docs/pager.md b/docs/pager.md index e4a666877..fbece2432 100644 --- a/docs/pager.md +++ b/docs/pager.md @@ -5,8 +5,43 @@ A library which provides paging layouts for Jetpack Compose. If you've used Android's [`ViewPager`](https://developer.android.com/reference/kotlin/androidx/viewpager/widget/ViewPager) before, it has similar properties. !!! warning - The pager layouts are currently experimental and the APIs could change at any time. - All of the APIs are marked with the `@ExperimentalPagerApi` annotation. + **This library is deprecated, with official pager support in [androidx.compose.foundation.pager](https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/package-summary).** The original documentation is below the migration guide. + +## Migration + +1. Make sure you are using Compose 1.4.0+ before attempting to migrate to `androidx.compose.foundation.pager`. +2. Change `com.google.accompanist.pager.HorizontalPager` to `androidx.compose.foundation.pager.HorizontalPager`, and the same for `com.google.accompanist.pager.VerticalPager` to change to `androidx.compose.foundation.pager.VerticalPager` +3. Change `count` variable to `pageCount`. +4. Change `itemSpacing` parameter to `pageSpacing`. +5. Change any usages of `rememberPagerState()` to `androidx.compose.foundation.pager.rememberPagerState()` +6. For more mappings - see the migration table below. +7. Run your changes on device and check to see if there are any differences. + +One thing to note is that there is a new parameter on `androidx.compose.foundation.Pager`, for `pageSize`, by default this +uses a `PageSize.Fill`, but can also be changed to use a fixed size, like `PageSize.Fixed(200.dp)` for a fixed size paging. + + +## Migration Table + +The following is a mapping of the pager classes from accompanist to androidx.compose: + +| accompanist/pager | androidx.compose.foundation | +|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| `HorizontalPager` | `androidx.compose.foundation.pager.HorizontalPager` | +| `VerticalPager` | `androidx.compose.foundation.pager.VerticalPager` | +| `rememberPagerState` | `androidx.compose.foundation.pager.rememberPagerState` | +| `PagerState#pageCount` | Use `canScrollForward` or `canScrollBackward` | +| `calculateCurrentOffsetForPage` | Use `(pagerState.currentPage - page) + pagerState.currentPageOffsetFraction` | +| `PagerState#currentPageOffset` | `PagerState#currentPageOffsetFraction` | +| `Modifier.pagerTabIndicatorOffset()` | Implement it yourself, or still include and use `accompanist-pager-indicators`, it now supports `androidx.compose.foundation.pager.PagerState` | +| `HorizontalPagerIndicator` | Implement it yourself, or still include and use `accompanist-pager-indicators`, it now supports `androidx.compose.foundation.pager.HorizontalPager` by explicitly providing a `pageCount` param to the `HorizontalPagerIndicator` method | +| `VerticalPagerIndicator` | Implement it yourself, or still include and use `accompanist-pager-indicators`, it now supports `androidx.compose.foundation.pager.HorizontalPager` by explicitly providing a `pageCount` param to the `HorizontalPagerIndicator` method | +| `PagerDefaults.flingBehavior()` | `androidx.compose.foundation.pager.PagerDefaults.flingBehavior()` | + +The biggest change is that `HorizontalPager` and `VerticalPager`'s number of pages is now called `pageCount` instead of `count`. + +# Deprecated Guidance for Accompanist Pager +The following is the deprecated guide for using Pager in Accompanist. Please see above migration section for how to use the `androidx.compose` Pager. ## HorizontalPager @@ -284,7 +319,7 @@ In v0.19.0 both `HorizontalPager` and `VerticalPager` were re-written to be base - The `infiniteLooping` parameter and feature have been removed. A sample demonstrating how to achieve this effect can be found [here][looping-sample]. - The `offscreenLimit` parameter has been removed. We no longer have control of what items are laid out 'off screen'. - The `dragEnabled` parameter has removed. -- `PagerScope` (the page item scope) no longer implements `BoxScope`. +- `PagerScope` (the page item scope) no longer implements `BoxScope`. --- @@ -298,7 +333,7 @@ repositories { dependencies { implementation "com.google.accompanist:accompanist-pager:" - // If using indicators, also depend on + // If using indicators, also depend on implementation "com.google.accompanist:accompanist-pager-indicators:" } ``` @@ -318,7 +353,7 @@ Make sure to read the [Contributing](../contributing) page first though. ``` Copyright 2021 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 diff --git a/docs/permissions.md b/docs/permissions.md index d4a6c1f70..2f8f3718d 100644 --- a/docs/permissions.md +++ b/docs/permissions.md @@ -66,7 +66,7 @@ For more examples, refer to the [samples](https://github.com/google/accompanist/ ## Limitations This permissions wrapper is built on top of the available Android platform APIs. We cannot extend -the platform's capabilities. For example, it's not possible to differentiate the between the +the platform's capabilities. For example, it's not possible to differentiate between the _it's the first time requesting the permission_ vs _the user doesn't want to be asked again_ use cases. diff --git a/drawablepainter/build.gradle b/drawablepainter/build.gradle deleted file mode 100644 index d78b0b3f5..000000000 --- a/drawablepainter/build.gradle +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2020 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. - */ - -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jetbrains.dokka' -} - -kotlin { - explicitApi() -} - -android { - namespace "com.google.accompanist.drawablepainter" - - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 21 - // targetSdkVersion has no effect for libraries. This is only used for the test APK - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - buildFeatures { - buildConfig false - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion libs.versions.composeCompiler.get() - } - - lintOptions { - textReport true - textOutput 'stdout' - // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks - checkReleaseBuilds false - } - - testOptions { - unitTests { - includeAndroidResources = true - } - animationsDisabled true - } -} - -dependencies { - implementation libs.compose.ui.ui - implementation libs.kotlin.coroutines.android -} - -apply plugin: "com.vanniktech.maven.publish" diff --git a/drawablepainter/build.gradle.kts b/drawablepainter/build.gradle.kts new file mode 100644 index 000000000..734fbadf6 --- /dev/null +++ b/drawablepainter/build.gradle.kts @@ -0,0 +1,80 @@ +/* + * Copyright 2023 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. + */ +@file:Suppress("UnstableApiUsage") + +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.android.kotlin.get().pluginId) + id(libs.plugins.jetbrains.dokka.get().pluginId) + id(libs.plugins.gradle.metalava.get().pluginId) + id(libs.plugins.vanniktech.maven.publish.get().pluginId) +} + +kotlin { + explicitApi() +} + +android { + namespace = "com.google.accompanist.drawablepainter" + + compileSdk = 33 + + defaultConfig { + minSdk = 21 + // targetSdkVersion has no effect for libraries. This is only used for the test APK + targetSdk = 33 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig = false + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + + lint { + textReport = true + textOutput = File("stdout") + // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks + checkReleaseBuilds = false + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + animationsDisabled = true + } +} + +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + +dependencies { + implementation(libs.compose.ui.ui) + implementation(libs.kotlin.coroutines.android) +} diff --git a/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt b/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt index 319b9a6b3..590674cef 100644 --- a/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt +++ b/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt @@ -17,7 +17,6 @@ package com.google.accompanist.drawablepainter import android.graphics.drawable.Animatable -import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.os.Build @@ -34,11 +33,9 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.asAndroidColorFilter -import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.nativeCanvas -import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.withSave @@ -155,7 +152,6 @@ class DrawablePainter( fun rememberDrawablePainter(drawable: Drawable?): Painter = remember(drawable) { when (drawable) { null -> EmptyPainter - is BitmapDrawable -> BitmapPainter(drawable.bitmap.asImageBitmap()) is ColorDrawable -> ColorPainter(Color(drawable.color)) // Since the DrawablePainter will be remembered and it implements RememberObserver, it // will receive the necessary events diff --git a/flowlayout/README.md b/flowlayout/README.md index 1fd0cf8fc..edc1e2395 100644 --- a/flowlayout/README.md +++ b/flowlayout/README.md @@ -2,6 +2,8 @@ [![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-flowlayout)](https://search.maven.org/search?q=g:com.google.accompanist) +> :warning: This library has been deprecated as official support is now available in Compose 1.4.0. Please see our [Migration Guide](https://google.github.io/accompanist/flowlayout/) for how to migrate. + Flow layouts adapted from the [Jetpack Compose][compose] alpha versions. Unlike the standard Row and Column composables, these lay out children in multiple rows/columns if they exceed the available space. diff --git a/flowlayout/api/current.api b/flowlayout/api/current.api index 57c716548..51c355764 100644 --- a/flowlayout/api/current.api +++ b/flowlayout/api/current.api @@ -1,18 +1,22 @@ // Signature format: 4.0 package com.google.accompanist.flowlayout { - public enum FlowCrossAxisAlignment { + @Deprecated public enum FlowCrossAxisAlignment { + method public static com.google.accompanist.flowlayout.FlowCrossAxisAlignment valueOf(String name) throws java.lang.IllegalArgumentException; + method public static com.google.accompanist.flowlayout.FlowCrossAxisAlignment[] values(); enum_constant public static final com.google.accompanist.flowlayout.FlowCrossAxisAlignment Center; enum_constant public static final com.google.accompanist.flowlayout.FlowCrossAxisAlignment End; enum_constant public static final com.google.accompanist.flowlayout.FlowCrossAxisAlignment Start; } public final class FlowKt { - method @androidx.compose.runtime.Composable public static void FlowColumn(optional androidx.compose.ui.Modifier modifier, optional com.google.accompanist.flowlayout.SizeMode mainAxisSize, optional com.google.accompanist.flowlayout.MainAxisAlignment mainAxisAlignment, optional float mainAxisSpacing, optional com.google.accompanist.flowlayout.FlowCrossAxisAlignment crossAxisAlignment, optional float crossAxisSpacing, optional com.google.accompanist.flowlayout.MainAxisAlignment lastLineMainAxisAlignment, kotlin.jvm.functions.Function0 content); - method @androidx.compose.runtime.Composable public static void FlowRow(optional androidx.compose.ui.Modifier modifier, optional com.google.accompanist.flowlayout.SizeMode mainAxisSize, optional com.google.accompanist.flowlayout.MainAxisAlignment mainAxisAlignment, optional float mainAxisSpacing, optional com.google.accompanist.flowlayout.FlowCrossAxisAlignment crossAxisAlignment, optional float crossAxisSpacing, optional com.google.accompanist.flowlayout.MainAxisAlignment lastLineMainAxisAlignment, kotlin.jvm.functions.Function0 content); + method @Deprecated @androidx.compose.runtime.Composable public static void FlowColumn(optional androidx.compose.ui.Modifier modifier, optional com.google.accompanist.flowlayout.SizeMode mainAxisSize, optional com.google.accompanist.flowlayout.MainAxisAlignment mainAxisAlignment, optional float mainAxisSpacing, optional com.google.accompanist.flowlayout.FlowCrossAxisAlignment crossAxisAlignment, optional float crossAxisSpacing, optional com.google.accompanist.flowlayout.MainAxisAlignment lastLineMainAxisAlignment, kotlin.jvm.functions.Function0 content); + method @Deprecated @androidx.compose.runtime.Composable public static void FlowRow(optional androidx.compose.ui.Modifier modifier, optional com.google.accompanist.flowlayout.SizeMode mainAxisSize, optional com.google.accompanist.flowlayout.MainAxisAlignment mainAxisAlignment, optional float mainAxisSpacing, optional com.google.accompanist.flowlayout.FlowCrossAxisAlignment crossAxisAlignment, optional float crossAxisSpacing, optional com.google.accompanist.flowlayout.MainAxisAlignment lastLineMainAxisAlignment, kotlin.jvm.functions.Function0 content); } - public enum MainAxisAlignment { + @Deprecated public enum MainAxisAlignment { + method public static com.google.accompanist.flowlayout.MainAxisAlignment valueOf(String name) throws java.lang.IllegalArgumentException; + method public static com.google.accompanist.flowlayout.MainAxisAlignment[] values(); enum_constant public static final com.google.accompanist.flowlayout.MainAxisAlignment Center; enum_constant public static final com.google.accompanist.flowlayout.MainAxisAlignment End; enum_constant public static final com.google.accompanist.flowlayout.MainAxisAlignment SpaceAround; @@ -21,7 +25,9 @@ package com.google.accompanist.flowlayout { enum_constant public static final com.google.accompanist.flowlayout.MainAxisAlignment Start; } - public enum SizeMode { + @Deprecated public enum SizeMode { + method public static com.google.accompanist.flowlayout.SizeMode valueOf(String name) throws java.lang.IllegalArgumentException; + method public static com.google.accompanist.flowlayout.SizeMode[] values(); enum_constant public static final com.google.accompanist.flowlayout.SizeMode Expand; enum_constant public static final com.google.accompanist.flowlayout.SizeMode Wrap; } diff --git a/flowlayout/build.gradle b/flowlayout/build.gradle deleted file mode 100644 index 6422c89ed..000000000 --- a/flowlayout/build.gradle +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2021 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. - */ - -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jetbrains.dokka' -} - -kotlin { - explicitApi() -} - -android { - namespace "com.google.accompanist.flowlayout" - - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 21 - // targetSdkVersion has no effect for libraries. This is only used for the test APK - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - buildFeatures { - buildConfig false - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion libs.versions.composeCompiler.get() - } - - lintOptions { - textReport true - textOutput 'stdout' - // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks - checkReleaseBuilds false - } - - packagingOptions { - // Some of the META-INF files conflict with coroutines-test. Exclude them to enable - // our test APK to build (has no effect on our AARs) - excludes += "/META-INF/AL2.0" - excludes += "/META-INF/LGPL2.1" - } - - testOptions { - unitTests { - includeAndroidResources = true - } - animationsDisabled true - } - - sourceSets { - test { - java.srcDirs += 'src/sharedTest/kotlin' - res.srcDirs += 'src/sharedTest/res' - } - androidTest { - java.srcDirs += 'src/sharedTest/kotlin' - res.srcDirs += 'src/sharedTest/res' - } - } -} - -dependencies { - implementation libs.compose.foundation.foundation - implementation libs.compose.ui.util - implementation libs.kotlin.coroutines.android - - // ====================== - // Test dependencies - // ====================== - - androidTestImplementation project(':internal-testutils') - testImplementation project(':internal-testutils') - - androidTestImplementation libs.junit - testImplementation libs.junit - - androidTestImplementation libs.truth - testImplementation libs.truth - - androidTestImplementation libs.compose.ui.test.junit4 - testImplementation libs.compose.ui.test.junit4 - - androidTestImplementation libs.compose.ui.test.manifest - testImplementation libs.compose.ui.test.manifest - - androidTestImplementation libs.androidx.test.runner - testImplementation libs.androidx.test.runner - - testImplementation libs.robolectric -} - -apply plugin: "com.vanniktech.maven.publish" diff --git a/flowlayout/build.gradle.kts b/flowlayout/build.gradle.kts new file mode 100644 index 000000000..38b8a921d --- /dev/null +++ b/flowlayout/build.gradle.kts @@ -0,0 +1,124 @@ +/* + * Copyright 2023 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. + */ +@file:Suppress("UnstableApiUsage") + +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.android.kotlin.get().pluginId) + id(libs.plugins.jetbrains.dokka.get().pluginId) + id(libs.plugins.gradle.metalava.get().pluginId) + id(libs.plugins.vanniktech.maven.publish.get().pluginId) +} + +kotlin { + explicitApi() +} + +android { + namespace = "com.google.accompanist.flowlayout" + + compileSdk = 33 + + defaultConfig { + minSdk = 21 + // targetSdkVersion has no effect for libraries. This is only used for the test APK + targetSdk = 33 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig = false + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + + lint { + textReport = true + textOutput = File("stdout") + // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks + checkReleaseBuilds = false + } + + packaging { + resources { + // Some of the META-INF files conflict with coroutines-test. Exclude them to enable + // our test APK to build (has no effect on our AARs) + excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") + } + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + animationsDisabled = true + } + + sourceSets { + named("test") { + java.srcDir("src/sharedTest/kotlin") + res.srcDir("src/sharedTest/res") + } + named("androidTest") { + java.srcDir("src/sharedTest/kotlin") + res.srcDir("src/sharedTest/res") + } + } +} + +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + +dependencies { + implementation(libs.compose.foundation.foundation) + implementation(libs.compose.ui.util) + implementation(libs.kotlin.coroutines.android) + + // ====================== + // Test dependencies + // ====================== + + androidTestImplementation(project(":internal-testutils")) + testImplementation(project(":internal-testutils")) + + androidTestImplementation(libs.junit) + testImplementation(libs.junit) + + androidTestImplementation(libs.truth) + testImplementation(libs.truth) + + androidTestImplementation(libs.compose.ui.test.junit4) + testImplementation(libs.compose.ui.test.junit4) + + androidTestImplementation(libs.compose.ui.test.manifest) + testImplementation(libs.compose.ui.test.manifest) + + androidTestImplementation(libs.androidx.test.runner) + testImplementation(libs.androidx.test.runner) + + testImplementation(libs.robolectric) +} diff --git a/flowlayout/src/main/java/com/google/accompanist/flowlayout/Flow.kt b/flowlayout/src/main/java/com/google/accompanist/flowlayout/Flow.kt index 1ffd98a4d..359a6799b 100644 --- a/flowlayout/src/main/java/com/google/accompanist/flowlayout/Flow.kt +++ b/flowlayout/src/main/java/com/google/accompanist/flowlayout/Flow.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") + package com.google.accompanist.flowlayout import androidx.compose.foundation.layout.Arrangement @@ -44,6 +46,17 @@ import kotlin.math.max * @param lastLineMainAxisAlignment Overrides the main axis alignment of the last row. */ @Composable +@Deprecated( + """ +accompanist/FlowRow is deprecated. +For more migration information, please visit https://google.github.io/accompanist/flowlayout/ +""", + replaceWith = ReplaceWith( + "FlowRow", + "androidx.compose.foundation.layout.FlowRow", + "androidx.compose.ui.Modifier" + ) +) public fun FlowRow( modifier: Modifier = Modifier, mainAxisSize: SizeMode = SizeMode.Wrap, @@ -82,6 +95,17 @@ public fun FlowRow( * @param lastLineMainAxisAlignment Overrides the main axis alignment of the last column. */ @Composable +@Deprecated( + """ +accompanist/FlowColumn is deprecated. +For more migration information, please visit https://google.github.io/accompanist/flowlayout/ +""", + replaceWith = ReplaceWith( + "FlowColumn", + "androidx.compose.foundation.layout.FlowColumn", + "androidx.compose.ui.Modifier" + ) +) public fun FlowColumn( modifier: Modifier = Modifier, mainAxisSize: SizeMode = SizeMode.Wrap, @@ -108,6 +132,12 @@ public fun FlowColumn( /** * Used to specify the alignment of a layout's children, in cross axis direction. */ +@Deprecated( + """ +accompanist/FlowCrossAxisAlignment is deprecated. +For more migration information, please visit https://google.github.io/accompanist/flowlayout/ +""" +) public enum class FlowCrossAxisAlignment { /** * Place children such that their center is in the middle of the cross axis. @@ -123,12 +153,24 @@ public enum class FlowCrossAxisAlignment { End, } +@Deprecated( + """ +accompanist/FlowMainAxisAlignment is deprecated. +For more migration information, please visit https://google.github.io/accompanist/flowlayout/ +""" +) public typealias FlowMainAxisAlignment = MainAxisAlignment /** * Layout model that arranges its children in a horizontal or vertical flow. */ @Composable +@Deprecated( + """ +accompanist/Flow is deprecated. +For more migration information, please visit https://google.github.io/accompanist/flowlayout/ +""" +) private fun Flow( modifier: Modifier, orientation: LayoutOrientation, @@ -278,6 +320,12 @@ private fun Flow( * Used to specify how a layout chooses its own size when multiple behaviors are possible. */ // TODO(popam): remove this when Flow is reworked +@Deprecated( + """ +accompanist/SizeMode is deprecated. +For more migration information, please visit https://google.github.io/accompanist/flowlayout/ +""" +) public enum class SizeMode { /** * Minimize the amount of free space by wrapping the children, @@ -291,9 +339,6 @@ public enum class SizeMode { Expand } -/** - * Used to specify the alignment of a layout's children, in main axis direction. - */ public enum class MainAxisAlignment(internal val arrangement: Arrangement.Vertical) { // TODO(soboleva) support RTl in Flow // workaround for now - use Arrangement that equals to previous Arrangement diff --git a/flowlayout/src/sharedTest/kotlin/com/google/accompanist/flowlayout/FlowLayoutTest.kt b/flowlayout/src/sharedTest/kotlin/com/google/accompanist/flowlayout/FlowLayoutTest.kt index 784d1da60..970db01dd 100644 --- a/flowlayout/src/sharedTest/kotlin/com/google/accompanist/flowlayout/FlowLayoutTest.kt +++ b/flowlayout/src/sharedTest/kotlin/com/google/accompanist/flowlayout/FlowLayoutTest.kt @@ -33,6 +33,7 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.math.roundToInt +@Suppress("DEPRECATION") @RunWith(AndroidJUnit4::class) class FlowLayoutTest : LayoutTest() { @Test diff --git a/flowlayout/src/sharedTest/kotlin/com/google/accompanist/flowlayout/LayoutTest.kt b/flowlayout/src/sharedTest/kotlin/com/google/accompanist/flowlayout/LayoutTest.kt index 45d979b13..6827e0ec8 100644 --- a/flowlayout/src/sharedTest/kotlin/com/google/accompanist/flowlayout/LayoutTest.kt +++ b/flowlayout/src/sharedTest/kotlin/com/google/accompanist/flowlayout/LayoutTest.kt @@ -60,7 +60,7 @@ open class LayoutTest { size: Ref, position: Ref, positionedLatch: CountDownLatch - ): Modifier = onGloballyPositioned { coordinates -> + ): Modifier = this then onGloballyPositioned { coordinates -> size.value = IntSize(coordinates.size.width, coordinates.size.height) position.value = coordinates.localToRoot(Offset(0f, 0f)) positionedLatch.countDown() diff --git a/gradle.properties b/gradle.properties index 61a9e46f3..49ddc10f4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -33,7 +33,7 @@ systemProp.org.gradle.internal.http.socketTimeout=120000 GROUP=com.google.accompanist # !! No longer need to update this manually when using a Compose SNAPSHOT -VERSION_NAME=0.29.1-SNAPSHOT +VERSION_NAME=0.31.2-SNAPSHOT POM_DESCRIPTION=Utilities for Jetpack Compose diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d7daee212..a6c045c7a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,23 +1,28 @@ [versions] -compose = "1.4.0-alpha04" -composeCompiler = "1.4.0-alpha02" +compose = "1.5.0-alpha01" +composeCompiler = "1.4.6" composeMaterial3 = "1.0.1" composesnapshot = "-" # a single character = no snapshot +dokka = "1.8.10" + # gradlePlugin and lint need to be updated together -gradlePlugin = "7.3.1" +gradlePlugin = "8.0.0" lintMinCompose = "30.0.0" ktlint = "0.45.2" -kotlin = "1.7.21" +kotlin = "1.8.20" coroutines = "1.6.4" okhttp = "3.12.13" coil = "1.3.2" androidxtest = "1.4.0" -androidxnavigation = "2.5.3" +androidxnavigation = "2.6.0-alpha08" androidxWindow = "1.0.0" +metalava = "0.3.2" +vanniktechPublish = "0.25.2" + [libraries] compose-ui-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } compose-ui-util = { module = "androidx.compose.ui:ui-util", version.ref = "compose" } @@ -35,8 +40,8 @@ compose-animation-animation = { module = "androidx.compose.animation:animation", snapper = "dev.chrisbanes.snapper:snapper:0.2.2" android-gradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "gradlePlugin" } -gradleMavenPublishPlugin = "com.vanniktech:gradle-maven-publish-plugin:0.17.0" -metalavaGradle = "me.tylerbwong.gradle:metalava-gradle:0.1.9" +gradleMavenPublishPlugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "vanniktechPublish" } +metalavaGradle = { module = "me.tylerbwong.gradle.metalava:plugin", version.ref = "metalava" } glide = "com.github.bumptech.glide:glide:4.12.0" @@ -49,7 +54,7 @@ kotlin-metadataJvm = "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.3.0" kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } -dokka = "org.jetbrains.dokka:dokka-gradle-plugin:1.5.0" +dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } okhttp-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-mockWebServer = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" } @@ -63,15 +68,16 @@ androidx-core = "androidx.core:core-ktx:1.8.0" androidx-activity-compose = "androidx.activity:activity-compose:1.5.1" androidx-fragment = "androidx.fragment:fragment-ktx:1.5.1" androidx-dynamicanimation = "androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03" -androidx-lifecycle-runtime = "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1" -androidx-lifecycle-viewmodel-compose = "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1" +androidx-lifecycle-runtime = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1" +androidx-lifecycle-viewmodel-compose = "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1" +androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common-java8:2.6.0" androidx-window = { module = "androidx.window:window", version.ref = "androidxWindow" } androidx-window-testing = { module = "androidx.window:window-testing", version.ref = "androidxWindow" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidxnavigation" } androidx-navigation-testing = { module = "androidx.navigation:navigation-testing", version.ref = "androidxnavigation" } -mdc = "com.google.android.material:material:1.7.0" +mdc = "com.google.android.material:material:1.8.0" napier = "io.github.aakira:napier:1.4.1" @@ -82,8 +88,8 @@ androidx-test-orchestrator = "androidx.test:orchestrator:1.4.1" androidx-test-uiAutomator = "androidx.test.uiautomator:uiautomator:2.2.0" # alpha for robolectric x compose fix -androidx-test-espressoCore = "androidx.test.espresso:espresso-core:3.5.0-alpha07" -androidx-test-espressoWeb = "androidx.test.espresso:espresso-web:3.5.0-alpha07" +androidx-test-espressoCore = "androidx.test.espresso:espresso-core:3.5.1" +androidx-test-espressoWeb = "androidx.test.espresso:espresso-web:3.5.1" junit = "junit:junit:4.13.2" truth = "com.google.truth:truth:1.1.2" @@ -97,3 +103,10 @@ android-tools-lint-api = { module = "com.android.tools.lint:lint-api", version.r android-tools-lint-tests = { module = "com.android.tools.lint:lint-tests", version.ref = "lintMinCompose" } squareup-mockwebserver = "com.squareup.okhttp3:mockwebserver:4.9.3" + +[plugins] +android-kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +android-library = { id = "com.android.library", version.ref = "gradlePlugin" } +jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } +gradle-metalava = { id = "me.tylerbwong.gradle.metalava", version.ref = "metalava" } +vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktechPublish" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8049c684f..37aef8d3f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882ed..65dcd68d6 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..6689b85be 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/insets-ui/api/current.api b/insets-ui/api/current.api index 6fe501c85..e7e7318ca 100644 --- a/insets-ui/api/current.api +++ b/insets-ui/api/current.api @@ -10,6 +10,7 @@ package com.google.accompanist.insets.ui { public final class ScaffoldKt { method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0 topBar, optional kotlin.jvm.functions.Function0 bottomBar, optional kotlin.jvm.functions.Function1 snackbarHost, optional kotlin.jvm.functions.Function0 floatingActionButton, optional int floatingActionButtonPosition, optional boolean isFloatingActionButtonDocked, optional kotlin.jvm.functions.Function1? drawerContent, optional boolean drawerGesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerElevation, optional long drawerBackgroundColor, optional long drawerContentColor, optional long drawerScrimColor, optional long backgroundColor, optional long contentColor, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1 content); method public static androidx.compose.runtime.ProvidableCompositionLocal getLocalScaffoldPadding(); + property public static final androidx.compose.runtime.ProvidableCompositionLocal LocalScaffoldPadding; } public final class TopAppBarKt { diff --git a/insets-ui/build.gradle b/insets-ui/build.gradle deleted file mode 100644 index 371b42827..000000000 --- a/insets-ui/build.gradle +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2020 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. - */ - -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jetbrains.dokka' -} - -kotlin { - explicitApi() -} - -android { - namespace "com.google.accompanist.insets.ui" - - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 21 - // targetSdkVersion has no effect for libraries. This is only used for the test APK - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - buildFeatures { - buildConfig false - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion libs.versions.composeCompiler.get() - } - - lintOptions { - textReport true - textOutput 'stdout' - // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks - checkReleaseBuilds false - } - - packagingOptions { - // Some of the META-INF files conflict with coroutines-test. Exclude them to enable - // our test APK to build (has no effect on our AARs) - excludes += "/META-INF/AL2.0" - excludes += "/META-INF/LGPL2.1" - } - - testOptions { - unitTests { - includeAndroidResources = true - } - unitTests.all { - useJUnit { - excludeCategories 'com.google.accompanist.internal.test.IgnoreOnRobolectric' - } - } - animationsDisabled true - } - - sourceSets { - test { - java.srcDirs += 'src/sharedTest/kotlin' - res.srcDirs += 'src/sharedTest/res' - } - androidTest { - java.srcDirs += 'src/sharedTest/kotlin' - res.srcDirs += 'src/sharedTest/res' - } - } -} - -dependencies { - api project(':insets') - api libs.compose.material.material - - implementation libs.kotlin.coroutines.android - - // ====================== - // Test dependencies - // ====================== - - androidTestImplementation project(':internal-testutils') - testImplementation project(':internal-testutils') - - androidTestImplementation libs.junit - testImplementation libs.junit - - androidTestImplementation libs.truth - testImplementation libs.truth - - androidTestImplementation libs.compose.ui.test.junit4 - testImplementation libs.compose.ui.test.junit4 - - androidTestImplementation libs.compose.ui.test.manifest - testImplementation libs.compose.ui.test.manifest - - androidTestImplementation libs.androidx.test.runner - testImplementation libs.androidx.test.runner - - testImplementation libs.robolectric -} - -apply plugin: "com.vanniktech.maven.publish" diff --git a/insets-ui/build.gradle.kts b/insets-ui/build.gradle.kts new file mode 100644 index 000000000..ac0763e37 --- /dev/null +++ b/insets-ui/build.gradle.kts @@ -0,0 +1,130 @@ +/* + * Copyright 2023 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. + */ +@file:Suppress("UnstableApiUsage") + +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.android.kotlin.get().pluginId) + id(libs.plugins.jetbrains.dokka.get().pluginId) + id(libs.plugins.gradle.metalava.get().pluginId) + id(libs.plugins.vanniktech.maven.publish.get().pluginId) +} + +kotlin { + explicitApi() +} + +android { + namespace = "com.google.accompanist.insets.ui" + + compileSdk = 33 + + defaultConfig { + minSdk = 21 + // targetSdkVersion has no effect for libraries. This is only used for the test APK + targetSdk = 33 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig = false + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + + lint { + textReport = true + textOutput = File("stdout") + // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks + checkReleaseBuilds = false + } + + packaging { + // Some of the META-INF files conflict with coroutines-test. Exclude them to enable + // our test APK to build (has no effect on our AARs) + resources { + excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") + } + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + unitTests.all { + it.useJUnit { + excludeCategories("com.google.accompanist.internal.test.IgnoreOnRobolectric") + } + } + animationsDisabled = true + } + + sourceSets { + named("test") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + named("androidTest") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + } +} + +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + +dependencies { + api(project(":insets")) + api(libs.compose.material.material) + + implementation(libs.kotlin.coroutines.android) + + // ====================== + // Test dependencies + // ====================== + + androidTestImplementation(project(":internal-testutils")) + testImplementation(project(":internal-testutils")) + + androidTestImplementation(libs.junit) + testImplementation(libs.junit) + + androidTestImplementation(libs.truth) + testImplementation(libs.truth) + + androidTestImplementation(libs.compose.ui.test.junit4) + testImplementation(libs.compose.ui.test.junit4) + + androidTestImplementation(libs.compose.ui.test.manifest) + testImplementation(libs.compose.ui.test.manifest) + + androidTestImplementation(libs.androidx.test.runner) + testImplementation(libs.androidx.test.runner) + + testImplementation(libs.robolectric) +} diff --git a/insets/api/current.api b/insets/api/current.api index 5a9993ea0..795630e4f 100644 --- a/insets/api/current.api +++ b/insets/api/current.api @@ -1,17 +1,19 @@ // Signature format: 4.0 package com.google.accompanist.insets { - @kotlin.RequiresOptIn(message="Animated Insets support is experimental. The API may be changed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalAnimatedInsets { + @kotlin.RequiresOptIn(message="Animated Insets support is experimental. The API may be changed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface ExperimentalAnimatedInsets { } @Deprecated public enum HorizontalSide { + method @Deprecated public static com.google.accompanist.insets.HorizontalSide valueOf(String name) throws java.lang.IllegalArgumentException; + method @Deprecated public static com.google.accompanist.insets.HorizontalSide[] values(); enum_constant @Deprecated public static final com.google.accompanist.insets.HorizontalSide Left; enum_constant @Deprecated public static final com.google.accompanist.insets.HorizontalSide Right; } @Deprecated @com.google.accompanist.insets.ExperimentalAnimatedInsets public final class ImeNestedScrollConnection implements androidx.compose.ui.input.nestedscroll.NestedScrollConnection { ctor @Deprecated public ImeNestedScrollConnection(android.view.View view, boolean scrollImeOffScreenWhenVisible, boolean scrollImeOnScreenWhenNotVisible); - method @Deprecated public suspend Object? onPostFling(long consumed, long available, kotlin.coroutines.Continuation p); + method @Deprecated public suspend Object? onPostFling(long consumed, long available, kotlin.coroutines.Continuation); method @Deprecated public long onPostScroll(long consumed, long available, int source); method @Deprecated public long onPreScroll(long available, int source); } @@ -22,16 +24,16 @@ package com.google.accompanist.insets { @Deprecated @androidx.compose.runtime.Stable public interface Insets { method @Deprecated public default com.google.accompanist.insets.Insets copy(optional int left, optional int top, optional int right, optional int bottom); - method @Deprecated @IntRange(from=0) public int getBottom(); - method @Deprecated @IntRange(from=0) public int getLeft(); - method @Deprecated @IntRange(from=0) public int getRight(); - method @Deprecated @IntRange(from=0) public int getTop(); + method @Deprecated @IntRange(from=0L) public int getBottom(); + method @Deprecated @IntRange(from=0L) public int getLeft(); + method @Deprecated @IntRange(from=0L) public int getRight(); + method @Deprecated @IntRange(from=0L) public int getTop(); method @Deprecated public default operator com.google.accompanist.insets.Insets minus(com.google.accompanist.insets.Insets other); method @Deprecated public default operator com.google.accompanist.insets.Insets plus(com.google.accompanist.insets.Insets other); - property @IntRange(from=0) public abstract int bottom; - property @IntRange(from=0) public abstract int left; - property @IntRange(from=0) public abstract int right; - property @IntRange(from=0) public abstract int top; + property @IntRange(from=0L) public abstract int bottom; + property @IntRange(from=0L) public abstract int left; + property @IntRange(from=0L) public abstract int right; + property @IntRange(from=0L) public abstract int top; field @Deprecated public static final com.google.accompanist.insets.Insets.Companion Companion; } @@ -68,6 +70,8 @@ package com.google.accompanist.insets { } @Deprecated public enum VerticalSide { + method @Deprecated public static com.google.accompanist.insets.VerticalSide valueOf(String name) throws java.lang.IllegalArgumentException; + method @Deprecated public static com.google.accompanist.insets.VerticalSide[] values(); enum_constant @Deprecated public static final com.google.accompanist.insets.VerticalSide Bottom; enum_constant @Deprecated public static final com.google.accompanist.insets.VerticalSide Top; } @@ -132,6 +136,7 @@ package com.google.accompanist.insets { public final class WindowInsetsKt { method @Deprecated @androidx.compose.runtime.Composable public static void ProvideWindowInsets(optional boolean consumeWindowInsets, optional boolean windowInsetsAnimationsEnabled, kotlin.jvm.functions.Function0 content); method @Deprecated public static androidx.compose.runtime.ProvidableCompositionLocal getLocalWindowInsets(); + property @Deprecated public static final androidx.compose.runtime.ProvidableCompositionLocal LocalWindowInsets; } public final class WindowInsetsTypeKt { diff --git a/insets/build.gradle b/insets/build.gradle deleted file mode 100644 index fd066ea45..000000000 --- a/insets/build.gradle +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2020 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. - */ - -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jetbrains.dokka' -} - -kotlin { - explicitApi() -} - -android { - namespace "com.google.accompanist.insets" - - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 21 - // targetSdkVersion has no effect for libraries. This is only used for the test APK - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - buildFeatures { - buildConfig false - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion libs.versions.composeCompiler.get() - } - - lintOptions { - textReport true - textOutput 'stdout' - // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks - checkReleaseBuilds false - } - - packagingOptions { - // Some of the META-INF files conflict with coroutines-test. Exclude them to enable - // our test APK to build (has no effect on our AARs) - excludes += "/META-INF/AL2.0" - excludes += "/META-INF/LGPL2.1" - } - - testOptions { - unitTests { - includeAndroidResources = true - } - animationsDisabled true - } - - sourceSets { - test { - java.srcDirs += 'src/sharedTest/kotlin' - res.srcDirs += 'src/sharedTest/res' - } - androidTest { - java.srcDirs += 'src/sharedTest/kotlin' - res.srcDirs += 'src/sharedTest/res' - } - } -} - -dependencies { - implementation libs.androidx.core - implementation libs.androidx.dynamicanimation - - implementation libs.compose.foundation.foundation - implementation libs.kotlin.coroutines.android - - // ====================== - // Test dependencies - // ====================== - - androidTestImplementation project(':internal-testutils') - testImplementation project(':internal-testutils') - - androidTestImplementation libs.junit - testImplementation libs.junit - - androidTestImplementation libs.truth - testImplementation libs.truth - - androidTestImplementation libs.compose.ui.test.junit4 - testImplementation libs.compose.ui.test.junit4 - - androidTestImplementation libs.compose.ui.test.manifest - testImplementation libs.compose.ui.test.manifest - - androidTestImplementation libs.androidx.test.runner - testImplementation libs.androidx.test.runner - - testImplementation libs.robolectric -} - -apply plugin: "com.vanniktech.maven.publish" diff --git a/insets/build.gradle.kts b/insets/build.gradle.kts new file mode 100644 index 000000000..a6acf8a5a --- /dev/null +++ b/insets/build.gradle.kts @@ -0,0 +1,126 @@ +/* + * Copyright 2023 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. + */ +@file:Suppress("UnstableApiUsage") + +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.android.kotlin.get().pluginId) + id(libs.plugins.jetbrains.dokka.get().pluginId) + id(libs.plugins.gradle.metalava.get().pluginId) + id(libs.plugins.vanniktech.maven.publish.get().pluginId) +} + +kotlin { + explicitApi() +} + +android { + namespace = "com.google.accompanist.insets" + + compileSdk = 33 + + defaultConfig { + minSdk = 21 + // targetSdkVersion has no effect for libraries. This is only used for the test APK + targetSdk = 33 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig = false + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + + lint { + textReport = true + textOutput = File("stdout") + // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks + checkReleaseBuilds = false + } + + packaging { + // Some of the META-INF files conflict with coroutines-test. Exclude them to enable + // our test APK to build (has no effect on our AARs) + resources { + excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") + } + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + animationsDisabled = true + } + + sourceSets { + named("test") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + named("androidTest") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + } +} + +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + +dependencies { + implementation(libs.androidx.core) + implementation(libs.androidx.dynamicanimation) + + implementation(libs.compose.foundation.foundation) + implementation(libs.kotlin.coroutines.android) + + // ====================== + // Test dependencies + // ====================== + + androidTestImplementation(project(":internal-testutils")) + testImplementation(project(":internal-testutils")) + + androidTestImplementation(libs.junit) + testImplementation(libs.junit) + + androidTestImplementation(libs.truth) + testImplementation(libs.truth) + + androidTestImplementation(libs.compose.ui.test.junit4) + testImplementation(libs.compose.ui.test.junit4) + + androidTestImplementation(libs.compose.ui.test.manifest) + testImplementation(libs.compose.ui.test.manifest) + + androidTestImplementation(libs.androidx.test.runner) + testImplementation(libs.androidx.test.runner) + + testImplementation(libs.robolectric) +} diff --git a/internal-testutils/build.gradle b/internal-testutils/build.gradle deleted file mode 100644 index 3795bac43..000000000 --- a/internal-testutils/build.gradle +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2020 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. - */ - -plugins { - id 'com.android.library' - id 'kotlin-android' -} - -android { - namespace "com.google.accompanist.internal.test" - - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 21 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - buildFeatures { - buildConfig false - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion libs.versions.composeCompiler.get() - } - - lintOptions { - textReport true - textOutput 'stdout' - // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks - checkReleaseBuilds false - } - - packagingOptions { - // Certain libraries include licence files in their JARs. Exclude them to enable - // our test APK to build (has no effect on our AARs) - excludes += "/META-INF/AL2.0" - excludes += "/META-INF/LGPL2.1" - } -} - -dependencies { - implementation libs.kotlin.stdlib - implementation libs.kotlin.coroutines.android - - implementation libs.compose.foundation.foundation - api libs.compose.ui.test.junit4 - - api libs.androidx.test.core - implementation libs.truth -} diff --git a/internal-testutils/build.gradle.kts b/internal-testutils/build.gradle.kts new file mode 100644 index 000000000..959311e18 --- /dev/null +++ b/internal-testutils/build.gradle.kts @@ -0,0 +1,71 @@ +/* + * Copyright 2023 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. + */ +@file:Suppress("UnstableApiUsage") + +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.android.kotlin.get().pluginId) +} + +android { + namespace = "com.google.accompanist.internal.test" + + compileSdk = 33 + + defaultConfig { + minSdk = 21 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig = false + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + + lint { + textReport = true + textOutput = File("stdout") + // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks + checkReleaseBuilds = false + } + packaging { + // Certain libraries include licence files in their JARs. Exclude them to enable + // our test APK to build (has no effect on our AARs) + resources { + excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") + } + } +} + +dependencies { + implementation(libs.kotlin.stdlib) + implementation(libs.kotlin.coroutines.android) + + implementation(libs.compose.foundation.foundation) + api(libs.compose.ui.test.junit4) + + api(libs.androidx.test.core) + implementation(libs.truth) +} diff --git a/mkdocs.yml b/mkdocs.yml index 215be2b12..8a408db74 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -99,5 +99,6 @@ markdown_extensions: permalink: true - pymdownx.betterem - pymdownx.superfences - - pymdownx.tabbed + - pymdownx.tabbed: + alternate_style: true - pymdownx.details diff --git a/navigation-animation/api/current.api b/navigation-animation/api/current.api index b3cf24f5d..656bddd84 100644 --- a/navigation-animation/api/current.api +++ b/navigation-animation/api/current.api @@ -11,13 +11,13 @@ package com.google.accompanist.navigation.animation { } public final class AnimatedNavHostKt { - method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedNavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional String? route, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition> popExitTransition, kotlin.jvm.functions.Function1 builder); - method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedNavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition> popExitTransition); + method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedNavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional String? route, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition> popExitTransition, kotlin.jvm.functions.Function1 builder); + method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedNavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition> popExitTransition); } public final class NavGraphBuilderKt { - method @androidx.compose.animation.ExperimentalAnimationApi public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List arguments, optional java.util.List deepLinks, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition>? enterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition>? exitTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition>? popEnterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition>? popExitTransition, kotlin.jvm.functions.Function2 content); - method @androidx.compose.animation.ExperimentalAnimationApi public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List arguments, optional java.util.List deepLinks, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition>? enterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition>? exitTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition>? popEnterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition>? popExitTransition, kotlin.jvm.functions.Function1 builder); + method @androidx.compose.animation.ExperimentalAnimationApi public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List arguments, optional java.util.List deepLinks, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition>? enterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition>? exitTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition>? popEnterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition>? popExitTransition, kotlin.jvm.functions.Function2 content); + method @androidx.compose.animation.ExperimentalAnimationApi public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List arguments, optional java.util.List deepLinks, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition>? enterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition>? exitTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.EnterTransition>? popEnterTransition, optional kotlin.jvm.functions.Function1,? extends androidx.compose.animation.ExitTransition>? popExitTransition, kotlin.jvm.functions.Function1 builder); } public final class NavHostControllerKt { diff --git a/navigation-animation/build.gradle b/navigation-animation/build.gradle index 484b1cb50..d3f5cf53d 100644 --- a/navigation-animation/build.gradle +++ b/navigation-animation/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -72,6 +73,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { api libs.androidx.navigation.compose implementation libs.compose.animation.animation diff --git a/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/AnimatedComposeNavigator.kt b/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/AnimatedComposeNavigator.kt index effa9dc70..4425bba4c 100644 --- a/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/AnimatedComposeNavigator.kt +++ b/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/AnimatedComposeNavigator.kt @@ -16,6 +16,7 @@ package com.google.accompanist.navigation.animation +import android.annotation.SuppressLint import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable @@ -24,6 +25,7 @@ import androidx.navigation.NavBackStackEntry import androidx.navigation.NavDestination import androidx.navigation.NavOptions import androidx.navigation.Navigator +import kotlin.collections.forEach /** * Navigator that navigates through [Composable]s. Every destination using this Navigator must @@ -39,6 +41,7 @@ public class AnimatedComposeNavigator : Navigator, navOptions: NavOptions?, diff --git a/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/AnimatedNavHost.kt b/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/AnimatedNavHost.kt index 0b12f3e5a..7bbee257d 100644 --- a/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/AnimatedNavHost.kt +++ b/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/AnimatedNavHost.kt @@ -18,7 +18,7 @@ package com.google.accompanist.navigation.animation import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.ContentTransform import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition @@ -78,12 +78,12 @@ public fun AnimatedNavHost( modifier: Modifier = Modifier, contentAlignment: Alignment = Alignment.Center, route: String? = null, - enterTransition: (AnimatedContentScope.() -> EnterTransition) = + enterTransition: (AnimatedContentTransitionScope.() -> EnterTransition) = { fadeIn(animationSpec = tween(700)) }, - exitTransition: (AnimatedContentScope.() -> ExitTransition) = + exitTransition: (AnimatedContentTransitionScope.() -> ExitTransition) = { fadeOut(animationSpec = tween(700)) }, - popEnterTransition: (AnimatedContentScope.() -> EnterTransition) = enterTransition, - popExitTransition: (AnimatedContentScope.() -> ExitTransition) = exitTransition, + popEnterTransition: (AnimatedContentTransitionScope.() -> EnterTransition) = enterTransition, + popExitTransition: (AnimatedContentTransitionScope.() -> ExitTransition) = exitTransition, builder: NavGraphBuilder.() -> Unit ) { AnimatedNavHost( @@ -121,12 +121,12 @@ public fun AnimatedNavHost( graph: NavGraph, modifier: Modifier = Modifier, contentAlignment: Alignment = Alignment.Center, - enterTransition: (AnimatedContentScope.() -> EnterTransition) = + enterTransition: (AnimatedContentTransitionScope.() -> EnterTransition) = { fadeIn(animationSpec = tween(700)) }, - exitTransition: (AnimatedContentScope.() -> ExitTransition) = + exitTransition: (AnimatedContentTransitionScope.() -> ExitTransition) = { fadeOut(animationSpec = tween(700)) }, - popEnterTransition: (AnimatedContentScope.() -> EnterTransition) = enterTransition, - popExitTransition: (AnimatedContentScope.() -> ExitTransition) = exitTransition, + popEnterTransition: (AnimatedContentTransitionScope.() -> EnterTransition) = enterTransition, + popExitTransition: (AnimatedContentTransitionScope.() -> ExitTransition) = exitTransition, ) { val lifecycleOwner = LocalLifecycleOwner.current @@ -164,7 +164,7 @@ public fun AnimatedNavHost( val backStackEntry = visibleEntries.lastOrNull() if (backStackEntry != null) { - val finalEnter: AnimatedContentScope.() -> EnterTransition = { + val finalEnter: AnimatedContentTransitionScope.() -> EnterTransition = { val targetDestination = targetState.destination as AnimatedComposeNavigator.Destination if (composeNavigator.isPop.value) { @@ -178,7 +178,7 @@ public fun AnimatedNavHost( } } - val finalExit: AnimatedContentScope.() -> ExitTransition = { + val finalExit: AnimatedContentTransitionScope.() -> ExitTransition = { val initialDestination = initialState.destination as AnimatedComposeNavigator.Destination if (composeNavigator.isPop.value) { @@ -243,16 +243,16 @@ public fun AnimatedNavHost( @ExperimentalAnimationApi internal val enterTransitions = mutableMapOf.() -> EnterTransition?)?>() + (AnimatedContentTransitionScope.() -> EnterTransition?)?>() @ExperimentalAnimationApi internal val exitTransitions = - mutableMapOf.() -> ExitTransition?)?>() + mutableMapOf.() -> ExitTransition?)?>() @ExperimentalAnimationApi internal val popEnterTransitions = - mutableMapOf.() -> EnterTransition?)?>() + mutableMapOf.() -> EnterTransition?)?>() @ExperimentalAnimationApi internal val popExitTransitions = - mutableMapOf.() -> ExitTransition?)?>() + mutableMapOf.() -> ExitTransition?)?>() diff --git a/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/NavGraphBuilder.kt b/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/NavGraphBuilder.kt index 887388efe..8326dfe0e 100644 --- a/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/NavGraphBuilder.kt +++ b/navigation-animation/src/main/java/com/google/accompanist/navigation/animation/NavGraphBuilder.kt @@ -16,7 +16,8 @@ package com.google.accompanist.navigation.animation -import androidx.compose.animation.AnimatedContentScope +import android.annotation.SuppressLint +import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition @@ -42,18 +43,19 @@ import androidx.navigation.get * @param popExitTransition callback to determine the destination's popExit transition * @param content composable for the destination */ +@SuppressLint("NewApi") // b/187418647 @ExperimentalAnimationApi public fun NavGraphBuilder.composable( route: String, arguments: List = emptyList(), deepLinks: List = emptyList(), - enterTransition: (AnimatedContentScope.() -> EnterTransition?)? = null, - exitTransition: (AnimatedContentScope.() -> ExitTransition?)? = null, + enterTransition: (AnimatedContentTransitionScope.() -> EnterTransition?)? = null, + exitTransition: (AnimatedContentTransitionScope.() -> ExitTransition?)? = null, popEnterTransition: ( - AnimatedContentScope.() -> EnterTransition? + AnimatedContentTransitionScope.() -> EnterTransition? )? = enterTransition, popExitTransition: ( - AnimatedContentScope.() -> ExitTransition? + AnimatedContentTransitionScope.() -> ExitTransition? )? = exitTransition, content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit ) { @@ -99,13 +101,13 @@ public fun NavGraphBuilder.navigation( route: String, arguments: List = emptyList(), deepLinks: List = emptyList(), - enterTransition: (AnimatedContentScope.() -> EnterTransition?)? = null, - exitTransition: (AnimatedContentScope.() -> ExitTransition?)? = null, + enterTransition: (AnimatedContentTransitionScope.() -> EnterTransition?)? = null, + exitTransition: (AnimatedContentTransitionScope.() -> ExitTransition?)? = null, popEnterTransition: ( - AnimatedContentScope.() -> EnterTransition? + AnimatedContentTransitionScope.() -> EnterTransition? )? = enterTransition, popExitTransition: ( - AnimatedContentScope.() -> ExitTransition? + AnimatedContentTransitionScope.() -> ExitTransition? )? = exitTransition, builder: NavGraphBuilder.() -> Unit ) { diff --git a/navigation-material/api/current.api b/navigation-material/api/current.api index c32d4a94e..1adf995f0 100644 --- a/navigation-material/api/current.api +++ b/navigation-material/api/current.api @@ -38,7 +38,7 @@ package com.google.accompanist.navigation.material { property public final androidx.compose.material.ModalBottomSheetValue targetValue; } - @kotlin.RequiresOptIn(message="This APIs are experimental and may change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) public @interface ExperimentalMaterialNavigationApi { + @kotlin.RequiresOptIn(message="This APIs are experimental and may change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterialNavigationApi { } public final class NavGraphBuilderKt { diff --git a/navigation-material/build.gradle b/navigation-material/build.gradle index 3f2b2a44c..445c7620f 100644 --- a/navigation-material/build.gradle +++ b/navigation-material/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -72,6 +73,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { api libs.androidx.navigation.compose implementation libs.compose.foundation.foundation diff --git a/navigation-material/src/main/java/com/google/accompanist/navigation/material/BottomSheetNavigator.kt b/navigation-material/src/main/java/com/google/accompanist/navigation/material/BottomSheetNavigator.kt index 86b3ba4ef..d0866f88b 100644 --- a/navigation-material/src/main/java/com/google/accompanist/navigation/material/BottomSheetNavigator.kt +++ b/navigation-material/src/main/java/com/google/accompanist/navigation/material/BottomSheetNavigator.kt @@ -16,6 +16,7 @@ package com.google.accompanist.navigation.material +import android.annotation.SuppressLint import androidx.compose.animation.core.AnimationSpec import androidx.compose.foundation.layout.ColumnScope import androidx.compose.material.ExperimentalMaterialApi @@ -42,9 +43,9 @@ import androidx.navigation.NavOptions import androidx.navigation.Navigator import androidx.navigation.NavigatorState import com.google.accompanist.navigation.material.BottomSheetNavigator.Destination +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.transform /** @@ -200,11 +201,16 @@ class BottomSheetNavigator( // the sheet first before deciding whether to re-show it or keep it hidden try { sheetState.hide() + } catch (_: CancellationException) { + // We catch but ignore possible cancellation exceptions as we don't want + // them to bubble up and cancel the whole produceState coroutine } finally { emit(backStackEntries.lastOrNull()) } } - .collectLatest { value = it } + .collect { + value = it + } } if (retainedEntry != null) { @@ -247,6 +253,7 @@ class BottomSheetNavigator( content = {} ) + @SuppressLint("NewApi") // b/187418647 override fun navigate( entries: List, navOptions: NavOptions?, diff --git a/navigation-material/src/main/java/com/google/accompanist/navigation/material/NavGraphBuilder.kt b/navigation-material/src/main/java/com/google/accompanist/navigation/material/NavGraphBuilder.kt index 21a0dd68a..55d8e205c 100644 --- a/navigation-material/src/main/java/com/google/accompanist/navigation/material/NavGraphBuilder.kt +++ b/navigation-material/src/main/java/com/google/accompanist/navigation/material/NavGraphBuilder.kt @@ -16,6 +16,7 @@ package com.google.accompanist.navigation.material +import android.annotation.SuppressLint import androidx.compose.foundation.layout.ColumnScope import androidx.compose.runtime.Composable import androidx.navigation.NamedNavArgument @@ -32,6 +33,7 @@ import androidx.navigation.get * @param deepLinks list of deep links to associate with the destinations * @param content the sheet content at the given destination */ +@SuppressLint("NewApi") // b/187418647 @ExperimentalMaterialNavigationApi public fun NavGraphBuilder.bottomSheet( route: String, diff --git a/pager-indicators/api/current.api b/pager-indicators/api/current.api index 1ea8a99c0..596c7c36b 100644 --- a/pager-indicators/api/current.api +++ b/pager-indicators/api/current.api @@ -2,12 +2,15 @@ package com.google.accompanist.pager { public final class PagerIndicatorKt { - method @androidx.compose.runtime.Composable @com.google.accompanist.pager.ExperimentalPagerApi public static void HorizontalPagerIndicator(com.google.accompanist.pager.PagerState pagerState, optional androidx.compose.ui.Modifier modifier, optional int pageCount, optional kotlin.jvm.functions.Function1 pageIndexMapping, optional long activeColor, optional long inactiveColor, optional float indicatorWidth, optional float indicatorHeight, optional float spacing, optional androidx.compose.ui.graphics.Shape indicatorShape); - method @androidx.compose.runtime.Composable @com.google.accompanist.pager.ExperimentalPagerApi public static void VerticalPagerIndicator(com.google.accompanist.pager.PagerState pagerState, optional androidx.compose.ui.Modifier modifier, optional int pageCount, optional kotlin.jvm.functions.Function1 pageIndexMapping, optional long activeColor, optional long inactiveColor, optional float indicatorHeight, optional float indicatorWidth, optional float spacing, optional androidx.compose.ui.graphics.Shape indicatorShape); + method @Deprecated @androidx.compose.runtime.Composable public static void HorizontalPagerIndicator(com.google.accompanist.pager.PagerState pagerState, optional androidx.compose.ui.Modifier modifier, optional int pageCount, optional kotlin.jvm.functions.Function1 pageIndexMapping, optional long activeColor, optional long inactiveColor, optional float indicatorWidth, optional float indicatorHeight, optional float spacing, optional androidx.compose.ui.graphics.Shape indicatorShape); + method @androidx.compose.runtime.Composable public static void HorizontalPagerIndicator(androidx.compose.foundation.pager.PagerState pagerState, int pageCount, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1 pageIndexMapping, optional long activeColor, optional long inactiveColor, optional float indicatorWidth, optional float indicatorHeight, optional float spacing, optional androidx.compose.ui.graphics.Shape indicatorShape); + method @Deprecated @androidx.compose.runtime.Composable public static void VerticalPagerIndicator(com.google.accompanist.pager.PagerState pagerState, optional androidx.compose.ui.Modifier modifier, optional int pageCount, optional kotlin.jvm.functions.Function1 pageIndexMapping, optional long activeColor, optional long inactiveColor, optional float indicatorHeight, optional float indicatorWidth, optional float spacing, optional androidx.compose.ui.graphics.Shape indicatorShape); + method @androidx.compose.runtime.Composable public static void VerticalPagerIndicator(androidx.compose.foundation.pager.PagerState pagerState, int pageCount, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1 pageIndexMapping, optional long activeColor, optional long inactiveColor, optional float indicatorHeight, optional float indicatorWidth, optional float spacing, optional androidx.compose.ui.graphics.Shape indicatorShape); } public final class PagerTabKt { - method @com.google.accompanist.pager.ExperimentalPagerApi public static androidx.compose.ui.Modifier pagerTabIndicatorOffset(androidx.compose.ui.Modifier, com.google.accompanist.pager.PagerState pagerState, java.util.List tabPositions, optional kotlin.jvm.functions.Function1 pageIndexMapping); + method @Deprecated @com.google.accompanist.pager.ExperimentalPagerApi public static androidx.compose.ui.Modifier pagerTabIndicatorOffset(androidx.compose.ui.Modifier, com.google.accompanist.pager.PagerState pagerState, java.util.List tabPositions, optional kotlin.jvm.functions.Function1 pageIndexMapping); + method @com.google.accompanist.pager.ExperimentalPagerApi public static androidx.compose.ui.Modifier pagerTabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.foundation.pager.PagerState pagerState, java.util.List tabPositions, optional kotlin.jvm.functions.Function1 pageIndexMapping); } } diff --git a/pager-indicators/build.gradle b/pager-indicators/build.gradle index 33825432b..668b8ff96 100644 --- a/pager-indicators/build.gradle +++ b/pager-indicators/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -83,6 +84,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { api project(':pager') api libs.compose.material.material diff --git a/pager-indicators/src/androidTest/kotlin/com/google/accompanist/pager/TabIndicatorTest.kt b/pager-indicators/src/androidTest/kotlin/com/google/accompanist/pager/TabIndicatorTest.kt index 3843d2667..5eadafbf5 100644 --- a/pager-indicators/src/androidTest/kotlin/com/google/accompanist/pager/TabIndicatorTest.kt +++ b/pager-indicators/src/androidTest/kotlin/com/google/accompanist/pager/TabIndicatorTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.layout.Box diff --git a/pager-indicators/src/androidTest/kotlin/com/google/accompanist/pager/TabIndicatorWithFoundationPagerTest.kt b/pager-indicators/src/androidTest/kotlin/com/google/accompanist/pager/TabIndicatorWithFoundationPagerTest.kt new file mode 100644 index 000000000..3fe519010 --- /dev/null +++ b/pager-indicators/src/androidTest/kotlin/com/google/accompanist/pager/TabIndicatorWithFoundationPagerTest.kt @@ -0,0 +1,181 @@ +/* + * Copyright 2022 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. + */ + +@file:Suppress("DEPRECATION") +package com.google.accompanist.pager + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ScrollableTabRow +import androidx.compose.material.Tab +import androidx.compose.material.TabRowDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.getBoundsInRoot +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.height +import androidx.compose.ui.unit.lerp +import androidx.compose.ui.unit.times +import androidx.compose.ui.unit.width +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import androidx.compose.foundation.pager.HorizontalPager as FoundationHorizontalPager +import androidx.compose.foundation.pager.PagerState as FoundationPagerState +import androidx.compose.foundation.pager.rememberPagerState as rememberFoundationPagerState + +@OptIn(ExperimentalFoundationApi::class) +@RunWith(AndroidJUnit4::class) +class TabIndicatorWithFoundationPagerTest { + @get:Rule + val rule = createComposeRule() + + private val IndicatorTag = "indicator" + private val TabRowTag = "TabRow" + + @Test + fun emptyPager() { + rule.setContent { + val pagerState = rememberFoundationPagerState() + TabRow(pagerState, 0) + } + } + + @Test + fun scrollOffsetIsPositive() { + lateinit var pagerState: FoundationPagerState + val pageCount = 4 + rule.setContent { + pagerState = rememberFoundationPagerState() + Column { + TabRow(pagerState, pageCount) + FoundationHorizontalPager(pageCount = pageCount, state = pagerState) { + Box(Modifier.fillMaxSize()) + } + } + } + + rule.runOnIdle { + runBlocking { pagerState.scrollToPage(1, 0.25f) } + } + + val tab1Bounds = rule.onNodeWithTag("1").getBoundsInRoot() + val tab2Bounds = rule.onNodeWithTag("2").getBoundsInRoot() + val indicatorBounds = rule.onNodeWithTag(IndicatorTag).getBoundsInRoot() + + with(rule.density) { + assertThat(indicatorBounds.left.roundToPx()) + .isEqualTo(lerp(tab1Bounds.left, tab2Bounds.left, 0.25f).roundToPx()) + assertThat(indicatorBounds.width.roundToPx()) + .isEqualTo(lerp(tab1Bounds.width, tab2Bounds.width, 0.25f).roundToPx()) + } + } + + @Test + fun scrollOffsetIsNegative() { + lateinit var pagerState: FoundationPagerState + val pageCount = 4 + rule.setContent { + pagerState = rememberFoundationPagerState() + Column { + TabRow(pagerState, pageCount) + FoundationHorizontalPager(pageCount = pageCount, state = pagerState) { + Box(Modifier.fillMaxSize()) + } + } + } + + rule.runOnIdle { + runBlocking { pagerState.scrollToPage(1, -0.25f) } + } + + val tab1Bounds = rule.onNodeWithTag("1").getBoundsInRoot() + val tab0Bounds = rule.onNodeWithTag("0").getBoundsInRoot() + val indicatorBounds = rule.onNodeWithTag(IndicatorTag).getBoundsInRoot() + + with(rule.density) { + assertThat(indicatorBounds.left.roundToPx()) + .isEqualTo(lerp(tab1Bounds.left, tab0Bounds.left, 0.25f).roundToPx()) + assertThat(indicatorBounds.width.roundToPx()) + .isEqualTo(lerp(tab1Bounds.width, tab0Bounds.width, 0.25f).roundToPx()) + } + } + + @Test + fun indicatorIsAtBottom() { + lateinit var pagerState: FoundationPagerState + val pageCount = 4 + rule.setContent { + pagerState = rememberFoundationPagerState() + Column { + TabRow(pagerState, pageCount) + FoundationHorizontalPager(pageCount = pageCount, state = pagerState) { + Box(Modifier.fillMaxSize()) + } + } + } + + rule.runOnIdle { + runBlocking { pagerState.scrollToPage(1, 0.25f) } + } + + val tabRowBounds = rule.onNodeWithTag(TabRowTag).getBoundsInRoot() + + val indicatorBounds = rule.onNodeWithTag(IndicatorTag).getBoundsInRoot() + + with(rule.density) { + assertThat(indicatorBounds.height.roundToPx()).isEqualTo(2.dp.roundToPx()) + assertThat(indicatorBounds.bottom).isEqualTo(tabRowBounds.bottom) + } + } + + @OptIn(ExperimentalPagerApi::class) + @Composable + private fun TabRow(pagerState: FoundationPagerState, pageCount: Int) { + ScrollableTabRow( + selectedTabIndex = pagerState.currentPage, + indicator = { tabPositions -> + TabRowDefaults.Indicator( + Modifier + .pagerTabIndicatorOffset(pagerState, tabPositions) + .testTag(IndicatorTag), + height = 2.dp + ) + }, + modifier = Modifier.testTag(TabRowTag) + ) { + // Add tabs for all of our pages + (0 until pageCount).forEach { index -> + Tab( + text = { Text("Tab $index", Modifier.padding(horizontal = index * 5.dp)) }, + selected = pagerState.currentPage == index, + modifier = Modifier.testTag("$index"), + onClick = {} + ) + } + } + } +} diff --git a/pager-indicators/src/main/java/com/google/accompanist/pager/PagerIndicator.kt b/pager-indicators/src/main/java/com/google/accompanist/pager/PagerIndicator.kt index 0731a080e..fdb1693b3 100644 --- a/pager-indicators/src/main/java/com/google/accompanist/pager/PagerIndicator.kt +++ b/pager-indicators/src/main/java/com/google/accompanist/pager/PagerIndicator.kt @@ -14,8 +14,10 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -28,6 +30,7 @@ import androidx.compose.material.ContentAlpha import androidx.compose.material.LocalContentAlpha import androidx.compose.material.LocalContentColor import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -40,7 +43,7 @@ import kotlin.math.absoluteValue import kotlin.math.sign /** - * An horizontally laid out indicator for a [HorizontalPager] or [VerticalPager], representing + * A horizontally laid out indicator for a [HorizontalPager] or [VerticalPager], representing * the currently active page and total pages drawn using a [Shape]. * * This element allows the setting of the [indicatorShape], which defines how the @@ -48,7 +51,7 @@ import kotlin.math.sign * * @sample com.google.accompanist.sample.pager.HorizontalPagerIndicatorSample * - * @param pagerState the state object of your [Pager] to be used to observe the list's state. + * @param pagerState the state object of your pager to be used to observe the list's state. * @param modifier the modifier to apply to this layout. * @param pageCount the size of indicators should be displayed, defaults to [PagerState.pageCount]. * If you are implementing a looping pager with a much larger [PagerState.pageCount] @@ -63,7 +66,13 @@ import kotlin.math.sign * @param spacing the spacing between each indicator in [Dp]. * @param indicatorShape the shape representing each indicator. This defaults to [CircleShape]. */ -@ExperimentalPagerApi +@Deprecated( + """ + HorizontalPagerIndicator for accompanist Pagers are deprecated, please use the version that takes + androidx.compose.foundation.pager.PagerState instead +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""" +) @Composable fun HorizontalPagerIndicator( pagerState: PagerState, @@ -77,6 +86,106 @@ fun HorizontalPagerIndicator( spacing: Dp = indicatorWidth, indicatorShape: Shape = CircleShape, ) { + val stateBridge = remember(pagerState) { + object : PagerStateBridge { + override val currentPage: Int + get() = pagerState.currentPage + override val currentPageOffset: Float + get() = pagerState.currentPageOffset + } + } + + HorizontalPagerIndicator( + pagerState = stateBridge, + pageCount = pageCount, + modifier = modifier, + pageIndexMapping = pageIndexMapping, + activeColor = activeColor, + inactiveColor = inactiveColor, + indicatorHeight = indicatorHeight, + indicatorWidth = indicatorWidth, + spacing = spacing, + indicatorShape = indicatorShape + ) +} + +/** + * A horizontally laid out indicator for a [androidx.compose.foundation.pager.HorizontalPager] or + * [androidx.compose.foundation.pager.VerticalPager], representing + * the currently active page and total pages drawn using a [Shape]. + * + * This element allows the setting of the [indicatorShape], which defines how the + * indicator is visually represented. + * + * @sample com.google.accompanist.sample.pager.HorizontalPagerIndicatorSample + * + * @param pagerState A [androidx.compose.foundation.pager.PagerState] object of your + * [androidx.compose.foundation.pager.VerticalPager] or + * [androidx.compose.foundation.pager.HorizontalPager]to be used to observe the list's state. + * @param modifier the modifier to apply to this layout. + * @param pageCount the size of indicators should be displayed. + * If you are implementing a looping pager with a much larger [pageCount] + * than indicators should displayed, e.g. [Int.MAX_VALUE], specify you real size in this param. + * @param pageIndexMapping describe how to get the position of active indicator by the giving page + * from [androidx.compose.foundation.pager.PagerState.currentPage]. + * @param activeColor the color of the active Page indicator + * @param inactiveColor the color of page indicators that are inactive. This defaults to + * [activeColor] with the alpha component set to the [ContentAlpha.disabled]. + * @param indicatorWidth the width of each indicator in [Dp]. + * @param indicatorHeight the height of each indicator in [Dp]. Defaults to [indicatorWidth]. + * @param spacing the spacing between each indicator in [Dp]. + * @param indicatorShape the shape representing each indicator. This defaults to [CircleShape]. + */ +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun HorizontalPagerIndicator( + pagerState: androidx.compose.foundation.pager.PagerState, + pageCount: Int, + modifier: Modifier = Modifier, + pageIndexMapping: (Int) -> Int = { it }, + activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + inactiveColor: Color = activeColor.copy(ContentAlpha.disabled), + indicatorWidth: Dp = 8.dp, + indicatorHeight: Dp = indicatorWidth, + spacing: Dp = indicatorWidth, + indicatorShape: Shape = CircleShape, +) { + val stateBridge = remember(pagerState) { + object : PagerStateBridge { + override val currentPage: Int + get() = pagerState.currentPage + override val currentPageOffset: Float + get() = pagerState.currentPageOffsetFraction + } + } + + HorizontalPagerIndicator( + pagerState = stateBridge, + pageCount = pageCount, + modifier = modifier, + pageIndexMapping = pageIndexMapping, + activeColor = activeColor, + inactiveColor = inactiveColor, + indicatorHeight = indicatorHeight, + indicatorWidth = indicatorWidth, + spacing = spacing, + indicatorShape = indicatorShape + ) +} + +@Composable +private fun HorizontalPagerIndicator( + pagerState: PagerStateBridge, + pageCount: Int, + modifier: Modifier = Modifier, + pageIndexMapping: (Int) -> Int = { it }, + activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + inactiveColor: Color = activeColor.copy(ContentAlpha.disabled), + indicatorWidth: Dp = 8.dp, + indicatorHeight: Dp = indicatorWidth, + spacing: Dp = indicatorWidth, + indicatorShape: Shape = CircleShape, +) { val indicatorWidthPx = LocalDensity.current.run { indicatorWidth.roundToPx() } val spacingPx = LocalDensity.current.run { spacing.roundToPx() } @@ -105,7 +214,12 @@ fun HorizontalPagerIndicator( val offset = pagerState.currentPageOffset val next = pageIndexMapping(pagerState.currentPage + offset.sign.toInt()) val scrollPosition = ((next - position) * offset.absoluteValue + position) - .coerceIn(0f, (pageCount - 1).coerceAtLeast(0).toFloat()) + .coerceIn( + 0f, + (pageCount - 1) + .coerceAtLeast(0) + .toFloat() + ) IntOffset( x = ((spacingPx + indicatorWidthPx) * scrollPosition).toInt(), @@ -125,7 +239,7 @@ fun HorizontalPagerIndicator( } /** - * An vertically laid out indicator for a [VerticalPager] or [HorizontalPager], representing + * A vertically laid out indicator for a [VerticalPager] or [HorizontalPager], representing * the currently active page and total pages drawn using a [Shape]. * * This element allows the setting of the [indicatorShape], which defines how the @@ -133,7 +247,7 @@ fun HorizontalPagerIndicator( * * @sample com.google.accompanist.sample.pager.VerticalPagerIndicatorSample * - * @param pagerState the state object of your [Pager] to be used to observe the list's state. + * @param pagerState the state object of your pager to be used to observe the list's state. * @param modifier the modifier to apply to this layout. * @param pageCount the size of indicators should be displayed, defaults to [PagerState.pageCount]. * If you are implementing a looping pager with a much larger [PagerState.pageCount] @@ -148,7 +262,13 @@ fun HorizontalPagerIndicator( * @param spacing the spacing between each indicator in [Dp]. * @param indicatorShape the shape representing each indicator. This defaults to [CircleShape]. */ -@ExperimentalPagerApi +@Deprecated( + """ + VerticalPagerIndicator for accompanist Pagers are deprecated, please use the version that takes + androidx.compose.foundation.pager.PagerState instead +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""" +) @Composable fun VerticalPagerIndicator( pagerState: PagerState, @@ -162,6 +282,104 @@ fun VerticalPagerIndicator( spacing: Dp = indicatorHeight, indicatorShape: Shape = CircleShape, ) { + val stateBridge = remember(pagerState) { + object : PagerStateBridge { + override val currentPage: Int + get() = pagerState.currentPage + override val currentPageOffset: Float + get() = pagerState.currentPageOffset + } + } + + VerticalPagerIndicator( + pagerState = stateBridge, + pageCount = pageCount, + modifier = modifier, + pageIndexMapping = pageIndexMapping, + activeColor = activeColor, + inactiveColor = inactiveColor, + indicatorHeight = indicatorHeight, + indicatorWidth = indicatorWidth, + spacing = spacing, + indicatorShape = indicatorShape + ) +} + +/** + * A vertically laid out indicator for a [androidx.compose.foundation.pager.VerticalPager] or + * [androidx.compose.foundation.pager.HorizontalPager], representing + * the currently active page and total pages drawn using a [Shape]. + * + * This element allows the setting of the [indicatorShape], which defines how the + * indicator is visually represented. + * + * @param pagerState A [androidx.compose.foundation.pager.PagerState] object of your + * [androidx.compose.foundation.pager.VerticalPager] or + * [androidx.compose.foundation.pager.HorizontalPager]to be used to observe the list's state. + * @param modifier the modifier to apply to this layout. + * @param pageCount the size of indicators should be displayed. If you are implementing a looping + * pager with a much larger [pageCount] than indicators should displayed, e.g. [Int.MAX_VALUE], + * specify you real size in this param. + * @param pageIndexMapping describe how to get the position of active indicator by the giving page + * from [androidx.compose.foundation.pager.PagerState.currentPage]. + * @param activeColor the color of the active Page indicator + * @param inactiveColor the color of page indicators that are inactive. This defaults to + * [activeColor] with the alpha component set to the [ContentAlpha.disabled]. + * @param indicatorHeight the height of each indicator in [Dp]. + * @param indicatorWidth the width of each indicator in [Dp]. Defaults to [indicatorHeight]. + * @param spacing the spacing between each indicator in [Dp]. + * @param indicatorShape the shape representing each indicator. This defaults to [CircleShape]. + */ +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun VerticalPagerIndicator( + pagerState: androidx.compose.foundation.pager.PagerState, + pageCount: Int, + modifier: Modifier = Modifier, + pageIndexMapping: (Int) -> Int = { it }, + activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + inactiveColor: Color = activeColor.copy(ContentAlpha.disabled), + indicatorHeight: Dp = 8.dp, + indicatorWidth: Dp = indicatorHeight, + spacing: Dp = indicatorHeight, + indicatorShape: Shape = CircleShape, +) { + val stateBridge = remember(pagerState) { + object : PagerStateBridge { + override val currentPage: Int + get() = pagerState.currentPage + override val currentPageOffset: Float + get() = pagerState.currentPageOffsetFraction + } + } + + VerticalPagerIndicator( + pagerState = stateBridge, + pageCount = pageCount, + modifier = modifier, + pageIndexMapping = pageIndexMapping, + activeColor = activeColor, + inactiveColor = inactiveColor, + indicatorHeight = indicatorHeight, + indicatorWidth = indicatorWidth, + spacing = spacing, + indicatorShape = indicatorShape + ) +} + +@Composable +private fun VerticalPagerIndicator( + pagerState: PagerStateBridge, + pageCount: Int, + modifier: Modifier = Modifier, + pageIndexMapping: (Int) -> Int = { it }, + activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + inactiveColor: Color = activeColor.copy(ContentAlpha.disabled), + indicatorHeight: Dp = 8.dp, + indicatorWidth: Dp = indicatorHeight, + spacing: Dp = indicatorHeight, + indicatorShape: Shape = CircleShape, +) { val indicatorHeightPx = LocalDensity.current.run { indicatorHeight.roundToPx() } val spacingPx = LocalDensity.current.run { spacing.roundToPx() } @@ -190,7 +408,12 @@ fun VerticalPagerIndicator( val offset = pagerState.currentPageOffset val next = pageIndexMapping(pagerState.currentPage + offset.sign.toInt()) val scrollPosition = ((next - position) * offset.absoluteValue + position) - .coerceIn(0f, (pageCount - 1).coerceAtLeast(0).toFloat()) + .coerceIn( + 0f, + (pageCount - 1) + .coerceAtLeast(0) + .toFloat() + ) IntOffset( x = 0, diff --git a/pager-indicators/src/main/java/com/google/accompanist/pager/PagerTab.kt b/pager-indicators/src/main/java/com/google/accompanist/pager/PagerTab.kt index e6cc3049c..e77a26d4a 100644 --- a/pager-indicators/src/main/java/com/google/accompanist/pager/PagerTab.kt +++ b/pager-indicators/src/main/java/com/google/accompanist/pager/PagerTab.kt @@ -14,8 +14,10 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.material.ScrollableTabRow import androidx.compose.material.TabPosition import androidx.compose.material.TabRow @@ -23,18 +25,60 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.lerp - /** * This indicator syncs up a [TabRow] or [ScrollableTabRow] tab indicator with a * [HorizontalPager] or [VerticalPager]. See the sample for a full demonstration. * * @sample com.google.accompanist.sample.pager.PagerWithTabs */ +@Deprecated( + """ + pagerTabIndicatorOffset for accompanist Pagers are deprecated, please use the version that takes + androidx.compose.foundation.pager.PagerState instead +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""" +) @ExperimentalPagerApi fun Modifier.pagerTabIndicatorOffset( pagerState: PagerState, tabPositions: List, pageIndexMapping: (Int) -> Int = { it }, +): Modifier { + val stateBridge = object : PagerStateBridge { + override val currentPage: Int + get() = pagerState.currentPage + override val currentPageOffset: Float + get() = pagerState.currentPageOffset + } + + return pagerTabIndicatorOffset(stateBridge, tabPositions, pageIndexMapping) +} + +/** + * This indicator syncs up a [TabRow] or [ScrollableTabRow] tab indicator with a + * [androidx.compose.foundation.pager.HorizontalPager] or + * [androidx.compose.foundation.pager.VerticalPager]. + */ +@OptIn(ExperimentalFoundationApi::class) +fun Modifier.pagerTabIndicatorOffset( + pagerState: androidx.compose.foundation.pager.PagerState, + tabPositions: List, + pageIndexMapping: (Int) -> Int = { it }, +): Modifier { + val stateBridge = object : PagerStateBridge { + override val currentPage: Int + get() = pagerState.currentPage + override val currentPageOffset: Float + get() = pagerState.currentPageOffsetFraction + } + + return pagerTabIndicatorOffset(stateBridge, tabPositions, pageIndexMapping) +} + +private fun Modifier.pagerTabIndicatorOffset( + pagerState: PagerStateBridge, + tabPositions: List, + pageIndexMapping: (Int) -> Int = { it }, ): Modifier = layout { measurable, constraints -> if (tabPositions.isEmpty()) { // If there are no pages, nothing to show @@ -75,3 +119,8 @@ fun Modifier.pagerTabIndicatorOffset( } } } + +internal interface PagerStateBridge { + val currentPage: Int + val currentPageOffset: Float +} diff --git a/pager/README.md b/pager/README.md index f5c4440c1..a5830554b 100644 --- a/pager/README.md +++ b/pager/README.md @@ -2,6 +2,8 @@ [![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-pager)](https://search.maven.org/search?q=g:com.google.accompanist) +> :warning: This library has been deprecated as official support is now available in Compose 1.4.0. Please see our [Migration Guide](https://google.github.io/accompanist/pager/) for how to migrate. + For more information, visit the documentation: https://google.github.io/accompanist/pager ## Download diff --git a/pager/api/current.api b/pager/api/current.api index b529f6681..c28e4acc2 100644 --- a/pager/api/current.api +++ b/pager/api/current.api @@ -1,62 +1,62 @@ // Signature format: 4.0 package com.google.accompanist.pager { - @kotlin.RequiresOptIn(message="Accompanist Pager is experimental. The API may be changed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) public @interface ExperimentalPagerApi { + @Deprecated @kotlin.RequiresOptIn(message="Accompanist Pager is experimental. The API may be changed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalPagerApi { } public final class Pager { - method @androidx.compose.runtime.Composable @com.google.accompanist.pager.ExperimentalPagerApi public static void HorizontalPager(int count, optional androidx.compose.ui.Modifier modifier, optional com.google.accompanist.pager.PagerState state, optional boolean reverseLayout, optional float itemSpacing, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional kotlin.jvm.functions.Function1? key, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2 content); - method @androidx.compose.runtime.Composable @com.google.accompanist.pager.ExperimentalPagerApi public static void VerticalPager(int count, optional androidx.compose.ui.Modifier modifier, optional com.google.accompanist.pager.PagerState state, optional boolean reverseLayout, optional float itemSpacing, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional kotlin.jvm.functions.Function1? key, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2 content); - method @com.google.accompanist.pager.ExperimentalPagerApi public static float calculateCurrentOffsetForPage(com.google.accompanist.pager.PagerScope, int page); + method @Deprecated @androidx.compose.runtime.Composable public static void HorizontalPager(int count, optional androidx.compose.ui.Modifier modifier, optional com.google.accompanist.pager.PagerState state, optional boolean reverseLayout, optional float itemSpacing, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional kotlin.jvm.functions.Function1? key, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2 content); + method @Deprecated @androidx.compose.runtime.Composable public static void VerticalPager(int count, optional androidx.compose.ui.Modifier modifier, optional com.google.accompanist.pager.PagerState state, optional boolean reverseLayout, optional float itemSpacing, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional kotlin.jvm.functions.Function1? key, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2 content); + method @Deprecated public static float calculateCurrentOffsetForPage(com.google.accompanist.pager.PagerScope, int page); } - @com.google.accompanist.pager.ExperimentalPagerApi public final class PagerDefaults { + @Deprecated public final class PagerDefaults { method @Deprecated @androidx.compose.runtime.Composable @dev.chrisbanes.snapper.ExperimentalSnapperApi public androidx.compose.foundation.gestures.FlingBehavior flingBehavior(com.google.accompanist.pager.PagerState state, optional androidx.compose.animation.core.DecayAnimationSpec decayAnimationSpec, optional androidx.compose.animation.core.AnimationSpec snapAnimationSpec, optional kotlin.jvm.functions.Function1 maximumFlingDistance, optional float endContentPadding); - method @androidx.compose.runtime.Composable @dev.chrisbanes.snapper.ExperimentalSnapperApi public androidx.compose.foundation.gestures.FlingBehavior flingBehavior(com.google.accompanist.pager.PagerState state, optional androidx.compose.animation.core.DecayAnimationSpec decayAnimationSpec, optional androidx.compose.animation.core.AnimationSpec snapAnimationSpec, optional float endContentPadding, kotlin.jvm.functions.Function3 snapIndex); - method @androidx.compose.runtime.Composable @dev.chrisbanes.snapper.ExperimentalSnapperApi public androidx.compose.foundation.gestures.FlingBehavior flingBehavior(com.google.accompanist.pager.PagerState state, optional androidx.compose.animation.core.DecayAnimationSpec decayAnimationSpec, optional androidx.compose.animation.core.AnimationSpec snapAnimationSpec, optional float endContentPadding); + method @Deprecated @androidx.compose.runtime.Composable @dev.chrisbanes.snapper.ExperimentalSnapperApi public androidx.compose.foundation.gestures.FlingBehavior flingBehavior(com.google.accompanist.pager.PagerState state, optional androidx.compose.animation.core.DecayAnimationSpec decayAnimationSpec, optional androidx.compose.animation.core.AnimationSpec snapAnimationSpec, optional float endContentPadding, kotlin.jvm.functions.Function3 snapIndex); + method @Deprecated @androidx.compose.runtime.Composable @dev.chrisbanes.snapper.ExperimentalSnapperApi public androidx.compose.foundation.gestures.FlingBehavior flingBehavior(com.google.accompanist.pager.PagerState state, optional androidx.compose.animation.core.DecayAnimationSpec decayAnimationSpec, optional androidx.compose.animation.core.AnimationSpec snapAnimationSpec, optional float endContentPadding); method @Deprecated public kotlin.jvm.functions.Function1 getSinglePageFlingDistance(); - method public kotlin.jvm.functions.Function3 getSinglePageSnapIndex(); + method @Deprecated public kotlin.jvm.functions.Function3 getSinglePageSnapIndex(); property @Deprecated public final kotlin.jvm.functions.Function1 singlePageFlingDistance; property public final kotlin.jvm.functions.Function3 singlePageSnapIndex; - field public static final com.google.accompanist.pager.PagerDefaults INSTANCE; + field @Deprecated public static final com.google.accompanist.pager.PagerDefaults INSTANCE; } - @androidx.compose.runtime.Stable @com.google.accompanist.pager.ExperimentalPagerApi public interface PagerScope { - method public int getCurrentPage(); - method public float getCurrentPageOffset(); + @Deprecated @androidx.compose.runtime.Stable public interface PagerScope { + method @Deprecated public int getCurrentPage(); + method @Deprecated public float getCurrentPageOffset(); property public abstract int currentPage; property public abstract float currentPageOffset; } - @androidx.compose.runtime.Stable @com.google.accompanist.pager.ExperimentalPagerApi public final class PagerState implements androidx.compose.foundation.gestures.ScrollableState { - ctor public PagerState(optional @IntRange(from=0) int currentPage); - method @Deprecated public suspend Object? animateScrollToPage(@IntRange(from=0) int page, optional @FloatRange(from=0.0, to=1.0) float pageOffset, optional androidx.compose.animation.core.AnimationSpec animationSpec, optional float initialVelocity, optional boolean skipPages, optional kotlin.coroutines.Continuation p); - method public suspend Object? animateScrollToPage(@IntRange(from=0) int page, optional @FloatRange(from=-1.0, to=1.0) float pageOffset, optional kotlin.coroutines.Continuation p); - method public float dispatchRawDelta(float delta); - method @IntRange(from=0) public int getCurrentPage(); - method public float getCurrentPageOffset(); - method public androidx.compose.foundation.interaction.InteractionSource getInteractionSource(); - method @IntRange(from=0) public int getPageCount(); + @Deprecated @androidx.compose.runtime.Stable public final class PagerState implements androidx.compose.foundation.gestures.ScrollableState { + ctor @Deprecated public PagerState(optional @IntRange(from=0L) int currentPage); + method @Deprecated public suspend Object? animateScrollToPage(@IntRange(from=0L) int page, optional @FloatRange(from=0.0, to=1.0) float pageOffset, optional androidx.compose.animation.core.AnimationSpec animationSpec, optional float initialVelocity, optional boolean skipPages, optional kotlin.coroutines.Continuation); + method @Deprecated public suspend Object? animateScrollToPage(@IntRange(from=0L) int page, optional @FloatRange(from=-1.0, to=1.0) float pageOffset, optional kotlin.coroutines.Continuation); + method @Deprecated public float dispatchRawDelta(float delta); + method @Deprecated @IntRange(from=0L) public int getCurrentPage(); + method @Deprecated public float getCurrentPageOffset(); + method @Deprecated public androidx.compose.foundation.interaction.InteractionSource getInteractionSource(); + method @Deprecated @IntRange(from=0L) public int getPageCount(); method @Deprecated public int getTargetPage(); - method public boolean isScrollInProgress(); - method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2,?> block, kotlin.coroutines.Continuation p); - method public suspend Object? scrollToPage(@IntRange(from=0) int page, optional @FloatRange(from=-1.0, to=1.0) float pageOffset, optional kotlin.coroutines.Continuation p); - property @IntRange(from=0) public final int currentPage; + method @Deprecated public boolean isScrollInProgress(); + method @Deprecated public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2,?> block, kotlin.coroutines.Continuation); + method @Deprecated public suspend Object? scrollToPage(@IntRange(from=0L) int page, optional @FloatRange(from=-1.0, to=1.0) float pageOffset, optional kotlin.coroutines.Continuation); + property @IntRange(from=0L) public final int currentPage; property public final float currentPageOffset; property public final androidx.compose.foundation.interaction.InteractionSource interactionSource; property public boolean isScrollInProgress; - property @IntRange(from=0) public final int pageCount; + property @Deprecated @IntRange(from=0L) public final int pageCount; property @Deprecated public final int targetPage; - field public static final com.google.accompanist.pager.PagerState.Companion Companion; + field @Deprecated public static final com.google.accompanist.pager.PagerState.Companion Companion; } - public static final class PagerState.Companion { - method public androidx.compose.runtime.saveable.Saver getSaver(); + @Deprecated public static final class PagerState.Companion { + method @Deprecated public androidx.compose.runtime.saveable.Saver getSaver(); property public final androidx.compose.runtime.saveable.Saver Saver; } public final class PagerStateKt { - method @androidx.compose.runtime.Composable @com.google.accompanist.pager.ExperimentalPagerApi public static com.google.accompanist.pager.PagerState rememberPagerState(optional @IntRange(from=0) int initialPage); + method @Deprecated @androidx.compose.runtime.Composable public static com.google.accompanist.pager.PagerState rememberPagerState(optional @IntRange(from=0L) int initialPage); } } diff --git a/pager/build.gradle b/pager/build.gradle index 4ae1205a2..4f6e79ec3 100644 --- a/pager/build.gradle +++ b/pager/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -83,6 +84,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { api libs.compose.foundation.foundation api libs.snapper diff --git a/pager/src/main/java/com/google/accompanist/pager/Pager.kt b/pager/src/main/java/com/google/accompanist/pager/Pager.kt index 20f14bb33..bd8e2fad6 100644 --- a/pager/src/main/java/com/google/accompanist/pager/Pager.kt +++ b/pager/src/main/java/com/google/accompanist/pager/Pager.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") @file:JvmName("Pager") package com.google.accompanist.pager @@ -60,6 +61,13 @@ import kotlinx.coroutines.flow.filter */ internal const val DebugLog = false +@Deprecated( + """ +accompanist/pager is deprecated. +The androidx.compose equivalent of Pager is androidx.compose.foundation.pager.Pager. +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""" +) @RequiresOptIn(message = "Accompanist Pager is experimental. The API may be changed in the future.") @Retention(AnnotationRetention.BINARY) annotation class ExperimentalPagerApi @@ -67,7 +75,13 @@ annotation class ExperimentalPagerApi /** * Contains the default values used by [HorizontalPager] and [VerticalPager]. */ -@ExperimentalPagerApi +@Deprecated( + """ +accompanist/pager is deprecated. +The androidx.compose equivalent of Pager is androidx.compose.foundation.pager.Pager. +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""" +) object PagerDefaults { /** * The default implementation for the `maximumFlingDistance` parameter of @@ -107,9 +121,15 @@ object PagerDefaults { * @param endContentPadding The amount of content padding on the end edge of the lazy list * in pixels (end/bottom depending on the scrolling direction). */ + @Deprecated( + """ + accompanist/pager is deprecated. + The androidx.compose equivalent of Pager is androidx.compose.foundation.pager.Pager. + For more migration information, please visit https://google.github.io/accompanist/pager/#migration + """ + ) @Composable @ExperimentalSnapperApi - @Deprecated("MaximumFlingDistance has been deprecated in Snapper, replaced with snapIndex") @Suppress("DEPRECATION") fun flingBehavior( state: PagerState, @@ -144,6 +164,13 @@ object PagerDefaults { * for the layout. Some common use cases include limiting the fling distance, and rounding up/down * to achieve snapping to groups of items. */ + @Deprecated( + """ +accompanist/pager is deprecated. +The androidx.compose equivalent of Pager is androidx.compose.foundation.pager.Pager +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""" + ) @Composable @ExperimentalSnapperApi fun flingBehavior( @@ -174,6 +201,16 @@ object PagerDefaults { * @param endContentPadding The amount of content padding on the end edge of the lazy list * in pixels (end/bottom depending on the scrolling direction). */ + @Deprecated( + """ +accompanist/pager is deprecated. +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""", + replaceWith = ReplaceWith( + "androidx.compose.foundation.pager.PagerDefaults.flingBehavior(state = state)", + "androidx.compose.foundation.pager.PagerDefaults" + ) + ) @Composable @ExperimentalSnapperApi fun flingBehavior( @@ -218,8 +255,18 @@ object PagerDefaults { * [PagerScope.currentPage] and other properties in [PagerScope]. */ @OptIn(ExperimentalSnapperApi::class) -@ExperimentalPagerApi @Composable +@Deprecated( + """ +accompanist/pager is deprecated. +The androidx.compose equivalent of HorizontalPager is androidx.compose.foundation.pager.HorizontalPager +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""", + replaceWith = ReplaceWith( + "HorizontalPager", + "androidx.compose.foundation.pager.HorizontalPager" + ) +) fun HorizontalPager( count: Int, modifier: Modifier = Modifier, @@ -274,8 +321,14 @@ fun HorizontalPager( * [PagerScope.currentPage] and other properties in [PagerScope]. */ @OptIn(ExperimentalSnapperApi::class) -@ExperimentalPagerApi @Composable +@Deprecated( + """ +accompanist/pager is deprecated. +The androidx.compose equivalent of VerticalPager is androidx.compose.foundation.pager.VerticalPager. +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""" +) fun VerticalPager( count: Int, modifier: Modifier = Modifier, @@ -308,7 +361,6 @@ fun VerticalPager( ) } -@ExperimentalPagerApi @Composable internal fun Pager( count: Int, @@ -431,7 +483,6 @@ internal fun Pager( } } -@OptIn(ExperimentalPagerApi::class) private class ConsumeFlingNestedScrollConnection( private val consumeHorizontal: Boolean, private val consumeVertical: Boolean, @@ -480,7 +531,13 @@ private fun Velocity.consume( /** * Scope for [HorizontalPager] content. */ -@ExperimentalPagerApi +@Deprecated( + """ +accompanist/pager is deprecated. +The androidx.compose equivalent of Pager is androidx.compose.foundation.pager.Pager. +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""" +) @Stable interface PagerScope { /** @@ -494,7 +551,6 @@ interface PagerScope { val currentPageOffset: Float } -@ExperimentalPagerApi private class PagerScopeImpl( private val state: PagerState, ) : PagerScope { @@ -511,7 +567,13 @@ private class PagerScopeImpl( * * @sample com.google.accompanist.sample.pager.HorizontalPagerWithOffsetTransition */ -@ExperimentalPagerApi +@Deprecated( + """ +accompanist/pager is deprecated. +The androidx.compose equivalent of Pager is androidx.compose.foundation.pager.Pager. +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""" +) fun PagerScope.calculateCurrentOffsetForPage(page: Int): Float { return (currentPage - page) + currentPageOffset } diff --git a/pager/src/main/java/com/google/accompanist/pager/PagerState.kt b/pager/src/main/java/com/google/accompanist/pager/PagerState.kt index a2fb3307f..bcbdd198e 100644 --- a/pager/src/main/java/com/google/accompanist/pager/PagerState.kt +++ b/pager/src/main/java/com/google/accompanist/pager/PagerState.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:Suppress("MemberVisibilityCanBePrivate") +@file:Suppress("DEPRECATION", "MemberVisibilityCanBePrivate") package com.google.accompanist.pager @@ -52,7 +52,17 @@ import kotlin.math.roundToInt * * @param initialPage the initial value for [PagerState.currentPage] */ -@ExperimentalPagerApi +@Deprecated( + """ +accompanist/pager is deprecated. +The androidx.compose equivalent of rememberPagerState is androidx.compose.foundation.pager.rememberPagerState(). +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""", + replaceWith = ReplaceWith( + "androidx.compose.foundation.pager.rememberPagerState(initialPage = initialPage)", + "androidx.compose.foundation.pager.rememberPagerState" + ) +) @Composable fun rememberPagerState( @IntRange(from = 0) initialPage: Int = 0, @@ -69,7 +79,17 @@ fun rememberPagerState( * * @param currentPage the initial value for [PagerState.currentPage] */ -@ExperimentalPagerApi +@Deprecated( + """ +accompanist/pager is deprecated. +The androidx.compose equivalent of Insets is Pager. +For more migration information, please visit https://google.github.io/accompanist/pager/#migration +""", + replaceWith = ReplaceWith( + "PagerState(currentPage = currentPage)", + "androidx.compose.foundation.pager.PagerState" + ) +) @Stable class PagerState( @IntRange(from = 0) currentPage: Int = 0, @@ -112,6 +132,7 @@ class PagerState( * The number of pages to display. */ @get:IntRange(from = 0) + @Deprecated("pageCount is deprecated, use androidx.compose.foundation.pager.PagerState.canScrollForward or androidx.compose.foundation.pager.PagerState.canScrollBackward") val pageCount: Int by derivedStateOf { lazyListState.layoutInfo.totalItemsCount } diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/BaseHorizontalPagerTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/BaseHorizontalPagerTest.kt index e02916f39..d884014f3 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/BaseHorizontalPagerTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/BaseHorizontalPagerTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.background diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/BaseVerticalPagerTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/BaseVerticalPagerTest.kt index e11a3ce36..098ac3a42 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/BaseVerticalPagerTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/BaseVerticalPagerTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.background diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerCrossAxisScrollingContentTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerCrossAxisScrollingContentTest.kt index 1759e291e..b615c3597 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerCrossAxisScrollingContentTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerCrossAxisScrollingContentTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.background diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerInfiniteHeightTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerInfiniteHeightTest.kt index 81884ed78..abeb32abd 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerInfiniteHeightTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerInfiniteHeightTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.layout.Box diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerScrollingContentTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerScrollingContentTest.kt index 6e7fbcf0f..c954c2f94 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerScrollingContentTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/HorizontalPagerScrollingContentTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.background diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/PagerStateUnitTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/PagerStateUnitTest.kt index 401680ee9..fa6661b5e 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/PagerStateUnitTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/PagerStateUnitTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.text.BasicText diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/PagerTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/PagerTest.kt index 29068ee56..8d5b51a49 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/PagerTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/PagerTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.runtime.Composable diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerInfiniteWidthTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerInfiniteWidthTest.kt index ab30cb42f..0db3e1c7a 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerInfiniteWidthTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerInfiniteWidthTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.horizontalScroll diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerScrollingContentTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerScrollingContentTest.kt index 78333781f..bc4c1d33f 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerScrollingContentTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerScrollingContentTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.background diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerScrollingCrossAxisContentTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerScrollingCrossAxisContentTest.kt index 6c8b79407..45dc4362c 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerScrollingCrossAxisContentTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/VerticalPagerScrollingCrossAxisContentTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.background diff --git a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/ZeroPageCountTest.kt b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/ZeroPageCountTest.kt index 2377621ca..b2b4b354f 100644 --- a/pager/src/sharedTest/kotlin/com/google/accompanist/pager/ZeroPageCountTest.kt +++ b/pager/src/sharedTest/kotlin/com/google/accompanist/pager/ZeroPageCountTest.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.pager import androidx.compose.foundation.layout.Box diff --git a/permissions/api/current.api b/permissions/api/current.api index 33d06a1a2..9348beb05 100644 --- a/permissions/api/current.api +++ b/permissions/api/current.api @@ -1,7 +1,7 @@ // Signature format: 4.0 package com.google.accompanist.permissions { - @kotlin.RequiresOptIn(message="Accompanist Permissions is experimental. The API may be changed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) public @interface ExperimentalPermissionsApi { + @kotlin.RequiresOptIn(message="Accompanist Permissions is experimental. The API may be changed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalPermissionsApi { } @androidx.compose.runtime.Stable @com.google.accompanist.permissions.ExperimentalPermissionsApi public interface MultiplePermissionsState { diff --git a/permissions/build.gradle b/permissions/build.gradle index b1d0dcd66..7c9379d26 100644 --- a/permissions/build.gradle +++ b/permissions/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -78,6 +79,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation libs.androidx.activity.compose implementation libs.compose.foundation.foundation diff --git a/placeholder-material/build.gradle b/placeholder-material/build.gradle index 3aaa6805a..5d6340d8b 100644 --- a/placeholder-material/build.gradle +++ b/placeholder-material/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -72,6 +73,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation libs.compose.material.material api project(':placeholder') diff --git a/placeholder-material3/build.gradle b/placeholder-material3/build.gradle index 9706f553a..028f1c949 100644 --- a/placeholder-material3/build.gradle +++ b/placeholder-material3/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -72,6 +73,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation libs.compose.material3.material3 api project(':placeholder') diff --git a/placeholder/build.gradle b/placeholder/build.gradle index 974a65860..45655999e 100644 --- a/placeholder/build.gradle +++ b/placeholder/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -87,6 +88,12 @@ android { namespace 'com.google.accompanist.placeholder' } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation libs.compose.foundation.foundation implementation libs.compose.ui.util diff --git a/sample/build.gradle b/sample/build.gradle index 90d12e5e8..acbd8f0db 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -44,6 +44,13 @@ android { composeOptions { kotlinCompilerExtensionVersion libs.versions.composeCompiler.get() } + + buildTypes { + release { + signingConfig signingConfigs.debug + } + } + namespace 'com.google.accompanist.sample' } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 16f4cf938..73bd53eee 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ + @@ -424,6 +425,15 @@ + + + + + + - slideIntoContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700)) + slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(700)) else -> null } }, exitTransition = { when (targetState.destination.route) { "Red" -> - slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700)) + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(700)) else -> null } }, popEnterTransition = { when (initialState.destination.route) { "Red" -> - slideIntoContainer(AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700)) + slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(700)) else -> null } }, popExitTransition = { when (targetState.destination.route) { "Red" -> - slideOutOfContainer(AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700)) + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(700)) else -> null } } @@ -131,36 +131,36 @@ fun ExperimentalAnimationNav() { enterTransition = { when (initialState.destination.route) { "Blue" -> - slideIntoContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700)) + slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(700)) "Green" -> - slideIntoContainer(AnimatedContentScope.SlideDirection.Up, animationSpec = tween(700)) + slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Up, animationSpec = tween(700)) else -> null } }, exitTransition = { when (targetState.destination.route) { "Blue" -> - slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700)) + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(700)) "Green" -> - slideOutOfContainer(AnimatedContentScope.SlideDirection.Up, animationSpec = tween(700)) + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Up, animationSpec = tween(700)) else -> null } }, popEnterTransition = { when (initialState.destination.route) { "Blue" -> - slideIntoContainer(AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700)) + slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(700)) "Green" -> - slideIntoContainer(AnimatedContentScope.SlideDirection.Down, animationSpec = tween(700)) + slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Down, animationSpec = tween(700)) else -> null } }, popExitTransition = { when (targetState.destination.route) { "Blue" -> - slideOutOfContainer(AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700)) + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(700)) "Green" -> - slideOutOfContainer(AnimatedContentScope.SlideDirection.Down, animationSpec = tween(700)) + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Down, animationSpec = tween(700)) else -> null } } @@ -177,7 +177,7 @@ fun ExperimentalAnimationNav() { when (initialState.destination.route) { "Red" -> slideIntoContainer( - AnimatedContentScope.SlideDirection.Up, animationSpec = tween(700) + AnimatedContentTransitionScope.SlideDirection.Up, animationSpec = tween(700) ) else -> null } @@ -186,7 +186,7 @@ fun ExperimentalAnimationNav() { when (targetState.destination.route) { "Red" -> slideOutOfContainer( - AnimatedContentScope.SlideDirection.Up, animationSpec = tween(700) + AnimatedContentTransitionScope.SlideDirection.Up, animationSpec = tween(700) ) else -> null } @@ -195,7 +195,7 @@ fun ExperimentalAnimationNav() { when (initialState.destination.route) { "Red" -> slideIntoContainer( - AnimatedContentScope.SlideDirection.Down, animationSpec = tween(700) + AnimatedContentTransitionScope.SlideDirection.Down, animationSpec = tween(700) ) else -> null } @@ -204,7 +204,7 @@ fun ExperimentalAnimationNav() { when (targetState.destination.route) { "Red" -> slideOutOfContainer( - AnimatedContentScope.SlideDirection.Down, animationSpec = tween(700) + AnimatedContentTransitionScope.SlideDirection.Down, animationSpec = tween(700) ) else -> null } diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/DocsSamples.kt b/sample/src/main/java/com/google/accompanist/sample/pager/DocsSamples.kt index e97de9c92..360866305 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/DocsSamples.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/DocsSamples.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:Suppress("UNUSED_ANONYMOUS_PARAMETER") +@file:Suppress("DEPRECATION", "UNUSED_ANONYMOUS_PARAMETER") package com.google.accompanist.sample.pager @@ -38,7 +38,6 @@ import com.google.accompanist.pager.VerticalPager import com.google.accompanist.pager.VerticalPagerIndicator import com.google.accompanist.pager.pagerTabIndicatorOffset import com.google.accompanist.pager.rememberPagerState -import kotlinx.coroutines.flow.collect @OptIn(ExperimentalPagerApi::class) @Composable diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerBasicSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerBasicSample.kt index f25930784..71d6e395a 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerBasicSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerBasicSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerDifferentPaddingsSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerDifferentPaddingsSample.kt index 6c542ad38..ed6707148 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerDifferentPaddingsSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerDifferentPaddingsSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.annotation.SuppressLint diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingIndicatorSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingIndicatorSample.kt index de2cfcbf2..577fdc3ee 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingIndicatorSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingIndicatorSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingSample.kt index 6ee585462..e5b5a98b4 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingTabsSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingTabsSample.kt index dc091bfcb..779a7f528 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingTabsSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingTabsSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerScrollingContentSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerScrollingContentSample.kt index 8a1964c2e..5ca0b976f 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerScrollingContentSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerScrollingContentSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerTabsSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerTabsSample.kt index 5c082127c..f16d66ee9 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerTabsSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerTabsSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerTransitionSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerTransitionSample.kt index e752d141d..307566023 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerTransitionSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerTransitionSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerWithIndicatorSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerWithIndicatorSample.kt index 73f246cc8..1a93de893 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerWithIndicatorSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerWithIndicatorSample.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:Suppress("UNUSED_ANONYMOUS_PARAMETER") +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/NestedPagersSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/NestedPagersSample.kt index 9fcc81711..74be950db 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/NestedPagersSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/NestedPagersSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/PagerSampleItem.kt b/sample/src/main/java/com/google/accompanist/sample/pager/PagerSampleItem.kt index acb7faf75..2dea8caba 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/PagerSampleItem.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/PagerSampleItem.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import androidx.compose.foundation.Image diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/VerticalPagerBasicSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/VerticalPagerBasicSample.kt index 24accb548..15c9c5013 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/VerticalPagerBasicSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/VerticalPagerBasicSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/pager/VerticalPagerWithIndicatorSample.kt b/sample/src/main/java/com/google/accompanist/sample/pager/VerticalPagerWithIndicatorSample.kt index e506c265f..ce0706533 100644 --- a/sample/src/main/java/com/google/accompanist/sample/pager/VerticalPagerWithIndicatorSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/pager/VerticalPagerWithIndicatorSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.pager import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/placeholder/PlaceholderBasicSample.kt b/sample/src/main/java/com/google/accompanist/sample/placeholder/PlaceholderBasicSample.kt index fda0925a0..fb6f7c5c1 100644 --- a/sample/src/main/java/com/google/accompanist/sample/placeholder/PlaceholderBasicSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/placeholder/PlaceholderBasicSample.kt @@ -21,7 +21,6 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme diff --git a/sample/src/main/java/com/google/accompanist/sample/swiperefresh/SwipeRefreshVerticalPagerSample.kt b/sample/src/main/java/com/google/accompanist/sample/swiperefresh/SwipeRefreshVerticalPagerSample.kt index bf9718107..d3b51faee 100644 --- a/sample/src/main/java/com/google/accompanist/sample/swiperefresh/SwipeRefreshVerticalPagerSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/swiperefresh/SwipeRefreshVerticalPagerSample.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.accompanist.sample.swiperefresh import android.os.Bundle diff --git a/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/DialogSystemBarsColorSample.kt b/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/DialogSystemBarsColorSample.kt index efab14917..9601fadac 100644 --- a/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/DialogSystemBarsColorSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/DialogSystemBarsColorSample.kt @@ -168,7 +168,7 @@ private fun Sample(parentSystemUiController: SystemUiController) { width = maxWidth.roundToPx(), height = maxHeight.roundToPx() ) - }, + } ), contentDescription = null, contentScale = ContentScale.Crop, diff --git a/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/SystemBarsColorSample.kt b/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/SystemBarsColorSample.kt index 4ddf26c5d..0b4b5226b 100644 --- a/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/SystemBarsColorSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/SystemBarsColorSample.kt @@ -132,7 +132,7 @@ private fun Sample() { width = maxWidth.roundToPx(), height = maxHeight.roundToPx() ) - }, + } ), contentDescription = null, contentScale = ContentScale.Crop, diff --git a/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/SystemBarsVisibilitySample.kt b/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/SystemBarsVisibilitySample.kt index fc50083ed..ee0370d69 100644 --- a/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/SystemBarsVisibilitySample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/systemuicontroller/SystemBarsVisibilitySample.kt @@ -105,20 +105,11 @@ private fun Sample() { DropdownMenuItem( onClick = { systemUiController.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH + WindowInsetsControllerCompat.BEHAVIOR_DEFAULT isShowingDropdownMenu = false } ) { - Text("BEHAVIOR_SHOW_BARS_BY_TOUCH") - } - DropdownMenuItem( - onClick = { - systemUiController.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE - isShowingDropdownMenu = false - } - ) { - Text("BEHAVIOR_SHOW_BARS_BY_SWIPE") + Text("BEHAVIOR_DEFAULT") } DropdownMenuItem( onClick = { diff --git a/sample/src/main/java/com/google/accompanist/sample/webview/BasicWebViewSample.kt b/sample/src/main/java/com/google/accompanist/sample/webview/BasicWebViewSample.kt index 878dbb8b9..1be89d49d 100644 --- a/sample/src/main/java/com/google/accompanist/sample/webview/BasicWebViewSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/webview/BasicWebViewSample.kt @@ -59,16 +59,16 @@ import com.google.accompanist.web.rememberWebViewNavigator import com.google.accompanist.web.rememberWebViewState class BasicWebViewSample : ComponentActivity() { + val initialUrl = "https://google.com" @SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { AccompanistSampleTheme { - var url by remember { mutableStateOf("https://google.com") } - val state = rememberWebViewState(url = url) + val state = rememberWebViewState(url = initialUrl) val navigator = rememberWebViewNavigator() - var textFieldValue by remember(state.content.getCurrentUrl()) { - mutableStateOf(state.content.getCurrentUrl() ?: "") + var textFieldValue by remember(state.lastLoadedUrl) { + mutableStateOf(state.lastLoadedUrl) } Column { @@ -100,7 +100,7 @@ class BasicWebViewSample : ComponentActivity() { } OutlinedTextField( - value = textFieldValue, + value = textFieldValue ?: "", onValueChange = { textFieldValue = it }, modifier = Modifier.fillMaxWidth() ) @@ -108,7 +108,9 @@ class BasicWebViewSample : ComponentActivity() { Button( onClick = { - url = textFieldValue + textFieldValue?.let { + navigator.loadUrl(it) + } }, modifier = Modifier.align(Alignment.CenterVertically) ) { @@ -128,7 +130,7 @@ class BasicWebViewSample : ComponentActivity() { val webClient = remember { object : AccompanistWebViewClient() { override fun onPageStarted( - view: WebView?, + view: WebView, url: String?, favicon: Bitmap? ) { @@ -140,7 +142,8 @@ class BasicWebViewSample : ComponentActivity() { WebView( state = state, - modifier = Modifier.weight(1f), + modifier = Modifier + .weight(1f), navigator = navigator, onCreated = { webView -> webView.settings.javaScriptEnabled = true diff --git a/sample/src/main/java/com/google/accompanist/sample/webview/WebViewSaveStateSample.kt b/sample/src/main/java/com/google/accompanist/sample/webview/WebViewSaveStateSample.kt new file mode 100644 index 000000000..dcfba3b75 --- /dev/null +++ b/sample/src/main/java/com/google/accompanist/sample/webview/WebViewSaveStateSample.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2023 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.accompanist.sample.webview + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.google.accompanist.web.WebView +import com.google.accompanist.web.rememberSaveableWebViewState +import com.google.accompanist.web.rememberWebViewNavigator +import com.google.accompanist.web.rememberWebViewState + +class WebViewSaveStateSample : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MaterialTheme { + Surface { + val navController = rememberNavController() + Column(Modifier.fillMaxSize()) { + Row { + Button(onClick = { navController.popBackStack() }) { + Text("Home") + } + Button(onClick = { navController.navigate("detail") }) { + Text("Detail") + } + } + + Spacer(modifier = Modifier.size(16.dp)) + + NavHost(navController = navController, startDestination = "home") { + composable("home") { + Home() + } + composable("detail") { + Detail() + } + } + } + } + } + } + } +} + +@Composable +private fun Home() { + val webViewState = rememberSaveableWebViewState() + val navigator = rememberWebViewNavigator() + + LaunchedEffect(navigator) { + val bundle = webViewState.viewState + if (bundle == null) { + // This is the first time load, so load the home page. + navigator.loadUrl("https://bbc.com") + } + } + + WebView( + state = webViewState, + navigator = navigator, + modifier = Modifier.fillMaxSize() + ) +} + +@Composable +private fun Detail() { + val webViewState = rememberWebViewState(url = "https://google.com") + + WebView( + state = webViewState, + modifier = Modifier.fillMaxSize() + ) +} diff --git a/sample/src/main/java/com/google/accompanist/sample/webview/WrappedContentWebViewSample.kt b/sample/src/main/java/com/google/accompanist/sample/webview/WrappedContentWebViewSample.kt index e18db0bd8..25c945ad8 100644 --- a/sample/src/main/java/com/google/accompanist/sample/webview/WrappedContentWebViewSample.kt +++ b/sample/src/main/java/com/google/accompanist/sample/webview/WrappedContentWebViewSample.kt @@ -17,13 +17,13 @@ package com.google.accompanist.sample.webview import android.os.Bundle +import android.webkit.WebView import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material.Button import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme @@ -54,7 +54,7 @@ class WrappedContentWebViewSample : ComponentActivity() { ModalBottomSheetLayout( sheetState = sheetState, sheetContent = { - WebContent() + WrappingWebContent("Hello") } ) { val scope = rememberCoroutineScope() @@ -73,11 +73,13 @@ class WrappedContentWebViewSample : ComponentActivity() { } /*** - * A sample WebView that is wrapping it's content inside of a bottom sheet. + * A sample WebView that is wrapping it's content height. * The sheet should be the size of the rendered content and not unbounded. */ @Composable -fun WebContent() { +fun WrappingWebContent( + body: String +) { val webViewState = rememberWebViewStateWithHTMLData( data = "\n" + "\n" + - "

Hello

" + "

$body

" ) WebView( state = webViewState, modifier = Modifier.fillMaxWidth() - .wrapContentHeight() - .heightIn(min = 1.dp) // A bottom sheet can't support content with 0 height. + .heightIn(min = 1.dp), // A bottom sheet can't support content with 0 height. + captureBackPresses = false, ) } diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index 5a504ee97..80bb8ece8 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -64,6 +64,7 @@ WebView: Basic WebView: Wrapped Content + WebView: Save State on Navigation Adaptive: TwoPane Basic Adaptive: TwoPane Horizontal diff --git a/swiperefresh/api/current.api b/swiperefresh/api/current.api index 14cfc484e..e604ee6b3 100644 --- a/swiperefresh/api/current.api +++ b/swiperefresh/api/current.api @@ -18,7 +18,7 @@ package com.google.accompanist.swiperefresh { method @Deprecated public float getIndicatorOffset(); method @Deprecated public boolean isRefreshing(); method @Deprecated public boolean isSwipeInProgress(); - method @Deprecated public void setRefreshing(boolean isRefreshing); + method @Deprecated public void setRefreshing(boolean); property public final float indicatorOffset; property public final boolean isRefreshing; property public final boolean isSwipeInProgress; diff --git a/swiperefresh/build.gradle b/swiperefresh/build.gradle index 1e8c7fd80..1f01ce098 100644 --- a/swiperefresh/build.gradle +++ b/swiperefresh/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -83,6 +84,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation libs.compose.material.material implementation libs.compose.ui.util diff --git a/systemuicontroller/api/current.api b/systemuicontroller/api/current.api index 0c1425005..b0000e3b6 100644 --- a/systemuicontroller/api/current.api +++ b/systemuicontroller/api/current.api @@ -11,16 +11,16 @@ package com.google.accompanist.systemuicontroller { method public boolean isStatusBarVisible(); method public default boolean isSystemBarsVisible(); method public void setNavigationBarColor(long color, optional boolean darkIcons, optional boolean navigationBarContrastEnforced, optional kotlin.jvm.functions.Function1 transformColorForLightContent); - method public void setNavigationBarContrastEnforced(boolean isNavigationBarContrastEnforced); - method public void setNavigationBarDarkContentEnabled(boolean navigationBarDarkContentEnabled); - method public void setNavigationBarVisible(boolean isNavigationBarVisible); + method public void setNavigationBarContrastEnforced(boolean); + method public void setNavigationBarDarkContentEnabled(boolean); + method public void setNavigationBarVisible(boolean); method public void setStatusBarColor(long color, optional boolean darkIcons, optional kotlin.jvm.functions.Function1 transformColorForLightContent); - method public void setStatusBarDarkContentEnabled(boolean statusBarDarkContentEnabled); - method public void setStatusBarVisible(boolean isStatusBarVisible); - method public void setSystemBarsBehavior(int systemBarsBehavior); + method public void setStatusBarDarkContentEnabled(boolean); + method public void setStatusBarVisible(boolean); + method public void setSystemBarsBehavior(int); method public default void setSystemBarsColor(long color, optional boolean darkIcons, optional boolean isNavigationBarContrastEnforced, optional kotlin.jvm.functions.Function1 transformColorForLightContent); - method public default void setSystemBarsDarkContentEnabled(boolean value); - method public default void setSystemBarsVisible(boolean value); + method public default void setSystemBarsDarkContentEnabled(boolean); + method public default void setSystemBarsVisible(boolean); property public abstract boolean isNavigationBarContrastEnforced; property public abstract boolean isNavigationBarVisible; property public abstract boolean isStatusBarVisible; diff --git a/systemuicontroller/build.gradle b/systemuicontroller/build.gradle index 76000df8f..2b921ec35 100644 --- a/systemuicontroller/build.gradle +++ b/systemuicontroller/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -87,6 +88,12 @@ android { namespace 'com.google.accompanist.systemuicontroller' } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation libs.androidx.core implementation libs.compose.ui.ui diff --git a/systemuicontroller/src/main/java/com/google/accompanist/systemuicontroller/SystemUiController.kt b/systemuicontroller/src/main/java/com/google/accompanist/systemuicontroller/SystemUiController.kt index 61307834d..c1363bb14 100644 --- a/systemuicontroller/src/main/java/com/google/accompanist/systemuicontroller/SystemUiController.kt +++ b/systemuicontroller/src/main/java/com/google/accompanist/systemuicontroller/SystemUiController.kt @@ -48,8 +48,8 @@ interface SystemUiController { /** * Control for the behavior of the system bars. This value should be one of the * [WindowInsetsControllerCompat] behavior constants: - * [WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH], - * [WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE] and + * [WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH] (Deprecated), + * [WindowInsetsControllerCompat.BEHAVIOR_DEFAULT] and * [WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE]. */ var systemBarsBehavior: Int diff --git a/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/ActivityRememberSystemUiControllerTest.kt b/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/ActivityRememberSystemUiControllerTest.kt index a25465c40..d50dcbc2a 100644 --- a/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/ActivityRememberSystemUiControllerTest.kt +++ b/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/ActivityRememberSystemUiControllerTest.kt @@ -246,6 +246,7 @@ class ActivityRememberSystemUiControllerTest { } } + @Suppress("DEPRECATION") @Test @SdkSuppress(minSdkVersion = 30) // TODO: https://issuetracker.google.com/issues/189366125 fun systemBarsBehavior_showBarsByTouch() { @@ -275,11 +276,11 @@ class ActivityRememberSystemUiControllerTest { rule.activityRule.scenario.onActivity { systemUiController.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE + WindowInsetsControllerCompat.BEHAVIOR_DEFAULT } assertThat(WindowCompat.getInsetsController(window, contentView).systemBarsBehavior) - .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE) + .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_DEFAULT) } @Test diff --git a/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/ActivitySystemUiControllerTest.kt b/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/ActivitySystemUiControllerTest.kt index ca03015f7..90d048339 100644 --- a/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/ActivitySystemUiControllerTest.kt +++ b/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/ActivitySystemUiControllerTest.kt @@ -226,6 +226,7 @@ class ActivitySystemUiControllerTest { } } + @Suppress("DEPRECATION") @Test @SdkSuppress(minSdkVersion = 30) // TODO: https://issuetracker.google.com/issues/189366125 fun systemBarsBehavior_showBarsByTouch() { @@ -249,11 +250,11 @@ class ActivitySystemUiControllerTest { } rule.scenario.onActivity { - controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE + controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT } assertThat(WindowCompat.getInsetsController(window, contentView).systemBarsBehavior) - .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE) + .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_DEFAULT) } @Test diff --git a/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/DialogRememberSystemUiControllerTest.kt b/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/DialogRememberSystemUiControllerTest.kt index e3e8c30b7..10cb2d8e0 100644 --- a/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/DialogRememberSystemUiControllerTest.kt +++ b/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/DialogRememberSystemUiControllerTest.kt @@ -287,6 +287,7 @@ class DialogRememberSystemUiControllerTest { } } + @Suppress("DEPRECATION") @Test @SdkSuppress(minSdkVersion = 30) // TODO: https://issuetracker.google.com/issues/189366125 fun systemBarsBehavior_showBarsByTouch() { @@ -326,11 +327,11 @@ class DialogRememberSystemUiControllerTest { rule.activityRule.scenario.onActivity { systemUiController.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE + WindowInsetsControllerCompat.BEHAVIOR_DEFAULT } assertThat(WindowCompat.getInsetsController(window, contentView).systemBarsBehavior) - .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE) + .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_DEFAULT) } @Test diff --git a/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/DialogSystemUiControllerTest.kt b/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/DialogSystemUiControllerTest.kt index 8678f9c22..c7fe00232 100644 --- a/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/DialogSystemUiControllerTest.kt +++ b/systemuicontroller/src/sharedTest/kotlin/com/google/accompanist/systemuicontroller/DialogSystemUiControllerTest.kt @@ -245,6 +245,7 @@ class DialogSystemUiControllerTest { } } + @Suppress("DEPRECATION") @Test @SdkSuppress(minSdkVersion = 30) // TODO: https://issuetracker.google.com/issues/189366125 fun systemBarsBehavior_showBarsByTouch() { @@ -270,11 +271,11 @@ class DialogSystemUiControllerTest { rule.scenario.onActivity { controller.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE + WindowInsetsControllerCompat.BEHAVIOR_DEFAULT } assertThat(WindowCompat.getInsetsController(window, contentView).systemBarsBehavior) - .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE) + .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_DEFAULT) } @Test diff --git a/testharness/build.gradle b/testharness/build.gradle index 1d5cd20b4..b889f9d0d 100644 --- a/testharness/build.gradle +++ b/testharness/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -107,6 +108,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation libs.compose.foundation.foundation implementation libs.androidx.core diff --git a/themeadapter-appcompat/build.gradle b/themeadapter-appcompat/build.gradle index abb0eba17..95b7c7db4 100644 --- a/themeadapter-appcompat/build.gradle +++ b/themeadapter-appcompat/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -84,6 +85,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation(project(':themeadapter-core')) diff --git a/themeadapter-core/build.gradle b/themeadapter-core/build.gradle index 7b3dff0be..317717a1d 100644 --- a/themeadapter-core/build.gradle +++ b/themeadapter-core/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -83,6 +84,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { api libs.androidx.core api libs.compose.foundation.foundation diff --git a/themeadapter-material/build.gradle b/themeadapter-material/build.gradle index a22e675d8..1e52c1c32 100644 --- a/themeadapter-material/build.gradle +++ b/themeadapter-material/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -83,6 +84,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation(project(':themeadapter-core')) diff --git a/themeadapter-material3/build.gradle b/themeadapter-material3/build.gradle index 25f4cb1b9..963ce4c74 100644 --- a/themeadapter-material3/build.gradle +++ b/themeadapter-material3/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -83,6 +84,12 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation(project(':themeadapter-core')) diff --git a/themeadapter-material3/src/main/java/com/google/accompanist/themeadapter/material3/Mdc3Theme.kt b/themeadapter-material3/src/main/java/com/google/accompanist/themeadapter/material3/Mdc3Theme.kt index e7fbfe857..054d81869 100644 --- a/themeadapter-material3/src/main/java/com/google/accompanist/themeadapter/material3/Mdc3Theme.kt +++ b/themeadapter-material3/src/main/java/com/google/accompanist/themeadapter/material3/Mdc3Theme.kt @@ -191,6 +191,7 @@ fun createMdc3Theme( val surfaceInverse = ta.parseColor(R.styleable.ThemeAdapterMaterial3Theme_colorSurfaceInverse) val onSurfaceInverse = ta.parseColor(R.styleable.ThemeAdapterMaterial3Theme_colorOnSurfaceInverse) val outline = ta.parseColor(R.styleable.ThemeAdapterMaterial3Theme_colorOutline) + val outlineVariant = ta.parseColor(R.styleable.ThemeAdapterMaterial3Theme_colorOutlineVariant) val error = ta.parseColor(R.styleable.ThemeAdapterMaterial3Theme_colorError) val onError = ta.parseColor(R.styleable.ThemeAdapterMaterial3Theme_colorOnError) val errorContainer = ta.parseColor(R.styleable.ThemeAdapterMaterial3Theme_colorErrorContainer) @@ -224,7 +225,7 @@ fun createMdc3Theme( inverseSurface = surfaceInverse, inverseOnSurface = onSurfaceInverse, outline = outline, - // TODO: MDC-Android doesn't include outlineVariant yet, add when available + outlineVariant = outlineVariant, error = error, onError = onError, errorContainer = errorContainer, @@ -256,7 +257,7 @@ fun createMdc3Theme( inverseSurface = surfaceInverse, inverseOnSurface = onSurfaceInverse, outline = outline, - // TODO: MDC-Android doesn't include outlineVariant yet, add when available + outlineVariant = outlineVariant, error = error, onError = onError, errorContainer = errorContainer, diff --git a/themeadapter-material3/src/main/res/values/theme_attrs.xml b/themeadapter-material3/src/main/res/values/theme_attrs.xml index 0041dfead..b026cebed 100644 --- a/themeadapter-material3/src/main/res/values/theme_attrs.xml +++ b/themeadapter-material3/src/main/res/values/theme_attrs.xml @@ -44,6 +44,7 @@ + diff --git a/web/api/current.api b/web/api/current.api index bd8c9f116..f91b42b04 100644 --- a/web/api/current.api +++ b/web/api/current.api @@ -38,18 +38,31 @@ package com.google.accompanist.web { } public abstract sealed class WebContent { - method public final String? getCurrentUrl(); + method @Deprecated public final String? getCurrentUrl(); } public static final class WebContent.Data extends com.google.accompanist.web.WebContent { - ctor public WebContent.Data(String data, optional String? baseUrl); + ctor public WebContent.Data(String data, optional String? baseUrl, optional String encoding, optional String? mimeType, optional String? historyUrl); method public String component1(); method public String? component2(); - method public com.google.accompanist.web.WebContent.Data copy(String data, String? baseUrl); + method public String component3(); + method public String? component4(); + method public String? component5(); + method public com.google.accompanist.web.WebContent.Data copy(String data, String? baseUrl, String encoding, String? mimeType, String? historyUrl); method public String? getBaseUrl(); method public String getData(); + method public String getEncoding(); + method public String? getHistoryUrl(); + method public String? getMimeType(); property public final String? baseUrl; property public final String data; + property public final String encoding; + property public final String? historyUrl; + property public final String? mimeType; + } + + public static final class WebContent.NavigatorOnly extends com.google.accompanist.web.WebContent { + field public static final com.google.accompanist.web.WebContent.NavigatorOnly INSTANCE; } public static final class WebContent.Url extends com.google.accompanist.web.WebContent { @@ -76,15 +89,21 @@ package com.google.accompanist.web { public final class WebViewKt { method @androidx.compose.runtime.Composable public static void WebView(com.google.accompanist.web.WebViewState state, optional androidx.compose.ui.Modifier modifier, optional boolean captureBackPresses, optional com.google.accompanist.web.WebViewNavigator navigator, optional kotlin.jvm.functions.Function1 onCreated, optional kotlin.jvm.functions.Function1 onDispose, optional com.google.accompanist.web.AccompanistWebViewClient client, optional com.google.accompanist.web.AccompanistWebChromeClient chromeClient, optional kotlin.jvm.functions.Function1? factory); + method @androidx.compose.runtime.Composable public static void WebView(com.google.accompanist.web.WebViewState state, android.widget.FrameLayout.LayoutParams layoutParams, optional androidx.compose.ui.Modifier modifier, optional boolean captureBackPresses, optional com.google.accompanist.web.WebViewNavigator navigator, optional kotlin.jvm.functions.Function1 onCreated, optional kotlin.jvm.functions.Function1 onDispose, optional com.google.accompanist.web.AccompanistWebViewClient client, optional com.google.accompanist.web.AccompanistWebChromeClient chromeClient, optional kotlin.jvm.functions.Function1? factory); + method public static androidx.compose.runtime.saveable.Saver getWebStateSaver(); + method @androidx.compose.runtime.Composable public static com.google.accompanist.web.WebViewState rememberSaveableWebViewState(); method @androidx.compose.runtime.Composable public static com.google.accompanist.web.WebViewNavigator rememberWebViewNavigator(optional kotlinx.coroutines.CoroutineScope coroutineScope); method @androidx.compose.runtime.Composable public static com.google.accompanist.web.WebViewState rememberWebViewState(String url, optional java.util.Map additionalHttpHeaders); - method @androidx.compose.runtime.Composable public static com.google.accompanist.web.WebViewState rememberWebViewStateWithHTMLData(String data, optional String? baseUrl); + method @androidx.compose.runtime.Composable public static com.google.accompanist.web.WebViewState rememberWebViewStateWithHTMLData(String data, optional String? baseUrl, optional String encoding, optional String? mimeType, optional String? historyUrl); + property public static final androidx.compose.runtime.saveable.Saver WebStateSaver; } @androidx.compose.runtime.Stable public final class WebViewNavigator { ctor public WebViewNavigator(kotlinx.coroutines.CoroutineScope coroutineScope); method public boolean getCanGoBack(); method public boolean getCanGoForward(); + method public void loadHtml(String html, optional String? baseUrl, optional String? mimeType, optional String? encoding, optional String? historyUrl); + method public void loadUrl(String url, optional java.util.Map additionalHttpHeaders); method public void navigateBack(); method public void navigateForward(); method public void reload(); @@ -97,17 +116,21 @@ package com.google.accompanist.web { ctor public WebViewState(com.google.accompanist.web.WebContent webContent); method public com.google.accompanist.web.WebContent getContent(); method public androidx.compose.runtime.snapshots.SnapshotStateList getErrorsForCurrentRequest(); + method public String? getLastLoadedUrl(); method public com.google.accompanist.web.LoadingState getLoadingState(); method public android.graphics.Bitmap? getPageIcon(); method public String? getPageTitle(); + method public android.os.Bundle? getViewState(); method public boolean isLoading(); - method public void setContent(com.google.accompanist.web.WebContent content); + method public void setContent(com.google.accompanist.web.WebContent); property public final com.google.accompanist.web.WebContent content; property public final androidx.compose.runtime.snapshots.SnapshotStateList errorsForCurrentRequest; property public final boolean isLoading; + property public final String? lastLoadedUrl; property public final com.google.accompanist.web.LoadingState loadingState; property public final android.graphics.Bitmap? pageIcon; property public final String? pageTitle; + property public final android.os.Bundle? viewState; } } diff --git a/web/build.gradle b/web/build.gradle index 29cef626b..3699afaf1 100644 --- a/web/build.gradle +++ b/web/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'org.jetbrains.dokka' + id 'me.tylerbwong.gradle.metalava' } kotlin { @@ -84,11 +85,21 @@ android { } } +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + dependencies { implementation libs.compose.material.material implementation libs.compose.ui.util + implementation libs.androidx.lifecycle.runtime implementation libs.androidx.activity.compose + + // TODO: Remove when fixed https://github.com/gradle/gradle/issues/24037 implementation libs.kotlin.coroutines.android + implementation libs.androidx.lifecycle.common // ====================== // Test dependencies diff --git a/web/src/androidTest/assets/test_link.html b/web/src/androidTest/assets/test_link.html new file mode 100644 index 000000000..0d0106852 --- /dev/null +++ b/web/src/androidTest/assets/test_link.html @@ -0,0 +1,25 @@ + + + + + + + Test link + + + + \ No newline at end of file diff --git a/web/src/androidTest/kotlin/com/google/accompanist/web/WebTest.kt b/web/src/androidTest/kotlin/com/google/accompanist/web/WebTest.kt index b518bc099..5b7fbfb3f 100644 --- a/web/src/androidTest/kotlin/com/google/accompanist/web/WebTest.kt +++ b/web/src/androidTest/kotlin/com/google/accompanist/web/WebTest.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.assertHeightIsAtLeast import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick import androidx.compose.ui.unit.dp import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches import androidx.test.espresso.web.model.Atoms.getCurrentUrl @@ -70,8 +71,8 @@ import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) // Emulator image doesn't have a WebView until API 26 // Google API emulator image seems to be really flaky before 28 so currently we will set these tests -// to min 29 and max 30. 31/32 image is also really flaky -@SdkSuppress(minSdkVersion = 28, maxSdkVersion = 30) +// to min 29. +@SdkSuppress(minSdkVersion = 28) class WebTest { @get:Rule val rule = createComposeRule() @@ -148,7 +149,7 @@ class WebTest { // Wait for the webview to load and then perform the check rule.waitForIdle() onWebView().check(webMatches(getCurrentUrl(), containsString(LINK_URL))) - assertThat(state.content.getCurrentUrl()) + assertThat(state.lastLoadedUrl) .isEqualTo(LINK_URL) } @@ -253,7 +254,7 @@ class WebTest { state, idleResource, client = object : AccompanistWebViewClient() { - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) pageStartCalled = true } @@ -283,7 +284,7 @@ class WebTest { state, idleResource, chromeClient = object : AccompanistWebChromeClient() { - override fun onReceivedTitle(view: WebView?, title: String?) { + override fun onReceivedTitle(view: WebView, title: String?) { super.onReceivedTitle(view, title) titleReceived = title } @@ -326,7 +327,7 @@ class WebTest { rule.waitForIdle() onWebView().check(webMatches(getCurrentUrl(), containsString("about:blank"))) - assertThat(state.content.getCurrentUrl()) + assertThat(state.lastLoadedUrl) .isEqualTo("about:blank") } @@ -349,7 +350,7 @@ class WebTest { // Wait for the webview to load and then perform the check rule.waitForIdle() onWebView().check(webMatches(getCurrentUrl(), containsString(LINK_URL))) - assertThat(state.content.getCurrentUrl()) + assertThat(state.lastLoadedUrl) .isEqualTo(LINK_URL) } @@ -447,13 +448,14 @@ class WebTest { mockServer.shutdown() } + @FlakyTest @Test fun testNavigatorBack() { lateinit var state: WebViewState lateinit var navigator: WebViewNavigator rule.setContent { - state = rememberWebViewStateWithHTMLData(data = TEST_DATA) + state = rememberWebViewState(url = TEST_LINK_FILE_URL) navigator = rememberWebViewNavigator() WebTestContent( @@ -464,20 +466,16 @@ class WebTest { } rule.waitForIdle() - - onWebView() - .withElement(findElement(Locator.ID, "link")) - .perform(webClick()) + clickOnWebViewLink() rule.waitUntil { navigator.canGoBack } - assertThat(state.content.getCurrentUrl()).isEqualTo(LINK_URL) + assertThat(state.lastLoadedUrl).isEqualTo(LINK_URL) navigator.navigateBack() // Check that we're back on the original page with the link onWebView() - .withElement(findElement(Locator.ID, "link")) - .check(webMatches(getText(), equalTo(LINK_TEXT))) + .withElement(findElement(Locator.LINK_TEXT, TEST_LINK_FILE_TEXT)) } @FlakyTest @@ -487,7 +485,7 @@ class WebTest { lateinit var navigator: WebViewNavigator rule.setContent { - state = rememberWebViewStateWithHTMLData(data = TEST_DATA) + state = rememberWebViewState(url = TEST_LINK_FILE_URL) navigator = rememberWebViewNavigator() WebTestContent( @@ -498,33 +496,32 @@ class WebTest { } rule.waitForIdle() - - onWebView() - .withElement(findElement(Locator.ID, "link")) - .perform(webClick()) + clickOnWebViewLink() rule.waitUntil { navigator.canGoBack } + assertThat(state.lastLoadedUrl).isEqualTo(LINK_URL) + navigator.navigateBack() // Check that we're back on the original page with the link onWebView() - .withElement(findElement(Locator.ID, "link")) - .check(webMatches(getText(), equalTo(LINK_TEXT))) + .withElement(findElement(Locator.LINK_TEXT, TEST_LINK_FILE_TEXT)) navigator.navigateForward() rule.waitUntil { navigator.canGoBack } rule.waitForIdle() - assertThat(state.content.getCurrentUrl()).isEqualTo(LINK_URL) + assertThat(state.lastLoadedUrl).isEqualTo(LINK_URL) } + @FlakyTest @Test fun testNavigatorCanGoBack() { lateinit var state: WebViewState lateinit var navigator: WebViewNavigator rule.setContent { - state = rememberWebViewStateWithHTMLData(data = TEST_DATA) + state = rememberWebViewState(url = TEST_LINK_FILE_URL) navigator = rememberWebViewNavigator() WebTestContent( @@ -535,23 +532,20 @@ class WebTest { } rule.waitForIdle() - assertThat(navigator.canGoBack).isFalse() - - onWebView() - .withElement(findElement(Locator.ID, "link")) - .perform(webClick()) + clickOnWebViewLink() rule.waitUntil { navigator.canGoBack } assertThat(navigator.canGoBack).isTrue() } + @FlakyTest @Test fun testNavigatorCanGoForward() { lateinit var state: WebViewState lateinit var navigator: WebViewNavigator rule.setContent { - state = rememberWebViewStateWithHTMLData(data = TEST_DATA) + state = rememberWebViewState(url = TEST_LINK_FILE_URL) navigator = rememberWebViewNavigator() WebTestContent( @@ -562,10 +556,7 @@ class WebTest { } rule.waitForIdle() - - onWebView() - .withElement(findElement(Locator.ID, "link")) - .perform(webClick()) + clickOnWebViewLink() rule.waitUntil { navigator.canGoBack } navigator.navigateBack() @@ -635,7 +626,7 @@ class WebTest { lateinit var state: WebViewState var pageStartedCalled = 0 val client = object : AccompanistWebViewClient() { - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) pageStartedCalled++ } @@ -716,11 +707,19 @@ class WebTest { // If the WebView is wrapping it's content successfully, the box will have some height. rule.onNodeWithTag("box").assertHeightIsAtLeast(1.dp) } + + private fun clickOnWebViewLink() { + // HACK: EspressoWeb webClick doesn't track back navigation for some reason + // so manually click the view + rule.onNodeWithTag(WebViewTag).performClick() + } } private const val LINK_ID = "link" private const val LINK_TEXT = "Click me" private const val LINK_URL = "file:///android_asset/test.html" +private const val TEST_LINK_FILE_URL = "file:///android_asset/test_link.html" +private const val TEST_LINK_FILE_TEXT = "Test link" private const val TITLE_TEXT = "A Test Title" private const val TEST_DATA = "$LINK_TEXT" diff --git a/web/src/main/java/com/google/accompanist/web/WebView.kt b/web/src/main/java/com/google/accompanist/web/WebView.kt index 8dac30b1b..724c8ea5b 100644 --- a/web/src/main/java/com/google/accompanist/web/WebView.kt +++ b/web/src/main/java/com/google/accompanist/web/WebView.kt @@ -18,16 +18,17 @@ package com.google.accompanist.web import android.content.Context import android.graphics.Bitmap +import android.os.Bundle import android.view.ViewGroup.LayoutParams import android.webkit.WebChromeClient import android.webkit.WebResourceError import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient +import android.widget.FrameLayout import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable @@ -36,12 +37,16 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.saveable.mapSaver +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.children +import com.google.accompanist.web.LoadingState.Finished +import com.google.accompanist.web.LoadingState.Loading import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -54,7 +59,11 @@ import kotlinx.coroutines.withContext * If you require more customisation you are most likely better rolling your own and using this * wrapper as an example. * + * The WebView attempts to set the layoutParams based on the Compose modifier passed in. If it + * is incorrectly sizing, use the layoutParams composable function instead. + * * @param state The webview state holder where the Uri to load is defined. + * @param modifier A compose modifier * @param captureBackPresses Set to true to have this Composable capture back presses and navigate * the WebView back. * @param navigator An optional navigator object that can be used to control the WebView's @@ -62,6 +71,8 @@ import kotlinx.coroutines.withContext * @param onCreated Called when the WebView is first created, this can be used to set additional * settings on the WebView. WebChromeClient and WebViewClient should not be set here as they will be * subsequently overwritten after this lambda is called. + * @param onDispose Called when the WebView is destroyed. Provides a bundle which can be saved + * if you need to save and restore state in this WebView. * @param client Provides access to WebViewClient via subclassing * @param chromeClient Provides access to WebChromeClient via subclassing * @param factory An optional WebView factory for using a custom subclass of WebView @@ -77,23 +88,116 @@ fun WebView( onDispose: (WebView) -> Unit = {}, client: AccompanistWebViewClient = remember { AccompanistWebViewClient() }, chromeClient: AccompanistWebChromeClient = remember { AccompanistWebChromeClient() }, - factory: ((Context) -> WebView)? = null + factory: ((Context) -> WebView)? = null, +) { + BoxWithConstraints(modifier) { + // WebView changes it's layout strategy based on + // it's layoutParams. We convert from Compose Modifier to + // layout params here. + val width = + if (constraints.hasFixedWidth) + LayoutParams.MATCH_PARENT + else + LayoutParams.WRAP_CONTENT + val height = + if (constraints.hasFixedHeight) + LayoutParams.MATCH_PARENT + else + LayoutParams.WRAP_CONTENT + + val layoutParams = FrameLayout.LayoutParams( + width, + height + ) + + WebView( + state, + layoutParams, + Modifier, + captureBackPresses, + navigator, + onCreated, + onDispose, + client, + chromeClient, + factory + ) + } +} + +/** + * A wrapper around the Android View WebView to provide a basic WebView composable. + * + * If you require more customisation you are most likely better rolling your own and using this + * wrapper as an example. + * + * The WebView attempts to set the layoutParams based on the Compose modifier passed in. If it + * is incorrectly sizing, use the layoutParams composable function instead. + * + * @param state The webview state holder where the Uri to load is defined. + * @param layoutParams A FrameLayout.LayoutParams object to custom size the underlying WebView. + * @param modifier A compose modifier + * @param captureBackPresses Set to true to have this Composable capture back presses and navigate + * the WebView back. + * @param navigator An optional navigator object that can be used to control the WebView's + * navigation from outside the composable. + * @param onCreated Called when the WebView is first created, this can be used to set additional + * settings on the WebView. WebChromeClient and WebViewClient should not be set here as they will be + * subsequently overwritten after this lambda is called. + * @param onDispose Called when the WebView is destroyed. Provides a bundle which can be saved + * if you need to save and restore state in this WebView. + * @param client Provides access to WebViewClient via subclassing + * @param chromeClient Provides access to WebChromeClient via subclassing + * @param factory An optional WebView factory for using a custom subclass of WebView + */ +@Composable +fun WebView( + state: WebViewState, + layoutParams: FrameLayout.LayoutParams, + modifier: Modifier = Modifier, + captureBackPresses: Boolean = true, + navigator: WebViewNavigator = rememberWebViewNavigator(), + onCreated: (WebView) -> Unit = {}, + onDispose: (WebView) -> Unit = {}, + client: AccompanistWebViewClient = remember { AccompanistWebViewClient() }, + chromeClient: AccompanistWebChromeClient = remember { AccompanistWebChromeClient() }, + factory: ((Context) -> WebView)? = null, ) { - var webView by remember { mutableStateOf(null) } + val webView = state.webView BackHandler(captureBackPresses && navigator.canGoBack) { webView?.goBack() } - LaunchedEffect(webView, navigator) { - with(navigator) { webView?.handleNavigationEvents() } - } + webView?.let { wv -> + LaunchedEffect(wv, navigator) { + with(navigator) { + wv.handleNavigationEvents() + } + } - val currentOnDispose by rememberUpdatedState(onDispose) + LaunchedEffect(wv, state) { + snapshotFlow { state.content }.collect { content -> + when (content) { + is WebContent.Url -> { + wv.loadUrl(content.url, content.additionalHttpHeaders) + } + + is WebContent.Data -> { + wv.loadDataWithBaseURL( + content.baseUrl, + content.data, + content.mimeType, + content.encoding, + content.historyUrl + ) + } - webView?.let { it -> - DisposableEffect(it) { - onDispose { currentOnDispose(it) } + is WebContent.NavigatorOnly -> { + // NO-OP + } + } + } } } @@ -104,59 +208,36 @@ fun WebView( client.navigator = navigator chromeClient.state = state - val runningInPreview = LocalInspectionMode.current - - BoxWithConstraints(modifier) { - AndroidView( - factory = { context -> - (factory?.invoke(context) ?: WebView(context)).apply { - onCreated(this) - - // WebView changes it's layout strategy based on - // it's layoutParams. We convert from Compose Modifier to - // layout params here. - val width = - if (constraints.hasFixedWidth) - LayoutParams.MATCH_PARENT - else - LayoutParams.WRAP_CONTENT - val height = - if (constraints.hasFixedHeight) - LayoutParams.MATCH_PARENT - else - LayoutParams.WRAP_CONTENT - - layoutParams = LayoutParams( - width, - height - ) - - webChromeClient = chromeClient - webViewClient = client - }.also { webView = it } - } - ) { view -> - // AndroidViews are not supported by preview, bail early - if (runningInPreview) return@AndroidView - - when (val content = state.content) { - is WebContent.Url -> { - val url = content.url + AndroidView( + factory = { context -> + val childView = (factory?.invoke(context) ?: WebView(context)).apply { + onCreated(this) - if (url.isNotEmpty() && url != view.url) { - view.loadUrl(url, content.additionalHttpHeaders.toMutableMap()) - } - } + this.layoutParams = layoutParams - is WebContent.Data -> { - view.loadDataWithBaseURL(content.baseUrl, content.data, null, "utf-8", null) + state.viewState?.let { + this.restoreState(it) } - } - navigator.canGoBack = view.canGoBack() - navigator.canGoForward = view.canGoForward() + webChromeClient = chromeClient + webViewClient = client + }.also { state.webView = it } + + // Workaround a crash on certain devices that expect WebView to be + // wrapped in a ViewGroup. + // b/243567497 + val parentLayout = FrameLayout(context) + parentLayout.layoutParams = layoutParams + parentLayout.addView(childView) + + parentLayout + }, + modifier = modifier, + onRelease = { parentFrame -> + val wv = parentFrame.children.first() as WebView + onDispose(wv) } - } + ) } /** @@ -173,43 +254,30 @@ open class AccompanistWebViewClient : WebViewClient() { open lateinit var navigator: WebViewNavigator internal set - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) state.loadingState = LoadingState.Loading(0.0f) state.errorsForCurrentRequest.clear() state.pageTitle = null state.pageIcon = null + + state.lastLoadedUrl = url } - override fun onPageFinished(view: WebView?, url: String?) { + override fun onPageFinished(view: WebView, url: String?) { super.onPageFinished(view, url) state.loadingState = LoadingState.Finished - navigator.canGoBack = view?.canGoBack() ?: false - navigator.canGoForward = view?.canGoForward() ?: false } - override fun doUpdateVisitedHistory( - view: WebView?, - url: String?, - isReload: Boolean - ) { + override fun doUpdateVisitedHistory(view: WebView, url: String?, isReload: Boolean) { super.doUpdateVisitedHistory(view, url, isReload) - // WebView will often update the current url itself. - // This happens in situations like redirects and navigating through - // history. We capture this change and update our state holder url. - // On older APIs (28 and lower), this method is called when loading - // html data. We don't want to update the state in this case as that will - // overwrite the html being loaded. - if (url != null && - !url.startsWith("data:text/html") && - state.content.getCurrentUrl() != url - ) { - state.content = state.content.withUrl(url) - } + + navigator.canGoBack = view.canGoBack() + navigator.canGoForward = view.canGoForward() } override fun onReceivedError( - view: WebView?, + view: WebView, request: WebResourceRequest?, error: WebResourceError? ) { @@ -219,24 +287,6 @@ open class AccompanistWebViewClient : WebViewClient() { state.errorsForCurrentRequest.add(WebViewError(request, error)) } } - - override fun shouldOverrideUrlLoading( - view: WebView?, - request: WebResourceRequest? - ): Boolean { - // If the url hasn't changed, this is probably an internal event like - // a javascript reload. We should let it happen. - if (view?.url == request?.url.toString()) { - return false - } - - // Override all url loads to make the single source of truth - // of the URL the state holder Url - request?.let { - state.content = state.content.withUrl(it.url.toString()) - } - return true - } } /** @@ -251,17 +301,17 @@ open class AccompanistWebChromeClient : WebChromeClient() { open lateinit var state: WebViewState internal set - override fun onReceivedTitle(view: WebView?, title: String?) { + override fun onReceivedTitle(view: WebView, title: String?) { super.onReceivedTitle(view, title) state.pageTitle = title } - override fun onReceivedIcon(view: WebView?, icon: Bitmap?) { + override fun onReceivedIcon(view: WebView, icon: Bitmap?) { super.onReceivedIcon(view, icon) state.pageIcon = icon } - override fun onProgressChanged(view: WebView?, newProgress: Int) { + override fun onProgressChanged(view: WebView, newProgress: Int) { super.onProgressChanged(view, newProgress) if (state.loadingState is LoadingState.Finished) return state.loadingState = LoadingState.Loading(newProgress / 100.0f) @@ -274,14 +324,24 @@ sealed class WebContent { val additionalHttpHeaders: Map = emptyMap(), ) : WebContent() - data class Data(val data: String, val baseUrl: String? = null) : WebContent() + data class Data( + val data: String, + val baseUrl: String? = null, + val encoding: String = "utf-8", + val mimeType: String? = null, + val historyUrl: String? = null + ) : WebContent() + @Deprecated("Use state.lastLoadedUrl instead") fun getCurrentUrl(): String? { return when (this) { is Url -> url is Data -> baseUrl + is NavigatorOnly -> throw IllegalStateException("Unsupported") } } + + object NavigatorOnly : WebContent() } internal fun WebContent.withUrl(url: String) = when (this) { @@ -317,6 +377,9 @@ sealed class LoadingState { */ @Stable class WebViewState(webContent: WebContent) { + var lastLoadedUrl by mutableStateOf(null) + internal set + /** * The content being loaded by the WebView */ @@ -353,6 +416,18 @@ class WebViewState(webContent: WebContent) { * For more fine grained control use the OnError callback of the WebView. */ val errorsForCurrentRequest: SnapshotStateList = mutableStateListOf() + + /** + * The saved view state from when the view was destroyed last. To restore state, + * use the navigator and only call loadUrl if the bundle is null. + * See WebViewSaveStateSample. + */ + var viewState: Bundle? = null + internal set + + // We need access to this in the state saver. An internal DisposableEffect or AndroidView + // onDestroy is called after the state saver and so can't be used. + internal var webView by mutableStateOf(null) } /** @@ -363,19 +438,47 @@ class WebViewState(webContent: WebContent) { */ @Stable class WebViewNavigator(private val coroutineScope: CoroutineScope) { + private sealed interface NavigationEvent { + object Back : NavigationEvent + object Forward : NavigationEvent + object Reload : NavigationEvent + object StopLoading : NavigationEvent + + data class LoadUrl( + val url: String, + val additionalHttpHeaders: Map = emptyMap() + ) : NavigationEvent + + data class LoadHtml( + val html: String, + val baseUrl: String? = null, + val mimeType: String? = null, + val encoding: String? = "utf-8", + val historyUrl: String? = null + ) : NavigationEvent + } - private enum class NavigationEvent { BACK, FORWARD, RELOAD, STOP_LOADING } - - private val navigationEvents: MutableSharedFlow = MutableSharedFlow() + private val navigationEvents: MutableSharedFlow = MutableSharedFlow(replay = 1) // Use Dispatchers.Main to ensure that the webview methods are called on UI thread internal suspend fun WebView.handleNavigationEvents(): Nothing = withContext(Dispatchers.Main) { navigationEvents.collect { event -> when (event) { - NavigationEvent.BACK -> goBack() - NavigationEvent.FORWARD -> goForward() - NavigationEvent.RELOAD -> reload() - NavigationEvent.STOP_LOADING -> stopLoading() + is NavigationEvent.Back -> goBack() + is NavigationEvent.Forward -> goForward() + is NavigationEvent.Reload -> reload() + is NavigationEvent.StopLoading -> stopLoading() + is NavigationEvent.LoadHtml -> loadDataWithBaseURL( + event.baseUrl, + event.html, + event.mimeType, + event.encoding, + event.historyUrl + ) + + is NavigationEvent.LoadUrl -> { + loadUrl(event.url, event.additionalHttpHeaders) + } } } } @@ -392,32 +495,63 @@ class WebViewNavigator(private val coroutineScope: CoroutineScope) { var canGoForward: Boolean by mutableStateOf(false) internal set + fun loadUrl(url: String, additionalHttpHeaders: Map = emptyMap()) { + coroutineScope.launch { + navigationEvents.emit( + NavigationEvent.LoadUrl( + url, + additionalHttpHeaders + ) + ) + } + } + + fun loadHtml( + html: String, + baseUrl: String? = null, + mimeType: String? = null, + encoding: String? = "utf-8", + historyUrl: String? = null + ) { + coroutineScope.launch { + navigationEvents.emit( + NavigationEvent.LoadHtml( + html, + baseUrl, + mimeType, + encoding, + historyUrl + ) + ) + } + } + /** * Navigates the webview back to the previous page. */ fun navigateBack() { - coroutineScope.launch { navigationEvents.emit(NavigationEvent.BACK) } + coroutineScope.launch { navigationEvents.emit(NavigationEvent.Back) } } /** * Navigates the webview forward after going back from a page. */ fun navigateForward() { - coroutineScope.launch { navigationEvents.emit(NavigationEvent.FORWARD) } + coroutineScope.launch { navigationEvents.emit(NavigationEvent.Forward) } } /** * Reloads the current page in the webview. */ fun reload() { - coroutineScope.launch { navigationEvents.emit(NavigationEvent.RELOAD) } + coroutineScope.launch { navigationEvents.emit(NavigationEvent.Reload) } } /** * Stops the current page load (if one is loading). */ fun stopLoading() { - coroutineScope.launch { navigationEvents.emit(NavigationEvent.STOP_LOADING) } + coroutineScope.launch { navigationEvents.emit(NavigationEvent.StopLoading) } } } @@ -459,13 +593,18 @@ fun rememberWebViewState( ): WebViewState = // Rather than using .apply {} here we will recreate the state, this prevents // a recomposition loop when the webview updates the url itself. - remember(url, additionalHttpHeaders) { + remember { WebViewState( WebContent.Url( url = url, additionalHttpHeaders = additionalHttpHeaders ) ) + }.apply { + this.content = WebContent.Url( + url = url, + additionalHttpHeaders = additionalHttpHeaders + ) } /** @@ -474,7 +613,56 @@ fun rememberWebViewState( * @param data The uri to load in the WebView */ @Composable -fun rememberWebViewStateWithHTMLData(data: String, baseUrl: String? = null): WebViewState = - remember(data, baseUrl) { - WebViewState(WebContent.Data(data, baseUrl)) +fun rememberWebViewStateWithHTMLData( + data: String, + baseUrl: String? = null, + encoding: String = "utf-8", + mimeType: String? = null, + historyUrl: String? = null +): WebViewState = + remember { + WebViewState(WebContent.Data(data, baseUrl, encoding, mimeType, historyUrl)) + }.apply { + this.content = WebContent.Data( + data, baseUrl, encoding, mimeType, historyUrl + ) } + +/** + * Creates a WebView state that is remembered across Compositions and saved + * across activity recreation. + * When using saved state, you cannot change the URL via recomposition. The only way to load + * a URL is via a WebViewNavigator. + * + * @param data The uri to load in the WebView + * @sample com.google.accompanist.sample.webview.WebViewSaveStateSample + */ +@Composable +fun rememberSaveableWebViewState(): WebViewState = + rememberSaveable(saver = WebStateSaver) { + WebViewState(WebContent.NavigatorOnly) + } + +val WebStateSaver = run { + val pageTitleKey = "pagetitle" + val lastLoadedUrlKey = "lastloaded" + val stateBundle = "bundle" + + mapSaver( + save = { + val viewState = Bundle().apply { it.webView?.saveState(this) } + mapOf( + pageTitleKey to it.pageTitle, + lastLoadedUrlKey to it.lastLoadedUrl, + stateBundle to viewState + ) + }, + restore = { + WebViewState(WebContent.NavigatorOnly).apply { + this.pageTitle = it[pageTitleKey] as String? + this.lastLoadedUrl = it[lastLoadedUrlKey] as String? + this.viewState = it[stateBundle] as Bundle? + } + } + ) +}