diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 41f83c1d..ea465f2e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ captures/ .vscode/ .kotlin/ +.direnv/ .preview/ +.integration/ .DS_Store diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 543c9445..e822ea5c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,19 +1,21 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.compose.compiler) alias(libs.plugins.roborazzi) + alias(libs.plugins.ksp) } android { namespace = "com.subtracks" - compileSdk = 35 + compileSdk = 37 buildToolsVersion = "37.0.0" defaultConfig { applicationId = "com.subtracks.next" minSdk = 24 - targetSdk = 35 + targetSdk = 37 versionCode = 1 versionName = "0.1.0" } @@ -27,16 +29,14 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } - testOptions { unitTests { isIncludeAndroidResources = true } } + sourceSets.getByName("test").kotlin.directories.add("src/integrationTest/kotlin") + buildTypes { release { isMinifyEnabled = false @@ -44,10 +44,42 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } +} + roborazzi { outputDir.set(file("src/test/screenshots")) } +ksp { + arg("room.schemaLocation", "$projectDir/schemas") +} + +val integrationTestClassName = "com.subtracks.data.source.subsonic.SubsonicSourceIntegrationTest" + +tasks.withType().configureEach { + if (name.endsWith("UnitTest")) { + filter { + excludeTestsMatching(integrationTestClassName) + } + } +} + +tasks.register("integrationTest") { + description = "Runs the integration tests against locally started Subsonic servers" + group = "verification" + val unitTest = tasks.named("testDebugUnitTest") + dependsOn("compileDebugUnitTestKotlin") + testClassesDirs = files(provider { unitTest.get().testClassesDirs }) + classpath = files(provider { unitTest.get().classpath }) + filter { + includeTestsMatching(integrationTestClassName) + } +} + dependencies { implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.core.ktx) @@ -58,6 +90,12 @@ dependencies { implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.material3) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.androidx.sqlite.bundled) + implementation(libs.androidx.room3.runtime) + implementation(libs.androidx.room3.sqlite.wrapper) + implementation(libs.okhttp) + ksp(libs.androidx.room3.compiler) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) @@ -70,4 +108,7 @@ dependencies { testImplementation(libs.androidx.compose.ui.test.junit4) testImplementation(libs.androidx.test.ext.junit) testImplementation(libs.androidx.test.core) + testImplementation(libs.androidx.sqlite.bundled.jvm) + testImplementation(libs.okhttp.mockwebserver) + testImplementation(libs.kotlinx.coroutines.test) } diff --git a/build.gradle.kts b/build.gradle.kts index 97ebae34..dcceef40 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,6 @@ plugins { alias(libs.plugins.android.application) apply false - alias(libs.plugins.kotlin.android) apply false - alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.roborazzi) apply false + alias(libs.plugins.ksp) apply false } diff --git a/flake.lock b/flake.lock index d3ec4cb3..51dc2107 100644 --- a/flake.lock +++ b/flake.lock @@ -2,17 +2,17 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1780453794, - "narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=", + "lastModified": 1789724158, + "narHash": "sha256-nlKgrm0dsVhOSopKheVBCcOpiIticufPPVLdVOI0euA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338", + "rev": "0a3468a402c449992505b6a9fc5b06580141b750", "type": "github" }, "original": { "owner": "NixOS", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", - "rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338", "type": "github" } }, diff --git a/flake.nix b/flake.nix index e88b5eba..f6d3f081 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "Subtracks - Kotlin/Jetpack Compose Android development shell"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/6b316287bae2ee04c9b93c8c858d930fd07d7338"; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; }; outputs = @@ -23,9 +23,9 @@ }; }; android = pkgs.androidenv.composeAndroidPackages { - platformVersions = [ "35" ]; + platformVersions = [ "37" ]; buildToolsVersions = [ "37.0.0" ]; - platformToolsVersion = "37.0.0"; + platformToolsVersion = "37.0.1"; includeEmulator = false; includeSystemImages = false; includeNDK = false; @@ -38,8 +38,13 @@ default = pkgs.mkShell { packages = [ pkgs.jdk21 - pkgs.gradle + pkgs.gradle_9 pkgs.git + pkgs.curl + pkgs.unzip + pkgs.nushell + pkgs.navidrome + pkgs.gonic androidSdk android.platform-tools ]; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a422132a..e1f727b2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,28 +1,20 @@ [versions] -agp = "8.7.3" -kotlin = "2.1.20" -coreKtx = "1.15.0" -lifecycle = "2.8.7" -activityCompose = "1.9.3" -composeBom = "2024.12.01" -navigation = "2.8.5" -paging = "3.3.5" -koin = "4.0.0" -coroutines = "1.9.0" -serialization = "1.7.3" -sqldelight = "2.0.2" -okhttp = "4.12.0" -retrofit = "2.11.0" -coil = "3.0.4" -media3 = "1.5.0" -datastore = "1.1.1" -material = "1.12.0" -roborazzi = "1.32.2" -robolectric = "4.14.1" +agp = "9.4.1" +kotlin = "2.3.10" +ksp = "2.3.10" +coreKtx = "1.19.0" +lifecycle = "2.11.0" +activityCompose = "1.13.0" +composeBom = "2026.09.00" +coroutines = "1.11.0" +room3 = "3.0.3" +sqliteBundled = "2.7.1" +okhttp = "5.5.0" +roborazzi = "1.74.0" +robolectric = "4.17" junit = "4.13.2" -androidxTestExtJunit = "1.2.1" -androidxTestCore = "1.6.1" -androidxTestRunner = "1.6.2" +androidxTestExtJunit = "1.3.0" +androidxTestCore = "1.7.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -37,25 +29,15 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } -androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } -androidx-paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" } -androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" } -androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" } -koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" } -koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin" } +androidx-sqlite-bundled = { group = "androidx.sqlite", name = "sqlite-bundled", version.ref = "sqliteBundled" } +androidx-sqlite-bundled-jvm = { group = "androidx.sqlite", name = "sqlite-bundled-jvm", version.ref = "sqliteBundled" } +androidx-room3-runtime = { group = "androidx.room3", name = "room3-runtime", version.ref = "room3" } +androidx-room3-compiler = { group = "androidx.room3", name = "room3-compiler", version.ref = "room3" } +androidx-room3-sqlite-wrapper = { group = "androidx.room3", name = "room3-sqlite-wrapper", version.ref = "room3" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } -kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } -sqldelight-android-driver = { group = "app.cash.sqldelight", name = "android-driver", version.ref = "sqldelight" } -sqldelight-sqlite-driver = { group = "app.cash.sqldelight", name = "sqlite-driver", version.ref = "sqldelight" } +kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } -okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } -retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } -coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" } -coil-network-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" } -media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" } -media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" } -media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" } -material = { group = "com.google.android.material", name = "material", version.ref = "material" } +okhttp-mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp" } 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-junit-rule = { group = "io.github.takahirom.roborazzi", name = "roborazzi-junit-rule", version.ref = "roborazzi" } @@ -63,12 +45,9 @@ robolectric = { group = "org.robolectric", name = "robolectric", version.ref = " junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxTestExtJunit" } androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" } -androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTestRunner" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } -sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b95..eddabd2e 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 9715a887..4dddcf84 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,8 +1,10 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip -distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip +distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index f5feea6d..249efbb0 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -173,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -206,15 +203,14 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c..8508ef68 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL%