create folders

This commit is contained in:
m
2026-09-12 15:07:59 +02:00
parent bada082c35
commit fca8ef4c13
2 changed files with 122 additions and 0 deletions
@@ -0,0 +1,72 @@
package com.vaultdrop.mobile.features.saf
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.DocumentsContract
import dagger.hilt.android.qualifiers.ApplicationContext
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
/**
* Crée un dossier physique via DocumentsContract (SAF) — facteur commun de
* `ensureVaultDropFolder` (onboarding) et de la feature « Créer un dossier ».
*
* Le parent peut être un URI *tree* (racine importée) ou *document* (dossier
* déjà créé/parcouru) : les deux sont acceptés par `createDocument`.
*/
@Singleton
class SafFolderCreator @Inject constructor(
@ApplicationContext private val context: Context,
) {
private val resolver: ContentResolver get() = context.contentResolver
/**
* Crée un sous-dossier nommé `name` sous `parentUri`. Retourne son URI SAF,
* ou null si le parent est absente ou si la création échoue.
*/
suspend fun createFolder(parentUri: String?, name: String): Uri? {
val parentDocumentUri = toDocumentUri(parentUri) ?: return null
val created = runCatching {
DocumentsContract.createDocument(
resolver,
parentDocumentUri,
DocumentsContract.Document.MIME_TYPE_DIR,
name,
)
}.getOrElse { e ->
Timber.w(e, "cannot create folder '$name' in $parentUri")
null
} ?: return null
// Best-effort : prolonge l'accès au-delà de l'intent original.
runCatching {
resolver.takePersistableUriPermission(
created,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
)
}.onFailure { Timber.w(it, "persistable uri permission absent for new folder") }
return created
}
/**
* `DocumentsContract.createDocument` attend un URI *document*, pas un URI
* *tree*. Convertit un tree URI en document URI (équivalent au dossier
* racine de l'arbre) — les deux autorités sont identiques.
*/
private fun toDocumentUri(uri: String?): Uri? {
val raw = uri?.let(Uri::parse) ?: return null
return if (DocumentsContract.isTreeUri(raw)) {
DocumentsContract.buildDocumentUriUsingTree(
raw,
DocumentsContract.getTreeDocumentId(raw),
)
} else {
raw
}
}
}
@@ -0,0 +1,50 @@
package com.vaultdrop.mobile.ui.components
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.vaultdrop.mobile.R
/** Dialog de nommage d'un nouveau dossier — partagé accueil / dossier. */
@Composable
fun FolderNameDialog(
onDismiss: () -> Unit,
onConfirm: (String) -> Unit,
) {
var name by remember { mutableStateOf("") }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.new_folder_name_title)) },
text = {
OutlinedTextField(
value = name,
onValueChange = { name = it },
placeholder = { Text(stringResource(R.string.new_folder_name_hint)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
},
confirmButton = {
TextButton(
onClick = { onConfirm(name.trim()) },
enabled = name.isNotBlank(),
) {
Text(stringResource(R.string.create_folder))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.pdf_builder_cancel))
}
},
)
}