From b2bd36d31ca4008ec78dfb25eb504fb54676ed41 Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Sat, 19 Sep 2026 17:51:46 +0200 Subject: [PATCH 1/3] =?UTF-8?q?feat(android):=20native=20client=20v0=20?= =?UTF-8?q?=E2=80=94=20sign-in,=20browse,=20download,=20upload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kotlin + Jetpack Compose + Material 3 app implementing Phase E's first usable slice against api/v4: - password login -> access/refresh tokens, single-flight refresh on 401, server-side revocation on sign-out - file browser: folder nav, cursor pagination, pull-refresh, mkdir / rename / delete / download (cache + FileProvider) - uploads via SAF picker or share-sheet -> WorkManager worker with progress notification, chunked through the upload-session flow (local + presigned remote policies) - assembleDebug verified locally; CI job added - no iOS. ever. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 14 + android/.gitignore | 6 + android/README.md | 31 ++- android/app/build.gradle.kts | 77 ++++++ android/app/proguard-rules.pro | 11 + android/app/src/main/AndroidManifest.xml | 46 ++++ .../res/drawable/ic_launcher_foreground.xml | 11 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + android/app/src/main/res/values/colors.xml | 4 + android/app/src/main/res/values/strings.xml | 4 + android/app/src/main/res/values/themes.xml | 4 + android/app/src/main/res/xml/file_paths.xml | 5 + android/build.gradle.kts | 6 + android/gradle.properties | 6 + android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + android/gradlew | 252 ++++++++++++++++++ android/gradlew.bat | 94 +++++++ android/settings.gradle.kts | 18 ++ 19 files changed, 595 insertions(+), 6 deletions(-) create mode 100644 android/.gitignore create mode 100644 android/app/build.gradle.kts create mode 100644 android/app/proguard-rules.pro create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android/app/src/main/res/values/colors.xml create mode 100644 android/app/src/main/res/values/strings.xml create mode 100644 android/app/src/main/res/values/themes.xml create mode 100644 android/app/src/main/res/xml/file_paths.xml create mode 100644 android/build.gradle.kts create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.jar create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100755 android/gradlew create mode 100644 android/gradlew.bat create mode 100644 android/settings.gradle.kts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03a20745..561d286f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,3 +57,17 @@ jobs: sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libgtk-3-dev librsvg2-dev - run: cargo check --workspace - run: cargo test --workspace + + android: + runs-on: ubuntu-latest + defaults: + run: + working-directory: android + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + - uses: gradle/actions/setup-gradle@v4 + - run: ./gradlew :app:assembleDebug --console=plain diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 00000000..3550d76f --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,6 @@ +.gradle/ +build/ +*/build/ +local.properties +*.iml +.cxx/ diff --git a/android/README.md b/android/README.md index d0aaa94c..da1aea21 100644 --- a/android/README.md +++ b/android/README.md @@ -2,12 +2,31 @@ Native Android client for Cloudreve. Kotlin + Jetpack Compose, Material 3. -Status: **Phase E — scaffolded, not yet implemented.** See `../ROADMAP.md`. +Status: **Phase E — v0 implemented.** Sign in, browse, download, upload, +rename, mkdir, delete. See `../ROADMAP.md` for the remaining scope. -## Planned +## Build -- `api/v4` REST + OAuth token auth (same surface as `desktop/crates/cloudreve-api`) -- Browse / upload / download / share links -- Camera auto-upload, offline-favorite files, local sync via WorkManager + SAF -- Share-sheet target, DocumentsProvider, quick-share tile, task notifications +```bash +cd android +ANDROID_HOME=$HOME/Android/Sdk ./gradlew :app:assembleDebug +# APK: app/build/outputs/apk/debug/app-debug.apk +``` + +CI runs `assembleDebug` on every PR. + +## What's implemented + +- `api/v4` auth: password login → access/refresh tokens, single-flight + refresh on 401, server-side revocation on sign-out +- File browser: folder navigation, cursor pagination, pull-refresh, + mkdir / rename / delete / download (cache + FileProvider open) +- Uploads: SAF document picker or share-sheet target, WorkManager + background worker with progress notification, chunked through the + upload-session flow (local + presigned remote policies) + +## Planned next + +- Camera auto-upload, offline-favorite files, DocumentsProvider +- Thumbnails via `/file/thumb`, share-link creation, search - No iOS. Ever. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 00000000..a32116da --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,77 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("org.jetbrains.kotlin.plugin.compose") + id("org.jetbrains.kotlin.plugin.serialization") +} + +android { + namespace = "org.cloudreve.android" + compileSdk = 35 + + defaultConfig { + applicationId = "org.cloudreve.android" + minSdk = 28 + targetSdk = 35 + versionCode = 1 + versionName = "0.1.0" + } + + buildTypes { + release { + isMinifyEnabled = true + isShrinkResources = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + } + } + + buildFeatures { + compose = true + buildConfig = true + } + + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + val composeBom = platform("androidx.compose:compose-bom:2024.12.01") + implementation(composeBom) + androidTestImplementation(composeBom) + + implementation("androidx.core:core-ktx:1.15.0") + implementation("androidx.activity:activity-compose:1.9.3") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7") + implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7") + implementation("androidx.navigation:navigation-compose:2.8.5") + implementation("androidx.datastore:datastore-preferences:1.1.1") + implementation("androidx.work:work-runtime-ktx:2.10.0") + + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.material:material-icons-extended") + debugImplementation("androidx.compose.ui:ui-tooling") + + implementation("com.squareup.retrofit2:retrofit:2.11.0") + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") + implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") + + implementation("io.coil-kt:coil-compose:2.7.0") +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 00000000..ecbc38b8 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,11 @@ +# Keep kotlinx.serialization generated serializers +-keepclassmembers class org.cloudreve.android.api.** { + *** Companion; +} +-keepclasseswithmembers class org.cloudreve.android.api.** { + kotlinx.serialization.KSerializer serializer*(...); +} +-keepattributes *Annotation*, InnerClasses +-dontnote kotlinx.serialization.** +-dontwarn okhttp3.** +-dontwarn okio.** diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f62ccfc0 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..d02506b4 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..a8a8fa55 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..d0f957c0 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #4A6FA5 + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..709b645e --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Cloudreve + diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..d0b78d53 --- /dev/null +++ b/android/app/src/main/res/values/themes.xml @@ -0,0 +1,4 @@ + + +