Implement solfa partition creation screen & options
This commit is contained in:
parent
1bbf91e29c
commit
bd936fe3b8
11 changed files with 611 additions and 97 deletions
|
|
@ -9,7 +9,6 @@ import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Code
|
import androidx.compose.material.icons.filled.Code
|
||||||
import androidx.compose.material.icons.filled.Edit
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconToggleButton
|
import androidx.compose.material3.IconToggleButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -173,10 +172,10 @@ object ScreenSolfa : Screen {
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
val isEditMode = sharedScreenModel.editModeState.value
|
val isEditMode = sharedScreenModel.sourceModeState.value
|
||||||
IconToggleButton(
|
IconToggleButton(
|
||||||
checked = isEditMode,
|
checked = isEditMode,
|
||||||
onCheckedChange = { sharedScreenModel.toggleEditMode() },
|
onCheckedChange = { sharedScreenModel.toggleSourceMode() },
|
||||||
modifier = Modifier.size(40.dp)
|
modifier = Modifier.size(40.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ fun GridActionMenu(
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
modifier = Modifier.width(50.dp),
|
||||||
imageVector = Icons.Default.Add,
|
imageVector = Icons.Default.Add,
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
|
|
@ -44,6 +45,7 @@ fun GridActionMenu(
|
||||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.secondary.copy(alpha = 0.5f))
|
border = BorderStroke(1.dp, MaterialTheme.colorScheme.secondary.copy(alpha = 0.5f))
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
modifier = Modifier.width(50.dp),
|
||||||
imageVector = Icons.Default.Delete,
|
imageVector = Icons.Default.Delete,
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import mg.dot.feufaro.getGlobalTemplate
|
||||||
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
|
||||||
|
import mg.dot.feufaro.viewmodel.PartitionMetadata
|
||||||
import mg.dot.feufaro.transformLyricsInput
|
import mg.dot.feufaro.transformLyricsInput
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
@ -713,7 +714,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
/** Si y a '-' dans template inutile d' ajouter au notes */
|
/** Si y a '-' dans template inutile d' ajouter au notes */
|
||||||
val hasDashInTemplate = editState.templateFragment.contains("-")
|
val hasDashInTemplate = editState.templateFragment.contains("-")
|
||||||
val newUserTokens = if (hasDashInTemplate) {
|
val newUserTokens = if (hasDashInTemplate) {
|
||||||
rawUserTokens.filter { it.matches(Regex("[drmfsltDRFSTzw].*")) }
|
rawUserTokens.filter { it.matches(Regex("[drmfsltDRFSTzw\\(\\)].*")) }
|
||||||
} else {
|
} else {
|
||||||
rawUserTokens
|
rawUserTokens
|
||||||
}
|
}
|
||||||
|
|
@ -760,26 +761,29 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
// println("Suppression de la note $ut (gérée par le saut du fragment d'origine)")
|
// println("Suppression de la note $ut (gérée par le saut du fragment d'origine)")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val processedToken = if (ut.matches(Regex("[drmfsltDRFSTzw].*"))) {
|
if (ut == "(" || ut == ")") {
|
||||||
val (fileBase, fileLvl) = parseNoteAndOctave(allTokens[i])
|
resultTokens.add(ut) // Insertion directe de la parenthèse sans incrémenter currentLogicalIdx
|
||||||
val (uBase, _) = parseNoteAndOctave(ut)
|
|
||||||
uBase + formatOctave(fileLvl + octaveDiff)
|
|
||||||
} else {
|
} else {
|
||||||
ut
|
val processedToken = if (ut.matches(Regex("[drmfsltDRFSTzw].*"))) {
|
||||||
}
|
val (fileBase, fileLvl) = parseNoteAndOctave(allTokens[i])
|
||||||
// println("j'aoute $processedToken")
|
val (uBase, _) = parseNoteAndOctave(ut)
|
||||||
|
uBase + formatOctave(fileLvl + octaveDiff)
|
||||||
|
} else {
|
||||||
|
ut
|
||||||
|
}
|
||||||
|
// println("j'aoute $processedToken")
|
||||||
|
|
||||||
resultTokens.add(processedToken)
|
resultTokens.add(processedToken)
|
||||||
|
if (processedToken.matches(Regex("[drmfsltDRFSTzw].*|[-―]"))) {
|
||||||
if (processedToken.matches(Regex("[drmfsltDRFSTzw].*|[-―]"))) {
|
/*println(
|
||||||
// println(
|
String.format(
|
||||||
// String.format(
|
"NEW | %-10s | INSERT | %-12d | REPLACE",
|
||||||
// "NEW | %-10s | INSERT | %-12d | REPLACE",
|
processedToken,
|
||||||
// processedToken,
|
currentLogicalIdx
|
||||||
// currentLogicalIdx
|
)
|
||||||
// )
|
)*/
|
||||||
// )
|
currentLogicalIdx++
|
||||||
currentLogicalIdx++
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1281,8 +1285,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
.replace("si", "S")
|
.replace("si", "S")
|
||||||
.replace("ta", "T")
|
.replace("ta", "T")
|
||||||
|
|
||||||
.replace("(","")
|
/*.replace("(","")
|
||||||
.replace(")","")
|
.replace(")","")*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseNoteLineToArray(noteLine: String): List<String> {
|
private fun parseNoteLineToArray(noteLine: String): List<String> {
|
||||||
|
|
@ -2607,4 +2611,77 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
}
|
}
|
||||||
return currentPointer
|
return currentPointer
|
||||||
}
|
}
|
||||||
}
|
private data class MeasurePattern(val groupSize: Int, val groupsPerSegment: Int, val segments: Int)
|
||||||
|
|
||||||
|
private val measurePatterns = mapOf(
|
||||||
|
"2" to MeasurePattern(groupSize = 4, groupsPerSegment = 2, segments = 2),//16
|
||||||
|
"3" to MeasurePattern(groupSize = 3, groupsPerSegment = 2, segments = 2),//12
|
||||||
|
"4" to MeasurePattern(groupSize = 4, groupsPerSegment = 4, segments = 2),// 32
|
||||||
|
"6" to MeasurePattern(groupSize = 6, groupsPerSegment = 1, segments = 2),// 12
|
||||||
|
"12" to MeasurePattern(groupSize = 12, groupsPerSegment = 1, segments = 2), //24
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun buildTemplatePattern(measure: String): String {
|
||||||
|
val measur = measure.substringBefore('/').trim()
|
||||||
|
val pattern = measurePatterns[measur.trim()] ?: measurePatterns.getValue("4")
|
||||||
|
val group = "4".repeat(pattern.groupSize)
|
||||||
|
val segment = List(pattern.groupsPerSegment) { group }.joinToString(" ")
|
||||||
|
return List(pattern.segments) { segment }.joinToString(" / ")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun createNewSolfa(partitionMetadata: PartitionMetadata) {
|
||||||
|
val lyricsPrefix = if(partitionMetadata.language == "Anglais") {
|
||||||
|
'E'
|
||||||
|
} else if(partitionMetadata.language == "Malagasy") {
|
||||||
|
'Y'
|
||||||
|
} else {
|
||||||
|
'Y'
|
||||||
|
}
|
||||||
|
val partUrl = if (partitionMetadata.url.isNotBlank() && partitionMetadata.url != "https://") {
|
||||||
|
"|u:${partitionMetadata.url}"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
val templatePattern = buildTemplatePattern(partitionMetadata.measure)
|
||||||
|
val notesPattern = templatePattern.replace(Regex("[0-9]"), "d")
|
||||||
|
|
||||||
|
val soflaContent = listOf(
|
||||||
|
"M0:|t:${partitionMetadata.title}|" +
|
||||||
|
"c:${partitionMetadata.key}|" +
|
||||||
|
"r:${partitionMetadata.rhythm}|" +
|
||||||
|
"a:${partitionMetadata.author}|" +
|
||||||
|
"h:${partitionMetadata.composer}|" +
|
||||||
|
"m:${partitionMetadata.measure}" +
|
||||||
|
partUrl,
|
||||||
|
"U0:z0:$templatePattern",
|
||||||
|
"N1: $notesPattern",
|
||||||
|
"N2: $notesPattern",
|
||||||
|
"N3: $notesPattern",
|
||||||
|
"N4: $notesPattern",
|
||||||
|
"${lyricsPrefix}1:",
|
||||||
|
).joinToString("\n")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val safeFileName = partitionMetadata.title
|
||||||
|
.ifBlank { "solfa" }
|
||||||
|
.replace(Regex("[\\\\/:*?\"<>|]"), "_")
|
||||||
|
|
||||||
|
val tempFile = File(System.getProperty("java.io.tmpdir"), "$safeFileName.txt")
|
||||||
|
tempFile.deleteOnExit()
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
tempFile.writeText(soflaContent)
|
||||||
|
println("Fichier temporaire créé : ${tempFile.absolutePath}")
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
justBuild(tempFile.absolutePath, soflaContent)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Erreur lors de la création du fichier temporaire : ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1042,57 +1042,18 @@ public fun getGlobalTemplate(editState: TUOEditState): String {
|
||||||
val filteredNotes = notes
|
val filteredNotes = notes
|
||||||
.replace(Solfa.REGEX_NOTE_TOADD, "$3$4")
|
.replace(Solfa.REGEX_NOTE_TOADD, "$3$4")
|
||||||
.replace("<", "")
|
.replace("<", "")
|
||||||
val tpl = notesToTemplate(filteredNotes)
|
val tpl = notesToTemplate(filteredNotes).replace(Regex("[()]"), "")
|
||||||
// println("Voix $voiceIdx | Notes: '$filteredNotes' -> Template: '$tpl'")
|
// println("Voix $voiceIdx | Notes: '$filteredNotes' -> Template: '$tpl'")
|
||||||
tpl
|
tpl
|
||||||
}
|
}
|
||||||
|
|
||||||
val rythmicTemplates = voiceTemplates.map { tpl -> tpl.replace(Regex("[()]"), "") }
|
val rythmicTemplates = voiceTemplates
|
||||||
val maxLen = rythmicTemplates.maxOfOrNull { it.length } ?: 1
|
val maxLen = rythmicTemplates.maxOfOrNull { it.length } ?: 1
|
||||||
// println("Longueur rythmique max détectée: $maxLen")
|
// println("Longueur rythmique max détectée: $maxLen")
|
||||||
|
|
||||||
val openBefore = BooleanArray(maxLen)
|
|
||||||
val closeBefore = BooleanArray(maxLen)
|
|
||||||
val openAfter = BooleanArray(maxLen)
|
|
||||||
val closeAfter = BooleanArray(maxLen)
|
|
||||||
|
|
||||||
var openAtTheEnd = false
|
|
||||||
var closeAtTheEnd = false
|
|
||||||
|
|
||||||
voiceTemplates.forEach { tpl ->
|
|
||||||
var rythmicIdx = 0
|
|
||||||
for (i in tpl.indices) {
|
|
||||||
val char = tpl[i]
|
|
||||||
if (char == '(' || char == ')') {
|
|
||||||
val textAfter = tpl.substring(i + 1)
|
|
||||||
val hasRythmicAfter = textAfter.any { it != '(' && it != ')' }
|
|
||||||
|
|
||||||
if (hasRythmicAfter) {
|
|
||||||
if (rythmicIdx < maxLen) {
|
|
||||||
if (char == '(') openBefore[rythmicIdx] = true
|
|
||||||
if (char == ')') closeBefore[rythmicIdx] = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (rythmicIdx > 0) {
|
|
||||||
if (char == '(') openAfter[rythmicIdx - 1] = true
|
|
||||||
if (char == ')') closeAfter[rythmicIdx - 1] = true
|
|
||||||
} else {
|
|
||||||
if (char == '(') openAtTheEnd = true
|
|
||||||
if (char == ')') closeAtTheEnd = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rythmicIdx++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val result = StringBuilder()
|
val result = StringBuilder()
|
||||||
|
|
||||||
for (i in 0 until maxLen) {
|
for (i in 0 until maxLen) {
|
||||||
if (openBefore[i]) result.append('(')
|
|
||||||
if (closeBefore[i]) result.append(')')
|
|
||||||
|
|
||||||
val chars = rythmicTemplates.mapNotNull { it.getOrNull(i) }.toSet()
|
val chars = rythmicTemplates.mapNotNull { it.getOrNull(i) }.toSet()
|
||||||
val previousChars = if(i>0) rythmicTemplates.mapNotNull { it.getOrNull(i-1) }.toSet() else "".toSet()
|
val previousChars = if(i>0) rythmicTemplates.mapNotNull { it.getOrNull(i-1) }.toSet() else "".toSet()
|
||||||
val activeChars = rythmicTemplates.mapNotNull { it.getOrNull(i) }
|
val activeChars = rythmicTemplates.mapNotNull { it.getOrNull(i) }
|
||||||
|
|
@ -1116,14 +1077,8 @@ public fun getGlobalTemplate(editState: TUOEditState): String {
|
||||||
result.append(combinedChar)
|
result.append(combinedChar)
|
||||||
}
|
}
|
||||||
// println("all = ${result.toString()}")
|
// println("all = ${result.toString()}")
|
||||||
|
|
||||||
if (openAfter[i]) result.append('(')
|
|
||||||
if (closeAfter[i]) result.append(')')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openAtTheEnd) result.append('(')
|
|
||||||
if (closeAtTheEnd) result.append(')')
|
|
||||||
|
|
||||||
// println("Template Global Final: '${result.toString()}'")
|
// println("Template Global Final: '${result.toString()}'")
|
||||||
// println("----------------------------------\n")
|
// println("----------------------------------\n")
|
||||||
return result.toString()
|
return result.toString()
|
||||||
|
|
@ -1149,8 +1104,9 @@ private fun notesToTemplate(notes: String): String {
|
||||||
for (match in matches) {
|
for (match in matches) {
|
||||||
val value = match.value
|
val value = match.value
|
||||||
when {
|
when {
|
||||||
value.matches(Regex("[.,()-]")) -> template.append(value)
|
value.matches(Regex("[.,-]")) -> template.append(value)
|
||||||
value.trim().isEmpty() -> template.append(" ")
|
value.trim().isEmpty() -> template.append(" ")
|
||||||
|
value.matches(Regex("[()]")) -> template.append("")
|
||||||
else -> template.append("D")
|
else -> template.append("D")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,8 @@ 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.Undo
|
import androidx.compose.material.icons.automirrored.filled.Undo
|
||||||
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.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Delete
|
|
||||||
import androidx.compose.material.icons.filled.Description
|
import androidx.compose.material.icons.filled.Description
|
||||||
import androidx.compose.material.icons.filled.Save
|
import androidx.compose.material.icons.filled.Save
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
|
@ -58,6 +56,7 @@ import mg.dot.feufaro.FileRepository
|
||||||
import mg.dot.feufaro.TUODetailDialog
|
import mg.dot.feufaro.TUODetailDialog
|
||||||
import mg.dot.feufaro.data.GridTUOData
|
import mg.dot.feufaro.data.GridTUOData
|
||||||
import mg.dot.feufaro.getPathValidator
|
import mg.dot.feufaro.getPathValidator
|
||||||
|
import mg.dot.feufaro.getPlatform
|
||||||
import mg.dot.feufaro.ui.rememberFileSaveLauncher
|
import mg.dot.feufaro.ui.rememberFileSaveLauncher
|
||||||
import mg.dot.feufaro.viewmodel.MidiMarkers
|
import mg.dot.feufaro.viewmodel.MidiMarkers
|
||||||
import mg.dot.feufaro.viewmodel.SolfaScreenModel
|
import mg.dot.feufaro.viewmodel.SolfaScreenModel
|
||||||
|
|
@ -567,6 +566,8 @@ fun AutoResizingText(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyVerticalGridTUO(
|
fun LazyVerticalGridTUO(
|
||||||
viewModel: GridTUOData,
|
viewModel: GridTUOData,
|
||||||
|
|
@ -605,6 +606,9 @@ fun LazyVerticalGridTUO(
|
||||||
var toggleWithDegreeAndChord by remember { mutableStateOf (false) }
|
var toggleWithDegreeAndChord by remember { mutableStateOf (false) }
|
||||||
|
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
||||||
|
val platform = getPlatform()
|
||||||
|
val isAndroid = platform.name.startsWith("Android")
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -956,9 +960,10 @@ fun LazyVerticalGridTUO(
|
||||||
|
|
||||||
val isAbsoluteLastGrid = (globalIndex == tuoList.size - 2)
|
val isAbsoluteLastGrid = (globalIndex == tuoList.size - 2)
|
||||||
if (editMode && isAbsoluteLastGrid) {
|
if (editMode && isAbsoluteLastGrid) {
|
||||||
|
val lastGridwidth = if(isAndroid) (gridWidthDp / gridColumnCount)*2 else ((gridWidthDp / gridColumnCount)*11)/10
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(gridWidthDp / gridColumnCount)
|
.width(lastGridwidth)
|
||||||
.fillMaxHeight(),
|
.fillMaxHeight(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
|
|
@ -987,7 +992,18 @@ fun LazyVerticalGridTUO(
|
||||||
|
|
||||||
val hairPinState by sharedScreenModel.isPendingHairPinOnEdit.collectAsState()
|
val hairPinState by sharedScreenModel.isPendingHairPinOnEdit.collectAsState()
|
||||||
|
|
||||||
|
val tooltipState = rememberTooltipState(isPersistent = true)
|
||||||
|
|
||||||
|
val showTooltipWarn = hairPinState.isPending && hairPinState.gridIndex == globalIndex
|
||||||
|
LaunchedEffect(hairPinState.isPending) {
|
||||||
|
if (showTooltipWarn) {
|
||||||
|
tooltipState.show()
|
||||||
|
} else {
|
||||||
|
tooltipState.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
key(oneTUO.numBlock) {
|
key(oneTUO.numBlock) {
|
||||||
|
val content : @Composable () -> Unit = {
|
||||||
Box(modifier = Modifier
|
Box(modifier = Modifier
|
||||||
.width(gridWidthDp / gridColumnCount)
|
.width(gridWidthDp / gridColumnCount)
|
||||||
.then(
|
.then(
|
||||||
|
|
@ -1170,6 +1186,22 @@ fun LazyVerticalGridTUO(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (showTooltipWarn) {
|
||||||
|
TooltipBox(
|
||||||
|
state = tooltipState,
|
||||||
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
tooltip = {
|
||||||
|
PlainTooltip {
|
||||||
|
Text("Autre grille pour compléter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1361,7 +1393,7 @@ fun EditSourceCompose(
|
||||||
sharedScreenModel: SharedScreenModel,
|
sharedScreenModel: SharedScreenModel,
|
||||||
solfaScreenModel: SolfaScreenModel
|
solfaScreenModel: SolfaScreenModel
|
||||||
) {
|
) {
|
||||||
var isEditorVisible = sharedScreenModel.editModeState.value
|
var isEditorVisible = sharedScreenModel.sourceModeState.value
|
||||||
var sourcePath = sharedScreenModel.activeFilePath.value
|
var sourcePath = sharedScreenModel.activeFilePath.value
|
||||||
var sourceContent = sharedScreenModel.fileContent.value ?: ""
|
var sourceContent = sharedScreenModel.fileContent.value ?: ""
|
||||||
var sourceTitle = sharedScreenModel.songTitle.value
|
var sourceTitle = sharedScreenModel.songTitle.value
|
||||||
|
|
@ -1510,7 +1542,7 @@ fun EditSourceCompose(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClose = { sharedScreenModel.toggleEditMode() }
|
onClose = { sharedScreenModel.toggleSourceMode() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package mg.dot.feufaro.ui
|
||||||
import SharedScreenModel
|
import SharedScreenModel
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
|
@ -19,7 +18,6 @@ import androidx.compose.material.icons.automirrored.filled.Undo
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
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.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
|
|
@ -27,7 +25,6 @@ import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.input.pointer.PointerEventType
|
import androidx.compose.ui.input.pointer.PointerEventType
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
|
@ -127,8 +124,9 @@ fun MainScreenWithDrawer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val isEditMode by sharedScreenModel.modeEditor.collectAsState()
|
val isEditMode by sharedScreenModel.modeEditor.collectAsState()
|
||||||
LaunchedEffect(isEditMode) {
|
val createMode by sharedScreenModel.createMode.collectAsState()
|
||||||
if(isEditMode) {
|
LaunchedEffect(isEditMode || createMode) {
|
||||||
|
if(isEditMode || createMode) {
|
||||||
drawerState.close()
|
drawerState.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -778,11 +776,28 @@ fun MainScreenWithDrawer(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(createMode) {
|
||||||
|
NewPartition(
|
||||||
|
onCreate = { partMtdata ->
|
||||||
|
solfaScreenModel.newSolfa(partMtdata)
|
||||||
|
sharedScreenModel.toggleCreateMode(!createMode)
|
||||||
|
sharedScreenModel.toggleEditorMode(createMode)
|
||||||
|
if(!isAndroid) {
|
||||||
|
sharedScreenModel.toggleSourceMode()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = {
|
||||||
|
sharedScreenModel.toggleCreateMode(false)
|
||||||
|
},
|
||||||
|
isAndroid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var isHovered by remember { mutableStateOf(false) }
|
var isHovered by remember { mutableStateOf(false) }
|
||||||
val offsetX by animateDpAsState(targetValue = if (isHovered || isSearchActive) 0.dp else 15.dp)
|
val offsetX by animateDpAsState(targetValue = if (isHovered || isSearchActive) 0.dp else 15.dp)
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = !isEditMode && !isQrVisible,
|
visible = !isEditMode && !isQrVisible && !createMode,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.padding(top = 15.dp)
|
.padding(top = 15.dp)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,382 @@
|
||||||
|
package mg.dot.feufaro.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.MusicNote
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import mg.dot.feufaro.solfa.Transpose
|
||||||
|
import mg.dot.feufaro.viewmodel.PartitionMetadata
|
||||||
|
import mg.dot.feufaro.viewmodel.measureOptions
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun NewPartition(
|
||||||
|
onCreate: (PartitionMetadata) -> Unit,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
isAndroid: Boolean
|
||||||
|
) {
|
||||||
|
val CardBg = MaterialTheme.colorScheme.background
|
||||||
|
val FieldBg = MaterialTheme.colorScheme.surface
|
||||||
|
val BorderColor = MaterialTheme.colorScheme.outline
|
||||||
|
val TextPrimary = MaterialTheme.colorScheme.onSurface
|
||||||
|
val TextSecondary = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
val AccentBlue = MaterialTheme.colorScheme.primary
|
||||||
|
val IconBg = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f)
|
||||||
|
val ButtonBg = MaterialTheme.colorScheme.primary.copy(alpha = 0.18f)
|
||||||
|
|
||||||
|
|
||||||
|
var title by remember { mutableStateOf("") }
|
||||||
|
var author by remember { mutableStateOf("") }
|
||||||
|
var composer by remember { mutableStateOf("") }
|
||||||
|
var rhythm by remember { mutableStateOf("") }
|
||||||
|
var measure by remember { mutableStateOf("4/4") }
|
||||||
|
var key by remember { mutableStateOf("C") }
|
||||||
|
var url by remember { mutableStateOf("https://") }
|
||||||
|
var keyMenuExpanded by remember { mutableStateOf(false) }
|
||||||
|
var langMenuExpanded by remember { mutableStateOf(false) }
|
||||||
|
val availableKeys = remember { Transpose.keyToNumber.toList() }
|
||||||
|
var lang by remember { if(isAndroid) mutableStateOf("🇲🇬") else mutableStateOf("Malagasy") }
|
||||||
|
val availableLang = remember { if(isAndroid) listOf("\uD83C\uDDFA\uD83C\uDDF8", "🇲🇬") else listOf("Anglais", "Malagasy") }
|
||||||
|
|
||||||
|
val fieldColors = OutlinedTextFieldDefaults.colors(
|
||||||
|
focusedBorderColor = AccentBlue,
|
||||||
|
unfocusedBorderColor = BorderColor,
|
||||||
|
focusedTextColor = TextPrimary,
|
||||||
|
unfocusedTextColor = TextPrimary,
|
||||||
|
focusedLabelColor = TextSecondary,
|
||||||
|
unfocusedLabelColor = TextSecondary,
|
||||||
|
cursorColor = AccentBlue,
|
||||||
|
focusedContainerColor = FieldBg,
|
||||||
|
unfocusedContainerColor = FieldBg,
|
||||||
|
focusedPlaceholderColor = TextSecondary,
|
||||||
|
unfocusedPlaceholderColor = TextSecondary,
|
||||||
|
)
|
||||||
|
|
||||||
|
var measureMenuExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
properties = DialogProperties(
|
||||||
|
usePlatformDefaultWidth = false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(containerColor = CardBg),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(if(isAndroid) 0.9f else 0.55f)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTapGestures { }
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(24.dp),
|
||||||
|
border = BorderStroke(1.dp, BorderColor.copy(alpha = 0.2f)),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 16.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(20.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.background(IconBg, CircleShape),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.MusicNote,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = AccentBlue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(12.dp))
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
"Nouvelle partition",
|
||||||
|
color = TextPrimary,
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Renseigner les informations de la partition",
|
||||||
|
color = TextSecondary,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = title,
|
||||||
|
onValueChange = { title = it },
|
||||||
|
label = { Text("Titre") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = fieldColors,
|
||||||
|
textStyle = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = author,
|
||||||
|
onValueChange = { author = it },
|
||||||
|
label = { Text("Auteur") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = fieldColors,
|
||||||
|
textStyle = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = composer,
|
||||||
|
onValueChange = { composer = it },
|
||||||
|
label = { Text("Compositeur / arrangeur") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = fieldColors,
|
||||||
|
textStyle = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
ExposedDropdownMenuBox(
|
||||||
|
expanded = keyMenuExpanded,
|
||||||
|
onExpandedChange = { keyMenuExpanded = it },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = key,
|
||||||
|
onValueChange = {},
|
||||||
|
readOnly = true,
|
||||||
|
label = { Text("Tonalité") },
|
||||||
|
trailingIcon = {
|
||||||
|
ExposedDropdownMenuDefaults.TrailingIcon(expanded = keyMenuExpanded)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.menuAnchor(MenuAnchorType.PrimaryEditable)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = fieldColors
|
||||||
|
)
|
||||||
|
ExposedDropdownMenu(
|
||||||
|
expanded = keyMenuExpanded,
|
||||||
|
onDismissRequest = { keyMenuExpanded = false },
|
||||||
|
modifier = Modifier.background(CardBg)
|
||||||
|
) {
|
||||||
|
availableKeys.forEach { kKey ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(kKey, color = TextPrimary) },
|
||||||
|
onClick = {
|
||||||
|
key = kKey
|
||||||
|
keyMenuExpanded = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = rhythm,
|
||||||
|
onValueChange = { rhythm = it },
|
||||||
|
label = { Text("Mesure poétique") },
|
||||||
|
placeholder = { Text("8.7.8.7.D") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = fieldColors,
|
||||||
|
textStyle = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
ExposedDropdownMenuBox(
|
||||||
|
expanded = langMenuExpanded,
|
||||||
|
onExpandedChange = { langMenuExpanded = it },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = lang,
|
||||||
|
onValueChange = {},
|
||||||
|
readOnly = true,
|
||||||
|
label = { Text("Langue des paroles") },
|
||||||
|
trailingIcon = {
|
||||||
|
ExposedDropdownMenuDefaults.TrailingIcon(expanded = langMenuExpanded)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.menuAnchor(MenuAnchorType.PrimaryEditable)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = fieldColors
|
||||||
|
)
|
||||||
|
ExposedDropdownMenu(
|
||||||
|
expanded = langMenuExpanded,
|
||||||
|
onDismissRequest = { langMenuExpanded = false },
|
||||||
|
modifier = Modifier.background(CardBg)
|
||||||
|
) {
|
||||||
|
availableLang.forEach { klang ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(klang, color = TextPrimary) },
|
||||||
|
onClick = {
|
||||||
|
lang = klang
|
||||||
|
langMenuExpanded = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Mesure",
|
||||||
|
color = TextSecondary,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.padding(start = 4.dp)
|
||||||
|
)
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
measureOptions.forEach { (mVal, mStyle) ->
|
||||||
|
val isSelected = (measure == mVal)
|
||||||
|
val tooltipState = rememberTooltipState()
|
||||||
|
|
||||||
|
TooltipBox(
|
||||||
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
tooltip = {
|
||||||
|
PlainTooltip {
|
||||||
|
Text("$mStyle")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state = tooltipState
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = { measure = mVal },
|
||||||
|
modifier = Modifier
|
||||||
|
.width(70.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
color = if (isSelected) AccentBlue.copy(alpha = 0.12f) else FieldBg,
|
||||||
|
border = BorderStroke(
|
||||||
|
width = if (isSelected) 1.5.dp else 1.dp,
|
||||||
|
color = if (isSelected) AccentBlue else BorderColor
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = mVal,
|
||||||
|
color = if (isSelected) AccentBlue else TextPrimary,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal
|
||||||
|
)
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = null,
|
||||||
|
colors = RadioButtonDefaults.colors(
|
||||||
|
selectedColor = AccentBlue,
|
||||||
|
unselectedColor = TextSecondary
|
||||||
|
),
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = url,
|
||||||
|
onValueChange = { url = it },
|
||||||
|
label = { Text("Source (URL)") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = fieldColors,
|
||||||
|
textStyle = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onDismissRequest,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
border = BorderStroke(0.5.dp, BorderColor),
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(contentColor = TextSecondary),
|
||||||
|
shape = RoundedCornerShape(10.dp)
|
||||||
|
) {
|
||||||
|
Text("Annuler")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onCreate(
|
||||||
|
PartitionMetadata(
|
||||||
|
title = title,
|
||||||
|
author = author,
|
||||||
|
composer = composer,
|
||||||
|
rhythm = rhythm,
|
||||||
|
measure = measure,
|
||||||
|
key = key,
|
||||||
|
url = url,
|
||||||
|
language = if(lang == "\uD83C\uDDF2\uD83C\uDDEC") "Malagasy" else "Anglais"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
enabled = title.isNotBlank(),
|
||||||
|
modifier = Modifier.weight(2f),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = ButtonBg,
|
||||||
|
contentColor = AccentBlue,
|
||||||
|
disabledContainerColor = ButtonBg.copy(alpha = 0.4f),
|
||||||
|
disabledContentColor = AccentBlue.copy(alpha = 0.4f),
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(10.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Add,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(6.dp))
|
||||||
|
Text("Créer la partition")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package mg.dot.feufaro.ui
|
||||||
import SharedScreenModel
|
import SharedScreenModel
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
|
@ -13,14 +14,17 @@ import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ListAlt
|
import androidx.compose.material.icons.automirrored.filled.ListAlt
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.NoteAdd
|
||||||
import androidx.compose.material.icons.automirrored.filled.PlaylistPlay
|
import androidx.compose.material.icons.automirrored.filled.PlaylistPlay
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
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.clip
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
|
@ -97,16 +101,29 @@ fun SimpleDrawerContent(
|
||||||
label = "Analyse d\'accords"
|
label = "Analyse d\'accords"
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(5.dp),
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
IconButton(
|
OutlinedButton(
|
||||||
onClick = { },
|
onClick = { sharedScreenModel.toggleCreateMode(true) },
|
||||||
modifier = Modifier.background(Color(0xFF81C784), shape = CircleShape).size(40.dp)
|
shape = IconButtonDefaults.filledShape,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Add, contentDescription = null, tint = Color.White)
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Default.NoteAdd, contentDescription = null, tint = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Text("Ajouter", style = MaterialTheme.typography.labelSmall)
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Créer une partition",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package mg.dot.feufaro.viewmodel
|
||||||
|
|
||||||
|
data class PartitionMetadata(
|
||||||
|
val title: String,
|
||||||
|
val author: String,
|
||||||
|
val composer: String,
|
||||||
|
val rhythm: String,
|
||||||
|
val measure: String,
|
||||||
|
val url: String,
|
||||||
|
val key: String,
|
||||||
|
val language: String,
|
||||||
|
)
|
||||||
|
val measureOptions = listOf(
|
||||||
|
Pair("2/4", "Marches, polkas, chants folkloriques"),
|
||||||
|
Pair("4/4", "Gospel, louange, hymnes, jazz, classique"),
|
||||||
|
Pair("3/4", "Valse, hymnes, chorals, classique"),
|
||||||
|
Pair("6/8", "Ballades, gospel, traditionnelle"),
|
||||||
|
Pair("9/4", "Œuvres spéciales, contemporain"),
|
||||||
|
Pair("2/2", "Chorals, hymnes, musique sacrée"),
|
||||||
|
Pair("3/2", "Hymnes classiques, chorals lents"),
|
||||||
|
Pair("12/8", "Blues, gospel, soul, slow")
|
||||||
|
)
|
||||||
|
|
@ -231,8 +231,8 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
private val _isQRCodeVisible = mutableStateOf(false)
|
private val _isQRCodeVisible = mutableStateOf(false)
|
||||||
val isQRCodeVisible: State<Boolean> = _isQRCodeVisible
|
val isQRCodeVisible: State<Boolean> = _isQRCodeVisible
|
||||||
|
|
||||||
private val _editModeState = mutableStateOf(false)
|
private val _sourceModeState = mutableStateOf(false)
|
||||||
val editModeState: State<Boolean> = _editModeState
|
val sourceModeState: State<Boolean> = _sourceModeState
|
||||||
|
|
||||||
data class HairPinEditState(
|
data class HairPinEditState(
|
||||||
val isPending: Boolean = false,
|
val isPending: Boolean = false,
|
||||||
|
|
@ -251,6 +251,13 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
_modeEditor.value = enabled
|
_modeEditor.value = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val _createMode = MutableStateFlow(false)
|
||||||
|
val createMode: StateFlow<Boolean> = _createMode
|
||||||
|
|
||||||
|
fun toggleCreateMode(state: Boolean) {
|
||||||
|
_createMode.value = state
|
||||||
|
}
|
||||||
|
|
||||||
private val _harmonyView = MutableStateFlow(false)
|
private val _harmonyView = MutableStateFlow(false)
|
||||||
val harmonyView: StateFlow<Boolean> = _harmonyView
|
val harmonyView: StateFlow<Boolean> = _harmonyView
|
||||||
fun toggleHarmonyMode(enabled: Boolean) {
|
fun toggleHarmonyMode(enabled: Boolean) {
|
||||||
|
|
@ -304,8 +311,8 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
fun toggleQRCodeVisibility() {
|
fun toggleQRCodeVisibility() {
|
||||||
_isQRCodeVisible.value = !_isQRCodeVisible.value
|
_isQRCodeVisible.value = !_isQRCodeVisible.value
|
||||||
}
|
}
|
||||||
fun toggleEditMode() {
|
fun toggleSourceMode() {
|
||||||
_editModeState.value = !_editModeState.value
|
_sourceModeState.value = !_sourceModeState.value
|
||||||
}
|
}
|
||||||
val qrCodeContent: State<String?>
|
val qrCodeContent: State<String?>
|
||||||
get() = mutableStateOf(
|
get() = mutableStateOf(
|
||||||
|
|
@ -604,7 +611,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
_currentPos.value = 0f
|
_currentPos.value = 0f
|
||||||
_dcDone.value = false
|
_dcDone.value = false
|
||||||
_dsDone.value = false
|
_dsDone.value = false
|
||||||
_editModeState.value = false
|
_sourceModeState.value = false
|
||||||
setTranspositionInterval(0)
|
setTranspositionInterval(0)
|
||||||
try {
|
try {
|
||||||
val midiFileName = fileRepository.getFileName(newMidiFile)
|
val midiFileName = fileRepository.getFileName(newMidiFile)
|
||||||
|
|
@ -705,7 +712,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
_showMidiCtrl.value = false
|
_showMidiCtrl.value = false
|
||||||
_expandedFAB.value = false
|
_expandedFAB.value = false
|
||||||
_showSearchMenu.value = false
|
_showSearchMenu.value = false
|
||||||
_editModeState.value = false
|
_sourceModeState.value = false
|
||||||
_harmonyView.value = false
|
_harmonyView.value = false
|
||||||
setTranspositionInterval(0)
|
setTranspositionInterval(0)
|
||||||
setSilentDurBefr(0)
|
setSilentDurBefr(0)
|
||||||
|
|
|
||||||
|
|
@ -50,4 +50,9 @@ class SolfaScreenModel(
|
||||||
solfa.saveTUOEdit(editState)
|
solfa.saveTUOEdit(editState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun newSolfa(metadata: PartitionMetadata) {
|
||||||
|
screenModelScope.launch(Dispatchers.IO) {
|
||||||
|
solfa.createNewSolfa(metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue