diff --git a/.drone.yml b/.drone.yml
index 569a7ae7724d..9347864809c2 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -148,7 +148,7 @@ steps:
commands:
- emulator -avd android -no-snapshot -gpu swiftshader_indirect -no-window -no-audio -skin 500x833 &
- sed -i s'#false#true#'g app/src/main/res/values/setup.xml
- - sed -i s'#showOnlyFailingTestsInReports = ciBuild#showOnlyFailingTestsInReports = false#' build.gradle
+ - sed -i s'#showOnlyFailingTestsInReports = ciBuild#showOnlyFailingTestsInReports = false#' build.gradle.kts
- scripts/wait_for_emulator.sh
- scripts/runAllScreenshotCombinations noCI false
- scripts/screenshotSummary.sh
diff --git a/app/build.gradle b/app/build.gradle
deleted file mode 100644
index 2ea044d01c42..000000000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,592 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-FileCopyrightText: 2024 Alper Ozturk
- * SPDX-FileCopyrightText: 2024 Tobias Kaminsky
- * SPDX-FileCopyrightText: 2024 Andy Scherzinger
- * SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas
- * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
- */
-import com.github.spotbugs.snom.Confidence
-import com.github.spotbugs.snom.Effort
-import com.github.spotbugs.snom.SpotBugsTask
-import org.gradle.internal.jvm.Jvm
-
-buildscript {
- dependencies {
- classpath "com.android.tools.build:gradle:$androidPluginVersion"
- classpath libs.spotbugs.gradle.plugin
- classpath libs.kotlin.gradle.plugin
- classpath libs.detekt.gradle.plugin
- classpath libs.commons.httpclient.commons.httpclient // remove after entire switch to lib v2
- classpath libs.shot
- classpath "org.jacoco:org.jacoco.core:$jacoco_version"
- classpath "org.jacoco:org.jacoco.report:$jacoco_version"
- classpath "org.jacoco:org.jacoco.agent:$jacoco_version"
- }
-}
-
-plugins {
- alias(libs.plugins.kotlin.compose)
- alias(libs.plugins.spotless)
- alias(libs.plugins.kapt)
- alias(libs.plugins.ksp) apply false
- alias(libs.plugins.kotlin.serialization)
-}
-
-apply plugin: "com.android.application"
-
-apply plugin: "kotlin-android"
-apply plugin: "kotlin-parcelize"
-apply plugin: "checkstyle"
-apply plugin: "pmd"
-apply from: "$rootProject.projectDir/jacoco.gradle"
-apply plugin: "com.github.spotbugs"
-apply plugin: "io.gitlab.arturbosch.detekt"
-
-// needed to make renovate run without shot, as shot requires Android SDK
-// https://github.com/pedrovgs/Shot/issues/300
-if (shotTest) {
- apply plugin: "shot"
-}
-apply plugin: "com.google.devtools.ksp"
-
-
-println "Gradle uses Java ${Jvm.current()}"
-
-configurations {
- configureEach {
- exclude group: "org.jetbrains", module: "annotations-java5" // via prism4j, already using annotations explicitly
- }
-}
-
-
-configurations.configureEach {
- resolutionStrategy.eachDependency {
- if (requested.group == "org.checkerframework" && requested.name != "checker-compat-qual") {
- useVersion(checkerVersion)
- because("https://github.com/google/ExoPlayer/issues/10007")
- }
-
- if (requested.group == "commons-logging" && requested.name == "commons-logging") {
- useTarget("org.slf4j:jcl-over-slf4j:1.7.36")
- }
- }
-}
-
-// semantic versioning for version code
-def versionMajor = 3
-def versionMinor = 35
-def versionPatch = 0
-def versionBuild = 0 // 0-50=Alpha / 51-98=RC / 90-99=stable
-
-
-def ndkEnv = new HashMap()
-file("$project.rootDir/ndk.env").readLines().each() {
- def (key, value) = it.tokenize("=")
- ndkEnv.put(key, value)
-}
-
-def perfAnalysis = project.hasProperty("perfAnalysis")
-
-def getConfigProperties() {
- def props = new Properties()
- def file = rootProject.file(".gradle/config.properties")
- if (file.exists()) {
- props.load(new FileInputStream(file))
- }
- return props
-}
-
-def configProps = getConfigProperties()
-
-android {
- // install this NDK version and Cmake to produce smaller APKs. Build will still work if not installed
- ndkVersion = "${ndkEnv.get("NDK_VERSION")}"
-
-
- namespace = "com.owncloud.android"
- testNamespace = "${namespace}.test"
-
- androidResources {
- generateLocaleConfig = true
- }
-
- defaultConfig {
- applicationId = "com.nextcloud.client"
- minSdk = 27
- targetSdk = 36
- compileSdk = 36
-
- buildConfigField "boolean", "CI", ciBuild.toString()
- buildConfigField "boolean", "RUNTIME_PERF_ANALYSIS", perfAnalysis.toString()
-
- javaCompileOptions {
- annotationProcessorOptions {
- arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
- }
- }
-
- // arguments to be passed to functional tests
- if (shotTest) {
- testInstrumentationRunner "com.karumi.shot.ShotTestRunner"
- } else {
- testInstrumentationRunner "com.nextcloud.client.TestRunner"
- }
- testInstrumentationRunnerArgument "TEST_SERVER_URL", "${NC_TEST_SERVER_BASEURL}"
- testInstrumentationRunnerArgument "TEST_SERVER_USERNAME", "${NC_TEST_SERVER_USERNAME}"
- testInstrumentationRunnerArgument "TEST_SERVER_PASSWORD", "${NC_TEST_SERVER_PASSWORD}"
- testInstrumentationRunnerArguments disableAnalytics: "true"
-
- versionCode versionMajor * 10000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
-
- if (versionBuild > 89) {
- versionName "${versionMajor}.${versionMinor}.${versionPatch}"
- } else if (versionBuild > 50) {
- versionName "${versionMajor}.${versionMinor}.${versionPatch} RC" + (versionBuild - 50)
- } else {
- versionName "${versionMajor}.${versionMinor}.${versionPatch} Alpha" + (versionBuild + 1)
- }
-
- // adapt structure from Eclipse to Gradle/Android Studio expectations;
- // see http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Configuring-the-Structure
-
- flavorDimensions += "default"
-
- buildTypes {
- release {
- buildConfigField "String", "NC_TEST_SERVER_DATA_STRING", "\"\""
- }
-
- debug {
- testCoverageEnabled = project.hasProperty("coverage")
- resConfigs "xxxhdpi"
-
- buildConfigField "String", "NC_TEST_SERVER_DATA_STRING", "\"nc://login/user:${configProps['NC_TEST_SERVER_USERNAME']}&password:${configProps['NC_TEST_SERVER_PASSWORD']}&server:${configProps['NC_TEST_SERVER_BASEURL']}\""
- }
- }
-
- buildFeatures {
- buildConfig = true
- }
-
- productFlavors {
- // used for f-droid
- generic {
- applicationId "com.nextcloud.client"
- dimension "default"
- }
-
- gplay {
- applicationId "com.nextcloud.client"
- dimension "default"
- }
-
- huawei {
- applicationId "com.nextcloud.client"
- dimension "default"
- }
-
- versionDev {
- applicationId "com.nextcloud.android.beta"
- dimension "default"
- versionCode 20220322
- versionName "20220322"
- }
-
- qa {
- applicationId "com.nextcloud.android.qa"
- dimension "default"
- versionCode 1
- versionName "1"
- }
- }
-
- testOptions {
- unitTests.returnDefaultValues = true
- animationsDisabled = true
- }
- }
-
- // adapt structure from Eclipse to Gradle/Android Studio expectations;
- // see http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Configuring-the-Structure
- packagingOptions {
- resources {
- excludes += "META-INF/LICENSE*"
- excludes += "META-INF/versions/9/OSGI-INF/MANIFEST*"
- pickFirst "MANIFEST.MF" // workaround for duplicated manifest on some dependencies
- }
- }
-
- tasks.register("checkstyle", Checkstyle) {
- configFile = file("${rootProject.projectDir}/checkstyle.xml")
- configProperties.checkstyleSuppressionsPath = file("${project.rootDir}/config/quality/checkstyle/suppressions.xml").absolutePath
- source "src"
- include "**/*.java"
- exclude "**/gen/**"
- classpath = files()
- }
-
- tasks.register("pmd", Pmd) {
- ruleSetFiles = files("${project.rootDir}/ruleset.xml")
- ignoreFailures = true // should continue checking
- ruleSets = []
-
- source "src"
- include "**/*.java"
- exclude "**/gen/**"
-
- reports {
- xml {
- destination = layout.buildDirectory.file("reports/pmd/pmd.xml").get().asFile
- }
- html {
- destination = layout.buildDirectory.file("reports/pmd/pmd.html").get().asFile
- }
- }
- }
-
- check.dependsOn "checkstyle", "spotbugsGplayDebug", "pmd", "lint", "spotlessKotlinCheck", "detekt"
-
- buildFeatures {
- dataBinding = true
- viewBinding = true
- aidl = true
- compose = true
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_17
- targetCompatibility JavaVersion.VERSION_17
- }
-
- kotlinOptions {
- jvmTarget = "17"
- }
-
- lint {
- abortOnError = false
- checkGeneratedSources = true
- disable "MissingTranslation", "GradleDependency", "VectorPath", "IconMissingDensityFolder", "IconDensities", "GoogleAppIndexingWarning", "MissingDefaultResource", "InvalidPeriodicWorkRequestInterval", "StringFormatInvalid", "MissingQuantity"
- htmlOutput = layout.buildDirectory.file("reports/lint/lint.html").get().asFile
- htmlReport = true
- }
-
- sourceSets {
- // Adds exported schema location as test app assets.
- androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
- }
-
- kapt {
- useBuildCache = true
- }
-}
-
-dependencies {
- // region Nextcloud library
- implementation("com.github.nextcloud:android-library:$androidLibraryVersion") {
- exclude group: "org.ogce", module: "xpp3" // unused in Android and brings wrong Junit version
- }
- // endregion
-
- // region Splash Screen
- implementation libs.splashscreen
- // endregion
-
- // region Jetpack Compose
- implementation(platform(libs.compose.bom))
- implementation(libs.material.icons.core)
- implementation(libs.compose.ui)
- implementation(libs.compose.ui.graphics)
- implementation(libs.compose.material3)
- debugImplementation(libs.compose.ui.tooling)
- implementation(libs.compose.ui.tooling.preview)
- // endregion
-
- // region Media3
- implementation libs.media3.ui
- implementation libs.media3.session
- implementation libs.media3.exoplayer
- implementation libs.media3.datasource
- // endregion
-
- // region Room
- implementation libs.room.runtime
- ksp "androidx.room:room-compiler:$roomVersion"
- androidTestImplementation libs.room.testing
- // endregion
-
- // region Espresso
- androidTestImplementation libs.espresso.core
- androidTestImplementation libs.espresso.contrib
- androidTestImplementation libs.espresso.web
- androidTestImplementation libs.espresso.accessibility
- androidTestImplementation libs.espresso.intents
- androidTestImplementation libs.espresso.idling.resource
- // endregion
-
- // region Glide
- implementation libs.glide
- ksp libs.ksp
- // endregion
-
- // region UI
- implementation libs.appcompat
- implementation libs.webkit
- implementation libs.cardview
- implementation libs.exifinterface
- implementation libs.fragment.ktx
- // endregion
-
- // region Worker
- implementation libs.work.runtime
- implementation libs.work.runtime.ktx
- // endregion
-
- // region Lifecycle
- implementation libs.lifecycle.viewmodel.ktx
- implementation libs.lifecycle.service
- implementation(libs.lifecycle.runtime.ktx)
- // endregion
-
- // region JUnit
- androidTestImplementation libs.junit
- androidTestImplementation libs.rules
- androidTestImplementation libs.runner
- androidTestUtil libs.orchestrator
- androidTestImplementation libs.core.ktx
- androidTestImplementation libs.core.testing
- // endregion
-
- // region other libraries
- compileOnly libs.org.jbundle.util.osgi.wrapped.org.apache.http.client
- implementation libs.commons.httpclient.commons.httpclient // remove after entire switch to lib v2
- implementation libs.jackrabbit.webdav // remove after entire switch to lib v2
- implementation libs.constraintlayout
- implementation libs.legacy.support.v4
- implementation libs.material
- implementation libs.disklrucache
- implementation libs.juniversalchardet // need this version for Android <7
- compileOnly libs.annotations
- implementation libs.commons.io
- implementation libs.eventbus
- implementation libs.ez.vcard
- implementation libs.nnio
- implementation libs.bcpkix.jdk18on
- implementation libs.gson
- implementation libs.sectioned.recyclerview
- implementation libs.photoview
- implementation libs.android.gif.drawable
- implementation libs.qrcodescanner // "com.github.blikoon:QRCodeScanner:0.1.2"
- implementation libs.flexbox
- implementation libs.androidsvg
- implementation libs.annotation
- implementation libs.emoji.google
- // endregion
-
- // region AppScan, document scanner not available on FDroid (generic) due to OpenCV binaries
- gplayImplementation project(":appscan")
- huaweiImplementation project(":appscan")
- qaImplementation project(":appscan")
- // endregion
-
- // region SpotBugs
- spotbugsPlugins libs.findsecbugs.plugin
- spotbugsPlugins libs.fb.contrib
- // endregion
-
- // region Dagger
- implementation libs.dagger
- implementation libs.dagger.android
- implementation libs.dagger.android.support
- kapt "com.google.dagger:dagger-compiler:$daggerVersion"
- kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
- // endregion
-
- // region Crypto
- implementation libs.conscrypt.android
- // endregion
-
- // region Library
- implementation libs.library
- // endregion
-
- // region Shimmer
- implementation libs.loaderviewlibrary
- // endregion
-
- // region Markdown rendering
- implementation libs.core
- implementation libs.ext.strikethrough
- implementation libs.ext.tables
- implementation libs.ext.tasklist
- implementation libs.html
- implementation libs.syntax.highlight
- implementation libs.prism4j
- kapt "io.noties:prism4j-bundler:$prismVersion"
- // endregion
-
- // region Image cropping / rotation
- implementation libs.android.image.cropper
- // endregion
-
- // region Maps
- implementation libs.osmdroid.android
- // endregion
-
- // region iCal4j
- implementation(libs.ical4j) {
- ["org.apache.commons", "commons-logging"].each {
- exclude group: "$it"
- }
- }
- // endregion
-
- // region LeakCanary
- if (perfAnalysis) {
- debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
- }
- // endregion
-
- // region Local Unit Test
- testImplementation libs.junit.junit
- testImplementation libs.mockito.core
- testImplementation libs.test.core
- testImplementation libs.json
- testImplementation libs.mockito.kotlin
- testImplementation libs.core.testing
- testImplementation "io.mockk:mockk:$mockkVersion"
- testImplementation libs.mockk.android
- // endregion
-
- // region Mocking support
- androidTestImplementation libs.dexopener // required to allow mocking on API 27 and older
- androidTestImplementation libs.mockito.kotlin
- androidTestImplementation libs.mockito.core
- androidTestImplementation(libs.mockito.android)
- androidTestImplementation libs.mockk.android
- androidTestImplementation libs.screenshot.core
- // endregion
-
- // region UIAutomator
- // UIAutomator - for cross-app UI tests, and to grant screen is turned on in Espresso tests
- // androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
- // fix conflict in dependencies; see http://g.co/androidstudio/app-test-app-conflict for details
- // androidTestImplementation "com.android.support:support-annotations:${supportLibraryVersion}"
- androidTestImplementation libs.screengrab
- // endregion
-
- // region Kotlin
- implementation libs.kotlin.stdlib
- // endregion
-
- // region Stateless
- implementation libs.stateless4j
- // endregion
-
- // region Google Play dependencies, upon each update first test: new registration, receive push
- gplayImplementation libs.firebase.messaging
- gplayImplementation libs.play.services.base
- gplayImplementation libs.review.ktx
- // endregion
-
- // region UI
- implementation libs.ui
- // endregion
-
- // region Image loading
- implementation libs.coil
- // endregion
-
- // kotlinx.serialization
- implementation(libs.kotlinx.serialization.json)
-}
-
-
-configurations.configureEach {
- resolutionStrategy {
- force "org.objenesis:objenesis:3.4"
- eachDependency { details ->
- if ("org.jacoco" == details.requested.group) {
- details.useVersion "$jacoco_version"
- }
- }
- }
-}
-
-// Run the compiler as a separate process
-tasks.withType(JavaCompile).configureEach {
- options.fork = true
-
- // Enable Incremental Compilation
- options.incremental = true
-}
-
-tasks.withType(Test).configureEach {
- // Run tests in parallel
- maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
-
- // increased logging for tests
- testLogging {
- events "passed", "skipped", "failed"
- }
-}
-
-android.applicationVariants.configureEach { variant ->
- variant.outputs.configureEach { output -> outputFileName = "${output.baseName}-${variant.versionCode}.apk"
- }
-}
-
-spotless {
- kotlin {
- target "**/*.kt"
- ktlint()
- }
-}
-
-detekt {
- config.setFrom("detekt.yml")
-}
-
-if (shotTest) {
- shot {
- showOnlyFailingTestsInReports = ciBuild
- // CI environment renders some shadows slightly different from local VMs
- // Add a 0.5% tolerance to account for that
- tolerance = ciBuild ? 0.1 : 0
- }
-}
-
-jacoco {
- toolVersion = "$jacoco_version"
-}
-
-spotbugs {
- ignoreFailures = true // should continue checking
- effort = Effort.MAX
- reportLevel = Confidence.valueOf("MEDIUM")
-}
-
-tasks.withType(SpotBugsTask){task ->
- String variantNameCap = task.name.replace("spotbugs", "")
- String variantName = variantNameCap.substring(0, 1).toLowerCase() + variantNameCap.substring(1)
- dependsOn "compile${variantNameCap}Sources"
-
- classes = fileTree(layout.buildDirectory.get().asFile.toString()+"/intermediates/javac/${variantName}/compile${variantNameCap}JavaWithJavac/classes/")
- excludeFilter = file("${project.rootDir}/scripts/analysis/spotbugs-filter.xml")
- reports {
- xml {
- required = true
- }
- html {
- required = true
- outputLocation = layout.buildDirectory.file("reports/spotbugs/spotbugs.html").get().asFile
- stylesheet = "fancy.xsl"
- }
- }
-}
-
-ksp {
- arg("room.schemaLocation", "$projectDir/schemas")
-}
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 000000000000..f7294858fb1e
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,517 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Jimly Asshiddiqy
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+@file:Suppress("UnstableApiUsage", "DEPRECATION")
+
+import com.android.build.gradle.internal.api.ApkVariantOutputImpl
+import com.github.spotbugs.snom.Confidence
+import com.github.spotbugs.snom.Effort
+import com.github.spotbugs.snom.SpotBugsTask
+import com.karumi.shot.ShotExtension
+import org.gradle.internal.jvm.Jvm
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import java.io.FileInputStream
+import java.util.Properties
+
+val shotTest = System.getenv("SHOT_TEST") == "true"
+val ciBuild = System.getenv("CI") == "true"
+val perfAnalysis = project.hasProperty("perfAnalysis")
+
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.compose)
+ alias(libs.plugins.spotless)
+ alias(libs.plugins.kapt)
+ alias(libs.plugins.ksp)
+ alias(libs.plugins.kotlin.serialization)
+ alias(libs.plugins.kotlin.parcelize)
+ alias(libs.plugins.jetbrains.kotlin.android)
+ alias(libs.plugins.spotbugs)
+ alias(libs.plugins.detekt)
+ // needed to make renovate run without shot, as shot requires Android SDK
+ // https://github.com/pedrovgs/Shot/issues/300
+ if (System.getenv("SHOT_TEST") == "true") alias(libs.plugins.shot)
+ id("checkstyle")
+ id("pmd")
+}
+apply(from = "${rootProject.projectDir}/jacoco.gradle.kts")
+
+println("Gradle uses Java ${Jvm.current()}")
+
+configurations.configureEach {
+ // via prism4j, already using annotations explicitly
+ exclude(group = "org.jetbrains", module = "annotations-java5")
+
+ resolutionStrategy {
+ force(libs.objenesis)
+
+ eachDependency {
+ if (requested.group == "org.checkerframework" && requested.name != "checker-compat-qual") {
+ useVersion(libs.versions.checker.get())
+ because("https://github.com/google/ExoPlayer/issues/10007")
+ } else if (requested.group == "org.jacoco") {
+ useVersion(libs.versions.jacoco.get())
+ } else if (requested.group == "commons-logging" && requested.name == "commons-logging") {
+ useTarget(libs.slfj)
+ }
+ }
+ }
+}
+
+// semantic versioning for version code
+val versionMajor = 3
+val versionMinor = 35
+val versionPatch = 0
+val versionBuild = 0 // 0-50=Alpha / 51-98=RC / 90-99=stable
+
+val ndkEnv = buildMap {
+ file("${project.rootDir}/ndk.env").readLines().forEach {
+ val (key, value) = it.split("=")
+ put(key, value)
+ }
+}
+
+val configProps = Properties().apply {
+ val file = rootProject.file(".gradle/config.properties")
+ if (file.exists()) load(FileInputStream(file))
+}
+
+val ncTestServerUsername = configProps["NC_TEST_SERVER_USERNAME"]
+val ncTestServerPassword = configProps["NC_TEST_SERVER_PASSWORD"]
+val ncTestServerBaseUrl = configProps["NC_TEST_SERVER_BASEURL"]
+
+android {
+ // install this NDK version and Cmake to produce smaller APKs. Build will still work if not installed
+ ndkVersion = "${ndkEnv["NDK_VERSION"]}"
+
+ namespace = "com.owncloud.android"
+ testNamespace = "${namespace}.test"
+
+ androidResources.generateLocaleConfig = true
+
+ defaultConfig {
+ applicationId = "com.nextcloud.client"
+ minSdk = 27
+ targetSdk = 36
+ compileSdk = 36
+
+ buildConfigField("boolean", "CI", ciBuild.toString())
+ buildConfigField("boolean", "RUNTIME_PERF_ANALYSIS", perfAnalysis.toString())
+
+ javaCompileOptions.annotationProcessorOptions {
+ arguments += mapOf("room.schemaLocation" to "$projectDir/schemas")
+ }
+
+ // arguments to be passed to functional tests
+ testInstrumentationRunner = if (shotTest) "com.karumi.shot.ShotTestRunner"
+ else "com.nextcloud.client.TestRunner"
+
+ testInstrumentationRunnerArguments += mapOf(
+ "TEST_SERVER_URL" to ncTestServerBaseUrl.toString(),
+ "TEST_SERVER_USERNAME" to ncTestServerUsername.toString(),
+ "TEST_SERVER_PASSWORD" to ncTestServerPassword.toString()
+ )
+ testInstrumentationRunnerArguments["disableAnalytics"] = "true"
+
+ versionCode = versionMajor * 10000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
+ versionName = when {
+ versionBuild > 89 -> "${versionMajor}.${versionMinor}.${versionPatch}"
+ versionBuild > 50 -> "${versionMajor}.${versionMinor}.${versionPatch} RC" + (versionBuild - 50)
+ else -> "${versionMajor}.${versionMinor}.${versionPatch} Alpha" + (versionBuild + 1)
+ }
+
+ // adapt structure from Eclipse to Gradle/Android Studio expectations;
+ // see http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Configuring-the-Structure
+
+ flavorDimensions += "default"
+
+ buildTypes {
+ release {
+ buildConfigField("String", "NC_TEST_SERVER_DATA_STRING", "\"\"")
+ }
+
+ debug {
+ enableUnitTestCoverage = project.hasProperty("coverage")
+ resConfigs("xxxhdpi")
+
+ buildConfigField(
+ "String",
+ "NC_TEST_SERVER_DATA_STRING",
+ "\"nc://login/user:${ncTestServerUsername}&password:${ncTestServerPassword}&server:${ncTestServerBaseUrl}\""
+ )
+ }
+ }
+
+ productFlavors {
+ // used for f-droid
+ register("generic") {
+ applicationId = "com.nextcloud.client"
+ dimension = "default"
+ }
+
+ register("gplay") {
+ applicationId = "com.nextcloud.client"
+ dimension = "default"
+ }
+
+ register("huawei") {
+ applicationId = "com.nextcloud.client"
+ dimension = "default"
+ }
+
+ register("versionDev") {
+ applicationId = "com.nextcloud.android.beta"
+ dimension = "default"
+ versionCode = 20220322
+ versionName = "20220322"
+ }
+
+ register("qa") {
+ applicationId = "com.nextcloud.android.qa"
+ dimension = "default"
+ versionCode = 1
+ versionName = "1"
+ }
+ }
+ }
+
+ applicationVariants.configureEach {
+ outputs.configureEach {
+ if (this is ApkVariantOutputImpl) this.outputFileName = "${this.baseName}-${this.versionCode}.apk"
+ }
+ }
+
+ testOptions {
+ unitTests.isReturnDefaultValues = true
+ animationsDisabled = true
+ }
+
+ // adapt structure from Eclipse to Gradle/Android Studio expectations;
+ // see http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Configuring-the-Structure
+ packaging.resources {
+ excludes.addAll(listOf("META-INF/LICENSE*", "META-INF/versions/9/OSGI-INF/MANIFEST*"))
+ pickFirsts.add("MANIFEST.MF") // workaround for duplicated manifest on some dependencies
+ }
+
+ buildFeatures {
+ buildConfig = true
+ dataBinding = true
+ viewBinding = true
+ aidl = true
+ compose = true
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ lint {
+ abortOnError = false
+ checkGeneratedSources = true
+ disable.addAll(
+ listOf(
+ "MissingTranslation",
+ "GradleDependency",
+ "VectorPath",
+ "IconMissingDensityFolder",
+ "IconDensities",
+ "GoogleAppIndexingWarning",
+ "MissingDefaultResource",
+ "InvalidPeriodicWorkRequestInterval",
+ "StringFormatInvalid",
+ "MissingQuantity"
+ )
+ )
+ htmlOutput = layout.buildDirectory.file("reports/lint/lint.html").get().asFile
+ htmlReport = true
+ }
+
+ sourceSets {
+ // Adds exported schema location as test app assets.
+ getByName("androidTest") {
+ assets.srcDirs(files("$projectDir/schemas"))
+ }
+ }
+
+}
+
+kapt.useBuildCache = true
+
+ksp.arg("room.schemaLocation", "$projectDir/schemas")
+
+kotlin.compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
+
+spotless.kotlin {
+ target("**/*.kt")
+ ktlint()
+}
+
+detekt.config.setFrom("detekt.yml")
+
+if (shotTest) configure {
+ showOnlyFailingTestsInReports = ciBuild
+ // CI environment renders some shadows slightly different from local VMs
+ // Add a 0.5% tolerance to account for that
+ tolerance = if (ciBuild) 0.1 else 0.0
+}
+
+
+spotbugs {
+ ignoreFailures = true // should continue checking
+ effort = Effort.MAX
+ reportLevel = Confidence.valueOf("MEDIUM")
+}
+
+tasks.register("checkstyle") {
+ configFile = file("${rootProject.projectDir}/checkstyle.xml")
+ setConfigProperties(
+ "checkstyleSuppressionsPath" to file("${rootProject.rootDir}/suppressions.xml").absolutePath
+ )
+ source("src")
+ include("**/*.java")
+ exclude("**/gen/**")
+ classpath = files()
+}
+
+tasks.register("pmd") {
+ ruleSetFiles = files("${rootProject.rootDir}/ruleset.xml")
+ ignoreFailures = true // should continue checking
+ ruleSets = emptyList()
+
+ source("src")
+ include("**/*.java")
+ exclude("**/gen/**")
+
+ reports {
+ xml.outputLocation.set(layout.buildDirectory.file("reports/pmd/pmd.xml").get().asFile)
+ html.outputLocation.set(layout.buildDirectory.file("reports/pmd/pmd.html").get().asFile)
+ }
+}
+
+tasks.withType().configureEach {
+ val variantNameCap = name.replace("spotbugs", "")
+ val variantName = variantNameCap.substring(0, 1).lowercase() + variantNameCap.substring(1)
+ dependsOn("compile${variantNameCap}Sources")
+
+ classes =
+ fileTree(layout.buildDirectory.get().asFile.toString() + "/intermediates/javac/${variantName}/compile${variantNameCap}JavaWithJavac/classes/")
+ excludeFilter = file("${project.rootDir}/scripts/analysis/spotbugs-filter.xml")
+ reports {
+ register("xml") {
+ required = true
+ }
+ register("html") {
+ required = true
+ outputLocation = layout.buildDirectory.file("reports/spotbugs/spotbugs.html").get().asFile
+ setStylesheet("fancy.xsl")
+ }
+ }
+}
+
+// Run the compiler as a separate process
+tasks.withType().configureEach {
+ options.isFork = true
+
+ // Enable Incremental Compilation
+ options.isIncremental = true
+}
+
+tasks.withType().configureEach {
+ // Run tests in parallel
+ maxParallelForks = Runtime.getRuntime().availableProcessors().div(2)
+
+ // increased logging for tests
+ testLogging.events("passed", "skipped", "failed")
+}
+
+tasks.named("check").configure {
+ dependsOn("checkstyle", "spotbugsGplayDebug", "pmd", "lint", "spotlessKotlinCheck", "detekt")
+}
+
+dependencies {
+ // region Nextcloud library
+ implementation(libs.android.library) {
+ exclude(group = "org.ogce", module = "xpp3") // unused in Android and brings wrong Junit version
+ }
+ // endregion
+
+ // region Splash Screen
+ implementation(libs.splashscreen)
+ // endregion
+
+ // region Jetpack Compose
+ implementation(platform(libs.compose.bom))
+ implementation(libs.material.icons.core)
+ implementation(libs.compose.ui)
+ implementation(libs.compose.ui.graphics)
+ implementation(libs.compose.material3)
+ implementation(libs.compose.ui.tooling.preview)
+ debugImplementation(libs.compose.ui.tooling)
+ // endregion
+
+ // region Media3
+ implementation(libs.bundles.media3)
+ // endregion
+
+ // region Room
+ implementation(libs.room.runtime)
+ ksp(libs.room.compiler)
+ androidTestImplementation(libs.room.testing)
+ // endregion
+
+ // region Espresso
+ androidTestImplementation(libs.bundles.espresso)
+ // endregion
+
+ // region Glide
+ implementation(libs.glide)
+ ksp(libs.ksp)
+ // endregion
+
+ // region UI
+ implementation(libs.bundles.ui)
+ // endregion
+
+ // region Worker
+ implementation(libs.work.runtime)
+ implementation(libs.work.runtime.ktx)
+ // endregion
+
+ // region Lifecycle
+ implementation(libs.lifecycle.viewmodel.ktx)
+ implementation(libs.lifecycle.service)
+ implementation(libs.lifecycle.runtime.ktx)
+ // endregion
+
+ // region JUnit
+ androidTestImplementation(libs.junit)
+ androidTestImplementation(libs.rules)
+ androidTestImplementation(libs.runner)
+ androidTestUtil(libs.orchestrator)
+ androidTestImplementation(libs.core.ktx)
+ androidTestImplementation(libs.core.testing)
+ // endregion
+
+ // region other libraries
+ compileOnly(libs.org.jbundle.util.osgi.wrapped.org.apache.http.client)
+ implementation(libs.commons.httpclient.commons.httpclient) // remove after entire switch to lib v2
+ implementation(libs.jackrabbit.webdav) // remove after entire switch to lib v2
+ implementation(libs.constraintlayout)
+ implementation(libs.legacy.support.v4)
+ implementation(libs.material)
+ implementation(libs.disklrucache)
+ implementation(libs.juniversalchardet) // need this version for Android <7
+ compileOnly(libs.annotations)
+ implementation(libs.commons.io)
+ implementation(libs.eventbus)
+ implementation(libs.ez.vcard)
+ implementation(libs.nnio)
+ implementation(libs.bcpkix.jdk18on)
+ implementation(libs.gson)
+ implementation(libs.sectioned.recyclerview)
+ implementation(libs.photoview)
+ implementation(libs.android.gif.drawable)
+ implementation(libs.qrcodescanner) // "com.github.blikoon:QRCodeScanner:0.1.2"
+ implementation(libs.flexbox)
+ implementation(libs.androidsvg)
+ implementation(libs.annotation)
+ implementation(libs.emoji.google)
+ // endregion
+
+ // region AppScan, document scanner not available on FDroid (generic) due to OpenCV binaries
+ "gplayImplementation"(project(":appscan"))
+ "huaweiImplementation"(project(":appscan"))
+ "qaImplementation"(project(":appscan"))
+ // endregion
+
+ // region SpotBugs
+ spotbugsPlugins(libs.findsecbugs.plugin)
+ spotbugsPlugins(libs.fb.contrib)
+ // endregion
+
+ // region Dagger
+ implementation(libs.dagger)
+ implementation(libs.dagger.android)
+ implementation(libs.dagger.android.support)
+ ksp(libs.dagger.compiler)
+ ksp(libs.dagger.processor)
+ // endregion
+
+ // region Crypto
+ implementation(libs.conscrypt.android)
+ // endregion
+
+ // region Library
+ implementation(libs.library)
+ // endregion
+
+ // region Shimmer
+ implementation(libs.loaderviewlibrary)
+ // endregion
+
+ // region Markdown rendering
+ implementation(libs.bundles.markdown.rendering)
+ kapt(libs.prism4j.bundler)
+ // endregion
+
+ // region Image cropping / rotation
+ implementation(libs.android.image.cropper)
+ // endregion
+
+ // region Maps
+ implementation(libs.osmdroid.android)
+ // endregion
+
+ // region iCal4j
+ implementation(libs.ical4j) {
+ listOf("org.apache.commons", "commons-logging").forEach { groupName -> exclude(group = groupName) }
+ }
+ // endregion
+
+ // region LeakCanary
+ if (perfAnalysis) debugImplementation(libs.leakcanary)
+ // endregion
+
+ // region Local Unit Test
+ testImplementation(libs.bundles.unit.test)
+ // endregion
+
+ // region Mocking support
+ androidTestImplementation(libs.bundles.mocking)
+ // endregion
+
+ // region UIAutomator
+ // UIAutomator - for cross-app UI tests, and to grant screen is turned on in Espresso tests
+ // androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0"
+ // fix conflict in dependencies; see http://g.co/androidstudio/app-test-app-conflict for details
+ // androidTestImplementation("com.android.support:support-annotations:${supportLibraryVersion}"
+ androidTestImplementation(libs.screengrab)
+ // endregion
+
+ // region Kotlin
+ implementation(libs.kotlin.stdlib)
+ // endregion
+
+ // region Stateless
+ implementation(libs.stateless4j)
+ // endregion
+
+ // region Google Play dependencies, upon each update first test: new registration, receive push
+ "gplayImplementation"(libs.bundles.gplay)
+ // endregion
+
+ // region UI
+ implementation(libs.ui)
+ // endregion
+
+ // region Image loading
+ implementation(libs.coil)
+ // endregion
+
+ // kotlinx.serialization
+ implementation(libs.kotlinx.serialization.json)
+}
\ No newline at end of file
diff --git a/appscan/build.gradle b/appscan/build.gradle
deleted file mode 100644
index 00984b58de8d..000000000000
--- a/appscan/build.gradle
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-FileCopyrightText: 2024 Alper Ozturk
- * SPDX-FileCopyrightText: 2023 Álvaro Brey
- * SPDX-FileCopyrightText: 2023 Andy Scherzinger
- * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
- */
-buildscript {
- dependencies {
- classpath "com.android.tools.build:gradle:$androidPluginVersion"
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- }
-}
-
-apply plugin: "com.android.library"
-apply plugin: "kotlin-android"
-
-android {
- namespace = "com.nextcloud.appscan"
-
- defaultConfig {
- minSdk = 27
- targetSdk = 36
- compileSdk = 36
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- minifyEnabled = false
- }
- }
-
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- }
-
- kotlinOptions {
- jvmTarget = "17"
- }
-}
-
-dependencies {
- implementation "androidx.appcompat:appcompat:$appCompatVersion"
- implementation "com.github.Hazzatur:Document-Scanning-Android-SDK:$documentScannerVersion"
- implementation "com.github.nextcloud.android-common:ui:$androidCommonLibraryVersion"
-}
diff --git a/appscan/build.gradle.kts b/appscan/build.gradle.kts
new file mode 100644
index 000000000000..5311613b85c7
--- /dev/null
+++ b/appscan/build.gradle.kts
@@ -0,0 +1,47 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Jimly Asshiddiqy
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.jetbrains.kotlin.android)
+}
+
+android {
+ namespace = "com.nextcloud.appscan"
+
+ defaultConfig {
+ minSdk = 27
+ compileSdk = 36
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ lint.targetSdk = 36
+ testOptions.targetSdk = 36
+}
+
+kotlin.compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_17)
+ freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn")
+}
+
+dependencies {
+ implementation(libs.appcompat)
+ implementation(libs.document.scanning.android.sdk)
+ implementation(libs.ui)
+}
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index ef00fbe90d1e..000000000000
--- a/build.gradle
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-FileCopyrightText: 2024 Alper Ozturk
- * SPDX-FileCopyrightText: 2023 Tobias Kaminsky
- * SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas
- * SPDX-FileCopyrightText: 2016 Andy Scherzinger
- * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
- */
-buildscript {
- ext {
- androidLibraryVersion ="2f798d3f4b0b18458af0c917eb39ad8ca45e5b55"
- androidCommonLibraryVersion = "0.29.0"
- androidPluginVersion = '8.13.0'
- androidxMediaVersion = "1.5.1"
- androidxTestVersion = "1.7.0"
- appCompatVersion = "1.7.1"
- checkerVersion = "3.21.2"
- daggerVersion = "2.57.2"
- documentScannerVersion = "1.2.3"
- espressoVersion = "3.6.1"
- jacoco_version = "0.8.14"
- kotlin_version = "2.2.21"
- markwonVersion = "4.6.2"
- mockitoVersion = "4.11.0"
- mockitoKotlinVersion = "4.1.0"
- mockkVersion = "1.14.6"
- prismVersion = "2.0.0"
- roomVersion = "2.8.0"
- workRuntime = "2.10.3"
-
- ciBuild = System.getenv("CI") == "true"
- shotTest = System.getenv("SHOT_TEST") == "true"
- }
-}
-
-subprojects {
- buildscript {
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
- }
- repositories {
- google()
- mavenCentral()
- maven { url = "https://jitpack.io" }
- }
-}
-
-tasks.register("clean", Delete) {
- delete rootProject.layout.buildDirectory
-}
-
-tasks.register("installGitHooks", Copy) {
- def sourceFolder = "${rootProject.projectDir}/scripts/hooks"
- def destFolder = "${rootProject.projectDir}/.git/hooks"
-
- description = "Install git hooks"
-
- from(sourceFolder) {
- include "*"
- }
- into destFolder
-
- eachFile { file ->
- println "${sourceFolder}/${file.relativeSourcePath} -> ${destFolder}/${file.path}"
- }
-}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 000000000000..75f16a6b6ae1
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,37 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Jimly Asshiddiqy
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.android.library) apply false
+ alias(libs.plugins.jetbrains.kotlin.android) apply false
+ alias(libs.plugins.kotlin.compose) apply false
+ alias(libs.plugins.spotless) apply false
+ alias(libs.plugins.kapt) apply false
+ alias(libs.plugins.ksp) apply false
+ alias(libs.plugins.kotlin.serialization) apply false
+ alias(libs.plugins.kotlin.parcelize) apply false
+ alias(libs.plugins.spotbugs) apply false
+ alias(libs.plugins.detekt) apply false
+ // needed to make renovate run without shot, as shot requires Android SDK
+ // https://github.com/pedrovgs/Shot/issues/300
+ alias(libs.plugins.shot) apply false
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
+
+tasks.register("installGitHooks") {
+ description = "Install git hooks"
+
+ val sourceFolder = "${rootProject.projectDir}/scripts/hooks"
+ val destFolder = "${rootProject.projectDir}/.git/hooks"
+
+ from(sourceFolder) { include("*") }
+ into(destFolder)
+ eachFile { println("${sourceFolder}/${file.path} -> ${destFolder}/${file.path}") }
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 513283130f07..dc1a6e3909d8 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -5,23 +5,30 @@
androidCommonLibraryVersion = "0.29.0"
androidGifDrawableVersion = "1.2.29"
androidImageCropperVersion = "4.6.0"
+androidLibraryVersion = "3546bd82fc"
+androidPluginVersion = '8.13.0'
androidsvgVersion = "1.4"
+androidxMediaVersion = "1.5.1"
androidxTestVersion = "1.7.0"
-annotationsVersion = "3.0.1u2"
annotationVersion = "1.9.1"
+annotationsVersion = "3.0.1u2"
appCompatVersion = "1.7.1"
bcpkixJdk18onVersion = "1.81"
cardviewVersion = "1.0.0"
+checker = "3.21.2"
coilVersion = "2.7.0"
commonsHttpclient = "3.1"
commonsIoVersion = "2.20.0"
+composeBom = "2025.10.01"
conscryptAndroidVersion = "2.5.3"
constraintlayoutVersion = "2.2.1"
coreTestingVersion = "2.2.0"
coreVersion = "0.15.0"
daggerVersion = "2.57.2"
+detektGradlePlugin = "1.23.8"
dexopenerVersion = "2.0.5"
disklrucacheVersion = "2.0.2"
+documentScannerVersion = "1.2.3"
emojiGoogleVersion = "0.21.0"
espressoVersion = "3.7.0"
eventbusVersion = "3.3.1"
@@ -36,10 +43,15 @@ glide = "5.0.5"
gsonVersion = "2.13.2"
ical4jVersion = "3.2.19"
jackrabbitWebdavVersion = "2.13.5"
+jacoco = "0.8.13"
jsonVersion = "20250517"
junit = "4.13.2"
junitVersion = "1.3.0"
juniversalchardetVersion = "2.5.0"
+kotlin = "2.2.21"
+kotlinxSerializationJson = "1.9.0"
+ksp = "2.3.0"
+leakcanary = "2.14"
legacySupportV4Version = "1.0.0"
libraryVersion = "1.3.0"
lifecycleViewmodelKtxVersion = "2.9.4"
@@ -52,6 +64,7 @@ mockitoKotlinVersion = "4.1.0"
mockitoVersion = "4.11.0"
mockkVersion = "1.14.6"
nnioVersion = "0.3.1"
+objenesis = "3.4"
orchestratorVersion = "1.6.1"
orgJbundleUtilOsgiWrappedOrgApacheHttpClientVersion = "4.1.2"
osmdroidAndroidVersion = "6.1.20"
@@ -64,23 +77,17 @@ roomVersion = "2.8.0"
screengrabVersion = "2.1.1"
sectionedRecyclerviewVersion = "0.6.1"
shotVersion = "6.1.0"
+slfj = "1.7.36"
splash-screen = "1.0.1"
-composeBom = "2025.10.01"
-materialIcons = "1.7.8"
spotbugsGradlePlugin = "6.4.4"
-detektGradlePlugin = "1.23.8"
spotless = "7.2.1"
stateless4jVersion = "2.6.0"
webkitVersion = "1.14.0"
workRuntime = "2.10.4"
-kotlinxSerializationJson = "1.9.0"
-
-kotlin = "2.2.21"
-ksp = "2.3.0"
[libraries]
-
# Crypto
+android-library = { module = "com.github.nextcloud:android-library", version.ref = "androidLibraryVersion" }
conscrypt-android = { module = "org.conscrypt:conscrypt-android", version.ref = "conscryptAndroidVersion" }
bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bcpkixJdk18onVersion" }
@@ -88,6 +95,7 @@ bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bc
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appCompatVersion" }
cardview = { module = "androidx.cardview:cardview", version.ref = "cardviewVersion" }
core-ktx = { module = "androidx.test:core-ktx", version.ref = "androidxTestVersion" }
+document-scanning-android-sdk = { module = "com.github.Hazzatur:Document-Scanning-Android-SDK", version.ref = "documentScannerVersion" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtxVersion" }
exifinterface = { module = "androidx.exifinterface:exifinterface", version.ref = "exifinterfaceVersion" }
material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIconsCoreVersion" }
@@ -132,20 +140,26 @@ jackrabbit-webdav = { module = "org.apache.jackrabbit:jackrabbit-webdav", versio
json = { module = "org.json:json", version.ref = "jsonVersion" }
juniversalchardet = { module = "com.github.albfernandez:juniversalchardet", version.ref = "juniversalchardetVersion" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
+leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" }
nnio = { module = "org.lukhnos:nnio", version.ref = "nnioVersion" }
org-jbundle-util-osgi-wrapped-org-apache-http-client = { module = "org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client", version.ref = "orgJbundleUtilOsgiWrappedOrgApacheHttpClientVersion" }
osmdroid-android = { module = "org.osmdroid:osmdroid-android", version.ref = "osmdroidAndroidVersion" }
+objenesis = { module = "org.objenesis:objenesis", version.ref = "objenesis" }
play-services-base = { module = "com.google.android.gms:play-services-base", version.ref = "playServicesBaseVersion" }
review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "reviewKtxVersion" }
+slfj = { module = "org.slf4j:jcl-over-slf4j", version.ref = "slfj" }
# Mockito
mockito-android = { module = "org.mockito:mockito-android", version.ref = "mockitoVersion" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockitoVersion" }
mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlinVersion" }
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockkVersion" }
+mockk = { module = "io.mockk:mockk", version.ref = "mockkVersion" }
# Dagger
dagger = { module = "com.google.dagger:dagger", version.ref = "daggerVersion" }
+dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "daggerVersion" }
+dagger-processor = { module = "com.google.dagger:dagger-android-processor", version.ref = "daggerVersion" }
dagger-android = { module = "com.google.dagger:dagger-android", version.ref = "daggerVersion" }
dagger-android-support = { module = "com.google.dagger:dagger-android-support", version.ref = "daggerVersion" }
@@ -167,7 +181,6 @@ compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
compose-material3 = { module = "androidx.compose.material3:material3" }
-material-icons = { module = "androidx.compose.material:material-icons-core-1.7.8"}
# Media3
media3-datasource = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" }
@@ -177,6 +190,7 @@ media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
# Room
room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomVersion" }
+room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomVersion" }
room-testing = { module = "androidx.room:room-testing", version.ref = "roomVersion" }
# Espresso
@@ -189,6 +203,7 @@ espresso-web = { module = "androidx.test.espresso:espresso-web", version.ref = "
# Test
junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" }
+
core-testing = { module = "androidx.arch.core:core-testing", version.ref = "coreTestingVersion" }
orchestrator = { module = "androidx.test:orchestrator", version.ref = "orchestratorVersion" }
rules = { module = "androidx.test:rules", version.ref = "androidxTestVersion" }
@@ -212,6 +227,7 @@ stateless4j = { module = "com.github.stateless4j:stateless4j", version.ref = "st
syntax-highlight = { module = "io.noties.markwon:syntax-highlight", version.ref = "markwonVersion" }
core = { module = "io.noties.markwon:core", version.ref = "markwonVersion" }
prism4j = { module = "io.noties:prism4j", version.ref = "prismVersion" }
+prism4j-bundler = { module = "io.noties:prism4j-bundler", version.ref = "prismVersion" }
# Nextcloud libraries
ui = { module = "com.github.nextcloud.android-common:ui", version.ref = "androidCommonLibraryVersion" }
@@ -221,9 +237,55 @@ qrcodescanner = { module = "com.github.nextcloud-deps:qrcodescanner", version.re
work-runtime = { module = "androidx.work:work-runtime", version.ref = "workRuntime" }
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntime" }
+[bundles]
+media3 = ["media3-ui", "media3-session", "media3-exoplayer", "media3-datasource"]
+espresso = ["espresso-core", "espresso-contrib", "espresso-web", "espresso-accessibility", "espresso-intents", "espresso-idling-resource"]
+ui = ["appcompat", "webkit", "cardview", "exifinterface", "fragment-ktx"]
+markdown-rendering = [
+ "core",
+ "ext-strikethrough",
+ "ext-tables",
+ "ext-tasklist",
+ "html",
+ "syntax-highlight",
+ "prism4j"
+]
+unit-test = [
+ "junit-junit",
+ "test-core",
+ "json",
+ "mockito-kotlin",
+ "mockk",
+ "mockk-android",
+ "mockito-core",
+ "mockito-android",
+ "core-testing"
+]
+mocking = [
+ "dexopener", # required to allow mocking on API 27 and older
+ "mockito-kotlin",
+ "mockk",
+ "mockk-android",
+ "mockito-core",
+ "mockito-android",
+ "screenshot-core"
+]
+gplay = [
+ "firebase-messaging",
+ "play-services-base",
+ "review-ktx"
+]
+
[plugins]
+android-application = { id = "com.android.application", version.ref = "androidPluginVersion" }
+android-library = { id = "com.android.library", version.ref = "androidPluginVersion" }
+kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
+spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugsGradlePlugin" }
+jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detektGradlePlugin" }
+shot = { id = "shot", version.ref = "shotVersion" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
\ No newline at end of file
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 67119508d0eb..7f7c63031fe9 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -1,4 +1,10 @@
+
true
@@ -10034,6 +10040,11 @@
+
+
+
+
+
@@ -10159,6 +10170,11 @@
+
+
+
+
+
@@ -10524,6 +10540,14 @@
+
+
+
+
+
+
+
+
@@ -11437,6 +11461,9 @@
+
+
+
@@ -13144,6 +13171,14 @@
+
+
+
+
+
+
+
+
@@ -13192,6 +13227,14 @@
+
+
+
+
+
+
+
+
@@ -13240,6 +13283,14 @@
+
+
+
+
+
+
+
+
@@ -13408,6 +13459,14 @@
+
+
+
+
+
+
+
+
@@ -13456,6 +13515,14 @@
+
+
+
+
+
+
+
+
@@ -13504,6 +13571,14 @@
+
+
+
+
+
+
+
+
@@ -13552,6 +13627,14 @@
+
+
+
+
+
+
+
+
@@ -18600,6 +18683,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -21034,6 +21127,14 @@
+
+
+
+
+
+
+
+
@@ -21320,6 +21421,9 @@
+
+
+
@@ -23202,6 +23306,11 @@
+
+
+
+
+
@@ -23953,6 +24062,11 @@
+
+
+
+
+
@@ -31163,6 +31277,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -31320,6 +31444,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/jacoco.gradle b/jacoco.gradle
deleted file mode 100644
index f3a7090b679b..000000000000
--- a/jacoco.gradle
+++ /dev/null
@@ -1,106 +0,0 @@
-apply plugin: "jacoco"
-
-jacoco {
- toolVersion = "$jacoco_version"
-}
-
-// Force Jacoco Version
-
-subprojects {
- configurations.all {
- resolutionStrategy {
- eachDependency { details ->
- if ("org.jacoco" == details.requested.group) {
- details.useVersion "$jacocoVersion"
- }
- }
- }
- }
-}
-
-project.afterEvaluate { project ->
-
- tasks.withType(Test).configureEach {
- jacoco.includeNoLocationClasses = true
- jacoco.excludes = ["jdk.internal.*"]
- }
-
- final flavor = "Gplay"
- final buildType = "Debug"
- final variant = "$flavor${buildType.capitalize()}"
- final taskName = "jacocoTest${variant.capitalize()}UnitTestReport"
-
- task "$taskName"(type: JacocoReport, dependsOn: "test${variant.capitalize()}UnitTest") {
-
- reports {
- csv.required = Boolean.FALSE
- xml.required = Boolean.TRUE
- html.required = Boolean.TRUE
- }
-
- final fileFilter = [
- // data binding
- "**/databinding/*",
- "android/databinding/**/*.class",
- "**/android/databinding/*Binding.class",
- "**/android/databinding/*",
- "**/androidx/databinding/*",
- "**/BR.*",
- // android
- "**/R.class",
- "**/R\$*.class",
- "**/BuildConfig.*",
- "**/Manifest*.*",
- "**/*Test*.*",
- "android/**/*.*",
- // kotlin
- "**/*MapperImpl*.*",
- "**/*\$ViewInjector*.*",
- "**/*\$ViewBinder*.*",
- "**/BuildConfig.*",
- "**/*Component*.*",
- "**/*BR*.*",
- "**/Manifest*.*",
- "**/*\$Lambda\$*.*",
- "**/*Companion*.*",
- "**/*Module*.*",
- "**/*Dagger*.*",
- "**/*Hilt*.*",
- "**/*MembersInjector*.*",
- "**/*_MembersInjector.class",
- "**/*_Factory*.*",
- "**/*_Provide*Factory*.*",
- "**/*Extensions*.*",
- // sealed and data classes
- "**/*\$Result.*",
- "**/*\$Result\$*.*",
- // adapters generated by moshi
- "**/*JsonAdapter.*",
- // Hilt
- "**/*Module.kt",
- "**/di/**",
- "dagger.hilt.internal/*",
- "hilt_aggregated_deps/*",
-
- "**/*\$Result.*", /* filtering `sealed` and `data` classes */
- "**/*\$Result\$*.*",/* filtering `sealed` and `data` classes */
- "**/*Args*.*", /* filtering Navigation Component generated classes */
- "**/*Directions*.*", /* filtering Navigation Component generated classes */
- "**/*inlined*.class", /* filtering inlined classes */
- "**/composables/**"
- /* INSERT ANY OTHER JUNK YOU WANT FILTERED OUT HERE */]
-
- final androidKotlinTree = fileTree(dir: "${project.buildDir}/tmp/kotlin-classes/${variant}", excludes: fileFilter)
- final kotlinTree = fileTree(dir: "${project.buildDir}/classes/kotlin/main", excludes: fileFilter)
- final javacTree = fileTree(dir: "${project.buildDir}/intermediates/javac/${variant}/classes", excludes: fileFilter)
-
- final mainSrc = "${project.projectDir}/src/main/java"
- final productFlavorSrc = "${project.projectDir}/src/${flavor}/java"
- final buildTypeSrc = "${project.projectDir}/src/${buildType}/java"
-
- sourceDirectories.setFrom files([mainSrc, productFlavorSrc, buildTypeSrc])
- classDirectories.setFrom files([androidKotlinTree, kotlinTree, javacTree])
- executionData.setFrom fileTree(dir: project.buildDir, includes: ["jacoco/test${variant.capitalize()}UnitTest.exec",
- "outputs/unit_test_code_coverage/${variant}UnitTest/test${variant.capitalize()}UnitTest.exec",])
- }
-}
diff --git a/jacoco.gradle.kts b/jacoco.gradle.kts
new file mode 100644
index 000000000000..fca6ee1c4eaa
--- /dev/null
+++ b/jacoco.gradle.kts
@@ -0,0 +1,116 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Jimly Asshiddiqy
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+apply()
+
+configure {
+ toolVersion = extensions
+ .getByType()
+ .find("libs")
+ .get()
+ .findVersion("jacoco")
+ .get()
+ .displayName
+}
+
+val flavor = "gplay"
+val buildType = "Debug"
+val variant = "$flavor${buildType.capitalize()}"
+
+val jacocoTask = tasks.register("jacocoTest${variant.capitalize()}UnitTestReport") {
+ reports {
+ csv.required = false
+ xml.required = true
+ html.required = true
+ }
+
+ val fileFilter = listOf(
+ // data binding
+ "**/databinding/*",
+ "android/databinding/**/*.class",
+ "**/android/databinding/*Binding.class",
+ "**/android/databinding/*",
+ "**/androidx/databinding/*",
+ "**/BR.*",
+ // android
+ "**/R.class",
+ "**/R\$*.class",
+ "**/BuildConfig.*",
+ "**/Manifest*.*",
+ "**/*Test*.*",
+ "android/**/*.*",
+ // kotlin
+ "**/*MapperImpl*.*",
+ "**/*\$ViewInjector*.*",
+ "**/*\$ViewBinder*.*",
+ "**/BuildConfig.*",
+ "**/*Component*.*",
+ "**/*BR*.*",
+ "**/Manifest*.*",
+ "**/*\$Lambda\$*.*",
+ "**/*Companion*.*",
+ "**/*Module*.*",
+ "**/*Dagger*.*",
+ "**/*Hilt*.*",
+ "**/*MembersInjector*.*",
+ "**/*_MembersInjector.class",
+ "**/*_Factory*.*",
+ "**/*_Provide*Factory*.*",
+ "**/*Extensions*.*",
+ // sealed and data classes
+ "**/*\$Result.*",
+ "**/*\$Result\$*.*",
+ // adapters generated by moshi
+ "**/*JsonAdapter.*",
+ // Hilt
+ "**/*Module.kt",
+ "**/di/**",
+ "dagger.hilt.internal/*",
+ "hilt_aggregated_deps/*",
+
+ "**/*\$Result.*", /* filtering `sealed` and `data` classes */
+ "**/*\$Result\$*.*",/* filtering `sealed` and `data` classes */
+ "**/*Args*.*", /* filtering Navigation Component generated classes */
+ "**/*Directions*.*", /* filtering Navigation Component generated classes */
+ "**/*inlined*.class", /* filtering inlined classes */
+ "**/composables/**"
+ /* INSERT ANY OTHER JUNK YOU WANT FILTERED OUT HERE */
+ )
+
+ val androidKotlinTree = project.fileTree("${project.layout.buildDirectory}/tmp/kotlin-classes/${variant}") {
+ exclude(fileFilter)
+ }
+ val javacTree = project.fileTree("${project.layout.buildDirectory}/intermediates/javac/${variant}") {
+ exclude(fileFilter)
+ }
+
+ sourceDirectories.setFrom(
+ project.layout.projectDirectory.dir("src/main/java"),
+ project.layout.projectDirectory.dir("src/${flavor}/java"),
+ project.layout.projectDirectory.dir("src/${buildType}/java")
+ )
+ classDirectories.setFrom(
+ project.layout.projectDirectory.files(
+ project.fileTree(project.layout.buildDirectory.dir("intermediates/javac/${variant}")) { exclude(fileFilter) },
+ project.fileTree(project.layout.buildDirectory.dir("tmp/kotlin-classes/$variant")) { exclude(fileFilter) }
+ )
+ )
+ executionData.setFrom(
+ project.fileTree(project.layout.buildDirectory.dir("jacoco")) {
+ include("**/test${variant.capitalize()}UnitTest.exec")
+ }
+ )
+}
+
+tasks.withType()
+ .matching { task -> task.name == "test${variant.capitalize()}UnitTest" }
+ .configureEach {
+ extensions.getByType().isIncludeNoLocationClasses = true
+ extensions.getByType().setExcludes(listOf("jdk.internal.*"))
+
+ finalizedBy(jacocoTask)
+ }
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index f8c7dc28b281..000000000000
--- a/settings.gradle
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2014-2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
- */
-rootProject.name = "Nextcloud"
-
-include ":app"
-include ":appscan"
-
-//includeBuild("../android-common") {
-// dependencySubstitution {
-// substitute module("com.github.nextcloud.android-common:ui") using project(":ui")
-// }
-//}
-
-//includeBuild("../android-library") {
-// dependencySubstitution {
-// substitute module('com.github.nextcloud:android-library') using project(':library') // broken on gradle 8.14.2, so use 8.13 if needed
-// }
-//}
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 000000000000..c0ebf04488ac
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,54 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Jimly Asshiddiqy
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+rootProject.name = "Nextcloud"
+
+pluginManagement {
+ resolutionStrategy.eachPlugin {
+ if (requested.id.id == "shot") useModule("com.karumi:shot:${requested.version}")
+ }
+
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ gradlePluginPortal()
+ mavenCentral()
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ maven("https://jitpack.io")
+ }
+}
+//includeBuild("../android-common") {
+// dependencySubstitution {
+// substitute module("com.github.nextcloud.android-common:ui") using project(":ui")
+// }
+//}
+
+//includeBuild("../android-library") {
+// dependencySubstitution {
+// substitute module('com.github.nextcloud:android-library') using project(':library') // broken on gradle 8.14.2, so use 8.13 if needed
+// }
+//}
+
+include(":app", ":appscan")
\ No newline at end of file