feufaro/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt
2025-07-08 11:58:49 +02:00

239 lines
No EOL
9.8 KiB
Kotlin

package mg.dot.feufaro
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import mg.dot.feufaro.musicXML.MusicXML
import feufaro.composeapp.generated.resources.Res
import feufaro.composeapp.generated.resources.compose_multiplatform
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mg.dot.feufaro.solfa.LazyVerticalGridTUO
import mg.dot.feufaro.solfa.Solfa
import org.koin.compose.koinInject
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.window.Popup
import kotlin.math.roundToInt
@Composable
@Preview
fun App(sharedViewModel: SharedViewModel = SharedViewModel()
) {
val fileRepository = koinInject<FileRepository>()
val displayConfigManager = koinInject<DisplayConfigManager>()
val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState()
// Load Configurations
val configScope = CoroutineScope(Dispatchers.Default)
var showContextualMenu by remember { mutableStateOf(false)}
var menuPosition by remember { mutableStateOf(Offset.Zero)}
LaunchedEffect(Unit) {
configScope.launch {
try {
displayConfigManager.loadConfigFromFile("assets://config.json")
} catch (e: Exception) {
e.printStackTrace()
}
}
}
val solfa = Solfa(sharedViewModel, fileRepository)
LaunchedEffect(currentDisplayConfig) {
if (currentDisplayConfig.playlist.isNotEmpty()) {
try {
sharedViewModel.setPlaylist(currentDisplayConfig.playlist)
solfa.loadNextInPlaylist()
} catch (e: Exception) {
println("Error loading playlist : ${e.message}")
}
}
}
val musicXML = MusicXML(fileRepository)
// Start App
//solfa.parse()
MaterialTheme {
MaterialTheme {
var showContent by remember { mutableStateOf(false) }
var gridWidthPx by remember { mutableStateOf(0) }
Column(
Modifier.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
offset ->
menuPosition = offset
showContextualMenu = true
}
)
},
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (showContextualMenu) {
Popup(
alignment = Alignment.TopStart,
offset = IntOffset(menuPosition.x.roundToInt(),
menuPosition.y.roundToInt()),
onDismissRequest = {
showContextualMenu = false
}
) {
ContextualMenu(onMenuItemClick = { item ->
println("Clicked: $item")
showContextualMenu = false
})
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.verticalScroll(rememberScrollState())
) {
val stanza: Int by sharedViewModel.stanza.collectAsState()
FlowRow(
modifier = Modifier.fillMaxWidth()
.windowInsetsPadding(WindowInsets.safeDrawing)
.padding(start = 8.dp, end = 8.dp, top = 8.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
val measureString: String by sharedViewModel.measure.collectAsState()
val songTitle: String by sharedViewModel.songTitle.collectAsState()
val songKey: String by sharedViewModel.songKey.collectAsState()
Text(text = songTitle, fontWeight = FontWeight.Bold)
Text(
text = songKey,
modifier = Modifier.background(Color(0xff, 0xea, 0xe7))
.padding(horizontal = 4.dp)
)
Text(text = measureString)
Text(text = "Stanza: $stanza")
val songAuthor: String by sharedViewModel.songAuthor.collectAsState()
val songComposer: String by sharedViewModel.songComposer.collectAsState()
val songRhythm: String by sharedViewModel.songRhythm.collectAsState()
val nbStanzas: Int by sharedViewModel.nbStanzas.collectAsState()
Text(text = songAuthor, fontStyle = FontStyle.Italic)
Text(text = songRhythm)
Text(text = songComposer)
Row() {
for (i in 1..nbStanzas) {
MGButton(
enabled = (i != stanza.toInt()),
onClick = {
sharedViewModel.setStanza(i.toString())
},
modifier = Modifier.padding(horizontal = 4.dp)
) {
Text("$i")
}
}
}
}
LazyVerticalGridTUO(
sharedViewModel,
gridWidthPx = gridWidthPx,
onGridWidthMeasured = { width -> gridWidthPx = width }
)
MGButton(onClick = {
//showContent = !showContent
solfa.loadNextInPlaylist()
}, modifier = Modifier.height(40.dp)) {
val debugData: String by sharedViewModel.data.collectAsState()
Text(debugData)
}
AnimatedVisibility(showContent) {
val greeting = remember { Greeting().greet() }
Column(
Modifier.fillMaxWidth().height(180.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(painterResource(Res.drawable.compose_multiplatform), null)
Text("Compose: $greeting")
}
}
}
}
}
}
}
fun String?.toColor(foreGround: Boolean = false): Color {
val defaultColor = if (foreGround) {
"00FFFFFF"
} else {
"00008800"
}
var hex = this?.removePrefix("#") ?: defaultColor
if (hex.length == 6) {
hex = "00" + hex
}
val red = hex.substring(2, 4).toInt(16)
val green = hex.substring(4, 6).toInt(16)
val blue = hex.substring(6, 8).toInt(16)
return try {
Color(red, green, blue)
} catch (e: NumberFormatException) {
if (foreGround) Color.White else Color.Black
}
}
@Composable
fun MGButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
content: @Composable RowScope.() -> Unit
) {
val displayConfigManager = koinInject<DisplayConfigManager>()
val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState()
val buttonColors = ButtonDefaults.buttonColors(
containerColor = currentDisplayConfig.buttonContainerColorHex.toColor(),
disabledContainerColor = currentDisplayConfig.buttonDisabledContainerColorHex.toColor(),
contentColor = currentDisplayConfig.buttonDisabledContentColorHex.toColor(),
disabledContentColor = currentDisplayConfig.buttonDisabledContentColorHex.toColor()
)
Button(
onClick = onClick,
modifier = modifier,
enabled = enabled,
colors = buttonColors,
content = content
)
}