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
|
package mg.dot.feufaro
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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.Add
|
||||||
import androidx.compose.material.icons.filled.Build
|
import androidx.compose.material.icons.filled.Build
|
||||||
import androidx.compose.material.icons.filled.Clear
|
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.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
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.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.toUpperCase
|
import androidx.compose.ui.unit.*
|
||||||
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.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
import androidx.compose.ui.window.PopupProperties
|
import androidx.compose.ui.window.PopupProperties
|
||||||
import mg.dot.feufaro.data.GridTUOData
|
|
||||||
import mg.dot.feufaro.solfa.TUOEditState
|
import mg.dot.feufaro.solfa.TUOEditState
|
||||||
import mg.dot.feufaro.solfa.TimeUnitObject
|
import mg.dot.feufaro.solfa.TimeUnitObject
|
||||||
|
|
||||||
|
|
@ -168,23 +157,47 @@ fun TUODetailDialog(
|
||||||
verticalArrangement = Arrangement.spacedBy(5.dp)
|
verticalArrangement = Arrangement.spacedBy(5.dp)
|
||||||
) {
|
) {
|
||||||
(0..3).forEach { voice ->
|
(0..3).forEach { voice ->
|
||||||
|
val currentNote = notes[voice] ?: ""
|
||||||
|
val isValid = validateMusicalInput(currentNote, templateFragment)
|
||||||
|
val tooltipState = rememberTooltipState(isPersistent = false)
|
||||||
Row(
|
Row(
|
||||||
|
) {
|
||||||
|
TooltipBox(
|
||||||
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
tooltip = {
|
||||||
|
if (!isValid) {
|
||||||
|
PlainTooltip(
|
||||||
|
containerColor = Color(0xFFF59E0B),
|
||||||
|
contentColor = Color.Black
|
||||||
|
) {
|
||||||
|
Text("Veuillez suivre ce format: $templateFragment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state = tooltipState
|
||||||
) {
|
) {
|
||||||
MyTextEditField(
|
MyTextEditField(
|
||||||
value = notes[voice] ?: "",
|
value = currentNote ?: "",
|
||||||
customFontSize = 14.sp,
|
customFontSize = 14.sp,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
customPadding = 8.dp,
|
customPadding = 8.dp,
|
||||||
customBrush = SolidColor(Color.White),
|
customBrush = SolidColor(Color.White),
|
||||||
isEditable = isEditable,
|
isEditable = isEditable,
|
||||||
isAddable = canAdd,
|
isAddable = canAdd,
|
||||||
|
isWarn = !isValid,
|
||||||
funTransform = ::transformMusicalInput,
|
funTransform = ::transformMusicalInput,
|
||||||
onValueChng = { newValue ->
|
onValueChng = { newValue ->
|
||||||
notes[voice] = newValue
|
notes[voice] = newValue
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(isValid, currentNote) {
|
||||||
|
if (!isValid && currentNote.isNotEmpty()) {
|
||||||
|
tooltipState.show()
|
||||||
|
} else {
|
||||||
|
tooltipState.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -302,6 +315,7 @@ fun MyTextEditField(
|
||||||
customBrush: Brush,
|
customBrush: Brush,
|
||||||
isEditable: Boolean,
|
isEditable: Boolean,
|
||||||
isAddable: Boolean,
|
isAddable: Boolean,
|
||||||
|
isWarn: Boolean? = false,
|
||||||
funTransform: ((String) -> String)? = null,
|
funTransform: ((String) -> String)? = null,
|
||||||
onValueChng: (String) -> Unit
|
onValueChng: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
@ -328,7 +342,62 @@ fun MyTextEditField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(Color.Black.copy(alpha = 0.3f), RoundedCornerShape(4.dp))
|
.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),
|
.padding(customPadding),
|
||||||
cursorBrush = customBrush
|
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.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
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.automirrored.filled.Undo
|
||||||
import androidx.compose.material.icons.filled.Build
|
import androidx.compose.material.icons.filled.Build
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
|
@ -576,7 +575,6 @@ fun LazyVerticalGridTUO(
|
||||||
selectedTUO = null
|
selectedTUO = null
|
||||||
}
|
}
|
||||||
|
|
||||||
var showFullChord by remember { mutableStateOf(false) }
|
|
||||||
val editMode by sharedScreenModel.modeEditor.collectAsState()
|
val editMode by sharedScreenModel.modeEditor.collectAsState()
|
||||||
|
|
||||||
Column(
|
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()) {
|
Row(modifier = Modifier.fillMaxWidth()) {
|
||||||
measureTUOs.forEachIndexed { indexInMeasure, oneTUO ->
|
measureTUOs.forEachIndexed { indexInMeasure, oneTUO ->
|
||||||
val globalIndex = (measureIndex * gridColumnCount) + indexInMeasure
|
val globalIndex = (measureIndex * gridColumnCount) + indexInMeasure
|
||||||
|
|
@ -837,11 +778,7 @@ fun LazyVerticalGridTUO(
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = LocalIndication.current,
|
indication = LocalIndication.current,
|
||||||
onClick = {
|
onClick = { }
|
||||||
if(!editMode) {
|
|
||||||
sharedScreenModel.seekToGrid(globalIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.pointerInput(globalIndex) {
|
.pointerInput(globalIndex) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
|
|
@ -851,6 +788,8 @@ fun LazyVerticalGridTUO(
|
||||||
selectedTUO = oneTUO
|
selectedTUO = oneTUO
|
||||||
selectedIndex = globalIndex
|
selectedIndex = globalIndex
|
||||||
showContextualMenu = true
|
showContextualMenu = true
|
||||||
|
} else {
|
||||||
|
sharedScreenModel.seekToGrid(globalIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -908,18 +847,7 @@ fun LazyVerticalGridTUO(
|
||||||
},*/
|
},*/
|
||||||
notesByVoice = (0..3).associate { i ->
|
notesByVoice = (0..3).associate { i ->
|
||||||
val rawNote = oneTUO.tuNotes.getOrNull(i + 1)?.toString() ?: ""
|
val rawNote = oneTUO.tuNotes.getOrNull(i + 1)?.toString() ?: ""
|
||||||
val fixedNote =
|
val fixedNote = autoFixNote(rawNote, template)
|
||||||
if (!expectedTemplate) {
|
|
||||||
rawNote
|
|
||||||
} else {
|
|
||||||
val count = rawNote.count()
|
|
||||||
|
|
||||||
if (/*count < 2 && */!rawNote.contains("•")) {
|
|
||||||
rawNote + "•―"
|
|
||||||
} else {
|
|
||||||
rawNote
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i to fixedNote
|
i to fixedNote
|
||||||
},
|
},
|
||||||
lyricsByStanza = (1..currentStanza).associate { s ->
|
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) }
|
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
|
_editModeState.value = false
|
||||||
_midiMarkersList.value = emptyList()
|
_midiMarkersList.value = emptyList()
|
||||||
_tuoTimestamps.value = emptyList()
|
_tuoTimestamps.value = emptyList()
|
||||||
updateSearchTxt("")
|
_searchTitle.value = ""
|
||||||
tempTimeUnitObjectList.clear()
|
tempTimeUnitObjectList.clear()
|
||||||
resetGridCount()
|
resetGridCount()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue