package com.shaarit.presentation.edit import androidx.compose.animation.* import androidx.compose.animation.core.* import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.AutoAwesome import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import coil.compose.AsyncImage import com.shaarit.presentation.add.ContentType import com.shaarit.ui.components.* import com.shaarit.ui.theme.* import com.shaarit.ui.theme.Purple import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class, androidx.compose.foundation.ExperimentalFoundationApi::class) @Composable fun EditLinkScreen( onNavigateBack: () -> Unit, viewModel: EditLinkViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsState() val url by viewModel.url.collectAsState() val title by viewModel.title.collectAsState() val description by viewModel.description.collectAsState() val selectedTags by viewModel.selectedTags.collectAsState() val newTagInput by viewModel.newTagInput.collectAsState() val availableTags by viewModel.availableTags.collectAsState() val isPrivate by viewModel.isPrivate.collectAsState() val tagSuggestions by viewModel.tagSuggestions.collectAsState() val contentType by viewModel.contentType.collectAsState() val snackbarHostState = remember { SnackbarHostState() } val focusManager = LocalFocusManager.current var showMarkdownPreview by remember { mutableStateOf(false) } val scrollState = rememberScrollState() val coroutineScope = rememberCoroutineScope() val aiEnrichmentState by viewModel.aiEnrichmentState.collectAsState() // State pour l'éditeur Markdown avec barre d'outils flottante val markdownEditorState = rememberMarkdownEditorState() // Pour faire défiler automatiquement vers la section tags quand le clavier s'ouvre val tagsSectionBringIntoViewRequester = remember { BringIntoViewRequester() } LaunchedEffect(uiState) { when (val state = uiState) { is EditLinkUiState.Success -> { onNavigateBack() } is EditLinkUiState.Error -> { snackbarHostState.showSnackbar(state.message) } else -> {} } } LaunchedEffect(Unit) { viewModel.aiErrorMessage.collect { message -> snackbarHostState.showSnackbar(message) } } LaunchedEffect(aiEnrichmentState) { if (aiEnrichmentState is AiEnrichmentState.Success) { snackbarHostState.showSnackbar("✨ Enrichissement IA appliqué !") viewModel.resetAiEnrichmentState() } } Box( modifier = Modifier .fillMaxSize() .background( brush = Brush.verticalGradient(colors = listOf(DeepNavy, DarkNavy)) ) ) { Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { TopAppBar( title = { Text( if (contentType == ContentType.NOTE) "Modifier la note" else "Modifier le lien", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold ) }, navigationIcon = { IconButton(onClick = onNavigateBack) { Icon( Icons.Default.ArrowBack, contentDescription = "Retour", tint = TextPrimary ) } }, colors = TopAppBarDefaults.topAppBarColors( containerColor = DeepNavy.copy(alpha = 0.9f), titleContentColor = TextPrimary ) ) }, containerColor = androidx.compose.ui.graphics.Color.Transparent ) { paddingValues -> when (uiState) { is EditLinkUiState.Loading -> { Box( modifier = Modifier .fillMaxSize() .padding(paddingValues), contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { CircularProgressIndicator(color = CyanPrimary) Spacer(modifier = Modifier.height(16.dp)) Text( "Chargement...", color = TextSecondary, style = MaterialTheme.typography.bodyMedium ) } } } else -> { Column( modifier = Modifier .padding(paddingValues) .padding(horizontal = 16.dp) .fillMaxSize() .imePadding() .verticalScroll(scrollState), verticalArrangement = Arrangement.spacedBy(12.dp) ) { // Content Type Selection (compact) GlassCard( modifier = Modifier.fillMaxWidth(), glowColor = CyanPrimary ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { // Bookmark option ContentTypeButton( icon = Icons.Default.Bookmark, label = "Lien", isSelected = contentType == ContentType.BOOKMARK, onClick = { viewModel.setContentType(ContentType.BOOKMARK) }, modifier = Modifier.weight(1f) ) // Note option ContentTypeButton( icon = Icons.Default.StickyNote2, label = "Note", isSelected = contentType == ContentType.NOTE, onClick = { viewModel.setContentType(ContentType.NOTE) }, modifier = Modifier.weight(1f) ) } } // URL Section (compact, only for Bookmarks) AnimatedVisibility( visible = contentType == ContentType.BOOKMARK, enter = expandVertically() + fadeIn(), exit = shrinkVertically() + fadeOut() ) { CompactFieldCard( icon = Icons.Default.Link, label = "URL" ) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { OutlinedTextField( value = url, onValueChange = { viewModel.url.value = it }, modifier = Modifier.weight(1f), placeholder = { Text("https://example.com", color = TextMuted) }, singleLine = true, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), colors = compactTextFieldColors(), shape = RoundedCornerShape(8.dp), textStyle = MaterialTheme.typography.bodyMedium ) // AI Magic Button AiMagicButton( onClick = { viewModel.analyzeUrlWithAi() }, isLoading = aiEnrichmentState is AiEnrichmentState.Loading, enabled = url.isNotBlank() && aiEnrichmentState !is AiEnrichmentState.Loading ) } } } // Title Section (compact) CompactFieldCard( icon = Icons.Default.Title, label = if (contentType == ContentType.NOTE) "Titre *" else "Titre" ) { OutlinedTextField( value = title, onValueChange = { viewModel.title.value = it }, modifier = Modifier.fillMaxWidth(), placeholder = { Text( if (contentType == ContentType.NOTE) "Titre de la note" else "Titre du lien", color = TextMuted ) }, singleLine = true, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), colors = compactTextFieldColors(), shape = RoundedCornerShape(8.dp), textStyle = MaterialTheme.typography.bodyMedium ) } // Description Section - Markdown Editor (plus grand en mode Note) GlassCard( modifier = Modifier .fillMaxWidth() .then( if (contentType == ContentType.NOTE) Modifier.heightIn(min = 400.dp) else Modifier ) ) { Column { // Header avec titre et toggle édition/apercu Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Icon( imageVector = Icons.Default.Description, contentDescription = null, tint = CyanPrimary, modifier = Modifier.size(20.dp) ) Column { Text( text = if (contentType == ContentType.NOTE) "Contenu" else "Description", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold, color = TextPrimary ) if (contentType == ContentType.NOTE) { Text( text = "Markdown supporté", style = MaterialTheme.typography.labelSmall, color = TextMuted ) } } } // Toggle édition/apercu simple Row( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { IconButton( onClick = { showMarkdownPreview = false }, modifier = Modifier.size(32.dp) ) { Icon( Icons.Default.Edit, contentDescription = "Éditer", tint = if (!showMarkdownPreview) CyanPrimary else TextMuted, modifier = Modifier.size(18.dp) ) } IconButton( onClick = { showMarkdownPreview = true }, modifier = Modifier.size(32.dp) ) { Icon( Icons.Default.Preview, contentDescription = "Aperçu", tint = if (showMarkdownPreview) CyanPrimary else TextMuted, modifier = Modifier.size(18.dp) ) } } } Spacer(modifier = Modifier.height(12.dp)) // Éditeur Markdown ou Aperçu if (showMarkdownPreview) { MarkdownPreview( markdown = description, modifier = Modifier .fillMaxWidth() .heightIn( min = if (contentType == ContentType.NOTE) 300.dp else 150.dp, max = if (contentType == ContentType.NOTE) 500.dp else 300.dp ) ) } else { SimpleMarkdownEditor( value = description, onValueChange = { viewModel.description.value = it }, editorState = markdownEditorState, modifier = Modifier .fillMaxWidth() .heightIn( min = if (contentType == ContentType.NOTE) 300.dp else 150.dp, max = if (contentType == ContentType.NOTE) 500.dp else 300.dp ), isNoteMode = contentType == ContentType.NOTE, placeholder = if (contentType == ContentType.NOTE) "Écrivez votre note ici..." else "Ajoutez une description..." ) } } } // Tags Section avec correction du clavier - se positionne au-dessus du clavier GlassCard( modifier = Modifier .fillMaxWidth() .bringIntoViewRequester(tagsSectionBringIntoViewRequester) ) { Column { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Icon( imageVector = Icons.Default.Tag, contentDescription = null, tint = CyanPrimary, modifier = Modifier.size(20.dp) ) Text( text = "Tags", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold, color = TextPrimary ) } Spacer(modifier = Modifier.height(12.dp)) // Selected tags if (selectedTags.isNotEmpty()) { LazyRow( horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(bottom = 12.dp) ) { items(selectedTags) { tag -> TagChip( tag = tag, isSelected = true, onClick = { viewModel.removeTag(tag) } ) } } } // New tag input - fermer le clavier sur Done Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { OutlinedTextField( value = newTagInput, onValueChange = { viewModel.onNewTagInputChanged(it) }, modifier = Modifier .weight(1f) .onFocusChanged { focusState -> if (focusState.isFocused) { // Faire défiler vers la section tags quand le champ est focusé coroutineScope.launch { tagsSectionBringIntoViewRequester.bringIntoView() } } }, placeholder = { Text("Ajouter un tag...", color = TextMuted) }, singleLine = true, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions( onDone = { if (newTagInput.isNotBlank()) { viewModel.addNewTag() } focusManager.clearFocus() } ), colors = compactTextFieldColors(), shape = RoundedCornerShape(8.dp), textStyle = MaterialTheme.typography.bodyMedium ) IconButton( onClick = { viewModel.addNewTag() focusManager.clearFocus() }, enabled = newTagInput.isNotBlank(), modifier = Modifier.size(40.dp) ) { Icon( Icons.Default.Add, contentDescription = "Ajouter", tint = if (newTagInput.isNotBlank()) CyanPrimary else TextMuted ) } } // Tag suggestions AnimatedVisibility( visible = tagSuggestions.isNotEmpty(), enter = expandVertically() + fadeIn(), exit = shrinkVertically() + fadeOut() ) { Column(modifier = Modifier.padding(top = 12.dp)) { Text( "Suggestions", style = MaterialTheme.typography.labelSmall, color = TextMuted, modifier = Modifier.padding(bottom = 8.dp) ) LazyRow( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { items(tagSuggestions.take(8)) { tag -> TagChip( tag = tag.name, isSelected = false, onClick = { viewModel.addTag(tag.name) focusManager.clearFocus() }, count = tag.occurrences ) } } } } // Popular tags if (tagSuggestions.isEmpty() && availableTags.isNotEmpty()) { Column(modifier = Modifier.padding(top = 12.dp)) { Text( "Populaires", style = MaterialTheme.typography.labelSmall, color = TextMuted, modifier = Modifier.padding(bottom = 8.dp) ) LazyRow( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { items( availableTags .filter { it.name !in selectedTags } .take(8) ) { tag -> TagChip( tag = tag.name, isSelected = false, onClick = { viewModel.addTag(tag.name) }, count = tag.occurrences ) } } } } } } // Privacy Section (compact) CompactFieldCard( icon = if (isPrivate) Icons.Default.Lock else Icons.Default.Public, label = if (isPrivate) "Privé" else "Public", onClick = { viewModel.isPrivate.value = !isPrivate } ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( if (isPrivate) "Seul vous pouvez voir" else "Visible par tous", style = MaterialTheme.typography.bodyMedium, color = TextSecondary ) Switch( checked = isPrivate, onCheckedChange = { viewModel.isPrivate.value = it }, colors = SwitchDefaults.colors( checkedThumbColor = CyanPrimary, checkedTrackColor = CyanPrimary.copy(alpha = 0.3f), uncheckedThumbColor = TextMuted, uncheckedTrackColor = SurfaceVariant ) ) } } Spacer(modifier = Modifier.height(8.dp)) // Update Button GradientButton( text = if (uiState is EditLinkUiState.Saving) "Enregistrement..." else if (contentType == ContentType.NOTE) "Enregistrer la note" else "Enregistrer les modifications", onClick = { focusManager.clearFocus() viewModel.updateLink() }, modifier = Modifier.fillMaxWidth(), enabled = when (contentType) { ContentType.BOOKMARK -> url.isNotBlank() && uiState !is EditLinkUiState.Saving ContentType.NOTE -> title.isNotBlank() && uiState !is EditLinkUiState.Saving } ) if (uiState is EditLinkUiState.Saving) { LinearProgressIndicator( modifier = Modifier.fillMaxWidth(), color = CyanPrimary, trackColor = SurfaceVariant ) } Spacer(modifier = Modifier.height(80.dp)) // Espace pour la barre d'outils flottante } } } } // Barre d'outils Markdown flottante - collée au-dessus du clavier FloatingMarkdownToolbar( editorState = markdownEditorState, onValueChange = { viewModel.description.value = it }, visible = !showMarkdownPreview, modifier = Modifier.align(Alignment.BottomCenter) ) } } /** * Bouton de sélection de type de contenu compact */ @Composable private fun ContentTypeButton( icon: androidx.compose.ui.graphics.vector.ImageVector, label: String, isSelected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier ) { Surface( onClick = onClick, shape = RoundedCornerShape(10.dp), color = if (isSelected) CyanPrimary.copy(alpha = 0.15f) else CardBackgroundElevated, border = if (isSelected) androidx.compose.foundation.BorderStroke(1.5.dp, CyanPrimary) else null, modifier = modifier ) { Row( modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = icon, contentDescription = null, tint = if (isSelected) CyanPrimary else TextSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(8.dp)) Text( text = label, style = MaterialTheme.typography.bodyMedium, color = if (isSelected) CyanPrimary else TextPrimary, fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal ) } } } /** * Carte de champ compacte */ @Composable private fun CompactFieldCard( icon: androidx.compose.ui.graphics.vector.ImageVector, label: String, onClick: (() -> Unit)? = null, content: @Composable ColumnScope.() -> Unit ) { val cardModifier = Modifier.fillMaxWidth() val finalModifier = if (onClick != null) { cardModifier.clickable(onClick = onClick) } else { cardModifier } GlassCard( modifier = finalModifier, glowColor = CyanPrimary.copy(alpha = 0.3f) ) { Column { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(bottom = 8.dp) ) { Icon( imageVector = icon, contentDescription = null, tint = CyanPrimary, modifier = Modifier.size(18.dp) ) Text( text = label, style = MaterialTheme.typography.labelMedium, color = TextSecondary, fontWeight = FontWeight.Medium ) } content() } } } /** * Couleurs pour les champs texte compacts */ @OptIn(ExperimentalMaterial3Api::class) @Composable private fun compactTextFieldColors() = OutlinedTextFieldDefaults.colors( focusedBorderColor = CyanPrimary, unfocusedBorderColor = SurfaceVariant, focusedLabelColor = CyanPrimary, unfocusedLabelColor = TextSecondary, cursorColor = CyanPrimary, focusedContainerColor = CardBackground.copy(alpha = 0.3f), unfocusedContainerColor = CardBackground.copy(alpha = 0.2f) ) /** * Bouton Magie IA pour enrichir automatiquement les informations du bookmark */ @Composable private fun AiMagicButton( onClick: () -> Unit, isLoading: Boolean, enabled: Boolean, modifier: Modifier = Modifier ) { val infiniteTransition = rememberInfiniteTransition(label = "ai_button") val shimmerAlpha by infiniteTransition.animateFloat( initialValue = 0.6f, targetValue = 1f, animationSpec = infiniteRepeatable( animation = tween(800, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "shimmer" ) Surface( onClick = onClick, enabled = enabled && !isLoading, shape = RoundedCornerShape(10.dp), color = if (enabled) Purple.copy(alpha = if (isLoading) shimmerAlpha * 0.3f else 0.15f) else SurfaceVariant, border = if (enabled) androidx.compose.foundation.BorderStroke(1.5.dp, Purple) else null, modifier = modifier.size(48.dp) ) { Box( contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize() ) { if (isLoading) { CircularProgressIndicator( modifier = Modifier.size(20.dp), color = Purple, strokeWidth = 2.dp ) } else { Icon( imageVector = Icons.Outlined.AutoAwesome, contentDescription = "Magie IA", tint = if (enabled) Purple else TextMuted, modifier = Modifier.size(22.dp) ) } } } }