diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 265ff2ba5..efe4ef29a 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -10,7 +10,7 @@ Thanks for submitting a pull request. To accept your pull request we need you do
**Ensure tests pass and code is formatted correctly**
- Run local tests on the `DemoDebug` variant by running `./gradlew testDemoDebug`
-- Fix code formatting: `./gradlew --init-script gradle/init.gradle.kts spotlessApply`
+- Fix code formatting: `./gradlew spotlessApply`
**Add a description**
diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml
index 076fd2acc..643ad0b92 100644
--- a/.github/workflows/Build.yaml
+++ b/.github/workflows/Build.yaml
@@ -48,7 +48,35 @@ jobs:
run: ./gradlew :build-logic:convention:check
- name: Check spotless
- run: ./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache
+ run: ./gradlew spotlessCheck
+
+ - name: Update Graphs
+ id: graphs_update
+ continue-on-error: true
+ run: ./gradlew graphUpdate
+
+ - name: Check Graphs
+ id: graphs_verify
+ run: |
+ if ! git diff --quiet "**/README.md"; then
+ echo "::error::Module graph updates detected. Please run './gradlew graphUpdate' locally and commit the changes."
+ exit 1
+ fi
+
+ - name: Prevent updating graphs if this is a fork
+ id: checkfork_graphs
+ continue-on-error: false
+ if: steps.graphs_verify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
+ run: |
+ echo "::error::Module graph updates detected, please update graphs with: ./gradlew graphUpdate" && exit 1
+
+ - name: Push updated graphs if available
+ uses: stefanzweifel/git-auto-commit-action@v5
+ if: steps.graphs_update.outcome == 'success' && steps.graphs_verify.outcome == 'failure' && github.event_name == 'pull_request'
+ with:
+ file_pattern: '**/README.md'
+ disable_globbing: true
+ commit_message: "Updates module graphs"
- name: Check Dependency Guard
id: dependencyguard_verify
diff --git a/.run/spotlessApply.run.xml b/.run/spotlessApply.run.xml
index 5c4dac833..c79311274 100644
--- a/.run/spotlessApply.run.xml
+++ b/.run/spotlessApply.run.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/AGENTS.md b/AGENTS.md
index 84ae649c1..8ba1d47b8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -11,7 +11,7 @@ Now in Android (KMP edition) — a Kotlin Multiplatform fork of Google's Now in
```bash
# Format code (must pass before merge)
./gradlew spotlessApply
-./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache
+./gradlew spotlessCheck
# Build
./gradlew :app:assemble # main app (all variants)
@@ -43,6 +43,9 @@ Now in Android (KMP edition) — a Kotlin Multiplatform fork of Google's Now in
# Build-logic check
./gradlew :build-logic:convention:check
+# Module graphs
+./gradlew graphUpdate # update README.md module graphs
+
# Badging check
./gradlew :app:checkReleaseBadging
```
diff --git a/app-nia-catalog/build.gradle.kts b/app-nia-catalog/build.gradle.kts
index abaf5b1eb..fc1404d7d 100644
--- a/app-nia-catalog/build.gradle.kts
+++ b/app-nia-catalog/build.gradle.kts
@@ -26,6 +26,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.compose)
alias(libs.plugins.compose)
+ alias(libs.plugins.spotless)
}
kotlin {
@@ -97,8 +98,8 @@ android {
versionCode = 1
versionName = "0.0.1" // X.Y.Z; X = Major, Y = minor, Z = Patch level
minSdk = 24
- targetSdk = 34
- compileSdk = 35
+ targetSdk = 36
+ compileSdk = 36
// The UI catalog does not depend on content from the app, however, it depends on modules
// which do, so we must specify a default value for the contentType dimension.
missingDimensionStrategy("contentType", "demo")
@@ -147,10 +148,24 @@ compose.desktop {
}
}
-compose.experimental {
-
-}
-
dependencyGuard {
configuration("releaseRuntimeClasspath")
}
+
+spotless {
+ kotlin {
+ target("src/**/*.kt")
+ ktlint(libs.versions.ktlint.get())
+ .editorConfigOverride(mapOf("android" to "true"))
+ licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
+ }
+ format("kts") {
+ target("*.kts")
+ targetExclude("**/build/**/*.kts")
+ licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
+ }
+ format("xml") {
+ target("src/**/*.xml")
+ licenseHeaderFile(rootProject.file("spotless/copyright.xml"), "(<[^!?])")
+ }
+}
diff --git a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt
index c48e02b95..8cae66b47 100644
--- a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt
+++ b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt
@@ -11,83 +11,100 @@ androidx.autofill:autofill:1.0.0
androidx.collection:collection-jvm:1.5.0
androidx.collection:collection-ktx:1.5.0
androidx.collection:collection:1.5.0
-androidx.compose.animation:animation-android:1.8.0
-androidx.compose.animation:animation-core-android:1.8.0
-androidx.compose.animation:animation-core:1.8.0
-androidx.compose.animation:animation:1.8.0
-androidx.compose.foundation:foundation-android:1.8.0
-androidx.compose.foundation:foundation-layout-android:1.8.0
-androidx.compose.foundation:foundation-layout:1.8.0
-androidx.compose.foundation:foundation:1.8.0
+androidx.compose.animation:animation-android:1.10.0
+androidx.compose.animation:animation-core-android:1.10.0
+androidx.compose.animation:animation-core:1.10.0
+androidx.compose.animation:animation:1.10.0
+androidx.compose.foundation:foundation-android:1.10.0
+androidx.compose.foundation:foundation-layout-android:1.10.0
+androidx.compose.foundation:foundation-layout:1.10.0
+androidx.compose.foundation:foundation:1.10.0
androidx.compose.material3.adaptive:adaptive-android:1.1.0
androidx.compose.material3.adaptive:adaptive-layout-android:1.1.0
androidx.compose.material3.adaptive:adaptive-layout:1.1.0
androidx.compose.material3.adaptive:adaptive:1.1.0
-androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.2
-androidx.compose.material3:material3-adaptive-navigation-suite:1.3.2
-androidx.compose.material3:material3-android:1.3.2
-androidx.compose.material3:material3:1.3.2
-androidx.compose.material:material-android:1.8.0
+androidx.compose.material3:material3-adaptive-navigation-suite-android:1.4.0
+androidx.compose.material3:material3-adaptive-navigation-suite:1.4.0
+androidx.compose.material3:material3-android:1.4.0
+androidx.compose.material3:material3:1.4.0
+androidx.compose.material:material-android:1.10.0
androidx.compose.material:material-icons-core-android:1.7.6
androidx.compose.material:material-icons-core:1.7.6
androidx.compose.material:material-icons-extended-android:1.7.6
androidx.compose.material:material-icons-extended:1.7.6
-androidx.compose.material:material-ripple-android:1.8.0
-androidx.compose.material:material-ripple:1.8.0
-androidx.compose.material:material:1.8.0
-androidx.compose.runtime:runtime-android:1.8.1
-androidx.compose.runtime:runtime-saveable-android:1.8.1
-androidx.compose.runtime:runtime-saveable:1.8.1
-androidx.compose.runtime:runtime:1.8.1
-androidx.compose.ui:ui-android:1.8.1
-androidx.compose.ui:ui-geometry-android:1.8.1
-androidx.compose.ui:ui-geometry:1.8.1
-androidx.compose.ui:ui-graphics-android:1.8.1
-androidx.compose.ui:ui-graphics:1.8.1
-androidx.compose.ui:ui-text-android:1.8.1
-androidx.compose.ui:ui-text:1.8.1
-androidx.compose.ui:ui-tooling-preview-android:1.8.1
-androidx.compose.ui:ui-tooling-preview:1.8.1
-androidx.compose.ui:ui-unit-android:1.8.1
-androidx.compose.ui:ui-unit:1.8.1
-androidx.compose.ui:ui-util-android:1.8.1
-androidx.compose.ui:ui-util:1.8.1
-androidx.compose.ui:ui:1.8.1
+androidx.compose.material:material-ripple-android:1.10.0
+androidx.compose.material:material-ripple:1.10.0
+androidx.compose.material:material:1.10.0
+androidx.compose.runtime:runtime-android:1.10.0
+androidx.compose.runtime:runtime-annotation-android:1.10.0
+androidx.compose.runtime:runtime-annotation:1.10.0
+androidx.compose.runtime:runtime-retain-android:1.10.0
+androidx.compose.runtime:runtime-retain:1.10.0
+androidx.compose.runtime:runtime-saveable-android:1.10.0
+androidx.compose.runtime:runtime-saveable:1.10.0
+androidx.compose.runtime:runtime:1.10.0
+androidx.compose.ui:ui-android:1.10.0
+androidx.compose.ui:ui-geometry-android:1.10.0
+androidx.compose.ui:ui-geometry:1.10.0
+androidx.compose.ui:ui-graphics-android:1.10.0
+androidx.compose.ui:ui-graphics:1.10.0
+androidx.compose.ui:ui-text-android:1.10.0
+androidx.compose.ui:ui-text:1.10.0
+androidx.compose.ui:ui-tooling-preview-android:1.10.0
+androidx.compose.ui:ui-tooling-preview:1.10.0
+androidx.compose.ui:ui-unit-android:1.10.0
+androidx.compose.ui:ui-unit:1.10.0
+androidx.compose.ui:ui-util-android:1.10.0
+androidx.compose.ui:ui-util:1.10.0
+androidx.compose.ui:ui:1.10.0
androidx.concurrent:concurrent-futures:1.1.0
-androidx.core:core-ktx:1.15.0
-androidx.core:core:1.15.0
+androidx.core:core-ktx:1.16.0
+androidx.core:core-viewtree:1.0.0
+androidx.core:core:1.16.0
androidx.customview:customview-poolingcontainer:1.0.0
+androidx.documentfile:documentfile:1.0.0
+androidx.dynamicanimation:dynamicanimation:1.0.0
androidx.emoji2:emoji2:1.4.0
androidx.exifinterface:exifinterface:1.4.1
androidx.graphics:graphics-path:1.0.1
androidx.interpolator:interpolator:1.0.0
-androidx.lifecycle:lifecycle-common-java8:2.8.7
-androidx.lifecycle:lifecycle-common-jvm:2.8.7
-androidx.lifecycle:lifecycle-common:2.8.7
-androidx.lifecycle:lifecycle-livedata-core:2.8.7
-androidx.lifecycle:lifecycle-process:2.8.7
-androidx.lifecycle:lifecycle-runtime-android:2.8.7
-androidx.lifecycle:lifecycle-runtime-compose-android:2.8.7
-androidx.lifecycle:lifecycle-runtime-compose:2.8.7
-androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.7
-androidx.lifecycle:lifecycle-runtime-ktx:2.8.7
-androidx.lifecycle:lifecycle-runtime:2.8.7
-androidx.lifecycle:lifecycle-viewmodel-android:2.8.7
-androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7
-androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.7
-androidx.lifecycle:lifecycle-viewmodel:2.8.7
+androidx.legacy:legacy-support-core-utils:1.0.0
+androidx.lifecycle:lifecycle-common-java8:2.9.4
+androidx.lifecycle:lifecycle-common-jvm:2.9.4
+androidx.lifecycle:lifecycle-common:2.9.4
+androidx.lifecycle:lifecycle-livedata-core-ktx:2.9.4
+androidx.lifecycle:lifecycle-livedata-core:2.9.4
+androidx.lifecycle:lifecycle-livedata:2.9.4
+androidx.lifecycle:lifecycle-process:2.9.4
+androidx.lifecycle:lifecycle-runtime-android:2.9.4
+androidx.lifecycle:lifecycle-runtime-compose-android:2.9.4
+androidx.lifecycle:lifecycle-runtime-compose:2.9.4
+androidx.lifecycle:lifecycle-runtime-ktx-android:2.9.4
+androidx.lifecycle:lifecycle-runtime-ktx:2.9.4
+androidx.lifecycle:lifecycle-runtime:2.9.4
+androidx.lifecycle:lifecycle-viewmodel-android:2.9.4
+androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4
+androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.9.4
+androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.4
+androidx.lifecycle:lifecycle-viewmodel:2.9.4
+androidx.loader:loader:1.0.0
+androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
+androidx.print:print:1.0.0
androidx.profileinstaller:profileinstaller:1.4.1
-androidx.savedstate:savedstate-ktx:1.2.1
-androidx.savedstate:savedstate:1.2.1
+androidx.savedstate:savedstate-android:1.3.3
+androidx.savedstate:savedstate-compose-android:1.3.3
+androidx.savedstate:savedstate-compose:1.3.3
+androidx.savedstate:savedstate-ktx:1.3.3
+androidx.savedstate:savedstate:1.3.3
androidx.startup:startup-runtime:1.1.1
androidx.tracing:tracing:1.2.0
+androidx.transition:transition:1.6.0
androidx.vectordrawable:vectordrawable-animated:1.1.0
androidx.vectordrawable:vectordrawable:1.1.0
androidx.versionedparcelable:versionedparcelable:1.1.1
-androidx.window.extensions.core:core:1.0.0
-androidx.window:window-core-android:1.3.0
-androidx.window:window-core:1.3.0
-androidx.window:window:1.3.0
+androidx.window:window-core-android:1.5.0
+androidx.window:window-core:1.5.0
+androidx.window:window:1.5.0
com.google.accompanist:accompanist-drawablepainter:0.37.3
com.google.guava:listenablefuture:1.0
com.squareup.okio:okio-jvm:3.11.0
@@ -96,43 +113,49 @@ io.coil-kt.coil3:coil-compose-core-android:3.2.0
io.coil-kt.coil3:coil-compose-core:3.2.0
io.coil-kt.coil3:coil-core-android:3.2.0
io.coil-kt.coil3:coil-core:3.2.0
-org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.4
-org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.8.4
-org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.8.4
-org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.4
-org.jetbrains.androidx.window:window-core:1.3.1
-org.jetbrains.compose.animation:animation-core:1.8.0
-org.jetbrains.compose.animation:animation:1.8.0
-org.jetbrains.compose.annotation-internal:annotation:1.8.0
-org.jetbrains.compose.collection-internal:collection:1.8.0
-org.jetbrains.compose.components:components-resources-android:1.8.0
-org.jetbrains.compose.components:components-resources:1.8.0
-org.jetbrains.compose.components:components-ui-tooling-preview-android:1.8.0
-org.jetbrains.compose.components:components-ui-tooling-preview:1.8.0
-org.jetbrains.compose.foundation:foundation-layout:1.8.0
-org.jetbrains.compose.foundation:foundation:1.8.0
+org.jetbrains.androidx.lifecycle:lifecycle-common:2.9.6
+org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.9.6
+org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.9.6
+org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.6
+org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.9.6
+org.jetbrains.androidx.savedstate:savedstate-compose:1.3.6
+org.jetbrains.androidx.savedstate:savedstate:1.3.6
+org.jetbrains.androidx.window:window-core:1.4.0
+org.jetbrains.compose.animation:animation-core:1.10.0
+org.jetbrains.compose.animation:animation:1.10.0
+org.jetbrains.compose.annotation-internal:annotation:1.10.0
+org.jetbrains.compose.collection-internal:collection:1.10.0
+org.jetbrains.compose.components:components-resources-android:1.10.0
+org.jetbrains.compose.components:components-resources:1.10.0
+org.jetbrains.compose.components:components-ui-tooling-preview-android:1.10.0
+org.jetbrains.compose.components:components-ui-tooling-preview:1.10.0
+org.jetbrains.compose.foundation:foundation-layout:1.10.0
+org.jetbrains.compose.foundation:foundation:1.10.0
org.jetbrains.compose.material3.adaptive:adaptive-layout:1.1.0
-org.jetbrains.compose.material3.adaptive:adaptive:1.1.0
-org.jetbrains.compose.material3:material3-adaptive-navigation-suite:1.8.0
-org.jetbrains.compose.material3:material3:1.8.0
+org.jetbrains.compose.material3.adaptive:adaptive:1.1.2
+org.jetbrains.compose.material3:material3-adaptive-navigation-suite:1.9.0
+org.jetbrains.compose.material3:material3:1.9.0
org.jetbrains.compose.material:material-icons-core:1.7.3
org.jetbrains.compose.material:material-icons-extended:1.7.3
-org.jetbrains.compose.material:material-ripple:1.8.0
-org.jetbrains.compose.material:material:1.8.0
-org.jetbrains.compose.runtime:runtime-saveable:1.8.0
-org.jetbrains.compose.runtime:runtime:1.8.0
-org.jetbrains.compose.ui:ui-backhandler-android:1.8.0
-org.jetbrains.compose.ui:ui-backhandler:1.8.0
-org.jetbrains.compose.ui:ui-geometry:1.8.0
-org.jetbrains.compose.ui:ui-graphics:1.8.0
-org.jetbrains.compose.ui:ui-text:1.8.0
-org.jetbrains.compose.ui:ui-unit:1.8.0
-org.jetbrains.compose.ui:ui-util:1.8.0
-org.jetbrains.compose.ui:ui:1.8.0
-org.jetbrains.kotlin:kotlin-stdlib:2.1.20
+org.jetbrains.compose.material:material-ripple:1.10.0
+org.jetbrains.compose.material:material:1.10.0
+org.jetbrains.compose.runtime:runtime-saveable:1.10.0
+org.jetbrains.compose.runtime:runtime:1.10.0
+org.jetbrains.compose.ui:ui-backhandler-android:1.9.1
+org.jetbrains.compose.ui:ui-backhandler:1.9.1
+org.jetbrains.compose.ui:ui-geometry:1.10.0
+org.jetbrains.compose.ui:ui-graphics:1.10.0
+org.jetbrains.compose.ui:ui-text:1.10.0
+org.jetbrains.compose.ui:ui-unit:1.10.0
+org.jetbrains.compose.ui:ui-util:1.10.0
+org.jetbrains.compose.ui:ui:1.10.0
+org.jetbrains.kotlin:kotlin-stdlib:2.3.0
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2
+org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3
+org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3
+org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3
org.jetbrains:annotations:23.0.0
org.jspecify:jspecify:1.0.0
diff --git a/app/release-badging.txt b/app/release-badging.txt
index 5c1511f5b..95054fd52 100644
--- a/app/release-badging.txt
+++ b/app/release-badging.txt
@@ -1,6 +1,6 @@
-package: name='com.google.samples.apps.nowinandroid' versionCode='8' versionName='0.1.2' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15'
-minSdkVersion:'21'
-targetSdkVersion:'34'
+package: name='com.google.samples.apps.nowinandroid' versionCode='8' versionName='0.1.2' platformBuildVersionName='16' platformBuildVersionCode='36' compileSdkVersion='36' compileSdkVersionCodename='16'
+minSdkVersion:'23'
+targetSdkVersion:'36'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
uses-permission: name='android.permission.POST_NOTIFICATIONS'
diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts
index 104cf1e6a..42d4d57b3 100644
--- a/build-logic/convention/build.gradle.kts
+++ b/build-logic/convention/build.gradle.kts
@@ -42,6 +42,7 @@ dependencies {
compileOnly(libs.compose.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
compileOnly(libs.ksp.gradlePlugin)
+ compileOnly(libs.spotless.gradlePlugin)
implementation(libs.truth)
lintChecks(libs.androidx.lint.gradle)
}
@@ -99,5 +100,9 @@ gradlePlugin {
id = "nowinandroid.di.koin"
implementationClass = "KoinConventionPlugin"
}
+ register("root") {
+ id = "nowinandroid.root"
+ implementationClass = "RootConventionPlugin"
+ }
}
}
diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt
index b0eece41d..c16b87a7e 100644
--- a/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt
@@ -1,17 +1,17 @@
/*
* 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
+ * 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
+ * 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.
+ * 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 com.android.build.api.dsl.ApplicationExtension
diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt
index d249e4cbf..6f7db9186 100644
--- a/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt
@@ -1,17 +1,17 @@
/*
* 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
+ * 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
+ * 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.
+ * 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 com.android.build.api.dsl.LibraryExtension
diff --git a/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt
index 884d6f076..c701983b1 100644
--- a/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt
@@ -1,17 +1,17 @@
/*
* 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
+ * 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
+ * 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.
+ * 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 com.android.build.api.dsl.ApplicationExtension
diff --git a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt
index 49c2eecec..e87b43290 100644
--- a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt
@@ -1,22 +1,23 @@
/*
* 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
+ * 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
+ * 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.
+ * 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 com.android.build.api.dsl.TestExtension
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.configureKotlinAndroid
+import com.google.samples.apps.nowinandroid.configureSpotlessForAndroid
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
@@ -27,10 +28,11 @@ class AndroidTestConventionPlugin : Plugin {
with(target) {
apply(plugin = "com.android.test")
apply(plugin = "org.jetbrains.kotlin.android")
+ configureSpotlessForAndroid()
extensions.configure {
configureKotlinAndroid(this)
- defaultConfig.targetSdk = 35
+ defaultConfig.targetSdk = 36
configureGradleManagedDevices(this)
}
}
diff --git a/build-logic/convention/src/main/kotlin/CmpApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/CmpApplicationConventionPlugin.kt
index aee833ee7..7cbafa706 100644
--- a/build-logic/convention/src/main/kotlin/CmpApplicationConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/CmpApplicationConventionPlugin.kt
@@ -1,17 +1,17 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * 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.
+ * 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 com.android.build.api.dsl.ApplicationExtension
@@ -20,6 +20,7 @@ import com.google.samples.apps.nowinandroid.configureBadgingTasks
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.configureKotlinAndroid
import com.google.samples.apps.nowinandroid.configurePrintApksTask
+import com.google.samples.apps.nowinandroid.configureSpotlessForAndroid
import com.google.samples.apps.nowinandroid.libs
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
@@ -44,9 +45,10 @@ class CmpApplicationConventionPlugin : Plugin {
// apply("com.dropbox.dependency-guard")
}
configureComposeMultiplatformApp()
+ configureSpotlessForAndroid()
extensions.configure {
configureKotlinAndroid(this)
- defaultConfig.targetSdk = 34
+ defaultConfig.targetSdk = 36
@Suppress("UnstableApiUsage")
testOptions.animationsDisabled = true
configureGradleManagedDevices(this)
@@ -68,7 +70,6 @@ class CmpApplicationConventionPlugin : Plugin {
"androidMainImplementation"(libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
"androidMainImplementation"(libs.findLibrary("androidx.tracing.ktx").get())
}
-
}
}
}
diff --git a/build-logic/convention/src/main/kotlin/CmpFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/CmpFeatureConventionPlugin.kt
index 674feab48..e60bc0aa0 100644
--- a/build-logic/convention/src/main/kotlin/CmpFeatureConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/CmpFeatureConventionPlugin.kt
@@ -1,17 +1,17 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * 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.
+ * 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 com.android.build.gradle.LibraryExtension
@@ -21,7 +21,6 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
-import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginExtension
// Convention plugin for the Compose Multiplatform feature module
class CmpFeatureConventionPlugin : Plugin {
@@ -62,7 +61,6 @@ class CmpFeatureConventionPlugin : Plugin {
"androidInstrumentedTestImplementation"(libs.findLibrary("androidx.test.junit").get())
"androidInstrumentedTestImplementation"(libs.findLibrary("androidx.test.runner").get())
}
-
}
}
}
diff --git a/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt
index a1477891d..f2be5c8ac 100644
--- a/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt
@@ -1,20 +1,21 @@
/*
* 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
+ * 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
+ * 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.
+ * 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 com.google.samples.apps.nowinandroid.configureKotlinJvm
+import com.google.samples.apps.nowinandroid.configureSpotlessForJvm
import com.google.samples.apps.nowinandroid.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
@@ -28,6 +29,7 @@ class JvmLibraryConventionPlugin : Plugin {
apply(plugin = "nowinandroid.android.lint")
configureKotlinJvm()
+ configureSpotlessForJvm()
dependencies {
"testImplementation"(libs.findLibrary("kotlin.test").get())
}
diff --git a/build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt
index 2c9a33b29..789d6a06b 100644
--- a/build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt
@@ -18,21 +18,23 @@ import com.android.build.gradle.LibraryExtension
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.configureKotlinAndroid
import com.google.samples.apps.nowinandroid.configureKotlinMultiplatform
+import com.google.samples.apps.nowinandroid.configureSpotlessForAndroid
import com.google.samples.apps.nowinandroid.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
-class KmpLibraryConventionPlugin: Plugin {
+class KmpLibraryConventionPlugin : Plugin {
override fun apply(target: Project) {
with(target) {
plugins.apply("com.android.library")
plugins.apply("org.jetbrains.kotlin.multiplatform")
configureKotlinMultiplatform()
+ configureSpotlessForAndroid()
extensions.configure {
configureKotlinAndroid(this)
- defaultConfig.targetSdk = 34
+ defaultConfig.targetSdk = 36
configureGradleManagedDevices(this)
// The resource prefix is derived from the module name,
// so resources inside ":core:module1" must be prefixed with "core_module1_"
diff --git a/build-logic/convention/src/main/kotlin/KoinConventionPlugin.kt b/build-logic/convention/src/main/kotlin/KoinConventionPlugin.kt
index 81022eeda..61894022a 100644
--- a/build-logic/convention/src/main/kotlin/KoinConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/KoinConventionPlugin.kt
@@ -21,7 +21,7 @@ import org.gradle.kotlin.dsl.dependencies
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
-class KoinConventionPlugin: Plugin {
+class KoinConventionPlugin : Plugin {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
@@ -49,7 +49,16 @@ class KoinConventionPlugin: Plugin {
}
project.tasks.withType(KotlinCompilationTask::class.java).configureEach {
- if(name != "kspCommonMainKotlinMetadata") {
+ if (name != "kspCommonMainKotlinMetadata") {
+ dependsOn("kspCommonMainKotlinMetadata")
+ }
+ }
+ // KSP2 uses KspAATask which isn't a KotlinCompilationTask but still
+ // needs to depend on kspCommonMainKotlinMetadata for the shared source set.
+ project.tasks.configureEach {
+ if (name.startsWith("ksp") && name != "kspCommonMainKotlinMetadata" &&
+ name.contains("Kotlin")
+ ) {
dependsOn("kspCommonMainKotlinMetadata")
}
}
diff --git a/build-logic/convention/src/main/kotlin/KotlinInjectConventionPlugin.kt b/build-logic/convention/src/main/kotlin/KotlinInjectConventionPlugin.kt
index cd151a5b2..a31042b22 100644
--- a/build-logic/convention/src/main/kotlin/KotlinInjectConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/KotlinInjectConventionPlugin.kt
@@ -19,7 +19,7 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
-class KotlinInjectConventionPlugin: Plugin {
+class KotlinInjectConventionPlugin : Plugin {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
@@ -42,4 +42,4 @@ class KotlinInjectConventionPlugin: Plugin {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/build-logic/convention/src/main/kotlin/RootConventionPlugin.kt b/build-logic/convention/src/main/kotlin/RootConventionPlugin.kt
new file mode 100644
index 000000000..ca8f77144
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/RootConventionPlugin.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2025 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 com.google.samples.apps.nowinandroid.configureGraphTasks
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class RootConventionPlugin : Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ configureGraphTasks()
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/SqlDelightConventionPlugin.kt b/build-logic/convention/src/main/kotlin/SqlDelightConventionPlugin.kt
index 29bc3bf54..688d8502a 100644
--- a/build-logic/convention/src/main/kotlin/SqlDelightConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/SqlDelightConventionPlugin.kt
@@ -17,10 +17,10 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
-class SqlDelightConventionPlugin: Plugin {
+class SqlDelightConventionPlugin : Plugin {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("app.cash.sqldelight")
}
}
-}
\ No newline at end of file
+}
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt
index c51dac5c9..9531e9caf 100644
--- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt
@@ -1,17 +1,17 @@
/*
* 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
+ * 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
+ * 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.
+ * 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.samples.apps.nowinandroid
@@ -30,6 +30,6 @@ import org.gradle.api.Project
internal fun LibraryAndroidComponentsExtension.disableUnnecessaryAndroidTests(
project: Project,
) = beforeVariants {
- it.androidTest.enable = it.androidTest.enable
- && project.projectDir.resolve("src/androidTest").exists()
+ it.androidTest.enable = it.androidTest.enable &&
+ project.projectDir.resolve("src/androidTest").exists()
}
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt
index e747325ee..803ee00e7 100644
--- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt
@@ -1,17 +1,17 @@
/*
* 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
+ * 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
+ * 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.
+ * 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.samples.apps.nowinandroid
@@ -123,7 +123,6 @@ fun Project.configureBadgingTasks(
badging = project.layout.buildDirectory.file(
"outputs/apk_from_bundle/${variant.name}/${variant.name}-badging.txt",
)
-
}
val updateBadgingTaskName = "update${capitalizedVariantName}Badging"
@@ -141,7 +140,6 @@ fun Project.configureBadgingTasks(
this.updateBadgingTaskName = updateBadgingTaskName
output = project.layout.buildDirectory.dir("intermediates/$checkBadgingTaskName")
-
}
}
}
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt
index bb4252577..2cadff20d 100644
--- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt
@@ -1,17 +1,17 @@
/*
* 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
+ * 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
+ * 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.
+ * 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.samples.apps.nowinandroid
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Graph.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Graph.kt
new file mode 100644
index 000000000..9d0c1db35
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Graph.kt
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2025 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.samples.apps.nowinandroid
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.ProjectDependency
+import java.io.File
+
+/**
+ * Module types for the KMP fork of Now in Android.
+ * Maps to convention plugin IDs.
+ */
+enum class PluginType(
+ val id: String,
+ val displayName: String,
+ val color: String,
+) {
+ CmpApplication(
+ id = "nowinandroid.cmp.application",
+ displayName = "cmp-application",
+ color = "#CAFFBF",
+ ),
+ CmpFeature(
+ id = "nowinandroid.cmp.feature",
+ displayName = "cmp-feature",
+ color = "#FFD6A5",
+ ),
+ KmpLibrary(
+ id = "nowinandroid.kmp.library",
+ displayName = "kmp-library",
+ color = "#9BF6FF",
+ ),
+ JvmLibrary(
+ id = "nowinandroid.jvm.library",
+ displayName = "jvm-library",
+ color = "#BDB2FF",
+ ),
+ AndroidTest(
+ id = "nowinandroid.android.test",
+ displayName = "android-test",
+ color = "#A0C4FF",
+ ),
+}
+
+/**
+ * Edge types representing different dependency configurations.
+ */
+enum class EdgeType(val mermaidStyle: String) {
+ Api("-->"),
+ Implementation("-.->"),
+}
+
+data class GraphEdge(
+ val from: String,
+ val to: String,
+ val type: EdgeType,
+ val label: String? = null,
+)
+
+private val supportedConfigurations = listOf(
+ "commonMainApi" to EdgeType.Api,
+ "commonMainImplementation" to EdgeType.Implementation,
+ "api" to EdgeType.Api,
+ "implementation" to EdgeType.Implementation,
+ "baselineProfile" to EdgeType.Implementation,
+ "testedApks" to EdgeType.Implementation,
+)
+
+private val labeledConfigurations = setOf("baselineProfile", "testedApks")
+
+/**
+ * Detects the plugin type of a project based on which convention plugins are applied.
+ */
+fun Project.pluginType(): PluginType? {
+ return PluginType.entries.firstOrNull { pluginManager.hasPlugin(it.id) }
+}
+
+/**
+ * Collects all project dependency edges for the given project.
+ */
+fun Project.collectEdges(): List {
+ val edges = mutableListOf()
+ for ((configName, edgeType) in supportedConfigurations) {
+ val config = configurations.findByName(configName) ?: continue
+ config.dependencies.filterIsInstance().forEach { dep ->
+ val label = if (configName in labeledConfigurations) configName else null
+ val depPath = dep.path
+ edges.add(GraphEdge(path, depPath, edgeType, label))
+ }
+ }
+ return edges
+}
+
+/**
+ * Generates the Mermaid graph text for a given root project, showing all
+ * modules that are reachable from the specified project.
+ */
+fun generateMermaidGraph(
+ rootProjectPath: String,
+ allEdges: Map>,
+ allPluginTypes: Map,
+ ignoredProjects: Set = emptySet(),
+): String {
+ // Find all reachable modules from the root
+ val reachable = mutableSetOf(rootProjectPath)
+ val queue = ArrayDeque()
+ queue.add(rootProjectPath)
+ while (queue.isNotEmpty()) {
+ val current = queue.removeFirst()
+ allEdges[current]?.forEach { edge ->
+ if (edge.to !in reachable && edge.to !in ignoredProjects) {
+ reachable.add(edge.to)
+ queue.add(edge.to)
+ }
+ }
+ }
+
+ val relevantEdges = allEdges.values.flatten()
+ .filter { it.from in reachable && it.to in reachable }
+ .filter { it.from !in ignoredProjects && it.to !in ignoredProjects }
+ .sortedWith(compareBy({ it.from }, { it.to }))
+
+ val modules = reachable.filter { it !in ignoredProjects }.sorted()
+
+ // Group modules by top-level parent for subgraph generation
+ val grouped = modules.groupBy { path ->
+ val parts = path.removePrefix(":").split(":")
+ if (parts.size > 1) ":${parts.first()}" else null
+ }
+
+ val sb = StringBuilder()
+ sb.appendLine("```mermaid")
+ sb.appendLine("---")
+ sb.appendLine("config:")
+ sb.appendLine(" layout: elk")
+ sb.appendLine(" elk:")
+ sb.appendLine(" nodePlacementStrategy: SIMPLE")
+ sb.appendLine("---")
+ sb.appendLine("graph TB")
+
+ // Render subgraphs for grouped modules
+ for ((group, members) in grouped.toSortedMap(nullsLast(compareBy { it }))) {
+ if (group != null && members.size > 1) {
+ sb.appendLine(" subgraph $group")
+ sb.appendLine(" direction TB")
+ for (member in members.sorted()) {
+ val shortName = member.split(":").last()
+ val pluginType = allPluginTypes[member]
+ val classDef = pluginType?.displayName ?: "unknown"
+ sb.appendLine(" $member[$shortName]:::$classDef")
+ }
+ sb.appendLine(" end")
+ }
+ }
+
+ // Render ungrouped modules (top-level modules)
+ for ((group, members) in grouped.toSortedMap(nullsLast(compareBy { it }))) {
+ if (group == null || members.size == 1) {
+ for (member in members.sorted()) {
+ val shortName = member.split(":").last()
+ val pluginType = allPluginTypes[member]
+ val classDef = pluginType?.displayName ?: "unknown"
+ sb.appendLine(" $member[$shortName]:::$classDef")
+ }
+ }
+ }
+
+ sb.appendLine()
+
+ // Render edges
+ for (edge in relevantEdges) {
+ val labelPart = if (edge.label != null) "|${edge.label}| " else ""
+ sb.appendLine(" ${edge.from} ${edge.type.mermaidStyle}$labelPart ${edge.to}")
+ }
+
+ sb.appendLine()
+
+ // Render classDef styles
+ val usedTypes = modules.mapNotNull { allPluginTypes[it] }.toSet()
+ for (type in PluginType.entries) {
+ sb.appendLine("classDef ${type.displayName} fill:${type.color},stroke:#000,stroke-width:2px,color:#000;")
+ }
+ sb.appendLine("classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;")
+
+ sb.appendLine("```")
+ return sb.toString()
+}
+
+/**
+ * Generates the legend section for the graph.
+ */
+fun generateLegend(): String {
+ val sb = StringBuilder()
+ sb.appendLine("Graph legend")
+ sb.appendLine()
+ sb.appendLine("```mermaid")
+ sb.appendLine("graph TB")
+
+ for (type in PluginType.entries) {
+ sb.appendLine(" ${type.displayName}[${type.displayName}]:::${type.displayName}")
+ }
+
+ sb.appendLine()
+ sb.appendLine(" cmp-application -.-> cmp-feature")
+ sb.appendLine(" kmp-library --> jvm-library")
+ sb.appendLine()
+
+ for (type in PluginType.entries) {
+ sb.appendLine("classDef ${type.displayName} fill:${type.color},stroke:#000,stroke-width:2px,color:#000;")
+ }
+ sb.appendLine("classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;")
+ sb.appendLine("```")
+ sb.appendLine()
+ sb.appendLine("")
+ return sb.toString()
+}
+
+/**
+ * Updates a README.md file, replacing content between graph region markers.
+ */
+fun updateReadmeGraph(readmeFile: File, graphContent: String) {
+ if (!readmeFile.exists()) return
+ val content = readmeFile.readText()
+ val startMarker = ""
+ val endMarker = ""
+
+ val startIdx = content.indexOf(startMarker)
+ val endIdx = content.indexOf(endMarker)
+
+ if (startIdx == -1 || endIdx == -1) return
+
+ val newContent = buildString {
+ append(content.substring(0, startIdx + startMarker.length))
+ appendLine()
+ append(graphContent)
+ appendLine(generateLegend())
+ append(content.substring(endIdx))
+ }
+
+ readmeFile.writeText(newContent)
+}
+
+/**
+ * Registers the `graphDump` and `graphUpdate` tasks on the root project.
+ */
+fun Project.configureGraphTasks() {
+ val ignoredProjects = providers.gradleProperty("graph.ignoredProjects")
+ .orElse("")
+ .map { it.split(",").map(String::trim).filter(String::isNotEmpty).toSet() }
+
+ tasks.register("graphDump") {
+ group = "documentation"
+ description = "Dumps the module dependency graph as Mermaid text"
+ doLast {
+ val allEdges = mutableMapOf>()
+ val allPluginTypes = mutableMapOf()
+
+ subprojects.forEach { sub ->
+ allEdges[sub.path] = sub.collectEdges()
+ allPluginTypes[sub.path] = sub.pluginType()
+ }
+
+ subprojects.forEach { sub ->
+ val readmeFile = sub.file("README.md")
+ if (readmeFile.exists() && readmeFile.readText().contains("")) {
+ val graph = generateMermaidGraph(
+ rootProjectPath = sub.path,
+ allEdges = allEdges,
+ allPluginTypes = allPluginTypes,
+ ignoredProjects = ignoredProjects.get(),
+ )
+ println("=== ${sub.path} ===")
+ println(graph)
+ }
+ }
+ }
+ }
+
+ tasks.register("graphUpdate") {
+ group = "documentation"
+ description = "Updates README.md files with module dependency graphs"
+ doLast {
+ val allEdges = mutableMapOf>()
+ val allPluginTypes = mutableMapOf()
+
+ subprojects.forEach { sub ->
+ allEdges[sub.path] = sub.collectEdges()
+ allPluginTypes[sub.path] = sub.pluginType()
+ }
+
+ subprojects.forEach { sub ->
+ val readmeFile = sub.file("README.md")
+ if (readmeFile.exists() && readmeFile.readText().contains("")) {
+ val graph = generateMermaidGraph(
+ rootProjectPath = sub.path,
+ allEdges = allEdges,
+ allPluginTypes = allPluginTypes,
+ ignoredProjects = ignoredProjects.get(),
+ )
+ updateReadmeGraph(readmeFile, graph)
+ println("Updated: ${readmeFile.relativeTo(rootDir)}")
+ }
+ }
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt
index ed1ea4254..3d8facd42 100644
--- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt
@@ -76,7 +76,6 @@ internal fun Project.configureJacoco(
"create${variant.name.capitalize()}CombinedCoverageReport",
JacocoReport::class,
) {
-
classDirectories.setFrom(
allJars,
allDirectories.map { dirs ->
@@ -97,7 +96,7 @@ internal fun Project.configureJacoco(
sourceDirectories.setFrom(
files(
variant.sources.java.toFilePaths(),
- variant.sources.kotlin.toFilePaths()
+ variant.sources.kotlin.toFilePaths(),
),
)
@@ -110,7 +109,6 @@ internal fun Project.configureJacoco(
)
}
-
variant.artifacts.forScope(ScopedArtifacts.Scope.PROJECT)
.use(reportTask)
.toGet(
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt
index 0fce68329..89fbccfcd 100644
--- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt
@@ -20,15 +20,13 @@ import com.android.build.api.dsl.CommonExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
-import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
-import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension
-import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
/**
@@ -38,10 +36,10 @@ internal fun Project.configureKotlinAndroid(
commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
commonExtension.apply {
- compileSdk = 35
+ compileSdk = 36
defaultConfig {
- minSdk = 21
+ minSdk = 23
}
compileOptions {
@@ -53,6 +51,13 @@ internal fun Project.configureKotlinAndroid(
}
}
+ // Gradle 9 defaults failOnNoDiscoveredTests to true. KMP modules may declare
+ // commonTest dependencies without having Android-specific test files, so disable
+ // the check to avoid false failures.
+ tasks.withType().configureEach {
+ failOnNoDiscoveredTests = false
+ }
+
configureKotlin()
dependencies {
@@ -71,6 +76,10 @@ internal fun Project.configureKotlinJvm() {
targetCompatibility = JavaVersion.VERSION_11
}
+ tasks.withType().configureEach {
+ failOnNoDiscoveredTests = false
+ }
+
configureKotlin()
}
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinMultiplatform.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinMultiplatform.kt
index 45dd97f6f..79a7241b3 100644
--- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinMultiplatform.kt
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinMultiplatform.kt
@@ -21,12 +21,8 @@ import org.gradle.api.Project
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.withType
-import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
-import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-import org.jetbrains.kotlin.konan.target.HostManager
/**
* A plugin that applies the Kotlin Multiplatform plugin and configures it for the project.
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaBuildType.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaBuildType.kt
index e4f40840d..f4d6f038b 100644
--- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaBuildType.kt
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaBuildType.kt
@@ -1,17 +1,17 @@
/*
* 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
+ * 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
+ * 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.
+ * 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.samples.apps.nowinandroid
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt
index f57e634cc..975c11616 100644
--- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.google.samples.apps.nowinandroid
import com.android.build.api.dsl.ApplicationExtension
@@ -7,7 +23,7 @@ import com.android.build.api.dsl.ProductFlavor
@Suppress("EnumEntryName")
enum class FlavorDimension {
- contentType
+ contentType,
}
// The content for the app can either come from local static data which is useful for demo
@@ -24,12 +40,12 @@ fun configureFlavors(
flavorConfigurationBlock: ProductFlavor.(flavor: NiaFlavor) -> Unit = {},
) {
commonExtension.apply {
- FlavorDimension.values().forEach { flavorDimension ->
+ FlavorDimension.entries.forEach { flavorDimension ->
flavorDimensions += flavorDimension.name
}
productFlavors {
- NiaFlavor.values().forEach { niaFlavor ->
+ NiaFlavor.entries.forEach { niaFlavor ->
register(niaFlavor.name) {
dimension = niaFlavor.dimension.name
flavorConfigurationBlock(this, niaFlavor)
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt
index 271fc51b7..e2a96eedf 100644
--- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt
@@ -1,17 +1,17 @@
/*
* 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
+ * 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
+ * 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.
+ * 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.samples.apps.nowinandroid
@@ -49,7 +49,9 @@ internal fun Project.configurePrintApksTask(extension: AndroidComponentsExtensio
javaSources.zip(kotlinSources) { javaDirs, kotlinDirs ->
javaDirs + kotlinDirs
}
- } else javaSources ?: kotlinSources
+ } else {
+ javaSources ?: kotlinSources
+ }
if (artifact != null && testSources != null) {
tasks.register(
@@ -96,8 +98,9 @@ internal abstract class PrintApkLocationTask : DefaultTask() {
val builtArtifacts = builtArtifactsLoader.get().load(apkFolder.get())
?: throw RuntimeException("Cannot load APKs")
- if (builtArtifacts.elements.size != 1)
+ if (builtArtifacts.elements.size != 1) {
throw RuntimeException("Expected one APK !")
+ }
val apk = File(builtArtifacts.elements.single().outputFile).toPath()
println(apk)
}
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/ProjectExtensions.kt
index e45d7f2e1..8bf6e1dfe 100644
--- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/ProjectExtensions.kt
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/ProjectExtensions.kt
@@ -1,17 +1,17 @@
/*
* 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
+ * 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
+ * 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.
+ * 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.samples.apps.nowinandroid
diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Spotless.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Spotless.kt
new file mode 100644
index 000000000..2d8c70aca
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Spotless.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.samples.apps.nowinandroid
+
+import com.diffplug.gradle.spotless.SpotlessExtension
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.apply
+import org.gradle.kotlin.dsl.configure
+
+/**
+ * Configures Spotless for Android/KMP modules (Kotlin + KTS + XML).
+ *
+ * Uses `target("src/ **")` instead of `target("** /") + targetExclude("build/")` to work around
+ * [spotless#2717](https://github.com/diffplug/spotless/issues/2717).
+ */
+fun Project.configureSpotlessForAndroid() {
+ configureSpotlessCommon()
+ val rootDir = isolated.rootProject.projectDirectory
+ extensions.configure {
+ format("xml") {
+ target("src/**/*.xml")
+ licenseHeaderFile(rootDir.file("spotless/copyright.xml").asFile, "(<[^!?])")
+ }
+ }
+}
+
+/**
+ * Configures Spotless for JVM-only modules (Kotlin + KTS, no XML).
+ */
+fun Project.configureSpotlessForJvm() {
+ configureSpotlessCommon()
+}
+
+private fun Project.configureSpotlessCommon() {
+ apply(plugin = "com.diffplug.spotless")
+ val rootDir = isolated.rootProject.projectDirectory
+ extensions.configure {
+ kotlin {
+ target("src/**/*.kt")
+ ktlint(libs.findVersion("ktlint").get().toString())
+ .editorConfigOverride(mapOf("android" to "true"))
+ licenseHeaderFile(rootDir.file("spotless/copyright.kt").asFile)
+ }
+ format("kts") {
+ target("*.kts")
+ targetExclude("**/build/**/*.kts")
+ licenseHeaderFile(rootDir.file("spotless/copyright.kts").asFile, "(^(?![\\/ ]\\*).*$)")
+ }
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 83c079a8e..7fd51b1f7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -54,9 +54,25 @@ plugins {
alias(libs.plugins.roborazzi) apply false
alias(libs.plugins.secrets) apply false
alias(libs.plugins.module.graph) apply true // Plugin applied to allow module graph generation
+ alias(libs.plugins.nowinandroid.root)
+ alias(libs.plugins.spotless)
alias(libs.plugins.jetbrains.compose) apply false
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.sqldelight.gradle.plugin) apply false
alias(libs.plugins.ktrofit) apply false
alias(libs.plugins.buildkonfig) apply false
}
+
+spotless {
+ kotlin {
+ target("build-logic/convention/src/**/*.kt")
+ ktlint(libs.versions.ktlint.get())
+ .editorConfigOverride(mapOf("android" to "true"))
+ licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
+ }
+ format("kts") {
+ target("*.kts", "build-logic/**/*.kts")
+ targetExclude("**/build/**/*.kts")
+ licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
+ }
+}
diff --git a/core/data/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt b/core/data/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt
index 9af93d54c..6f9d401ad 100644
--- a/core/data/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt
+++ b/core/data/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt
@@ -23,8 +23,6 @@ import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.NetworkRequest.Builder
-import android.os.Build.VERSION
-import android.os.Build.VERSION_CODES
import androidx.core.content.getSystemService
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
@@ -79,13 +77,8 @@ internal class ConnectivityManagerNetworkMonitor(
.flowOn(ioDispatcher)
.conflate()
- @Suppress("DEPRECATION")
- private fun ConnectivityManager.isCurrentlyConnected() = when {
- VERSION.SDK_INT >= VERSION_CODES.M ->
- activeNetwork
- ?.let(::getNetworkCapabilities)
- ?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-
- else -> activeNetworkInfo?.isConnected
- } ?: false
+ private fun ConnectivityManager.isCurrentlyConnected(): Boolean {
+ val networkCapabilities = getNetworkCapabilities(activeNetwork) ?: return false
+ return networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ }
}
diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts
index 74cb07430..92df27483 100644
--- a/core/network/build.gradle.kts
+++ b/core/network/build.gradle.kts
@@ -27,6 +27,10 @@ plugins {
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
}
+ktorfit {
+ compilerPluginVersion.set("2.3.3")
+}
+
android {
namespace = "com.google.samples.apps.nowinandroid.core.network"
testOptions {
diff --git a/gradle/init.gradle.kts b/gradle/init.gradle.kts
deleted file mode 100644
index cb3d34e48..000000000
--- a/gradle/init.gradle.kts
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-val ktlintVersion = "1.5.0"
-
-initscript {
- val spotlessVersion = "7.0.2"
-
- repositories {
- mavenCentral()
- }
-
- dependencies {
- classpath("com.diffplug.spotless:spotless-plugin-gradle:$spotlessVersion")
- }
-}
-
-rootProject {
- subprojects {
- apply()
- extensions.configure {
- kotlin {
- target("**/*.kt")
- targetExclude("**/build/**/*.kt")
- ktlint(ktlintVersion).editorConfigOverride(
- mapOf(
- "android" to "true",
- ),
- )
- licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
- }
- format("kts") {
- target("**/*.kts")
- targetExclude("**/build/**/*.kts")
- // Look for the first line that doesn't have a block comment (assumed to be the license)
- licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
- }
- format("xml") {
- target("**/*.xml")
- targetExclude("**/build/**/*.xml")
- // Look for the first XML tag that isn't a comment (