Full Code of google/accompanist for AI

main 12ec3408fc10 cached
157 files
515.2 KB
114.8k tokens
1 requests
Download .txt
Showing preview only (561K chars total). Download the full file or copy to clipboard to get everything.
Repository: google/accompanist
Branch: main
Commit: 12ec3408fc10
Files: 157
Total size: 515.2 KB

Directory structure:
gitextract_cgrc05nu/

├── .allstar/
│   └── binary_artifacts.yaml
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── adaptive-bug-report.md
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   ├── general-bug-report.md
│   │   ├── general-other-bug-report.md
│   │   ├── navigation-material-bug-report.md
│   │   ├── permissions-bug-report.md
│   │   └── testharness-bug-report.md
│   ├── auto-merge.yml
│   ├── ci-gradle.properties
│   ├── pull_request_template.md
│   ├── release-drafter.yml
│   └── workflows/
│       ├── automerger.yml
│       ├── build-snapshot.yml
│       ├── build.yml
│       ├── issues-stale.yml
│       ├── publish-docs.yml
│       └── update-release.yml
├── .gitignore
├── .idea/
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   ├── copyright/
│   │   ├── AOSP.xml
│   │   └── profiles_settings.xml
│   ├── inspectionProfiles/
│   │   ├── ktlint.xml
│   │   └── profiles_settings.xml
│   ├── kotlinScripting.xml
│   ├── kotlinc.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── ASSETS_LICENSE.txt
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── adaptive/
│   ├── README.md
│   ├── api/
│   │   └── current.api
│   ├── build.gradle.kts
│   ├── gradle.properties
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── com/
│       │           └── google/
│       │               └── accompanist/
│       │                   └── adaptive/
│       │                       ├── DisplayFeatures.kt
│       │                       ├── FoldAwareColumn.kt
│       │                       ├── FoldAwareColumnScope.kt
│       │                       ├── RowColumnImpl.kt
│       │                       ├── RowColumnMeasurementHelper.kt
│       │                       └── TwoPane.kt
│       ├── sharedTest/
│       │   └── kotlin/
│       │       └── com/
│       │           └── google/
│       │               └── accompanist/
│       │                   └── adaptive/
│       │                       ├── DisplayFeaturesTest.kt
│       │                       ├── FoldAwareColumnTest.kt
│       │                       └── TwoPaneTest.kt
│       └── test/
│           └── resources/
│               └── robolectric.properties
├── build-logic/
│   ├── convention/
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           └── kotlin/
│   │               ├── AndroidLibraryComposeConventionPlugin.kt
│   │               ├── AndroidLibraryConventionPlugin.kt
│   │               ├── AndroidLibraryPublishedConventionPlugin.kt
│   │               ├── AndroidLintConventionPlugin.kt
│   │               └── com/
│   │                   └── google/
│   │                       └── accompanist/
│   │                           ├── AndroidCompose.kt
│   │                           ├── BundleInsideHelper.kt
│   │                           ├── KotlinAndroid.kt
│   │                           └── ProjectExtensions.kt
│   ├── gradle.properties
│   └── settings.gradle.kts
├── build.gradle
├── checksum.sh
├── docs/
│   ├── adaptive.md
│   ├── appcompat-theme.md
│   ├── drawablepainter.md
│   ├── migration.md
│   ├── navigation-animation.md
│   ├── navigation-material.md
│   ├── permissions.md
│   ├── systemuicontroller.md
│   ├── updating.md
│   ├── using-snapshot-version.md
│   └── web.md
├── drawablepainter/
│   ├── README.md
│   ├── api/
│   │   └── current.api
│   ├── build.gradle.kts
│   ├── gradle.properties
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           └── java/
│               └── com/
│                   └── google/
│                       └── accompanist/
│                           └── drawablepainter/
│                               └── DrawablePainter.kt
├── generate_docs.sh
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── images/
│   └── Social.sketch
├── internal-testutils/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── google/
│           │           └── accompanist/
│           │               └── internal/
│           │                   └── test/
│           │                       ├── ActivityScenario.kt
│           │                       ├── Assertions.kt
│           │                       ├── IgnoreOnRobolectric.kt
│           │                       ├── TestUtils.kt
│           │                       └── WaitUntil.kt
│           └── res/
│               └── values/
│                   └── themes.xml
├── mkdocs.yml
├── permissions/
│   ├── README.md
│   ├── api/
│   │   └── current.api
│   ├── build.gradle.kts
│   ├── gradle.properties
│   └── src/
│       ├── androidTest/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── com/
│       │           └── google/
│       │               └── accompanist/
│       │                   └── permissions/
│       │                       ├── FakeTests.kt
│       │                       ├── MultipleAndSinglePermissionsTest.kt
│       │                       ├── MultiplePermissionsStateTest.kt
│       │                       ├── PermissionStateTest.kt
│       │                       ├── RequestMultiplePermissionsTest.kt
│       │                       ├── RequestPermissionTest.kt
│       │                       ├── TestUtils.kt
│       │                       └── test/
│       │                           ├── EmptyPermissionsTestActivity.kt
│       │                           └── PermissionsTestActivity.kt
│       └── main/
│           ├── AndroidManifest.xml
│           └── java/
│               └── com/
│                   └── google/
│                       └── accompanist/
│                           └── permissions/
│                               ├── MultiplePermissionsState.kt
│                               ├── MutableMultiplePermissionsState.kt
│                               ├── MutablePermissionState.kt
│                               ├── PermissionState.kt
│                               └── PermissionsUtil.kt
├── permissions-lint/
│   ├── README.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── google/
│       │   │           └── accompanist/
│       │   │               └── permissions/
│       │   │                   └── lint/
│       │   │                       ├── PermissionsIssueRegistry.kt
│       │   │                       ├── PermissionsLaunchDetector.kt
│       │   │                       └── util/
│       │   │                           ├── ComposableUtils.kt
│       │   │                           ├── KotlinMetadataUtils.kt
│       │   │                           ├── Names.kt
│       │   │                           └── PsiUtils.kt
│       │   └── resources/
│       │       └── META-INF/
│       │           └── services/
│       │               └── com.android.tools.lint.client.api.IssueRegistry
│       └── test/
│           └── java/
│               └── com/
│                   └── google/
│                       └── accompanist/
│                           └── permissions/
│                               └── lint/
│                                   └── PermissionsLaunchDetectorTest.kt
├── release/
│   ├── secring.gpg.aes
│   ├── signing-cleanup.sh
│   ├── signing-setup.sh
│   └── signing.properties.aes
├── sample/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── google/
│           │           └── accompanist/
│           │               └── sample/
│           │                   ├── ImageLoadingSampleUtils.kt
│           │                   ├── MainActivity.kt
│           │                   ├── MainScreen.kt
│           │                   ├── Theme.kt
│           │                   ├── adaptive/
│           │                   │   ├── BasicTwoPaneSample.kt
│           │                   │   ├── DraggableFoldAwareColumnSample.kt
│           │                   │   ├── HorizontalTwoPaneSample.kt
│           │                   │   ├── NavDrawerFoldAwareColumnSample.kt
│           │                   │   ├── NavRailFoldAwareColumnSample.kt
│           │                   │   └── VerticalTwoPaneSample.kt
│           │                   ├── drawablepainter/
│           │                   │   └── DocSamples.kt
│           │                   └── permissions/
│           │                       ├── RequestLocationPermissionsSample.kt
│           │                       ├── RequestMultiplePermissionsSample.kt
│           │                       └── RequestPermissionSample.kt
│           └── res/
│               ├── drawable/
│               │   ├── ic_launcher_background.xml
│               │   └── rectangle.xml
│               ├── drawable-v24/
│               │   └── ic_launcher_foreground.xml
│               ├── mipmap-anydpi-v26/
│               │   ├── ic_launcher.xml
│               │   └── ic_launcher_round.xml
│               ├── values/
│               │   └── strings.xml
│               └── values-ar/
│                   └── strings.xml
├── scripts/
│   └── run-tests.sh
├── settings.gradle.kts
└── spotless/
    ├── copyright.txt
    └── greclipse.properties

================================================
FILE CONTENTS
================================================

================================================
FILE: .allstar/binary_artifacts.yaml
================================================
# Exemption reason: This repo uses binary artifacts to ship gradle.jar for users. It does not allow any others.
# Exemption timeframe: permanent
optConfig:
  optOut: true


================================================
FILE: .github/ISSUE_TEMPLATE/adaptive-bug-report.md
================================================
---
name: Adaptive bug report
about: Create a report about adaptive
title: "[Adaptive]"
labels: adaptive
assignees: alexvanyo

---

**Description**

**Steps to reproduce**

**Expected behavior** 

**Additional context**


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

## Describe the bug
A clear and concise description of what the bug is.

## To Reproduce
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

## Expected behavior
A clear and concise description of what you expected to happen.

## Screenshots?
If applicable, add screenshots to help explain your problem.

## Environment:
 - Android OS version: [e.g. Android 5.0]
 - Device: [e.g. Emulator, Google Pixel 4]
 - Accompanist version: [e.g. v0.X]

## Additional context
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false


================================================
FILE: .github/ISSUE_TEMPLATE/general-bug-report.md
================================================
---
name: General Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

## Describe the bug
A clear and concise description of what the bug is.

## To Reproduce (if applicable)
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

## Expected behavior (if applicable)
A clear and concise description of what you expected to happen.

## Screenshots? (if applicable)
If applicable, add screenshots to help explain your problem.

## Environment: (if applicable)
 - Android OS version: [e.g. Android 5.0]
 - Device: [e.g. Emulator, Google Pixel 4]
 - Accompanist version: [e.g. v0.X]

## Additional context
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/general-other-bug-report.md
================================================
---
name: General/Other bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

## Describe the bug
A clear and concise description of what the bug is.

## To Reproduce (if applicable)
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

## Expected behavior (if applicable)
A clear and concise description of what you expected to happen.

## Screenshots? (if applicable)
If applicable, add screenshots to help explain your problem.

## Environment: (if applicable)
 - Android OS version: [e.g. Android 5.0]
 - Device: [e.g. Emulator, Google Pixel 4]
 - Accompanist version: [e.g. v0.X]

## Additional context
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/navigation-material-bug-report.md
================================================
---
name: Navigation Material bug report
about: Create a report to help us improve
title: "[Navigation Material] "
labels: ''
assignees: jossiwolf

---

**Description**

**Steps to reproduce**

**Expected behavior** 

**Additional context**


================================================
FILE: .github/ISSUE_TEMPLATE/permissions-bug-report.md
================================================
---
name: Permissions bug report
about: Create a report to help us improve
title: "[Permissions] "
labels: ''
assignees: bentrengrove

---

**Description**

**Steps to reproduce**

**Expected behavior** 

**Additional context**


================================================
FILE: .github/ISSUE_TEMPLATE/testharness-bug-report.md
================================================
---
name: Test harness bug report
about: Create a report about test harness
title: "[Test Harness]"
labels: testharness
assignees: alexvanyo

---

**Description**

**Steps to reproduce**

**Expected behavior** 

**Additional context**


================================================
FILE: .github/auto-merge.yml
================================================
# Config for github.com/bobvanderlinden/probot-auto-merge
minApprovals:
  COLLABORATOR: 1
requiredLabels:
  - automerge
mergeMethod: merge
reportStatus: true

================================================
FILE: .github/ci-gradle.properties
================================================
#
# Copyright 2019 Google LLC
#
# 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
#
#     http://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.
#

# Turn Gradle daemon off due to https://github.com/Kotlin/dokka/issues/1405
org.gradle.daemon=false

org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4608m -XX:MaxMetaspaceSize=2048m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.workers.max=2

kotlin.compiler.execution.strategy=in-process


================================================
FILE: .github/pull_request_template.md
================================================
### Please add the library name to the PR title. Example: "[Insets] Fixes typo" ###


================================================
FILE: .github/release-drafter.yml
================================================
name-template: 'v$NEXT_PATCH_VERSION 🌈'
tag-template: 'v$NEXT_PATCH_VERSION'
template: |
  ## What’s Changed

  $CHANGES

================================================
FILE: .github/workflows/automerger.yml
================================================
name: main to snapshot auto merger

on:
  push:
    branches:
      - main

jobs:
  automerge:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: '0' # 0 == fetch all history history
          ref: 'snapshot'
          token: ${{ secrets.AUTOMERGE_PAT }}

      - run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git fetch origin
          git merge origin/main --no-edit
          git push


================================================
FILE: .github/workflows/build-snapshot.yml
================================================
name: Build & test (snapshot)

on:
  push:
    branches:
      - snapshot
    paths-ignore:
      - '**.md'
  pull_request:
    branches:
      - snapshot
  workflow_dispatch:

jobs:
  build:
    # Skip build if head commit contains 'skip ci'
    if: "!contains(github.event.head_commit.message, 'skip ci')"

    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - uses: actions/checkout@v2
        with:
          # Fetch expanded history, which is needed for affected module detection
          fetch-depth: '500'

      - 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
        with:
          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: Build
        run: |
          ./gradlew --scan --stacktrace \
              spotlessCheck \
              assemble \
              metalavaCheckCompatibilityRelease \
              lintDebug

      - name: Unit Tests
        run: |
          ./scripts/run-tests.sh \
              --unit-tests \
              --run-affected \
              --affected-base-ref=$BASE_REF

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-robolectric
          path: |
            **/build/test-results/*
            **/build/reports/*

      - name: Clean secrets
        if: always()
        run: release/signing-cleanup.sh

  test:
    runs-on: macos-latest
    needs: build
    timeout-minutes: 50

    strategy:
      # Allow tests to continue on other devices if they fail on one device.
      fail-fast: false
      matrix:
        api-level: [ 22, 26, 29, 31, 32 ]
        shard: [ 0, 1 ] # Need to update shard-count below if this changes

    env:
      TERM: dumb

    steps:
      - uses: actions/checkout@v2
        with:
          # Fetch expanded history, which is needed for affected module detection
          fetch-depth: '500'

      - 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
        with:
          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') }}

      # Determine what emulator image to use. We run all API 28+ emulators using
      # the google_apis image
      - name: Determine emulator target
        id: determine-target
        env:
          API_LEVEL: ${{ matrix.api-level }}
        run: |
          TARGET="default"
          if [ "$API_LEVEL" -ge "28" ]; then
            TARGET="google_apis"
          fi
          echo "TARGET=$TARGET" >> $GITHUB_OUTPUT

      - name: Run tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: ${{ matrix.api-level }}
          target: ${{ steps.determine-target.outputs.TARGET }}
          profile: Galaxy Nexus
          script: ./scripts/run-tests.sh --log-file=logcat.txt --run-affected --affected-base-ref=$BASE_REF --shard-index=${{ matrix.shard }} --shard-count=2

      - name: Clean secrets
        if: always()
        run: release/signing-cleanup.sh

      - name: Upload logs
        if: always()
        uses: actions/upload-artifact@v2
        with:
          name: logs-${{ matrix.api-level }}-${{ steps.determine-target.outputs.TARGET }}-${{ matrix.shard }}
          path: logcat.txt

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v2
        with:
          name: test-results-${{ matrix.api-level }}-${{ steps.determine-target.outputs.TARGET }}-${{ matrix.shard }}
          path: |
            **/build/reports/*
            **/build/outputs/*/connected/*

  deploy:
    if: github.event_name == 'push' # only deploy for pushed commits (not PRs)

    runs-on: ubuntu-latest
    needs: [ build, test ]
    timeout-minutes: 30
    env:
      TERM: dumb

    steps:
      - uses: actions/checkout@v2

      - 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
        with:
          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: Deploy to Sonatype
        run: ./gradlew publish --no-parallel --stacktrace --no-configuration-cache
        env:
          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}

      - name: Clean secrets
        if: always()
        run: release/signing-cleanup.sh


================================================
FILE: .github/workflows/build.yml
================================================
name: Build & test

on:
  push:
    branches:
      - main
      - compose-1.0
      - compose-1.1
      - compose-1.2
      - compose-1.3
      - compose-1.4
      - compose-1.5
      - compose-1.6
    paths-ignore:
      - '**.md'
  pull_request:

jobs:
  build:
    # Skip build if head commit contains 'skip ci'
    if: "!contains(github.event.head_commit.message, 'skip ci')"

    runs-on: ubuntu-latest
    timeout-minutes: 45

    steps:
      - uses: actions/checkout@v2
        with:
          # Fetch expanded history, which is needed for affected module detection
          fetch-depth: '500'

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Setup java
        uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 17

      - name: Decrypt secrets
        run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }}

      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2
        
      - name: Build
        run: |
          ./gradlew --scan --stacktrace \
              spotlessCheck \
              assemble \
              metalavaCheckCompatibilityRelease \
              lintDebug

      - name: Unit Tests
        run: |
          ./scripts/run-tests.sh \
              --unit-tests \
              --run-affected \
              --affected-base-ref=$BASE_REF

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-robolectric
          path: |
            **/build/test-results/*
            **/build/reports/*

      - name: Clean secrets
        if: always()
        run: release/signing-cleanup.sh

  test:
    runs-on: ubuntu-latest
    needs: build
    timeout-minutes: 70

    strategy:
      # Allow tests to continue on other devices if they fail on one device.
      fail-fast: false
      matrix:
        api-level: [ 22, 26, 30 ]
        shard: [ 0, 1 ] # Need to update shard-count below if this changes

    env:
      TERM: dumb

    steps:
      - uses: actions/checkout@v3
        with:
          # Fetch expanded history, which is needed for affected module detection
          fetch-depth: '500'

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Enable KVM
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm

      - name: Setup java
        uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 17

      - name: Decrypt secrets
        run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }}

      - 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
      - name: Determine emulator target
        id: determine-target
        env:
          API_LEVEL: ${{ matrix.api-level }}
        run: |
          TARGET="default"
          if [ "$API_LEVEL" -ge "28" ]; then
            TARGET="google_apis"
          fi
          echo "TARGET=$TARGET" >> $GITHUB_OUTPUT
      - name: Determine emulator arch
        id: determine-arch
        env: 
          API_LEVEL: ${{ matrix.api-level }}
        run: |
          ARCH="x86"
          if [ "$API_LEVEL" -ge "29" ]; then
            ARCH="x86_64"
          fi
          echo "ARCH=$ARCH" >> $GITHUB_OUTPUT
      - name: Run tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: ${{ matrix.api-level }}
          arch: ${{ steps.determine-arch.outputs.ARCH }}
          target: ${{ steps.determine-target.outputs.TARGET }}
          profile: Galaxy Nexus
          script: ./scripts/run-tests.sh --log-file=logcat.txt --run-affected --affected-base-ref=$BASE_REF --shard-index=${{ matrix.shard }} --shard-count=2

      - name: Clean secrets
        if: always()
        run: release/signing-cleanup.sh

      - name: Upload logs
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: logs-${{ matrix.api-level }}-${{ steps.determine-target.outputs.TARGET }}-${{ matrix.shard }}
          path: logcat.txt

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ matrix.api-level }}-${{ steps.determine-target.outputs.TARGET }}-${{ matrix.shard }}
          path: |
            **/build/reports/*
            **/build/outputs/*/connected/*

  deploy:
    if: github.event_name == 'push' # only deploy for pushed commits (not PRs)

    runs-on: ubuntu-latest
    needs: [ build, test ]
    timeout-minutes: 30
    env:
      TERM: dumb

    steps:
      - uses: actions/checkout@v2

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Setup java
        uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 17

      - name: Decrypt secrets
        run: release/signing-setup.sh ${{ secrets.ENCRYPT_KEY }}

      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2

      - name: Deploy to Sonatype
        run: ./gradlew publish --no-parallel --stacktrace --no-configuration-cache
        env:
          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}

      - name: Clean secrets
        if: always()
        run: release/signing-cleanup.sh


================================================
FILE: .github/workflows/issues-stale.yml
================================================
name: 'Close stale issues and PRs'
on:
  schedule:
    - cron: '15 3 * * *'

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/stale@v3
        with:
          stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
          days-before-stale: 45
          days-before-close: 5
          exempt-all-pr-milestones: true
          exempt-issue-labels: 'waiting for info,waiting on dependency'


================================================
FILE: .github/workflows/publish-docs.yml
================================================
name: Publish docs

on:
  push:
    tags:
      - v*
  workflow_dispatch:

jobs:
  deploy_docs:
    runs-on: ubuntu-latest
    env:
      TERM: dumb

    steps:
      - uses: actions/checkout@v2

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Setup java
        uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 17

      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2

      - name: Setup Python
        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-material=="9.*"

      - name: Generate docs
        run: ./generate_docs.sh

      - name: Build site
        run: mkdocs build

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./site


================================================
FILE: .github/workflows/update-release.yml
================================================
name: Update release

on:
  push:
    branches:
      - main

jobs:
  update_draft_release:
    runs-on: ubuntu-latest
    steps:
      # pin directly to 6.0.0 because we don't want to update without knowledge
      - uses: release-drafter/release-drafter@3f0f87098bd6b5c5b9a36d49c41d998ea58f9348
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
# Gradle
.gradle
build/

captures

/local.properties

# IntelliJ .idea folder
.idea/workspace.xml
.idea/libraries
.idea/caches
.idea/navEditor.xml
.idea/tasks.xml
.idea/modules.xml
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/deploymentTargetDropDown.xml
.idea/misc.xml
.idea/androidTestResultsUserPreferences.xml
.idea/deploymentTargetSelector.xml
gradle.xml
*.iml

# General
.DS_Store
.externalNativeBuild

# Do not commit plain-text signing info
release/*.properties
release/*.gpg

# VS Code config
org.eclipse.buildship.core.prefs
.classpath
.project

# Temporary API docs
docs/api
package-list-coil-base

# Mkdocs temporary serving folder
docs-gen
site
*.bak

# Lint reports
lint-report.*


================================================
FILE: .idea/codeStyles/Project.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <code_scheme name="Project" version="173">
    <JetCodeStyleSettings>
      <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="99" />
      <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="99" />
      <option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="true" />
      <option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="true" />
      <option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="true" />
      <option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="true" />
      <option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="true" />
      <option name="CONTINUATION_INDENT_IN_IF_CONDITIONS" value="true" />
      <option name="CONTINUATION_INDENT_IN_ELVIS" value="true" />
      <option name="WRAP_EXPRESSION_BODY_FUNCTIONS" value="0" />
      <option name="IF_RPAREN_ON_NEW_LINE" value="false" />
      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
    </JetCodeStyleSettings>
    <codeStyleSettings language="XML">
      <indentOptions>
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
      </indentOptions>
      <arrangement>
        <rules>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:android</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:id</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>style</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
              <order>ANDROID_ATTRIBUTE_ORDER</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>.*</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
        </rules>
      </arrangement>
    </codeStyleSettings>
    <codeStyleSettings language="kotlin">
      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
      <indentOptions>
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
      </indentOptions>
    </codeStyleSettings>
  </code_scheme>
</component>

================================================
FILE: .idea/codeStyles/codeStyleConfig.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <state>
    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
  </state>
</component>


================================================
FILE: .idea/copyright/AOSP.xml
================================================
<component name="CopyrightManager">
  <copyright>
    <option name="notice" value="Copyright &amp;#36;today.year The Android Open Source Project&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;     https://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
    <option name="myName" value="AOSP" />
  </copyright>
</component>

================================================
FILE: .idea/copyright/profiles_settings.xml
================================================
<component name="CopyrightManager">
  <settings default="AOSP" />
</component>

================================================
FILE: .idea/inspectionProfiles/ktlint.xml
================================================
<component name="InspectionProjectProfileManager">
  <profile version="1.0">
    <option name="myName" value="ktlint" />
    <inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" />
    <inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
  </profile>
</component>


================================================
FILE: .idea/inspectionProfiles/profiles_settings.xml
================================================
<component name="InspectionProjectProfileManager">
  <settings>
    <option name="PROJECT_PROFILE" value="ktlint" />
    <version value="1.0" />
  </settings>
</component>


================================================
FILE: .idea/kotlinScripting.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="KotlinScriptingSettings">
    <option name="isAutoReloadEnabled" value="true" />
  </component>
</project>

================================================
FILE: .idea/kotlinc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="Kotlin2JvmCompilerArguments">
    <option name="jvmTarget" value="1.8" />
  </component>
  <component name="KotlinJpsPluginSettings">
    <option name="version" value="2.0.20" />
  </component>
</project>

================================================
FILE: .idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RunConfigurationProducerService">
    <option name="ignoredProducers">
      <set>
        <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
        <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
        <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
        <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
        <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
        <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
        <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
        <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
      </set>
    </option>
  </component>
</project>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="" vcs="Git" />
  </component>
</project>

================================================
FILE: ASSETS_LICENSE.txt
================================================
All font files are licensed under the SIL OPEN FONT LICENSE license. All other files are licensed under the Apache 2 license.


SIL OPEN FONT LICENSE
Version 1.1 - 26 February 2007

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting — in part or in whole — any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

================================================
FILE: CONTRIBUTING.md
================================================
# How to Contribute

We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.

## New Features/Libraries

Before contributing large new features and/or libraries please start a discussion 
with us first via GitHub Issues and check that we can support it.
We are unable to support all new features, even though we wish we could! If we 
are unable to support adding your feature, we always encourage you to open source it 
in your own repository to help the Compose community grow.

## Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.

## Code Reviews

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.


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   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

       http://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: README.md
================================================
![Accompanist logo](docs/header.png)

Accompanist is a group of libraries that aim to supplement [Jetpack Compose][compose] with features that are commonly required by developers but not yet available.

Accompanist is a labs like environment for new Compose APIs. We use it to help fill known gaps in the Compose toolkit, experiment with new APIs and to gather insight into the development experience of developing a Compose library. The goal of these libraries is to upstream them into the official toolkit, at which point they will be deprecated and removed from Accompanist.

For more details like, why does this library exist? Why is it not part of AndroidX? Will you be releasing more libraries? Check out our [Accompanist FAQ](https://medium.com/p/b55117b02712).

## Compose versions

Each [release](https://github.com/google/accompanist/releases) outlines what version of the Compose UI libraries it depends on. We are currently releasing multiple versions of Accompanist for the different versions of Compose:

<table>
 <tr>
  <td>Compose 1.0 (1.0.x)</td><td><img alt="Maven Central" src="https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-permissions?versionPrefix=0.20"></td>
 </tr>
 <tr>
  <td>Compose 1.1 (1.1.x)</td><td><img alt="Maven Central" src="https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-permissions?versionPrefix=0.23"></td>
 </tr>
 <tr>
  <td>Compose UI 1.2 (1.2.x)</td><td><img alt="Maven Central" src="https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-permissions?versionPrefix=0.25"></td>
 </tr>
 <tr>
  <td>Compose UI 1.3 (1.3.x)</td><td><img alt="Maven Central" src="https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-permissions?versionPrefix=0.28"></td>
 </tr>
 <tr>
  <td>Compose UI 1.4 (1.4.x)</td><td><img alt="Maven Central" src="https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-permissions?versionPrefix=0.30"></td>
 </tr>
 <tr>
  <td>Compose UI 1.5 (1.5.x)</td><td><img alt="Maven Central" src="https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-permissions?versionPrefix=0.32"></td>
 </tr>
 <tr>
  <td>Compose UI 1.6 (1.6.x)</td><td><img alt="Maven Central" src="https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-permissions?versionPrefix=0.34"></td>
 </tr>
  <tr>
  <td>Compose UI 1.7+ (1.7.x)</td><td><img alt="Maven Central" src="https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-permissions?versionPrefix=0.37"></td>
 </tr>
</table>

For stable versions of Compose, we use the latest *stable* version of the Compose compiler. For non-stable versions (alpha, beta, etc), we use the latest compiler at the time of release.

> :warning: **Ensure you are using the Accompanist version that matches with your Compose UI version**: If you upgrade Accompanist, it will upgrade your Compose libraries version via transitive dependencies.

## Libraries

### 📫 [Permissions](./permissions/)
A library that provides [Android runtime permissions][runtimepermissions] support for Jetpack Compose.

### 🖌️ [Drawable Painter](./drawablepainter/)
A library which provides a way to use Android Drawables as Jetpack Compose Painters.

### 📜 [Adaptive](./adaptive/)
A library providing a collection of utilities for adaptive layouts.

### 🧭✨[Navigation-Animation](./navigation-animation/) (Deprecated & Removed)
See our [Migration Guide](https://google.github.io/accompanist/navigation-animation/) for migrating to using built in support for animations in Jetpack Navigation Compose. 

### 🧭🎨️ [Navigation-Material](./navigation-material/) (Deprecated & Removed)
See our [Migration Guide](https://google.github.io/accompanist/navigation-material/) for migrating to using built in material-navigation support. 

### 🍫 [System UI Controller](./systemuicontroller/) (Deprecated & Removed)
We recommend migrating to edge to edge. See our [Migration Guide](https://google.github.io/accompanist/systemuicontroller/) for more details.

---

## Future?

Any of the features available in this group of libraries may become obsolete in the future, at which point they will (probably) become deprecated. 

We will aim to provide a migration path (where possible), to whatever supersedes the functionality.

## Snapshots

Snapshots of the current development version of Accompanist are available, which track the latest commit. See [here](docs/using-snapshot-version.md) for more information. 

---

### Why the name?

The library is all about adding some utilities around Compose. Music composing is done by a
composer, and since this library is about supporting composition, the supporting role of an [accompanist](https://en.wikipedia.org/wiki/Accompaniment) felt like a good name.

## Contributions

Please contribute! We will gladly review any pull requests.
Make sure to read the [Contributing](CONTRIBUTING.md) page first though.

## License

```
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.
```

[appcompat]: https://developer.android.com/jetpack/androidx/releases/appcompat
[compose]: https://developer.android.com/jetpack/compose
[snap]: https://oss.sonatype.org/content/repositories/snapshots/com/google/accompanist/
[mdc]: https://github.com/material-components/material-components-android
[windowinsets]: https://developer.android.com/reference/kotlin/android/view/WindowInsets
[viewpager]: https://developer.android.com/reference/kotlin/androidx/viewpager/widget/ViewPager
[runtimepermissions]: https://developer.android.com/guide/topics/permissions/overview


================================================
FILE: adaptive/README.md
================================================
# Adaptive utilities for Jetpack Compose

[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-adaptive)](https://search.maven.org/search?q=g:com.google.accompanist)

For more information, visit the documentation: https://google.github.io/accompanist/adaptive

## Download

```groovy
repositories {
    mavenCentral()
}

dependencies {
    implementation "com.google.accompanist:accompanist-adaptive:<version>"
}
```

Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. These are updated on every commit.

  [snap]: https://oss.sonatype.org/content/repositories/snapshots/com/google/accompanist/accompanist-adaptive/

================================================
FILE: adaptive/api/current.api
================================================
// Signature format: 4.0
package com.google.accompanist.adaptive {

  public final class DisplayFeaturesKt {
    method @androidx.compose.runtime.Composable public static java.util.List<androidx.window.layout.DisplayFeature> calculateDisplayFeatures(android.app.Activity activity);
  }

  public final class FoldAwareColumnKt {
    method @androidx.compose.runtime.Composable public static void FoldAwareColumn(java.util.List<? extends androidx.window.layout.DisplayFeature> displayFeatures, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.PaddingValues foldPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function1<? super com.google.accompanist.adaptive.FoldAwareColumnScope,kotlin.Unit> content);
  }

  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FoldAwareColumnScope {
    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal alignment);
    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignBy(androidx.compose.ui.Modifier, androidx.compose.ui.layout.VerticalAlignmentLine alignmentLine);
    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignBy(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier ignoreFold(androidx.compose.ui.Modifier);
  }

  @kotlin.jvm.JvmInline public final value class FoldAwareConfiguration {
    field public static final com.google.accompanist.adaptive.FoldAwareConfiguration.Companion Companion;
  }

  public static final class FoldAwareConfiguration.Companion {
    method public int getAllFolds();
    method public int getHorizontalFoldsOnly();
    method public int getVerticalFoldsOnly();
    property public final int AllFolds;
    property public final int HorizontalFoldsOnly;
    property public final int VerticalFoldsOnly;
  }

  public final class SplitResult {
    ctor public SplitResult(androidx.compose.foundation.gestures.Orientation gapOrientation, androidx.compose.ui.geometry.Rect gapBounds);
    method public androidx.compose.ui.geometry.Rect getGapBounds();
    method public androidx.compose.foundation.gestures.Orientation getGapOrientation();
    property public final androidx.compose.ui.geometry.Rect gapBounds;
    property public final androidx.compose.foundation.gestures.Orientation gapOrientation;
  }

  public final class TwoPaneKt {
    method public static com.google.accompanist.adaptive.TwoPaneStrategy HorizontalTwoPaneStrategy(float splitOffset, optional boolean offsetFromStart, optional float gapWidth);
    method public static com.google.accompanist.adaptive.TwoPaneStrategy HorizontalTwoPaneStrategy(float splitFraction, optional float gapWidth);
    method @androidx.compose.runtime.Composable public static void TwoPane(kotlin.jvm.functions.Function0<kotlin.Unit> first, kotlin.jvm.functions.Function0<kotlin.Unit> second, com.google.accompanist.adaptive.TwoPaneStrategy strategy, java.util.List<? extends androidx.window.layout.DisplayFeature> displayFeatures, optional androidx.compose.ui.Modifier modifier, optional int foldAwareConfiguration);
    method public static com.google.accompanist.adaptive.TwoPaneStrategy VerticalTwoPaneStrategy(float splitOffset, optional boolean offsetFromTop, optional float gapHeight);
    method public static com.google.accompanist.adaptive.TwoPaneStrategy VerticalTwoPaneStrategy(float splitFraction, optional float gapHeight);
  }

  public fun interface TwoPaneStrategy {
    method public com.google.accompanist.adaptive.SplitResult calculateSplitResult(androidx.compose.ui.unit.Density density, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.layout.LayoutCoordinates layoutCoordinates);
  }

}



================================================
FILE: adaptive/build.gradle.kts
================================================
/*
 * 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 {
    alias(libs.plugins.accompanist.android.library)
    alias(libs.plugins.accompanist.android.library.compose)
    alias(libs.plugins.accompanist.android.library.published)
}

android {
    namespace = "com.google.accompanist.adaptive"

    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")
        }
    }
}

dependencies {
    api(libs.compose.foundation.foundation)
    api(libs.compose.ui.ui)
    api(libs.androidx.window)
    
    implementation(libs.kotlin.coroutines.android)
    implementation(libs.compose.ui.util)

    // ======================
    // 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)

    androidTestImplementation(libs.androidx.window.testing)
    testImplementation(libs.androidx.window.testing)

    testImplementation(libs.robolectric)
}


================================================
FILE: adaptive/gradle.properties
================================================
POM_ARTIFACT_ID=accompanist-adaptive
POM_NAME=Accompanist Adaptive library
POM_PACKAGING=aar

================================================
FILE: adaptive/src/main/AndroidManifest.xml
================================================
<!--
  ~ 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.
  -->

<manifest />


================================================
FILE: adaptive/src/main/java/com/google/accompanist/adaptive/DisplayFeatures.kt
================================================
/*
 * 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.
 */

package com.google.accompanist.adaptive

import android.app.Activity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.window.layout.DisplayFeature
import androidx.window.layout.WindowInfoTracker

/**
 * Calculates the list of [DisplayFeature]s from the given [activity].
 */
@Composable
public fun calculateDisplayFeatures(activity: Activity): List<DisplayFeature> {
    val windowLayoutInfo = remember(activity) {
        WindowInfoTracker.getOrCreate(activity).windowLayoutInfo(activity)
    }
    val displayFeatures by produceState(
        initialValue = emptyList<DisplayFeature>(),
        key1 = windowLayoutInfo
    ) {
        windowLayoutInfo.collect { info ->
            value = info.displayFeatures
        }
    }

    return displayFeatures
}


================================================
FILE: adaptive/src/main/java/com/google/accompanist/adaptive/FoldAwareColumn.kt
================================================
/*
 * 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.adaptive

import android.util.Range
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.layout.IntrinsicMeasurable
import androidx.compose.ui.layout.IntrinsicMeasureScope
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasurePolicy
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.findRootCoordinates
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.InspectorValueInfo
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.window.layout.DisplayFeature
import androidx.window.layout.FoldingFeature
import kotlin.math.roundToInt

/**
 * A simplified version of [Column] that places children in a fold-aware manner.
 *
 * The layout starts placing children from the top of the available space. If there is a horizontal
 * [separating](https://developer.android.com/reference/kotlin/androidx/window/layout/FoldingFeature#isSeparating())
 * fold present in the window, then the layout will check to see if any children overlap the fold.
 * If a child would overlap the fold in its current position, then the layout will increase its
 * y coordinate so that the child is now placed below the fold, and any subsequent children will
 * also be placed below the fold.
 *
 *
 * @param displayFeatures a list of display features the device currently has
 * @param modifier an optional modifier for the layout
 * @param foldPadding the optional padding to add around a fold
 * @param horizontalAlignment the horizontal alignment of the layout's children.
 */
@Composable
public fun FoldAwareColumn(
    displayFeatures: List<DisplayFeature>,
    modifier: Modifier = Modifier,
    foldPadding: PaddingValues = PaddingValues(),
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable FoldAwareColumnScope.() -> Unit,
) {
    Layout(
        modifier = modifier,
        measurePolicy = foldAwareColumnMeasurePolicy(
            verticalArrangement = Arrangement.Top,
            horizontalAlignment = horizontalAlignment,
            fold = {
                // Extract folding feature if horizontal and separating
                displayFeatures.find {
                    it is FoldingFeature && it.orientation == FoldingFeature.Orientation.HORIZONTAL &&
                        it.isSeparating
                } as FoldingFeature?
            },
            foldPadding = foldPadding,
        ),
        content = { FoldAwareColumnScopeInstance.content() }
    )
}

/**
 * FoldAwareColumn version of [rowColumnMeasurePolicy] that uses [FoldAwareColumnMeasurementHelper.foldAwarePlaceHelper]
 * method instead of [RowColumnMeasurementHelper.placeHelper]
 */
// TODO: change from internal to private once metalava issue is solved https://issuetracker.google.com/issues/271539608
@Composable
internal fun foldAwareColumnMeasurePolicy(
    verticalArrangement: Arrangement.Vertical,
    horizontalAlignment: Alignment.Horizontal,
    fold: () -> FoldingFeature?,
    foldPadding: PaddingValues
) = remember(verticalArrangement, horizontalAlignment, fold, foldPadding) {

    val orientation = LayoutOrientation.Vertical
    val arrangement: (Int, IntArray, LayoutDirection, Density, IntArray) -> Unit =
        { totalSize, size, _, density, outPosition ->
            with(verticalArrangement) { density.arrange(totalSize, size, outPosition) }
        }
    val arrangementSpacing = verticalArrangement.spacing
    val crossAxisAlignment = CrossAxisAlignment.horizontal(horizontalAlignment)
    val crossAxisSize = SizeMode.Wrap

    object : MeasurePolicy {
        override fun MeasureScope.measure(
            measurables: List<Measurable>,
            constraints: Constraints
        ): MeasureResult {
            val placeables = arrayOfNulls<Placeable?>(measurables.size)
            val rowColumnMeasureHelper =
                FoldAwareColumnMeasurementHelper(
                    orientation,
                    arrangement,
                    arrangementSpacing,
                    crossAxisSize,
                    crossAxisAlignment,
                    measurables,
                    placeables
                )

            val measureResult = rowColumnMeasureHelper
                .measureWithoutPlacing(
                    this,
                    constraints, 0, measurables.size
                )

            val layoutWidth: Int
            val layoutHeight: Int
            if (orientation == LayoutOrientation.Horizontal) {
                layoutWidth = measureResult.mainAxisSize
                layoutHeight = measureResult.crossAxisSize
            } else {
                layoutWidth = measureResult.crossAxisSize
                layoutHeight = measureResult.mainAxisSize
            }

            // Calculate fold bounds in pixels (including any added fold padding)
            val foldBoundsPx = with(density) {
                val topPaddingPx = foldPadding.calculateTopPadding().roundToPx()
                val bottomPaddingPx = foldPadding.calculateBottomPadding().roundToPx()

                fold()?.bounds?.let {
                    Rect(
                        left = it.left.toFloat(),
                        top = it.top.toFloat() - topPaddingPx,
                        right = it.right.toFloat(),
                        bottom = it.bottom.toFloat() + bottomPaddingPx
                    )
                }
            }

            // We only know how much padding is added inside the placement scope, so just add fold height
            // and height of the largest child when laying out to cover the maximum possible height
            val heightPadding = foldBoundsPx?.let { bounds ->
                val largestChildHeight = rowColumnMeasureHelper.placeables.maxOfOrNull {
                    if ((it?.parentData as? RowColumnParentData)?.ignoreFold == true) {
                        0
                    } else {
                        it?.height ?: 0
                    }
                } ?: 0
                bounds.height.roundToInt() + largestChildHeight
            } ?: 0
            val paddedLayoutHeight = layoutHeight + heightPadding

            return layout(layoutWidth, paddedLayoutHeight) {
                rowColumnMeasureHelper.foldAwarePlaceHelper(
                    this,
                    measureResult,
                    0,
                    layoutDirection,
                    foldBoundsPx
                )
            }
        }

        override fun IntrinsicMeasureScope.minIntrinsicWidth(
            measurables: List<IntrinsicMeasurable>,
            height: Int
        ) = MinIntrinsicWidthMeasureBlock(orientation)(
            measurables,
            height,
            arrangementSpacing.roundToPx()
        )

        override fun IntrinsicMeasureScope.minIntrinsicHeight(
            measurables: List<IntrinsicMeasurable>,
            width: Int
        ) = MinIntrinsicHeightMeasureBlock(orientation)(
            measurables,
            width,
            arrangementSpacing.roundToPx()
        )

        override fun IntrinsicMeasureScope.maxIntrinsicWidth(
            measurables: List<IntrinsicMeasurable>,
            height: Int
        ) = MaxIntrinsicWidthMeasureBlock(orientation)(
            measurables,
            height,
            arrangementSpacing.roundToPx()
        )

        override fun IntrinsicMeasureScope.maxIntrinsicHeight(
            measurables: List<IntrinsicMeasurable>,
            width: Int
        ) = MaxIntrinsicHeightMeasureBlock(orientation)(
            measurables,
            width,
            arrangementSpacing.roundToPx()
        )
    }
}

/**
 * Inherits from [RowColumnMeasurementHelper] to place children in a fold-aware manner
 */
private class FoldAwareColumnMeasurementHelper(
    orientation: LayoutOrientation,
    arrangement: (Int, IntArray, LayoutDirection, Density, IntArray) -> Unit,
    arrangementSpacing: Dp,
    crossAxisSize: SizeMode,
    crossAxisAlignment: CrossAxisAlignment,
    measurables: List<Measurable>,
    placeables: Array<Placeable?>
) : RowColumnMeasurementHelper(
    orientation,
    arrangement,
    arrangementSpacing,
    crossAxisSize,
    crossAxisAlignment,
    measurables,
    placeables
) {
    /**
     * Copy of [placeHelper] that has been modified for FoldAwareColumn implementation
     */
    @OptIn(ExperimentalComposeUiApi::class)
    fun foldAwarePlaceHelper(
        placeableScope: Placeable.PlacementScope,
        measureResult: RowColumnMeasureHelperResult,
        crossAxisOffset: Int,
        layoutDirection: LayoutDirection,
        foldBoundsPx: Rect?
    ) {
        with(placeableScope) {
            val layoutBounds = coordinates!!.trueBoundsInWindow()

            var placeableY = 0

            for (i in measureResult.startIndex until measureResult.endIndex) {
                val placeable = placeables[i]!!
                val mainAxisPositions = measureResult.mainAxisPositions
                val crossAxisPosition = getCrossAxisPosition(
                    placeable,
                    (measurables[i].parentData as? RowColumnParentData),
                    measureResult.crossAxisSize,
                    layoutDirection,
                    measureResult.beforeCrossAxisAlignmentLine
                ) + crossAxisOffset
                if (orientation == LayoutOrientation.Horizontal) {
                    placeable.place(
                        mainAxisPositions[i - measureResult.startIndex],
                        crossAxisPosition
                    )
                } else {
                    val relativeBounds = Rect(
                        left = 0f,
                        top = placeableY.toFloat(),
                        right = placeable.width.toFloat(),
                        bottom = (placeableY + placeable.height).toFloat()
                    )
                    val absoluteBounds =
                        relativeBounds.translate(layoutBounds.left, layoutBounds.top)

                    // If placeable overlaps fold, push placeable below
                    if (foldBoundsPx?.overlapsVertically(absoluteBounds) == true &&
                        (placeable.parentData as? RowColumnParentData)?.ignoreFold != true
                    ) {
                        placeableY = (foldBoundsPx.bottom - layoutBounds.top).toInt()
                    }

                    placeable.place(crossAxisPosition, placeableY)

                    placeableY += placeable.height
                }
            }
        }
    }
}

/**
 * Copy of original [LayoutCoordinates.boundsInWindow], but without the nonzero dimension check.
 *
 * Instead of returning [Rect.Zero] for a layout with zero width/height, this method will still
 * return a Rect with the layout's bounds.
 */
@VisibleForTesting
internal fun LayoutCoordinates.trueBoundsInWindow(): Rect {
    val root = findRootCoordinates()
    val bounds = boundsInRoot()
    val rootWidth = root.size.width.toFloat()
    val rootHeight = root.size.height.toFloat()

    val boundsLeft = bounds.left.coerceIn(0f, rootWidth)
    val boundsTop = bounds.top.coerceIn(0f, rootHeight)
    val boundsRight = bounds.right.coerceIn(0f, rootWidth)
    val boundsBottom = bounds.bottom.coerceIn(0f, rootHeight)

    val topLeft = root.localToWindow(Offset(boundsLeft, boundsTop))
    val topRight = root.localToWindow(Offset(boundsRight, boundsTop))
    val bottomRight = root.localToWindow(Offset(boundsRight, boundsBottom))
    val bottomLeft = root.localToWindow(Offset(boundsLeft, boundsBottom))

    val left = minOf(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x)
    val top = minOf(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y)
    val right = maxOf(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x)
    val bottom = maxOf(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y)

    return Rect(left, top, right, bottom)
}

/**
 * Checks if the vertical ranges of the two Rects overlap (inclusive)
 */
private fun Rect.overlapsVertically(other: Rect): Boolean {
    val verticalRange = Range(top, bottom)
    val otherVerticalRange = Range(other.top, other.bottom)
    return verticalRange.overlaps(otherVerticalRange)
}

/**
 * Inclusive check to see if the given float ranges overlap
 */
private fun Range<Float>.overlaps(other: Range<Float>): Boolean {
    return (lower >= other.lower && lower <= other.upper) || (upper >= other.lower && upper <= other.upper)
}

/**
 * Copy of [RowColumnParentData] that has been modified to include the new ignoreFold field.
 */
internal data class RowColumnParentData(
    var weight: Float = 0f,
    var fill: Boolean = true,
    var crossAxisAlignment: CrossAxisAlignment? = null,
    var ignoreFold: Boolean = false
)

internal class IgnoreFoldModifier(
    inspectorInfo: InspectorInfo.() -> Unit
) : ParentDataModifier, InspectorValueInfo(inspectorInfo) {
    override fun Density.modifyParentData(parentData: Any?) =
        ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
            it.ignoreFold = true
        }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        return other is IgnoreFoldModifier
    }

    override fun hashCode(): Int {
        return 0
    }

    override fun toString(): String =
        "IgnoreFoldModifier(ignoreFold=true)"
}


================================================
FILE: adaptive/src/main/java/com/google/accompanist/adaptive/FoldAwareColumnScope.kt
================================================
/*
 * 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.adaptive

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.LayoutScopeMarker
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Measured
import androidx.compose.ui.layout.VerticalAlignmentLine
import androidx.compose.ui.platform.debugInspectorInfo

/**
 * Copy of [ColumnScope] that excludes the weight Modifier attribute.
 *
 * Also adds a new [ignoreFold] Modifier attribute.
 */
@LayoutScopeMarker
@Immutable
public interface FoldAwareColumnScope {
    /**
     * Ignore the fold when placing this child within the [FoldAwareColumn].
     */
    @Stable
    public fun Modifier.ignoreFold(): Modifier

    /**
     * Align the element horizontally within the [Column]. This alignment will have priority over
     * the [Column]'s `horizontalAlignment` parameter.
     *
     * Example usage:
     * @sample androidx.compose.foundation.layout.samples.SimpleAlignInColumn
     */
    @Stable
    public fun Modifier.align(alignment: Alignment.Horizontal): Modifier

    /**
     * Position the element horizontally such that its [alignmentLine] aligns with sibling elements
     * also configured to [alignBy]. [alignBy] is a form of [align],
     * so both modifiers will not work together if specified for the same layout.
     * Within a [Column], all components with [alignBy] will align horizontally using
     * the specified [VerticalAlignmentLine]s or values provided using the other
     * [alignBy] overload, forming a sibling group.
     * At least one element of the sibling group will be placed as it had [Alignment.Start] align
     * in [Column], and the alignment of the other siblings will be then determined such that
     * the alignment lines coincide. Note that if only one element in a [Column] has the
     * [alignBy] modifier specified the element will be positioned
     * as if it had [Alignment.Start] align.
     *
     * Example usage:
     * @sample androidx.compose.foundation.layout.samples.SimpleRelativeToSiblingsInColumn
     */
    @Stable
    public fun Modifier.alignBy(alignmentLine: VerticalAlignmentLine): Modifier

    /**
     * Position the element horizontally such that the alignment line for the content as
     * determined by [alignmentLineBlock] aligns with sibling elements also configured to
     * [alignBy]. [alignBy] is a form of [align], so both modifiers
     * will not work together if specified for the same layout.
     * Within a [Column], all components with [alignBy] will align horizontally using
     * the specified [VerticalAlignmentLine]s or values obtained from [alignmentLineBlock],
     * forming a sibling group.
     * At least one element of the sibling group will be placed as it had [Alignment.Start] align
     * in [Column], and the alignment of the other siblings will be then determined such that
     * the alignment lines coincide. Note that if only one element in a [Column] has the
     * [alignBy] modifier specified the element will be positioned
     * as if it had [Alignment.Start] align.
     *
     * Example usage:
     * @sample androidx.compose.foundation.layout.samples.SimpleRelativeToSiblings
     */
    @Stable
    public fun Modifier.alignBy(alignmentLineBlock: (Measured) -> Int): Modifier
}

internal object FoldAwareColumnScopeInstance : FoldAwareColumnScope {
    @Stable
    override fun Modifier.ignoreFold() = this.then(
        IgnoreFoldModifier(
            inspectorInfo = debugInspectorInfo {
                name = "ignoreFold"
                value = true
            }
        )
    )

    @Stable
    override fun Modifier.align(alignment: Alignment.Horizontal) = this.then(
        HorizontalAlignModifier(
            horizontal = alignment,
            inspectorInfo = debugInspectorInfo {
                name = "align"
                value = alignment
            }
        )
    )

    @Stable
    override fun Modifier.alignBy(alignmentLine: VerticalAlignmentLine) = this.then(
        SiblingsAlignedModifier.WithAlignmentLine(
            alignmentLine = alignmentLine,
            inspectorInfo = debugInspectorInfo {
                name = "alignBy"
                value = alignmentLine
            }
        )
    )

    @Stable
    override fun Modifier.alignBy(alignmentLineBlock: (Measured) -> Int) = this.then(
        SiblingsAlignedModifier.WithAlignmentLineBlock(
            block = alignmentLineBlock,
            inspectorInfo = debugInspectorInfo {
                name = "alignBy"
                value = alignmentLineBlock
            }
        )
    )
}


================================================
FILE: adaptive/src/main/java/com/google/accompanist/adaptive/RowColumnImpl.kt
================================================
/*
 * 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.adaptive

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.layout.AlignmentLine
import androidx.compose.ui.layout.IntrinsicMeasurable
import androidx.compose.ui.layout.Measured
import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.InspectorValueInfo
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastForEach
import com.google.accompanist.adaptive.LayoutOrientation.Horizontal
import com.google.accompanist.adaptive.LayoutOrientation.Vertical
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt

/**
 * Copied from:
 * RowColumnImpl.kt
 * https://android-review.googlesource.com/c/platform/frameworks/support/+/2260390/27/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
 *
 * The only changes were updating access modifiers and removing unused code
 */

/**
 * [Row] will be [Horizontal], [Column] is [Vertical].
 */
internal enum class LayoutOrientation {
    Horizontal,
    Vertical
}

/**
 * Used to specify the alignment of a layout's children, in cross axis direction.
 */
@Immutable
internal sealed class CrossAxisAlignment {
    /**
     * Aligns to [size]. If this is a vertical alignment, [layoutDirection] should be
     * [LayoutDirection.Ltr].
     *
     * @param size The remaining space (total size - content size) in the container.
     * @param layoutDirection The layout direction of the content if horizontal or
     * [LayoutDirection.Ltr] if vertical.
     * @param placeable The item being aligned.
     * @param beforeCrossAxisAlignmentLine The space before the cross-axis alignment line if
     * an alignment line is being used or 0 if no alignment line is being used.
     */
    internal abstract fun align(
        size: Int,
        layoutDirection: LayoutDirection,
        placeable: Placeable,
        beforeCrossAxisAlignmentLine: Int
    ): Int

    /**
     * Returns `true` if this is [Relative].
     */
    internal open val isRelative: Boolean
        get() = false

    /**
     * Returns the alignment line position relative to the left/top of the space or `null` if
     * this alignment doesn't rely on alignment lines.
     */
    internal open fun calculateAlignmentLinePosition(placeable: Placeable): Int? = null

    companion object {
        /**
         * Place children such that their center is in the middle of the cross axis.
         */
        @Stable
        val Center: CrossAxisAlignment = CenterCrossAxisAlignment

        /**
         * Place children such that their start edge is aligned to the start edge of the cross
         * axis. TODO(popam): Consider rtl directionality.
         */
        @Stable
        val Start: CrossAxisAlignment = StartCrossAxisAlignment

        /**
         * Place children such that their end edge is aligned to the end edge of the cross
         * axis. TODO(popam): Consider rtl directionality.
         */
        @Stable
        val End: CrossAxisAlignment = EndCrossAxisAlignment

        /**
         * Align children by their baseline.
         */
        fun AlignmentLine(alignmentLine: AlignmentLine): CrossAxisAlignment =
            AlignmentLineCrossAxisAlignment(AlignmentLineProvider.Value(alignmentLine))

        /**
         * Align children relative to their siblings using the alignment line provided as a
         * parameter using [AlignmentLineProvider].
         */
        internal fun Relative(alignmentLineProvider: AlignmentLineProvider): CrossAxisAlignment =
            AlignmentLineCrossAxisAlignment(alignmentLineProvider)

        /**
         * Align children with vertical alignment.
         */
        internal fun vertical(vertical: Alignment.Vertical): CrossAxisAlignment =
            VerticalCrossAxisAlignment(vertical)

        /**
         * Align children with horizontal alignment.
         */
        internal fun horizontal(horizontal: Alignment.Horizontal): CrossAxisAlignment =
            HorizontalCrossAxisAlignment(horizontal)
    }

    private object CenterCrossAxisAlignment : CrossAxisAlignment() {
        override fun align(
            size: Int,
            layoutDirection: LayoutDirection,
            placeable: Placeable,
            beforeCrossAxisAlignmentLine: Int
        ): Int {
            return size / 2
        }
    }

    private object StartCrossAxisAlignment : CrossAxisAlignment() {
        override fun align(
            size: Int,
            layoutDirection: LayoutDirection,
            placeable: Placeable,
            beforeCrossAxisAlignmentLine: Int
        ): Int {
            return if (layoutDirection == LayoutDirection.Ltr) 0 else size
        }
    }

    private object EndCrossAxisAlignment : CrossAxisAlignment() {
        override fun align(
            size: Int,
            layoutDirection: LayoutDirection,
            placeable: Placeable,
            beforeCrossAxisAlignmentLine: Int
        ): Int {
            return if (layoutDirection == LayoutDirection.Ltr) size else 0
        }
    }

    private class AlignmentLineCrossAxisAlignment(
        val alignmentLineProvider: AlignmentLineProvider
    ) : CrossAxisAlignment() {
        override val isRelative: Boolean
            get() = true

        override fun calculateAlignmentLinePosition(placeable: Placeable): Int {
            return alignmentLineProvider.calculateAlignmentLinePosition(placeable)
        }

        override fun align(
            size: Int,
            layoutDirection: LayoutDirection,
            placeable: Placeable,
            beforeCrossAxisAlignmentLine: Int
        ): Int {
            val alignmentLinePosition =
                alignmentLineProvider.calculateAlignmentLinePosition(placeable)
            return if (alignmentLinePosition != AlignmentLine.Unspecified) {
                val line = beforeCrossAxisAlignmentLine - alignmentLinePosition
                if (layoutDirection == LayoutDirection.Rtl) {
                    size - line
                } else {
                    line
                }
            } else {
                0
            }
        }
    }

    private class VerticalCrossAxisAlignment(
        val vertical: Alignment.Vertical
    ) : CrossAxisAlignment() {
        override fun align(
            size: Int,
            layoutDirection: LayoutDirection,
            placeable: Placeable,
            beforeCrossAxisAlignmentLine: Int
        ): Int {
            return vertical.align(0, size)
        }
    }

    private class HorizontalCrossAxisAlignment(
        val horizontal: Alignment.Horizontal
    ) : CrossAxisAlignment() {
        override fun align(
            size: Int,
            layoutDirection: LayoutDirection,
            placeable: Placeable,
            beforeCrossAxisAlignmentLine: Int
        ): Int {
            return horizontal.align(0, size, layoutDirection)
        }
    }
}

/**
 * Box [Constraints], but which abstract away width and height in favor of main axis and cross axis.
 */
internal data class OrientationIndependentConstraints(
    val mainAxisMin: Int,
    val mainAxisMax: Int,
    val crossAxisMin: Int,
    val crossAxisMax: Int
) {
    constructor(c: Constraints, orientation: LayoutOrientation) : this(
        if (orientation === Horizontal) c.minWidth else c.minHeight,
        if (orientation === Horizontal) c.maxWidth else c.maxHeight,
        if (orientation === Horizontal) c.minHeight else c.minWidth,
        if (orientation === Horizontal) c.maxHeight else c.maxWidth
    )

    // Creates a new instance with the same main axis constraints and maximum tight cross axis.
    fun stretchCrossAxis() = OrientationIndependentConstraints(
        mainAxisMin,
        mainAxisMax,
        if (crossAxisMax != Constraints.Infinity) crossAxisMax else crossAxisMin,
        crossAxisMax
    )

    // Given an orientation, resolves the current instance to traditional constraints.
    fun toBoxConstraints(orientation: LayoutOrientation) =
        if (orientation === Horizontal) {
            Constraints(mainAxisMin, mainAxisMax, crossAxisMin, crossAxisMax)
        } else {
            Constraints(crossAxisMin, crossAxisMax, mainAxisMin, mainAxisMax)
        }

    // Given an orientation, resolves the max width constraint this instance represents.
    fun maxWidth(orientation: LayoutOrientation) =
        if (orientation === Horizontal) {
            mainAxisMax
        } else {
            crossAxisMax
        }

    // Given an orientation, resolves the max height constraint this instance represents.
    fun maxHeight(orientation: LayoutOrientation) =
        if (orientation === Horizontal) {
            crossAxisMax
        } else {
            mainAxisMax
        }
}

internal val IntrinsicMeasurable.rowColumnParentData: RowColumnParentData?
    get() = parentData as? RowColumnParentData

internal val RowColumnParentData?.weight: Float
    get() = this?.weight ?: 0f

internal val RowColumnParentData?.fill: Boolean
    get() = this?.fill ?: true

internal val RowColumnParentData?.crossAxisAlignment: CrossAxisAlignment?
    get() = this?.crossAxisAlignment

internal val RowColumnParentData?.isRelative: Boolean
    get() = this.crossAxisAlignment?.isRelative ?: false

internal fun MinIntrinsicWidthMeasureBlock(orientation: LayoutOrientation) =
    if (orientation == Horizontal) {
        IntrinsicMeasureBlocks.HorizontalMinWidth
    } else {
        IntrinsicMeasureBlocks.VerticalMinWidth
    }

internal fun MinIntrinsicHeightMeasureBlock(orientation: LayoutOrientation) =
    if (orientation == Horizontal) {
        IntrinsicMeasureBlocks.HorizontalMinHeight
    } else {
        IntrinsicMeasureBlocks.VerticalMinHeight
    }

internal fun MaxIntrinsicWidthMeasureBlock(orientation: LayoutOrientation) =
    if (orientation == Horizontal) {
        IntrinsicMeasureBlocks.HorizontalMaxWidth
    } else {
        IntrinsicMeasureBlocks.VerticalMaxWidth
    }

internal fun MaxIntrinsicHeightMeasureBlock(orientation: LayoutOrientation) =
    if (orientation == Horizontal) {
        IntrinsicMeasureBlocks.HorizontalMaxHeight
    } else {
        IntrinsicMeasureBlocks.VerticalMaxHeight
    }

internal object IntrinsicMeasureBlocks {
    val HorizontalMinWidth: (List<IntrinsicMeasurable>, Int, Int) -> Int =
        { measurables, availableHeight, mainAxisSpacing ->
            intrinsicSize(
                measurables,
                { h -> minIntrinsicWidth(h) },
                { w -> maxIntrinsicHeight(w) },
                availableHeight,
                mainAxisSpacing,
                Horizontal,
                Horizontal
            )
        }
    val VerticalMinWidth: (List<IntrinsicMeasurable>, Int, Int) -> Int =
        { measurables, availableHeight, mainAxisSpacing ->
            intrinsicSize(
                measurables,
                { h -> minIntrinsicWidth(h) },
                { w -> maxIntrinsicHeight(w) },
                availableHeight,
                mainAxisSpacing,
                Vertical,
                Horizontal
            )
        }
    val HorizontalMinHeight: (List<IntrinsicMeasurable>, Int, Int) -> Int =
        { measurables, availableWidth, mainAxisSpacing ->
            intrinsicSize(
                measurables,
                { w -> minIntrinsicHeight(w) },
                { h -> maxIntrinsicWidth(h) },
                availableWidth,
                mainAxisSpacing,
                Horizontal,
                Vertical
            )
        }
    val VerticalMinHeight: (List<IntrinsicMeasurable>, Int, Int) -> Int =
        { measurables, availableWidth, mainAxisSpacing ->
            intrinsicSize(
                measurables,
                { w -> minIntrinsicHeight(w) },
                { h -> maxIntrinsicWidth(h) },
                availableWidth,
                mainAxisSpacing,
                Vertical,
                Vertical
            )
        }
    val HorizontalMaxWidth: (List<IntrinsicMeasurable>, Int, Int) -> Int =
        { measurables, availableHeight, mainAxisSpacing ->
            intrinsicSize(
                measurables,
                { h -> maxIntrinsicWidth(h) },
                { w -> maxIntrinsicHeight(w) },
                availableHeight,
                mainAxisSpacing,
                Horizontal,
                Horizontal
            )
        }
    val VerticalMaxWidth: (List<IntrinsicMeasurable>, Int, Int) -> Int =
        { measurables, availableHeight, mainAxisSpacing ->
            intrinsicSize(
                measurables,
                { h -> maxIntrinsicWidth(h) },
                { w -> maxIntrinsicHeight(w) },
                availableHeight,
                mainAxisSpacing,
                Vertical,
                Horizontal
            )
        }
    val HorizontalMaxHeight: (List<IntrinsicMeasurable>, Int, Int) -> Int =
        { measurables, availableWidth, mainAxisSpacing ->
            intrinsicSize(
                measurables,
                { w -> maxIntrinsicHeight(w) },
                { h -> maxIntrinsicWidth(h) },
                availableWidth,
                mainAxisSpacing,
                Horizontal,
                Vertical
            )
        }
    val VerticalMaxHeight: (List<IntrinsicMeasurable>, Int, Int) -> Int =
        { measurables, availableWidth, mainAxisSpacing ->
            intrinsicSize(
                measurables,
                { w -> maxIntrinsicHeight(w) },
                { h -> maxIntrinsicWidth(h) },
                availableWidth,
                mainAxisSpacing,
                Vertical,
                Vertical
            )
        }
}

private fun intrinsicSize(
    children: List<IntrinsicMeasurable>,
    intrinsicMainSize: IntrinsicMeasurable.(Int) -> Int,
    intrinsicCrossSize: IntrinsicMeasurable.(Int) -> Int,
    crossAxisAvailable: Int,
    mainAxisSpacing: Int,
    layoutOrientation: LayoutOrientation,
    intrinsicOrientation: LayoutOrientation
) = if (layoutOrientation == intrinsicOrientation) {
    intrinsicMainAxisSize(children, intrinsicMainSize, crossAxisAvailable, mainAxisSpacing)
} else {
    intrinsicCrossAxisSize(
        children,
        intrinsicCrossSize,
        intrinsicMainSize,
        crossAxisAvailable,
        mainAxisSpacing
    )
}

private fun intrinsicMainAxisSize(
    children: List<IntrinsicMeasurable>,
    mainAxisSize: IntrinsicMeasurable.(Int) -> Int,
    crossAxisAvailable: Int,
    mainAxisSpacing: Int
): Int {
    var weightUnitSpace = 0
    var fixedSpace = 0
    var totalWeight = 0f
    children.fastForEach { child ->
        val weight = child.rowColumnParentData.weight
        val size = child.mainAxisSize(crossAxisAvailable)
        if (weight == 0f) {
            fixedSpace += size
        } else if (weight > 0f) {
            totalWeight += weight
            weightUnitSpace = max(weightUnitSpace, (size / weight).roundToInt())
        }
    }
    return (weightUnitSpace * totalWeight).roundToInt() + fixedSpace +
        (children.size - 1) * mainAxisSpacing
}

private fun intrinsicCrossAxisSize(
    children: List<IntrinsicMeasurable>,
    mainAxisSize: IntrinsicMeasurable.(Int) -> Int,
    crossAxisSize: IntrinsicMeasurable.(Int) -> Int,
    mainAxisAvailable: Int,
    mainAxisSpacing: Int
): Int {
    var fixedSpace = min((children.size - 1) * mainAxisSpacing, mainAxisAvailable)
    var crossAxisMax = 0
    var totalWeight = 0f
    children.fastForEach { child ->
        val weight = child.rowColumnParentData.weight
        if (weight == 0f) {
            // Ask the child how much main axis space it wants to occupy. This cannot be more
            // than the remaining available space.
            val mainAxisSpace = min(
                child.mainAxisSize(Constraints.Infinity),
                mainAxisAvailable - fixedSpace
            )
            fixedSpace += mainAxisSpace
            // Now that the assigned main axis space is known, ask about the cross axis space.
            crossAxisMax = max(crossAxisMax, child.crossAxisSize(mainAxisSpace))
        } else if (weight > 0f) {
            totalWeight += weight
        }
    }

    // For weighted children, calculate how much main axis space weight=1 would represent.
    val weightUnitSpace = if (totalWeight == 0f) {
        0
    } else if (mainAxisAvailable == Constraints.Infinity) {
        Constraints.Infinity
    } else {
        (max(mainAxisAvailable - fixedSpace, 0) / totalWeight).roundToInt()
    }

    children.fastForEach { child ->
        val weight = child.rowColumnParentData.weight
        // Now the main axis for weighted children is known, so ask about the cross axis space.
        if (weight > 0f) {
            crossAxisMax = max(
                crossAxisMax,
                child.crossAxisSize(
                    if (weightUnitSpace != Constraints.Infinity) {
                        (weightUnitSpace * weight).roundToInt()
                    } else {
                        Constraints.Infinity
                    }
                )
            )
        }
    }
    return crossAxisMax
}

internal class LayoutWeightImpl(
    val weight: Float,
    val fill: Boolean,
    inspectorInfo: InspectorInfo.() -> Unit
) : ParentDataModifier, InspectorValueInfo(inspectorInfo) {
    override fun Density.modifyParentData(parentData: Any?) =
        ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
            it.weight = weight
            it.fill = fill
        }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        val otherModifier = other as? LayoutWeightImpl ?: return false
        return weight == otherModifier.weight &&
            fill == otherModifier.fill
    }

    override fun hashCode(): Int {
        var result = weight.hashCode()
        result = 31 * result + fill.hashCode()
        return result
    }

    override fun toString(): String =
        "LayoutWeightImpl(weight=$weight, fill=$fill)"
}

internal sealed class SiblingsAlignedModifier(
    inspectorInfo: InspectorInfo.() -> Unit
) : ParentDataModifier, InspectorValueInfo(inspectorInfo) {
    abstract override fun Density.modifyParentData(parentData: Any?): Any?

    internal class WithAlignmentLineBlock(
        val block: (Measured) -> Int,
        inspectorInfo: InspectorInfo.() -> Unit
    ) : SiblingsAlignedModifier(inspectorInfo) {
        override fun Density.modifyParentData(parentData: Any?): Any {
            return ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
                it.crossAxisAlignment =
                    CrossAxisAlignment.Relative(AlignmentLineProvider.Block(block))
            }
        }

        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            val otherModifier = other as? WithAlignmentLineBlock ?: return false
            return block == otherModifier.block
        }

        override fun hashCode(): Int = block.hashCode()

        override fun toString(): String = "WithAlignmentLineBlock(block=$block)"
    }

    internal class WithAlignmentLine(
        val alignmentLine: AlignmentLine,
        inspectorInfo: InspectorInfo.() -> Unit
    ) : SiblingsAlignedModifier(inspectorInfo) {
        override fun Density.modifyParentData(parentData: Any?): Any {
            return ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
                it.crossAxisAlignment =
                    CrossAxisAlignment.Relative(AlignmentLineProvider.Value(alignmentLine))
            }
        }

        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            val otherModifier = other as? WithAlignmentLine ?: return false
            return alignmentLine == otherModifier.alignmentLine
        }

        override fun hashCode(): Int = alignmentLine.hashCode()

        override fun toString(): String = "WithAlignmentLine(line=$alignmentLine)"
    }
}

internal class HorizontalAlignModifier(
    val horizontal: Alignment.Horizontal,
    inspectorInfo: InspectorInfo.() -> Unit
) : ParentDataModifier, InspectorValueInfo(inspectorInfo) {
    override fun Density.modifyParentData(parentData: Any?): RowColumnParentData {
        return ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
            it.crossAxisAlignment = CrossAxisAlignment.horizontal(horizontal)
        }
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        val otherModifier = other as? HorizontalAlignModifier ?: return false
        return horizontal == otherModifier.horizontal
    }

    override fun hashCode(): Int = horizontal.hashCode()

    override fun toString(): String =
        "HorizontalAlignModifier(horizontal=$horizontal)"
}

internal class VerticalAlignModifier(
    val vertical: Alignment.Vertical,
    inspectorInfo: InspectorInfo.() -> Unit
) : ParentDataModifier, InspectorValueInfo(inspectorInfo) {
    override fun Density.modifyParentData(parentData: Any?): RowColumnParentData {
        return ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
            it.crossAxisAlignment = CrossAxisAlignment.vertical(vertical)
        }
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        val otherModifier = other as? VerticalAlignModifier ?: return false
        return vertical == otherModifier.vertical
    }

    override fun hashCode(): Int = vertical.hashCode()

    override fun toString(): String =
        "VerticalAlignModifier(vertical=$vertical)"
}

/**
 * Provides the alignment line.
 */
internal sealed class AlignmentLineProvider {
    abstract fun calculateAlignmentLinePosition(placeable: Placeable): Int
    data class Block(val lineProviderBlock: (Measured) -> Int) : AlignmentLineProvider() {
        override fun calculateAlignmentLinePosition(
            placeable: Placeable
        ): Int {
            return lineProviderBlock(placeable)
        }
    }

    data class Value(val alignmentLine: AlignmentLine) : AlignmentLineProvider() {
        override fun calculateAlignmentLinePosition(placeable: Placeable): Int {
            return placeable[alignmentLine]
        }
    }
}

/**
 * Used to specify how a layout chooses its own size when multiple behaviors are possible.
 */
// TODO(popam): remove this when Flow is reworked
internal enum class SizeMode {
    /**
     * Minimize the amount of free space by wrapping the children,
     * subject to the incoming layout constraints.
     */
    Wrap,

    /**
     * Maximize the amount of free space by expanding to fill the available space,
     * subject to the incoming layout constraints.
     */
    Expand
}


================================================
FILE: adaptive/src/main/java/com/google/accompanist/adaptive/RowColumnMeasurementHelper.kt
================================================
/*
 * 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.adaptive

import androidx.compose.ui.layout.AlignmentLine
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import kotlin.math.sign

/**
 * Copied from:
 * RowColumnMeasurementHelper.kt
 * https://android-review.googlesource.com/c/platform/frameworks/support/+/2260390/27/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
 *
 * The only changes were updating access modifiers and making RowColumnMeasurementHelper an open class
 */

/**
 * This is a data class that holds the determined width, height of a row,
 * and information on how to retrieve main axis and cross axis positions.
 */
internal class RowColumnMeasureHelperResult(
    val crossAxisSize: Int,
    val mainAxisSize: Int,
    val startIndex: Int,
    val endIndex: Int,
    val beforeCrossAxisAlignmentLine: Int,
    val mainAxisPositions: IntArray,
)

/**
 * RowColumnMeasurementHelper
 * Measures the row and column without placing, useful for reusing row/column logic
 */
internal open class RowColumnMeasurementHelper(
    val orientation: LayoutOrientation,
    val arrangement: (Int, IntArray, LayoutDirection, Density, IntArray) -> Unit,
    val arrangementSpacing: Dp,
    val crossAxisSize: SizeMode,
    val crossAxisAlignment: CrossAxisAlignment,
    val measurables: List<Measurable>,
    val placeables: Array<Placeable?>
) {

    private val rowColumnParentData = Array(measurables.size) {
        measurables[it].rowColumnParentData
    }

    fun Placeable.mainAxisSize() =
        if (orientation == LayoutOrientation.Horizontal) width else height

    fun Placeable.crossAxisSize() =
        if (orientation == LayoutOrientation.Horizontal) height else width

    /**
     * Measures the row and column without placing, useful for reusing row/column logic
     *
     * @param measureScope The measure scope to retrieve density
     * @param constraints The desired constraints for the startIndex and endIndex
     * can hold null items if not measured.
     * @param startIndex The startIndex (inclusive) when examining measurables, placeable
     * and parentData
     * @param endIndex The ending index (exclusive) when examinning measurable, placeable
     * and parentData
     */
    fun measureWithoutPlacing(
        measureScope: MeasureScope,
        constraints: Constraints,
        startIndex: Int,
        endIndex: Int
    ): RowColumnMeasureHelperResult {
        @Suppress("NAME_SHADOWING")
        val constraints = OrientationIndependentConstraints(constraints, orientation)
        val arrangementSpacingPx = with(measureScope) {
            arrangementSpacing.roundToPx()
        }

        var totalWeight = 0f
        var fixedSpace = 0
        var crossAxisSpace = 0
        var weightChildrenCount = 0

        var anyAlignBy = false
        val subSize = endIndex - startIndex

        // First measure children with zero weight.
        var spaceAfterLastNoWeight = 0
        for (i in startIndex until endIndex) {
            val child = measurables[i]
            val parentData = rowColumnParentData[i]
            val weight = parentData.weight

            if (weight > 0f) {
                totalWeight += weight
                ++weightChildrenCount
            } else {
                val mainAxisMax = constraints.mainAxisMax
                val placeable = placeables[i] ?: child.measure(
                    // Ask for preferred main axis size.
                    constraints.copy(
                        mainAxisMin = 0,
                        mainAxisMax = if (mainAxisMax == Constraints.Infinity) {
                            Constraints.Infinity
                        } else {
                            mainAxisMax - fixedSpace
                        },
                        crossAxisMin = 0
                    ).toBoxConstraints(orientation)
                )
                spaceAfterLastNoWeight = min(
                    arrangementSpacingPx,
                    mainAxisMax - fixedSpace - placeable.mainAxisSize()
                )
                fixedSpace += placeable.mainAxisSize() + spaceAfterLastNoWeight
                crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
                anyAlignBy = anyAlignBy || parentData.isRelative
                placeables[i] = placeable
            }
        }

        var weightedSpace = 0
        if (weightChildrenCount == 0) {
            // fixedSpace contains an extra spacing after the last non-weight child.
            fixedSpace -= spaceAfterLastNoWeight
        } else {
            // Measure the rest according to their weights in the remaining main axis space.
            val targetSpace =
                if (totalWeight > 0f && constraints.mainAxisMax != Constraints.Infinity) {
                    constraints.mainAxisMax
                } else {
                    constraints.mainAxisMin
                }
            val remainingToTarget =
                targetSpace - fixedSpace - arrangementSpacingPx * (weightChildrenCount - 1)

            val weightUnitSpace = if (totalWeight > 0) remainingToTarget / totalWeight else 0f
            var remainder = remainingToTarget - (startIndex until endIndex).sumOf {
                (weightUnitSpace * rowColumnParentData[it].weight).roundToInt()
            }

            for (i in startIndex until endIndex) {
                if (placeables[i] == null) {
                    val child = measurables[i]
                    val parentData = rowColumnParentData[i]
                    val weight = parentData.weight
                    check(weight > 0) { "All weights <= 0 should have placeables" }
                    // After the weightUnitSpace rounding, the total space going to be occupied
                    // can be smaller or larger than remainingToTarget. Here we distribute the
                    // loss or gain remainder evenly to the first children.
                    val remainderUnit = remainder.sign
                    remainder -= remainderUnit
                    val childMainAxisSize = max(
                        0,
                        (weightUnitSpace * weight).roundToInt() + remainderUnit
                    )
                    val placeable = child.measure(
                        OrientationIndependentConstraints(
                            if (parentData.fill && childMainAxisSize != Constraints.Infinity) {
                                childMainAxisSize
                            } else {
                                0
                            },
                            childMainAxisSize,
                            0,
                            constraints.crossAxisMax
                        ).toBoxConstraints(orientation)
                    )
                    weightedSpace += placeable.mainAxisSize()
                    crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
                    anyAlignBy = anyAlignBy || parentData.isRelative
                    placeables[i] = placeable
                }
            }
            weightedSpace = (weightedSpace + arrangementSpacingPx * (weightChildrenCount - 1))
                .coerceAtMost(constraints.mainAxisMax - fixedSpace)
        }

        var beforeCrossAxisAlignmentLine = 0
        var afterCrossAxisAlignmentLine = 0
        if (anyAlignBy) {
            for (i in startIndex until endIndex) {
                val placeable = placeables[i]!!
                val parentData = rowColumnParentData[i]
                val alignmentLinePosition = parentData.crossAxisAlignment
                    ?.calculateAlignmentLinePosition(placeable)
                if (alignmentLinePosition != null) {
                    beforeCrossAxisAlignmentLine = max(
                        beforeCrossAxisAlignmentLine,
                        alignmentLinePosition.let {
                            if (it != AlignmentLine.Unspecified) it else 0
                        }
                    )
                    afterCrossAxisAlignmentLine = max(
                        afterCrossAxisAlignmentLine,
                        placeable.crossAxisSize() -
                            (
                                alignmentLinePosition.let {
                                    if (it != AlignmentLine.Unspecified) {
                                        it
                                    } else {
                                        placeable.crossAxisSize()
                                    }
                                }
                                )
                    )
                }
            }
        }

        // Compute the Row or Column size and position the children.
        val mainAxisLayoutSize = max(fixedSpace + weightedSpace, constraints.mainAxisMin)
        val crossAxisLayoutSize = if (constraints.crossAxisMax != Constraints.Infinity &&
            crossAxisSize == SizeMode.Expand
        ) {
            constraints.crossAxisMax
        } else {
            max(
                crossAxisSpace,
                max(
                    constraints.crossAxisMin,
                    beforeCrossAxisAlignmentLine + afterCrossAxisAlignmentLine
                )
            )
        }
        val mainAxisPositions = IntArray(subSize) { 0 }
        val childrenMainAxisSize = IntArray(subSize) { index ->
            placeables[index + startIndex]!!.mainAxisSize()
        }

        return RowColumnMeasureHelperResult(
            mainAxisSize = mainAxisLayoutSize,
            crossAxisSize = crossAxisLayoutSize,
            startIndex = startIndex,
            endIndex = endIndex,
            beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine,
            mainAxisPositions = mainAxisPositions(
                mainAxisLayoutSize,
                childrenMainAxisSize,
                mainAxisPositions,
                measureScope
            )
        )
    }

    private fun mainAxisPositions(
        mainAxisLayoutSize: Int,
        childrenMainAxisSize: IntArray,
        mainAxisPositions: IntArray,
        measureScope: MeasureScope
    ): IntArray {
        arrangement(
            mainAxisLayoutSize,
            childrenMainAxisSize,
            measureScope.layoutDirection,
            measureScope,
            mainAxisPositions
        )
        return mainAxisPositions
    }

    protected fun getCrossAxisPosition(
        placeable: Placeable,
        parentData: RowColumnParentData?,
        crossAxisLayoutSize: Int,
        layoutDirection: LayoutDirection,
        beforeCrossAxisAlignmentLine: Int
    ): Int {
        val childCrossAlignment = parentData?.crossAxisAlignment ?: crossAxisAlignment
        return childCrossAlignment.align(
            size = crossAxisLayoutSize - placeable.crossAxisSize(),
            layoutDirection = if (orientation == LayoutOrientation.Horizontal) {
                LayoutDirection.Ltr
            } else {
                layoutDirection
            },
            placeable = placeable,
            beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine
        )
    }

    fun placeHelper(
        placeableScope: Placeable.PlacementScope,
        measureResult: RowColumnMeasureHelperResult,
        crossAxisOffset: Int,
        layoutDirection: LayoutDirection,
    ) {
        with(placeableScope) {
            for (i in measureResult.startIndex until measureResult.endIndex) {
                val placeable = placeables[i]
                placeable!!
                val mainAxisPositions = measureResult.mainAxisPositions
                val crossAxisPosition = getCrossAxisPosition(
                    placeable,
                    (measurables[i].parentData as? RowColumnParentData),
                    measureResult.crossAxisSize,
                    layoutDirection,
                    measureResult.beforeCrossAxisAlignmentLine
                ) + crossAxisOffset
                if (orientation == LayoutOrientation.Horizontal) {
                    placeable.place(
                        mainAxisPositions[i - measureResult.startIndex],
                        crossAxisPosition
                    )
                } else {
                    placeable.place(
                        crossAxisPosition,
                        mainAxisPositions[i - measureResult.startIndex]
                    )
                }
            }
        }
    }
}


================================================
FILE: adaptive/src/main/java/com/google/accompanist/adaptive/TwoPane.kt
================================================
/*
 * 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.
 */

package com.google.accompanist.adaptive

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.toComposeRect
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.constrain
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import androidx.compose.ui.unit.dp
import androidx.window.layout.DisplayFeature
import androidx.window.layout.FoldingFeature
import kotlin.math.roundToInt

/**
 * A layout that places two different pieces of content defined by the [first] and [second]
 * slots where the arrangement, sizes and separation behaviour is controlled by [TwoPaneStrategy].
 *
 * [TwoPane] is fold and hinges aware using the provided the [displayFeatures] (which should
 * normally be calculated via [calculateDisplayFeatures]). The layout will be adapted to properly
 * separate [first] and [second] panes so they don't interfere with hardware hinges (vertical or
 * horizontal) as specified in [displayFeatures], or respect folds when needed (for example, when
 * foldable is half-folded (90-degree fold AKA tabletop) the split will become on the bend).
 *
 * To only be aware of folds with a specific orientation, pass in an alternate
 * [foldAwareConfiguration] to only adjust for vertical or horizontal folds.
 *
 * The [TwoPane] layout will always place both [first] and [second], based on the provided
 * [strategy] and window environment. If you instead only want to place one or the other,
 * that should be controlled at a higher level and not calling [TwoPane] if placing both is not
 * desired.
 *
 * @param first the first content of the layout, a left-most in LTR, a right-most in RTL and
 * top-most in a vertical split based on the [SplitResult] of [TwoPaneStrategy.calculateSplitResult]
 * @param second the second content of the layout, a right-most in the LTR, a left-most in the RTL
 * and the bottom-most in a vertical split based on the [SplitResult] of
 * [TwoPaneStrategy.calculateSplitResult]
 * @param strategy strategy of the two pane that controls the arrangement of the layout
 * @param displayFeatures the list of known display features to automatically avoid
 * @param foldAwareConfiguration the types of display features to automatically avoid
 * @param modifier an optional modifier for the layout
 */
@Composable
public fun TwoPane(
    first: @Composable () -> Unit,
    second: @Composable () -> Unit,
    strategy: TwoPaneStrategy,
    displayFeatures: List<DisplayFeature>,
    modifier: Modifier = Modifier,
    foldAwareConfiguration: FoldAwareConfiguration = FoldAwareConfiguration.AllFolds,
) {
    TwoPane(
        first = first,
        second = second,
        strategy = when (foldAwareConfiguration) {
            FoldAwareConfiguration.HorizontalFoldsOnly -> {
                VerticalTwoPaneStrategy(
                    displayFeatures = displayFeatures,
                    defaultStrategy = strategy,
                )
            }
            FoldAwareConfiguration.VerticalFoldsOnly -> {
                HorizontalTwoPaneStrategy(
                    displayFeatures = displayFeatures,
                    defaultStrategy = strategy,
                )
            }
            FoldAwareConfiguration.AllFolds -> {
                TwoPaneStrategy(
                    displayFeatures = displayFeatures,
                    defaultStrategy = strategy,
                )
            }
            else -> error("Unknown FoldAware value!")
        },
        modifier = modifier,
    )
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
internal fun TwoPane(
    first: @Composable () -> Unit,
    second: @Composable () -> Unit,
    strategy: TwoPaneStrategy,
    modifier: Modifier = Modifier
) {
    val density = LocalDensity.current
    Layout(
        modifier = modifier.wrapContentSize(),
        content = {
            Box(Modifier.layoutId("first")) {
                first()
            }
            Box(Modifier.layoutId("second")) {
                second()
            }
        }
    ) { measurable, constraints ->
        val firstMeasurable = measurable.find { it.layoutId == "first" }!!
        val secondMeasurable = measurable.find { it.layoutId == "second" }!!

        layout(constraints.maxWidth, constraints.maxHeight) {
            val splitResult = strategy.calculateSplitResult(
                density = density,
                layoutDirection = layoutDirection,
                layoutCoordinates = coordinates ?: return@layout
            )

            val gapOrientation = splitResult.gapOrientation
            val gapBounds = splitResult.gapBounds

            val gapLeft = constraints.constrainWidth(gapBounds.left.roundToInt())
            val gapRight = constraints.constrainWidth(gapBounds.right.roundToInt())
            val gapTop = constraints.constrainHeight(gapBounds.top.roundToInt())
            val gapBottom = constraints.constrainHeight(gapBounds.bottom.roundToInt())
            val firstConstraints =
                if (gapOrientation == Orientation.Vertical) {
                    val width = when (layoutDirection) {
                        LayoutDirection.Ltr -> gapLeft
                        LayoutDirection.Rtl -> constraints.maxWidth - gapRight
                    }

                    constraints.copy(minWidth = width, maxWidth = width)
                } else {
                    constraints.copy(minHeight = gapTop, maxHeight = gapTop)
                }
            val secondConstraints =
                if (gapOrientation == Orientation.Vertical) {
                    val width = when (layoutDirection) {
                        LayoutDirection.Ltr -> constraints.maxWidth - gapRight
                        LayoutDirection.Rtl -> gapLeft
                    }
                    constraints.copy(minWidth = width, maxWidth = width)
                } else {
                    val height = constraints.maxHeight - gapBottom
                    constraints.copy(minHeight = height, maxHeight = height)
                }
            val firstPlaceable = firstMeasurable.measure(constraints.constrain(firstConstraints))
            val secondPlaceable = secondMeasurable.measure(constraints.constrain(secondConstraints))

            firstPlaceable.placeRelative(0, 0)
            val detailOffsetX =
                if (gapOrientation == Orientation.Vertical) {
                    constraints.maxWidth - secondPlaceable.width
                } else {
                    0
                }
            val detailOffsetY =
                if (gapOrientation == Orientation.Vertical) {
                    0
                } else {
                    constraints.maxHeight - secondPlaceable.height
                }
            secondPlaceable.placeRelative(detailOffsetX, detailOffsetY)
        }
    }
}

/**
 * The configuration for which type of folds for a [TwoPane] to automatically avoid.
 */
@JvmInline
public value class FoldAwareConfiguration private constructor(private val value: Int) {

    public companion object {
        /**
         * The [TwoPane] will only be aware of horizontal folds only, splitting the content
         * vertically.
         */
        public val HorizontalFoldsOnly: FoldAwareConfiguration = FoldAwareConfiguration(0)

        /**
         * The [TwoPane] will only be aware of vertical folds only, splitting the content
         * horizontally.
         */
        public val VerticalFoldsOnly: FoldAwareConfiguration = FoldAwareConfiguration(1)

        /**
         * The [TwoPane] will be aware of both horizontal and vertical folds, splitting the content
         * vertically and horizontally respectively.
         */
        public val AllFolds: FoldAwareConfiguration = FoldAwareConfiguration(2)
    }
}

/**
 * Returns the specification for where to place a split in [TwoPane] as a result of
 * [TwoPaneStrategy.calculateSplitResult]
 */
public class SplitResult(

    /**
     * Whether the gap is vertical or horizontal
     */
    public val gapOrientation: Orientation,

    /**
     * The bounds that are nether a `start` pane or an `end` pane, but a separation between those
     * two. In case width or height is 0 - it means that the gap itself is a 0 width/height, but the
     * place within the layout is still defined.
     *
     * The [gapBounds] should be defined in local bounds to the [TwoPane].
     */
    public val gapBounds: Rect,
)

/**
 * A strategy for configuring the [TwoPane] component, that is responsible for the meta-data
 * corresponding to the arrangement of the two panes of the layout.
 */
public fun interface TwoPaneStrategy {
    /**
     * Calculates the split result in local bounds of the [TwoPane].
     *
     * @param density the [Density] for measuring and laying out
     * @param layoutDirection the [LayoutDirection] for measuring and laying out
     * @param layoutCoordinates the [LayoutCoordinates] of the [TwoPane]
     */
    public fun calculateSplitResult(
        density: Density,
        layoutDirection: LayoutDirection,
        layoutCoordinates: LayoutCoordinates
    ): SplitResult
}

/**
 * A strategy for configuring the [TwoPane] component, that is responsible for the meta-data
 * corresponding to the arrangement of the two panes of the layout.
 *
 * This strategy can be conditional: If `null` is returned from [calculateSplitResult], then this
 * strategy did not produce a split result to use, and a different strategy should be used.
 */
private fun interface ConditionalTwoPaneStrategy {
    /**
     * Calculates the split result in local bounds of the [TwoPane], or `null` if this strategy
     * does not apply.
     *
     * @param density the [Density] for measuring and laying out
     * @param layoutDirection the [LayoutDirection] for measuring and laying out
     * @param layoutCoordinates the [LayoutCoordinates] of the [TwoPane]
     */
    fun calculateSplitResult(
        density: Density,
        layoutDirection: LayoutDirection,
        layoutCoordinates: LayoutCoordinates
    ): SplitResult?
}

/**
 * Returns a [TwoPaneStrategy] that will place the slots horizontally.
 *
 * The gap will be placed at the given [splitFraction] from start, with the given
 * [gapWidth].
 */
public fun HorizontalTwoPaneStrategy(
    splitFraction: Float,
    gapWidth: Dp = 0.dp,
): TwoPaneStrategy = FractionHorizontalTwoPaneStrategy(
    splitFraction = splitFraction,
    gapWidth = gapWidth
)

/**
 * Returns a [TwoPaneStrategy] that will place the slots horizontally.
 *
 * The gap will be placed at [splitOffset] either from the start or end based on
 * [offsetFromStart], with the given [gapWidth].
 */
public fun HorizontalTwoPaneStrategy(
    splitOffset: Dp,
    offsetFromStart: Boolean = true,
    gapWidth: Dp = 0.dp,
): TwoPaneStrategy = FixedOffsetHorizontalTwoPaneStrategy(
    splitOffset = splitOffset,
    offsetFromStart = offsetFromStart,
    gapWidth = gapWidth
)

/**
 * Returns a [TwoPaneStrategy] that will place the slots horizontally.
 *
 * The gap will be placed at the given [splitFraction] from top, with the given
 * [gapHeight].
 */
public fun VerticalTwoPaneStrategy(
    splitFraction: Float,
    gapHeight: Dp = 0.dp,
): TwoPaneStrategy = FractionVerticalTwoPaneStrategy(
    splitFraction = splitFraction,
    gapHeight = gapHeight
)

/**
 * Returns a [TwoPaneStrategy] that will place the slots horizontally.
 *
 * The gap will be placed at [splitOffset] either from the top or bottom based on
 * [offsetFromTop], with the given [gapHeight].
 */
public fun VerticalTwoPaneStrategy(
    splitOffset: Dp,
    offsetFromTop: Boolean = true,
    gapHeight: Dp = 0.dp,
): TwoPaneStrategy = FixedOffsetVerticalTwoPaneStrategy(
    splitOffset = splitOffset,
    offsetFromTop = offsetFromTop,
    gapHeight = gapHeight
)

/**
 * Returns a [TwoPaneStrategy] that will place the slots vertically or horizontally if there is a
 * horizontal or vertical fold respectively.
 *
 * If there is no fold, then the [defaultStrategy] will be used instead.
 */
private fun TwoPaneStrategy(
    displayFeatures: List<DisplayFeature>,
    defaultStrategy: TwoPaneStrategy,
): TwoPaneStrategy = TwoPaneStrategy(
    FoldAwareHorizontalTwoPaneStrategy(displayFeatures),
    FoldAwareVerticalTwoPaneStrategy(displayFeatures),
    defaultStrategy = defaultStrategy
)

/**
 * Returns a [TwoPaneStrategy] that will place the slots horizontally if there is a vertical fold.
 *
 * If there is no fold, then the [defaultStrategy] will be used instead.
 */
private fun HorizontalTwoPaneStrategy(
    displayFeatures: List<DisplayFeature>,
    defaultStrategy: TwoPaneStrategy,
): TwoPaneStrategy = TwoPaneStrategy(
    FoldAwareHorizontalTwoPaneStrategy(displayFeatures),
    defaultStrategy = defaultStrategy
)

/**
 * Returns a [TwoPaneStrategy] that will place the slots vertically if there is a horizontal fold.
 *
 * If there is no fold, then the [defaultStrategy] will be used instead.
 */
private fun VerticalTwoPaneStrategy(
    displayFeatures: List<DisplayFeature>,
    defaultStrategy: TwoPaneStrategy,
): TwoPaneStrategy = TwoPaneStrategy(
    FoldAwareVerticalTwoPaneStrategy(displayFeatures),
    defaultStrategy = defaultStrategy
)

/**
 * Returns a composite [TwoPaneStrategy].
 *
 * The conditional strategies (if any) will be attempted in order, and their split result used
 * if they return one. If none return a split result, then the [defaultStrategy] will be used,
 * which guarantees returning a [SplitResult].
 */
private fun TwoPaneStrategy(
    vararg conditionalStrategies: ConditionalTwoPaneStrategy,
    defaultStrategy: TwoPaneStrategy
): TwoPaneStrategy = TwoPaneStrategy { density, layoutDirection, layoutCoordinates ->
    conditionalStrategies.firstNotNullOfOrNull { conditionalTwoPaneStrategy ->
        conditionalTwoPaneStrategy.calculateSplitResult(
            density = density,
            layoutDirection = layoutDirection,
            layoutCoordinates = layoutCoordinates
        )
    } ?: defaultStrategy.calculateSplitResult(
        density = density,
        layoutDirection = layoutDirection,
        layoutCoordinates = layoutCoordinates
    )
}

/**
 * Returns a [ConditionalTwoPaneStrategy] that will place the slots horizontally if there is a
 * vertical fold, or `null` if there is no fold.
 */
private fun FoldAwareHorizontalTwoPaneStrategy(
    displayFeatures: List<DisplayFeature>,
): ConditionalTwoPaneStrategy = ConditionalTwoPaneStrategy { _, _, layoutCoordinates ->
    val verticalFold = displayFeatures.find {
        it is FoldingFeature && it.orientation == FoldingFeature.Orientation.VERTICAL
    } as FoldingFeature?

    if (verticalFold != null &&
        (
            verticalFold.isSeparating ||
                verticalFold.occlusionType == FoldingFeature.OcclusionType.FULL
            ) &&
        verticalFold.bounds.toComposeRect().overlaps(layoutCoordinates.boundsInWindow())
    ) {
        val foldBounds = verticalFold.bounds.toComposeRect()
        SplitResult(
            gapOrientation = Orientation.Vertical,
            gapBounds = Rect(
                layoutCoordinates.windowToLocal(foldBounds.topLeft),
                layoutCoordinates.windowToLocal(foldBounds.bottomRight)
            )
        )
    } else {
        null
    }
}

/**
 * Returns a [ConditionalTwoPaneStrategy] that will place the slots vertically if there is a
 * horizontal fold, or `null` if there is no fold.
 */
private fun FoldAwareVerticalTwoPaneStrategy(
    displayFeatures: List<DisplayFeature>,
): ConditionalTwoPaneStrategy = ConditionalTwoPaneStrategy { _, _, layoutCoordinates ->
    val horizontalFold = displayFeatures.find {
        it is FoldingFeature && it.orientation == FoldingFeature.Orientation.HORIZONTAL
    } as FoldingFeature?

    if (horizontalFold != null &&
        (
            horizontalFold.isSeparating ||
                horizontalFold.occlusionType == FoldingFeature.OcclusionType.FULL
            ) &&
        horizontalFold.bounds.toComposeRect().overlaps(layoutCoordinates.boundsInWindow())
    ) {
        val foldBounds = horizontalFold.bounds.toComposeRect()
        SplitResult(
            gapOrientation = Orientation.Horizontal,
            gapBounds = Rect(
                layoutCoordinates.windowToLocal(foldBounds.topLeft),
                layoutCoordinates.windowToLocal(foldBounds.bottomRight)
            )
        )
    } else {
        null
    }
}

/**
 * Returns a [TwoPaneStrategy] that will place the slots horizontally.
 *
 * The gap will be placed at the given [splitFraction] from start, with the given [gapWidth].
 *
 * This strategy is _not_ fold aware.
 */
internal fun FractionHorizontalTwoPaneStrategy(
    splitFraction: Float,
    gapWidth: Dp = 0.dp,
): TwoPaneStrategy = TwoPaneStrategy { density, layoutDirection, layoutCoordinates ->
    val splitX = layoutCoordinates.size.width * when (layoutDirection) {
        LayoutDirection.Ltr -> splitFraction
        LayoutDirection.Rtl -> 1 - splitFraction
    }
    val splitWidthPixel = with(density) { gapWidth.toPx() }

    SplitResult(
        gapOrientation = Orientation.Vertical,
        gapBounds = Rect(
            left = splitX - splitWidthPixel / 2f,
            top = 0f,
            right = splitX + splitWidthPixel / 2f,
            bottom = layoutCoordinates.size.height.toFloat(),
        )
    )
}

/**
 * Returns a [TwoPaneStrategy] that will place the slots horizontally.
 *
 * The gap will be placed at [splitOffset] either from the start or end based on
 * [offsetFromStart], with the given [gapWidth].
 *
 * This strategy is _not_ fold aware.
 */
internal fun FixedOffsetHorizontalTwoPaneStrategy(
    splitOffset: Dp,
    offsetFromStart: Boolean,
    gapWidth: Dp = 0.dp,
): TwoPaneStrategy = TwoPaneStrategy { density, layoutDirection, layoutCoordinates ->
    val splitOffsetPixel = with(density) { splitOffset.toPx() }
    val splitX = when (layoutDirection) {
        LayoutDirection.Ltr ->
            if (offsetFromStart) {
                splitOffsetPixel
            } else {
                layoutCoordinates.size.width - splitOffsetPixel
            }
        LayoutDirection.Rtl ->
            if (offsetFromStart) {
                layoutCoordinates.size.width - splitOffsetPixel
            } else {
                splitOffsetPixel
            }
    }
    val splitWidthPixel = with(density) { gapWidth.toPx() }

    SplitResult(
        gapOrientation = Orientation.Vertical,
        gapBounds = Rect(
            left = splitX - splitWidthPixel / 2f,
            top = 0f,
            right = splitX + splitWidthPixel / 2f,
            bottom = layoutCoordinates.size.height.toFloat(),
        )
    )
}

/**
 * Returns a [TwoPaneStrategy] that will place the slots horizontally.
 *
 * The split will be placed at the given [splitFraction] from start, with the given [gapHeight].
 *
 * This strategy is _not_ fold aware.
 */
internal fun FractionVerticalTwoPaneStrategy(
    splitFraction: Float,
    gapHeight: Dp = 0.dp,
): TwoPaneStrategy = TwoPaneStrategy { density, _, layoutCoordinates ->
    val splitY = layoutCoordinates.size.height * splitFraction
    val splitHeightPixel = with(density) { gapHeight.toPx() }

    SplitResult(
        gapOrientation = Orientation.Horizontal,
        gapBounds = Rect(
            left = 0f,
            top = splitY - splitHeightPixel / 2f,
            right = layoutCoordinates.size.width.toFloat(),
            bottom = splitY + splitHeightPixel / 2f,
        )
    )
}

/**
 * Returns a [TwoPaneStrategy] that will place the slots horizontally.
 *
 * The split will be placed at [splitOffset] either from the top or bottom based on
 * [offsetFromTop], with the given [gapHeight].
 *
 * This strategy is _not_ fold aware.
 */
internal fun FixedOffsetVerticalTwoPaneStrategy(
    splitOffset: Dp,
    offsetFromTop: Boolean,
    gapHeight: Dp = 0.dp,
): TwoPaneStrategy = TwoPaneStrategy { density, _, layoutCoordinates ->
    val splitOffsetPixel = with(density) { splitOffset.toPx() }
    val splitY =
        if (offsetFromTop) {
            splitOffsetPixel
        } else {
            layoutCoordinates.size.height - splitOffsetPixel
        }
    val splitHeightPixel = with(density) { gapHeight.toPx() }

    SplitResult(
        gapOrientation = Orientation.Horizontal,
        gapBounds = Rect(
            left = 0f,
            top = splitY - splitHeightPixel / 2f,
            right = layoutCoordinates.size.width.toFloat(),
            bottom = splitY + splitHeightPixel / 2f,
        )
    )
}


================================================
FILE: adaptive/src/sharedTest/kotlin/com/google/accompanist/adaptive/DisplayFeaturesTest.kt
================================================
/*
 * 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.
 */

package com.google.accompanist.adaptive

import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.window.layout.DisplayFeature
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowLayoutInfo
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class DisplayFeaturesTest {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @get:Rule
    val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()

    @Test
    fun empty_folding_features_is_correct() {
        lateinit var displayFeatures: List<DisplayFeature>

        composeTestRule.setContent {
            displayFeatures = calculateDisplayFeatures(activity = composeTestRule.activity)
        }

        windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(WindowLayoutInfo(emptyList()))

        composeTestRule.waitForIdle()

        assertThat(displayFeatures).isEmpty()
    }

    @Test
    fun single_folding_features_is_correct() {
        lateinit var displayFeatures: List<DisplayFeature>

        composeTestRule.setContent {
            displayFeatures = calculateDisplayFeatures(activity = composeTestRule.activity)
        }

        val fakeFoldingFeature = FoldingFeature(
            activity = composeTestRule.activity,
            center = 200,
            size = 40,
            state = FoldingFeature.State.HALF_OPENED,
            orientation = FoldingFeature.Orientation.VERTICAL,
        )

        windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(
            WindowLayoutInfo(
                listOf(
                    fakeFoldingFeature
                )
            )
        )

        composeTestRule.waitForIdle()

        assertThat(displayFeatures).hasSize(1)
        assertThat(displayFeatures[0]).isEqualTo(fakeFoldingFeature)
    }

    @Test
    fun updating_folding_features_is_correct() {
        lateinit var displayFeatures: List<DisplayFeature>

        composeTestRule.setContent {
            displayFeatures = calculateDisplayFeatures(activity = composeTestRule.activity)
        }

        windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(WindowLayoutInfo(emptyList()))

        val fakeFoldingFeature = FoldingFeature(
            activity = composeTestRule.activity,
            center = 200,
            size = 40,
            state = FoldingFeature.State.HALF_OPENED,
            orientation = FoldingFeature.Orientation.VERTICAL,
        )

        windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(
            WindowLayoutInfo(
                listOf(
                    fakeFoldingFeature
                )
            )
        )

        composeTestRule.waitForIdle()

        assertThat(displayFeatures).hasSize(1)
        assertThat(displayFeatures[0]).isEqualTo(fakeFoldingFeature)
    }
}


================================================
FILE: adaptive/src/sharedTest/kotlin/com/google/accompanist/adaptive/FoldAwareColumnTest.kt
================================================
/*
 * 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.adaptive

import android.annotation.SuppressLint
import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.toComposeRect
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowLayoutInfo
import androidx.window.layout.WindowMetricsCalculator
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import com.google.accompanist.adaptive.FoldAwareColumnScopeInstance.ignoreFold
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class FoldAwareColumnTest {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @get:Rule
    val publisherRule = WindowLayoutInfoPublisherRule()

    private val testTag = "FoldAwareColumnTestTag"
    private var firstSpacerHeightDp = 0.dp
    private var secondSpacerTopPx = 0f
    private var secondSpacerBottomPx = 0f

    @After
    fun cleanUp() {
        firstSpacerHeightDp = 0.dp
        secondSpacerTopPx = 0f
        secondSpacerBottomPx = 0f
    }

    @Test
    fun second_spacer_placed_below_fold_with_hinge() {
        composeTestRule.setContent {
            FoldAwareColumnWithSpacers()
        }

        val foldBoundsPx = simulateFoldingFeature()

        assertEquals(foldBoundsPx.bottom, secondSpacerTopPx)
    }

    @Test
    fun second_spacer_placed_below_fold_with_separating_fold() {
        composeTestRule.setContent {
            FoldAwareColumnWithSpacers()
        }

        val foldBoundsPx = simulateFoldingFeature(foldSizePx = 0)

        assertEquals(foldBoundsPx.bottom, secondSpacerTopPx)
    }

    @Test
    fun second_spacer_placed_below_first_spacer_without_fold() {
        composeTestRule.setContent {
            FoldAwareColumnWithSpacers()
        }

        composeTestRule.onNodeWithTag(testTag).assertTopPositionInRootIsEqualTo(firstSpacerHeightDp)
    }

    @Test
    fun second_spacer_placed_below_first_spacer_with_non_separating_fold() {
        composeTestRule.setContent {
            FoldAwareColumnWithSpacers()
        }

        simulateFoldingFeature(foldSizePx = 0, foldState = FoldingFeature.State.FLAT)

        composeTestRule.onNodeWithTag(testTag).assertTopPositionInRootIsEqualTo(firstSpacerHeightDp)
    }

    @Test
    fun second_spacer_placed_below_first_spacer_with_vertical_hinge() {
        composeTestRule.setContent {
            FoldAwareColumnWithSpacers()
        }

        simulateFoldingFeature(foldOrientation = FoldingFeature.Orientation.VERTICAL)

        composeTestRule.onNodeWithTag(testTag).assertTopPositionInRootIsEqualTo(firstSpacerHeightDp)
    }

    @Test
    fun second_spacer_placed_below_first_spacer_with_ignore_fold_modifier() {
        composeTestRule.setContent {
            FoldAwareColumnWithSpacers(secondSpacerModifier = Modifier.ignoreFold())
        }

        simulateFoldingFeature()

        composeTestRule.onNodeWithTag(testTag).assertTopPositionInRootIsEqualTo(firstSpacerHeightDp)
    }

    @Test
    fun even_fold_padding_modifier_applies_around_hinge() {
        val foldPaddingDp = 20.dp
        lateinit var density: Density

        composeTestRule.setContent {
            density = LocalDensity.current

            FoldAwareColumnWithSpacers(
                foldPadding = PaddingValues(vertical = foldPaddingDp)
            )
        }

        val foldBoundsPx = simulateFoldingFeature()

        with(density) {
            assertEquals(foldBoundsPx.bottom + foldPaddingDp.roundToPx(), secondSpacerTopPx)
        }
    }

    @Test
    fun uneven_fold_padding_modifier_applies_around_hinge() {
        val foldPaddingBottom = 40.dp
        lateinit var density: Density

        composeTestRule.setContent {
            density = LocalDensity.current

            FoldAwareColumnWithSpacers(
                foldPadding = PaddingValues(top = 15.dp, bottom = foldPaddingBottom)
            )
        }

        val foldBoundsPx = simulateFoldingFeature()

        with(density) {
            assertEquals(foldBoundsPx.bottom + foldPaddingBottom.roundToPx(), secondSpacerTopPx)
        }
    }

    @Test
    fun layout_bounds_align_with_child_bounds_without_separating_fold() {
        composeTestRule.setContent {
            FoldAwareColumnWithSpacers()
        }

        val layoutBottomPx = composeTestRule.onRoot()
            .fetchSemanticsNode().layoutInfo.coordinates.trueBoundsInWindow().bottom

        assertEquals(layoutBottomPx, secondSpacerBottomPx)
    }

    @Test
    fun layout_bounds_contain_child_bounds_when_placed_above_hinge() {
        composeTestRule.setContent {
            FoldAwareColumnWithSpacers(
                firstSpacerHeightPct = 0.1f,
                secondSpacerHeightPct = 0.1f
            )
        }

        simulateFoldingFeature()

        val layoutBottomPx = composeTestRule.onRoot()
            .fetchSemanticsNode().layoutInfo.coordinates.trueBoundsInWindow().bottom

        assert(secondSpacerBottomPx <= layoutBottomPx)
    }

    @Test
    fun layout_bounds_contain_child_bounds_when_placed_below_hinge() {
        composeTestRule.setContent {
            FoldAwareColumnWithSpacers()
        }

        simulateFoldingFeature()

        val layoutBottomPx = composeTestRule.onRoot()
            .fetchSemanticsNode().layoutInfo.coordinates.trueBoundsInWindow().bottom

        assert(secondSpacerBottomPx <= layoutBottomPx)
    }

    /**
     * Test layout for FoldAwareColumn that includes two spacers with the provided heights
     */
    @Composable
    @SuppressLint("ModifierParameter")
    private fun FoldAwareColumnWithSpacers(
        foldPadding: PaddingValues = PaddingValues(),
        firstSpacerHeightPct: Float = 0.25f,
        secondSpacerHeightPct: Float = 0.25f,
        secondSpacerModifier: Modifier = Modifier,
    ) {
        var secondSpacerHeightDp: Dp
        val metrics = remember(LocalConfiguration.current) {
            WindowMetricsCalculator.getOrCreate()
                .computeCurrentWindowMetrics(composeTestRule.activity)
        }

        with(LocalDensity.current) {
            val windowHeight = metrics.bounds.height().toDp().value
            firstSpacerHeightDp = (firstSpacerHeightPct * windowHeight).dp
            secondSpacerHeightDp = (secondSpacerHeightPct * windowHeight).dp
        }

        FoldAwareColumn(
            displayFeatures = calculateDisplayFeatures(activity = composeTestRule.activity),
            foldPadding = foldPadding,
        ) {
            Spacer(
                modifier = Modifier.height(firstSpacerHeightDp)
            )
            Spacer(
                modifier = secondSpacerModifier
                    .height(secondSpacerHeightDp)
                    .testTag(testTag)
                    .onGloballyPositioned {
                        secondSpacerTopPx = it.positionInWindow().y
                        secondSpacerBottomPx = secondSpacerTopPx + it.size.height
                    }
            )
        }
    }

    /**
     * Simulates a Jetpack Window Manager folding feature with the provided properties and returns
     * the bounding box of the fold
     */
    private fun simulateFoldingFeature(
        foldSizePx: Int = 25,
        foldState: FoldingFeature.State = FoldingFeature.State.HALF_OPENED,
        foldOrientation: FoldingFeature.Orientation = FoldingFeature.Orientation.HORIZONTAL
    ): Rect {
        val fakeFoldingFeature = FoldingFeature(
            activity = composeTestRule.activity,
            size = foldSizePx,
            state = foldState,
            orientation = foldOrientation,
        )

        publisherRule.overrideWindowLayoutInfo(WindowLayoutInfo(listOf(fakeFoldingFeature)))

        composeTestRule.waitForIdle()

        return fakeFoldingFeature.bounds.toComposeRect()
    }
}


================================================
FILE: adaptive/src/sharedTest/kotlin/com/google/accompanist/adaptive/TwoPaneTest.kt
================================================
/*
 * 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.
 */

package com.google.accompanist.adaptive

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
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.layout.LayoutCoordinates
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.test.DeviceConfigurationOverride
import androidx.compose.ui.test.ForcedSize
import androidx.compose.ui.test.LayoutDirection
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.then
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpRect
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toIntRect
import androidx.compose.ui.unit.toOffset
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.window.core.ExperimentalWindowApi
import androidx.window.layout.DisplayFeature
import androidx.window.layout.FoldingFeature
import androidx.window.testing.layout.FoldingFeature
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.math.roundToInt

@RunWith(AndroidJUnit4::class)
class TwoPaneTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun fraction_horizontal_renders_correctly_ltr() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp)) then
                    DeviceConfigurationOverride.LayoutDirection(LayoutDirection.Ltr)
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FractionHorizontalTwoPaneStrategy(
                        splitFraction = 1f / 3f
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(300.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(300.dp, 0.dp),
                    DpSize(600.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fraction_horizontal_renders_correctly_rtl() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp)) then
                    DeviceConfigurationOverride.LayoutDirection(LayoutDirection.Rtl)
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FractionHorizontalTwoPaneStrategy(
                        splitFraction = 1f / 3f
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(600.dp, 0.dp),
                    DpSize(300.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(600.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fraction_horizontal_renders_correctly_with_split_width_ltr() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp)) then
                    DeviceConfigurationOverride.LayoutDirection(LayoutDirection.Ltr)
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FractionHorizontalTwoPaneStrategy(
                        splitFraction = 1f / 3f,
                        gapWidth = 64.dp
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(268.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(332.dp, 0.dp),
                    DpSize(568.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fraction_horizontal_renders_correctly_with_split_width_rtl() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp)) then
                    DeviceConfigurationOverride.LayoutDirection(LayoutDirection.Rtl)
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FractionHorizontalTwoPaneStrategy(
                        splitFraction = 1f / 3f,
                        gapWidth = 64.dp
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(632.dp, 0.dp),
                    DpSize(268.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(568.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fraction_vertical_renders_correctly() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp))
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FractionVerticalTwoPaneStrategy(
                        splitFraction = 1f / 3f
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(900.dp, 400.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 400.dp),
                    DpSize(900.dp, 800.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fraction_vertical_renders_correctly_with_split_height() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp))
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FractionVerticalTwoPaneStrategy(
                        splitFraction = 1f / 3f,
                        gapHeight = 64.dp
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(900.dp, 368.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 432.dp),
                    DpSize(900.dp, 768.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fixed_offset_horizontal_from_start_horizontal_renders_correctly_ltr() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp)) then
                    DeviceConfigurationOverride.LayoutDirection(LayoutDirection.Ltr)
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FixedOffsetHorizontalTwoPaneStrategy(
                        splitOffset = 200.dp,
                        offsetFromStart = true,
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(200.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(200.dp, 0.dp),
                    DpSize(700.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fixed_offset_horizontal_from_start_horizontal_renders_correctly_rtl() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp)) then
                    DeviceConfigurationOverride.LayoutDirection(LayoutDirection.Rtl)
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FixedOffsetHorizontalTwoPaneStrategy(
                        splitOffset = 200.dp,
                        offsetFromStart = true,
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(700.dp, 0.dp),
                    DpSize(200.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(700.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fixed_offset_horizontal_from_start_renders_correctly_with_split_width_ltr() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp)) then
                    DeviceConfigurationOverride.LayoutDirection(LayoutDirection.Ltr)
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FixedOffsetHorizontalTwoPaneStrategy(
                        splitOffset = 200.dp,
                        offsetFromStart = true,
                        gapWidth = 64.dp
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(168.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(232.dp, 0.dp),
                    DpSize(668.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fixed_offset_horizontal_from_start_renders_correctly_with_split_width_rtl() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp)) then
                    DeviceConfigurationOverride.LayoutDirection(LayoutDirection.Rtl)
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FixedOffsetHorizontalTwoPaneStrategy(
                        splitOffset = 200.dp,
                        offsetFromStart = true,
                        gapWidth = 64.dp
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(732.dp, 0.dp),
                    DpSize(168.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(668.dp, 1200.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fixed_offset_vertical_from_top_renders_correctly() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp))
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FixedOffsetVerticalTwoPaneStrategy(
                        splitOffset = 300.dp,
                        offsetFromTop = true
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(900.dp, 300.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 300.dp),
                    DpSize(900.dp, 900.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fixed_offset_vertical_from_top_renders_correctly_with_split_height() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp))
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FixedOffsetVerticalTwoPaneStrategy(
                        splitOffset = 300.dp,
                        offsetFromTop = true,
                        gapHeight = 64.dp
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(900.dp, 268.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 332.dp),
                    DpSize(900.dp, 868.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fixed_offset_vertical_from_bottom_renders_correctly() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp))
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FixedOffsetVerticalTwoPaneStrategy(
                        splitOffset = 300.dp,
                        offsetFromTop = false
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(900.dp, 900.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 900.dp),
                    DpSize(900.dp, 300.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun fixed_offset_vertical_from_bottom_renders_correctly_with_split_height() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp))
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = FixedOffsetVerticalTwoPaneStrategy(
                        splitOffset = 300.dp,
                        offsetFromTop = false,
                        gapHeight = 64.dp
                    ),
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(900.dp, 868.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 932.dp),
                    DpSize(900.dp, 268.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun two_pane_strategy_uses_fallback_when_no_fold_present() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates
        val displayFeatures = DelegateList {
            fakeDisplayFeatures(
                density = density,
                twoPaneCoordinates = twoPaneCoordinates,
                localFoldingFeatures = emptyList()
            )
        }

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp))
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = VerticalTwoPaneStrategy(
                        splitFraction = 1f / 3f
                    ),
                    displayFeatures = displayFeatures,
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(900.dp, 400.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 400.dp),
                    DpSize(900.dp, 800.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun two_pane_strategy_uses_vertical_placing_when_occluding_horizontal_fold_present() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates
        val displayFeatures = DelegateList {
            fakeDisplayFeatures(
                density = density,
                twoPaneCoordinates = twoPaneCoordinates,
                localFoldingFeatures = listOf(
                    LocalFoldingFeature(
                        center = 600.dp,
                        size = 0.dp,
                        state = FoldingFeature.State.HALF_OPENED,
                        orientation = FoldingFeature.Orientation.HORIZONTAL
                    )
                )
            )
        }

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp))
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = VerticalTwoPaneStrategy(
                        splitFraction = 1f / 3f
                    ),
                    displayFeatures = displayFeatures,
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(900.dp, 600.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 600.dp),
                    DpSize(900.dp, 600.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun two_pane_strategy_uses_vertical_placing_when_separating_horizontal_fold_present() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates
        val displayFeatures = DelegateList {
            fakeDisplayFeatures(
                density = density,
                twoPaneCoordinates = twoPaneCoordinates,
                localFoldingFeatures = listOf(
                    LocalFoldingFeature(
                        center = 600.dp,
                        size = 60.dp,
                        state = FoldingFeature.State.FLAT,
                        orientation = FoldingFeature.Orientation.HORIZONTAL
                    )
                )
            )
        }

        composeTestRule.setContent {
            DeviceConfigurationOverride(
                DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1200.dp))
            ) {
                density = LocalDensity.current
                TwoPane(
                    first = {
                        Spacer(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                                .onPlaced { firstCoordinates = it }
                        )
                    },
                    second = {
                        Spacer(
                            Modifier
                                .background(Color.Blue)
                                .fillMaxSize()
                                .onPlaced { secondCoordinates = it }
                        )
                    },
                    strategy = VerticalTwoPaneStrategy(
                        splitFraction = 1f / 3f
                    ),
                    displayFeatures = displayFeatures,
                    modifier = Modifier.onPlaced { twoPaneCoordinates = it }
                )
            }
        }

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 0.dp),
                    DpSize(900.dp, 570.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(firstCoordinates),
            1f
        )

        compareRectWithTolerance(
            with(density) {
                DpRect(
                    DpOffset(0.dp, 630.dp),
                    DpSize(900.dp, 570.dp)
                ).toRect().round().toRect()
            },
            twoPaneCoordinates.localBoundingBoxOf(secondCoordinates),
            1f
        )
    }

    @Test
    fun two_pane_strategy_uses_fallback_when_non_occluding_horizontal_fold_present() {
        lateinit var density: Density
        lateinit var twoPaneCoordinates: LayoutCoordinates
        lateinit var firstCoordinates: LayoutCoordinates
        lateinit var secondCoordinates: LayoutCoordinates
        val displayFeatu
Download .txt
gitextract_cgrc05nu/

├── .allstar/
│   └── binary_artifacts.yaml
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── adaptive-bug-report.md
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   ├── general-bug-report.md
│   │   ├── general-other-bug-report.md
│   │   ├── navigation-material-bug-report.md
│   │   ├── permissions-bug-report.md
│   │   └── testharness-bug-report.md
│   ├── auto-merge.yml
│   ├── ci-gradle.properties
│   ├── pull_request_template.md
│   ├── release-drafter.yml
│   └── workflows/
│       ├── automerger.yml
│       ├── build-snapshot.yml
│       ├── build.yml
│       ├── issues-stale.yml
│       ├── publish-docs.yml
│       └── update-release.yml
├── .gitignore
├── .idea/
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   ├── copyright/
│   │   ├── AOSP.xml
│   │   └── profiles_settings.xml
│   ├── inspectionProfiles/
│   │   ├── ktlint.xml
│   │   └── profiles_settings.xml
│   ├── kotlinScripting.xml
│   ├── kotlinc.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── ASSETS_LICENSE.txt
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── adaptive/
│   ├── README.md
│   ├── api/
│   │   └── current.api
│   ├── build.gradle.kts
│   ├── gradle.properties
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── com/
│       │           └── google/
│       │               └── accompanist/
│       │                   └── adaptive/
│       │                       ├── DisplayFeatures.kt
│       │                       ├── FoldAwareColumn.kt
│       │                       ├── FoldAwareColumnScope.kt
│       │                       ├── RowColumnImpl.kt
│       │                       ├── RowColumnMeasurementHelper.kt
│       │                       └── TwoPane.kt
│       ├── sharedTest/
│       │   └── kotlin/
│       │       └── com/
│       │           └── google/
│       │               └── accompanist/
│       │                   └── adaptive/
│       │                       ├── DisplayFeaturesTest.kt
│       │                       ├── FoldAwareColumnTest.kt
│       │                       └── TwoPaneTest.kt
│       └── test/
│           └── resources/
│               └── robolectric.properties
├── build-logic/
│   ├── convention/
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           └── kotlin/
│   │               ├── AndroidLibraryComposeConventionPlugin.kt
│   │               ├── AndroidLibraryConventionPlugin.kt
│   │               ├── AndroidLibraryPublishedConventionPlugin.kt
│   │               ├── AndroidLintConventionPlugin.kt
│   │               └── com/
│   │                   └── google/
│   │                       └── accompanist/
│   │                           ├── AndroidCompose.kt
│   │                           ├── BundleInsideHelper.kt
│   │                           ├── KotlinAndroid.kt
│   │                           └── ProjectExtensions.kt
│   ├── gradle.properties
│   └── settings.gradle.kts
├── build.gradle
├── checksum.sh
├── docs/
│   ├── adaptive.md
│   ├── appcompat-theme.md
│   ├── drawablepainter.md
│   ├── migration.md
│   ├── navigation-animation.md
│   ├── navigation-material.md
│   ├── permissions.md
│   ├── systemuicontroller.md
│   ├── updating.md
│   ├── using-snapshot-version.md
│   └── web.md
├── drawablepainter/
│   ├── README.md
│   ├── api/
│   │   └── current.api
│   ├── build.gradle.kts
│   ├── gradle.properties
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           └── java/
│               └── com/
│                   └── google/
│                       └── accompanist/
│                           └── drawablepainter/
│                               └── DrawablePainter.kt
├── generate_docs.sh
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── images/
│   └── Social.sketch
├── internal-testutils/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── google/
│           │           └── accompanist/
│           │               └── internal/
│           │                   └── test/
│           │                       ├── ActivityScenario.kt
│           │                       ├── Assertions.kt
│           │                       ├── IgnoreOnRobolectric.kt
│           │                       ├── TestUtils.kt
│           │                       └── WaitUntil.kt
│           └── res/
│               └── values/
│                   └── themes.xml
├── mkdocs.yml
├── permissions/
│   ├── README.md
│   ├── api/
│   │   └── current.api
│   ├── build.gradle.kts
│   ├── gradle.properties
│   └── src/
│       ├── androidTest/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── com/
│       │           └── google/
│       │               └── accompanist/
│       │                   └── permissions/
│       │                       ├── FakeTests.kt
│       │                       ├── MultipleAndSinglePermissionsTest.kt
│       │                       ├── MultiplePermissionsStateTest.kt
│       │                       ├── PermissionStateTest.kt
│       │                       ├── RequestMultiplePermissionsTest.kt
│       │                       ├── RequestPermissionTest.kt
│       │                       ├── TestUtils.kt
│       │                       └── test/
│       │                           ├── EmptyPermissionsTestActivity.kt
│       │                           └── PermissionsTestActivity.kt
│       └── main/
│           ├── AndroidManifest.xml
│           └── java/
│               └── com/
│                   └── google/
│                       └── accompanist/
│                           └── permissions/
│                               ├── MultiplePermissionsState.kt
│                               ├── MutableMultiplePermissionsState.kt
│                               ├── MutablePermissionState.kt
│                               ├── PermissionState.kt
│                               └── PermissionsUtil.kt
├── permissions-lint/
│   ├── README.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── google/
│       │   │           └── accompanist/
│       │   │               └── permissions/
│       │   │                   └── lint/
│       │   │                       ├── PermissionsIssueRegistry.kt
│       │   │                       ├── PermissionsLaunchDetector.kt
│       │   │                       └── util/
│       │   │                           ├── ComposableUtils.kt
│       │   │                           ├── KotlinMetadataUtils.kt
│       │   │                           ├── Names.kt
│       │   │                           └── PsiUtils.kt
│       │   └── resources/
│       │       └── META-INF/
│       │           └── services/
│       │               └── com.android.tools.lint.client.api.IssueRegistry
│       └── test/
│           └── java/
│               └── com/
│                   └── google/
│                       └── accompanist/
│                           └── permissions/
│                               └── lint/
│                                   └── PermissionsLaunchDetectorTest.kt
├── release/
│   ├── secring.gpg.aes
│   ├── signing-cleanup.sh
│   ├── signing-setup.sh
│   └── signing.properties.aes
├── sample/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── google/
│           │           └── accompanist/
│           │               └── sample/
│           │                   ├── ImageLoadingSampleUtils.kt
│           │                   ├── MainActivity.kt
│           │                   ├── MainScreen.kt
│           │                   ├── Theme.kt
│           │                   ├── adaptive/
│           │                   │   ├── BasicTwoPaneSample.kt
│           │                   │   ├── DraggableFoldAwareColumnSample.kt
│           │                   │   ├── HorizontalTwoPaneSample.kt
│           │                   │   ├── NavDrawerFoldAwareColumnSample.kt
│           │                   │   ├── NavRailFoldAwareColumnSample.kt
│           │                   │   └── VerticalTwoPaneSample.kt
│           │                   ├── drawablepainter/
│           │                   │   └── DocSamples.kt
│           │                   └── permissions/
│           │                       ├── RequestLocationPermissionsSample.kt
│           │                       ├── RequestMultiplePermissionsSample.kt
│           │                       └── RequestPermissionSample.kt
│           └── res/
│               ├── drawable/
│               │   ├── ic_launcher_background.xml
│               │   └── rectangle.xml
│               ├── drawable-v24/
│               │   └── ic_launcher_foreground.xml
│               ├── mipmap-anydpi-v26/
│               │   ├── ic_launcher.xml
│               │   └── ic_launcher_round.xml
│               ├── values/
│               │   └── strings.xml
│               └── values-ar/
│                   └── strings.xml
├── scripts/
│   └── run-tests.sh
├── settings.gradle.kts
└── spotless/
    ├── copyright.txt
    └── greclipse.properties
Condensed preview — 157 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (557K chars).
[
  {
    "path": ".allstar/binary_artifacts.yaml",
    "chars": 171,
    "preview": "# Exemption reason: This repo uses binary artifacts to ship gradle.jar for users. It does not allow any others.\n# Exempt"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/adaptive-bug-report.md",
    "chars": 220,
    "preview": "---\nname: Adaptive bug report\nabout: Create a report about adaptive\ntitle: \"[Adaptive]\"\nlabels: adaptive\nassignees: alex"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 669,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n## Describe the "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 28,
    "preview": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/general-bug-report.md",
    "chars": 741,
    "preview": "---\nname: General Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n## Descr"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/general-other-bug-report.md",
    "chars": 747,
    "preview": "---\nname: General/Other bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n##"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/navigation-material-bug-report.md",
    "chars": 241,
    "preview": "---\nname: Navigation Material bug report\nabout: Create a report to help us improve\ntitle: \"[Navigation Material] \"\nlabel"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/permissions-bug-report.md",
    "chars": 228,
    "preview": "---\nname: Permissions bug report\nabout: Create a report to help us improve\ntitle: \"[Permissions] \"\nlabels: ''\nassignees:"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/testharness-bug-report.md",
    "chars": 235,
    "preview": "---\nname: Test harness bug report\nabout: Create a report about test harness\ntitle: \"[Test Harness]\"\nlabels: testharness\n"
  },
  {
    "path": ".github/auto-merge.yml",
    "chars": 157,
    "preview": "# Config for github.com/bobvanderlinden/probot-auto-merge\nminApprovals:\n  COLLABORATOR: 1\nrequiredLabels:\n  - automerge\n"
  },
  {
    "path": ".github/ci-gradle.properties",
    "chars": 865,
    "preview": "#\n# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 84,
    "preview": "### Please add the library name to the PR title. Example: \"[Insets] Fixes typo\" ###\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "chars": 120,
    "preview": "name-template: 'v$NEXT_PATCH_VERSION 🌈'\ntag-template: 'v$NEXT_PATCH_VERSION'\ntemplate: |\n  ## What’s Changed\n\n  $CHANGES"
  },
  {
    "path": ".github/workflows/automerger.yml",
    "chars": 522,
    "preview": "name: main to snapshot auto merger\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  automerge:\n    runs-on: ubuntu-lates"
  },
  {
    "path": ".github/workflows/build-snapshot.yml",
    "chars": 5807,
    "preview": "name: Build & test (snapshot)\n\non:\n  push:\n    branches:\n      - snapshot\n    paths-ignore:\n      - '**.md'\n  pull_reque"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 5872,
    "preview": "name: Build & test\n\non:\n  push:\n    branches:\n      - main\n      - compose-1.0\n      - compose-1.1\n      - compose-1.2\n "
  },
  {
    "path": ".github/workflows/issues-stale.yml",
    "chars": 519,
    "preview": "name: 'Close stale issues and PRs'\non:\n  schedule:\n    - cron: '15 3 * * *'\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n "
  },
  {
    "path": ".github/workflows/publish-docs.yml",
    "chars": 1078,
    "preview": "name: Publish docs\n\non:\n  push:\n    tags:\n      - v*\n  workflow_dispatch:\n\njobs:\n  deploy_docs:\n    runs-on: ubuntu-late"
  },
  {
    "path": ".github/workflows/update-release.yml",
    "chars": 362,
    "preview": "name: Update release\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  update_draft_release:\n    runs-on: ubuntu-latest\n "
  },
  {
    "path": ".gitignore",
    "chars": 700,
    "preview": "# Gradle\n.gradle\nbuild/\n\ncaptures\n\n/local.properties\n\n# IntelliJ .idea folder\n.idea/workspace.xml\n.idea/libraries\n.idea/"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "chars": 4461,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <JetCodeStyleSettings>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "chars": 143,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n "
  },
  {
    "path": ".idea/copyright/AOSP.xml",
    "chars": 813,
    "preview": "<component name=\"CopyrightManager\">\n  <copyright>\n    <option name=\"notice\" value=\"Copyright &amp;#36;today.year The And"
  },
  {
    "path": ".idea/copyright/profiles_settings.xml",
    "chars": 78,
    "preview": "<component name=\"CopyrightManager\">\n  <settings default=\"AOSP\" />\n</component>"
  },
  {
    "path": ".idea/inspectionProfiles/ktlint.xml",
    "chars": 359,
    "preview": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"ktlint\" />"
  },
  {
    "path": ".idea/inspectionProfiles/profiles_settings.xml",
    "chars": 172,
    "preview": "<component name=\"InspectionProjectProfileManager\">\n  <settings>\n    <option name=\"PROJECT_PROFILE\" value=\"ktlint\" />\n   "
  },
  {
    "path": ".idea/kotlinScripting.xml",
    "chars": 186,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"KotlinScriptingSettings\">\n    <option na"
  },
  {
    "path": ".idea/kotlinc.xml",
    "chars": 284,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Kotlin2JvmCompilerArguments\">\n    <optio"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "chars": 964,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <o"
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 167,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": "ASSETS_LICENSE.txt",
    "chars": 4133,
    "preview": "All font files are licensed under the SIL OPEN FONT LICENSE license. All other files are licensed under the Apache 2 lic"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1660,
    "preview": "# How to Contribute\n\nWe'd love to accept your patches and contributions to this project. There are\njust a few small guid"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 6106,
    "preview": "![Accompanist logo](docs/header.png)\n\nAccompanist is a group of libraries that aim to supplement [Jetpack Compose][compo"
  },
  {
    "path": "adaptive/README.md",
    "chars": 700,
    "preview": "# Adaptive utilities for Jetpack Compose\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanis"
  },
  {
    "path": "adaptive/api/current.api",
    "chars": 4049,
    "preview": "// Signature format: 4.0\npackage com.google.accompanist.adaptive {\n\n  public final class DisplayFeaturesKt {\n    method "
  },
  {
    "path": "adaptive/build.gradle.kts",
    "chars": 2262,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "adaptive/gradle.properties",
    "chars": 92,
    "preview": "POM_ARTIFACT_ID=accompanist-adaptive\nPOM_NAME=Accompanist Adaptive library\nPOM_PACKAGING=aar"
  },
  {
    "path": "adaptive/src/main/AndroidManifest.xml",
    "chars": 648,
    "preview": "<!--\n  ~ Copyright 2022 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "adaptive/src/main/java/com/google/accompanist/adaptive/DisplayFeatures.kt",
    "chars": 1514,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "adaptive/src/main/java/com/google/accompanist/adaptive/FoldAwareColumn.kt",
    "chars": 14917,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "adaptive/src/main/java/com/google/accompanist/adaptive/FoldAwareColumnScope.kt",
    "chars": 5383,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "adaptive/src/main/java/com/google/accompanist/adaptive/RowColumnImpl.kt",
    "chars": 23679,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "adaptive/src/main/java/com/google/accompanist/adaptive/RowColumnMeasurementHelper.kt",
    "chars": 13383,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "adaptive/src/main/java/com/google/accompanist/adaptive/TwoPane.kt",
    "chars": 21756,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "adaptive/src/sharedTest/kotlin/com/google/accompanist/adaptive/DisplayFeaturesTest.kt",
    "chars": 3765,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "adaptive/src/sharedTest/kotlin/com/google/accompanist/adaptive/FoldAwareColumnTest.kt",
    "chars": 9501,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "adaptive/src/sharedTest/kotlin/com/google/accompanist/adaptive/TwoPaneTest.kt",
    "chars": 56866,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "adaptive/src/test/resources/robolectric.properties",
    "chars": 133,
    "preview": "# Pin SDK to 30 since Robolectric does not currently support API 31:\n# https://github.com/robolectric/robolectric/issues"
  },
  {
    "path": "build-logic/convention/build.gradle.kts",
    "chars": 2222,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt",
    "chars": 1235,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt",
    "chars": 2026,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n *   Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidLibraryPublishedConventionPlugin.kt",
    "chars": 1437,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt",
    "chars": 1751,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/accompanist/AndroidCompose.kt",
    "chars": 2262,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/accompanist/BundleInsideHelper.kt",
    "chars": 8454,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/accompanist/KotlinAndroid.kt",
    "chars": 2390,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/accompanist/ProjectExtensions.kt",
    "chars": 931,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/gradle.properties",
    "chars": 183,
    "preview": "# Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534\norg.gradle.parallel=t"
  },
  {
    "path": "build-logic/settings.gradle.kts",
    "chars": 922,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build.gradle",
    "chars": 7854,
    "preview": "/*\n * Copyright 2020 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "checksum.sh",
    "chars": 1148,
    "preview": "#!/bin/bash\n\n# Copyright 2021 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "docs/adaptive.md",
    "chars": 3979,
    "preview": "# Adaptive utilities for Jetpack Compose\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanis"
  },
  {
    "path": "docs/appcompat-theme.md",
    "chars": 8529,
    "preview": "# AppCompat Compose Theme Adapter\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accom"
  },
  {
    "path": "docs/drawablepainter.md",
    "chars": 1676,
    "preview": "# Drawable Painter\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-drawable"
  },
  {
    "path": "docs/migration.md",
    "chars": 2080,
    "preview": "# Migration from dev.chrisbanes.accompanist\n\nIn March 2021, the Accompanist project moved from [github.com/chrisbanes/ac"
  },
  {
    "path": "docs/navigation-animation.md",
    "chars": 10845,
    "preview": "# Jetpack Navigation Compose Animation\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/"
  },
  {
    "path": "docs/navigation-material.md",
    "chars": 4511,
    "preview": "# Jetpack Navigation Compose Material\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/a"
  },
  {
    "path": "docs/permissions.md",
    "chars": 3940,
    "preview": "# Jetpack Compose Permissions\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompani"
  },
  {
    "path": "docs/systemuicontroller.md",
    "chars": 4036,
    "preview": "# System UI Controller for Jetpack Compose\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompan"
  },
  {
    "path": "docs/updating.md",
    "chars": 3740,
    "preview": "# Updating & releasing Accompanist\n\nThis doc is mostly for maintainers.\n\n## New features & bugfixes\nAll new features sho"
  },
  {
    "path": "docs/using-snapshot-version.md",
    "chars": 2509,
    "preview": "# Using a Snapshot Version of the Library\n\nIf you would like to depend on the cutting edge version of the Accompanist\nli"
  },
  {
    "path": "docs/web.md",
    "chars": 2375,
    "preview": "# WebView wrapper for Jetpack Compose\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/a"
  },
  {
    "path": "drawablepainter/README.md",
    "chars": 489,
    "preview": "# Accompanist Drawable Painter\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.google.accompanis"
  },
  {
    "path": "drawablepainter/api/current.api",
    "chars": 951,
    "preview": "// Signature format: 4.0\npackage com.google.accompanist.drawablepainter {\n\n  public final class DrawablePainter extends "
  },
  {
    "path": "drawablepainter/build.gradle.kts",
    "chars": 1017,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "drawablepainter/gradle.properties",
    "chars": 107,
    "preview": "POM_ARTIFACT_ID=accompanist-drawablepainter\nPOM_NAME=Accompanist Drawable Painter library\nPOM_PACKAGING=aar"
  },
  {
    "path": "drawablepainter/src/main/AndroidManifest.xml",
    "chars": 811,
    "preview": "<!--\n  ~ Copyright 2020 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt",
    "chars": 6925,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "generate_docs.sh",
    "chars": 1355,
    "preview": "#!/bin/bash\n\n# Copyright 2021 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gradle/libs.versions.toml",
    "chars": 6126,
    "preview": "[versions]\n\ncompose = \"1.7.0\"\ncomposeMaterial3 = \"1.0.1\"\ncomposesnapshot = \"-\" # a single character = no snapshot\n\ndesug"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 253,
    "preview": "#Wed Jul 10 11:49:25 AEST 2024\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\:/"
  },
  {
    "path": "gradle.properties",
    "chars": 1857,
    "preview": "#\n# Copyright 2020 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n#"
  },
  {
    "path": "gradlew",
    "chars": 8473,
    "preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "gradlew.bat",
    "chars": 2868,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "internal-testutils/build.gradle.kts",
    "chars": 1111,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "internal-testutils/src/main/AndroidManifest.xml",
    "chars": 790,
    "preview": "<!--\n  ~ Copyright 2021 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "internal-testutils/src/main/java/com/google/accompanist/internal/test/ActivityScenario.kt",
    "chars": 923,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "internal-testutils/src/main/java/com/google/accompanist/internal/test/Assertions.kt",
    "chars": 2748,
    "preview": "/*\n * Copyright 2020 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "internal-testutils/src/main/java/com/google/accompanist/internal/test/IgnoreOnRobolectric.kt",
    "chars": 803,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "internal-testutils/src/main/java/com/google/accompanist/internal/test/TestUtils.kt",
    "chars": 1236,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "internal-testutils/src/main/java/com/google/accompanist/internal/test/WaitUntil.kt",
    "chars": 1321,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "internal-testutils/src/main/res/values/themes.xml",
    "chars": 943,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2021 The Android Open Source Project\n  ~\n  ~ Licensed under th"
  },
  {
    "path": "mkdocs.yml",
    "chars": 1580,
    "preview": "# Project information\nsite_name: 'Accompanist'\nsite_description: 'A group of libraries to help write Jetpack Compose app"
  },
  {
    "path": "permissions/README.md",
    "chars": 705,
    "preview": "# Permissions for Jetpack Compose\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accom"
  },
  {
    "path": "permissions/api/current.api",
    "chars": 4454,
    "preview": "// Signature format: 4.0\npackage com.google.accompanist.permissions {\n\n  @kotlin.RequiresOptIn(message=\"Accompanist Perm"
  },
  {
    "path": "permissions/build.gradle.kts",
    "chars": 2345,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/gradle.properties",
    "chars": 90,
    "preview": "POM_ARTIFACT_ID=accompanist-permissions\nPOM_NAME=Accompanist Permissions\nPOM_PACKAGING=aar"
  },
  {
    "path": "permissions/src/androidTest/AndroidManifest.xml",
    "chars": 1342,
    "preview": "<!--\n  ~ Copyright 2021 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "permissions/src/androidTest/java/com/google/accompanist/permissions/FakeTests.kt",
    "chars": 1363,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/androidTest/java/com/google/accompanist/permissions/MultipleAndSinglePermissionsTest.kt",
    "chars": 11303,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/androidTest/java/com/google/accompanist/permissions/MultiplePermissionsStateTest.kt",
    "chars": 2866,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/androidTest/java/com/google/accompanist/permissions/PermissionStateTest.kt",
    "chars": 2183,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/androidTest/java/com/google/accompanist/permissions/RequestMultiplePermissionsTest.kt",
    "chars": 5688,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/androidTest/java/com/google/accompanist/permissions/RequestPermissionTest.kt",
    "chars": 4632,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/androidTest/java/com/google/accompanist/permissions/TestUtils.kt",
    "chars": 4734,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/androidTest/java/com/google/accompanist/permissions/test/EmptyPermissionsTestActivity.kt",
    "chars": 1277,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/androidTest/java/com/google/accompanist/permissions/test/PermissionsTestActivity.kt",
    "chars": 2699,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/main/AndroidManifest.xml",
    "chars": 810,
    "preview": "<!--\n  ~ Copyright 2021 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "permissions/src/main/java/com/google/accompanist/permissions/MultiplePermissionsState.kt",
    "chars": 5208,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/main/java/com/google/accompanist/permissions/MutableMultiplePermissionsState.kt",
    "chars": 5992,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/main/java/com/google/accompanist/permissions/MutablePermissionState.kt",
    "chars": 4053,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/main/java/com/google/accompanist/permissions/PermissionState.kt",
    "chars": 4159,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions/src/main/java/com/google/accompanist/permissions/PermissionsUtil.kt",
    "chars": 5541,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions-lint/README.md",
    "chars": 693,
    "preview": "# Lint checks for Permissions for Jetpack Compose\n\nLint checks for preventing calling `PermissionState.launchPermissionR"
  },
  {
    "path": "permissions-lint/build.gradle.kts",
    "chars": 2177,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions-lint/src/main/java/com/google/accompanist/permissions/lint/PermissionsIssueRegistry.kt",
    "chars": 1400,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions-lint/src/main/java/com/google/accompanist/permissions/lint/PermissionsLaunchDetector.kt",
    "chars": 3916,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions-lint/src/main/java/com/google/accompanist/permissions/lint/util/ComposableUtils.kt",
    "chars": 12367,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions-lint/src/main/java/com/google/accompanist/permissions/lint/util/KotlinMetadataUtils.kt",
    "chars": 6011,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions-lint/src/main/java/com/google/accompanist/permissions/lint/util/Names.kt",
    "chars": 5874,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions-lint/src/main/java/com/google/accompanist/permissions/lint/util/PsiUtils.kt",
    "chars": 2072,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "permissions-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry",
    "chars": 667,
    "preview": "#\n# Copyright 2021 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n#"
  },
  {
    "path": "permissions-lint/src/test/java/com/google/accompanist/permissions/lint/PermissionsLaunchDetectorTest.kt",
    "chars": 11203,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "release/signing-cleanup.sh",
    "chars": 656,
    "preview": "#!/bin/sh\n\n# Copyright 2021 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "release/signing-setup.sh",
    "chars": 983,
    "preview": "#!/bin/bash\n\n# Copyright 2021 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "sample/build.gradle.kts",
    "chars": 2265,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/AndroidManifest.xml",
    "chars": 6044,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2022 The Android Open Source Project\n  ~\n  ~ Licensed under th"
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/ImageLoadingSampleUtils.kt",
    "chars": 1237,
    "preview": "/*\n * Copyright 2020 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/MainActivity.kt",
    "chars": 4219,
    "preview": "/*\n * Copyright 2020 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/MainScreen.kt",
    "chars": 3476,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/Theme.kt",
    "chars": 2004,
    "preview": "/*\n * Copyright 2020 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/adaptive/BasicTwoPaneSample.kt",
    "chars": 3568,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/adaptive/DraggableFoldAwareColumnSample.kt",
    "chars": 4349,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/adaptive/HorizontalTwoPaneSample.kt",
    "chars": 3038,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/adaptive/NavDrawerFoldAwareColumnSample.kt",
    "chars": 4044,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/adaptive/NavRailFoldAwareColumnSample.kt",
    "chars": 3517,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/adaptive/VerticalTwoPaneSample.kt",
    "chars": 3031,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/drawablepainter/DocSamples.kt",
    "chars": 1216,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/permissions/RequestLocationPermissionsSample.kt",
    "chars": 3558,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/permissions/RequestMultiplePermissionsSample.kt",
    "chars": 4041,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/java/com/google/accompanist/sample/permissions/RequestPermissionSample.kt",
    "chars": 2470,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "sample/src/main/res/drawable/ic_launcher_background.xml",
    "chars": 6241,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2020 The Android Open Source Project\n  ~\n  ~ Licensed under th"
  },
  {
    "path": "sample/src/main/res/drawable/rectangle.xml",
    "chars": 787,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2020 The Android Open Source Project\n  ~\n  ~ Licensed under th"
  },
  {
    "path": "sample/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "chars": 2515,
    "preview": "<!--\n  ~ Copyright 2020 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "chars": 907,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2020 The Android Open Source Project\n  ~\n  ~ Licensed under th"
  },
  {
    "path": "sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "chars": 907,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2020 The Android Open Source Project\n  ~\n  ~ Licensed under th"
  },
  {
    "path": "sample/src/main/res/values/strings.xml",
    "chars": 4808,
    "preview": "<!--\n  ~ Copyright 2022 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "sample/src/main/res/values-ar/strings.xml",
    "chars": 757,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2022 The Android Open Source Project\n  ~\n  ~ Licensed under the"
  },
  {
    "path": "scripts/run-tests.sh",
    "chars": 2700,
    "preview": "#!/bin/bash\n\n# Copyright 2021 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "settings.gradle.kts",
    "chars": 1363,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "spotless/copyright.txt",
    "chars": 619,
    "preview": "/*\n * Copyright $YEAR The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "spotless/greclipse.properties",
    "chars": 1852,
    "preview": "#\n# Copyright 2020 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n#"
  }
]

// ... and 4 more files (download for full content)

About this extraction

This page contains the full source code of the google/accompanist GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 157 files (515.2 KB), approximately 114.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!