Compare commits
3 commits
38b6e17008
...
65c56db655
| Author | SHA1 | Date | |
|---|---|---|---|
| 65c56db655 | |||
| 68dd6216b2 | |||
| cadb2a6b3f |
5 changed files with 221 additions and 172 deletions
|
|
@ -1,15 +1,9 @@
|
||||||
package mg.dot.feufaro.solfa
|
package mg.dot.feufaro.solfa
|
||||||
|
|
||||||
import SharedScreenModel
|
import SharedScreenModel
|
||||||
import androidx.compose.runtime.remember
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import mg.dot.feufaro.FileRepository
|
import mg.dot.feufaro.FileRepository
|
||||||
import mg.dot.feufaro.SaveSettings
|
import mg.dot.feufaro.SaveSettings
|
||||||
import mg.dot.feufaro.data.GridTUOData
|
|
||||||
import mg.dot.feufaro.launchFilePicker
|
import mg.dot.feufaro.launchFilePicker
|
||||||
import mg.dot.feufaro.midi.MidiPitch
|
import mg.dot.feufaro.midi.MidiPitch
|
||||||
import mg.dot.feufaro.midi.MidiWriterKotlin
|
import mg.dot.feufaro.midi.MidiWriterKotlin
|
||||||
|
|
@ -450,11 +444,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
MODIF NOTES N1 N2 N3 N4
|
MODIF NOTES N1 N2 N3 N4
|
||||||
*/
|
*/
|
||||||
// println("taille tA: ${templatArray.size} et le trgt $targetIdx")
|
// println("taille tA: ${templatArray.size} et le trgt $targetIdx")
|
||||||
val originalBlock = templatArray[targetIdx]
|
val updatedNotes = updateSourceLines(lines, templatArray, targetIdx, editState)
|
||||||
val markerRegex = Regex("""\$\{.*?\}|\$\w+""")
|
|
||||||
val deleteCount = originalBlock.replace(markerRegex, "").count { it.isLetter() }
|
|
||||||
|
|
||||||
val updatedNotes = updateSourceLines(lines, templatArray, targetIdx, deleteCount, editState)
|
|
||||||
|
|
||||||
// RESTAURATION DES TEMPLATES
|
// RESTAURATION DES TEMPLATES
|
||||||
if (templateString != "") {
|
if (templateString != "") {
|
||||||
|
|
@ -523,11 +513,10 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
lines: MutableList<String>,
|
lines: MutableList<String>,
|
||||||
fragments: List<String>,
|
fragments: List<String>,
|
||||||
targetIdx: Int,
|
targetIdx: Int,
|
||||||
deleteCount: Int,
|
|
||||||
editState: TUOEditState
|
editState: TUOEditState
|
||||||
): MutableList<String> {
|
): MutableList<String> {
|
||||||
/* Notes N1 N2 N3 N4 */
|
/* Notes N1 N2 N3 N4 */
|
||||||
updateNotesInLines(lines, fragments, targetIdx, deleteCount, editState)
|
updateNotesInLines(lines, fragments, targetIdx, editState)
|
||||||
/* MODIF LYRICS */
|
/* MODIF LYRICS */
|
||||||
//updateLyricsInLines(lines, fragments, targetIdx, deleteCount, editState)
|
//updateLyricsInLines(lines, fragments, targetIdx, deleteCount, editState)
|
||||||
return lines
|
return lines
|
||||||
|
|
@ -632,7 +621,6 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
lines: MutableList<String>,
|
lines: MutableList<String>,
|
||||||
fragments: List<String>,
|
fragments: List<String>,
|
||||||
targetIdx: Int,
|
targetIdx: Int,
|
||||||
deleteCount: Int,
|
|
||||||
editState: TUOEditState
|
editState: TUOEditState
|
||||||
) {
|
) {
|
||||||
val notesByVoice = editState.notesByVoice
|
val notesByVoice = editState.notesByVoice
|
||||||
|
|
@ -647,7 +635,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
val prefix = currentLine.substringBefore(":") + ":"
|
val prefix = currentLine.substringBefore(":") + ":"
|
||||||
val noteBody = currentLine.substringAfter(":")
|
val noteBody = currentLine.substringAfter(":")
|
||||||
val hasAnchor = noteBody.contains("#")
|
val hasAnchor = noteBody.contains("#")
|
||||||
val noteExpanded = expandNotes(noteBody)
|
val noteExpanded = expandNotes(noteBody.replace(Regex("\\s+"), ""))
|
||||||
|
|
||||||
// println("Notes===>$noteExpanded")
|
// println("Notes===>$noteExpanded")
|
||||||
val newNot = notesByVoice[voiceNum] ?: ""
|
val newNot = notesByVoice[voiceNum] ?: ""
|
||||||
|
|
@ -666,10 +654,11 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
val cleanNewNote = revertMusicalInput(newNote)
|
val cleanNewNote = revertMusicalInput(newNote)
|
||||||
|
|
||||||
|
|
||||||
val regex = Regex("#\\S[',]*|\\s#\\S[',]*|/|\\(|\\)|[drmfsltDRFSTzw][0-9'¹²³⁴⁵₁₂₃₄₅,]*|[-.]")
|
val regex = Regex("#\\S[',]*|\\s#\\S[',]*|/|\\(|\\)|[drmfsltDRFSTzw][0-9'¹²³⁴⁵₁₂₃₄₅,]*|[-.―•]")
|
||||||
|
val allTokens = regex.findAll(noteExpanded)
|
||||||
|
.map { it.value }
|
||||||
val allTokens = regex.findAll(noteExpanded).map { it.value }.toList()
|
.filter { it != "." }
|
||||||
|
.toList()
|
||||||
|
|
||||||
val targetPointer = getNotePointer(fragments, targetIdx, currentLine)
|
val targetPointer = getNotePointer(fragments, targetIdx, currentLine)
|
||||||
|
|
||||||
|
|
@ -693,7 +682,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"
|
val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"
|
||||||
|
|
||||||
if (isStructural) {
|
if (isStructural) {
|
||||||
// println(String.format("%-5d | %-10s | %-8s | %-12s | %-10s", i, token, "STRUCT", "-", "Keep"))
|
println(String.format("%-5d | %-10s | %-8s | %-12s | %-10s", i, token, "STRUCT", "-", "Keep"))
|
||||||
resultTokens.add(token)
|
resultTokens.add(token)
|
||||||
i++
|
i++
|
||||||
continue
|
continue
|
||||||
|
|
@ -701,65 +690,62 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
|
|
||||||
if (currentLogicalIdx == targetPointer) {
|
if (currentLogicalIdx == targetPointer) {
|
||||||
val originalToken = allTokens[i]
|
val originalToken = allTokens[i]
|
||||||
var replacement = if (!hasAnchor) {
|
|
||||||
val (newBase, newLevel) = parseNoteAndOctave(revertMusicalInput(newNot))
|
|
||||||
val (oldBase, oldLevel) = parseNoteAndOctave(revertMusicalInput(oldNot))
|
|
||||||
val octaveDiff = newLevel - oldLevel
|
|
||||||
|
|
||||||
val (tokenBase, tokenLevel) = parseNoteAndOctave(originalToken)
|
//println("Insertition Multiples : $newNot")
|
||||||
newBase + formatOctave(tokenLevel + octaveDiff)
|
val rawUserTokens = regex.findAll(revertMusicalInput(newNot)).map { it.value }.toList()
|
||||||
|
/** Si y a '-' dans template inutile d' ajouter au notes */
|
||||||
|
val hasDashInTemplate = editState.templateFragment.contains("-")
|
||||||
|
val newUserTokens = if (hasDashInTemplate) {
|
||||||
|
rawUserTokens.filter { it.matches(Regex("[drmfsltDRFSTzw].*")) }
|
||||||
} else {
|
} else {
|
||||||
// SI y a '#'
|
rawUserTokens
|
||||||
val anchorMatch = Regex("#([drmfsltDRFST])[',]*").find(noteBody)
|
}
|
||||||
val anchorLevel = if (anchorMatch != null) {
|
//val newUserTokens = regex.findAll(revertMusicalInput(newNot)).map { it.value }.toList()
|
||||||
val anchorStr = anchorMatch.value
|
val oldUserTokens = regex.findAll(revertMusicalInput(oldNot)).map { it.value }.toList()
|
||||||
anchorStr.count { it == '\'' } - anchorStr.count { it == ',' }
|
|
||||||
} else 0
|
|
||||||
|
|
||||||
val newNotCleaned = revertMusicalInput(newNot)
|
val firstNewNote = newUserTokens.firstOrNull { it.matches(Regex("[drmfsltDRFSTzw].*")) } ?: ""
|
||||||
val oldNotCleaned = revertMusicalInput(oldNot)
|
val firstOldNote = oldUserTokens.firstOrNull { it.matches(Regex("[drmfsltDRFSTzw].*")) } ?: ""
|
||||||
|
|
||||||
val (newBase, newLevel) = parseNoteAndOctave(newNotCleaned)
|
val (_, newLvl) = parseNoteAndOctave(firstNewNote)
|
||||||
val (oldBase, oldLevel) = parseNoteAndOctave(oldNotCleaned)
|
val (_, oldLvl) = parseNoteAndOctave(firstOldNote)
|
||||||
|
val octaveDiff = newLvl - oldLvl
|
||||||
|
|
||||||
val sourceLevel = newLevel - anchorLevel
|
|
||||||
val formattedOctave = formatOctave(sourceLevel)
|
|
||||||
val finalPureNew = newBase + formattedOctave
|
|
||||||
|
|
||||||
val pureOldBase = oldBase + formatOctave(oldLevel - anchorLevel)
|
val oldTokensForThisFragment = regex.findAll(revertMusicalInput(oldNot))
|
||||||
|
.map { it.value }
|
||||||
|
.filter { it.matches(Regex("[drmfsltDRFSTzw].*|[-―]")) }
|
||||||
|
.toList()
|
||||||
|
val dynamicDeleteCount = oldTokensForThisFragment.size
|
||||||
|
|
||||||
// println("Ancre niveau: $anchorLevel | UI Level: $newLevel -> Source Level: $sourceLevel")
|
|
||||||
//
|
newUserTokens.forEach { ut ->
|
||||||
if (finalPureNew.startsWith(pureOldBase) && pureOldBase.isNotEmpty()) {
|
val processedToken = if (ut.matches(Regex("[drmfsltDRFSTzw].*"))) {
|
||||||
val addedPart = finalPureNew.substring(pureOldBase.length)
|
val (fileBase, fileLvl) = parseNoteAndOctave(allTokens[i])
|
||||||
originalToken + addedPart
|
val (uBase, _) = parseNoteAndOctave(ut)
|
||||||
|
uBase + formatOctave(fileLvl + octaveDiff)
|
||||||
} else {
|
} else {
|
||||||
finalPureNew
|
ut
|
||||||
|
}
|
||||||
|
// println("j'aoute $processedToken")
|
||||||
|
|
||||||
|
resultTokens.add(processedToken)
|
||||||
|
|
||||||
|
if (processedToken.matches(Regex("[drmfsltDRFSTzw].*|[-―]"))) {
|
||||||
|
println(String.format("NEW | %-10s | INSERT | %-12d | REPLACE", processedToken, currentLogicalIdx))
|
||||||
|
currentLogicalIdx++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// println(
|
var skipped = 0
|
||||||
// String.format(
|
while (skipped < dynamicDeleteCount && i < allTokens.size) {
|
||||||
// "%-5s | %-10s | %-8s | %-12s | %-10s",
|
val nextT = allTokens[i]
|
||||||
// "NEW", replacement, "INSERT", currentLogicalIdx, "REPLACE ($originalToken)"
|
if (!nextT.contains("#") && nextT != "/" && nextT != "(" && nextT != ")") {
|
||||||
// )
|
skipped++
|
||||||
// )
|
|
||||||
resultTokens.add(replacement)
|
|
||||||
|
|
||||||
var notesSkipped = 0
|
|
||||||
while (notesSkipped < deleteCount && i < allTokens.size) {
|
|
||||||
val nextToken = allTokens[i]
|
|
||||||
val isNextStructural =
|
|
||||||
nextToken.contains("#") || nextToken == "/" || nextToken == "(" || nextToken == ")"
|
|
||||||
|
|
||||||
if (!isNextStructural) {
|
|
||||||
notesSkipped++
|
|
||||||
} else if (hasAnchor) {
|
} else if (hasAnchor) {
|
||||||
resultTokens.add(nextToken)
|
resultTokens.add(nextT) // Garder les ancres si on est en mode ancre
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
currentLogicalIdx += deleteCount
|
|
||||||
} else {
|
} else {
|
||||||
// AFFICHAGE NOTE NORMALE
|
// AFFICHAGE NOTE NORMALE
|
||||||
// println(
|
// println(
|
||||||
|
|
@ -804,7 +790,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
private fun revertMusicalInput(input: String): String {
|
private fun revertMusicalInput(input: String): String {
|
||||||
return input
|
return input
|
||||||
.replace("• ,", "")
|
.replace("• ,", "")
|
||||||
.replace("―•", "")
|
.replace("•", "")
|
||||||
.replace("―", "-")
|
.replace("―", "-")
|
||||||
.replace("•", "")
|
.replace("•", "")
|
||||||
.replace("³", "'''")
|
.replace("³", "'''")
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
||||||
) {
|
) {
|
||||||
MyTextEditField(
|
TooltipBox(
|
||||||
value = notes[voice] ?: "",
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
customFontSize = 14.sp,
|
tooltip = {
|
||||||
color = Color.White,
|
if (!isValid) {
|
||||||
customPadding = 8.dp,
|
PlainTooltip(
|
||||||
customBrush = SolidColor(Color.White),
|
containerColor = Color(0xFFF59E0B),
|
||||||
isEditable = isEditable,
|
contentColor = Color.Black
|
||||||
isAddable = canAdd,
|
) {
|
||||||
funTransform = ::transformMusicalInput,
|
Text("Veuillez suivre ce format: $templateFragment")
|
||||||
onValueChng = { newValue ->
|
}
|
||||||
notes[voice] = newValue
|
}
|
||||||
}
|
},
|
||||||
)
|
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,
|
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()
|
||||||
|
}
|
||||||
|
|
@ -106,6 +106,20 @@ fun MainScreenWithDrawer(
|
||||||
val currentActiveFilePath = sharedScreenModel.activeFilePath.value
|
val currentActiveFilePath = sharedScreenModel.activeFilePath.value
|
||||||
val fileName = currentActiveFilePath.substringAfterLast('/')
|
val fileName = currentActiveFilePath.substringAfterLast('/')
|
||||||
|
|
||||||
|
var sourceTitle = sharedScreenModel.songTitle.value
|
||||||
|
val originalPath = remember(sourceTitle) {
|
||||||
|
if (!currentActiveFilePath.contains("_tmp") && !currentActiveFilePath.contains("/tmp/")) {
|
||||||
|
currentActiveFilePath
|
||||||
|
} else {
|
||||||
|
currentActiveFilePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceContent = sharedScreenModel.fileContent.value ?: ""
|
||||||
|
var codeContent by remember { mutableStateOf(sourceContent?: "") }
|
||||||
|
LaunchedEffect(sourceContent) {
|
||||||
|
codeContent = sourceContent
|
||||||
|
}
|
||||||
val saveLauncher = rememberFileSaveLauncher { chosenPath ->
|
val saveLauncher = rememberFileSaveLauncher { chosenPath ->
|
||||||
if (chosenPath != null) {
|
if (chosenPath != null) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
|
|
@ -246,7 +260,13 @@ fun MainScreenWithDrawer(
|
||||||
) {
|
) {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
//sharedScreenModel.
|
scope.launch {
|
||||||
|
codeContent = sourceContent
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
solfaScreenModel.loadExternalFile(originalPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
}, modifier = Modifier.alpha(0.45f)
|
}, modifier = Modifier.alpha(0.45f)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|
|
||||||
|
|
@ -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