diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 62924bd05..6241805a1 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -2,7 +2,11 @@ - diff --git a/README.md b/README.md index fa5e0f382..a0e4197e8 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,18 @@ Accompanist is a group of libraries that contains some utilities which I've found myself copying around projects which use [Jetpack Compose][compose]. Currently, it contains: - - 🖼️ [Coil image loading composables](./coil/README.md) - - 🖼️ [Picasso image loading composables](./picasso/README.md) - - 🖼️ [Glide image loading composables](./glide/README.md) +### Image loading +A number of libraries which integrate popular image loading libraries into Jetpack Compose: + + - 🖼️ [Coil image loading composables](./coil/) + - 🖼️ [Picasso image loading composables](./picasso/) + - 🖼️ [Glide image loading composables](./glide/) + +### 📐 [Insets](./insets/) +A library which brings [WindowInsets](https://developer.android.com/reference/kotlin/android/view/WindowInsets) support to Jetpack Compose. + + +--- [Jetpack Compose][compose] is a fast-moving project and I'll be updating these libraries to match the latest tagged release as quickly as possible. Each [release listing](https://github.com/chrisbanes/accompanist/releases) will outline what version of Compose libraries it depends on. diff --git a/buildSrc/src/main/java/dev/chrisbanes/accompanist/buildsrc/dependencies.kt b/buildSrc/src/main/java/dev/chrisbanes/accompanist/buildsrc/dependencies.kt index 65debdba1..7b51f6b30 100644 --- a/buildSrc/src/main/java/dev/chrisbanes/accompanist/buildsrc/dependencies.kt +++ b/buildSrc/src/main/java/dev/chrisbanes/accompanist/buildsrc/dependencies.kt @@ -85,7 +85,7 @@ object Libs { const val core = "androidx.core:core:1.2.0" const val coreKtx = "androidx.core:core-ktx:1.2.0" - const val appcompat = "androidx.appcompat:appcompat:1.3.0-alpha02" + const val coreAlpha = "androidx.core:core:1.5.0-alpha04" } object Coil { diff --git a/generate_docs.sh b/generate_docs.sh index 7fe071774..b3a042846 100755 --- a/generate_docs.sh +++ b/generate_docs.sh @@ -25,28 +25,27 @@ cp CONTRIBUTING.md $DOCS_ROOT/contributing.md cp images/social.png $DOCS_ROOT/header.png sed -i.bak 's/CONTRIBUTING.md/contributing/' $DOCS_ROOT/index.md -sed -i.bak 's/coil\/README.md/glide/' $DOCS_ROOT/index.md -sed -i.bak 's/glide\/README.md/coil/' $DOCS_ROOT/index.md -sed -i.bak 's/picasso\/README.md/picasso/' $DOCS_ROOT/index.md +sed -i.bak 's/README.md//' $DOCS_ROOT/index.md sed -i.bak 's/images\/social.png/header.png/' $DOCS_ROOT/index.md +# Convert docs/xxx.md links to just xxx/ +sed -i.bak 's/docs\/\([a-zA-Z-]*\).md/\1/' $DOCS_ROOT/index.md + cp coil/README.md $DOCS_ROOT/coil.md mkdir -p $DOCS_ROOT/coil -cp coil/images/crossfade.gif $DOCS_ROOT/coil/crossfade.gif -sed -i.bak 's/images\/crossfade.gif/crossfade.gif/' $DOCS_ROOT/coil.md +cp -r coil/images $DOCS_ROOT/coil cp picasso/README.md $DOCS_ROOT/picasso.md mkdir -p $DOCS_ROOT/picasso -cp picasso/images/crossfade.gif $DOCS_ROOT/picasso/crossfade.gif -sed -i.bak 's/images\/crossfade.gif/crossfade.gif/' $DOCS_ROOT/picasso.md +cp -r picasso/images $DOCS_ROOT/picasso cp glide/README.md $DOCS_ROOT/glide.md mkdir -p $DOCS_ROOT/glide -cp glide/images/crossfade.gif $DOCS_ROOT/glide/crossfade.gif -sed -i.bak 's/images\/crossfade.gif/crossfade.gif/' $DOCS_ROOT/glide.md +cp -r glide/images $DOCS_ROOT/glide -# Convert docs/xxx.md links to just xxx/ -sed -i.bak 's/docs\/\([a-zA-Z-]*\).md/\1/' $DOCS_ROOT/index.md +cp insets/README.md $DOCS_ROOT/insets.md +mkdir -p $DOCS_ROOT/insets +cp -r insets/images $DOCS_ROOT/insets ######################### # Tidy up Dokka output diff --git a/insets/README.md b/insets/README.md new file mode 100644 index 000000000..a316d99cc --- /dev/null +++ b/insets/README.md @@ -0,0 +1,119 @@ +# Insets for Jetpack Compose + +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.chrisbanes.accompanist/accompanist-insets/badge.svg)](https://search.maven.org/search?q=g:dev.chrisbanes.accompanist) + +Insets for Jetpack Compose takes a lot of the ideas which drove [Insetter][insetter-view] for views, and applies them for use in composables. + +## Usage +To setup Insets in your composables, you need to call the `ProvideWindowInsets` function and +wrap your content. This would typically be done near the top level of your composable hierarchy: + +``` kotlin +setContent { + MaterialTheme { + ProvideWindowInsets { + // your content + } + } +} +``` + +> Note: Whether `ProvideWindowInsets` is called outside or within `MaterialTheme` doesn't particularly matter. + +`ProvideWindowInsets` allows the library to set an [`OnApplyWindowInsetsListener`][insetslistener] on your content's host view. That listener is used to update the value of an ambient bundled in this library: `AmbientWindowInsets`. + +`AmbientWindowInsets` holds an instance of `WindowInsets` which contains the value of various [WindowInsets][insets] [types][insettypes]. You can use the values manually like so: + +``` kotlin +@Composable +fun ImeAvoidingBox() { + val insets = AmbientWindowInsets.current + + Box(Modifier.padding(bottom = insets.ime.bottom)) +} +``` + +...but we also provide some easy-to-use [Modifier][modifier]s. + +### Modifiers + +We provide two types of modifiers for easy handling of insets: padding and size. + +#### Padding modifiers +The padding modifiers allow you to apply padding to a composable which matches a specific type of inset. Currently we provide: + +- `Modifier.statusBarsPadding()` +- `Modifier.navigationBarsPadding()` +- `Modifier.systemBarsPadding()` + +These are commonly used to move composables out from under the system bars. The common example would be a [`FloatingActionButton`][fab]: + +``` kotlin +FloatingActionButton( + icon = { Icon(...) }, + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(16.dp) // normal 16dp of padding for FABs + .navigationBarsPadding() // Move it out from under the nav bar +) +``` + +#### Size modifiers +The size modifiers allow you to match the size of a composable to a specific type of inset. Currently we provide: + +- `Modifier.statusBarsHeight()` +- `Modifier.navigationBarsHeight()` +- `Modifier.navigationBarsWidth()` + +These are commonly used to allow composables behind the system bars, to provide background protection, or similar: + +``` kotlin +Spacer( + Modifier + .background(Color.Black.copy(alpha = 0.7f)) + .statusBarsHeight() // Match the height of the status bar + .fillMaxWidth() +) +``` + +### PaddingValues +Compose also provides the concept of [`PaddingValues`][paddingvalues], a data class which contains the padding values to be applied on all dimensions (similar to a rect). This is commonly used with container composables, such as [`LazyColumn`][lazycolumn], to set the content padding. + +You may want to use inset values for content padding, so this library provides the `Insets.toPaddingValues()` extension function to convert between `Insets` and `PaddingValues`. Here's an example of using the system bars insets: + +``` kotlin +LazyColumn( + contentPadding = AmbientWindowInsets.current.systemBars.toPaddingValues() +) +``` + +For a more complex example, see the [`EdgeToEdgeLazyColumn`](./sample/src/main/java/dev/chrisbanes/accompanist/sample/insets/EdgeToEdgeLazyColumn.kt) example: + + + + + +## Download + +```groovy +repositories { + mavenCentral() +} + +dependencies { + implementation "dev.chrisbanes.accompanist:accompanist-insets:" +} +``` + +Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. These are updated on every commit. + +[compose]: https://developer.android.com/jetpack/compose +[snap]: https://oss.sonatype.org/content/repositories/snapshots/dev/chrisbanes/accompanist/accompanist-insets/ +[insetter-view]: https://github.com/chrisbanes/insetter +[insets]: https://developer.android.com/reference/kotlin/androidx/core/view/WindowInsetsCompat +[insettypes]: https://developer.android.com/reference/kotlin/androidx/core/view/WindowInsetsCompat.Type +[insetslistener]: https://developer.android.com/reference/kotlin/androidx/core/view/OnApplyWindowInsetsListener +[modifier]: https://developer.android.com/reference/kotlin/androidx/ui/core/Modifier +[paddingvalues]: https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/PaddingValues +[lazycolumn]: https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/package-summary#lazycolumn +[fab]: https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#floatingactionbutton diff --git a/insets/api/insets.api b/insets/api/insets.api new file mode 100644 index 000000000..b34b90b88 --- /dev/null +++ b/insets/api/insets.api @@ -0,0 +1,51 @@ +public final class dev/chrisbanes/accompanist/insets/ComposeInsets { + public static final fun ProvideWindowInsets (ZLkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun add-L4cKpv0 (Landroidx/compose/foundation/layout/PaddingValues;FFFF)Landroidx/compose/foundation/layout/PaddingValues; + public static final fun add-L4cKpv0$default (Landroidx/compose/foundation/layout/PaddingValues;FFFFILjava/lang/Object;)Landroidx/compose/foundation/layout/PaddingValues; + public static final fun getAmbientWindowInsets ()Landroidx/compose/runtime/ProvidableAmbient; + public static final fun navigationBarsHeight-wxomhCo (Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier; + public static final fun navigationBarsHeight-wxomhCo$default (Landroidx/compose/ui/Modifier;FILjava/lang/Object;)Landroidx/compose/ui/Modifier; + public static final fun navigationBarsPadding (Landroidx/compose/ui/Modifier;ZZZ)Landroidx/compose/ui/Modifier; + public static final fun navigationBarsPadding$default (Landroidx/compose/ui/Modifier;ZZZILjava/lang/Object;)Landroidx/compose/ui/Modifier; + public static final fun navigationBarsWidth-UTaBBDU (Landroidx/compose/ui/Modifier;Ldev/chrisbanes/accompanist/insets/HorizontalSide;F)Landroidx/compose/ui/Modifier; + public static final fun navigationBarsWidth-UTaBBDU$default (Landroidx/compose/ui/Modifier;Ldev/chrisbanes/accompanist/insets/HorizontalSide;FILjava/lang/Object;)Landroidx/compose/ui/Modifier; + public static final fun statusBarsHeight-wxomhCo (Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier; + public static final fun statusBarsHeight-wxomhCo$default (Landroidx/compose/ui/Modifier;FILjava/lang/Object;)Landroidx/compose/ui/Modifier; + public static final fun statusBarsPadding (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier; + public static final fun systemBarsPadding (Landroidx/compose/ui/Modifier;Z)Landroidx/compose/ui/Modifier; + public static final fun systemBarsPadding$default (Landroidx/compose/ui/Modifier;ZILjava/lang/Object;)Landroidx/compose/ui/Modifier; + public static final fun toPaddingValues (Ldev/chrisbanes/accompanist/insets/Insets;ZZZZLandroidx/compose/runtime/Composer;II)Landroidx/compose/foundation/layout/PaddingValues; +} + +public final class dev/chrisbanes/accompanist/insets/HorizontalSide : java/lang/Enum { + public static final field Left Ldev/chrisbanes/accompanist/insets/HorizontalSide; + public static final field Right Ldev/chrisbanes/accompanist/insets/HorizontalSide; + public static final fun valueOf (Ljava/lang/String;)Ldev/chrisbanes/accompanist/insets/HorizontalSide; + public static final fun values ()[Ldev/chrisbanes/accompanist/insets/HorizontalSide; +} + +public final class dev/chrisbanes/accompanist/insets/Insets { + public fun ()V + public final fun getBottom ()I + public final fun getLeft ()I + public final fun getRight ()I + public final fun getTop ()I + public final fun isVisible ()Z +} + +public final class dev/chrisbanes/accompanist/insets/VerticalSide : java/lang/Enum { + public static final field Bottom Ldev/chrisbanes/accompanist/insets/VerticalSide; + public static final field Top Ldev/chrisbanes/accompanist/insets/VerticalSide; + public static final fun valueOf (Ljava/lang/String;)Ldev/chrisbanes/accompanist/insets/VerticalSide; + public static final fun values ()[Ldev/chrisbanes/accompanist/insets/VerticalSide; +} + +public final class dev/chrisbanes/accompanist/insets/WindowInsets { + public fun ()V + public final fun getIme ()Ldev/chrisbanes/accompanist/insets/Insets; + public final fun getNavigationBars ()Ldev/chrisbanes/accompanist/insets/Insets; + public final fun getStatusBars ()Ldev/chrisbanes/accompanist/insets/Insets; + public final fun getSystemBars ()Ldev/chrisbanes/accompanist/insets/Insets; + public final fun getSystemGestures ()Ldev/chrisbanes/accompanist/insets/Insets; +} + diff --git a/insets/build.gradle b/insets/build.gradle new file mode 100644 index 000000000..d24b963fa --- /dev/null +++ b/insets/build.gradle @@ -0,0 +1,93 @@ +/* + * 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. + */ + +import dev.chrisbanes.accompanist.buildsrc.Libs + +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'org.jetbrains.dokka' +} + +kotlin { + explicitApi() +} + +android { + compileSdkVersion 30 + + defaultConfig { + minSdkVersion 21 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig false + compose true + } + + composeOptions { + kotlinCompilerVersion Libs.Kotlin.version + kotlinCompilerExtensionVersion Libs.AndroidX.Compose.version + } + + lintOptions { + textReport true + textOutput 'stdout' + // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks + checkReleaseBuilds false + } + + testOptions { + unitTests { + includeAndroidResources = true + } + } +} + +afterEvaluate { + tasks.withType(org.jetbrains.dokka.gradle.DokkaTask).configureEach { + outputDirectory.set(rootProject.file('docs/api')) + } +} + +dependencies { + implementation Libs.AndroidX.coreAlpha + implementation Libs.AndroidX.Compose.runtime + implementation Libs.AndroidX.Compose.foundation + + implementation Libs.Kotlin.stdlib + // implementation Libs.Coroutines.android + + // ====================== + // Test dependencies + // ====================== + + androidTestImplementation Libs.junit + androidTestImplementation Libs.truth + + androidTestImplementation Libs.AndroidX.Compose.test + androidTestImplementation Libs.AndroidX.Compose.ui + androidTestImplementation Libs.AndroidX.Test.rules + androidTestImplementation Libs.AndroidX.Test.runner +} + +apply plugin: "com.vanniktech.maven.publish" diff --git a/insets/gradle.properties b/insets/gradle.properties new file mode 100644 index 000000000..214b56a69 --- /dev/null +++ b/insets/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=accompanist-insets +POM_NAME=Accompanist Insets library +POM_PACKAGING=aar \ No newline at end of file diff --git a/insets/images/edge-to-edge-list.jpg b/insets/images/edge-to-edge-list.jpg new file mode 100644 index 000000000..789e7bdfd Binary files /dev/null and b/insets/images/edge-to-edge-list.jpg differ diff --git a/insets/src/androidTest/AndroidManifest.xml b/insets/src/androidTest/AndroidManifest.xml new file mode 100644 index 000000000..1191f293b --- /dev/null +++ b/insets/src/androidTest/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/insets/src/androidTest/java/dev/chrisbanes/accompanist/insets/InsetsAssertions.kt b/insets/src/androidTest/java/dev/chrisbanes/accompanist/insets/InsetsAssertions.kt new file mode 100644 index 000000000..961482887 --- /dev/null +++ b/insets/src/androidTest/java/dev/chrisbanes/accompanist/insets/InsetsAssertions.kt @@ -0,0 +1,51 @@ +/* + * 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. + */ + +package dev.chrisbanes.accompanist.insets + +import android.graphics.Rect +import androidx.core.view.WindowInsetsCompat +import com.google.common.truth.Truth + +internal fun WindowInsets.assertEqualTo(insets: androidx.core.view.WindowInsetsCompat) { + systemBars.assertEqualTo( + insets = insets.getInsets(WindowInsetsCompat.Type.systemBars()), + visible = insets.isVisible(WindowInsetsCompat.Type.systemBars()), + ) + + statusBars.assertEqualTo( + insets = insets.getInsets(WindowInsetsCompat.Type.statusBars()), + visible = insets.isVisible(WindowInsetsCompat.Type.statusBars()), + ) + + navigationBars.assertEqualTo( + insets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()), + visible = insets.isVisible(WindowInsetsCompat.Type.navigationBars()), + ) + + ime.assertEqualTo( + insets = insets.getInsets(WindowInsetsCompat.Type.ime()), + visible = insets.isVisible(WindowInsetsCompat.Type.ime()), + ) +} + +internal fun Insets.assertEqualTo(insets: androidx.core.graphics.Insets, visible: Boolean) { + // This might look a bit weird, why are we using a Rect? Well, it makes the assertion + // error message much easier to read, by containing all of the dimensions. + Truth.assertThat(Rect(left, top, right, bottom)) + .isEqualTo(Rect(insets.left, insets.top, insets.right, insets.bottom)) + Truth.assertThat(this.isVisible).isEqualTo(visible) +} diff --git a/insets/src/androidTest/java/dev/chrisbanes/accompanist/insets/InsetsTest.kt b/insets/src/androidTest/java/dev/chrisbanes/accompanist/insets/InsetsTest.kt new file mode 100644 index 000000000..a1d6537f9 --- /dev/null +++ b/insets/src/androidTest/java/dev/chrisbanes/accompanist/insets/InsetsTest.kt @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package dev.chrisbanes.accompanist.insets + +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.test.filters.LargeTest +import androidx.test.filters.SdkSuppress +import androidx.ui.test.createAndroidComposeRule +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@LargeTest +@RunWith(JUnit4::class) +class InsetsTest { + @get:Rule + val composeTestRule = createAndroidComposeRule(InsetsTestActivity::class.java) + + @Test + @SdkSuppress(minSdkVersion = 23) // ViewCompat.getRootWindowInsets + fun assertValuesMatchViewInsets() { + lateinit var composeWindowInsets: WindowInsets + composeTestRule.setContent { + ProvideWindowInsets { + composeWindowInsets = AmbientWindowInsets.current + } + } + + lateinit var rootWindowInsets: WindowInsetsCompat + composeTestRule.activityRule.scenario.onActivity { + rootWindowInsets = ViewCompat.getRootWindowInsets(it.window.decorView)!! + } + + composeWindowInsets.assertEqualTo(rootWindowInsets) + } +} diff --git a/insets/src/androidTest/java/dev/chrisbanes/accompanist/insets/InsetsTestActivity.kt b/insets/src/androidTest/java/dev/chrisbanes/accompanist/insets/InsetsTestActivity.kt new file mode 100644 index 000000000..a9152b9e2 --- /dev/null +++ b/insets/src/androidTest/java/dev/chrisbanes/accompanist/insets/InsetsTestActivity.kt @@ -0,0 +1,31 @@ +/* + * 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. + */ + +package dev.chrisbanes.accompanist.insets + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.core.view.WindowCompat + +/** + * [ComponentActivity] which automatically requests for the decor not to fit system windows. + */ +class InsetsTestActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) + } +} diff --git a/insets/src/main/AndroidManifest.xml b/insets/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9ec81546e --- /dev/null +++ b/insets/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + diff --git a/insets/src/main/java/dev/chrisbanes/accompanist/insets/Insets.kt b/insets/src/main/java/dev/chrisbanes/accompanist/insets/Insets.kt new file mode 100644 index 000000000..f6e54c016 --- /dev/null +++ b/insets/src/main/java/dev/chrisbanes/accompanist/insets/Insets.kt @@ -0,0 +1,171 @@ +/* + * 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. + */ + +@file:Suppress("NOTHING_TO_INLINE", "unused") + +@file:JvmName("ComposeInsets") +@file:JvmMultifileClass + +package dev.chrisbanes.accompanist.insets + +import android.view.View +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.Providers +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.staticAmbientOf +import androidx.compose.ui.platform.ViewAmbient +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsCompat.Type + +/** + * Main holder of our inset values. + */ +@Stable +class WindowInsets { + /** + * Inset values which match [WindowInsetsCompat.Type.systemBars] + */ + val systemBars = Insets() + + /** + * Inset values which match [WindowInsetsCompat.Type.systemGestures] + */ + val systemGestures = Insets() + + /** + * Inset values which match [WindowInsetsCompat.Type.navigationBars] + */ + val navigationBars = Insets() + + /** + * Inset values which match [WindowInsetsCompat.Type.statusBars] + */ + val statusBars = Insets() + + /** + * Inset values which match [WindowInsetsCompat.Type.ime] + */ + val ime = Insets() +} + +@Stable +class Insets { + /** + * The left dimension of these insets in pixels. + */ + var left by mutableStateOf(0) + internal set + + /** + * The top dimension of these insets in pixels. + */ + var top by mutableStateOf(0) + internal set + + /** + * The right dimension of these insets in pixels. + */ + var right by mutableStateOf(0) + internal set + + /** + * The bottom dimension of these insets in pixels. + */ + var bottom by mutableStateOf(0) + internal set + + /** + * Whether the insets are currently visible. + */ + var isVisible by mutableStateOf(true) + internal set +} + +val AmbientWindowInsets = staticAmbientOf { + error("AmbientInsets value not available. Are you using ProvideWindowInsets?") +} + +/** + * Applies any [WindowInsetsCompat] values to [AmbientWindowInsets], which are then available + * within [content]. + * + * @param consumeWindowInsets Whether to consume any [WindowInsetsCompat]s which are dispatched to + * the host view. Defaults to `true`. + */ +@Composable +fun ProvideWindowInsets( + consumeWindowInsets: Boolean = true, + content: @Composable () -> Unit +) { + val view = ViewAmbient.current + + val windowInsets = remember { WindowInsets() } + + DisposableEffect(view) { + ViewCompat.setOnApplyWindowInsetsListener(view) { _, wic -> + windowInsets.systemBars.updateFrom(wic, Type.systemBars()) + windowInsets.systemGestures.updateFrom(wic, Type.systemGestures()) + windowInsets.statusBars.updateFrom(wic, Type.statusBars()) + windowInsets.navigationBars.updateFrom(wic, Type.navigationBars()) + windowInsets.ime.updateFrom(wic, Type.ime()) + + if (consumeWindowInsets) WindowInsetsCompat.CONSUMED else wic + } + + // Add an OnAttachStateChangeListener to request an inset pass each time we're attached + // to the window + val attachListener = object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) = v.requestApplyInsets() + override fun onViewDetachedFromWindow(v: View) = Unit + } + view.addOnAttachStateChangeListener(attachListener) + + if (view.isAttachedToWindow) { + // If the view is already attached, we can request an inset pass now + view.requestApplyInsets() + } + + onDispose { + view.removeOnAttachStateChangeListener(attachListener) + } + } + + Providers(AmbientWindowInsets provides windowInsets) { + content() + } +} + +/** + * Updates our mutable state backed [Insets] from an Android system insets. + */ +private fun Insets.updateFrom(windowInsets: WindowInsetsCompat, type: Int) { + val insets = windowInsets.getInsets(type) + left = insets.left + top = insets.top + right = insets.right + bottom = insets.bottom + + isVisible = windowInsets.isVisible(type) +} + +enum class HorizontalSide { Left, Right } +enum class VerticalSide { Top, Bottom } diff --git a/insets/src/main/java/dev/chrisbanes/accompanist/insets/Padding.kt b/insets/src/main/java/dev/chrisbanes/accompanist/insets/Padding.kt new file mode 100644 index 000000000..a28407817 --- /dev/null +++ b/insets/src/main/java/dev/chrisbanes/accompanist/insets/Padding.kt @@ -0,0 +1,188 @@ +/* + * 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. + */ + +@file:Suppress("NOTHING_TO_INLINE", "unused") + +@file:JvmName("ComposeInsets") +@file:JvmMultifileClass + +package dev.chrisbanes.accompanist.insets + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.ui.LayoutModifier +import androidx.compose.ui.Measurable +import androidx.compose.ui.MeasureScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.platform.DensityAmbient +import androidx.compose.ui.platform.LayoutDirectionAmbient +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.offset + +/** + * Selectively apply additional space which matches the width/height of any system bars present + * on the respective edges of the screen. + * + * @param enabled Whether to apply padding using the system bars dimensions on the respective edges. + * Defaults to `true`. + */ +fun Modifier.systemBarsPadding( + enabled: Boolean = true +): Modifier = composed { + insetsPadding( + insets = AmbientWindowInsets.current.systemBars, + left = enabled, + top = enabled, + right = enabled, + bottom = enabled + ) +} + +/** + * Apply additional space which matches the height of the status bars height along the top edge + * of the content. + */ +fun Modifier.statusBarsPadding(): Modifier = composed { + insetsPadding(insets = AmbientWindowInsets.current.statusBars, top = true) +} + +/** + * Apply additional space which matches the height of the navigation bars height + * along the [bottom] edge of the content, and additional space which matches the width of + * the navigation bars on the respective [left] and [right] edges. + * + * @param bottom Whether to apply padding to the bottom edge, which matches the navigation bars + * height (if present) at the bottom edge of the screen. Defaults to `true`. + * @param left Whether to apply padding to the left edge, which matches the navigation bars width + * (if present) on the left edge of the screen. Defaults to `true`. + * @param right Whether to apply padding to the right edge, which matches the navigation bars width + * (if present) on the right edge of the screen. Defaults to `true`. + */ +fun Modifier.navigationBarsPadding( + bottom: Boolean = true, + left: Boolean = true, + right: Boolean = true +): Modifier = composed { + insetsPadding( + insets = AmbientWindowInsets.current.navigationBars, + left = left, + right = right, + bottom = bottom + ) +} + +/** + * Allows conditional setting of [insets] on each dimension. + */ +private inline fun Modifier.insetsPadding( + insets: Insets, + left: Boolean = false, + top: Boolean = false, + right: Boolean = false, + bottom: Boolean = false, +): Modifier = this then InsetsPaddingModifier(insets, left, top, right, bottom) + +private data class InsetsPaddingModifier( + private val insets: Insets, + private val applyLeft: Boolean, + private val applyTop: Boolean, + private val applyRight: Boolean, + private val applyBottom: Boolean +) : LayoutModifier { + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureScope.MeasureResult { + val left = if (applyLeft) insets.left else 0 + val top = if (applyTop) insets.top else 0 + val right = if (applyRight) insets.right else 0 + val bottom = if (applyBottom) insets.bottom else 0 + val horizontal = left + right + val vertical = top + bottom + + val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) + + val width = (placeable.width + horizontal) + .coerceIn(constraints.minWidth, constraints.maxWidth) + val height = (placeable.height + vertical) + .coerceIn(constraints.minHeight, constraints.maxHeight) + return layout(width, height) { + placeable.place(left, top) + } + } +} + +/** + * Returns the current insets converted into a [PaddingValues]. + * + * @param start Whether to apply the inset on the start dimension. + * @param top Whether to apply the inset on the top dimension. + * @param end Whether to apply the inset on the end dimension. + * @param bottom Whether to apply the inset on the bottom dimension. + */ +@Composable +fun Insets.toPaddingValues( + start: Boolean = true, + top: Boolean = true, + end: Boolean = true, + bottom: Boolean = true +): PaddingValues = with(DensityAmbient.current) { + val layoutDirection = LayoutDirectionAmbient.current + PaddingValues( + start = when { + start && layoutDirection == LayoutDirection.Ltr -> this@toPaddingValues.left.toDp() + start && layoutDirection == LayoutDirection.Rtl -> this@toPaddingValues.right.toDp() + else -> 0.dp + }, + top = when { + top -> this@toPaddingValues.top.toDp() + else -> 0.dp + }, + end = when { + end && layoutDirection == LayoutDirection.Ltr -> this@toPaddingValues.right.toDp() + end && layoutDirection == LayoutDirection.Rtl -> this@toPaddingValues.left.toDp() + else -> 0.dp + }, + bottom = when { + bottom -> this@toPaddingValues.bottom.toDp() + else -> 0.dp + } + ) +} + +/** + * Returns a new [PaddingValues] with the provided values added to each relevant dimension. + * + * @param start Value to add to the start dimension. + * @param top Value to add to the top dimension. + * @param end Value to add to the end dimension. + * @param bottom Value to add to the bottom dimension. + */ +inline fun PaddingValues.add( + start: Dp = 0.dp, + top: Dp = 0.dp, + end: Dp = 0.dp, + bottom: Dp = 0.dp, +): PaddingValues = copy( + start = this.start + start, + top = this.top + top, + end = this.end + end, + bottom = this.bottom + bottom +) diff --git a/insets/src/main/java/dev/chrisbanes/accompanist/insets/Size.kt b/insets/src/main/java/dev/chrisbanes/accompanist/insets/Size.kt new file mode 100644 index 000000000..87b433a57 --- /dev/null +++ b/insets/src/main/java/dev/chrisbanes/accompanist/insets/Size.kt @@ -0,0 +1,259 @@ +/* + * 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. + */ + +@file:Suppress("NOTHING_TO_INLINE", "unused") + +@file:JvmName("ComposeInsets") +@file:JvmMultifileClass + +package dev.chrisbanes.accompanist.insets + +import androidx.compose.foundation.layout.height +import androidx.compose.ui.LayoutModifier +import androidx.compose.ui.Measurable +import androidx.compose.ui.MeasureScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.layout.IntrinsicMeasurable +import androidx.compose.ui.layout.IntrinsicMeasureScope +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * Declare the height of the content to match the height of the status bars exactly. + * + * This is very handy when used with `Spacer` to push content below the status bars: + * ``` + * Column { + * Spacer(Modifier.statusBarHeight()) + * + * // Content to be drawn below status bars (y-axis) + * } + * ``` + * + * It's also useful when used to draw a scrim which matches the status bars: + * ``` + * Spacer( + * Modifier.statusBarHeight() + * .fillMaxWidth() + * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) + * ) + * ``` + * + * Internally this matches the behavior of the [Modifier.height] modifier. + * + * @param additional Any additional height to add to the status bars size. + */ +fun Modifier.statusBarsHeight( + additional: Dp = 0.dp +): Modifier = composed { + InsetsSizeModifier( + insets = AmbientWindowInsets.current.statusBars, + heightSide = VerticalSide.Top, + additionalHeight = additional + ) +} + +/** + * Declare the preferred height of the content to match the height of the navigation bars when + * present at the bottom of the screen. + * + * This is very handy when used with `Spacer` to push content below the navigation bars: + * ``` + * Column { + * // Content to be drawn above status bars (y-axis) + * Spacer(Modifier.navigationBarHeight()) + * } + * ``` + * + * It's also useful when used to draw a scrim which matches the navigation bars: + * ``` + * Spacer( + * Modifier.navigationBarHeight() + * .fillMaxWidth() + * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) + * ) + * ``` + * + * Internally this matches the behavior of the [Modifier.height] modifier. + * + * @param additional Any additional height to add to the status bars size. + */ +fun Modifier.navigationBarsHeight( + additional: Dp = 0.dp +): Modifier = composed { + InsetsSizeModifier( + insets = AmbientWindowInsets.current.navigationBars, + heightSide = VerticalSide.Bottom, + additionalHeight = additional + ) +} + +/** + * Declare the preferred width of the content to match the width of the navigation bars, + * on the given [side]. + * + * This is very handy when used with `Spacer` to push content inside from any vertical + * navigation bars (typically when the device is in landscape): + * ``` + * Row { + * Spacer(Modifier.navigationBarWidth(HorizontalSide.Left)) + * + * // Content to be inside the navigation bars (x-axis) + * + * Spacer(Modifier.navigationBarWidth(HorizontalSide.Right)) + * } + * ``` + * + * It's also useful when used to draw a scrim which matches the navigation bars: + * ``` + * Spacer( + * Modifier.navigationBarWidth(HorizontalSide.Left) + * .fillMaxHeight() + * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) + * ) + * ``` + * + * Internally this matches the behavior of the [Modifier.height] modifier. + * + * @param side The navigation bar side to use as the source for the width. + * @param additional Any additional width to add to the status bars size. + */ +fun Modifier.navigationBarsWidth( + side: HorizontalSide, + additional: Dp = 0.dp +): Modifier = composed { + InsetsSizeModifier( + insets = AmbientWindowInsets.current.navigationBars, + widthSide = side, + additionalWidth = additional + ) +} + +/** + * [Modifier] class which powers the modifiers above. This is the lower level modifier which + * supports the functionality through a number of parameters. + * + * We may make this public at some point. If you need this, please let us know via the + * issue tracker. + */ +private data class InsetsSizeModifier( + private val insets: Insets, + private val widthSide: HorizontalSide? = null, + private val additionalWidth: Dp = 0.dp, + private val heightSide: VerticalSide? = null, + private val additionalHeight: Dp = 0.dp +) : LayoutModifier { + private val Density.targetConstraints: Constraints + get() { + val additionalWidthPx = additionalWidth.toIntPx() + val additionalHeightPx = additionalHeight.toIntPx() + return Constraints( + minWidth = additionalWidthPx + when (widthSide) { + HorizontalSide.Left -> insets.left + HorizontalSide.Right -> insets.right + null -> 0 + }, + minHeight = additionalHeightPx + when (heightSide) { + VerticalSide.Top -> insets.top + VerticalSide.Bottom -> insets.bottom + null -> 0 + }, + maxWidth = when (widthSide) { + HorizontalSide.Left -> insets.left + additionalWidthPx + HorizontalSide.Right -> insets.right + additionalWidthPx + null -> Constraints.Infinity + }, + maxHeight = when (heightSide) { + VerticalSide.Top -> insets.top + additionalHeightPx + VerticalSide.Bottom -> insets.bottom + additionalHeightPx + null -> Constraints.Infinity + } + ) + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureScope.MeasureResult { + val wrappedConstraints = targetConstraints.let { targetConstraints -> + val resolvedMinWidth = if (widthSide != null) { + targetConstraints.minWidth + } else { + constraints.minWidth.coerceAtMost(targetConstraints.maxWidth) + } + val resolvedMaxWidth = if (widthSide != null) { + targetConstraints.maxWidth + } else { + constraints.maxWidth.coerceAtLeast(targetConstraints.minWidth) + } + val resolvedMinHeight = if (heightSide != null) { + targetConstraints.minHeight + } else { + constraints.minHeight.coerceAtMost(targetConstraints.maxHeight) + } + val resolvedMaxHeight = if (heightSide != null) { + targetConstraints.maxHeight + } else { + constraints.maxHeight.coerceAtLeast(targetConstraints.minHeight) + } + Constraints( + resolvedMinWidth, + resolvedMaxWidth, + resolvedMinHeight, + resolvedMaxHeight + ) + } + val placeable = measurable.measure(wrappedConstraints) + return layout(placeable.width, placeable.height) { + placeable.place(0, 0) + } + } + + override fun IntrinsicMeasureScope.minIntrinsicWidth( + measurable: IntrinsicMeasurable, + height: Int + ) = measurable.minIntrinsicWidth(height).let { + val constraints = targetConstraints + it.coerceIn(constraints.minWidth, constraints.maxWidth) + } + + override fun IntrinsicMeasureScope.maxIntrinsicWidth( + measurable: IntrinsicMeasurable, + height: Int + ) = measurable.maxIntrinsicWidth(height).let { + val constraints = targetConstraints + it.coerceIn(constraints.minWidth, constraints.maxWidth) + } + + override fun IntrinsicMeasureScope.minIntrinsicHeight( + measurable: IntrinsicMeasurable, + width: Int + ) = measurable.minIntrinsicHeight(width).let { + val constraints = targetConstraints + it.coerceIn(constraints.minHeight, constraints.maxHeight) + } + + override fun IntrinsicMeasureScope.maxIntrinsicHeight( + measurable: IntrinsicMeasurable, + width: Int + ) = measurable.maxIntrinsicHeight(width).let { + val constraints = targetConstraints + it.coerceIn(constraints.minHeight, constraints.maxHeight) + } +} diff --git a/mkdocs.yml b/mkdocs.yml index 3fe236944..01f92d4de 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -17,11 +17,13 @@ nav: - 'Coil': coil.md - 'Glide': glide.md - 'Picasso': picasso.md + - 'Insets': insets.md - 'API': - 'Coil': api/coil/index.md - 'Glide': api/glide/index.md - 'Picasso': api/picasso/index.md - 'Image Loading Core': api/imageloading-core/index.md + - 'Insets': api/insets/index.md - 'Snapshots': using-snapshot-version.md - 'Contributing': contributing.md - 'Maintainers': diff --git a/sample/build.gradle b/sample/build.gradle index c413fb0e7..b61726970 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -53,6 +53,7 @@ dependencies { implementation project(':picasso') implementation project(':coil') implementation project(':glide') + implementation project(':insets') implementation Libs.Coil.gif @@ -60,10 +61,6 @@ dependencies { implementation Libs.AndroidX.Compose.material implementation Libs.AndroidX.Compose.foundation implementation Libs.AndroidX.Compose.layout - implementation Libs.AndroidX.Compose.material - - implementation Libs.AndroidX.coreKtx - implementation Libs.AndroidX.appcompat implementation Libs.Kotlin.stdlib } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 5a6ff51e6..bed416b20 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -121,6 +121,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/AccompanistSampleActivity.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/AccompanistSampleActivity.kt new file mode 100644 index 000000000..072e79b5a --- /dev/null +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/AccompanistSampleActivity.kt @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package dev.chrisbanes.accompanist.sample + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.setContent + +/** + * Simple activity which allows sub-classes to pass in a [Composable] content lambda. + */ +open class AccompanistSampleActivity( + private val content: @Composable () -> Unit +) : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + MaterialTheme { + content() + } + } + } +} diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilBasicSample.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilBasicSample.kt index b69180d2c..d9a6bf54a 100644 --- a/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilBasicSample.kt +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilBasicSample.kt @@ -18,8 +18,6 @@ package dev.chrisbanes.accompanist.sample.coil import android.content.Context import android.os.Build.VERSION.SDK_INT -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Box @@ -31,7 +29,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable @@ -39,7 +36,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ContextAmbient -import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil.ImageLoader @@ -48,20 +44,11 @@ import coil.decode.ImageDecoderDecoder import coil.request.ImageRequest import coil.transform.CircleCropTransformation import dev.chrisbanes.accompanist.coil.CoilImage +import dev.chrisbanes.accompanist.sample.AccompanistSampleActivity import dev.chrisbanes.accompanist.sample.R import dev.chrisbanes.accompanist.sample.randomSampleImageUrl -class CoilBasicSample : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - MaterialTheme { - Sample() - } - } - } -} +class CoilBasicSample : AccompanistSampleActivity(content = { Sample() }) @OptIn(ExperimentalLayout::class) @Composable diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilGridSample.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilGridSample.kt index abd46698f..2f4ee2a5d 100644 --- a/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilGridSample.kt +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilGridSample.kt @@ -16,37 +16,24 @@ package dev.chrisbanes.accompanist.sample.coil -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text import androidx.compose.foundation.layout.ExperimentalLayout import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize -import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.chrisbanes.accompanist.coil.CoilImage +import dev.chrisbanes.accompanist.sample.AccompanistSampleActivity import dev.chrisbanes.accompanist.sample.R import dev.chrisbanes.accompanist.sample.randomSampleImageUrl -class CoilGridSample : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - MaterialTheme { - Sample() - } - } - } -} +class CoilGridSample : AccompanistSampleActivity(content = { Sample() }) private const val NumberItems = 60 diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilLazyColumnSample.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilLazyColumnSample.kt index 5b5823b1d..26a4ceb52 100644 --- a/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilLazyColumnSample.kt +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/coil/CoilLazyColumnSample.kt @@ -16,8 +16,6 @@ package dev.chrisbanes.accompanist.sample.coil -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.Text import androidx.compose.foundation.layout.ExperimentalLayout import androidx.compose.foundation.layout.Row @@ -32,24 +30,14 @@ import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.chrisbanes.accompanist.coil.CoilImage +import dev.chrisbanes.accompanist.sample.AccompanistSampleActivity import dev.chrisbanes.accompanist.sample.R import dev.chrisbanes.accompanist.sample.randomSampleImageUrl -class CoilLazyColumnSample : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - MaterialTheme { - Sample() - } - } - } -} +class CoilLazyColumnSample : AccompanistSampleActivity(content = { Sample() }) private const val NumberItems = 60 diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideBasicSample.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideBasicSample.kt index a9d210532..f59d35f36 100644 --- a/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideBasicSample.kt +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideBasicSample.kt @@ -16,8 +16,6 @@ package dev.chrisbanes.accompanist.sample.glide -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Box @@ -29,31 +27,20 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.chrisbanes.accompanist.glide.GlideImage +import dev.chrisbanes.accompanist.sample.AccompanistSampleActivity import dev.chrisbanes.accompanist.sample.R import dev.chrisbanes.accompanist.sample.randomSampleImageUrl -class GlideBasicSample : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - MaterialTheme { - Sample() - } - } - } -} +class GlideBasicSample : AccompanistSampleActivity(content = { Sample() }) @OptIn(ExperimentalLayout::class) @Composable diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideGridSample.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideGridSample.kt index 708650e61..b2a16201a 100644 --- a/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideGridSample.kt +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideGridSample.kt @@ -16,37 +16,24 @@ package dev.chrisbanes.accompanist.sample.glide -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text import androidx.compose.foundation.layout.ExperimentalLayout import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize -import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.chrisbanes.accompanist.glide.GlideImage +import dev.chrisbanes.accompanist.sample.AccompanistSampleActivity import dev.chrisbanes.accompanist.sample.R import dev.chrisbanes.accompanist.sample.randomSampleImageUrl -class GlideGridSample : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - MaterialTheme { - Sample() - } - } - } -} +class GlideGridSample : AccompanistSampleActivity(content = { Sample() }) private const val NumberItems = 60 diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideLazyColumnSample.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideLazyColumnSample.kt index 83af76abc..b694bde95 100644 --- a/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideLazyColumnSample.kt +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/glide/GlideLazyColumnSample.kt @@ -16,8 +16,6 @@ package dev.chrisbanes.accompanist.sample.glide -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.Text import androidx.compose.foundation.layout.ExperimentalLayout import androidx.compose.foundation.layout.Row @@ -32,24 +30,14 @@ import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.chrisbanes.accompanist.glide.GlideImage +import dev.chrisbanes.accompanist.sample.AccompanistSampleActivity import dev.chrisbanes.accompanist.sample.R import dev.chrisbanes.accompanist.sample.randomSampleImageUrl -class GlideLazyColumnSample : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - MaterialTheme { - Sample() - } - } - } -} +class GlideLazyColumnSample : AccompanistSampleActivity(content = { Sample() }) private const val NumberItems = 60 diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/insets/EdgeToEdgeLazyColumn.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/insets/EdgeToEdgeLazyColumn.kt new file mode 100644 index 000000000..5ee40d9cf --- /dev/null +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/insets/EdgeToEdgeLazyColumn.kt @@ -0,0 +1,193 @@ +/* + * 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. + */ + +package dev.chrisbanes.accompanist.sample.insets + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.preferredSize +import androidx.compose.foundation.layout.preferredWidth +import androidx.compose.foundation.lazy.LazyColumnFor +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.FloatingActionButton +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.TopAppBar +import androidx.compose.material.contentColorFor +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Face +import androidx.compose.material.primarySurface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.onSizeChanged +import androidx.compose.ui.platform.DensityAmbient +import androidx.compose.ui.platform.setContent +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.core.view.WindowCompat +import dev.chrisbanes.accompanist.glide.GlideImage +import dev.chrisbanes.accompanist.insets.AmbientWindowInsets +import dev.chrisbanes.accompanist.insets.ProvideWindowInsets +import dev.chrisbanes.accompanist.insets.add +import dev.chrisbanes.accompanist.insets.navigationBarsPadding +import dev.chrisbanes.accompanist.insets.statusBarsPadding +import dev.chrisbanes.accompanist.insets.toPaddingValues +import dev.chrisbanes.accompanist.sample.R +import dev.chrisbanes.accompanist.sample.randomSampleImageUrl + +class EdgeToEdgeLazyColumn : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Turn off the decor fitting system windows, which means we need to through handling + // insets + WindowCompat.setDecorFitsSystemWindows(window, false) + + setContent { + MaterialTheme { + Sample() + } + } + } +} + +@OptIn(ExperimentalStdlibApi::class) +@Composable +private fun Sample() { + ProvideWindowInsets { + Surface { + Box(Modifier.fillMaxSize()) { + // A state instance which allows us to track the size of the top app bar + var topAppBarSize by remember { mutableStateOf(0) } + + LazyColumnFor( + items = listItems, + // We use the systemBar insets as the source of our content padding. + // We add on the topAppBarSize, so that the content is displayed below + // the app bar. Since the top inset is already contained within the app + // bar height, we disable handling it in toPaddingValues(). + contentPadding = AmbientWindowInsets.current.systemBars + .toPaddingValues(top = false) + .add(top = with(DensityAmbient.current) { topAppBarSize.toDp() }) + ) { imageUrl -> + ListItem(imageUrl, Modifier.fillMaxWidth()) + } + + /** + * We show a translucent app bar above which floats about the content. Our + * [InsetAwareTopAppBar] below automatically draws behind the status bar too. + */ + InsetAwareTopAppBar( + title = { Text(stringResource(R.string.insets_title_list)) }, + backgroundColor = MaterialTheme.colors.surface.copy(alpha = 0.9f), + modifier = Modifier.fillMaxWidth() + // We use onSizeChanged to track the app bar height, and update + // our state above + .onSizeChanged { topAppBarSize = it.height } + ) + + FloatingActionButton( + onClick = { /* TODO */ }, + icon = { Icon(Icons.Default.Face) }, + modifier = Modifier.align(Alignment.BottomEnd) + .navigationBarsPadding() + .padding(16.dp) + ) + } + } + } +} + +@OptIn(ExperimentalStdlibApi::class) +private val listItems = buildList { + repeat(40) { + add(randomSampleImageUrl(it)) + } +} + +/** + * A wrapper around [TopAppBar] which uses [Modifier.statusBarsPadding] to shift the app bar's + * contents down, but still draws the background behind the status bar too. + */ +@Composable +private fun InsetAwareTopAppBar( + title: @Composable () -> Unit, + modifier: Modifier = Modifier, + navigationIcon: @Composable (() -> Unit)? = null, + actions: @Composable RowScope.() -> Unit = {}, + backgroundColor: Color = MaterialTheme.colors.primarySurface, + contentColor: Color = contentColorFor(backgroundColor), + elevation: Dp = 4.dp +) { + Surface( + color = backgroundColor, + elevation = elevation, + modifier = modifier + ) { + TopAppBar( + title = title, + navigationIcon = navigationIcon, + actions = actions, + backgroundColor = Color.Transparent, + contentColor = contentColor, + elevation = 0.dp, + modifier = Modifier.statusBarsPadding() + ) + } +} + +/** + * Simple list item row which displays an image and text. + */ +@Composable +private fun ListItem( + imageUrl: String, + modifier: Modifier = Modifier +) { + Row(modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + GlideImage( + data = imageUrl, + modifier = Modifier.preferredSize(64.dp) + .clip(RoundedCornerShape(4.dp)) + ) + + Spacer(Modifier.preferredWidth(16.dp)) + + Text( + text = "Text", + style = MaterialTheme.typography.subtitle2, + modifier = Modifier.weight(1f) + .align(Alignment.CenterVertically) + ) + } +} diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/insets/InsetsBasicSample.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/insets/InsetsBasicSample.kt new file mode 100644 index 000000000..63caa392a --- /dev/null +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/insets/InsetsBasicSample.kt @@ -0,0 +1,83 @@ +/* + * 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. + */ + +package dev.chrisbanes.accompanist.sample.insets + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.FloatingActionButton +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Face +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.setContent +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.view.WindowCompat +import dev.chrisbanes.accompanist.insets.ProvideWindowInsets +import dev.chrisbanes.accompanist.insets.navigationBarsPadding +import dev.chrisbanes.accompanist.insets.statusBarsPadding +import dev.chrisbanes.accompanist.sample.R + +class InsetsBasicSample : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Turn off the decor fitting system windows, which means we need to through handling + // insets + WindowCompat.setDecorFitsSystemWindows(window, false) + + setContent { + MaterialTheme { + Sample() + } + } + } +} + +@Composable +private fun Sample() { + ProvideWindowInsets { + Box(Modifier.fillMaxSize()) { + TopAppBar( + title = { + Text(stringResource(R.string.insets_title_basic)) + }, + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth() + .statusBarsPadding() + ) + + FloatingActionButton( + onClick = { /* */ }, + icon = { Icon(Icons.Default.Face) }, + modifier = Modifier.align(Alignment.BottomEnd) + .navigationBarsPadding() + .padding(16.dp) + ) + } + } +} diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoBasicSample.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoBasicSample.kt index 79b44c663..f66b4d53b 100644 --- a/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoBasicSample.kt +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoBasicSample.kt @@ -16,8 +16,6 @@ package dev.chrisbanes.accompanist.sample.picasso -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Box @@ -29,31 +27,20 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.chrisbanes.accompanist.picasso.PicassoImage +import dev.chrisbanes.accompanist.sample.AccompanistSampleActivity import dev.chrisbanes.accompanist.sample.R import dev.chrisbanes.accompanist.sample.randomSampleImageUrl -class PicassoBasicSample : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - MaterialTheme { - Sample() - } - } - } -} +class PicassoBasicSample : AccompanistSampleActivity(content = { Sample() }) @OptIn(ExperimentalLayout::class) @Composable diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoGridSample.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoGridSample.kt index 682b9cd7c..b62d0aca8 100644 --- a/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoGridSample.kt +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoGridSample.kt @@ -16,37 +16,24 @@ package dev.chrisbanes.accompanist.sample.picasso -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text import androidx.compose.foundation.layout.ExperimentalLayout import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize -import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.chrisbanes.accompanist.picasso.PicassoImage +import dev.chrisbanes.accompanist.sample.AccompanistSampleActivity import dev.chrisbanes.accompanist.sample.R import dev.chrisbanes.accompanist.sample.randomSampleImageUrl -class PicassoGridSample : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - MaterialTheme { - Sample() - } - } - } -} +class PicassoGridSample : AccompanistSampleActivity(content = { Sample() }) private const val NumberItems = 60 diff --git a/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoLazyColumnSample.kt b/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoLazyColumnSample.kt index b37abf5a4..0f3e4c851 100644 --- a/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoLazyColumnSample.kt +++ b/sample/src/main/java/dev/chrisbanes/accompanist/sample/picasso/PicassoLazyColumnSample.kt @@ -16,8 +16,6 @@ package dev.chrisbanes.accompanist.sample.picasso -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.Text import androidx.compose.foundation.layout.ExperimentalLayout import androidx.compose.foundation.layout.Row @@ -32,24 +30,14 @@ import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.chrisbanes.accompanist.picasso.PicassoImage +import dev.chrisbanes.accompanist.sample.AccompanistSampleActivity import dev.chrisbanes.accompanist.sample.R import dev.chrisbanes.accompanist.sample.randomSampleImageUrl -class PicassoLazyColumnSample : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - MaterialTheme { - Sample() - } - } - } -} +class PicassoLazyColumnSample : AccompanistSampleActivity(content = { Sample() }) private const val NumberItems = 60 diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index a128d118a..7e8b946ae 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -28,4 +28,7 @@ Glide: Basic Glide: Grid Glide: Lazy row + + Insets: Basic + Insets: Edge-to-edge list diff --git a/sample/src/main/res/values/themes.xml b/sample/src/main/res/values/themes.xml index 41470d66a..e05c1f958 100644 --- a/sample/src/main/res/values/themes.xml +++ b/sample/src/main/res/values/themes.xml @@ -14,7 +14,7 @@ ~ limitations under the License. --> - + + + diff --git a/settings.gradle b/settings.gradle index fecc699ac..a1c4443d2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -30,4 +30,5 @@ include ':picasso' include ':glide' include ':imageloading-core' include ':imageloading-testutils' +include ':insets' include ':sample'