Compare commits
No commits in common. "65c56db6557621f6fefacdcd5e4c186f916cf94e" and "38b6e17008e84c162ea025a18e0087ad83e8f14c" have entirely different histories.
65c56db655
...
38b6e17008
5 changed files with 172 additions and 221 deletions
|
|
@ -1,9 +1,15 @@
|
||||||
package mg.dot.feufaro.solfa
|
package mg.dot.feufaro.solfa
|
||||||
|
|
||||||
import SharedScreenModel
|
import SharedScreenModel
|
||||||
import kotlinx.coroutines.*
|
import androidx.compose.runtime.remember
|
||||||
|
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
|
||||||
|
|
@ -444,7 +450,11 @@ 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 updatedNotes = updateSourceLines(lines, templatArray, targetIdx, editState)
|
val originalBlock = templatArray[targetIdx]
|
||||||
|
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 != "") {
|
||||||
|
|
@ -513,10 +523,11 @@ 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, editState)
|
updateNotesInLines(lines, fragments, targetIdx, deleteCount, editState)
|
||||||
/* MODIF LYRICS */
|
/* MODIF LYRICS */
|
||||||
//updateLyricsInLines(lines, fragments, targetIdx, deleteCount, editState)
|
//updateLyricsInLines(lines, fragments, targetIdx, deleteCount, editState)
|
||||||
return lines
|
return lines
|
||||||
|
|
@ -621,6 +632,7 @@ 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
|
||||||
|
|
@ -635,7 +647,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.replace(Regex("\\s+"), ""))
|
val noteExpanded = expandNotes(noteBody)
|
||||||
|
|
||||||
// println("Notes===>$noteExpanded")
|
// println("Notes===>$noteExpanded")
|
||||||
val newNot = notesByVoice[voiceNum] ?: ""
|
val newNot = notesByVoice[voiceNum] ?: ""
|
||||||
|
|
@ -654,11 +666,10 @@ 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 }
|
|
||||||
.filter { it != "." }
|
val allTokens = regex.findAll(noteExpanded).map { it.value }.toList()
|
||||||
.toList()
|
|
||||||
|
|
||||||
val targetPointer = getNotePointer(fragments, targetIdx, currentLine)
|
val targetPointer = getNotePointer(fragments, targetIdx, currentLine)
|
||||||
|
|
||||||
|
|
@ -682,7 +693,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
|
||||||
|
|
@ -690,62 +701,65 @@ 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
|
||||||
|
|
||||||
//println("Insertition Multiples : $newNot")
|
val (tokenBase, tokenLevel) = parseNoteAndOctave(originalToken)
|
||||||
val rawUserTokens = regex.findAll(revertMusicalInput(newNot)).map { it.value }.toList()
|
newBase + formatOctave(tokenLevel + octaveDiff)
|
||||||
/** 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 {
|
||||||
rawUserTokens
|
// SI y a '#'
|
||||||
}
|
val anchorMatch = Regex("#([drmfsltDRFST])[',]*").find(noteBody)
|
||||||
//val newUserTokens = regex.findAll(revertMusicalInput(newNot)).map { it.value }.toList()
|
val anchorLevel = if (anchorMatch != null) {
|
||||||
val oldUserTokens = regex.findAll(revertMusicalInput(oldNot)).map { it.value }.toList()
|
val anchorStr = anchorMatch.value
|
||||||
|
anchorStr.count { it == '\'' } - anchorStr.count { it == ',' }
|
||||||
|
} else 0
|
||||||
|
|
||||||
val firstNewNote = newUserTokens.firstOrNull { it.matches(Regex("[drmfsltDRFSTzw].*")) } ?: ""
|
val newNotCleaned = revertMusicalInput(newNot)
|
||||||
val firstOldNote = oldUserTokens.firstOrNull { it.matches(Regex("[drmfsltDRFSTzw].*")) } ?: ""
|
val oldNotCleaned = revertMusicalInput(oldNot)
|
||||||
|
|
||||||
val (_, newLvl) = parseNoteAndOctave(firstNewNote)
|
val (newBase, newLevel) = parseNoteAndOctave(newNotCleaned)
|
||||||
val (_, oldLvl) = parseNoteAndOctave(firstOldNote)
|
val (oldBase, oldLevel) = parseNoteAndOctave(oldNotCleaned)
|
||||||
val octaveDiff = newLvl - oldLvl
|
|
||||||
|
|
||||||
|
val sourceLevel = newLevel - anchorLevel
|
||||||
|
val formattedOctave = formatOctave(sourceLevel)
|
||||||
|
val finalPureNew = newBase + formattedOctave
|
||||||
|
|
||||||
val oldTokensForThisFragment = regex.findAll(revertMusicalInput(oldNot))
|
val pureOldBase = oldBase + formatOctave(oldLevel - anchorLevel)
|
||||||
.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 ->
|
//
|
||||||
val processedToken = if (ut.matches(Regex("[drmfsltDRFSTzw].*"))) {
|
if (finalPureNew.startsWith(pureOldBase) && pureOldBase.isNotEmpty()) {
|
||||||
val (fileBase, fileLvl) = parseNoteAndOctave(allTokens[i])
|
val addedPart = finalPureNew.substring(pureOldBase.length)
|
||||||
val (uBase, _) = parseNoteAndOctave(ut)
|
originalToken + addedPart
|
||||||
uBase + formatOctave(fileLvl + octaveDiff)
|
|
||||||
} else {
|
} else {
|
||||||
ut
|
finalPureNew
|
||||||
}
|
|
||||||
// println("j'aoute $processedToken")
|
|
||||||
|
|
||||||
resultTokens.add(processedToken)
|
|
||||||
|
|
||||||
if (processedToken.matches(Regex("[drmfsltDRFSTzw].*|[-―]"))) {
|
|
||||||
println(String.format("NEW | %-10s | INSERT | %-12d | REPLACE", processedToken, currentLogicalIdx))
|
|
||||||
currentLogicalIdx++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var skipped = 0
|
// println(
|
||||||
while (skipped < dynamicDeleteCount && i < allTokens.size) {
|
// String.format(
|
||||||
val nextT = allTokens[i]
|
// "%-5s | %-10s | %-8s | %-12s | %-10s",
|
||||||
if (!nextT.contains("#") && nextT != "/" && nextT != "(" && nextT != ")") {
|
// "NEW", replacement, "INSERT", currentLogicalIdx, "REPLACE ($originalToken)"
|
||||||
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(nextT) // Garder les ancres si on est en mode ancre
|
resultTokens.add(nextToken)
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
currentLogicalIdx += deleteCount
|
||||||
} else {
|
} else {
|
||||||
// AFFICHAGE NOTE NORMALE
|
// AFFICHAGE NOTE NORMALE
|
||||||
// println(
|
// println(
|
||||||
|
|
@ -790,7 +804,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,7 +1,6 @@
|
||||||
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
|
||||||
|
|
@ -12,10 +11,14 @@ 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
|
||||||
|
|
@ -23,9 +26,17 @@ 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.unit.*
|
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.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
|
||||||
|
|
||||||
|
|
@ -157,47 +168,23 @@ 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(
|
MyTextEditField(
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
value = notes[voice] ?: "",
|
||||||
tooltip = {
|
customFontSize = 14.sp,
|
||||||
if (!isValid) {
|
color = Color.White,
|
||||||
PlainTooltip(
|
customPadding = 8.dp,
|
||||||
containerColor = Color(0xFFF59E0B),
|
customBrush = SolidColor(Color.White),
|
||||||
contentColor = Color.Black
|
isEditable = isEditable,
|
||||||
) {
|
isAddable = canAdd,
|
||||||
Text("Veuillez suivre ce format: $templateFragment")
|
funTransform = ::transformMusicalInput,
|
||||||
}
|
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -315,7 +302,6 @@ 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
|
||||||
) {
|
) {
|
||||||
|
|
@ -342,62 +328,7 @@ 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,6 +15,7 @@ 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
|
||||||
|
|
@ -575,6 +576,7 @@ 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(
|
||||||
|
|
@ -758,6 +760,63 @@ 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
|
||||||
|
|
@ -778,7 +837,11 @@ 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(
|
||||||
|
|
@ -788,8 +851,6 @@ fun LazyVerticalGridTUO(
|
||||||
selectedTUO = oneTUO
|
selectedTUO = oneTUO
|
||||||
selectedIndex = globalIndex
|
selectedIndex = globalIndex
|
||||||
showContextualMenu = true
|
showContextualMenu = true
|
||||||
} else {
|
|
||||||
sharedScreenModel.seekToGrid(globalIndex)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -847,7 +908,18 @@ 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 = autoFixNote(rawNote, template)
|
val fixedNote =
|
||||||
|
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 ->
|
||||||
|
|
@ -1227,50 +1299,4 @@ fun EditorActionButtons(onUndo: () -> Unit, onBuild: () -> Unit, onSave: () -> U
|
||||||
IconButton(onClick = onSave) { Icon(Icons.Default.Save, null, tint = Color(0xFF4CAF50)) }
|
IconButton(onClick = onSave) { Icon(Icons.Default.Save, null, tint = Color(0xFF4CAF50)) }
|
||||||
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,20 +106,6 @@ 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) {
|
||||||
|
|
@ -260,13 +246,7 @@ fun MainScreenWithDrawer(
|
||||||
) {
|
) {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
//sharedScreenModel.
|
||||||
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()
|
||||||
_searchTitle.value = ""
|
updateSearchTxt("")
|
||||||
tempTimeUnitObjectList.clear()
|
tempTimeUnitObjectList.clear()
|
||||||
resetGridCount()
|
resetGridCount()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue