feat: sauvegarde du compte CalDAV même si la connexion échoue, avec indicateur visuel

This commit is contained in:
2026-06-06 07:18:34 +02:00
parent dc6847d205
commit 2e59d54de6
2 changed files with 47 additions and 4 deletions
@@ -15,6 +15,7 @@ import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Download import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.Sync import androidx.compose.material.icons.outlined.Sync
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@@ -23,6 +24,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButton
@@ -185,9 +187,10 @@ fun SettingsScreen(
} }
discovery.second?.let { error -> discovery.second?.let { error ->
Text( Text(
text = error, text = "Connexion échouée — le compte a été ajouté sans synchronisation : $error",
color = MaterialTheme.colorScheme.error, color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(horizontal = 16.dp), style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
) )
} }
@@ -240,10 +243,28 @@ private fun SectionTitle(text: String) {
@Composable @Composable
private fun CalDavSourceRow(source: Source, onDelete: () -> Unit) { private fun CalDavSourceRow(source: Source, onDelete: () -> Unit) {
val connectionFailed = source.caldavData?.calendarHomeUrl == null
ListItem( ListItem(
leadingContent = { Icon(Icons.Outlined.AccountCircle, contentDescription = null) }, leadingContent = {
if (connectionFailed) {
Icon(Icons.Outlined.Warning, contentDescription = "Connexion échouée", tint = MaterialTheme.colorScheme.error)
} else {
Icon(Icons.Outlined.AccountCircle, contentDescription = null)
}
},
headlineContent = { Text(source.displayName) }, headlineContent = { Text(source.displayName) },
supportingContent = { Text(source.caldavData?.serverUrl ?: "") }, supportingContent = {
Column {
Text(source.caldavData?.serverUrl ?: "")
if (connectionFailed) {
Text(
text = "Non connecté",
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.labelSmall,
)
}
}
},
trailingContent = { trailingContent = {
IconButton(onClick = onDelete) { IconButton(onClick = onDelete) {
Icon(Icons.Outlined.Delete, contentDescription = "Supprimer", tint = MaterialTheme.colorScheme.error) Icon(Icons.Outlined.Delete, contentDescription = "Supprimer", tint = MaterialTheme.colorScheme.error)
@@ -11,6 +11,8 @@ import com.planify.mobile.data.preferences.AppPreferences
import com.planify.mobile.data.preferences.ThemeMode import com.planify.mobile.data.preferences.ThemeMode
import com.planify.mobile.data.sync.SyncScheduler import com.planify.mobile.data.sync.SyncScheduler
import com.planify.mobile.domain.model.Source import com.planify.mobile.domain.model.Source
import com.planify.mobile.domain.model.SourceCalDavData
import com.planify.mobile.domain.model.SourceType
import com.planify.mobile.domain.repository.ProjectRepository import com.planify.mobile.domain.repository.ProjectRepository
import com.planify.mobile.domain.repository.SourceRepository import com.planify.mobile.domain.repository.SourceRepository
import com.planify.mobile.domain.repository.TaskRepository import com.planify.mobile.domain.repository.TaskRepository
@@ -23,6 +25,9 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
data class SettingsUiState( data class SettingsUiState(
@@ -98,6 +103,23 @@ class SettingsViewModel @Inject constructor(
if (uiState.value.syncEnabled) syncScheduler.schedule() if (uiState.value.syncEnabled) syncScheduler.schedule()
} }
is DiscoveryResult.Failure -> { is DiscoveryResult.Failure -> {
// Save the account anyway so the user keeps their credentials
val now = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
val sourceId = UUID.randomUUID().toString()
val fallback = Source(
id = sourceId,
type = SourceType.CALDAV,
displayName = username,
addedAt = now,
updatedAt = now,
caldavData = SourceCalDavData(
serverUrl = baseUrl,
username = username,
calendarHomeUrl = null,
),
)
credentialStore.savePassword(sourceId, password)
sourceRepository.insertSource(fallback)
_discoveryState.update { false to result.message } _discoveryState.update { false to result.message }
} }
} }