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 @@ + + +