Add custom keyboards for search & edit notes
This commit is contained in:
parent
9da97209c9
commit
340f72855f
8 changed files with 1613 additions and 835 deletions
|
|
@ -15,12 +15,14 @@ import androidx.compose.material.icons.filled.RemoveCircleOutline
|
||||||
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.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
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.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -141,6 +143,7 @@ private val markerGroups = listOf(
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
fun MarkerPopup(
|
fun MarkerPopup(
|
||||||
menuPosition: IntOffset,
|
menuPosition: IntOffset,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
|
|
@ -155,7 +158,8 @@ fun MarkerPopup(
|
||||||
properties = PopupProperties(
|
properties = PopupProperties(
|
||||||
focusable = true,
|
focusable = true,
|
||||||
dismissOnBackPress = true,
|
dismissOnBackPress = true,
|
||||||
dismissOnClickOutside = true
|
dismissOnClickOutside = true,
|
||||||
|
usePlatformDefaultWidth = false
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
package mg.dot.feufaro
|
package mg.dot.feufaro
|
||||||
|
|
||||||
import SharedScreenModel
|
import SharedScreenModel
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||||
|
|
@ -23,6 +20,7 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.layout.positionInParent
|
import androidx.compose.ui.layout.positionInParent
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.text.TextRange
|
import androidx.compose.ui.text.TextRange
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
|
@ -37,7 +35,7 @@ import mg.dot.feufaro.solfa.MarkerPopup
|
||||||
import mg.dot.feufaro.solfa.Solfa
|
import mg.dot.feufaro.solfa.Solfa
|
||||||
import mg.dot.feufaro.solfa.TUOEditState
|
import mg.dot.feufaro.solfa.TUOEditState
|
||||||
import mg.dot.feufaro.solfa.TimeUnitObject
|
import mg.dot.feufaro.solfa.TimeUnitObject
|
||||||
import mg.dot.feufaro.solfa.extractAnchorFromNoteLine
|
import mg.dot.feufaro.ui.LocalSolfaKeyboardState
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -61,6 +59,18 @@ fun TUODetailDialog(
|
||||||
putAll(editState.notesByVoice)
|
putAll(editState.notesByVoice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val isAndroid = remember { getPlatform().name.startsWith("Android") }
|
||||||
|
val keyboardState = LocalSolfaKeyboardState.current
|
||||||
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
var selectedVoiceIndex by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
keyboardState.isDialogActive = true
|
||||||
|
onDispose {
|
||||||
|
keyboardState.hide()
|
||||||
|
keyboardState.isDialogActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
val originalNotes = mutableStateMapOf<Int, String>().apply {
|
val originalNotes = mutableStateMapOf<Int, String>().apply {
|
||||||
putAll(editState.notesByVoice)
|
putAll(editState.notesByVoice)
|
||||||
}
|
}
|
||||||
|
|
@ -114,20 +124,103 @@ fun TUODetailDialog(
|
||||||
var markerButtonPos by remember {
|
var markerButtonPos by remember {
|
||||||
mutableStateOf(IntOffset.Zero)
|
mutableStateOf(IntOffset.Zero)
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(selectedVoiceIndex, notes[selectedVoiceIndex]) {
|
||||||
|
if (keyboardState.isVisible) {
|
||||||
|
val currentRawText = notes[selectedVoiceIndex] ?: ""
|
||||||
|
keyboardState.currentText = transformMusicalInput(currentRawText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun attachKeyboardToVoice(voiceIndex: Int) {
|
||||||
|
selectedVoiceIndex = voiceIndex
|
||||||
|
if(isAndroid) {
|
||||||
|
keyboardController?.hide()
|
||||||
|
|
||||||
|
val rawInitialText = notes[voiceIndex] ?: ""
|
||||||
|
val formattedInitialText = transformMusicalInput(rawInitialText)
|
||||||
|
keyboardState.currentText = formattedInitialText
|
||||||
|
|
||||||
|
keyboardState.show(
|
||||||
|
initialVoice = voiceIndex,
|
||||||
|
initialValue = formattedInitialText,
|
||||||
|
onNote = { noteToAdd ->
|
||||||
|
val currentText = notes[voiceIndex] ?: ""
|
||||||
|
val newText = currentText + noteToAdd
|
||||||
|
val formattedText = transformMusicalInput(newText)
|
||||||
|
notes[voiceIndex] = formattedText
|
||||||
|
keyboardState.currentText = formattedText
|
||||||
|
},
|
||||||
|
onDelete = {
|
||||||
|
val currentText = notes[voiceIndex] ?: ""
|
||||||
|
if (currentText.isNotEmpty()) {
|
||||||
|
val octaveRegex = Regex("['`,₁₂₃₄₅¹²³⁴⁵]+$")
|
||||||
|
|
||||||
|
val newText = if (octaveRegex.containsMatchIn(currentText)) {
|
||||||
|
currentText.replace(octaveRegex, "")
|
||||||
|
} else {
|
||||||
|
currentText.dropLast(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
notes[voiceIndex] = transformMusicalInput(newText)
|
||||||
|
keyboardState.currentText = transformMusicalInput(newText)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSymbol = { symbol ->
|
||||||
|
val currentText = notes[voiceIndex] ?: ""
|
||||||
|
val newText = currentText + symbol
|
||||||
|
notes[voiceIndex] = transformMusicalInput(newText)
|
||||||
|
keyboardState.currentText = transformMusicalInput(newText)
|
||||||
|
},
|
||||||
|
onClose = {
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
onBuild = {
|
||||||
|
val finalLyricsMap = editState.lyricsByStanza.toMutableMap()
|
||||||
|
finalLyricsMap.putAll(editedLyricsMap)
|
||||||
|
if (!finalLyricsMap.containsKey(currentStanza)) {
|
||||||
|
finalLyricsMap[currentStanza] = lyricsLines.getOrNull(0)?.trim() ?: ""
|
||||||
|
}
|
||||||
|
val state = TUOEditState(
|
||||||
|
tuoIndex = globalIndex,
|
||||||
|
notesByVoice = notes.toMap(),
|
||||||
|
originalNotes = originalNotes.toMap(),
|
||||||
|
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
|
||||||
|
lyricsByStanza = finalLyricsMap,
|
||||||
|
templateFragment = templateFragment,
|
||||||
|
marker = if(marker.isEmpty()) "_" else marker,
|
||||||
|
originalSep = initialSep,
|
||||||
|
sep = newSep
|
||||||
|
)
|
||||||
|
onSave(state)
|
||||||
|
},
|
||||||
|
onTab = {
|
||||||
|
val nextVoice = (selectedVoiceIndex + 1) % 4
|
||||||
|
attachKeyboardToVoice(nextVoice)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Popup(
|
Popup(
|
||||||
offset = menuPosition,
|
offset = menuPosition,
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = {
|
||||||
|
if(!isAndroid && !keyboardState.isVisible) {
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
},
|
||||||
properties = PopupProperties(
|
properties = PopupProperties(
|
||||||
focusable = true,
|
focusable = if(isAndroid) false else true,
|
||||||
dismissOnBackPress = true,
|
dismissOnBackPress = !isAndroid || !keyboardState.isVisible,
|
||||||
dismissOnClickOutside = true
|
dismissOnClickOutside = !isAndroid || !keyboardState.isVisible
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.widthIn(max=125.dp)
|
.widthIn(max=125.dp)
|
||||||
.heightIn(max = 400.dp),
|
.heightIn(max = 400.dp)
|
||||||
|
.imePadding()
|
||||||
|
.navigationBarsPadding(),
|
||||||
shape = MaterialTheme.shapes.small,
|
shape = MaterialTheme.shapes.small,
|
||||||
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(0.75f),
|
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(0.75f),
|
||||||
) {
|
) {
|
||||||
|
|
@ -500,48 +593,76 @@ fun TUODetailDialog(
|
||||||
val validation = validateMusicalInput(currentNote, templateFragment)
|
val validation = validateMusicalInput(currentNote, templateFragment)
|
||||||
val voiceAnchor = anchorsByVoice[voice]?.replace("#","")
|
val voiceAnchor = anchorsByVoice[voice]?.replace("#","")
|
||||||
val hasAnchor = !voiceAnchor.isNullOrBlank()
|
val hasAnchor = !voiceAnchor.isNullOrBlank()
|
||||||
|
val isSelected = selectedVoiceIndex == voice && keyboardState.isVisible
|
||||||
|
|
||||||
Box(modifier = Modifier.wrapContentSize()
|
Box(modifier = Modifier
|
||||||
|
//.wrapContentSize()
|
||||||
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
MyTextEditField(
|
if(!isAndroid) {
|
||||||
value = currentNote ?: "",
|
MyTextEditField(
|
||||||
customFontSize = 14.sp,
|
value = currentNote ?: "",
|
||||||
color = Color.White,
|
customFontSize = 14.sp,
|
||||||
customPadding = 8.dp,
|
color = Color.White,
|
||||||
customBrush = SolidColor(Color.White),
|
customPadding = 8.dp,
|
||||||
isEditable = isEditable,
|
customBrush = SolidColor(Color.White),
|
||||||
isAddable = canAdd,
|
isEditable = isEditable,
|
||||||
isWarn = !validation.isValid,
|
isAddable = canAdd,
|
||||||
funTransform = ::transformMusicalInput,
|
isWarn = !validation.isValid,
|
||||||
onValueChng = { newValue ->
|
funTransform = ::transformMusicalInput,
|
||||||
notes[voice] = newValue
|
onValueChng = { newValue ->
|
||||||
}
|
notes[voice] = newValue
|
||||||
)
|
}
|
||||||
if (hasAnchor) {
|
)
|
||||||
Spacer(modifier = Modifier.width(2.dp))
|
if (hasAnchor) {
|
||||||
|
Spacer(modifier = Modifier.width(2.dp))
|
||||||
|
|
||||||
TooltipBox(
|
TooltipBox(
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
tooltip = {
|
tooltip = {
|
||||||
PlainTooltip(containerColor = Color.DarkGray, contentColor = Color.White) {
|
PlainTooltip(containerColor = Color.DarkGray, contentColor = Color.White) {
|
||||||
Text("Ancrée sur ${transformMusicalInput(voiceAnchor)}", fontSize = 11.sp)
|
Text("Ancrée sur ${transformMusicalInput(voiceAnchor)}", fontSize = 11.sp)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
state = rememberTooltipState()
|
state = rememberTooltipState()
|
||||||
) {
|
|
||||||
Surface(
|
|
||||||
shape = RoundedCornerShape(3.dp),
|
|
||||||
color = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.8f),
|
|
||||||
modifier = Modifier.padding(start = 2.dp)
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Surface(
|
||||||
text = "⚓",
|
shape = RoundedCornerShape(3.dp),
|
||||||
fontSize = 10.sp,
|
color = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.8f),
|
||||||
color = Color.Black,
|
modifier = Modifier.padding(start = 2.dp)
|
||||||
modifier = Modifier.padding(horizontal = 2.dp, vertical = 1.dp)
|
) {
|
||||||
)
|
Text(
|
||||||
|
text = "⚓",
|
||||||
|
fontSize = 10.sp,
|
||||||
|
color = Color.Black,
|
||||||
|
modifier = Modifier.padding(horizontal = 2.dp, vertical = 1.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
SolfaTextEditField(
|
||||||
|
value = currentNote ?: "",
|
||||||
|
customFontSize = 14.sp,
|
||||||
|
color = if (isSelected) MaterialTheme.colorScheme.tertiary else Color.White,
|
||||||
|
customPadding = 8.dp,
|
||||||
|
customBrush = SolidColor(Color.White),
|
||||||
|
isWarn = !validation.isValid,
|
||||||
|
readOnly = isAndroid,
|
||||||
|
funTransform = ::transformMusicalInput,
|
||||||
|
onValueChng = { newValue ->
|
||||||
|
notes[voice] = newValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if(isAndroid) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.clickable {
|
||||||
|
attachKeyboardToVoice(voice)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -549,124 +670,136 @@ fun TUODetailDialog(
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
// --- SECTION LYRICS ---
|
if(!isAndroid) {
|
||||||
lyricsLines.forEachIndexed { index, line ->
|
// --- SECTION LYRICS ---
|
||||||
val displayedLine = line
|
lyricsLines.forEachIndexed { index, line ->
|
||||||
LaunchedEffect(displayedLine) {
|
val displayedLine = line
|
||||||
if (displayedLine != line) {
|
LaunchedEffect(displayedLine) {
|
||||||
lyricsLines[index] = displayedLine
|
if (displayedLine != line) {
|
||||||
val updatedLyricsMap = editState.lyricsByStanza.toMutableMap()
|
lyricsLines[index] = displayedLine
|
||||||
updatedLyricsMap[index + 1] = displayedLine
|
val updatedLyricsMap = editState.lyricsByStanza.toMutableMap()
|
||||||
|
updatedLyricsMap[index + 1] = displayedLine
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Row(
|
||||||
Row(
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
modifier = Modifier.padding(vertical = 2.dp)
|
||||||
modifier = Modifier.padding(vertical = 2.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth(0.8f)
|
|
||||||
) {
|
) {
|
||||||
|
Column(
|
||||||
val isLyricsValid = validateLyricsInput(line, templateFragment)
|
modifier = Modifier.fillMaxWidth(0.8f)
|
||||||
val tooltipState2 = rememberTooltipState(isPersistent = false)
|
|
||||||
Row(
|
|
||||||
) {
|
) {
|
||||||
TooltipBox(
|
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
|
||||||
tooltip = {
|
|
||||||
if (!isLyricsValid) {
|
|
||||||
PlainTooltip(
|
|
||||||
containerColor = Color(0XFF34EB71),
|
|
||||||
contentColor = Color.White
|
|
||||||
) {
|
|
||||||
Text("Veuillez suivre ce format: $templateFragment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
state = tooltipState2
|
|
||||||
) {
|
|
||||||
MyTextEditField(
|
|
||||||
value = displayedLine,
|
|
||||||
customFontSize = 13.sp,
|
|
||||||
color = Color.White,
|
|
||||||
customPadding = 8.dp,
|
|
||||||
customBrush = SolidColor(Color.White),
|
|
||||||
isEditable = isEditable,
|
|
||||||
isAddable = canAdd,
|
|
||||||
isWarn = !isLyricsValid,
|
|
||||||
funTransform = { input -> transformLyricsInput(input, templateFragment) },
|
|
||||||
onValueChng = { newValue ->
|
|
||||||
val trimmedValue = newValue.trimStart()
|
|
||||||
val dataToSave = if (trimmedValue.endsWith(" ")) trimmedValue else "$trimmedValue "
|
|
||||||
lyricsLines[index] = trimmedValue
|
|
||||||
val updatedLyricsMap = editState.lyricsByStanza.toMutableMap()
|
|
||||||
editedLyricsMap[index + 1] = dataToSave
|
|
||||||
|
|
||||||
val updatedState = editState.copy(lyricsByStanza = updatedLyricsMap)
|
val isLyricsValid = validateLyricsInput(line, templateFragment)
|
||||||
|
val tooltipState2 = rememberTooltipState(isPersistent = false)
|
||||||
|
Row(
|
||||||
|
) {
|
||||||
|
TooltipBox(
|
||||||
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
tooltip = {
|
||||||
|
if (!isLyricsValid) {
|
||||||
|
PlainTooltip(
|
||||||
|
containerColor = Color(0XFF34EB71),
|
||||||
|
contentColor = Color.White
|
||||||
|
) {
|
||||||
|
Text("Veuillez suivre ce format: $templateFragment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state = tooltipState2
|
||||||
|
) {
|
||||||
|
MyTextEditField(
|
||||||
|
value = displayedLine,
|
||||||
|
customFontSize = 13.sp,
|
||||||
|
color = Color.White,
|
||||||
|
customPadding = 8.dp,
|
||||||
|
customBrush = SolidColor(Color.White),
|
||||||
|
isEditable = isEditable,
|
||||||
|
isAddable = canAdd,
|
||||||
|
isWarn = !isLyricsValid,
|
||||||
|
funTransform = { input ->
|
||||||
|
transformLyricsInput(
|
||||||
|
input,
|
||||||
|
templateFragment
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onValueChng = { newValue ->
|
||||||
|
val trimmedValue = newValue.trimStart()
|
||||||
|
val dataToSave =
|
||||||
|
if (trimmedValue.endsWith(" ")) trimmedValue else "$trimmedValue "
|
||||||
|
lyricsLines[index] = trimmedValue
|
||||||
|
val updatedLyricsMap = editState.lyricsByStanza.toMutableMap()
|
||||||
|
editedLyricsMap[index + 1] = dataToSave
|
||||||
|
|
||||||
|
val updatedState = editState.copy(lyricsByStanza = updatedLyricsMap)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isEditable || canAdd) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
if (index >= 0) {
|
||||||
|
if (index == 0) lyricsLines.add("_") else lyricsLines.removeAt(index)
|
||||||
}
|
}
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (index == 0) Icons.Default.Add else Icons.Default.Clear,
|
||||||
|
tint = if (index == 0) MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f) else MaterialTheme.colorScheme.tertiary.copy(
|
||||||
|
alpha = 1.5f
|
||||||
|
),
|
||||||
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isEditable || canAdd) {
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
if (index >= 0) {
|
|
||||||
if (index == 0) lyricsLines.add("_") else lyricsLines.removeAt(index)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = if (index == 0) Icons.Default.Add else Icons.Default.Clear,
|
|
||||||
tint = if (index == 0) MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f) else MaterialTheme.colorScheme.tertiary.copy(alpha = 1.5f),
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
if(!isAndroid) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
Row(
|
||||||
horizontalArrangement = Arrangement.End
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
horizontalArrangement = Arrangement.End
|
||||||
IconButton(onClick = onDismiss, modifier = Modifier.size(20.dp)) {
|
) {
|
||||||
Icon(
|
IconButton(onClick = onDismiss, modifier = Modifier.size(20.dp)) {
|
||||||
Icons.Default.Clear,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = Color.LightGray
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isEditable || canAdd) {
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
val finalLyricsMap = editState.lyricsByStanza.toMutableMap()
|
|
||||||
finalLyricsMap.putAll(editedLyricsMap)
|
|
||||||
if (!finalLyricsMap.containsKey(currentStanza)) {
|
|
||||||
finalLyricsMap[currentStanza] = lyricsLines.getOrNull(0)?.trim() ?: ""
|
|
||||||
}
|
|
||||||
val state = TUOEditState(
|
|
||||||
tuoIndex = globalIndex,
|
|
||||||
notesByVoice = notes.toMap(),
|
|
||||||
originalNotes = originalNotes.toMap(),
|
|
||||||
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
|
|
||||||
lyricsByStanza = finalLyricsMap,
|
|
||||||
templateFragment = templateFragment,
|
|
||||||
marker = if(marker.isEmpty()) "_" else marker,
|
|
||||||
originalSep = initialSep,
|
|
||||||
sep = newSep
|
|
||||||
)
|
|
||||||
onSave(state)
|
|
||||||
},
|
|
||||||
modifier = Modifier.size(22.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Build,
|
Icons.Default.Clear,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f)
|
tint = Color.LightGray
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (isEditable || canAdd) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
val finalLyricsMap = editState.lyricsByStanza.toMutableMap()
|
||||||
|
finalLyricsMap.putAll(editedLyricsMap)
|
||||||
|
if (!finalLyricsMap.containsKey(currentStanza)) {
|
||||||
|
finalLyricsMap[currentStanza] = lyricsLines.getOrNull(0)?.trim() ?: ""
|
||||||
|
}
|
||||||
|
val state = TUOEditState(
|
||||||
|
tuoIndex = globalIndex,
|
||||||
|
notesByVoice = notes.toMap(),
|
||||||
|
originalNotes = originalNotes.toMap(),
|
||||||
|
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
|
||||||
|
lyricsByStanza = finalLyricsMap,
|
||||||
|
templateFragment = templateFragment,
|
||||||
|
marker = if(marker.isEmpty()) "_" else marker,
|
||||||
|
originalSep = initialSep,
|
||||||
|
sep = newSep
|
||||||
|
)
|
||||||
|
onSave(state)
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(22.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Build,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -907,6 +1040,75 @@ fun MyTextEditField(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SolfaTextEditField(
|
||||||
|
value: String,
|
||||||
|
customFontSize: TextUnit,
|
||||||
|
font: FontFamily? = FontFamily.SansSerif,
|
||||||
|
color: Color,
|
||||||
|
customPadding: Dp,
|
||||||
|
customBrush: Brush,
|
||||||
|
isWarn: Boolean? = false,
|
||||||
|
readOnly: Boolean,
|
||||||
|
funTransform: ((String) -> String)? = null,
|
||||||
|
onValueChng: (String) -> Unit
|
||||||
|
) {
|
||||||
|
var textFieldValueState by remember {
|
||||||
|
mutableStateOf(TextFieldValue(text = value, selection = TextRange(value.length)))
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(value) {
|
||||||
|
if (textFieldValueState.text != value) {
|
||||||
|
val newSelection = if (textFieldValueState.selection.start <= value.length) {
|
||||||
|
textFieldValueState.selection
|
||||||
|
} else {
|
||||||
|
TextRange(value.length)
|
||||||
|
}
|
||||||
|
textFieldValueState = TextFieldValue(text = value, selection = newSelection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
|
value = textFieldValueState,
|
||||||
|
onValueChange = { newValue ->
|
||||||
|
println("ça change $newValue => ${textFieldValueState.text}")
|
||||||
|
val transformedText = funTransform?.invoke(newValue.text) ?: newValue.text
|
||||||
|
val finalSelection = if (transformedText.length != newValue.text.length) {
|
||||||
|
if (newValue.selection.start <= transformedText.length) newValue.selection else TextRange(transformedText.length)
|
||||||
|
} else {
|
||||||
|
newValue.selection
|
||||||
|
}
|
||||||
|
|
||||||
|
textFieldValueState = TextFieldValue(
|
||||||
|
text = transformedText,
|
||||||
|
selection = finalSelection
|
||||||
|
)
|
||||||
|
println("et ça trnasforme en = $transformedText")
|
||||||
|
onValueChng(transformedText)
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
autoCorrectEnabled = false,
|
||||||
|
keyboardType = KeyboardType.Ascii
|
||||||
|
),
|
||||||
|
textStyle = TextStyle(
|
||||||
|
color = color,
|
||||||
|
fontSize = customFontSize,
|
||||||
|
fontFamily = font
|
||||||
|
),
|
||||||
|
readOnly = readOnly,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(Color.Black.copy(alpha = 0.3f), RoundedCornerShape(4.dp))
|
||||||
|
.border(
|
||||||
|
if (isWarn!!) 1.dp else 0.dp,
|
||||||
|
if (isWarn!!) MaterialTheme.colorScheme.tertiary.copy(alpha = 1.5f) else Color.Transparent,
|
||||||
|
shape = RoundedCornerShape(4.dp)
|
||||||
|
)
|
||||||
|
.padding(customPadding),
|
||||||
|
cursorBrush = customBrush
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
data class ValidNoteResult(
|
data class ValidNoteResult(
|
||||||
val isValid: Boolean,
|
val isValid: Boolean,
|
||||||
val message: String
|
val message: String
|
||||||
|
|
|
||||||
|
|
@ -265,6 +265,13 @@ class TimeUnitObject (val pTemplate: PTemplate, val prevTUO: TimeUnitObject?, co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val REGEX_CLEAN_PREFIX = Regex("(\\d+\\.)+")
|
||||||
|
private val musicForPtSerifBIRegex = Regex("""^(Largh\.|Grave|Largo|Lento|Adagio|And\.|Andantino|Mod\.|Moderato|Alleg\.|All\.|Viv\.|Vivacissimo|Presto|Prestiss\.|accel\.|rit\.|rall\.|riten\.|string\.|allarg\.|a tempo|Tempo I|rubato|meno mosso|più mosso|cres\.|<|decresc\.|dim\.|>|rfz|fp|pf|sub\.p|sub\.f)""",RegexOption.IGNORE_CASE)
|
||||||
|
private val musicForEmmentRegex = Regex("""^(ppp|pp|mp|mf|fff|ff|p|f|sfz|sf|fz|rfz|fp|pf)\b""",RegexOption.IGNORE_CASE)
|
||||||
|
private val blankPrefixRegex =Regex("""z([0-9A-Z])[:]""")
|
||||||
|
private val noteRegex = Regex("([drmfsltDRFSTw][ia]?|―)[0-9'¹²³⁴₁₂₃₄,]*")
|
||||||
|
private val inclusionRegex = Regex("^I([0-9]):(.*)")
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimeUnitComposable(
|
fun TimeUnitComposable(
|
||||||
tuo: TimeUnitObject,
|
tuo: TimeUnitObject,
|
||||||
|
|
@ -273,17 +280,34 @@ fun TimeUnitComposable(
|
||||||
gridActive: Boolean,
|
gridActive: Boolean,
|
||||||
isCurrentBlock: Boolean = false,
|
isCurrentBlock: Boolean = false,
|
||||||
parentFocusRequester: FocusRequester? = null,
|
parentFocusRequester: FocusRequester? = null,
|
||||||
transpositionInterval: Int
|
transpositionInterval: Int,
|
||||||
|
hairpinRange: HairpinRange? = null
|
||||||
) {
|
) {
|
||||||
val col = if (tuo.getNum() % 2 == 0) MaterialTheme.colorScheme.tertiary.copy(alpha = 0.05f) else MaterialTheme.colorScheme.error.copy(alpha = 0.05f)
|
val col = if (tuo.getNum() % 2 == 0) MaterialTheme.colorScheme.tertiary.copy(alpha = 0.05f) else MaterialTheme.colorScheme.error.copy(alpha = 0.05f)
|
||||||
val currentDensity = LocalDensity.current
|
val currentDensity = LocalDensity.current
|
||||||
|
|
||||||
val animatedColor by animateColorAsState(
|
val animatedColor = if (gridActive) {
|
||||||
targetValue = if (gridActive) MaterialTheme.colorScheme.secondary.copy(alpha = 1f) else col,
|
animateColorAsState(
|
||||||
animationSpec = tween(durationMillis = 100) // Très court pour rester réactif
|
targetValue = MaterialTheme.colorScheme.secondary.copy(alpha = 1f),
|
||||||
)
|
animationSpec = tween(durationMillis = 100)
|
||||||
|
).value
|
||||||
|
} else {
|
||||||
|
col
|
||||||
|
}
|
||||||
|
|
||||||
val focusRequesters = remember { List(4) { FocusRequester() } }
|
val focusRequesters = remember { List(4) { FocusRequester() } }
|
||||||
|
|
||||||
|
// Mettre en cache les annotations et les chaînes transposées
|
||||||
|
val multiLineText = remember(tuo, transpositionInterval) {
|
||||||
|
val notes = tuo.noteAsMultiString()
|
||||||
|
Transpose.transposeText(text = notes, interval = transpositionInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
val annotationsCache = remember(tuo, transpositionInterval) {
|
||||||
|
tuo.annotate()
|
||||||
|
tuo.annotations()
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(animatedColor)
|
.background(animatedColor)
|
||||||
|
|
@ -389,15 +413,13 @@ fun TimeUnitComposable(
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
tuo.annotate()
|
/*tuo.annotate()*/
|
||||||
// Utile pour que Compose recalcule la cellule. La valeur sera toutefois toujours égale à ""
|
// Utile pour que Compose recalcule la cellule. La valeur sera toutefois toujours égale à ""
|
||||||
val mutableNoteVersionX = if (tuo.mutableNoteVersion == -1) "7" else ""
|
val mutableNoteVersionX = if (tuo.mutableNoteVersion == -1) "7" else ""
|
||||||
val multiLineNotes = tuo.noteAsMultiString()
|
|
||||||
val multiLineText = Transpose.transposeText(text = multiLineNotes, interval = transpositionInterval)
|
|
||||||
var textLayoutResult: TextLayoutResult? by remember { mutableStateOf(null) }
|
var textLayoutResult: TextLayoutResult? by remember { mutableStateOf(null) }
|
||||||
Box (modifier = Modifier.fillMaxWidth()
|
Box (modifier = Modifier.fillMaxWidth()
|
||||||
.drawBehind {
|
.drawBehind {
|
||||||
tuo.annotations().map { ta ->
|
annotationsCache.map { ta ->
|
||||||
ta.underlineSpec.map { us ->
|
ta.underlineSpec.map { us ->
|
||||||
var xStart = 0f
|
var xStart = 0f
|
||||||
val separatorLength = if (tuo.sep0 in listOf(":", "!")) 1 else 0
|
val separatorLength = if (tuo.sep0 in listOf(":", "!")) 1 else 0
|
||||||
|
|
@ -435,7 +457,8 @@ fun TimeUnitComposable(
|
||||||
else -> Color.Black
|
else -> Color.Black
|
||||||
}
|
}
|
||||||
val totalHeight = textLayoutResult?.size?.height ?: 0
|
val totalHeight = textLayoutResult?.size?.height ?: 0
|
||||||
val nbNotes = (multiLineText + mutableNoteVersionX).split("\n").size
|
// # FIX
|
||||||
|
val nbNotes = multiLineText.count { it == '\n' } +1
|
||||||
val yPos = ta.voiceNumber * totalHeight.toFloat() / nbNotes
|
val yPos = ta.voiceNumber * totalHeight.toFloat() / nbNotes
|
||||||
drawLine(
|
drawLine(
|
||||||
colorUnderline,
|
colorUnderline,
|
||||||
|
|
@ -446,22 +469,24 @@ fun TimeUnitComposable(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
){
|
){
|
||||||
val annotatedText = buildAnnotatedString {
|
val annotatedText = remember(multiLineText, MaterialTheme.colorScheme.primary) {
|
||||||
multiLineText.split(">").mapIndexed { index, text ->
|
buildAnnotatedString {
|
||||||
if (index %2 == 0) {
|
multiLineText.split(">").mapIndexed { index, text ->
|
||||||
append(text)
|
if (index %2 == 0) {
|
||||||
} else {
|
append(text)
|
||||||
withStyle(
|
} else {
|
||||||
style = SpanStyle(
|
withStyle(
|
||||||
fontSize = 10.sp,
|
style = SpanStyle(
|
||||||
baselineShift = BaselineShift.Superscript,
|
fontSize = 10.sp,
|
||||||
//fontStyle = FontStyle.Italic,
|
baselineShift = BaselineShift.Superscript,
|
||||||
color = MaterialTheme.colorScheme.primary
|
//fontStyle = FontStyle.Italic,
|
||||||
)) {
|
/*color = MaterialTheme.colorScheme.primary*/
|
||||||
append(text+" ")
|
)) {
|
||||||
}
|
append(text+" ")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -485,6 +510,8 @@ fun TimeUnitComposable(
|
||||||
|
|
||||||
data class TUOWidthMeasure(val width: Dp, val isReady: Boolean)
|
data class TUOWidthMeasure(val width: Dp, val isReady: Boolean)
|
||||||
|
|
||||||
|
data class HairpinRange(val startBlock: Int, val endBlock: Int, val symbol: Char)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun bestTUOWidth(items: List<TimeUnitObject>): TUOWidthMeasure {
|
fun bestTUOWidth(items: List<TimeUnitObject>): TUOWidthMeasure {
|
||||||
val textMeasurer = rememberTextMeasurer()
|
val textMeasurer = rememberTextMeasurer()
|
||||||
|
|
@ -656,7 +683,9 @@ fun LazyVerticalGridTUO(
|
||||||
val tuoTimestamps by sharedScreenModel.tuoTimestamps.collectAsState()
|
val tuoTimestamps by sharedScreenModel.tuoTimestamps.collectAsState()
|
||||||
val activeRowIndex by sharedScreenModel.activeIndex.collectAsStateWithLifecycle()
|
val activeRowIndex by sharedScreenModel.activeIndex.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val measures = tuoList.drop(1).chunked(gridColumnCount)
|
val measures = remember(tuoList, gridColumnCount) {
|
||||||
|
tuoList.drop(1).chunked(gridColumnCount)
|
||||||
|
}
|
||||||
// Avant column affichage:
|
// Avant column affichage:
|
||||||
val metadataList = remember(tuoList) {
|
val metadataList = remember(tuoList) {
|
||||||
tuoList.drop(1).mapIndexedNotNull { globalIndex, oneTUO ->
|
tuoList.drop(1).mapIndexedNotNull { globalIndex, oneTUO ->
|
||||||
|
|
@ -691,6 +720,32 @@ fun LazyVerticalGridTUO(
|
||||||
}.distinctBy { it.gridIndex }
|
}.distinctBy { it.gridIndex }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hairpinByEndBlock: Map<Int, HairpinRange> = remember(tuoList) {
|
||||||
|
val map = mutableMapOf<Int, HairpinRange>()
|
||||||
|
var pendingStartBlock: Int? = null
|
||||||
|
var pendingSymbol: Char? = null
|
||||||
|
tuoList.drop(1).forEach { oneTUO ->
|
||||||
|
when (oneTUO.hasHairPin()) {
|
||||||
|
'<', '>' -> {
|
||||||
|
pendingStartBlock = oneTUO.numBlock
|
||||||
|
pendingSymbol = oneTUO.hasHairPin()
|
||||||
|
}
|
||||||
|
'=' -> {
|
||||||
|
val startBlock = pendingStartBlock
|
||||||
|
val symbol = pendingSymbol
|
||||||
|
if (startBlock != null && symbol != null) {
|
||||||
|
map[oneTUO.numBlock] = HairpinRange(startBlock, oneTUO.numBlock, symbol)
|
||||||
|
}
|
||||||
|
pendingStartBlock = null
|
||||||
|
pendingSymbol = null
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Envoyer les données au ViewModel une seule fois
|
// Envoyer les données au ViewModel une seule fois
|
||||||
LaunchedEffect(metadataList) {
|
LaunchedEffect(metadataList) {
|
||||||
if (metadataList.isNotEmpty()) {
|
if (metadataList.isNotEmpty()) {
|
||||||
|
|
@ -713,7 +768,6 @@ fun LazyVerticalGridTUO(
|
||||||
|
|
||||||
val textMeasurer = rememberTextMeasurer()
|
val textMeasurer = rememberTextMeasurer()
|
||||||
val containerWidthDp = gridWidthDp / gridColumnCount
|
val containerWidthDp = gridWidthDp / gridColumnCount
|
||||||
val REGEX_CLEAN_PREFIX = Regex("(\\d+\\.)+")
|
|
||||||
val isAnySyllableTooLong = remember(currentStanza, gridWidthDp, gridColumnCount) {
|
val isAnySyllableTooLong = remember(currentStanza, gridWidthDp, gridColumnCount) {
|
||||||
val allMeasuresSyllables = tuoList.drop(1).chunked(gridColumnCount).map { measureTUOss ->
|
val allMeasuresSyllables = tuoList.drop(1).chunked(gridColumnCount).map { measureTUOss ->
|
||||||
measureTUOss.map { it.getSingleSyllable(currentStanza) }.map { column ->
|
measureTUOss.map { it.getSingleSyllable(currentStanza) }.map { column ->
|
||||||
|
|
@ -786,7 +840,7 @@ fun LazyVerticalGridTUO(
|
||||||
|
|
||||||
if (tuo.isTriolet()) {
|
if (tuo.isTriolet()) {
|
||||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
val arcWidth = with(density) { size.width * 0.65f }
|
val arcWidth = size.width * 0.65f
|
||||||
drawArc(
|
drawArc(
|
||||||
color = FEUFAROO_TRIOLET_COLOR,
|
color = FEUFAROO_TRIOLET_COLOR,
|
||||||
startAngle = 200f,
|
startAngle = 200f,
|
||||||
|
|
@ -800,18 +854,14 @@ fun LazyVerticalGridTUO(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((hairPinSymbol == '=') && (TimeUnitObject.lastHairPinSymbol != null)) {
|
|
||||||
// println("LastHairpin: ${TimeUnitObject.lastHairPinSymbol} ${TimeUnitObject.lastHairPinStart}")
|
val hairpinRange = hairpinByEndBlock[tuo.numBlock]
|
||||||
val hairPinStart = TimeUnitObject.lastHairPinStart
|
if (hairpinRange != null) {
|
||||||
val lastHairPinSymbol = TimeUnitObject.lastHairPinSymbol
|
|
||||||
val hairPinStartLine: Int = (hairPinStart - 1) / gridColumnCount
|
|
||||||
val hairPinEndLine: Int = (tuo.numBlock - 1) / gridColumnCount
|
|
||||||
// if (hairPinStartLine == hairPinEndLine) {
|
|
||||||
Canvas(
|
Canvas(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
val xStart = if (lastHairPinSymbol == '>') -size.width * (tuo.numBlock - hairPinStart) else size.width / 2
|
val xStart = if (hairpinRange.symbol == '>') -size.width * (tuo.numBlock - hairpinRange.startBlock) else size.width / 2
|
||||||
val xEnd = if (lastHairPinSymbol == '>') size.width / 2 else -size.width * (tuo.numBlock - hairPinStart)
|
val xEnd = if (hairpinRange.symbol == '>') size.width / 2 else -size.width * (tuo.numBlock - hairpinRange.startBlock)
|
||||||
val offsetUp = -yHeight * 0.30f
|
val offsetUp = -yHeight * 0.30f
|
||||||
val hairpinHeight = yHeight * 0.70f
|
val hairpinHeight = yHeight * 0.70f
|
||||||
|
|
||||||
|
|
@ -832,20 +882,12 @@ fun LazyVerticalGridTUO(
|
||||||
strokeWidth = 0.5f
|
strokeWidth = 0.5f
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
TimeUnitObject.endHairPin()
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
if (hairPinSymbol != null && hairPinSymbol != '=') {
|
|
||||||
TimeUnitObject.startHairPin(hairPinSymbol, tuo.numBlock)
|
|
||||||
}
|
|
||||||
// @todo pTemplate.markerToString retourne les marqueurs comme une seule chaîne
|
// @todo pTemplate.markerToString retourne les marqueurs comme une seule chaîne
|
||||||
// problème si template = $QD:,-$QD
|
// problème si template = $QD:,-$QD
|
||||||
tuo.pTemplate.resetCalledMarker()
|
tuo.pTemplate.resetCalledMarker()
|
||||||
|
|
||||||
val musicForPtSerifBIRegex = Regex("""^(Largh\.|Grave|Largo|Lento|Adagio|And\.|Andantino|Mod\.|Moderato|Alleg\.|All\.|Viv\.|Vivacissimo|Presto|Prestiss\.|accel\.|rit\.|rall\.|riten\.|string\.|allarg\.|a tempo|Tempo I|rubato|meno mosso|più mosso|cres\.|<|decresc\.|dim\.|>|rfz|fp|pf|sub\.p|sub\.f)""",RegexOption.IGNORE_CASE)
|
|
||||||
val musicForEmmentRegex = Regex("""^(ppp|pp|mp|mf|fff|ff|p|f|sfz|sf|fz|rfz|fp|pf)\b""",RegexOption.IGNORE_CASE)
|
|
||||||
|
|
||||||
val markerList = tuo.pTemplate.markerToList()
|
val markerList = tuo.pTemplate.markerToList()
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.wrapContentSize(unbounded = true, align = Alignment.CenterStart),
|
modifier = Modifier.wrapContentSize(unbounded = true, align = Alignment.CenterStart),
|
||||||
|
|
@ -1063,7 +1105,8 @@ fun LazyVerticalGridTUO(
|
||||||
gridActive = isActive,
|
gridActive = isActive,
|
||||||
isCurrentBlock = isSelectedByKeyboard && editMode,
|
isCurrentBlock = isSelectedByKeyboard && editMode,
|
||||||
parentFocusRequester = focusRequester,
|
parentFocusRequester = focusRequester,
|
||||||
transpositionInterval = currentInterval
|
transpositionInterval = currentInterval,
|
||||||
|
hairpinRange = hairpinByEndBlock[oneTUO.numBlock]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (showContextualMenu && selectedIndex == globalIndex) {
|
if (showContextualMenu && selectedIndex == globalIndex) {
|
||||||
|
|
@ -1119,7 +1162,6 @@ fun LazyVerticalGridTUO(
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
val afterU0T0 = fullLine.substringAfter("0:")
|
val afterU0T0 = fullLine.substringAfter("0:")
|
||||||
val blankPrefixRegex =Regex("""z([0-9A-Z])[:]""")
|
|
||||||
val match = blankPrefixRegex.find(afterU0T0)
|
val match = blankPrefixRegex.find(afterU0T0)
|
||||||
val blankPrefix = match?.groups?.get(1)?.value ?: ""
|
val blankPrefix = match?.groups?.get(1)?.value ?: ""
|
||||||
|
|
||||||
|
|
@ -1217,7 +1259,6 @@ fun LazyVerticalGridTUO(
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
val columnWidthDp = gridWidthDp / gridColumnCount
|
val columnWidthDp = gridWidthDp / gridColumnCount
|
||||||
val REGEX_CLEAN_PREFIX = Regex("(\\d+\\.)+")
|
|
||||||
val cleanAllTemps = allTemps.map { column ->
|
val cleanAllTemps = allTemps.map { column ->
|
||||||
column.map { syllable ->
|
column.map { syllable ->
|
||||||
syllable.replace(REGEX_CLEAN_PREFIX, "").trim()
|
syllable.replace(REGEX_CLEAN_PREFIX, "").trim()
|
||||||
|
|
@ -1295,8 +1336,9 @@ fun makeSpaceBetweenSyllables(
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val noteWidthPx = with(density) { noteWidthDp.toPx() }
|
val noteWidthPx = with(density) { noteWidthDp.toPx() }
|
||||||
val style = TextStyle(fontSize = fontSize)
|
val style = TextStyle(fontSize = fontSize)
|
||||||
val spaceWidthPx = textMeasurer.measure("\u00A0", style).size.width
|
val spaceWidthPx = remember(textMeasurer, style) {
|
||||||
|
textMeasurer.measure("\u00A0", style).size.width
|
||||||
|
}
|
||||||
|
|
||||||
return { currentSyl, allT, s_i, index ->
|
return { currentSyl, allT, s_i, index ->
|
||||||
var textAlign = Alignment.Center
|
var textAlign = Alignment.Center
|
||||||
|
|
@ -1308,38 +1350,25 @@ fun makeSpaceBetweenSyllables(
|
||||||
|
|
||||||
val excessPx = currentWidth - noteWidthPx
|
val excessPx = currentWidth - noteWidthPx
|
||||||
val isExcess = (excessPx > 0)
|
val isExcess = (excessPx > 0)
|
||||||
val neededSpaces = if (isExcess) (excessPx / spaceWidthPx).toInt() + 1 else 0
|
val neededSpaces = if (isExcess && spaceWidthPx > 0) (excessPx / spaceWidthPx).toInt() + 1 else 0
|
||||||
|
|
||||||
val nextNextSyl = allT.getOrNull(s_i + 2)?.getOrNull(index) ?: ""
|
|
||||||
// println("801: prev: [$prev_syl]${prev_syl.length}:[${textMeasurer.measure(prev_syl, style).size.width}], current [$currentSyl]:[${textMeasurer.measure(currentSyl, style).size.width}], next [$next_syl]:[${textMeasurer.measure(next_syl, style).size.width}], isExcess=$excessPx & need $neededSpaces \t spwp $spaceWidthPx\n")
|
|
||||||
|
|
||||||
val totalSpacesPossible = (noteWidthPx / spaceWidthPx).toInt()
|
|
||||||
val paddingNeeded = totalSpacesPossible - (currentSyl.length+4)
|
|
||||||
val resultText = when {
|
val resultText = when {
|
||||||
!isExcess && (paddingNeeded > 0) -> {
|
!isExcess && (noteWidthPx > 0 && spaceWidthPx > 0) -> {
|
||||||
textAlign = Alignment.TopStart
|
val totalSpacesPossible = (noteWidthPx / spaceWidthPx).toInt()
|
||||||
currentSyl + "\u00A0".repeat(paddingNeeded)
|
val paddingNeeded = totalSpacesPossible - (currentSyl.length + 4)
|
||||||
}
|
if (paddingNeeded > 0) {
|
||||||
isExcess && next_syl.length < prev_syl.length -> {
|
textAlign = Alignment.TopStart
|
||||||
"\u00A0".repeat(neededSpaces) + currentSyl
|
currentSyl + "\u00A0".repeat(paddingNeeded)
|
||||||
}
|
|
||||||
isExcess && next_syl.length >= prev_syl.length -> {
|
|
||||||
currentSyl + "\u00A0".repeat(neededSpaces)
|
|
||||||
}
|
|
||||||
|
|
||||||
isExcess && next_syl.isNotEmpty() -> {
|
|
||||||
val nextWidth = textMeasurer.measure(next_syl, style).size.width
|
|
||||||
val nextExcess = nextWidth - noteWidthPx
|
|
||||||
|
|
||||||
if (nextExcess > 0 && (allT.getOrNull(s_i + 2)?.getOrNull(index)?.length ?: 0) > next_syl.length) {
|
|
||||||
// Si le mot d'après va se décaler à gauche, moi je me décale aussi à gauche
|
|
||||||
val compensationSpaces = ((nextExcess / spaceWidthPx) / 2).toInt() + 1
|
|
||||||
"\u00A0".repeat(compensationSpaces) + currentSyl
|
|
||||||
} else {
|
} else {
|
||||||
currentSyl
|
currentSyl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isExcess && spaceWidthPx > 0 && next_syl.length < prev_syl.length -> {
|
||||||
|
"\u00A0".repeat(neededSpaces) + currentSyl
|
||||||
|
}
|
||||||
|
isExcess && spaceWidthPx > 0 -> {
|
||||||
|
currentSyl + "\u00A0".repeat(neededSpaces)
|
||||||
|
}
|
||||||
else -> currentSyl
|
else -> currentSyl
|
||||||
}
|
}
|
||||||
resultText to textAlign
|
resultText to textAlign
|
||||||
|
|
@ -1356,7 +1385,6 @@ private suspend fun expandInclusions(
|
||||||
val lastSlash = currentFilePath.lastIndexOf('/')
|
val lastSlash = currentFilePath.lastIndexOf('/')
|
||||||
val directory = if (lastSlash != -1) currentFilePath.substring(0, lastSlash + 1) else ""
|
val directory = if (lastSlash != -1) currentFilePath.substring(0, lastSlash + 1) else ""
|
||||||
|
|
||||||
val inclusionRegex = Regex("^I([0-9]):(.*)")
|
|
||||||
|
|
||||||
lines.forEach { line ->
|
lines.forEach { line ->
|
||||||
val match = inclusionRegex.find(line)
|
val match = inclusionRegex.find(line)
|
||||||
|
|
@ -1592,7 +1620,6 @@ fun autoFixNote(rawNote: String, template: String): String {
|
||||||
// println("==================================================")
|
// println("==================================================")
|
||||||
if (rawNote == "_") return rawNote
|
if (rawNote == "_") return rawNote
|
||||||
|
|
||||||
val noteRegex = Regex("([drmfsltDRFSTw][ia]?|―)[0-9'¹²³⁴₁₂₃₄,]*")
|
|
||||||
val actualNotes = if (rawNote.isEmpty()) emptyList() else noteRegex.findAll(rawNote).map { it.value }.toList()
|
val actualNotes = if (rawNote.isEmpty()) emptyList() else noteRegex.findAll(rawNote).map { it.value }.toList()
|
||||||
// println(" • Notes détectées dans RawNote : $actualNotes (Taille: ${actualNotes.size})")
|
// println(" • Notes détectées dans RawNote : $actualNotes (Taille: ${actualNotes.size})")
|
||||||
/*if (rawNote.contains("•") || rawNote.contains("―") || rawNote.contains(",")) {
|
/*if (rawNote.contains("•") || rawNote.contains("―") || rawNote.contains(",")) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
package mg.dot.feufaro.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CustomKeyboard(
|
||||||
|
onDigitClick: (String) -> Unit,
|
||||||
|
onDeleteClick: () -> Unit,
|
||||||
|
onClearClick: () -> Unit,
|
||||||
|
onPrefixClick: (String) -> Unit,
|
||||||
|
isFullScreen: Boolean,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.75f),
|
||||||
|
tonalElevation = 0.dp,
|
||||||
|
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 12.dp, vertical = 10.dp)
|
||||||
|
.then(
|
||||||
|
if (isFullScreen) Modifier.navigationBarsPadding() else Modifier
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
val prefixes = listOf("EWS", "FFPM", "FF", "Antema", "Tsanta", "Salamo")
|
||||||
|
LazyRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
contentPadding = PaddingValues(horizontal = 1.dp)
|
||||||
|
) {
|
||||||
|
items(prefixes) { prefix ->
|
||||||
|
Button(
|
||||||
|
onClick = { onPrefixClick("$prefix ") },
|
||||||
|
modifier = Modifier.height(38.dp),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.15f),
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
),
|
||||||
|
contentPadding = PaddingValues(horizontal = 14.dp, vertical = 0.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = prefix,
|
||||||
|
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val keys = listOf(
|
||||||
|
listOf("1", "2", "3"),
|
||||||
|
listOf("4", "5", "6"),
|
||||||
|
listOf("7", "8", "9"),
|
||||||
|
listOf("↓", "0", "⌫")
|
||||||
|
)
|
||||||
|
|
||||||
|
for (row in keys) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
for (key in row) {
|
||||||
|
val isActionKey = key == "⌫" || key == "↓"
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
when (key) {
|
||||||
|
"⌫" -> onDeleteClick()
|
||||||
|
"↓" -> onClearClick()
|
||||||
|
else -> onDigitClick(key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(44.dp),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = if (isActionKey)
|
||||||
|
MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.6f)
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.2f),
|
||||||
|
contentColor = if (isActionKey)
|
||||||
|
MaterialTheme.colorScheme.onErrorContainer
|
||||||
|
else
|
||||||
|
Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = key,
|
||||||
|
fontSize = MaterialTheme.typography.headlineLarge.fontSize,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -59,6 +59,7 @@ fun SimpleDrawerContent(
|
||||||
val internalList by sharedScreenModel.internalItems.collectAsState()
|
val internalList by sharedScreenModel.internalItems.collectAsState()
|
||||||
val externalList by sharedScreenModel.externalItems.collectAsState()
|
val externalList by sharedScreenModel.externalItems.collectAsState()
|
||||||
val playList by sharedScreenModel.playlistItems.collectAsState()
|
val playList by sharedScreenModel.playlistItems.collectAsState()
|
||||||
|
val favoriteLists by sharedScreenModel.playlistItems.collectAsState()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
var internalExpanded by remember { mutableStateOf(false) }
|
var internalExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
@ -154,7 +155,6 @@ fun SimpleDrawerContent(
|
||||||
if (internalExpanded) {
|
if (internalExpanded) {
|
||||||
items(internalList) { item ->
|
items(internalList) { item ->
|
||||||
val isSelected = item.path == activePath
|
val isSelected = item.path == activePath
|
||||||
val favoriteLists by sharedScreenModel.playlistItems.collectAsState()
|
|
||||||
val isFavorite = favoriteLists.contains(item)
|
val isFavorite = favoriteLists.contains(item)
|
||||||
|
|
||||||
NavigationDrawerItem(
|
NavigationDrawerItem(
|
||||||
|
|
@ -207,7 +207,6 @@ fun SimpleDrawerContent(
|
||||||
if (externalExpanded) {
|
if (externalExpanded) {
|
||||||
items(externalList) { item ->
|
items(externalList) { item ->
|
||||||
val isSelected = item.path == activePath
|
val isSelected = item.path == activePath
|
||||||
val favoriteLists by sharedScreenModel.playlistItems.collectAsState()
|
|
||||||
val isFavorite = favoriteLists.contains(item)
|
val isFavorite = favoriteLists.contains(item)
|
||||||
|
|
||||||
if (item.path != "") {
|
if (item.path != "") {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,296 @@
|
||||||
|
package mg.dot.feufaro.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.KeyboardBackspace
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.KeyboardTab
|
||||||
|
import androidx.compose.material.icons.filled.Build
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
|
import androidx.compose.material.icons.filled.Save
|
||||||
|
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.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import mg.dot.feufaro.solfa.Transpose
|
||||||
|
|
||||||
|
enum class OctaveMode {
|
||||||
|
LOWER, NORMAL, UPPER
|
||||||
|
}
|
||||||
|
val SOLFA_NOTES = Transpose.noteToNumber
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SolfaKeyboard(
|
||||||
|
currentVoiceIndex: Int,
|
||||||
|
currentValue: String = "",
|
||||||
|
onNoteClick: (String) -> Unit,
|
||||||
|
onTabClick: () -> Unit,
|
||||||
|
onBackspaceClick: () -> Unit,
|
||||||
|
onBuildClick: () -> Unit,
|
||||||
|
onCloseClick: () -> Unit,
|
||||||
|
onCustomSymbolClick: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var selectedOctave by remember { mutableStateOf("") }
|
||||||
|
val voiceLabels = listOf("S", "A", "T", "B")
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(Color(0xFF232323)),
|
||||||
|
color = Color(0xFF232323),
|
||||||
|
tonalElevation = 8.dp
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(6.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(5.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(5.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onTabClick,
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary),
|
||||||
|
shape = RoundedCornerShape(4.dp),
|
||||||
|
contentPadding = PaddingValues(horizontal = 6.dp, vertical = 2.dp),
|
||||||
|
modifier = Modifier.height(36.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Default.KeyboardTab,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(2.dp))
|
||||||
|
Text(
|
||||||
|
text = voiceLabels.getOrElse(currentVoiceIndex) { "S" },
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
|
value = currentValue,
|
||||||
|
onValueChange = {},
|
||||||
|
readOnly = true,
|
||||||
|
textStyle = TextStyle(
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1.5f)
|
||||||
|
.height(36.dp)
|
||||||
|
.background(Color.Black.copy(alpha = 0.4f), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = {
|
||||||
|
onNoteClick(",")
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color(0xFF383838)
|
||||||
|
),
|
||||||
|
modifier = Modifier.height(36.dp).weight(0.7f)
|
||||||
|
) {
|
||||||
|
Text(",", fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
||||||
|
}
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = {
|
||||||
|
onNoteClick("'")
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color(0xFF383838)
|
||||||
|
),
|
||||||
|
modifier = Modifier.height(36.dp)
|
||||||
|
) {
|
||||||
|
Text("'", fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(0.5f))
|
||||||
|
KeyButton("di", Modifier.weight(1f)) { onNoteClick("di") }
|
||||||
|
KeyButton("ri", Modifier.weight(1f)) { onNoteClick("ri") }
|
||||||
|
Spacer(modifier = Modifier.weight(1.3f))
|
||||||
|
KeyButton("fi", Modifier.weight(1f)) { onNoteClick("fi") }
|
||||||
|
KeyButton("si", Modifier.weight(1f)) { onNoteClick("si") }
|
||||||
|
KeyButton("ta", Modifier.weight(1f)) { onNoteClick("ta") }
|
||||||
|
Spacer(modifier = Modifier.weight(0.5f))
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
val diatonicNotes = listOf("d", "r", "m", "f", "s", "l", "t")
|
||||||
|
for (note in diatonicNotes) {
|
||||||
|
KeyButton(note, Modifier.weight(1f)) { onNoteClick(note) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
KeyButton("―", Modifier.weight(1f)) { onCustomSymbolClick("-") }
|
||||||
|
KeyButton("• ,", Modifier.weight(1f)) { onCustomSymbolClick(";") }
|
||||||
|
KeyButton("•", Modifier.weight(0.5f)) { onCustomSymbolClick(".") }
|
||||||
|
KeyButton(",", Modifier.weight(0.5f)) { onCustomSymbolClick(" ,") }
|
||||||
|
KeyButton("Espace", Modifier.weight(1.6f)) { onCustomSymbolClick(" ") }
|
||||||
|
KeyButton("(", Modifier.weight(0.6f)) { onCustomSymbolClick("(") }
|
||||||
|
KeyButton(")", Modifier.weight(0.6f)) { onCustomSymbolClick(")") }
|
||||||
|
KeyButton("<", Modifier.weight(0.5f)) { onCustomSymbolClick("<") }
|
||||||
|
KeyButton(">", Modifier.weight(0.5f)) { onCustomSymbolClick(">") }
|
||||||
|
KeyButton("z", Modifier.weight(1f)) { onNoteClick("z") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(48.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onCloseClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
shape = RoundedCornerShape(4.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.errorContainer),
|
||||||
|
contentPadding = PaddingValues(0.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Close,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onErrorContainer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onBackspaceClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
shape = RoundedCornerShape(4.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4A4A4A)),
|
||||||
|
contentPadding = PaddingValues(0.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Default.KeyboardBackspace,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onBuildClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1.2f),
|
||||||
|
shape = RoundedCornerShape(4.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary),
|
||||||
|
contentPadding = PaddingValues(0.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Save,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun KeyButton(
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
containerColor: Color = Color(0xFF383838),
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier.height(49.dp),
|
||||||
|
shape = RoundedCornerShape(4.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = containerColor),
|
||||||
|
contentPadding = PaddingValues(0.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SolfaKeyboardState {
|
||||||
|
var isVisible by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var isDialogActive by mutableStateOf(false)
|
||||||
|
var activeVoiceIndex by mutableStateOf(0)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var currentText by mutableStateOf("")
|
||||||
|
var onNoteInput: ((String) -> Unit)? = null
|
||||||
|
var onBackspace: (() -> Unit)? = null
|
||||||
|
var onCloseDialog: (() -> Unit)? = null
|
||||||
|
var onBuildEdit: (() -> Unit)? = null
|
||||||
|
var onCustomSymbol: ((String) -> Unit)? = null
|
||||||
|
var onTabNextVoice: (() -> Unit)? = null
|
||||||
|
var onMarker: (() -> Unit)? = null
|
||||||
|
|
||||||
|
fun show(
|
||||||
|
initialVoice: Int = 0,
|
||||||
|
initialValue: String = "",
|
||||||
|
onNote: (String) -> Unit,
|
||||||
|
onDelete: () -> Unit,
|
||||||
|
onSymbol: (String) -> Unit,
|
||||||
|
onBuild: () -> Unit,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
onTab: () -> Unit
|
||||||
|
) {
|
||||||
|
if (!isDialogActive) return
|
||||||
|
activeVoiceIndex = initialVoice
|
||||||
|
onNoteInput = onNote
|
||||||
|
onBackspace = onDelete
|
||||||
|
onCustomSymbol = onSymbol
|
||||||
|
onCloseDialog = onClose
|
||||||
|
onBuildEdit = onBuild
|
||||||
|
onTabNextVoice = onTab
|
||||||
|
isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hide() {
|
||||||
|
isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberSolfaKeyboardState() = remember { SolfaKeyboardState() }
|
||||||
|
|
||||||
|
val LocalSolfaKeyboardState = staticCompositionLocalOf <SolfaKeyboardState> {
|
||||||
|
error("Aucun SolfaKeyboardState fourni")
|
||||||
|
}
|
||||||
|
|
@ -612,7 +612,6 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
_dcDone.value = false
|
_dcDone.value = false
|
||||||
_dsDone.value = false
|
_dsDone.value = false
|
||||||
_sourceModeState.value = false
|
_sourceModeState.value = false
|
||||||
setTranspositionInterval(0)
|
|
||||||
try {
|
try {
|
||||||
val midiFileName = fileRepository.getFileName(newMidiFile)
|
val midiFileName = fileRepository.getFileName(newMidiFile)
|
||||||
println("Opening xx129 $midiFileName")
|
println("Opening xx129 $midiFileName")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue