Resolve crash in TUO by refactoring composables - Android

This commit is contained in:
Hasinjato 2026-09-10 14:09:07 +03:00
parent 10eca05472
commit 1d7273a531

View file

@ -320,84 +320,6 @@ fun TimeUnitComposable(
modifier = Modifier
.background(animatedColor)
) {
if (TimeUnitObject._hasMarker) {
val lineHeight = 20.sp
val density = LocalDensity.current
val lineHeightDp : Dp = with(density) {
lineHeight.toDp()
}
var fontStyle = FontStyle.Normal
var fontWeight = FontWeight.Normal
val hairPinSymbol = tuo.hasHairPin()
val yHeight = with(density) { lineHeightDp.toPx()}
/*if (tuo.isTriolet()) {
Canvas(modifier = Modifier.fillMaxSize()) {
val arcWidth = with(density) { size.width * 0.75f}
drawArc(
color = FEUFAROO_TRIOLET_COLOR,
startAngle = 200f,
sweepAngle = 140f,
useCenter = false,
topLeft = Offset(0f, yHeight / 2),
size = Size(arcWidth, yHeight),
style = Stroke(width = 2f)
)
}
}
if ((hairPinSymbol == '=') && (TimeUnitObject.lastHairPinSymbol != null)) {
println("LastHairpin: ${TimeUnitObject.lastHairPinSymbol} ${TimeUnitObject.lastHairPinStart}")
val hairPinStart = TimeUnitObject.lastHairPinStart
val lastHairPinSymbol = TimeUnitObject.lastHairPinSymbol
val hairPinStartLine: Int = (hairPinStart - 1) / gridColumnCount
val hairPinEndLine: Int = (tuo.numBlock - 1) / gridColumnCount
if (hairPinStartLine == hairPinEndLine) {
Canvas(
modifier = Modifier.fillMaxSize()
) {
val xStart = if (lastHairPinSymbol == '>') -size.width * (tuo.numBlock - hairPinStart) else size.width/2
val xEnd = if (lastHairPinSymbol == '>') size.width/2 else -size.width * (tuo.numBlock - hairPinStart)
drawLine(
Color.DarkGray,
start = Offset(x=xStart, y=0f),
end = Offset(xEnd, yHeight/2)
)
drawLine(
Color.DarkGray,
start = Offset(xStart, yHeight),
end = Offset(xEnd, yHeight/2)
)
}
TimeUnitObject.endHairPin()
}
}
if (hairPinSymbol != null && hairPinSymbol != '=') {
TimeUnitObject.startHairPin(hairPinSymbol, tuo.numBlock)
}
// @todo pTemplate.markerToString retourne les marqueurs comme une seule chaîne
// problème si template = $QD:,-$QD
tuo.pTemplate.resetCalledMarker()
val text = tuo.pTemplate.markerToString()
if (text.contains(Regex("^([mfp]+|[rR]it.*)$"))) {
fontStyle = FontStyle.Italic
}
if (text.contains("p")) {
fontWeight = FontWeight.Bold
}*/
/*AutoResizingText(
text = text,
minFontSize = 18.sp,
maxFontSize = 18.sp,
maxLines = 2,
fontStyle = fontStyle,
fontWeight = fontWeight,
modifier = Modifier.fillMaxWidth()
.height(lineHeightDp))*/
}
Row (modifier = Modifier.height(IntrinsicSize.Min)){
if (tuo.sep0 == "/" || tuo.sep0 == "|") {
Box(
@ -850,104 +772,18 @@ fun LazyVerticalGridTUO(
}
var fontStyle = FontStyle.Normal
var fontWeight = FontWeight.Normal
val hairPinSymbol = tuo.hasHairPin()
val yHeight = with(density) { lineHeightDp.toPx() }
if (tuo.isTriolet()) {
Canvas(modifier = Modifier.fillMaxSize()) {
val arcWidth = size.width * 0.65f
drawArc(
color = colorUnderline,
startAngle = 200f,
sweepAngle = 140f,
useCenter = false,
//topLeft = Offset(0f, yHeight/* / 2*/),
topLeft = Offset(size.width *0.05f, -yHeight * 0.1f),
size = Size(arcWidth, yHeight),
style = Stroke(width = 2f)
)
}
}
val hairpinRange = hairpinByEndBlock[tuo.numBlock]
if (hairpinRange != null) {
Canvas(
modifier = Modifier.fillMaxSize()
) {
val xStart = if (hairpinRange.symbol == '>') -size.width * (tuo.numBlock - hairpinRange.startBlock) else size.width / 2
val xEnd = if (hairpinRange.symbol == '>') size.width / 2 else -size.width * (tuo.numBlock - hairpinRange.startBlock)
val offsetUp = -yHeight * 0.30f
val hairpinHeight = yHeight * 0.70f
val yTop = 0f + offsetUp
val yCenter = (hairpinHeight / 2) + offsetUp
val yBottom = hairpinHeight + offsetUp
drawLine(
color = colorUnderline,
start = Offset(x = xStart, y = yTop),
end = Offset(xEnd, yCenter),
strokeWidth = 0.5f
)
drawLine(
color = colorUnderline,
start = Offset(xStart, yBottom),
end = Offset(xEnd, yCenter),
strokeWidth = 0.5f
)
}
}
// @todo pTemplate.markerToString retourne les marqueurs comme une seule chaîne
// problème si template = $QD:,-$QD
tuo.pTemplate.resetCalledMarker()
val markerList = tuo.pTemplate.markerToList()
Row(
modifier = Modifier.wrapContentSize(unbounded = true, align = Alignment.CenterStart),
verticalAlignment = Alignment.CenterVertically
) {
markerList.forEach { marker ->
Column {
var markerFontFamily: FontFamily = FontFamily.Default
var markerFontSize: Float = MaterialTheme.typography.titleMedium.fontSize.value
when {
musicForPtSerifBIRegex.matches(marker) -> {
markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value
markerFontFamily = ptSerifBoldItalic
}
musicForEmmentRegex.matches(marker) -> {
markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value + 11
markerFontFamily = emmentaler
}
else -> {
markerFontFamily = ptSerif
}
}
Text(
text = marker,
modifier = Modifier
.wrapContentSize(unbounded = true, align = Alignment.CenterStart,),
softWrap = false,
maxLines = 1,
fontStyle = fontStyle,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium.copy(
color = MaterialTheme.colorScheme.onSecondary,
fontSize = markerFontSize.sp,
fontFamily = markerFontFamily
)
)
if (marker != markerList.last()) {
Spacer(modifier = Modifier.width(2.dp))
}
}
}
}
TuoHeaderDecorations(
tuo,
colorUnderline,
yHeight,
hairpinByEndBlock,
ptSerifBoldItalic,
emmentaler,
ptSerif,
fontStyle
)
}
}
}
@ -959,55 +795,17 @@ fun LazyVerticalGridTUO(
verticalAlignment = Alignment.CenterVertically
) {
measureTUOs.forEachIndexed { indexInMeasure, tuo ->
val chordMap = (1..4).mapNotNull { voice ->
val noteStr = tuo.tuNotes.getOrNull(voice)?.toString()
if (noteStr != null) voice to noteStr else null
}.toMap()
val degreeName = remember(chordMap) {
if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeDegree(chordMap) else null
}
val appliedInterval by sharedScreenModel.transpositionInterval.collectAsState()
val songKey = sharedScreenModel.songKey.value
val chordName = remember(chordMap, songKey, appliedInterval) {
if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeChordName(chordMap, songKey, appliedInterval) else null
}
Column(
modifier = Modifier
.width(gridWidthDp / gridColumnCount)
) {
val maxFontSize = 12.sp
val minFontSize = 10.sp
var currentFontSize by remember { mutableStateOf(maxFontSize) }
TextButton(
onClick = { toggleWithDegreeAndChord = !toggleWithDegreeAndChord },
contentPadding = PaddingValues(0.dp),
modifier = Modifier.height(24.dp).fillMaxWidth()
) {
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
val textToDisplay = if (toggleWithDegreeAndChord) (chordName ?: "") else (degreeName ?: "")
Text(
text = textToDisplay,
onTextLayout = { textLayoutResult ->
if (textLayoutResult.hasVisualOverflow && currentFontSize > minFontSize) {
currentFontSize = (currentFontSize.value * 0.9f).sp
}
},
softWrap = false,
maxLines = 1,
overflow = TextOverflow.Visible,
style = TextStyle(
color = if (!toggleWithDegreeAndChord) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary.copy(red = MaterialTheme.colorScheme.secondary.red * 0.4f, green = MaterialTheme.colorScheme.secondary.green * 0.4f, blue = MaterialTheme.colorScheme.secondary.blue * 0.4f ),
fontWeight = FontWeight.Bold,
fontSize = currentFontSize
)
)
}
}
}
TuoChordRowItem(
tuo,
sharedScreenModel,
gridWidthDp,
gridColumnCount,
toggleWithDegreeAndChord,
onToggleChange = {
toggleWithDegreeAndChord = it
},
modifier = Modifier.height(24.dp).fillMaxWidth()
)
}
}
}
@ -1192,73 +990,21 @@ fun LazyVerticalGridTUO(
val currentSelected = selectedTUO
if ((showEditDialog || showDetailDialog) && currentSelected != null && selectedIndex == globalIndex) {
val canAdd = showAddDialog
val canEdit = showEditDialog || canAdd
val template = oneTUO.pTemplate.template
val expectedTemplate = template.count { it.isLetter() } == 2 && template.contains(".")
val fileC = sharedScreenModel.fileContent.value ?: ""
val lines = fileC.split("\n").toMutableList()
val u0Idx = lines.indexOfFirst { it.startsWith("U0:") }
val t0Idx = lines.indexOfFirst { it.startsWith("T0:") }
val fullLine = when {
t0Idx != -1 -> {
lines[t0Idx]
}
u0Idx != -1 -> {
lines[u0Idx]
}
else -> ""
}
val afterU0T0 = fullLine.substringAfter("0:")
val match = blankPrefixRegex.find(afterU0T0)
val blankPrefix = match?.groups?.get(1)?.value ?: ""
val anchorsByVoice = (0..3).associate { voiceIdx ->
val linePrefix = "N${voiceIdx + 1}:"
val fullLine = lines.firstOrNull { it.startsWith(linePrefix) } ?: ""
voiceIdx to extractAnchorFromNoteLine(fullLine)
}
val editState = TUOEditState(
tuoIndex = oneTUO.firstTuoIndex,
notesByVoice = (0..3).associate { i ->
val rawNote = oneTUO.tuNotes.getOrNull(i + 1)?.toString() ?: ""
val fixedNote = autoFixNote(rawNote, template)
i to fixedNote
},
lyricsByStanza = (1..currentStanza).associate { s ->
s to oneTUO.lyricsAsMultiString(currentStanza)
}.toMutableMap(),
templateFragment = oneTUO.pTemplate.template,
marker = listOfNotNull(
oneTUO.pTemplate.markerToString().takeIf { it.isNotBlank() },
oneTUO.hasHairPin()?.toString()?.takeIf { it.isNotBlank() }
).joinToString(" "),
silentDuration = blankPrefix,
sep = oneTUO.sep0
)
TUODetailDialog(
editState = editState,
tuo = currentSelected,
currentStanza = currentStanza,
menuPosition = IntOffset(
menuOffset.x.roundToInt(),
menuOffset.y.roundToInt()
),
val lines = fileC.split("\n")
TuoEditDialogWrapper(
oneTUO = currentSelected,
globalIndex = globalIndex,
isEditable = canEdit,
canAdd = canAdd,
currentStanza = currentStanza,
menuOffset = menuOffset,
sharedScreenModel = sharedScreenModel,
anchorsByVoice = anchorsByVoice,
lines = lines,
onDismiss = {
showAddDialog = false
showDetailDialog = false
showEditDialog = false
},
onSave = { newState ->
sharedScreenModel.openTUOEditor(newState)
onSaveSuccess = {
showAddDialog = false
showEditDialog = false
},
@ -1378,6 +1124,257 @@ fun LazyVerticalGridTUO(
}
}
@Composable
private fun TuoChordRowItem(
tuo: TimeUnitObject,
sharedScreenModel: SharedScreenModel,
gridWidthDp: Dp,
gridColumnCount: Int,
toggleWithDegreeAndChord: Boolean,
onToggleChange: (Boolean) -> Unit,
modifier: Modifier
) {
val chordMap = (1..4).mapNotNull { voice ->
val noteStr = tuo.tuNotes.getOrNull(voice)?.toString()
if (noteStr != null) voice to noteStr else null
}.toMap()
val degreeName = remember(chordMap) {
if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeDegree(chordMap) else null
}
val appliedInterval by sharedScreenModel.transpositionInterval.collectAsState()
val songKey = sharedScreenModel.songKey.value
val chordName = remember(chordMap, songKey, appliedInterval) {
if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeChordName(chordMap, songKey, appliedInterval) else null
}
Column(
modifier = Modifier
.width(gridWidthDp / gridColumnCount)
) {
val maxFontSize = 12.sp
val minFontSize = 10.sp
var currentFontSize by remember { mutableStateOf(maxFontSize) }
TextButton(
onClick = { onToggleChange(!toggleWithDegreeAndChord) },
contentPadding = PaddingValues(0.dp),
modifier = modifier
) {
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
val textToDisplay = if (toggleWithDegreeAndChord) (chordName ?: "") else (degreeName ?: "")
Text(
text = textToDisplay,
onTextLayout = { textLayoutResult ->
if (textLayoutResult.hasVisualOverflow && currentFontSize > minFontSize) {
currentFontSize = (currentFontSize.value * 0.9f).sp
}
},
softWrap = false,
maxLines = 1,
overflow = TextOverflow.Visible,
style = TextStyle(
color = if (!toggleWithDegreeAndChord) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary.copy(
red = MaterialTheme.colorScheme.secondary.red * 0.4f,
green = MaterialTheme.colorScheme.secondary.green * 0.4f,
blue = MaterialTheme.colorScheme.secondary.blue * 0.4f
),
fontWeight = FontWeight.Bold,
fontSize = currentFontSize
)
)
}
}
}
}
@Composable
private fun TuoHeaderDecorations(
tuo: TimeUnitObject,
colorUnderline: Color,
yHeight: Float,
hairpinByEndBlock: Map<Int, HairpinRange>,
ptSerifBoldItalic: FontFamily,
emmentaler: FontFamily,
ptSerif: FontFamily,
fontStyle: FontStyle
) {
if (tuo.isTriolet()) {
Canvas(modifier = Modifier.fillMaxSize()) {
val arcWidth = size.width * 0.65f
drawArc(
color = colorUnderline,
startAngle = 200f,
sweepAngle = 140f,
useCenter = false,
//topLeft = Offset(0f, yHeight/* / 2*/),
topLeft = Offset(size.width * 0.05f, -yHeight * 0.1f),
size = Size(arcWidth, yHeight),
style = Stroke(width = 2f)
)
}
}
val hairpinRange = hairpinByEndBlock[tuo.numBlock]
if (hairpinRange != null) {
Canvas(
modifier = Modifier.fillMaxSize()
) {
val xStart =
if (hairpinRange.symbol == '>') -size.width * (tuo.numBlock - hairpinRange.startBlock) else size.width / 2
val xEnd =
if (hairpinRange.symbol == '>') size.width / 2 else -size.width * (tuo.numBlock - hairpinRange.startBlock)
val offsetUp = -yHeight * 0.30f
val hairpinHeight = yHeight * 0.70f
val yTop = 0f + offsetUp
val yCenter = (hairpinHeight / 2) + offsetUp
val yBottom = hairpinHeight + offsetUp
drawLine(
color = colorUnderline,
start = Offset(x = xStart, y = yTop),
end = Offset(xEnd, yCenter),
strokeWidth = 0.5f
)
drawLine(
color = colorUnderline,
start = Offset(xStart, yBottom),
end = Offset(xEnd, yCenter),
strokeWidth = 0.5f
)
}
}
// problème si template = $QD:,-$QD
tuo.pTemplate.resetCalledMarker()
val markerList = tuo.pTemplate.markerToList()
Row(
modifier = Modifier.wrapContentSize(unbounded = true, align = Alignment.CenterStart),
verticalAlignment = Alignment.CenterVertically
) {
markerList.forEach { marker ->
Column {
var markerFontFamily: FontFamily = FontFamily.Default
var markerFontSize: Float = MaterialTheme.typography.titleMedium.fontSize.value
when {
musicForPtSerifBIRegex.matches(marker) -> {
markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value
markerFontFamily = ptSerifBoldItalic
}
musicForEmmentRegex.matches(marker) -> {
markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value + 11
markerFontFamily = emmentaler
}
else -> {
markerFontFamily = ptSerif
}
}
Text(
text = marker,
modifier = Modifier
.wrapContentSize(unbounded = true, align = Alignment.CenterStart),
softWrap = false,
maxLines = 1,
fontStyle = fontStyle,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium.copy(
color = MaterialTheme.colorScheme.onSecondary,
fontSize = markerFontSize.sp,
fontFamily = markerFontFamily
)
)
if (marker != markerList.last()) {
Spacer(modifier = Modifier.width(2.dp))
}
}
}
}
}
@Composable
private fun TuoEditDialogWrapper(
oneTUO: TimeUnitObject,
globalIndex: Int,
currentStanza: Int,
menuOffset: Offset,
sharedScreenModel: SharedScreenModel,
lines: List<String>,
onDismiss: () -> Unit,
onSaveSuccess: () -> Unit,
onIndexChanged: (Int) -> Unit
) {
val template = oneTUO.pTemplate.template
val u0Idx = lines.indexOfFirst { it.startsWith("U0:") }
val t0Idx = lines.indexOfFirst { it.startsWith("T0:") }
val fullLine = when {
t0Idx != -1 -> {
lines[t0Idx]
}
u0Idx != -1 -> {
lines[u0Idx]
}
else -> ""
}
val afterU0T0 = fullLine.substringAfter("0:")
val match = blankPrefixRegex.find(afterU0T0)
val blankPrefix = match?.groups?.get(1)?.value ?: ""
val anchorsByVoice = (0..3).associate { voiceIdx ->
val linePrefix = "N${voiceIdx + 1}:"
val fullLine = lines.firstOrNull { it.startsWith(linePrefix) } ?: ""
voiceIdx to extractAnchorFromNoteLine(fullLine)
}
val editState = TUOEditState(
tuoIndex = oneTUO.firstTuoIndex,
notesByVoice = (0..3).associate { i ->
val rawNote = oneTUO.tuNotes.getOrNull(i + 1)?.toString() ?: ""
val fixedNote = autoFixNote(rawNote, template)
i to fixedNote
},
lyricsByStanza = (1..currentStanza).associate { s ->
s to oneTUO.lyricsAsMultiString(currentStanza)
}.toMutableMap(),
templateFragment = oneTUO.pTemplate.template,
marker = listOfNotNull(
oneTUO.pTemplate.markerToString().takeIf { it.isNotBlank() },
oneTUO.hasHairPin()?.toString()?.takeIf { it.isNotBlank() }
).joinToString(" "),
silentDuration = blankPrefix,
sep = oneTUO.sep0
)
TUODetailDialog(
editState = editState,
tuo = oneTUO,
currentStanza = currentStanza,
menuPosition = IntOffset(
menuOffset.x.roundToInt(),
menuOffset.y.roundToInt()
),
globalIndex = globalIndex,
isEditable = true,
canAdd = false,
sharedScreenModel = sharedScreenModel,
anchorsByVoice = anchorsByVoice,
onDismiss = onDismiss,
onSave = { newState ->
sharedScreenModel.openTUOEditor(newState)
onSaveSuccess()
},
onIndexChanged = onIndexChanged
)
}
@Composable
fun makeSpaceBetweenSyllables(
textMeasurer: TextMeasurer,