fix(android): local-policy uploads, cleartext HTTP, original icon

Phone smoke test against a local Cloudreve server surfaced two real
bugs in the release build:

- uploadFile treated a missing upload_urls as "nothing to send", but
  local/slave policies return no presigned URLs: chunks must go to
  POST /api/v4/file/upload/{session}/{index} and the server completes
  on the last chunk. Result was a zero-filled placeholder that stayed
  lock-held. Now honours rapid_uploaded and falls back to the
  conventional endpoint; 0-byte files send one empty chunk so the
  session completes.
- usesCleartextTraffic was unset, so http:// servers failed silently
  at the socket. BYO-server means plain-HTTP home installs are
  expected; allowed explicitly.

Launcher icon now derives from the original Cloudreve artwork
(desktop/src-tauri/icons/icon.png): full mipmap set, adaptive
foreground/background, plus Play Store icon + feature graphic assets.

Verified on-device (Android 16): login, browse, create folder,
upload (bytes verified server-side), download, share link, offline
files screen, DocumentsProvider root.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pull/3593/head
Tomas Dvorak 2 weeks ago
parent 80f1f54c1e
commit e830d286fb

@ -4,3 +4,4 @@ build/
local.properties local.properties
*.iml *.iml
.cxx/ .cxx/
.kotlin/

@ -20,6 +20,7 @@
<application <application
android:name=".CloudreveApp" android:name=".CloudreveApp"
android:allowBackup="true" android:allowBackup="true"
android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"

@ -149,6 +149,7 @@ data class UploadSessionResponse(
val uri: String = "", val uri: String = "",
@SerialName("storage_policy") val storagePolicy: StoragePolicy? = null, @SerialName("storage_policy") val storagePolicy: StoragePolicy? = null,
@SerialName("callback_secret") val callbackSecret: String = "", @SerialName("callback_secret") val callbackSecret: String = "",
@SerialName("rapid_uploaded") val rapidUploaded: Boolean = false,
) )
@Serializable @Serializable

@ -109,22 +109,28 @@ class FileRepository(private val api: ApiClient) {
mimeType = mime, mimeType = mime,
) )
val urls = session.uploadUrls if (session.rapidUploaded) return session
if (urls.isNullOrEmpty()) {
// Zero-chunk policies (e.g. rapid-upload hit) need nothing further.
return session
}
val svc = api.service() val urls = session.uploadUrls
// Local/slave policies return no presigned URLs: chunks go to the
// conventional POST /api/v4/file/upload/{session}/{index} endpoint and
// the server completes the session once every chunk has landed.
val conventional = urls.isNullOrEmpty()
val chunkSize = if (session.chunkSize > 0) session.chunkSize else localFile.length().coerceAtLeast(1) val chunkSize = if (session.chunkSize > 0) session.chunkSize else localFile.length().coerceAtLeast(1)
val totalChunks = ((localFile.length() + chunkSize - 1) / chunkSize).toInt().coerceAtLeast(1)
val svc = api.service()
var sent = 0L var sent = 0L
localFile.inputStream().buffered().use { input -> localFile.inputStream().buffered().use { input ->
val buffer = ByteArray(chunkSize.coerceAtMost(64L * 1024 * 1024).toInt()) val buffer = ByteArray(chunkSize.coerceAtMost(64L * 1024 * 1024).toInt())
var index = 0 for (index in 0 until totalChunks) {
while (true) {
val read = input.readNBytes(buffer, 0, buffer.size) val read = input.readNBytes(buffer, 0, buffer.size)
if (read <= 0 || index >= urls.size) break if (index == 0 && read == 0 && localFile.length() > 0) break
val target = resolveUrl(urls[index]) val target = if (conventional) {
session_serverBase() + "/api/v4/file/upload/${session.sessionId}/$index"
} else {
if (index >= urls!!.size) break
resolveUrl(urls[index])
}
val body = buffer.copyOf(read) val body = buffer.copyOf(read)
.toRequestBody(mime.toMediaTypeOrNull()) .toRequestBody(mime.toMediaTypeOrNull())
val resp = if (target.contains("/file/upload/")) { val resp = if (target.contains("/file/upload/")) {
@ -138,8 +144,8 @@ class FileRepository(private val api: ApiClient) {
} }
resp.body()?.close() resp.body()?.close()
sent += read sent += read
index++
onProgress(sent, localFile.length()) onProgress(sent, localFile.length())
if (read <= 0) break
} }
} }

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- stylized cloud -->
<path
android:fillColor="#FFFFFF"
android:pathData="M79,62c-1.1,0 -2.2,0.1 -3.3,0.3C72,52 62.4,44 51,44c-10,0 -18.5,6.4 -21.7,15.4C22.9,60.6 19,64.7 19,69.7 19,76.6 25.2,82 32.5,82L79,82c6.6,0 12,-4.9 12,-11S85.6,62 79,62z" />
</vector>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" /> <background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ic_launcher_background">#4A6FA5</color> <color name="ic_launcher_background">#FFFFFF</color>
</resources> </resources>

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Loading…
Cancel
Save