Warn user if notes was not correspond to template on edit notes & autocomplete note on ui
This commit is contained in:
parent
cadb2a6b3f
commit
68dd6216b2
3 changed files with 148 additions and 105 deletions
|
|
@ -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(
|
||||
|
||||
) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
if (!isValid) {
|
||||
PlainTooltip(
|
||||
containerColor = Color(0xFFF59E0B),
|
||||
contentColor = Color.Black
|
||||
) {
|
||||
Text("Veuillez suivre ce format: $templateFragment")
|
||||
}
|
||||
}
|
||||
},
|
||||
state = tooltipState
|
||||
) {
|
||||
MyTextEditField(
|
||||
value = notes[voice] ?: "",
|
||||
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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue