diff --git a/.circleci/config.yml b/.circleci/config.yml
index 67fabf9b..e8e37921 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,13 +3,13 @@
version: 2.1
orbs:
- slack: circleci/slack@4.13.3
- gh: circleci/github-cli@2.3.0
+ slack: circleci/slack@5.1.1
+ gh: circleci/github-cli@2.5.0
executors:
android:
docker:
- - image: cimg/android:2024.01
+ - image: cimg/android:2024.11
commands:
check_is_skipping_vrt:
@@ -72,7 +72,7 @@ commands:
command: |
mkdir -p ./temp/zip
if [ $IS_EXIST_SCREENSHOTS = "true" ]; then
- zip -r ./temp/zip/screenshots.zip ./app/build/outputs/roborazzi
+ zip -r ./temp/zip/screenshots.zip ./screenshots
fi
- save_cache:
paths:
@@ -176,7 +176,7 @@ commands:
git checkout --orphan screenshots_$CIRCLE_BRANCH
git rm --cached -rf .
- add_files=$(find . -type f -path "./app/build/outputs/roborazzi/*")
+ add_files=$(find . -type f -path "./screenshots/*")
for file in $add_files; do
git add -f $file
done
@@ -200,13 +200,13 @@ commands:
if [ $IS_SKIPPING_VRT = "false" ]; then
git push origin --delete compare_$CIRCLE_BRANCH || true
- fileSize=$(echo $(find ./app/build/outputs/roborazzi -type f | grep -e '.*_compare.png' | wc -l | sed -e 's/ //g'))
+ fileSize=$(echo $(find ./screenshots/compare -type f | grep -e '.*_compare.webp' | wc -l | sed -e 's/ //g'))
echo "fileSize: $fileSize"
if [ $fileSize -ne 0 ]; then
git checkout --orphan compare_$CIRCLE_BRANCH
git rm --cached -rf .
- add_files=$(find . -type f -path "./app/build/outputs/roborazzi/*" -name "*_compare.png")
+ add_files=$(find . -type f -path "./screenshots/compare/*" -name "*_compare.webp")
for file in $add_files; do
git add -f $file
done
@@ -229,7 +229,7 @@ commands:
echo "| File name | Image |" >> comment
echo "|-------|-------|" >> comment
- files=$(find . -type f -path "./app/build/outputs/roborazzi/*" -name "*_compare.png")
+ files=$(find . -type f -path "./screenshots/compare/*" -name "*_compare.webp")
for file in $files; do
fileName=$(basename "$file" | sed -r 's/(.{20})/\1
/g')
echo "| [$fileName](https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/blob/compare_$CIRCLE_BRANCH/$file) | ![](https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/blob/compare_$CIRCLE_BRANCH/$file?raw=true) |" >> comment
diff --git a/.github/actions/incoming-webhook/action.yml b/.github/actions/incoming-webhook/action.yml
index 13d0d5ae..64e3e1d6 100644
--- a/.github/actions/incoming-webhook/action.yml
+++ b/.github/actions/incoming-webhook/action.yml
@@ -15,7 +15,7 @@ runs:
# https://github.com/slackapi/slack-github-action
- name: Send GitHub Action trigger data to Slack workflow
id: slack
- uses: slackapi/slack-github-action@v1.26.0
+ uses: slackapi/slack-github-action@v2.0.0
with:
payload: |
{
diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml
index 7a411be1..fe9df7dd 100644
--- a/.github/actions/setup/action.yml
+++ b/.github/actions/setup/action.yml
@@ -23,4 +23,4 @@ runs:
cache: gradle
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v3
\ No newline at end of file
+ uses: gradle/actions/setup-gradle@v4
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 417ffb90..d8dc418a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@ output.json
*.jks
*.keystore
keystore.properties
+/screenshots/
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 4bec4ea8..7643783a 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,5 +1,8 @@
+
+
+
@@ -113,5 +116,8 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index a55e7a17..6e6eec11 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,5 +1,6 @@
+
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
index f99b8ef5..7782577e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
source "https://rubygems.org"
git_source(:github) { |repo_name| "https://github.com/kosenda/SimpleCompoundInterestCalculation" }
-gem 'danger', '~> 9.4.0'
+gem 'danger', '~> 9.5.0'
gem "danger-checkstyle_format"
gem "danger-jacoco"
gem 'danger-android_lint'
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index 8079cf1a..79953da7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,8 +1,10 @@
GEM
remote: https://rubygems.org/
specs:
- addressable (2.8.6)
- public_suffix (>= 2.0.2, < 6.0)
+ addressable (2.8.7)
+ public_suffix (>= 2.0.2, < 7.0)
+ ansi (1.5.0)
+ ast (1.1.0)
base64 (0.2.0)
claide (1.1.0)
claide-plugins (0.9.2)
@@ -12,7 +14,8 @@ GEM
colored2 (3.1.2)
cork (0.3.0)
colored2 (~> 3.1)
- danger (9.4.3)
+ danger (9.5.1)
+ base64 (~> 0.2)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
colored2 (~> 3.1)
@@ -22,9 +25,12 @@ GEM
git (~> 1.13)
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.0)
- no_proxy_fix
octokit (>= 4.0)
+ pstore (~> 0.1)
terminal-table (>= 1, < 4)
+ danger-android_lint (0.0.12)
+ danger-plugin-api (~> 1.0)
+ oga
danger-checkstyle_format (0.1.1)
danger-plugin-api (~> 1.0)
ox (~> 2.0)
@@ -33,11 +39,12 @@ GEM
nokogiri-happymapper (~> 0.6)
danger-plugin-api (1.0.0)
danger (> 2.0)
- faraday (2.9.0)
- faraday-net_http (>= 2.0, < 3.2)
+ faraday (2.11.0)
+ faraday-net_http (>= 2.0, < 3.4)
+ logger
faraday-http-cache (2.5.1)
faraday (>= 0.8)
- faraday-net_http (3.1.0)
+ faraday-net_http (3.3.0)
net-http
git (1.19.1)
addressable (~> 2.8)
@@ -46,10 +53,10 @@ GEM
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
+ logger (1.6.1)
nap (1.1.0)
net-http (0.4.1)
uri
- no_proxy_fix (0.1.2)
nokogiri (1.16.2-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.2-x86_64-linux)
@@ -60,26 +67,34 @@ GEM
base64
faraday (>= 1, < 3)
sawyer (~> 0.9)
+ oga (0.3.4)
+ ast
+ ruby-ll (~> 2.1)
open4 (1.3.4)
ox (2.14.17)
- public_suffix (5.0.4)
+ pstore (0.1.3)
+ public_suffix (5.1.1)
racc (1.7.3)
rchardet (1.8.0)
- rexml (3.2.6)
+ rexml (3.3.9)
+ ruby-ll (2.1.3)
+ ansi
+ ast
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
- unicode-display_width (2.5.0)
- uri (0.13.0)
+ unicode-display_width (2.6.0)
+ uri (0.13.1)
PLATFORMS
arm64-darwin-22
x86_64-linux
DEPENDENCIES
- danger (~> 9.4.0)
+ danger (~> 9.5.0)
+ danger-android_lint
danger-checkstyle_format
danger-jacoco
diff --git a/README.md b/README.md
index e338a6b1..44e25b89 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ https://github.com/kosenda/hiragana-converter/blob/develop/REFERENCE.md
|App Update|In App Update|
|App Review|In App Review|
|Coil|Image loading library|
+|ComposablePreviewScanner|Help auto-generate screenshot tests|
|Crashlytics|Firebase crashlytics|
|Danger|Automatic review|
|Hilt|Dependency Injection|
@@ -55,7 +56,6 @@ https://github.com/kosenda/hiragana-converter/blob/develop/REFERENCE.md
|Roborazzi|Make JVM Android Integration Test Visible|
|Room|Database|
|Secrets gradle plugin|Reading API keys from `local.properties`|
-|Showkase|auto-generates a browser for Jetpack Compose UI|
|Timber|Log output library|
|Truth|Assertions used in testing|
|Turbine|testing library for kotlinx.coroutines Flow|
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2291cb1b..7ccf8a41 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,5 +1,6 @@
+import com.github.takahirom.roborazzi.ExperimentalRoborazziApi
import com.google.firebase.perf.plugin.FirebasePerfExtension
-import ksnd.hiraganaconverter.kotlinOptions
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.io.FileInputStream
import java.util.Properties
@@ -22,21 +23,23 @@ plugins {
android {
namespace = "ksnd.hiraganaconverter"
- compileSdk = 34
+ compileSdk = 35
defaultConfig {
applicationId = "ksnd.hiraganaconverter"
minSdk = 26
- targetSdk = 34
- versionCode = 44
- versionName = "1.33"
+ targetSdk = 35
+ versionCode = 45
+ versionName = "1.34"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_17.toString()
+ kotlin {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_17)
+ }
}
androidResources {
generateLocaleConfig = true
@@ -91,9 +94,6 @@ android {
buildConfig = true
compose = true
}
- composeCompiler {
- enableStrongSkippingMode = true
- }
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
@@ -102,6 +102,18 @@ android {
testOptions {
unitTests.isIncludeAndroidResources = true
unitTests.isReturnDefaultValues = true
+ unitTests.all {
+ it.systemProperties["robolectric.pixelCopyRenderMode"] = "hardware"
+ }
+ }
+}
+
+roborazzi {
+ @OptIn(ExperimentalRoborazziApi::class)
+ generateComposePreviewRobolectricTests {
+ enable = true
+ testerQualifiedClassName = "ksnd.hiraganaconverter.RoborazziComposePreviewTest"
+ packages = listOf("ksnd.hiraganaconverter")
}
}
@@ -130,6 +142,10 @@ dependencies {
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.lifecycle.runtime.compose)
+ // Supported: Workaround for AGP not merging test manifest
+ // ref: https://github.com/robolectric/robolectric/pull/4736
+ debugImplementation(libs.androidx.compose.ui.test.manifest)
+
// Lottie
implementation(libs.lottie)
@@ -151,16 +167,18 @@ dependencies {
// Navigation
implementation(libs.androidx.navigation.compose)
- // Showkase
- debugImplementation(libs.showkase)
- implementation(libs.showkase.annotation)
- kspDebug(libs.showkase.processor)
-
// kotlinx serialization
implementation(libs.kotlinx.serialization.json)
// AboutLibraries
implementation(libs.aboutLibraries)
+
+ // Roborazzi (for ComposablePreviewScanner)
+ testImplementation(libs.roborazzi.compose.preview.scanner.support)
+ testImplementation(libs.junit)
+ testImplementation(libs.robolectric)
+ testImplementation(libs.composable.preview.scanner)
+ testImplementation(libs.webp.image.io)
}
tasks.withType().configureEach {
diff --git a/app/src/main/java/ksnd/hiraganaconverter/ShowkaseRootModule.kt b/app/src/main/java/ksnd/hiraganaconverter/ShowkaseRootModule.kt
deleted file mode 100644
index d994da28..00000000
--- a/app/src/main/java/ksnd/hiraganaconverter/ShowkaseRootModule.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package ksnd.hiraganaconverter
-
-import com.airbnb.android.showkase.annotation.ShowkaseRoot
-import com.airbnb.android.showkase.annotation.ShowkaseRootModule
-
-@ShowkaseRoot
-class ShowkaseRootModule : ShowkaseRootModule
diff --git a/app/src/main/java/ksnd/hiraganaconverter/view/RequestReviewDialog.kt b/app/src/main/java/ksnd/hiraganaconverter/view/RequestReviewDialog.kt
index f64d7c80..a57ecdd6 100644
--- a/app/src/main/java/ksnd/hiraganaconverter/view/RequestReviewDialog.kt
+++ b/app/src/main/java/ksnd/hiraganaconverter/view/RequestReviewDialog.kt
@@ -7,6 +7,8 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import ksnd.hiraganaconverter.core.resource.R
+import ksnd.hiraganaconverter.core.ui.preview.UiModePreview
+import ksnd.hiraganaconverter.core.ui.theme.HiraganaConverterTheme
@Composable
fun RequestReviewDialog(onLater: () -> Unit, onOk: () -> Unit) {
@@ -44,15 +46,13 @@ fun RequestReviewDialog(onLater: () -> Unit, onOk: () -> Unit) {
)
}
-// FIXME: Skip in ShowkaseComposable does not work.
-// @UiModePreview
-// @Composable
-// @ShowkaseComposable(skip = true)
-// fun PreviewRequestReviewDialog() {
-// HiraganaConverterTheme {
-// RequestReviewDialog(
-// onLater = {},
-// onOk = {},
-// )
-// }
-// }
+@UiModePreview
+@Composable
+private fun PreviewRequestReviewDialog() {
+ HiraganaConverterTheme {
+ RequestReviewDialog(
+ onLater = {},
+ onOk = {},
+ )
+ }
+}
diff --git a/app/src/test/java/ksnd/hiraganaconverter/PreviewTest.kt b/app/src/test/java/ksnd/hiraganaconverter/PreviewTest.kt
deleted file mode 100644
index c50e7c09..00000000
--- a/app/src/test/java/ksnd/hiraganaconverter/PreviewTest.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package ksnd.hiraganaconverter
-
-import android.content.res.Configuration
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.platform.LocalConfiguration
-import com.airbnb.android.showkase.models.Showkase
-import com.airbnb.android.showkase.models.ShowkaseBrowserComponent
-import com.github.takahirom.roborazzi.DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH
-import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers
-import com.github.takahirom.roborazzi.captureRoboImage
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.ParameterizedRobolectricTestRunner
-import org.robolectric.annotation.Config
-import org.robolectric.annotation.GraphicsMode
-
-// ref: https://github.com/DroidKaigi/conference-app-2023/pull/217
-@RunWith(ParameterizedRobolectricTestRunner::class)
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-@Config(qualifiers = RobolectricDeviceQualifiers.NexusOne)
-class PreviewTest(
- private val param: Pair,
-) {
-
- @Test
- fun previewScreenshot() {
- val (showkaseBrowserComponent, count) = param
- val componentName = showkaseBrowserComponent.componentName.replace(" ", "")
- val filePath = DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH + "/" + componentName + "_" + count + ".png"
- captureRoboImage(filePath) {
- val newConfiguration = Configuration().apply {
- this.uiMode = if (componentName.contains(other = "dark", ignoreCase = true)) {
- Configuration.UI_MODE_NIGHT_YES
- } else {
- Configuration.UI_MODE_NIGHT_NO
- }
- }
- CompositionLocalProvider(LocalConfiguration provides newConfiguration) {
- showkaseBrowserComponent.component()
- }
- }
- }
-
- companion object {
- @ParameterizedRobolectricTestRunner.Parameters
- @JvmStatic
- fun components(): Iterable> {
- val countMap = mutableMapOf()
- return Showkase.getMetadata().componentList.map { showkaseBrowserComponent ->
- val componentName = showkaseBrowserComponent.componentName
- val count = countMap.getOrDefault(key = componentName, defaultValue = 0)
- countMap[componentName] = count + 1
- arrayOf(showkaseBrowserComponent to count)
- }
- }
- }
-}
diff --git a/app/src/test/java/ksnd/hiraganaconverter/RoborazziComposePreviewTest.kt b/app/src/test/java/ksnd/hiraganaconverter/RoborazziComposePreviewTest.kt
new file mode 100644
index 00000000..43f993c7
--- /dev/null
+++ b/app/src/test/java/ksnd/hiraganaconverter/RoborazziComposePreviewTest.kt
@@ -0,0 +1,95 @@
+package ksnd.hiraganaconverter
+
+import android.content.Context
+import android.content.res.Configuration
+import androidx.activity.ComponentActivity
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.test.core.app.ApplicationProvider
+import com.github.takahirom.roborazzi.ComposePreviewTester
+import com.github.takahirom.roborazzi.ExperimentalRoborazziApi
+import com.github.takahirom.roborazzi.LosslessWebPImageIoFormat
+import com.github.takahirom.roborazzi.RoborazziOptions
+import com.github.takahirom.roborazzi.captureRoboImage
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.Shadows
+import org.robolectric.shadows.ShadowDisplay
+import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreviewScanner
+import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo
+import sergio.sastre.composable.preview.scanner.android.screenshotid.AndroidPreviewScreenshotIdBuilder
+import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview
+import kotlin.math.roundToInt
+
+private const val SCREEN_SHOT_PATH = "../screenshots/"
+private const val COMPARE_PATH = "../screenshots/compare/"
+
+/**
+ * ref:
+ * - https://github.com/takahirom/roborazzi
+ * - https://github.com/sergio-sastre/ComposablePreviewScanner
+ * - https://github.com/DeNA/android-modern-architecture-test-handson/blob/main/docs/handson/VisualRegressionTest_Preview_ComposablePreviewScanner.md
+ */
+@OptIn(ExperimentalRoborazziApi::class)
+class RoborazziComposePreviewTest : ComposePreviewTester {
+
+ private val composeTestRule = createAndroidComposeRule()
+
+ override fun options(): ComposePreviewTester.Options {
+ // Use composeTestRule as a JUnit 4 Rule
+ val testLifecycleOptions = ComposePreviewTester.Options.JUnit4TestLifecycleOptions(
+ testRuleFactory = { composeTestRule },
+ )
+ return super.options().copy(testLifecycleOptions = testLifecycleOptions)
+ }
+
+ override fun previews(): List> =
+ AndroidComposablePreviewScanner()
+ // Configure roborazzi's packages in :app/build.gradle.kts
+ .scanPackageTrees(*options().scanOptions.packages.toTypedArray())
+ .getPreviews()
+
+ override fun test(preview: ComposablePreview) {
+ val screenshotId = AndroidPreviewScreenshotIdBuilder(preview).build()
+ val filePath = "$SCREEN_SHOT_PATH$screenshotId.webp"
+
+ preview.apply {
+ if (this.previewInfo.uiMode == Configuration.UI_MODE_NIGHT_YES) {
+ RuntimeEnvironment.setQualifiers("+night")
+ }
+
+ setDisplaySize(this.previewInfo.widthDp, this.previewInfo.heightDp)
+ }
+
+ // Change the environment and regenerate the activity
+ // Otherwise, environment will not be reflected
+ composeTestRule.activityRule.scenario.recreate()
+
+ composeTestRule.apply {
+ setContent {
+ preview()
+ }
+ onRoot().captureRoboImage(
+ filePath = filePath,
+ roborazziOptions = RoborazziOptions(
+ compareOptions = RoborazziOptions.CompareOptions(
+ outputDirectoryPath = COMPARE_PATH,
+ ),
+ recordOptions = RoborazziOptions.RecordOptions(
+ imageIoFormat = LosslessWebPImageIoFormat(),
+ ),
+ ),
+ )
+ }
+ }
+}
+
+private fun setDisplaySize(widthDp: Int, heightDp: Int) {
+ val context = ApplicationProvider.getApplicationContext()
+ val display = ShadowDisplay.getDefaultDisplay()
+ val density = context.resources.displayMetrics.density
+
+ Shadows.shadowOf(display).apply {
+ if (widthDp != -1) setWidth((widthDp * density).roundToInt())
+ if (heightDp != -1) setHeight((heightDp * density).roundToInt())
+ }
+}
diff --git a/app/src/test/java/ksnd/hiraganaconverter/view/screen/ConverterScreenTest.kt b/app/src/test/java/ksnd/hiraganaconverter/view/screen/ConverterScreenTest.kt
deleted file mode 100644
index a2bdf03b..00000000
--- a/app/src/test/java/ksnd/hiraganaconverter/view/screen/ConverterScreenTest.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-package ksnd.hiraganaconverter.view.screen
-
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.TopAppBarDefaults
-import androidx.compose.material3.rememberTopAppBarState
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers
-import com.github.takahirom.roborazzi.captureRoboImage
-import ksnd.hiraganaconverter.core.ui.theme.HiraganaConverterTheme
-import ksnd.hiraganaconverter.feature.converter.ConvertUiState
-import ksnd.hiraganaconverter.feature.converter.ConverterScreenContent
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.annotation.Config
-import org.robolectric.annotation.GraphicsMode
-
-@OptIn(ExperimentalMaterial3Api::class)
-@RunWith(AndroidJUnit4::class)
-@GraphicsMode(GraphicsMode.Mode.NATIVE)
-@Config(qualifiers = RobolectricDeviceQualifiers.Pixel6Pro)
-class ConverterScreenTest {
- @Test
- fun converterScreen_light() {
- captureRoboImage {
- HiraganaConverterTheme(isDarkTheme = false) {
- ConverterScreenContent(
- uiState = ConvertUiState(),
- snackbarHostState = SnackbarHostState(),
- scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()),
- changeHiraKanaType = {},
- clearAllText = {},
- convert = {},
- updateInputText = {},
- updateOutputText = {},
- hideErrorCard = {},
- navigateScreen = {},
- )
- }
- }
- }
-
- @Test
- fun converterScreen_dark() {
- captureRoboImage {
- HiraganaConverterTheme(isDarkTheme = true) {
- ConverterScreenContent(
- uiState = ConvertUiState(),
- snackbarHostState = SnackbarHostState(),
- scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()),
- changeHiraKanaType = {},
- clearAllText = {},
- convert = {},
- updateInputText = {},
- updateOutputText = {},
- hideErrorCard = {},
- navigateScreen = {},
- )
- }
- }
- }
-}
diff --git a/app/src/test/resources/robolectric.properties b/app/src/test/resources/robolectric.properties
new file mode 100644
index 00000000..4b38ad50
--- /dev/null
+++ b/app/src/test/resources/robolectric.properties
@@ -0,0 +1 @@
+sdk=34
\ No newline at end of file
diff --git a/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/AndroidLibraryComposePlugin.kt b/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/AndroidLibraryComposePlugin.kt
index cf90659a..95123e54 100644
--- a/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/AndroidLibraryComposePlugin.kt
+++ b/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/AndroidLibraryComposePlugin.kt
@@ -29,9 +29,6 @@ class AndroidLibraryComposePlugin : Plugin {
add("implementation", libs.findLibrary("androidx.compose.ui.tooling.preview").get())
add("testImplementation", libs.findLibrary("androidx.compose.ui.test.junit4").get())
add("implementation", libs.findLibrary("androidx.lifecycle.runtime.compose").get())
- add("debugImplementation", libs.findLibrary("showkase").get())
- add("implementation", libs.findLibrary("showkase.annotation").get())
- add("kspDebug", libs.findLibrary("showkase.processor").get())
}
}
}
diff --git a/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/AndroidLibraryPlugin.kt b/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/AndroidLibraryPlugin.kt
index 87a4a2aa..061cfe82 100644
--- a/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/AndroidLibraryPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/AndroidLibraryPlugin.kt
@@ -5,6 +5,9 @@ import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
+import org.gradle.kotlin.dsl.withType
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
class AndroidLibraryPlugin : Plugin {
override fun apply(target: Project) {
@@ -14,7 +17,7 @@ class AndroidLibraryPlugin : Plugin {
apply("org.jetbrains.kotlin.android")
}
extensions.configure {
- compileSdk = 34
+ compileSdk = 35
defaultConfig {
minSdk = 26
}
@@ -22,8 +25,10 @@ class AndroidLibraryPlugin : Plugin {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_17.toString()
+ tasks.withType().configureEach {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_17)
+ }
}
}
}
diff --git a/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/Extensions.kt b/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/Extensions.kt
index a3abad7d..4cb3e322 100644
--- a/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/Extensions.kt
+++ b/build-logic/convention/src/main/kotlin/ksnd/hiraganaconverter/Extensions.kt
@@ -1,23 +1,13 @@
package ksnd.hiraganaconverter
-import com.android.build.api.dsl.CommonExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
-import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.withType
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
-import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
-
-/*
- * ref: https://github.com/android/nowinandroid/blob/main/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt
- */
-fun CommonExtension<*, *, *, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) {
- (this as ExtensionAware).extensions.configure("kotlinOptions", block)
-}
/*
* ref: https://github.com/android/nowinandroid/blob/main/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt
diff --git a/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/TopBar.kt b/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/TopBar.kt
index b60c7847..d19876f1 100644
--- a/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/TopBar.kt
+++ b/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/TopBar.kt
@@ -32,6 +32,7 @@ import ksnd.hiraganaconverter.core.ui.isTest
import ksnd.hiraganaconverter.core.ui.navigation.Nav
import ksnd.hiraganaconverter.core.ui.parts.button.CustomIconButton
import ksnd.hiraganaconverter.core.ui.preview.UiModePreview
+import ksnd.hiraganaconverter.core.ui.theme.HiraganaConverterTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -98,8 +99,10 @@ fun TopBar(
@UiModePreview
@Composable
fun PreviewTopBar() {
- TopBar(
- scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()),
- navigateScreen = {},
- )
+ HiraganaConverterTheme {
+ TopBar(
+ scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()),
+ navigateScreen = {},
+ )
+ }
}
diff --git a/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/button/CustomButtonWithBackground.kt b/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/button/CustomButtonWithBackground.kt
index 0de3767f..632cf154 100644
--- a/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/button/CustomButtonWithBackground.kt
+++ b/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/button/CustomButtonWithBackground.kt
@@ -5,11 +5,10 @@ import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.material.ripple.LocalRippleTheme
-import androidx.compose.material.ripple.RippleAlpha
-import androidx.compose.material.ripple.RippleTheme
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.LocalRippleConfiguration
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -27,6 +26,7 @@ import ksnd.hiraganaconverter.core.ui.preview.UiModePreview
import ksnd.hiraganaconverter.core.ui.rememberButtonScaleState
import ksnd.hiraganaconverter.core.ui.theme.HiraganaConverterTheme
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomButtonWithBackground(
modifier: Modifier = Modifier,
@@ -39,15 +39,7 @@ fun CustomButtonWithBackground(
val buttonScaleState = rememberButtonScaleState()
val localView = LocalView.current
- CompositionLocalProvider(
- LocalRippleTheme provides object : RippleTheme {
- @Composable
- override fun defaultColor() = Color.Transparent
-
- @Composable
- override fun rippleAlpha() = RippleAlpha(0f, 0f, 0f, 0f)
- },
- ) {
+ CompositionLocalProvider(LocalRippleConfiguration provides null) {
IconButton(
modifier = modifier
.padding(all = 8.dp)
diff --git a/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/dialog/MovesToSiteDialog.kt b/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/dialog/MovesToSiteDialog.kt
index f996ead5..e33af99f 100644
--- a/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/dialog/MovesToSiteDialog.kt
+++ b/core/ui/src/main/java/ksnd/hiraganaconverter/core/ui/parts/dialog/MovesToSiteDialog.kt
@@ -4,20 +4,31 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
-import com.airbnb.android.showkase.annotation.ShowkaseComposable
import ksnd.hiraganaconverter.core.resource.R
import ksnd.hiraganaconverter.core.ui.preview.UiModePreview
import ksnd.hiraganaconverter.core.ui.theme.HiraganaConverterTheme
@Composable
-fun MovesToSiteDialog(onDismissRequest: () -> Unit, onClick: () -> Unit, url: String) {
+fun MoveToBrowserDialog(
+ url: String,
+ onMoveToBrowser: () -> Unit,
+ onDismissRequest: () -> Unit,
+) {
+ val urlHandler = LocalUriHandler.current
+
AlertDialog(
onDismissRequest = onDismissRequest,
title = { Text(text = stringResource(id = R.string.move_to_browser)) },
text = { Text(text = url) },
confirmButton = {
- TextButton(onClick = onClick) {
+ TextButton(
+ onClick = {
+ urlHandler.openUri(url)
+ onMoveToBrowser()
+ },
+ ) {
Text(text = stringResource(id = R.string.ok))
}
},
@@ -31,13 +42,12 @@ fun MovesToSiteDialog(onDismissRequest: () -> Unit, onClick: () -> Unit, url: St
@UiModePreview
@Composable
-@ShowkaseComposable(skip = true)
-fun PreviewMovesToSiteDialog() {
+private fun PreviewMoveToBrowserDialog() {
HiraganaConverterTheme {
- MovesToSiteDialog(
+ MoveToBrowserDialog(
onDismissRequest = {},
- onClick = {},
- url = "架空のURL",
+ onMoveToBrowser = {},
+ url = "https://example.com",
)
}
}
diff --git a/feature/info/src/main/java/ksnd/hiraganaconverter/feature/info/InfoScreen.kt b/feature/info/src/main/java/ksnd/hiraganaconverter/feature/info/InfoScreen.kt
index ebfa13d7..1f32a282 100644
--- a/feature/info/src/main/java/ksnd/hiraganaconverter/feature/info/InfoScreen.kt
+++ b/feature/info/src/main/java/ksnd/hiraganaconverter/feature/info/InfoScreen.kt
@@ -1,5 +1,6 @@
package ksnd.hiraganaconverter.feature.info
+import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -47,7 +48,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalUriHandler
@@ -56,6 +56,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import ksnd.hiraganaconverter.core.analytics.LocalAnalytics
@@ -68,7 +69,7 @@ import ksnd.hiraganaconverter.core.ui.parts.GooCreditImage
import ksnd.hiraganaconverter.core.ui.parts.button.CustomIconButton
import ksnd.hiraganaconverter.core.ui.parts.button.TransitionButton
import ksnd.hiraganaconverter.core.ui.parts.card.TitleCard
-import ksnd.hiraganaconverter.core.ui.parts.dialog.MovesToSiteDialog
+import ksnd.hiraganaconverter.core.ui.parts.dialog.MoveToBrowserDialog
import ksnd.hiraganaconverter.core.ui.preview.UiModePreview
import ksnd.hiraganaconverter.core.ui.theme.HiraganaConverterTheme
import ksnd.hiraganaconverter.core.ui.theme.urlColor
@@ -99,8 +100,6 @@ private fun InfoScreenContent(
onBackPressed: () -> Unit,
onClickLicense: () -> Unit,
) {
- val urlHandler = LocalUriHandler.current
- val context = LocalContext.current
val layoutDirection = LocalLayoutDirection.current
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
val coroutineScope = rememberCoroutineScope()
@@ -156,26 +155,24 @@ private fun InfoScreenContent(
}
if (isShowMovesToAppSiteDialog) {
- MovesToSiteDialog(
+ MoveToBrowserDialog(
onDismissRequest = {
isShowMovesToAppSiteDialog = false
},
- onClick = {
+ onMoveToBrowser = {
isShowMovesToAppSiteDialog = false
- urlHandler.openUri(uri = context.getString(R.string.review_url))
},
url = stringResource(id = R.string.review_url),
)
}
if (isShowMovesToApiSiteDialog) {
- MovesToSiteDialog(
+ MoveToBrowserDialog(
onDismissRequest = {
isShowMovesToApiSiteDialog = false
},
- onClick = {
+ onMoveToBrowser = {
isShowMovesToApiSiteDialog = false
- urlHandler.openUri(uri = context.getString(R.string.goo_url))
},
url = stringResource(id = R.string.goo_url),
)
@@ -384,7 +381,8 @@ private fun UrlText(url: String, onURLClick: () -> Unit) {
)
}
-@UiModePreview
+@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES, heightDp = 1100)
+@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO, heightDp = 1100)
@Composable
fun PreviewInfoScreenContent() {
HiraganaConverterTheme {
diff --git a/feature/setting/src/main/java/ksnd/hiraganaconverter/feature/setting/SettingScreen.kt b/feature/setting/src/main/java/ksnd/hiraganaconverter/feature/setting/SettingScreen.kt
index 65d99de1..1623b286 100644
--- a/feature/setting/src/main/java/ksnd/hiraganaconverter/feature/setting/SettingScreen.kt
+++ b/feature/setting/src/main/java/ksnd/hiraganaconverter/feature/setting/SettingScreen.kt
@@ -1,5 +1,6 @@
package ksnd.hiraganaconverter.feature.setting
+import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -36,6 +37,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.launch
@@ -155,7 +157,8 @@ private fun SettingScreenContent(
}
}
-@UiModePreview
+@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES, heightDp = 1300)
+@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO, heightDp = 1300)
@Composable
fun PreviewSettingScreenContent() {
HiraganaConverterTheme {
diff --git a/gradle.properties b/gradle.properties
index 2abf4c0e..062af591 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -23,3 +23,5 @@ kotlin.code.style=official
android.nonTransitiveRClass=true
org.gradle.caching=true
+
+roborazzi.record.image.extension=webp
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 91253de2..69ccae25 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,50 +1,51 @@
[versions]
-accompanist = "0.34.0"
-activity = "1.9.0"
-androidGradlePlugin = "8.5.1"
+accompanist = "0.36.0"
+activity = "1.9.3"
+androidGradlePlugin = "8.7.2"
androidxAppCompat = "1.7.0"
-androidxCompose = "1.6.8"
-androidxComposeMaterial3 = "1.2.1"
+androidxCompose = "1.7.5"
+androidxComposeMaterial3 = "1.3.1"
androidxCoreSplashscreen = "1.0.1"
androidxDataStore = "1.1.1"
androidxHilt = "1.2.0"
-androidxLifecycle = "2.8.2"
-androidxNavigation = "2.8.0-beta05"
+androidxLifecycle = "2.8.7"
+androidxNavigation = "2.8.4"
androidxTestCore = "1.6.1"
appUpdate = "2.1.0"
coil = "2.7.0"
-coroutines = "1.8.1"
-coroutineTest = "1.8.1"
+coroutines = "1.9.0"
+coroutineTest = "1.9.0"
dokka = "1.9.20"
gmsPlugin = "4.4.2"
-firebaseBom = "33.1.2"
+firebaseBom = "33.6.0"
firebaseCrashlyticsPlugin = "3.0.2"
firebasePerfPlugin = "1.4.2"
firebaseAppdistributionPlugin = "5.0.0"
-hilt = "2.51.1"
+hilt = "2.52"
junit4 = "4.13.2"
-kotlin = "2.0.0"
-kotlinxSerializationJson = "1.7.1"
-kotlinxDatetime = "0.6.0"
-ksp = "2.0.0-1.0.22"
-ktlint = "1.3.1"
-lazyColumnScrollbar = "2.1.0"
-lottie = "6.4.1"
+kotlin = "2.0.21"
+kotlinxSerializationJson = "1.7.3"
+kotlinxDatetime = "0.6.1"
+ksp = "2.0.21-1.0.28"
+ktlint = "1.4.1"
+lazyColumnScrollbar = "2.2.0"
+lottie = "6.6.0"
okhttp3 = "4.12.0"
playReview = "2.0.1"
retrofit = "2.11.0"
retrofitSerialization = "1.0.0"
-robolectric = "4.13"
+robolectric = "4.14"
room = "2.6.1"
secrets = "2.0.1"
timber = "5.0.1"
truth = "1.4.4"
-turbine = "1.1.0"
-mockk = "1.13.12"
-roborazzi = "1.23.0"
-showkase = "1.0.3"
-aboutLibraries = "11.2.2"
-konsist = "0.15.1"
+turbine = "1.2.0"
+mockk = "1.13.13"
+roborazzi = "1.32.2"
+aboutLibraries = "11.2.3"
+konsist = "0.16.1"
+composablePreviewScanner = "0.4.0"
+webpImageIO = "0.3.3"
# ** I'm using it, so no deletions allowed. **
jacoco = "0.8.10"
@@ -61,6 +62,7 @@ androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "androidxCompose" }
androidx-compose-ui-google-fonts = { group = "androidx.compose.ui", name = "ui-text-google-fonts", version.ref = "androidxCompose" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "androidxCompose" }
+androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "androidxCompose" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "androidxCompose" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "androidxCompose" }
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "androidxCoreSplashscreen" }
@@ -120,6 +122,10 @@ mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" }
roborazzi-compose = { group = "io.github.takahirom.roborazzi", name = "roborazzi-compose", version.ref = "roborazzi" }
roborazzi-junit4-rule = { group = "io.github.takahirom.roborazzi", name = "roborazzi-junit-rule", version.ref = "roborazzi" }
+roborazzi-compose-preview-scanner-support = { group = "io.github.takahirom.roborazzi", name = "roborazzi-compose-preview-scanner-support", version.ref = "roborazzi" }
+
+# ComposablePreviewScanner
+composable-preview-scanner = { group = "io.github.sergio-sastre.ComposablePreviewScanner", name = "android", version.ref = "composablePreviewScanner" }
# Firebase
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref="firebaseBom"}
@@ -149,17 +155,15 @@ kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-pl
# LazyColumnScrollbar
lazyColumnScrollbar = { group = "com.github.nanihadesuka", name = "LazyColumnScrollbar", version.ref = "lazyColumnScrollbar" }
-# Showkase
-showkase = { group = "com.airbnb.android", name = "showkase", version.ref = "showkase" }
-showkase-annotation = { group = "com.airbnb.android", name = "showkase-annotation", version.ref = "showkase" }
-showkase-processor = { group = "com.airbnb.android", name = "showkase-processor", version.ref = "showkase" }
-
# AboutLibraries
aboutLibraries = { group = "com.mikepenz", name = "aboutlibraries-compose-m3", version.ref = "aboutLibraries" }
# Konsist
konsist = { group = "com.lemonappdev", name = "konsist", version.ref = "konsist" }
+# WebP ImageIO
+webp-image-io = { group = "io.github.darkxanter", name = "webp-imageio", version.ref = "webpImageIO" }
+
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 2c352119..a4b76b95 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 09523c0e..94113f20 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME