From 35d91b4e063db5fc23ece34ad45c4d266a7f6531 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 28 Apr 2026 14:17:57 -0300 Subject: [PATCH 01/11] Upgrade dependencies and migrate groovy scripts to kotlin --- .editorconfig | 2 +- .idea/compiler.xml | 2 +- app/build.gradle | 207 -------------------- app/build.gradle.kts | 228 +++++++++++++++++++++++ app/proguard-rules.pro | 1 + build.gradle | 21 --- build.gradle.kts | 12 ++ gradle/wrapper/gradle-wrapper.properties | 4 +- settings.gradle | 9 - settings.gradle.kts | 25 +++ 10 files changed, 270 insertions(+), 241 deletions(-) delete mode 100644 app/build.gradle create mode 100644 app/build.gradle.kts delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/.editorconfig b/.editorconfig index 6dd9661f..8406c0aa 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,2 +1,2 @@ -[*.{kt, kts}] +[*.{kt,kts}] max_line_length = 118 diff --git a/.idea/compiler.xml b/.idea/compiler.xml index b589d56e..b86273d9 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 01cc83f4..00000000 --- a/app/build.gradle +++ /dev/null @@ -1,207 +0,0 @@ -plugins { - id 'com.android.application' - id 'kotlin-android' - id 'kotlin-kapt' - id 'dagger.hilt.android.plugin' - id 'org.jetbrains.kotlin.plugin.compose' - id 'org.jlleitschuh.gradle.ktlint' version '10.2.1' -} - -static def versionCodeEpoch() { - return (new Date().getTime() / 1000).toInteger() -} - -def gitCommit = { -> - def stdout = new ByteArrayOutputStream() - exec { - commandLine 'git', 'rev-parse', '--short=7', 'HEAD' - standardOutput = stdout - } - return stdout.toString().trim() -} - -android { - compileSdk 36 - - defaultConfig { - namespace "org.onionshare.android" - - applicationId "org.onionshare.android" - minSdk 21 - targetSdk 36 - versionCode 26 - versionName "0.2.3-beta" - - vectorDrawables { - useSupportLibrary true - } - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - testInstrumentationRunnerArguments disableAnalytics: 'true' - } - - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - debug { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - flavorDimensions "releaseType" - productFlavors { - stable { - dimension "releaseType" - } - fdroid { - dimension "releaseType" - applicationIdSuffix ".fdroid" - // version codes get multiplied by 10 and an ABI suffix gets added to the code - // if 'splitApk' property is set - } - nightly { - dimension "releaseType" - applicationIdSuffix ".nightly" - versionCode versionCodeEpoch() - versionNameSuffix " ($gitCommit)" - } - } - splits { - abi { - // can not be defined per flavor, so we use a property to turn this on for F-Droid - enable project.hasProperty('splitApk') - reset() // Resets the list of ABIs to remove all included by default - include "armeabi-v7a", "arm64-v8a", "x86", "x86_64" - universalApk false - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - coreLibraryDesugaringEnabled true - } - kotlinOptions { - jvmTarget = '17' - freeCompilerArgs += '-opt-in=kotlin.RequiresOptIn' - } - buildFeatures { - compose true - buildConfig true - } - packagingOptions { - jniLibs { - useLegacyPackaging = true - } - exclude 'META-INF/*' - // Due to https://github.com/Kotlin/kotlinx.coroutines/issues/2023 - exclude 'META-INF/licenses/*' - exclude '**/attach_hotspot_windows.dll' - } - lintOptions { - disable 'MissingTranslation', 'ExtraTranslation', 'MissingQuantity' - } -} - -configurations { - tor -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - - implementation 'androidx.core:core-ktx:1.16.0' - implementation 'androidx.appcompat:appcompat:1.7.1' - implementation 'androidx.activity:activity-compose:1.10.1' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.9.1' - implementation 'com.google.android.material:material:1.12.0' - - implementation platform("androidx.compose:compose-bom:$compose_version") - implementation 'androidx.compose.material3:material3' - implementation 'androidx.compose.material:material-icons-extended' - implementation 'androidx.compose.ui:ui-tooling-preview' - - implementation "androidx.navigation:navigation-compose:2.9.0" - implementation "androidx.hilt:hilt-navigation-compose:1.2.0" - implementation "androidx.profileinstaller:profileinstaller:1.4.1" - implementation "androidx.security:security-crypto:1.1.0-beta01" - - implementation "com.google.dagger:hilt-android:$hilt_version" - kapt "com.google.dagger:hilt-compiler:$hilt_version" - - // Tor - implementation "org.briarproject:onionwrapper-android:0.1.4" - tor 'org.briarproject:tor-android:0.4.8.19' - tor 'org.briarproject:lyrebird-android:0.6.2' - implementation 'org.briarproject:moat-api:0.3' - implementation 'com.squareup.okhttp3:okhttp:4.12.0' - - def ktor_version = '2.3.13' - implementation "io.ktor:ktor-server-core:$ktor_version" - implementation "io.ktor:ktor-server-netty:$ktor_version" - implementation "io.ktor:ktor-server-pebble:$ktor_version" - implementation "io.ktor:ktor-server-status-pages:$ktor_version" - implementation "io.ktor:ktor-server-call-logging:$ktor_version" - implementation 'org.slf4j:slf4j-api:2.0.16' - implementation 'org.slf4j:jul-to-slf4j:2.0.5' - implementation 'com.github.tony19:logback-android:3.0.0' - - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' - - debugImplementation 'androidx.compose.ui:ui-tooling' - debugImplementation 'androidx.compose.ui:ui-test-manifest' - - testImplementation 'junit:junit:4.13.2' - testImplementation 'io.mockk:mockk:1.14.2' - testImplementation 'org.slf4j:slf4j-jdk14:2.0.7' - testImplementation 'app.cash.turbine:turbine:1.0.0' - testImplementation 'org.jetbrains.kotlin:kotlin-test' - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2' - - androidTestImplementation 'androidx.test.ext:junit:1.2.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' - androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.8.2" - androidTestImplementation 'tools.fastlane:screengrab:2.1.1' -} - -// Map for the version code that gives each ABI a value. -ext.abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, x86: 3, x86_64: 4] -// For each APK output variant, override versionCode with a combination of ext.abiCodes + variant.versionCode. -android.applicationVariants.configureEach { variant -> - // Assigns a different version code for each output APK - variant.outputs.each { output -> - // Stores the value of ext.abiCodes that is associated with the ABI for this variant. - def baseAbiVersionCode = - // Determines the ABI for this variant and returns the mapped value. - project.ext.abiCodes.get(output.getFilter(com.android.build.OutputFile.ABI)) - if (baseAbiVersionCode != null) { - output.versionCodeOverride = 10 * variant.versionCode + baseAbiVersionCode - } - // leaves version code alone of there's no baseAbiVersionCode - } -} - -def torLibsDir = 'src/main/jniLibs' - -tasks.register('cleanTorBinaries') { - outputs.dir torLibsDir - doLast { - delete fileTree(torLibsDir) - } -} - -clean.dependsOn cleanTorBinaries - -tasks.register('unpackTorBinaries') { - outputs.dir torLibsDir - doLast { - copy { - from configurations.tor.collect { zipTree(it) } - into torLibsDir - } - } - dependsOn cleanTorBinaries -} -preBuild.dependsOn unpackTorBinaries - -apply from: "${rootProject.rootDir}/gradle/ktlint.gradle" diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..84e1bbd7 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,228 @@ +import com.android.build.api.variant.FilterConfiguration +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import java.util.Date + +plugins { + id("com.android.application") + id("com.google.devtools.ksp") + id("com.google.dagger.hilt.android") + id("org.jetbrains.kotlin.plugin.compose") + id("org.jlleitschuh.gradle.ktlint") +} + +val hiltVersion = "2.59.2" +val composeVersion = "2026.04.01" +val tor by configurations.creating + +fun versionCodeEpoch(): Int = (Date().time / 1000).toInt() + +fun gitCommit(): String { + val process = ProcessBuilder("git", "rev-parse", "--short=8", "HEAD") + .directory(rootDir) + .redirectErrorStream(true) + .start() + process.waitFor() + return process.inputStream.use { it.readBytes().decodeToString().trim() } +} + +android { + namespace = "org.onionshare.android" + compileSdk = 36 + + defaultConfig { + applicationId = "org.onionshare.android" + minSdk = 23 + targetSdk = 36 + versionCode = 26 + versionName = "0.3.0-beta" + + vectorDrawables { + useSupportLibrary = true + } + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["disableAnalytics"] = "true" + } + + buildTypes { + getByName("release") { + isMinifyEnabled = true + isShrinkResources = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + getByName("debug") { + isMinifyEnabled = true + isShrinkResources = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + flavorDimensions += "releaseType" + + productFlavors { + create("stable") { + dimension = "releaseType" + } + create("fdroid") { + dimension = "releaseType" + applicationIdSuffix = ".fdroid" + // version codes get multiplied by 10 and an ABI suffix gets added to the code + // if 'splitApk' property is set + } + create("nightly") { + dimension = "releaseType" + applicationIdSuffix = ".nightly" + versionCode = versionCodeEpoch() + versionNameSuffix = " (${gitCommit()})" + } + } + + splits { + abi { + // can not be defined per flavor, so we use a property to turn this on for F-Droid + isEnable = project.hasProperty("splitApk") + reset() + include("armeabi-v7a", "arm64-v8a", "x86", "x86_64") + isUniversalApk = false + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + isCoreLibraryDesugaringEnabled = true + } + + buildFeatures { + compose = true + buildConfig = true + } + + packaging { + jniLibs { + useLegacyPackaging = true + } + resources { + excludes += setOf( + "META-INF/*", + // Due to https://github.com/Kotlin/kotlinx.coroutines/issues/2023 + "META-INF/licenses/*", + "**/attach_hotspot_windows.dll", + ) + } + } + + lint { + disable += setOf("MissingTranslation", "ExtraTranslation", "MissingQuantity") + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") + } +} + +dependencies { + implementation(kotlin("reflect")) + + implementation("androidx.core:core-ktx:1.18.0") + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("androidx.activity:activity-compose:1.13.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0") + implementation("androidx.documentfile:documentfile:1.1.0") + implementation("com.google.android.material:material:1.13.0") + + implementation(platform("androidx.compose:compose-bom:$composeVersion")) + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.material:material-icons-extended") + implementation("androidx.compose.ui:ui-tooling-preview") + + implementation("androidx.navigation:navigation-compose:2.9.8") + implementation("androidx.hilt:hilt-navigation-compose:1.3.0") + implementation("androidx.profileinstaller:profileinstaller:1.4.1") + implementation("androidx.security:security-crypto:1.1.0") + + implementation("com.google.dagger:hilt-android:$hiltVersion") + ksp("com.google.dagger:hilt-compiler:$hiltVersion") + + // Tor + implementation("org.briarproject:onionwrapper-android:0.1.4") + add(tor.name, "org.briarproject:tor-android:0.4.8.22") + add(tor.name, "org.briarproject:lyrebird-android:0.6.2") + implementation("org.briarproject:moat-api:0.4") + implementation("com.squareup.okhttp3:okhttp:4.12.0") + + val ktorVersion = "2.3.13" + implementation("io.ktor:ktor-server-core:$ktorVersion") + implementation("io.ktor:ktor-server-netty:$ktorVersion") + implementation("io.ktor:ktor-server-pebble:$ktorVersion") + implementation("io.ktor:ktor-server-status-pages:$ktorVersion") + implementation("io.ktor:ktor-server-call-logging:$ktorVersion") + implementation("org.slf4j:slf4j-api:2.0.17") + implementation("org.slf4j:jul-to-slf4j:2.0.17") + implementation("com.github.tony19:logback-android:3.0.0") + + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") + + debugImplementation("androidx.compose.ui:ui-tooling") + debugImplementation("androidx.compose.ui:ui-test-manifest") + + testImplementation("junit:junit:4.13.2") + testImplementation("io.mockk:mockk:1.14.9") + testImplementation("org.slf4j:slf4j-jdk14:2.0.17") + testImplementation("app.cash.turbine:turbine:1.2.1") + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2") + + androidTestImplementation("androidx.test.ext:junit:1.3.0") + androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") + androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.11.0") + androidTestImplementation("tools.fastlane:screengrab:2.1.1") +} + +val abiCodes = mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86" to 3, "x86_64" to 4) + +androidComponents { + onVariants(selector().all()) { variant -> + variant.outputs.forEach { output -> + val abi = output.filters + .find { it.filterType == FilterConfiguration.FilterType.ABI } + ?.identifier + val baseAbiVersionCode = abiCodes[abi] + if (baseAbiVersionCode != null) { + output.versionCode.set(output.versionCode.map { 10 * it + baseAbiVersionCode }) + } + } + } +} + +val torLibsDir = "src/main/jniLibs" + +val cleanTorBinaries = tasks.register("cleanTorBinaries") { + outputs.dir(torLibsDir) + doLast { + project.delete(project.fileTree(torLibsDir)) + } +} + +tasks.named("clean") { + dependsOn(cleanTorBinaries) +} + +val unpackTorBinaries = tasks.register("unpackTorBinaries") { + outputs.dir(torLibsDir) + dependsOn(cleanTorBinaries) + doLast { + project.copy { + from(tor.resolve().map { project.zipTree(it) }) + into(torLibsDir) + } + } +} + +tasks.named("preBuild") { + dependsOn(unpackTorBinaries) +} + +apply(from = "${rootProject.rootDir}/gradle/ktlint.gradle") diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 5265e0da..3d8641ab 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -20,6 +20,7 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile +-dontoptimize -dontobfuscate -keepattributes SourceFile, LineNumberTable, *Annotation*, Signature, InnerClasses, EnclosingMethod diff --git a/build.gradle b/build.gradle deleted file mode 100644 index ff129ef7..00000000 --- a/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -buildscript { - ext { - kotlin_version = '2.1.21' - hilt_version = '2.56.2' - compose_version = '2025.06.00' - } - repositories { - google() - mavenCentral() - } - dependencies { - classpath "com.android.tools.build:gradle:8.10.0" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" - classpath "org.jetbrains.kotlin:compose-compiler-gradle-plugin:$kotlin_version" - } -} - -tasks.register('clean', Delete) { - delete rootProject.buildDir -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..46bc509b --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("com.android.application") apply false + id("org.jetbrains.kotlin.plugin.compose") apply false + id("com.google.dagger.hilt.android") apply false + id("com.google.devtools.ksp") apply false + id("org.jlleitschuh.gradle.ktlint") apply false +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4db4a865..ab6968a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip -distributionSha256Sum=7ebdac923867a3cec0098302416d1e3c6c0c729fc4e2e05c10637a8af33a76c5 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-all.zip +distributionSha256Sum=708d2c6ecc97ca9a11838ef64a6c2301151b8dd10387e22dc1a12c30557cab5b diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index ba914d6e..00000000 --- a/settings.gradle +++ /dev/null @@ -1,9 +0,0 @@ -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - } -} -rootProject.name = "OnionShare" -include ':app' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..5e84548b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + plugins { + id("com.android.application") version "9.2.0" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.21" + id("com.google.dagger.hilt.android") version "2.59.2" + id("com.google.devtools.ksp") version "2.3.7" + id("org.jlleitschuh.gradle.ktlint") version "10.2.1" + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "OnionShare" +include(":app") From bc94fbb87fa17d486704bb0f8fc45cb946e7b275 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 28 Apr 2026 14:53:32 -0300 Subject: [PATCH 02/11] Use gradle version catalog --- app/build.gradle.kts | 111 +++++++++++++++++++------------------- build.gradle.kts | 17 ++++-- gradle/libs.versions.toml | 99 ++++++++++++++++++++++++++++++++++ settings.gradle.kts | 15 ------ 4 files changed, 165 insertions(+), 77 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 84e1bbd7..56b3e014 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,15 +3,13 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.util.Date plugins { - id("com.android.application") - id("com.google.devtools.ksp") - id("com.google.dagger.hilt.android") - id("org.jetbrains.kotlin.plugin.compose") - id("org.jlleitschuh.gradle.ktlint") + alias(libs.plugins.android.application) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.ktlint) } -val hiltVersion = "2.59.2" -val composeVersion = "2026.04.01" val tor by configurations.creating fun versionCodeEpoch(): Int = (Date().time / 1000).toInt() @@ -27,7 +25,7 @@ fun gitCommit(): String { android { namespace = "org.onionshare.android" - compileSdk = 36 + compileSdk = 37 defaultConfig { applicationId = "org.onionshare.android" @@ -124,61 +122,60 @@ kotlin { } dependencies { - implementation(kotlin("reflect")) + implementation(libs.kotlin.reflect) - implementation("androidx.core:core-ktx:1.18.0") - implementation("androidx.appcompat:appcompat:1.7.1") - implementation("androidx.activity:activity-compose:1.13.0") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0") - implementation("androidx.documentfile:documentfile:1.1.0") - implementation("com.google.android.material:material:1.13.0") + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.documentfile) + implementation(libs.material) - implementation(platform("androidx.compose:compose-bom:$composeVersion")) - implementation("androidx.compose.material3:material3") - implementation("androidx.compose.material:material-icons-extended") - implementation("androidx.compose.ui:ui-tooling-preview") + implementation(platform(libs.compose.bom)) + implementation(libs.compose.material3) + implementation(libs.compose.material.icons.extended) + implementation(libs.compose.ui.tooling.preview) - implementation("androidx.navigation:navigation-compose:2.9.8") - implementation("androidx.hilt:hilt-navigation-compose:1.3.0") - implementation("androidx.profileinstaller:profileinstaller:1.4.1") - implementation("androidx.security:security-crypto:1.1.0") + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.androidx.profileinstaller) + implementation(libs.androidx.security.crypto) - implementation("com.google.dagger:hilt-android:$hiltVersion") - ksp("com.google.dagger:hilt-compiler:$hiltVersion") + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) // Tor - implementation("org.briarproject:onionwrapper-android:0.1.4") - add(tor.name, "org.briarproject:tor-android:0.4.8.22") - add(tor.name, "org.briarproject:lyrebird-android:0.6.2") - implementation("org.briarproject:moat-api:0.4") - implementation("com.squareup.okhttp3:okhttp:4.12.0") - - val ktorVersion = "2.3.13" - implementation("io.ktor:ktor-server-core:$ktorVersion") - implementation("io.ktor:ktor-server-netty:$ktorVersion") - implementation("io.ktor:ktor-server-pebble:$ktorVersion") - implementation("io.ktor:ktor-server-status-pages:$ktorVersion") - implementation("io.ktor:ktor-server-call-logging:$ktorVersion") - implementation("org.slf4j:slf4j-api:2.0.17") - implementation("org.slf4j:jul-to-slf4j:2.0.17") - implementation("com.github.tony19:logback-android:3.0.0") - - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") - - debugImplementation("androidx.compose.ui:ui-tooling") - debugImplementation("androidx.compose.ui:ui-test-manifest") - - testImplementation("junit:junit:4.13.2") - testImplementation("io.mockk:mockk:1.14.9") - testImplementation("org.slf4j:slf4j-jdk14:2.0.17") - testImplementation("app.cash.turbine:turbine:1.2.1") - testImplementation("org.jetbrains.kotlin:kotlin-test") - testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2") - - androidTestImplementation("androidx.test.ext:junit:1.3.0") - androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") - androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.11.0") - androidTestImplementation("tools.fastlane:screengrab:2.1.1") + implementation(libs.briar.onionwrapper.android) + add(tor.name, libs.briar.tor.android) + add(tor.name, libs.briar.lyrebird.android) + implementation(libs.briar.moat.api) + implementation(libs.okhttp) + + implementation(libs.ktor.server.core) + implementation(libs.ktor.server.netty) + implementation(libs.ktor.server.pebble) + implementation(libs.ktor.server.status.pages) + implementation(libs.ktor.server.call.logging) + implementation(libs.slf4j.api) + implementation(libs.slf4j.jul.to.slf4j) + implementation(libs.logback.android) + + coreLibraryDesugaring(libs.desugar.jdk.libs) + + debugImplementation(libs.compose.ui.tooling) + debugImplementation(libs.compose.ui.test.manifest) + + testImplementation(libs.junit4) + testImplementation(libs.mockk) + testImplementation(libs.slf4j.jdk14) + testImplementation(libs.turbine) + testImplementation(libs.kotlin.test) + testImplementation(libs.kotlinx.coroutines.test) + + androidTestImplementation(libs.androidx.test.ext.junit) + androidTestImplementation(libs.androidx.test.espresso.core) + androidTestImplementation(libs.compose.ui.test.junit4) + androidTestImplementation(libs.screengrab) } val abiCodes = mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86" to 3, "x86_64" to 4) diff --git a/build.gradle.kts b/build.gradle.kts index 46bc509b..30603535 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,16 @@ plugins { - id("com.android.application") apply false - id("org.jetbrains.kotlin.plugin.compose") apply false - id("com.google.dagger.hilt.android") apply false - id("com.google.devtools.ksp") apply false - id("org.jlleitschuh.gradle.ktlint") apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.hilt.android) apply false + alias(libs.plugins.ksp) apply false + alias(libs.plugins.ktlint) apply false +} + +allprojects { + repositories { + mavenCentral() + google() + } } tasks.register("clean") { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..062dff0f --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,99 @@ +[versions] +agp = "9.2.0" +kotlin = "2.3.21" +hilt = "2.59.2" +ksp = "2.3.7" +ktlint-plugin = "10.2.1" + +compose-bom = "2026.04.01" +compose-ui-test-junit4 = "1.11.0" + +androidx-core-ktx = "1.18.0" +androidx-appcompat = "1.7.1" +androidx-activity-compose = "1.13.0" +androidx-lifecycle-runtime-ktx = "2.10.0" +androidx-documentfile = "1.1.0" +androidx-navigation-compose = "2.9.8" +androidx-hilt-navigation-compose = "1.3.0" +androidx-profileinstaller = "1.4.1" +androidx-security-crypto = "1.1.0" +androidx-test-ext-junit = "1.3.0" +androidx-test-espresso-core = "3.7.0" + +material = "1.13.0" +desugar-jdk-libs = "2.1.5" + +onionwrapper = "0.1.4" +tor-android = "0.4.8.22" +lyrebird-android = "0.6.2" +moat-api = "0.4" + +okhttp = "4.12.0" +ktor = "2.3.13" +slf4j = "2.0.17" +logback-android = "3.0.0" + +junit4 = "4.13.2" +mockk = "1.14.9" +turbine = "1.2.1" +kotlinx-coroutines-test = "1.10.2" +screengrab = "2.1.1" + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-plugin" } + +[libraries] +kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } +kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" } +kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx-coroutines-test" } + +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidx-activity-compose" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidx-lifecycle-runtime-ktx" } +androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "androidx-documentfile" } +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation-compose" } +androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidx-hilt-navigation-compose" } +androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "androidx-profileinstaller" } +androidx-security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "androidx-security-crypto" } +androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } +androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-test-espresso-core" } + +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +desugar-jdk-libs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugar-jdk-libs" } + +compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } +compose-material3 = { group = "androidx.compose.material3", name = "material3" } +compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } +compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "compose-ui-test-junit4" } + +hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } +hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } + +briar-onionwrapper-android = { group = "org.briarproject", name = "onionwrapper-android", version.ref = "onionwrapper" } +briar-tor-android = { group = "org.briarproject", name = "tor-android", version.ref = "tor-android" } +briar-lyrebird-android = { group = "org.briarproject", name = "lyrebird-android", version.ref = "lyrebird-android" } +briar-moat-api = { group = "org.briarproject", name = "moat-api", version.ref = "moat-api" } + +okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } +ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" } +ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" } +ktor-server-pebble = { group = "io.ktor", name = "ktor-server-pebble", version.ref = "ktor" } +ktor-server-status-pages = { group = "io.ktor", name = "ktor-server-status-pages", version.ref = "ktor" } +ktor-server-call-logging = { group = "io.ktor", name = "ktor-server-call-logging", version.ref = "ktor" } +slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" } +slf4j-jul-to-slf4j = { group = "org.slf4j", name = "jul-to-slf4j", version.ref = "slf4j" } +slf4j-jdk14 = { group = "org.slf4j", name = "slf4j-jdk14", version.ref = "slf4j" } +logback-android = { group = "com.github.tony19", name = "logback-android", version.ref = "logback-android" } + +junit4 = { group = "junit", name = "junit", version.ref = "junit4" } +mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } +turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } +screengrab = { group = "tools.fastlane", name = "screengrab", version.ref = "screengrab" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 5e84548b..8443d756 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,21 +4,6 @@ pluginManagement { mavenCentral() gradlePluginPortal() } - plugins { - id("com.android.application") version "9.2.0" - id("org.jetbrains.kotlin.plugin.compose") version "2.3.21" - id("com.google.dagger.hilt.android") version "2.59.2" - id("com.google.devtools.ksp") version "2.3.7" - id("org.jlleitschuh.gradle.ktlint") version "10.2.1" - } -} - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - } } rootProject.name = "OnionShare" From 79b433bada600e7906436f5fda66638122fd1e2a Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 28 Apr 2026 15:30:29 -0300 Subject: [PATCH 03/11] Upgrade Ktor server to 3.x --- app/build.gradle.kts | 2 +- .../android/server/WebserverManager.kt | 33 +++++++++++-------- gradle/libs.versions.toml | 2 +- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 56b3e014..f12a877d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,7 +29,7 @@ android { defaultConfig { applicationId = "org.onionshare.android" - minSdk = 23 + minSdk = 26 targetSdk = 36 versionCode = 26 versionName = "0.3.0-beta" diff --git a/app/src/main/java/org/onionshare/android/server/WebserverManager.kt b/app/src/main/java/org/onionshare/android/server/WebserverManager.kt index fa8b50c1..45cf8282 100644 --- a/app/src/main/java/org/onionshare/android/server/WebserverManager.kt +++ b/app/src/main/java/org/onionshare/android/server/WebserverManager.kt @@ -12,15 +12,15 @@ import io.ktor.server.application.Application import io.ktor.server.application.ApplicationStarted import io.ktor.server.application.ApplicationStopped import io.ktor.server.application.ApplicationStopping -import io.ktor.server.application.call import io.ktor.server.application.install -import io.ktor.server.engine.ApplicationEngine +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.connector import io.ktor.server.engine.embeddedServer import io.ktor.server.http.content.staticResources import io.ktor.server.netty.Netty import io.ktor.server.pebble.Pebble import io.ktor.server.pebble.PebbleContent -import io.ktor.server.plugins.callloging.CallLogging +import io.ktor.server.plugins.calllogging.CallLogging import io.ktor.server.plugins.statuspages.StatusPages import io.ktor.server.response.header import io.ktor.server.response.respond @@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import org.onionshare.android.BuildConfig.DEBUG import org.slf4j.LoggerFactory +import java.net.ServerSocket import java.security.SecureRandom import java.util.concurrent.RejectedExecutionException import javax.inject.Inject @@ -51,28 +52,32 @@ sealed class WebServerState { class WebserverManager @Inject constructor() { private val secureRandom = SecureRandom() - private var server: ApplicationEngine? = null + private var server: EmbeddedServer<*, *>? = null private val _state = MutableStateFlow(WebServerState.Stopped(false)) val state = _state.asStateFlow() + @Volatile var contentPath = "" private set - suspend fun start(sendPage: SendPage): Int { + fun start(sendPage: SendPage): Int { _state.value = WebServerState.Starting contentPath = getRandomPath() val staticPath = getStaticPath() val pathMap = mapOf("static_url_path" to staticPath, "content_path" to contentPath) + val port = findFreePort() TrafficStats.setThreadStatsTag(0x42) val server = embeddedServer( factory = Netty, - host = "127.0.0.1", - port = 0, // will be chosen randomly - watchPaths = emptyList(), configure = { + connector { + host = "127.0.0.1" + this.port = port + } // disable response timeout responseWriteTimeoutSeconds = 0 - }) { + } + ) { if (DEBUG) install(CallLogging) install(Pebble) { loader(ClasspathLoader().apply { prefix = "assets/templates" }) @@ -85,7 +90,7 @@ class WebserverManager @Inject constructor() { } }.also { it.start() } this.server = server - return server.resolvedConnectors().first().port + return port } fun stop(isFinishingDownloading: Boolean = false) { @@ -103,6 +108,8 @@ class WebserverManager @Inject constructor() { } } + private fun findFreePort(): Int = ServerSocket(0).use { it.localPort } + private fun getRandomPath(): String { val randomBytes = ByteArray(16).apply { secureRandom.nextBytes(this) } return Base64.encodeToString(randomBytes, NO_PADDING or URL_SAFE).trimEnd() @@ -113,14 +120,14 @@ class WebserverManager @Inject constructor() { } private fun Application.addListener() { - environment.monitor.subscribe(ApplicationStarted) { + monitor.subscribe(ApplicationStarted) { _state.value = WebServerState.Started } - environment.monitor.subscribe(ApplicationStopping) { + monitor.subscribe(ApplicationStopping) { // only update if we are not already stopping if (state.value !is WebServerState.Stopping) _state.value = WebServerState.Stopping() } - environment.monitor.subscribe(ApplicationStopped) { + monitor.subscribe(ApplicationStopped) { LOG.info("Stopped") val downloadComplete = (state.value as? WebServerState.Stopping)?.downloadComplete ?: false _state.value = WebServerState.Stopped(downloadComplete) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 062dff0f..07a09087 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ lyrebird-android = "0.6.2" moat-api = "0.4" okhttp = "4.12.0" -ktor = "2.3.13" +ktor = "3.4.3" slf4j = "2.0.17" logback-android = "3.0.0" From b9bb6f280f53d574a7a23f3861b6499d38ed2b05 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 28 Apr 2026 16:09:56 -0300 Subject: [PATCH 04/11] Don't choke on files with same name, but different URIs --- .../onionshare/android/files/FileManager.kt | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/onionshare/android/files/FileManager.kt b/app/src/main/java/org/onionshare/android/files/FileManager.kt index 2f23ede9..85eeecd8 100644 --- a/app/src/main/java/org/onionshare/android/files/FileManager.kt +++ b/app/src/main/java/org/onionshare/android/files/FileManager.kt @@ -69,21 +69,37 @@ class FileManager @Inject constructor( private fun addFiles(uris: List) { val existingFiles = filesState.value.files + val existingNames = existingFiles.map { it.basename }.toMutableSet() val files = uris.mapNotNull { uri -> // continue if we already have that file if (existingFiles.any { it.uri == uri }) return@mapNotNull null val documentFile = DocumentFile.fromSingleUri(ctx, uri) ?: error("Only API < 19") - val name = documentFile.name ?: uri.getFallBackName() ?: error("Uri has no path $uri") + val preliminaryName = documentFile.name ?: uri.getFallBackName() ?: error("Uri has no path $uri") val size = documentFile.length() val sizeHuman = if (size == 0L) ctx.getString(R.string.unknown) else { formatShortFileSize(ctx, size) } - SendFile(name, sizeHuman, size, uri, documentFile.type) + val name = getName(existingNames, preliminaryName) + existingNames.add(name) + SendFile( + basename = name, + size_human = sizeHuman, + size = size, + uri = uri, + mimeType = documentFile.type, + ) } _filesState.value = FilesState(existingFiles + files) } + private fun getName(existingNames: Set, name: String): String { + return if (name in existingNames) getName( + existingNames = existingNames, + name = "${name.substringBeforeLast(".")}-1.${name.substringAfterLast(".")}" + ) else name + } + fun removeFile(file: SendFile) { // release persistable Uri permission again file.releaseUriPermission() @@ -140,7 +156,7 @@ class FileManager @Inject constructor( LOG.warn("Error while opening file: ", e) throw FileErrorException(file) } catch (e: SecurityException) { - LOG.warn("Error while opening file: SecurityException") + LOG.warn("Error while opening file: ", e) throw FileErrorException(file) } } From 111cda89df1ce5894e7980e49d4546cacb92d085 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 28 Apr 2026 16:38:19 -0300 Subject: [PATCH 05/11] increase memory available to gradle --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 0086716c..328240b0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,13 +6,13 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xms4g -Xmx8g -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK +# Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX From e454d77196846043f1c7c4148837049f3e90117f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 28 Apr 2026 16:53:08 -0300 Subject: [PATCH 06/11] Add proguard rules which are apparently required --- app/proguard-rules.pro | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 3d8641ab..10509c40 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -47,3 +47,14 @@ -dontwarn org.jetbrains.annotations.* -dontwarn reactor.blockhound.integration.BlockHoundIntegration -dontwarn javax.mail.** +-dontwarn javax.naming.ldap.LdapName +-dontwarn javax.naming.ldap.Rdn +-dontwarn jdk.jfr.Category +-dontwarn jdk.jfr.DataAmount +-dontwarn jdk.jfr.Description +-dontwarn jdk.jfr.Enabled +-dontwarn jdk.jfr.Event +-dontwarn jdk.jfr.FlightRecorder +-dontwarn jdk.jfr.Label +-dontwarn jdk.jfr.MemoryAddress +-dontwarn jdk.jfr.Name From a7c84697ecd06bf0f656d9860eff4fca0c202a97 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 28 Apr 2026 17:12:28 -0300 Subject: [PATCH 07/11] Make bottom sheet scrollable so users with extra large font and display size can still access stop button --- .../android/ui/share/ShareBottomSheet.kt | 21 ++++++++++++++++++- .../onionshare/android/ui/share/ShareUi.kt | 14 ++++++------- .../android/ui/share/ShareUiSetup.kt | 6 +++--- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/onionshare/android/ui/share/ShareBottomSheet.kt b/app/src/main/java/org/onionshare/android/ui/share/ShareBottomSheet.kt index 73b86db1..cbe56b7e 100644 --- a/app/src/main/java/org/onionshare/android/ui/share/ShareBottomSheet.kt +++ b/app/src/main/java/org/onionshare/android/ui/share/ShareBottomSheet.kt @@ -14,8 +14,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Circle @@ -109,7 +111,9 @@ fun BottomSheet(state: ShareUiState, onSheetButtonClicked: () -> Unit) { val bottomPadding = with(LocalDensity.current) { WindowInsets.safeDrawing.getBottom(this).toDp() } - Column(modifier = Modifier.padding(bottom = bottomPadding)) { + Column(modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(bottom = bottomPadding)) { Row( verticalAlignment = CenterVertically, modifier = Modifier.padding(16.dp), @@ -266,6 +270,21 @@ fun ShareBottomSheetSharingPreview() { } } +@Preview(showBackground = true, fontScale = 3.0f) +@Composable +fun ShareBottomSheetSharingLargeFontPreview() { + OnionshareTheme { + Surface(color = MaterialTheme.colorScheme.background) { + BottomSheet( + state = ShareUiState.Sharing( + "http://openpravyvc6spbd4flzn4g2iqu4sxzsizbtb5aqec25t76dnoo5w7yd.onion/eW91IGFyZSBhIG5lcmQ7KQ", + ), + onSheetButtonClicked = {}, + ) + } + } +} + @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES) @Composable fun ShareBottomSheetSharingPreviewNight() { diff --git a/app/src/main/java/org/onionshare/android/ui/share/ShareUi.kt b/app/src/main/java/org/onionshare/android/ui/share/ShareUi.kt index 179a468b..f6047712 100644 --- a/app/src/main/java/org/onionshare/android/ui/share/ShareUi.kt +++ b/app/src/main/java/org/onionshare/android/ui/share/ShareUi.kt @@ -1,7 +1,5 @@ package org.onionshare.android.ui.share -import android.content.res.Configuration.UI_MODE_NIGHT_YES -import android.net.Uri import androidx.annotation.StringRes import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -51,9 +49,11 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max +import androidx.core.net.toUri import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import kotlinx.coroutines.delay @@ -271,11 +271,11 @@ fun MainContent( } } -@Preview(showBackground = true) @Composable -fun DefaultPreview() { +@PreviewLightDark +private fun DefaultPreview() { val files = listOf( - SendFile("foo", "23 KiB", 1337L, Uri.parse(""), null) + SendFile("foo", "23 KiB", 1337L, "".toUri(), null) ) OnionshareTheme { ShareUi( @@ -289,9 +289,9 @@ fun DefaultPreview() { } } -@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES) +@Preview(showBackground = true) @Composable -fun NightModePreview() { +private fun PreviewLargeFont() { OnionshareTheme { ShareUi( navController = rememberNavController(), diff --git a/app/src/main/java/org/onionshare/android/ui/share/ShareUiSetup.kt b/app/src/main/java/org/onionshare/android/ui/share/ShareUiSetup.kt index 3ae1729f..a05f714e 100644 --- a/app/src/main/java/org/onionshare/android/ui/share/ShareUiSetup.kt +++ b/app/src/main/java/org/onionshare/android/ui/share/ShareUiSetup.kt @@ -69,10 +69,10 @@ private fun onFabClicked( ) { try { contentLauncher.launch(arrayOf("*/*")) - } catch (e: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { try { contentFallbackLauncher.launch("*/*") - } catch (e: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { Toast.makeText(context, R.string.add_files_not_supported, Toast.LENGTH_SHORT).show() } } @@ -86,7 +86,7 @@ private fun onSheetButtonClicked( if (viewModel.shareState.value is ShareUiState.AddingFiles && viewModel.needsDozeWhitelisting) { try { batteryLauncher.launch(getDozeWhitelistingIntent(context)) - } catch (e: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { // this is really unusual (happened once on a Samsung Galaxy A5 with SDK 23), just pray and proceed viewModel.onSheetButtonClicked() } From 0f143df17d99bdc0dcc50eb39b7997db6fcc2586 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 28 Apr 2026 17:19:26 -0300 Subject: [PATCH 08/11] Add dependabot.yml to update github actions --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..8ac6b8c4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" From 29313a3d28b425985d24f2b788d92eaf8eb28976 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 29 Apr 2026 14:43:26 -0300 Subject: [PATCH 09/11] Add missing proguard files, thanks Michael! --- app/proguard-rules.pro | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 10509c40..563df593 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -33,7 +33,8 @@ # Keep Netty classes that are loaded via reflection -keep class io.netty.channel.** { *; } --keep class io.netty.buffer.WrappedByteBuf { *; } +-keep class io.netty.buffer.** { *; } +-keep class io.netty.util.concurrent.** { *; } -keep class io.netty.util.ReferenceCountUtil { *; } -keep class io.netty.handler.codec.MessageToMessageEncoder { *; } From 2b0b61b438d1675a584c4fea2ed9ea2915f84d4d Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 15 May 2026 16:01:17 -0300 Subject: [PATCH 10/11] Target SDK 37 --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f12a877d..320d6fa6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -30,7 +30,7 @@ android { defaultConfig { applicationId = "org.onionshare.android" minSdk = 26 - targetSdk = 36 + targetSdk = 37 versionCode = 26 versionName = "0.3.0-beta" From 82be6f219b704d281a946a40e252e853b38e06ff Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 15 Jun 2026 17:16:30 -0300 Subject: [PATCH 11/11] More dependency updates --- gradle/libs.versions.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 07a09087..fa77b81c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,14 +1,14 @@ [versions] -agp = "9.2.0" +agp = "9.2.1" kotlin = "2.3.21" hilt = "2.59.2" ksp = "2.3.7" ktlint-plugin = "10.2.1" -compose-bom = "2026.04.01" -compose-ui-test-junit4 = "1.11.0" +compose-bom = "2026.05.01" +compose-ui-test-junit4 = "1.11.2" -androidx-core-ktx = "1.18.0" +androidx-core-ktx = "1.19.0" androidx-appcompat = "1.7.1" androidx-activity-compose = "1.13.0" androidx-lifecycle-runtime-ktx = "2.10.0" @@ -20,23 +20,23 @@ androidx-security-crypto = "1.1.0" androidx-test-ext-junit = "1.3.0" androidx-test-espresso-core = "3.7.0" -material = "1.13.0" +material = "1.14.0" desugar-jdk-libs = "2.1.5" onionwrapper = "0.1.4" -tor-android = "0.4.8.22" +tor-android = "0.4.9.9" lyrebird-android = "0.6.2" moat-api = "0.4" okhttp = "4.12.0" ktor = "3.4.3" -slf4j = "2.0.17" +slf4j = "2.0.18" logback-android = "3.0.0" junit4 = "4.13.2" -mockk = "1.14.9" +mockk = "1.14.11" turbine = "1.2.1" -kotlinx-coroutines-test = "1.10.2" +kotlinx-coroutines-test = "1.11.0" screengrab = "2.1.1" [plugins]