Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
52b10a8
feat(PublicSharePassword): Put back the layout to enter password
FabianDevel Jan 14, 2026
f980820
feat(PublicSharePassword): Add sharelink token in the api calls
FabianDevel Jan 14, 2026
b73ad37
refactor(PublicSharePassword): Add authToken directly in callApi
FabianDevel Jan 15, 2026
0d4f9ae
fix(PublicSharePassword): Fix the normal public shares not working an…
FabianDevel Jan 15, 2026
adaaec6
refactor(PublicSharePassword): Rename fileId into rootFileId to mitig…
FabianDevel Jan 15, 2026
30af9f4
feat(PublicSharePassword): Add sharelink auth token in the files to h…
FabianDevel Jan 15, 2026
20b4e51
fix(PublicSharePassword): Fix count api call not working
FabianDevel Jan 15, 2026
d6bd7eb
feat(PublicSharePassword): Add sharelink auth token in image preview
FabianDevel Jan 15, 2026
82a3ccf
refactor(PublicSharePassword): Factorize the appendQuery function
FabianDevel Jan 15, 2026
b34550e
chore(PublicShare): Add missing commas
FabianDevel Jan 16, 2026
a10af0f
chore(PublicShare): Add clarifying commentary
FabianDevel Jan 16, 2026
79a5918
fix(PublicSharePassword): Fix downloadUrl
FabianDevel Jan 30, 2026
54ecd11
refactor(SafeNavigate): Replace deprecated safeNavigate methods
FabianDevel Feb 2, 2026
94a2354
chore(PublicShare): Remove now useless code
FabianDevel Feb 2, 2026
933ebc1
feat: Handle action done in password screen
benjaminVadon Feb 19, 2026
510ad78
fix: Provide file name for when the shared root is protected by password
benjaminVadon Feb 20, 2026
a9b8f4d
feat: Retrieve root file when come back from preview
benjaminVadon Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/src/main/java/com/infomaniak/drive/MatomoDrive.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak kDrive - Android
* Copyright (C) 2022-2024 Infomaniak Network SA
* Copyright (C) 2022-2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -121,7 +121,6 @@ object MatomoDrive : Matomo {
OpenBookmark("openBookmark"),
OpenCreationWebview("openCreationWebview"),
OpenFromUserMenuCard("openFromUserMenuCard"),
OpenInBrowser("openInBrowser"),
OpenLoginWebview("openLoginWebview"),
OpenWith("openWith"),
Pause("pause"),
Expand Down Expand Up @@ -166,6 +165,7 @@ object MatomoDrive : Matomo {
TryAddingFileWithDriveFull("tryAddingFileWithDriveFull"),
Update("update"),
UploadFile("uploadFile"),
ValidatePassword("validatePassword"),
ViewGrid("viewGrid"),
ViewList("viewList"),
}
Expand Down
61 changes: 41 additions & 20 deletions app/src/main/java/com/infomaniak/drive/data/api/ApiRoutes.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak kDrive - Android
* Copyright (C) 2022-2025 Infomaniak Network SA
* Copyright (C) 2022-2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -114,7 +114,7 @@ object ApiRoutes {
//region File url
fun getDownloadFileUrl(file: File): String = with(file) {
val downloadUrl = if (isPublicShared()) {
downloadPublicShareFile(driveId, publicShareUuid, id)
downloadPublicShareFile(driveId, publicShareUuid, id, publicShareAuthToken)
} else {
downloadFile(file)
}
Expand All @@ -124,25 +124,25 @@ object ApiRoutes {

fun getThumbnailUrl(file: File) = with(file) {
when {
isPublicShared() -> getPublicShareFileThumbnail(driveId, publicShareUuid, id)
isPublicShared() -> getPublicShareFileThumbnail(driveId, publicShareUuid, id, publicShareAuthToken)
isTrashed() -> thumbnailTrashFile(file)
else -> thumbnailFile(file)
}
}

fun getImagePreviewUrl(file: File): String = with(file) {
val url = if (isPublicShared()) {
getPublicShareFilePreview(driveId, publicShareUuid, id)
getPublicShareFilePreview(driveId, publicShareUuid, id, publicShareAuthToken)
} else {
imagePreviewFile(file)
}

return "$url?width=2500&height=1500&quality=80"
return url.appendQuery("width=2500&height=1500&quality=80")
}

fun getOnlyOfficeUrl(file: File) = with(file) {
if (isPublicShared()) {
showPublicShareOfficeFile(driveId, publicShareUuid, id)
showPublicShareOfficeFile(driveId, publicShareUuid, id, publicShareAuthToken)
} else {
"$AUTOLOG_URL?url=${showOffice(file)}"
}
Expand Down Expand Up @@ -313,38 +313,48 @@ object ApiRoutes {

/** Public Share */
//region Public share
fun getPublicShareInfo(driveId: Int, linkUuid: String) = "${getPublicShareUrlV2(driveId, linkUuid)}/init"
fun getPublicShareInfo(driveId: Int, linkUuid: String): String = "${getPublicShareUrlV2(driveId, linkUuid)}/init"

fun submitPublicSharePassword(driveId: Int, linkUuid: String) = "${getPublicShareUrlV2(driveId, linkUuid)}/auth"

fun getPublicShareRootFile(driveId: Int, linkUuid: String, fileId: Int): String {
return "$SHARE_URL_V3/$driveId/share/$linkUuid/files/$fileId?$sharedFileWithQuery"
}

fun getPublicShareChildrenFiles(driveId: Int, linkUuid: String, fileId: Int, sortType: SortType): String {
fun getPublicShareChildrenFiles(
driveId: Int,
linkUuid: String,
fileId: Int,
sortType: SortType,
authToken: String?,
): String {
val orderQuery = "order_by=${sortType.orderBy}&order=${sortType.order}"
return "$SHARE_URL_V3/$driveId/share/$linkUuid/files/$fileId/files?$sharedFileWithQuery&$orderQuery"
val authParam = authToken?.let { "&sharelink_token=$it" } ?: ""
return "$SHARE_URL_V3/$driveId/share/$linkUuid/files/$fileId/files?$sharedFileWithQuery&$orderQuery$authParam"
Comment on lines +324 to +333
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPublicShareChildrenFiles appends sharelink_token directly into the URL without URL-encoding. If the token contains reserved characters, the resulting URL can be invalid or interpreted incorrectly. Prefer building the query via appendQuery(...) (with proper encoding) or using an HttpUrl builder; also avoid appending when the token is blank.

Copilot uses AI. Check for mistakes.
}

fun getPublicShareFileCount(driveId: Int, linkUuid: String, fileId: Int): String {
return "${publicShareFile(driveId, linkUuid, fileId)}/count"
}

private fun getPublicShareFileThumbnail(driveId: Int, linkUuid: String, fileId: Int): String {
return "${publicShareFile(driveId, linkUuid, fileId)}/thumbnail"
private fun getPublicShareFileThumbnail(driveId: Int, linkUuid: String, fileId: Int, authToken: String? = null): String {
val authParam = authToken?.let { "?sharelink_token=$it" } ?: ""
return "${publicShareFile(driveId, linkUuid, fileId)}/thumbnail$authParam"
}

private fun getPublicShareFilePreview(driveId: Int, linkUuid: String, fileId: Int): String {
return "${publicShareFile(driveId, linkUuid, fileId)}/preview"
private fun getPublicShareFilePreview(driveId: Int, linkUuid: String, fileId: Int, authToken: String? = null): String {
val authParam = authToken?.let { "?sharelink_token=$it" } ?: ""
return "${publicShareFile(driveId, linkUuid, fileId)}/preview$authParam"
}

private fun downloadPublicShareFile(driveId: Int, linkUuid: String, fileId: Int): String {
return "${publicShareFile(driveId, linkUuid, fileId)}/download"
private fun downloadPublicShareFile(driveId: Int, linkUuid: String, fileId: Int, authToken: String? = null): String {
val authParam = authToken?.let { "?sharelink_token=$it" } ?: ""
return "${publicShareFile(driveId, linkUuid, fileId)}/download$authParam"
}

private fun showPublicShareOfficeFile(driveId: Int, linkUuid: String, fileId: Int): String {
// For now, this call fails because the back hasn't dev the conversion of office files to pdf for mobile
return "$SHARE_URL_V1/share/$driveId/$linkUuid/preview/text/$fileId"
private fun showPublicShareOfficeFile(driveId: Int, linkUuid: String, fileId: Int, authToken: String? = null): String {
val authParam = authToken?.let { "?sharelink_token=$it" } ?: ""
return "$SHARE_URL_V1/share/$driveId/$linkUuid/preview/text/$fileId$authParam"
Comment on lines +340 to +357
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several helpers (getPublicShareFileThumbnail, getPublicShareFilePreview, downloadPublicShareFile, showPublicShareOfficeFile, downloadPublicShareArchive) build ?sharelink_token=$it via raw string concatenation. This should URL-encode the token and skip appending when it’s blank to avoid malformed URLs and accidental “empty token” requests.

Copilot uses AI. Check for mistakes.
}

fun importPublicShareFiles(driveId: Int) = "${driveURLV2(driveId)}/imports/sharelink"
Expand All @@ -353,8 +363,14 @@ object ApiRoutes {
return "${getPublicShareUrlV2(driveId, linkUuid)}/archive"
}

fun downloadPublicShareArchive(driveId: Int, publicShareUuid: String, archiveUuid: String): String {
return "${buildPublicShareArchive(driveId, publicShareUuid)}/$archiveUuid/download"
fun downloadPublicShareArchive(
driveId: Int,
publicShareUuid: String,
archiveUuid: String,
authToken: String? = null,
): String {
val authParam = authToken?.let { "?sharelink_token=$it" } ?: ""
return "${buildPublicShareArchive(driveId, publicShareUuid)}/$archiveUuid/download$authParam"
}

private fun publicShareFile(driveId: Int, linkUuid: String, fileId: Int): String {
Expand Down Expand Up @@ -446,4 +462,9 @@ object ApiRoutes {

private fun showOffice(file: File) = "${OFFICE_URL}/${file.driveId}/${file.id}"
//endregion

fun String.appendQuery(query: String): String {
val querySeparator = if (contains("?")) "&" else "?"
return this + querySeparator + query
}
Comment on lines +466 to +469
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use Uri.Builder or buildUrl from ktor to manage all the case of ApiRoutes ?

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak kDrive - Android
* Copyright (C) 2025 Infomaniak Network SA
* Copyright (C) 2025-2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -24,6 +24,7 @@ import com.infomaniak.core.network.models.ApiResponse
import com.infomaniak.core.network.models.ApiResponseStatus
import com.infomaniak.core.network.networking.HttpClient
import com.infomaniak.drive.data.api.ApiRoutes
import com.infomaniak.drive.data.api.ApiRoutes.appendQuery
import com.infomaniak.drive.data.api.ApiRoutes.loadCursor
import com.infomaniak.drive.data.api.CursorApiResponse
import com.infomaniak.drive.data.models.ArchiveUUID
Expand All @@ -39,26 +40,33 @@ import okhttp3.OkHttpClient

object PublicShareApiRepository {

suspend fun getPublicShareInfo(driveId: Int, linkUuid: String): ApiResponse<ShareLink> {
return callApi(
suspend fun getPublicShareInfo(driveId: Int, linkUuid: String, authToken: String? = null): ApiResponse<ShareLink> {
return callPublicShareApi(
url = ApiRoutes.getPublicShareInfo(driveId, linkUuid),
method = GET,
authToken = authToken,
okHttpClient = PublicShareHttpClient.okHttpClientWithTokenInterceptor,
)
}

suspend fun submitPublicSharePassword(driveId: Int, linkUuid: String, password: String): ApiResponse<Boolean> {
return callApi(
suspend fun submitPublicSharePassword(driveId: Int, linkUuid: String, password: String): ApiResponse<PublicShareToken> {
return callPublicShareApi(
url = ApiRoutes.submitPublicSharePassword(driveId, linkUuid),
method = POST,
body = mapOf("password" to password),
)
}

suspend fun getPublicShareRootFile(driveId: Int, linkUuid: String, fileId: FileId): ApiResponse<File> {
return callApi(
suspend fun getPublicShareRootFile(
driveId: Int,
linkUuid: String,
fileId: FileId,
authToken: String? = null,
): ApiResponse<File> {
return callPublicShareApi(
url = ApiRoutes.getPublicShareRootFile(driveId, linkUuid, fileId),
method = GET,
authToken = authToken,
)
}

Expand All @@ -68,23 +76,38 @@ object PublicShareApiRepository {
folderId: FileId,
sortType: SortType,
cursor: String?,
authToken: String? = null,
): CursorApiResponse<List<File>> {
val url = ApiRoutes.getPublicShareChildrenFiles(driveId, linkUuid, folderId, sortType) + "&${loadCursor(cursor)}"
val baseUrl = ApiRoutes.getPublicShareChildrenFiles(driveId, linkUuid, folderId, sortType, authToken)
val url = baseUrl + "&${loadCursor(cursor)}"

return callApiWithCursor(url, GET)
}

suspend fun getPublicShareFileCount(driveId: Int, linkUuid: String, fileId: Int): ApiResponse<FileCount> {
return callApi(
suspend fun getPublicShareFileCount(
driveId: Int,
linkUuid: String,
fileId: Int,
authToken: String? = null,
): ApiResponse<FileCount> {
return callPublicShareApi(
url = ApiRoutes.getPublicShareFileCount(driveId, linkUuid, fileId),
method = GET,
authToken = authToken,
)
}

suspend fun buildPublicShareArchive(driveId: Int, linkUuid: String, archiveBody: ArchiveBody): ApiResponse<ArchiveUUID> {
return callApi(
suspend fun buildPublicShareArchive(
driveId: Int,
linkUuid: String,
archiveBody: ArchiveBody,
authToken: String? = null,
): ApiResponse<ArchiveUUID> {
return callPublicShareApi(
url = ApiRoutes.buildPublicShareArchive(driveId, linkUuid),
method = POST,
body = archiveBody,
authToken = authToken,
)
}

Expand All @@ -97,6 +120,7 @@ object PublicShareApiRepository {
fileIds: List<Int>,
exceptedFileIds: List<Int>,
password: String = "",
authToken: String? = null,
): ApiResponse<List<FileExternalImport>> {

val body: MutableMap<String, Any> = mutableMapOf(
Expand All @@ -109,9 +133,10 @@ object PublicShareApiRepository {
if (fileIds.isNotEmpty()) body["file_ids"] = fileIds.toTypedArray()
if (exceptedFileIds.isNotEmpty()) body["except_file_ids"] = exceptedFileIds.toTypedArray()

return callApi(
return callPublicShareApi(
url = ApiRoutes.importPublicShareFiles(destinationDriveId),
method = POST,
authToken = authToken,
body = body,
okHttpClient = AccountUtils.getHttpClient(
userId = destinationUserId,
Expand All @@ -121,13 +146,15 @@ object PublicShareApiRepository {
)
}

private suspend inline fun <reified T> callApi(
private suspend inline fun <reified T> callPublicShareApi(
url: String,
method: ApiController.ApiMethod,
authToken: String? = null,
body: Any? = null,
okHttpClient: OkHttpClient = HttpClient.okHttpClient,
): T {
return ApiController.callApi(url, method, body, okHttpClient)
val authentifiedUrl = authToken?.let { token -> url.appendQuery("sharelink_token=$token") } ?: url
return ApiController.callApi(authentifiedUrl, method, body, okHttpClient)
}

private suspend inline fun <reified T> callApiWithCursor(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Infomaniak kDrive - Android
* Copyright (C) 2025-2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.drive.data.api.publicshare

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class PublicShareToken(
val token: String = "",
) : Parcelable
7 changes: 6 additions & 1 deletion app/src/main/java/com/infomaniak/drive/data/models/File.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak kDrive - Android
* Copyright (C) 2022-2025 Infomaniak Network SA
* Copyright (C) 2022-2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -161,6 +161,11 @@ open class File(
@Transient
var publicShareUuid: String = ""

@IgnoredOnParcel
@Ignore
@Transient
var publicShareAuthToken: String? = null

val revisedAtInMillis: Long inline get() = revisedAt * 1000

fun initUid() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak kDrive - Android
* Copyright (C) 2022-2025 Infomaniak Network SA
* Copyright (C) 2022-2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak kDrive - Android
* Copyright (C) 2022-2025 Infomaniak Network SA
* Copyright (C) 2022-2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -228,7 +228,12 @@ class FileListViewModel(application: Application) : AndroidViewModel(application
fun getFileCount(folder: File): LiveData<FileCount> = liveData(Dispatchers.IO) {
lastItemCount?.let { emit(it) }
val apiResponse = if (folder.isPublicShared()) {
PublicShareApiRepository.getPublicShareFileCount(folder.driveId, folder.publicShareUuid, folder.id)
PublicShareApiRepository.getPublicShareFileCount(
driveId = folder.driveId,
linkUuid = folder.publicShareUuid,
fileId = folder.id,
authToken = folder.publicShareAuthToken,
)
} else {
ApiRepository.getFileCount(folder)
}
Expand Down
Loading