Warn user if notes was not correspond to template on edit notes & autocomplete note on ui

This commit is contained in:
Hasinjato 2026-05-12 15:29:52 +03:00
parent cadb2a6b3f
commit 68dd6216b2
3 changed files with 148 additions and 105 deletions

View file

@ -1,6 +1,7 @@
package mg.dot.feufaro
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
@ -11,14 +12,10 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Save
import androidx.compose.material.icons.filled.Savings
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
@ -26,17 +23,9 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.toUpperCase
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.unit.*
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import mg.dot.feufaro.data.GridTUOData
import mg.dot.feufaro.solfa.TUOEditState
import mg.dot.feufaro.solfa.TimeUnitObject
@ -168,23 +157,47 @@ fun TUODetailDialog(
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
(0..3).forEach { voice ->
val currentNote = notes[voice] ?: ""
val isValid = validateMusicalInput(currentNote, templateFragment)
val tooltipState = rememberTooltipState(isPersistent = false)
Row(
) {
MyTextEditField(
value = notes[voice] ?: "",
customFontSize = 14.sp,
color = Color.White,
customPadding = 8.dp,
customBrush = SolidColor(Color.White),
isEditable = isEditable,
isAddable = canAdd,
funTransform = ::transformMusicalInput,
onValueChng = { newValue ->
notes[voice] = newValue
}
)
TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
tooltip = {
if (!isValid) {
PlainTooltip(
containerColor = Color(0xFFF59E0B),
contentColor = Color.Black
) {
Text("Veuillez suivre ce format: $templateFragment")
}
}
},
state = tooltipState
) {
MyTextEditField(
value = currentNote ?: "",
customFontSize = 14.sp,
color = Color.White,
customPadding = 8.dp,
customBrush = SolidColor(Color.White),
isEditable = isEditable,
isAddable = canAdd,
isWarn = !isValid,
funTransform = ::transformMusicalInput,
onValueChng = { newValue ->
notes[voice] = newValue
}
)
}
}
LaunchedEffect(isValid, currentNote) {
if (!isValid && currentNote.isNotEmpty()) {
tooltipState.show()
} else {
tooltipState.dismiss()
}
}
}
}
@ -302,6 +315,7 @@ fun MyTextEditField(
customBrush: Brush,
isEditable: Boolean,
isAddable: Boolean,
isWarn: Boolean? = false,
funTransform: ((String) -> String)? = null,
onValueChng: (String) -> Unit
) {
@ -328,7 +342,62 @@ fun MyTextEditField(
modifier = Modifier
.fillMaxWidth()
.background(Color.Black.copy(alpha = 0.3f), RoundedCornerShape(4.dp))
.border(
if (isWarn!!) 1.dp else 0.dp,
if (isWarn!!) Color(0xFFF59E0B) else Color.Transparent,
shape = RoundedCornerShape(4.dp)
)
.padding(customPadding),
cursorBrush = customBrush
)
}
fun validateMusicalInput(input: String, template: String): Boolean {
if (template.isEmpty()) return true
val cleanInput = input.replace("(", "").replace(")", "").replace(" ", "")
val cleanTemplate = template.replace("(", "").replace(")", "")
val regexPattern = buildString {
cleanTemplate.forEachIndexed { index, char ->
when (char) {
in 'A'..'Y' -> {
append("(?:―|di|ri|fi|si|ta|[drmfslt])[¹²³₁₂₃]*")
}
'z' -> {
append(" ?")
}
'.' -> append("")
',' -> append(",")
'-' -> {
val prev = if (index > 0) cleanTemplate[index - 1] else null
val next = if (index < cleanTemplate.lastIndex) cleanTemplate[index + 1] else null
if (prev == '.' && next == ',') {
append("?[\\s]*")
} else {
append("")
}
}
else -> append(Regex.escape(char.toString()))
}
}
}
val regex = Regex("^$regexPattern$")
val isMatched = regex.matches(cleanInput)
/*println("""
DÉBOGAGE VALIDATION
Template Origine : $template
Regex Générée : ^$regexPattern$
Input Reçu : "$input"
Résultat : ${if (isMatched) "VALIDE (OUI)" else "INVALIDE (NON)"}
""".trimIndent())*/
return isMatched
}

View file

@ -15,7 +15,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBackIos
import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Close
@ -576,7 +575,6 @@ fun LazyVerticalGridTUO(
selectedTUO = null
}
var showFullChord by remember { mutableStateOf(false) }
val editMode by sharedScreenModel.modeEditor.collectAsState()
Column(
@ -760,63 +758,6 @@ fun LazyVerticalGridTUO(
}
}
}
/*Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
measureTUOs.forEachIndexed { indexInMeasure, tuo ->
val chordMap = (1..4).mapNotNull { voice ->
val noteStr = tuo.tuNotes.getOrNull(voice)?.toString()
if (noteStr != null) voice to noteStr else null
}.toMap()
val degreeName = remember(chordMap) {
if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeDegree(chordMap) else null
}
val chordName = remember(chordMap) {
if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeChordName(chordMap, sharedScreenModel.songKey.value) else null
}
Column(
modifier = Modifier
.width(gridWidthDp / gridColumnCount)
) {
// 1. Définir une plage de tailles de police
val maxFontSize = 13.sp
val minFontSize = 8.sp
var currentFontSize by remember { mutableStateOf(maxFontSize) }
TextButton(
onClick = { showFullChord = !showFullChord },
contentPadding = PaddingValues(0.dp),
modifier = Modifier.height(24.dp).fillMaxWidth() // fillMaxWidth pour occuper toute la colonne
) {
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
val textToDisplay = if (showFullChord) (chordName ?: "") else (degreeName ?: "")
Text(
text = textToDisplay,
onTextLayout = { textLayoutResult ->
if (textLayoutResult.hasVisualOverflow && currentFontSize > minFontSize) {
currentFontSize = (currentFontSize.value * 0.9f).sp
}
},
softWrap = false, // Empêche le retour à la ligne
maxLines = 1,
overflow = TextOverflow.Visible,
style = TextStyle(
color = if (showFullChord) Color(0xFF1B5E20) else Color(0xFF0D47A1),
fontWeight = FontWeight.Bold,
fontSize = currentFontSize // Utilisation de la taille dynamique[cite: 1]
)
)
}
}
}
}
}*/
Row(modifier = Modifier.fillMaxWidth()) {
measureTUOs.forEachIndexed { indexInMeasure, oneTUO ->
val globalIndex = (measureIndex * gridColumnCount) + indexInMeasure
@ -837,11 +778,7 @@ fun LazyVerticalGridTUO(
.combinedClickable(
interactionSource = interactionSource,
indication = LocalIndication.current,
onClick = {
if(!editMode) {
sharedScreenModel.seekToGrid(globalIndex)
}
}
onClick = { }
)
.pointerInput(globalIndex) {
detectTapGestures(
@ -851,6 +788,8 @@ fun LazyVerticalGridTUO(
selectedTUO = oneTUO
selectedIndex = globalIndex
showContextualMenu = true
} else {
sharedScreenModel.seekToGrid(globalIndex)
}
}
)
@ -908,18 +847,7 @@ fun LazyVerticalGridTUO(
},*/
notesByVoice = (0..3).associate { i ->
val rawNote = oneTUO.tuNotes.getOrNull(i + 1)?.toString() ?: ""
val fixedNote =
if (!expectedTemplate) {
rawNote
} else {
val count = rawNote.count()
if (/*count < 2 && */!rawNote.contains("")) {
rawNote + "•―"
} else {
rawNote
}
}
val fixedNote = autoFixNote(rawNote, template)
i to fixedNote
},
lyricsByStanza = (1..currentStanza).associate { s ->
@ -1300,3 +1228,49 @@ fun EditorActionButtons(onUndo: () -> Unit, onBuild: () -> Unit, onSave: () -> U
IconButton(onClick = onClose) { Icon(Icons.Default.Close, null, tint = Color.Red) }
}
}
fun autoFixNote(rawNote: String, template: String): String {
if (rawNote.isEmpty() || rawNote == "_") return rawNote
val noteRegex = Regex("([drmfsltDRFSTw][ia]?|―)[0-9'¹²³⁴₁₂₃₄,]*")
val actualNotes = noteRegex.findAll(rawNote).map { it.value }.toList()
if (rawNote.contains("") || rawNote.contains("") || rawNote.contains(",")) {
val templateNotesCount = template.count { it in 'A'..'Y' }
if (actualNotes.size >= templateNotesCount) return rawNote
}
val result = StringBuilder()
var noteIdx = 0
for (i in template.indices) {
val char = template[i]
when {
char in 'A'..'Y' -> {
if (noteIdx < actualNotes.size) {
result.append(actualNotes[noteIdx])
noteIdx++
} else {
result.append("")
}
}
char == '-' -> {
val prev = if (i > 0) template[i - 1] else null
val next = if (i < template.lastIndex) template[i + 1] else null
if (prev == '.' && next == ',') result.append(" ") else result.append("")
}
char == 'z' -> result.append(" ")
char == '.' -> {
val currentText = result.toString()
if (!currentText.endsWith("")) result.append("")
}
char == ',' -> {
val currentText = result.toString()
if (!currentText.endsWith(",")) result.append(",")
}
char == '(' -> result.append("(")
char == ')' -> result.append(")")
}
}
return result.toString()
}

View file

@ -649,7 +649,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
_editModeState.value = false
_midiMarkersList.value = emptyList()
_tuoTimestamps.value = emptyList()
updateSearchTxt("")
_searchTitle.value = ""
tempTimeUnitObjectList.clear()
resetGridCount()
}