Improve ui/ux and dynamic theme & fontsize controls on Settings

This commit is contained in:
Hasinjato 2026-06-22 10:06:19 +03:00
parent e91bc7d08e
commit 4070315b94
15 changed files with 942 additions and 421 deletions

View file

@ -1,6 +1,6 @@
{ {
"themeMode": "DARK", "themeMode": "LIGHT",
"fontSize": 18.5, "fontSize": 14,
"playlist": [ "playlist": [
"assets://a.txt", "assets://a.txt",
"assets://ffpm-617.txt", "assets://ffpm-617.txt",

View file

@ -4,6 +4,7 @@ import SharedScreenModel
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@ -14,33 +15,26 @@ import kotlinx.coroutines.launch
import org.koin.compose.koinInject import org.koin.compose.koinInject
import cafe.adriel.voyager.navigator.CurrentScreen import cafe.adriel.voyager.navigator.CurrentScreen
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import mg.dot.feufaro.ui.FeufaroTheme
import mg.dot.feufaro.viewmodel.SolfaScreenModel import mg.dot.feufaro.viewmodel.SolfaScreenModel
@Composable @Composable
fun App() { fun App() {
val displayConfigManager = koinInject<DisplayConfigManager>()
val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState()
FeufaroTheme(themeModeSelected = currentDisplayConfig.themeMode, userFontSize = currentDisplayConfig.fontSize) {
Navigator(screen = ScreenSolfa) { Navigator(screen = ScreenSolfa) {
CurrentScreen() CurrentScreen()
} }
}
val fileRepository = koinInject<FileRepository>() val fileRepository = koinInject<FileRepository>()
val displayConfigManager = koinInject<DisplayConfigManager>()
val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState()
// Load Configurations
val configScope = CoroutineScope(Dispatchers.Default) val configScope = CoroutineScope(Dispatchers.Default)
val sharedScreenModel = koinInject<SharedScreenModel>() val sharedScreenModel = koinInject<SharedScreenModel>()
val solfaScreenModel = koinInject<SolfaScreenModel>() val solfaScreenModel = koinInject<SolfaScreenModel>()
LaunchedEffect(Unit) {
configScope.launch {
try {
displayConfigManager.loadConfigFromFile("assets://config.json")
} catch (e: Exception) {
e.printStackTrace()
}
}
}
LaunchedEffect(currentDisplayConfig) { LaunchedEffect(currentDisplayConfig) {
if (currentDisplayConfig.playlist.isNotEmpty()) { if (currentDisplayConfig.playlist.isNotEmpty()) {
if (DeepLinkHandler.hasConsumedDeepLink) { if (DeepLinkHandler.hasConsumedDeepLink) {
@ -94,10 +88,10 @@ fun MGButton(
val displayConfigManager = koinInject<DisplayConfigManager>() val displayConfigManager = koinInject<DisplayConfigManager>()
val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState() val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState()
val buttonColors = ButtonDefaults.buttonColors( val buttonColors = ButtonDefaults.buttonColors(
containerColor = currentDisplayConfig.buttonContainerColorHex.toColor(), containerColor = MaterialTheme.colorScheme.error,
disabledContainerColor = currentDisplayConfig.buttonDisabledContainerColorHex.toColor(), contentColor = MaterialTheme.colorScheme.onError,
contentColor = currentDisplayConfig.buttonDisabledContentColorHex.toColor(), disabledContainerColor = MaterialTheme.colorScheme.error.copy(alpha = 0.6f),
disabledContentColor = currentDisplayConfig.buttonDisabledContentColorHex.toColor() disabledContentColor = MaterialTheme.colorScheme.onError.copy(alpha = 0.7f)
) )
Button( Button(
onClick = onClick, onClick = onClick,

View file

@ -1,14 +1,16 @@
package mg.dot.feufaro package mg.dot.feufaro
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import mg.dot.feufaro.config.AppConfigJson import mg.dot.feufaro.config.AppConfigJson
import mg.dot.feufaro.config.DisplayConfig import mg.dot.feufaro.config.DisplayConfig
import kotlinx.serialization.json.Json import java.util.prefs.Preferences
import kotlin.String
import kotlinx.serialization.Serializable
@Serializable @Serializable
data class AppConfigJson( data class AppConfigJson(
@ -36,20 +38,29 @@ class DisplayConfigManager(
private val fileRepository: FileRepository private val fileRepository: FileRepository
) )
{ {
private val _displayConfig = MutableStateFlow( private val prefs = Preferences.userRoot().node("mg.dot.feufaro")
DisplayConfig( private val _displayConfig = MutableStateFlow(DisplayConfig())
themeMode = "SYSTEM",
fontSize = 16f,
playlist = listOf(),
buttonContainerColorHex = "#FF000000",
buttonContentColorHex = "#FFFFFFFF",
buttonDisabledContainerColorHex = "#FF888888",
buttonDisabledContentColorHex = "#FFAA6666"
),
)
val displayConfig: StateFlow<DisplayConfig> = _displayConfig.asStateFlow() val displayConfig: StateFlow<DisplayConfig> = _displayConfig.asStateFlow()
private val configScope = CoroutineScope(Dispatchers.Default)
init {
loadInitialConfig()
}
private fun loadInitialConfig() {
configScope.launch {
loadConfigFromFile("assets://config.json")
val jsonConfig = _displayConfig.value
val finalTheme = prefs.get("themeMode", jsonConfig.themeMode)
val finalFontSize = prefs.getFloat("fontSize", jsonConfig.fontSize)
_displayConfig.value = jsonConfig.copy(
themeMode = finalTheme,
fontSize = finalFontSize
)
}
}
suspend fun loadConfigFromFile(filePath: String) { suspend fun loadConfigFromFile(filePath: String) {
try { try {
val jsonString = fileRepository.readFileContent(filePath) val jsonString = fileRepository.readFileContent(filePath)
@ -61,5 +72,29 @@ class DisplayConfigManager(
e.printStackTrace() e.printStackTrace()
} }
} }
fun updateThemeMode(themeMode: String) {
prefs.put("themeMode", themeMode)
prefs.flush()
_displayConfig.value = _displayConfig.value.copy(themeMode = themeMode)
}
fun updateFontSize(fontSize: Float) {
prefs.putFloat("fontSize", fontSize)
prefs.flush()
_displayConfig.value = _displayConfig.value.copy(fontSize = fontSize)
}
fun resetToDefault() {
try {
prefs.remove("themeMode")
prefs.remove("fontSize")
prefs.flush()
loadInitialConfig()
} catch (e: Exception) {
println("Erreur lors de la réinitialisation : ${e.message}")
}
}
// NOUVELLE MÉTHODE : Pour basculer le mode débogage // NOUVELLE MÉTHODE : Pour basculer le mode débogage
} }

View file

@ -11,6 +11,7 @@ import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Edit 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.Text import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@ -151,14 +152,15 @@ object ScreenSolfa : Screen {
val measureString: String by sharedScreenModel.measure.collectAsState() val measureString: String by sharedScreenModel.measure.collectAsState()
val songTitle: String by sharedScreenModel.songTitle.collectAsState() val songTitle: String by sharedScreenModel.songTitle.collectAsState()
val songKey: String by sharedScreenModel.songKey.collectAsState() val songKey: String by sharedScreenModel.songKey.collectAsState()
Text(text = songTitle, fontWeight = FontWeight.Bold) Text(text = songTitle, style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Bold)
Text( Text(
text = songKey, text = songKey,
modifier = Modifier.background(Color(0xff, 0xea, 0xe7)) style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.background(MaterialTheme.colorScheme.tertiary.copy(alpha = 0.1f))
.padding(horizontal = 4.dp) .padding(horizontal = 4.dp)
) )
Text(text = measureString) Text(text = measureString, style = MaterialTheme.typography.bodyLarge)
Text(text = "Stanza: $stanza") Text(text = "Stanza: $stanza", style = MaterialTheme.typography.bodyLarge)
//ScreenTranspose.Content() //ScreenTranspose.Content()
} }
LazyVerticalGridTUO( LazyVerticalGridTUO(

View file

@ -4,12 +4,12 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class DisplayConfig ( data class DisplayConfig (
val themeMode: String, val themeMode: String = "LIGHT",
val fontSize: Float, val fontSize: Float = 14f,
val playlist: List<String>, val playlist: List<String> = emptyList(),
val buttonContainerColorHex: String, val buttonContainerColorHex: String = "#737EFC",
val buttonContentColorHex: String , val buttonContentColorHex: String = "#FFFFFF",
val buttonDisabledContainerColorHex: String, val buttonDisabledContainerColorHex: String = "#CCCCCC",
val buttonDisabledContentColorHex: String , val buttonDisabledContentColorHex: String = "#888888"
) )

View file

@ -91,10 +91,16 @@ fun TUODetailDialog(
if (existing.isEmpty() && canAdd) add("_") else add(existing) if (existing.isEmpty() && canAdd) add("_") else add(existing)
} }
} }
val lyricsLines = result val lyricsLines = remember(editState.lyricsByStanza) {
.flatMap { it.split("\n") } mutableStateListOf<String>().apply {
.map { it.trim() } addAll(editState.lyricsByStanza.values.toList())
.toMutableList() }
}
val editedLyricsMap = remember {
mutableStateMapOf<Int, String>().apply {
putAll(editState.lyricsByStanza)
}
}
var canAddMark = mutableStateOf(false) var canAddMark = mutableStateOf(false)
Popup( Popup(
@ -111,7 +117,7 @@ fun TUODetailDialog(
.widthIn(max=125.dp) .widthIn(max=125.dp)
.heightIn(max = 400.dp), .heightIn(max = 400.dp),
shape = MaterialTheme.shapes.small, shape = MaterialTheme.shapes.small,
color = Color(0xFF2D2D2D).copy(0.75f), color = MaterialTheme.colorScheme.onSecondaryContainer.copy(0.75f),
) { ) {
Column(modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp).fillMaxWidth()) { Column(modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp).fillMaxWidth()) {
Row ( Row (
@ -269,7 +275,7 @@ fun TUODetailDialog(
}) { }) {
Text( Text(
text = "/", text = "/",
color = Color.Cyan, color = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f),
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center textAlign = TextAlign.Center
@ -283,7 +289,7 @@ fun TUODetailDialog(
}) { }) {
Icon( Icon(
imageVector = Icons.Default.Add, imageVector = Icons.Default.Add,
tint = Color.Cyan, tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f),
contentDescription = null contentDescription = null
) )
} }
@ -297,7 +303,7 @@ fun TUODetailDialog(
MyTextEditField( MyTextEditField(
value = templateFragment, value = templateFragment,
customFontSize = 14.sp, customFontSize = 14.sp,
color = Color.Yellow, color = MaterialTheme.colorScheme.tertiary.copy(alpha = 1.5f),
customPadding = 8.dp, customPadding = 8.dp,
customBrush = SolidColor(Color.White), customBrush = SolidColor(Color.White),
isEditable = false, isEditable = false,
@ -313,7 +319,7 @@ fun TUODetailDialog(
MyTextEditField( MyTextEditField(
value = marker, value = marker,
customFontSize = 14.sp, customFontSize = 14.sp,
color = Color.Yellow, color = MaterialTheme.colorScheme.tertiary.copy(alpha = 1.5f),
customPadding = 8.dp, customPadding = 8.dp,
customBrush = SolidColor(Color.White), customBrush = SolidColor(Color.White),
isEditable = isEditable, isEditable = isEditable,
@ -342,7 +348,7 @@ fun TUODetailDialog(
}) { }) {
Icon( Icon(
imageVector = Icons.Default.Add, imageVector = Icons.Default.Add,
tint = Color.Green, tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f),
contentDescription = null contentDescription = null
) )
} }
@ -366,7 +372,7 @@ fun TUODetailDialog(
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 12.dp), .padding(bottom = 12.dp),
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
color = if (firstValidation.isValid) Color(0xFF10B981) else Color(0xFFF59E0B), color = if (firstValidation.isValid) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.tertiary.copy(alpha = 1.5f),
shadowElevation = 2.dp shadowElevation = 2.dp
) { ) {
Row( Row(
@ -414,6 +420,14 @@ fun TUODetailDialog(
// --- SECTION LYRICS --- // --- SECTION LYRICS ---
lyricsLines.forEachIndexed { index, line -> lyricsLines.forEachIndexed { index, line ->
val displayedLine = line
LaunchedEffect(displayedLine) {
if (displayedLine != line) {
lyricsLines[index] = 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)
@ -421,19 +435,48 @@ fun TUODetailDialog(
Column( Column(
modifier = Modifier.fillMaxWidth(0.8f) modifier = Modifier.fillMaxWidth(0.8f)
) { ) {
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( MyTextEditField(
value = line, value = displayedLine,
customFontSize = 13.sp, customFontSize = 13.sp,
color = Color.White, color = Color.White,
customPadding = 8.dp, customPadding = 8.dp,
customBrush = SolidColor(Color.White), customBrush = SolidColor(Color.White),
isEditable = isEditable, isEditable = isEditable,
isAddable = canAdd, isAddable = canAdd,
isWarn = !isLyricsValid,
funTransform = { input -> transformLyricsInput(input, templateFragment) },
onValueChng = { newValue -> onValueChng = { newValue ->
lyricsLines[index] = 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) { if (isEditable || canAdd) {
IconButton( IconButton(
onClick = { onClick = {
@ -443,7 +486,7 @@ fun TUODetailDialog(
}) { }) {
Icon( Icon(
imageVector = if (index == 0) Icons.Default.Add else Icons.Default.Clear, imageVector = if (index == 0) Icons.Default.Add else Icons.Default.Clear,
tint = if (index == 0) Color.Green else Color.Red, tint = if (index == 0) MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f) else MaterialTheme.colorScheme.tertiary.copy(alpha = 1.5f),
contentDescription = null contentDescription = null
) )
} }
@ -467,12 +510,17 @@ fun TUODetailDialog(
if (isEditable || canAdd) { if (isEditable || canAdd) {
IconButton( IconButton(
onClick = { onClick = {
val finalLyricsMap = editState.lyricsByStanza.toMutableMap()
finalLyricsMap.putAll(editedLyricsMap)
if (!finalLyricsMap.containsKey(currentStanza)) {
finalLyricsMap[currentStanza] = lyricsLines.getOrNull(0)?.trim() ?: ""
}
val state = TUOEditState( val state = TUOEditState(
tuoIndex = globalIndex, tuoIndex = globalIndex,
notesByVoice = notes.toMap(), notesByVoice = notes.toMap(),
originalNotes = originalNotes.toMap(), originalNotes = originalNotes.toMap(),
originalLyricsByStanza = originalLyricsByStz.toMutableMap(), originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
lyricsByStanza = mutableMapOf(currentStanza to lyricsLines.joinToString(" ")), lyricsByStanza = finalLyricsMap,
templateFragment = templateFragment, templateFragment = templateFragment,
marker = if(marker.isEmpty()) "_" else marker, marker = if(marker.isEmpty()) "_" else marker,
originalSep = initialSep, originalSep = initialSep,
@ -485,7 +533,7 @@ fun TUODetailDialog(
Icon( Icon(
Icons.Default.Build, Icons.Default.Build,
contentDescription = null, contentDescription = null,
tint = Color.Green tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f)
) )
} }
} }
@ -494,6 +542,102 @@ fun TUODetailDialog(
} }
} }
} }
private fun unpackLyrics(lyrics: String): String {
val comments = Solfa.REGEX_COMMENT.findAll(lyrics)
val commentsIterator = comments.iterator()
val loadedLyrics = lyrics
.replace(Solfa.REGEX_LYRICS_REPETITION) { matchResult ->
val repeating = matchResult.destructured.match.groupValues[1]
"_".repeat(repeating.toString().toInt())
}
.replace(Regex("(?<![\\?:,\\.; ])_"), "-_")
.replace(Regex("_-(?=_)"), "_")
.replace(" -_", "_")
.replace("--_", "-_")
.replace(Regex("_$"), "")
val lyricsFinal = Solfa.REGEX_COMMENT.replace(loadedLyrics) { matchResult ->
commentsIterator.next().value
}
return lyricsFinal
}
private fun smartYLyrics(lyrics: String): String {
val comments = Solfa.REGEX_COMMENT.findAll(lyrics)
val commentsIterator = comments.iterator()
val loadedLyrics = lyrics
.replace(Solfa.REGEX_VOWELS_STAGE1, "$0_")
.replace(Solfa.REGEX_VOWELS_STAGE2, "$1_")
.replace(Solfa.REGEX_VOWELS_STAGE3, "$1_")
.replace(" ", " _")
.replace("_\\ _", " ")
.replace("_\\", "")
.replace("_0", "")
.replace(Solfa.REGEX_MALAGASY_MN, "$1$2_$3")
.replace(Solfa.REGEX_MALAGASY_MN_STAGE2, "$1-_")
.replace("_n'", "n'_")
val lyricsFinal = Solfa.REGEX_COMMENT.replace(loadedLyrics) { matchResult ->
commentsIterator.next().value
}
return unpackLyrics(lyricsFinal)
}
public fun transformLyricsInput(input: String, template: String): String {
val templateCount = template.count { it.isLetter() && it.lowercaseChar() != 'z' }
val processed = smartYLyrics(input)
val currentSyllables = processed.split("_").filter { it.isNotEmpty() }
val currentCount = currentSyllables.size
// println("\n\n\tDEBUG [Start] Input: '$input' | TemplateCount: $templateCount | Found: $currentCount ${currentSyllables.joinToString("|")}")
// println("DEBUG [Segments]: $currentSyllables")
val rawResult = if (currentCount <= templateCount) {
// println("DEBUG [Status]: Pas de fusion nécessaire.")
input
} else {
val parts = currentSyllables.toMutableList()
val excess = currentCount - templateCount
// println("DEBUG [Fusion]: Besoin de fusionner $excess fois.")
for (i in 0 until excess) {
val idx = parts.size - 2/* - i*/
if (idx >= 0) {
// println("DEBUG [Loop $i]: Fusion de l'index $idx avec ${idx + 1}")
// println("DEBUG [Avant]: '${parts[idx]}' et '${parts[idx + 1]}'")
val part1 = parts[idx].trimEnd()
val part2 = parts[idx + 1].trimStart()
val cleanPart1 = part1.replace("-", "")
val lastChar = if (cleanPart1.isNotEmpty()) cleanPart1.last() else ' '
val endsWithVowel = "aeiouyòàéìỳAEIOUY".contains(lastChar)
val isVowelConsonantPair = endsWithVowel && part2.length == 1 && !"aeiouyòàéìỳAEIOUY".contains(part2.first())
if (part2.isNotEmpty()) {
if (isVowelConsonantPair) {
parts[idx] = part1 + part2
} else {
parts[idx] = part1 + "\\ " + part2
}
} else {
parts[idx] = part1 + "\\"
}
parts.removeAt(idx + 1)
}
// println("DEBUG [Après]: Résultat index $idx -> '${parts}'")
}
val result = parts.joinToString("")
// println("DEBUG [Final]: '$result'")
result
}
val REGEX_MALAGASY_MN_OUTPUT = Regex("-([mn])")
val correctedResult = rawResult.replace(REGEX_MALAGASY_MN_OUTPUT, "$1-")
if (correctedResult != rawResult) {
// println("DEBUG [Correction MN]: '$rawResult' -> '$correctedResult'")
}
return correctedResult
}
private fun transformMusicalInput(input: String): String { private fun transformMusicalInput(input: String): String {
return input.lowercase() return input.lowercase()
.replace(";", "• ,") .replace(";", "• ,")
@ -585,7 +729,7 @@ fun MyTextEditField(
.background(Color.Black.copy(alpha = 0.3f), RoundedCornerShape(4.dp)) .background(Color.Black.copy(alpha = 0.3f), RoundedCornerShape(4.dp))
.border( .border(
if (isWarn!!) 1.dp else 0.dp, if (isWarn!!) 1.dp else 0.dp,
if (isWarn!!) Color(0xFFF59E0B) else Color.Transparent, if (isWarn!!) MaterialTheme.colorScheme.tertiary.copy(alpha = 1.5f) else Color.Transparent,
shape = RoundedCornerShape(4.dp) shape = RoundedCornerShape(4.dp)
) )
.padding(customPadding), .padding(customPadding),

View file

@ -268,11 +268,11 @@ fun TimeUnitComposable(
parentFocusRequester: FocusRequester? = null, parentFocusRequester: FocusRequester? = null,
transpositionInterval: Int transpositionInterval: Int
) { ) {
val col = if (tuo.getNum() % 2 == 0) Color(0xff, 0xfa, 0xf7) else Color(0xfb, 0xf3, 0xff) 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 by animateColorAsState(
targetValue = if (gridActive) Color.Cyan.copy(alpha = 0.5f) else col, targetValue = if (gridActive) MaterialTheme.colorScheme.secondary.copy(alpha = 1f) else col,
animationSpec = tween(durationMillis = 100) // Très court pour rester réactif animationSpec = tween(durationMillis = 100) // Très court pour rester réactif
) )
val focusRequesters = remember { List(4) { FocusRequester() } } val focusRequesters = remember { List(4) { FocusRequester() } }
@ -449,7 +449,7 @@ fun TimeUnitComposable(
fontSize = 10.sp, fontSize = 10.sp,
baselineShift = BaselineShift.Superscript, baselineShift = BaselineShift.Superscript,
//fontStyle = FontStyle.Italic, //fontStyle = FontStyle.Italic,
color = FEUFAROO_KEY_CHANGE_COLOR color = MaterialTheme.colorScheme.primary
)) { )) {
append(text+" ") append(text+" ")
} }
@ -459,6 +459,7 @@ fun TimeUnitComposable(
} }
Text( Text(
text = annotatedText, text = annotatedText,
style = MaterialTheme.typography.bodyLarge,
onTextLayout = { result -> onTextLayout = { result ->
textLayoutResult = result textLayoutResult = result
} }
@ -768,8 +769,8 @@ fun LazyVerticalGridTUO(
fontStyle = fontStyle, fontStyle = fontStyle,
fontWeight = fontWeight, fontWeight = fontWeight,
style = TextStyle( style = TextStyle(
color = Color.Black, color = MaterialTheme.colorScheme.onSecondaryContainer,
fontSize = 17.sp fontSize = MaterialTheme.typography.titleMedium.fontSize
) )
) )
} }
@ -792,11 +793,11 @@ fun LazyVerticalGridTUO(
.width(gridWidthDp / gridColumnCount) .width(gridWidthDp / gridColumnCount)
.border( .border(
width = if (isSelectedByKeyboard && editMode) 2.dp else 0.dp, width = if (isSelectedByKeyboard && editMode) 2.dp else 0.dp,
color = if (isSelectedByKeyboard && editMode) Color(0xFF9C27B0) else Color.Transparent, color = if (isSelectedByKeyboard && editMode) MaterialTheme.colorScheme.error else Color.Transparent,
shape = RoundedCornerShape(4.dp) shape = RoundedCornerShape(4.dp)
) )
.background( .background(
if (isHovered) Color.Black.copy(alpha = 0.05f) else Color.Transparent, if (isHovered) MaterialTheme.colorScheme.onSecondary.copy(alpha = 0.05f) else Color.Transparent,
RoundedCornerShape(4.dp) RoundedCornerShape(4.dp)
) )
.combinedClickable( .combinedClickable(
@ -994,18 +995,18 @@ fun LazyVerticalGridTUO(
text = dynamicSpaceSyl, text = dynamicSpaceSyl,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(end = 4.dp)/*.border(1.dp, Color.Yellow, RectangleShape)*/ .padding(end = 4.dp)
.wrapContentSize(unbounded = true, align = alignmentText), .wrapContentSize(unbounded = true, align = alignmentText),
softWrap = false, softWrap = false,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Visible, overflow = TextOverflow.Visible,
style = TextStyle( style = TextStyle(
color = when { color = when {
(isTooLong) -> Color(0XFF121212) (isTooLong) -> MaterialTheme.colorScheme.onBackground
(index == 0) -> Color.Black (index == 0) -> MaterialTheme.colorScheme.onBackground
else -> Color(0XFF3B3A39) else -> MaterialTheme.colorScheme.onBackground
}, },
fontSize = 16.sp, fontSize = MaterialTheme.typography.titleMedium.fontSize,
), ),
onTextLayout = { txtLayoutRes -> onTextLayout = { txtLayoutRes ->
textWidth = with(density) { txtLayoutRes.size.width.toDp() } textWidth = with(density) { txtLayoutRes.size.width.toDp() }

View file

@ -2,6 +2,8 @@ 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.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.*
@ -24,7 +26,10 @@ 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.pointerInput
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -169,6 +174,8 @@ fun MainScreenWithDrawer(
} }
} }
val isQrVisible = sharedScreenModel.isQRCodeVisible.value
ModalNavigationDrawer(drawerState = drawerState, drawerContent = { ModalNavigationDrawer(drawerState = drawerState, drawerContent = {
SimpleDrawerContent( SimpleDrawerContent(
items, items,
@ -350,17 +357,20 @@ fun MainScreenWithDrawer(
maxLines = 1, maxLines = 1,
softWrap = false, softWrap = false,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.headlineLarge
) )
} }
} }
} }
} }
}, navigationIcon = { }, navigationIcon = {
if(!isQrVisible) {
IconButton(onClick = { IconButton(onClick = {
scope.launch { drawerState.open() } scope.launch { drawerState.open() }
}) { }) {
Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu") Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu")
} }
}
}, actions = { }, actions = {
var tempInterval by remember(fileContent) { mutableStateOf(0) } var tempInterval by remember(fileContent) { mutableStateOf(0) }
var isEyeVisible by remember { mutableStateOf(false) } var isEyeVisible by remember { mutableStateOf(false) }
@ -382,7 +392,7 @@ fun MainScreenWithDrawer(
) { ) {
Text( Text(
text = tempUiKey, text = tempUiKey,
fontSize = 25.sp, style = MaterialTheme.typography.displaySmall,
fontWeight = FontWeight.Black, fontWeight = FontWeight.Black,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
color = if (isCurrentlyTransposed && !isPendingChange) Color(0xFFFFD700) else Color.White, color = if (isCurrentlyTransposed && !isPendingChange) Color(0xFFFFD700) else Color.White,
@ -441,7 +451,7 @@ fun MainScreenWithDrawer(
} }
val centralIcon = if (isPendingChange) Icons.Filled.Check else Icons.Filled.SwapHoriz val centralIcon = if (isPendingChange) Icons.Filled.Check else Icons.Filled.SwapHoriz
val centralTint = if (isPendingChange) Color(0xFFFFD700) else Color.White val centralTint = if (isPendingChange) MaterialTheme.colorScheme.tertiary else Color.White
val tooltipText = if (isPendingChange) "Transposer en $tempUiKey" else if (isCurrentlyTransposed) "Revenir en $songKey" else "Transposer" val tooltipText = if (isPendingChange) "Transposer en $tempUiKey" else if (isCurrentlyTransposed) "Revenir en $songKey" else "Transposer"
TooltipBox( TooltipBox(
@ -522,11 +532,9 @@ fun MainScreenWithDrawer(
}, },
modifier = Modifier.fillMaxWidth().height(36.dp), modifier = Modifier.fillMaxWidth().height(36.dp),
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
border = BorderStroke(1.dp, Color(0xFFFFD700).copy(alpha = 0.5f)), border = BorderStroke(1.dp, MaterialTheme.colorScheme.tertiary.copy(alpha = 0.5f)),
colors = ButtonDefaults.outlinedButtonColors( colors = ButtonDefaults.outlinedButtonColors(
contentColor = Color( contentColor = MaterialTheme.colorScheme.tertiary
0xFFFFD700
)
) )
) { ) {
Text( Text(
@ -571,7 +579,6 @@ fun MainScreenWithDrawer(
} }
} }
}, },
containerColor = Color(0x20000000),
icon = Icons.AutoMirrored.Default.Undo icon = Icons.AutoMirrored.Default.Undo
) )
} }
@ -593,7 +600,6 @@ fun MainScreenWithDrawer(
} }
} }
}, },
containerColor = Color(0x20000000),
icon = Icons.Filled.SaveAs icon = Icons.Filled.SaveAs
) )
} }
@ -607,11 +613,10 @@ fun MainScreenWithDrawer(
onClick = { onClick = {
sharedScreenModel.toggleEditorMode(false) sharedScreenModel.toggleEditorMode(false)
}, },
containerColor = Color(0x20000000),
icon = Icons.Default.Close icon = Icons.Default.Close
) )
} }
} else { } else if(!isQrVisible) {
AnimatedVisibility( AnimatedVisibility(
visible = isExpanded and !showMidiCtrl, visible = isExpanded and !showMidiCtrl,
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 }, enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
@ -623,7 +628,6 @@ fun MainScreenWithDrawer(
onClick = { onClick = {
sharedScreenModel.descGridCount(1) sharedScreenModel.descGridCount(1)
}, },
containerColor = Color(0x15000000),
size = 30.dp, size = 30.dp,
icon = Icons.Default.Remove icon = Icons.Default.Remove
) )
@ -633,7 +637,6 @@ fun MainScreenWithDrawer(
onClick = { onClick = {
sharedScreenModel.addGridCount(1) sharedScreenModel.addGridCount(1)
}, },
containerColor = Color(0x15000000),
size = 30.dp, size = 30.dp,
icon = Icons.Default.Add icon = Icons.Default.Add
) )
@ -650,7 +653,6 @@ fun MainScreenWithDrawer(
sharedScreenModel.toggleQRCodeVisibility() sharedScreenModel.toggleQRCodeVisibility()
sharedScreenModel.setExpandedFAB(false) sharedScreenModel.setExpandedFAB(false)
}, },
containerColor = Color(0x15000000),
icon = Icons.Filled.QrCode icon = Icons.Filled.QrCode
) )
} }
@ -665,7 +667,6 @@ fun MainScreenWithDrawer(
onClick = { onClick = {
showPrintSettings = !showPrintSettings showPrintSettings = !showPrintSettings
}, },
containerColor = Color(0x15000000),
icon = Icons.Filled.Print icon = Icons.Filled.Print
) )
} }
@ -679,7 +680,6 @@ fun MainScreenWithDrawer(
onClick = { onClick = {
sharedScreenModel.setMidiCtrl(!showMidiCtrl) sharedScreenModel.setMidiCtrl(!showMidiCtrl)
}, },
containerColor = Color(0x15000000),
icon = if (showMidiCtrl) Icons.Filled.StopCircle else Icons.Filled.PlayCircle icon = if (showMidiCtrl) Icons.Filled.StopCircle else Icons.Filled.PlayCircle
) )
} }
@ -690,7 +690,6 @@ fun MainScreenWithDrawer(
refreshTrigeer++ refreshTrigeer++
sharedScreenModel.loadNewSong("$midiFile") sharedScreenModel.loadNewSong("$midiFile")
}, },
containerColor = if (isExpanded) Color(0x25000000) else Color.Transparent,
icon = if (isExpanded) Icons.Filled.Close else Icons.Filled.Menu icon = if (isExpanded) Icons.Filled.Close else Icons.Filled.Menu
) )
} }
@ -748,17 +747,42 @@ fun MainScreenWithDrawer(
/*.windowInsetsPadding(currentInsets.union(WindowInsets.ime))*/ /*.windowInsetsPadding(currentInsets.union(WindowInsets.ime))*/
) { ) {
content(PaddingValues(0.dp)) content(PaddingValues(0.dp))
if (sharedScreenModel.isQRCodeVisible.value) {
AnimatedVisibility(
visible = isQrVisible,
enter = slideInVertically(
initialOffsetY = { fullHeight -> fullHeight }
) + fadeIn(),
exit = slideOutVertically(
targetOffsetY = { fullHeight -> fullHeight }
) + fadeOut()
) {
QRDisplay( QRDisplay(
sharedScreenModel = sharedScreenModel, sharedScreenModel = sharedScreenModel,
fileRepository = solfaScreenModel.fileRepository fileRepository = solfaScreenModel.fileRepository
) )
} }
var isHovered by remember { mutableStateOf(false) }
val offsetX by animateDpAsState(targetValue = if (isHovered || isSearchActive) 0.dp else 15.dp)
AnimatedVisibility( AnimatedVisibility(
visible = !isEditMode, visible = !isEditMode && !isQrVisible,
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.padding(16.dp) .padding(top = 15.dp)
.offset(x = offsetX)
.pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
when (event.type) {
PointerEventType.Enter -> isHovered = true
PointerEventType.Exit -> isHovered = false
}
}
}
}
) { ) {
IconButton( IconButton(
onClick = { onClick = {
@ -778,14 +802,14 @@ fun MainScreenWithDrawer(
.size(55.dp) .size(55.dp)
.alpha(0.6f) .alpha(0.6f)
.background( .background(
color = Color.Blue, color = MaterialTheme.colorScheme.tertiary,
shape = CircleShape shape = CircleShape
) )
) { ) {
Icon( Icon(
imageVector = if (isSearchActive) Icons.Default.Close else Icons.Default.Search, imageVector = if (isSearchActive) Icons.Default.Close else Icons.Default.Search,
contentDescription = null, contentDescription = null,
tint = Color.White tint = MaterialTheme.colorScheme.onTertiary
) )
} }
} }
@ -884,20 +908,19 @@ fun MainScreenWithDrawer(
@Composable @Composable
private fun MyFAB( private fun MyFAB(
onClick: () -> Unit, onClick: () -> Unit,
containerColor: Color,
size: Dp ?= 55.dp, size: Dp ?= 55.dp,
icon: ImageVector icon: ImageVector
) { ) {
FloatingActionButton( FloatingActionButton(
onClick = onClick, onClick = onClick,
modifier = Modifier.size(size!!), modifier = Modifier.size(size!!),
containerColor = containerColor, containerColor = MaterialTheme.colorScheme.onSecondary.copy(alpha = 0.1f),
elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp) elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp)
) { ) {
Icon( Icon(
imageVector = icon, imageVector = icon,
contentDescription = null, contentDescription = null,
tint = Color.Blue.copy(alpha = 0.75f) tint = MaterialTheme.colorScheme.primary
) )
} }
} }

View file

@ -23,6 +23,7 @@ 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.clip
import androidx.compose.ui.draw.paint import androidx.compose.ui.draw.paint
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.TransformOrigin
@ -121,8 +122,25 @@ fun MidiControlPanel(
) { ) {
Column( Column(
modifier = modifier modifier = modifier
.padding(24.dp)
.fillMaxWidth(if (platform.startsWith("Android")) 1f else 0.6f) .fillMaxWidth(if (platform.startsWith("Android")) 1f else 0.6f)
.background(color = Color.Gray.copy(alpha = 0.5f), shape = RoundedCornerShape(size = 5.dp)), .background(
color = MaterialTheme.colorScheme.onSecondary.copy(alpha = 0.12f),
shape = RoundedCornerShape(24.dp)
)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.8f),
shape = RoundedCornerShape(24.dp)
)
.shadow(
elevation = 40.dp,
shape = RoundedCornerShape(24.dp),
clip = false,
ambientColor = Color.Black.copy(alpha = 0.1f),
spotColor = Color.Black.copy(alpha = 0.3f)
),
/*.background(color = Color.Gray.copy(alpha = 0.5f), shape = RoundedCornerShape(size = 5.dp)),*/
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
AnimatedVisibility( AnimatedVisibility(
@ -130,40 +148,43 @@ fun MidiControlPanel(
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
Text("${currentPos.toInt() / 1000}s", color = Color.White) Text("${currentPos.toInt() / 1000}s", color = MaterialTheme.colorScheme.onSecondary, fontSize = MaterialTheme.typography.titleSmall.fontSize)
Slider( Slider(
value = currentPos, value = currentPos,
onValueChange = onSeek, onValueChange = onSeek,
valueRange = 0f..(if (duration > 0) duration else 1f), valueRange = 0f..(if (duration > 0) duration else 1f),
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
colors = SliderDefaults.colors( colors = SliderDefaults.colors(
thumbColor = Color.Red, activeTrackColor = MaterialTheme.colorScheme.primary,
activeTrackColor = Color.Green, inactiveTrackColor = Color.White.copy(alpha = 0.2f),
inactiveTrackColor = Color.Gray, thumbColor = MaterialTheme.colorScheme.secondary,
inactiveTickColor = Color(0xffb0BEC5), disabledThumbColor = Color.Gray,
disabledThumbColor = Color(0xff78909C), disabledActiveTrackColor = Color.Gray.copy(alpha = 0.5f)
disabledActiveTickColor = Color(0xff757575),
disabledActiveTrackColor = Color(0xffBDBDBD),
disabledInactiveTickColor = Color(0xff616161),
disabledInactiveTrackColor = Color(0xffBCAAA4),
), ),
thumb = { thumb = {
Box( Box(
modifier = Modifier modifier = Modifier
.size(15.dp) .size(12.dp)
.background(Color.Gray, CircleShape) .background(MaterialTheme.colorScheme.secondary, CircleShape)
.shadow(4.dp, CircleShape)
) )
}, },
track = { sliderState -> track = { sliderState ->
SliderDefaults.Track( SliderDefaults.Track(
sliderState = sliderState, sliderState = sliderState,
modifier = Modifier.height(5.dp) modifier = Modifier.height(6.dp).clip(CircleShape),
colors = SliderDefaults.colors(
activeTrackColor = MaterialTheme.colorScheme.primary,
inactiveTrackColor = MaterialTheme.colorScheme.onSecondary.copy(alpha = 0.6f)
),
drawStopIndicator = null,
thumbTrackGapSize = 2.dp,
) )
} }
) )
Text("${momo / 1000}s", color = Color.White) Text("${momo / 1000}s", color = MaterialTheme.colorScheme.onSecondary, fontSize = MaterialTheme.typography.titleSmall.fontSize)
} }
} }
@ -279,11 +300,16 @@ fun MidiControlPanel(
modifier = Modifier.padding(horizontal = 2.dp) modifier = Modifier.padding(horizontal = 2.dp)
) { displayedBpm -> ) { displayedBpm ->
val isCenter = displayedBpm == currentBpmInt val isCenter = displayedBpm == currentBpmInt
// 1. On prépare le style de manière propre en dehors du Text
val bpmTextStyle = if (isCenter) {
MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold)
} else {
MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Normal)
}
Text( Text(
text = "$displayedBpm", text = displayedBpm.toString(),
color = if (isCenter) Color.White else Color.Gray.copy(alpha = 0.4f), color = if (isCenter) Color.White else Color.Gray.copy(alpha = 0.4f),
fontSize = if (isCenter) 22.sp else 14.sp, style = bpmTextStyle,
fontWeight = if (isCenter) FontWeight.Bold else FontWeight.Normal,
modifier = Modifier modifier = Modifier
.clip(CircleShape) .clip(CircleShape)
.clickable(enabled = !isCenter) { updateTempoToBpm(displayedBpm) } .clickable(enabled = !isCenter) { updateTempoToBpm(displayedBpm) }
@ -308,7 +334,7 @@ fun MidiControlPanel(
Text( Text(
text = "bpm", text = "bpm",
color = Color.White.copy(0.6f), color = Color.White.copy(0.6f),
fontSize = 12.sp, fontSize = MaterialTheme.typography.bodyMedium.fontSize,
modifier = Modifier.padding(end = 8.dp) modifier = Modifier.padding(end = 8.dp)
) )
} }
@ -340,8 +366,8 @@ fun MidiControlPanel(
}, },
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = when { containerColor = when {
(isWaitingForB || isLooping) -> Color.Red (isWaitingForB || isLooping) -> MaterialTheme.colorScheme.error
else -> Color.LightGray else -> MaterialTheme.colorScheme.onError
} }
), ),
contentPadding = PaddingValues(horizontal = 2.dp, vertical = 0.dp), contentPadding = PaddingValues(horizontal = 2.dp, vertical = 0.dp),
@ -356,7 +382,7 @@ fun MidiControlPanel(
Text( Text(
text = "A", text = "A",
color = if (isWaitingForB || isLooping) Color.White else Color.Black, color = if (isWaitingForB || isLooping) Color.White else Color.Black,
fontSize = 16.sp fontSize = MaterialTheme.typography.titleLarge.fontSize
) )
Icon( Icon(
@ -369,7 +395,7 @@ fun MidiControlPanel(
Text( Text(
text = "B", text = "B",
color = if (isLooping) Color.White else Color.Black, color = if (isLooping) Color.White else Color.Black,
fontSize = 16.sp fontSize = MaterialTheme.typography.titleLarge.fontSize
) )
} }
} }
@ -388,106 +414,60 @@ fun MidiControlPanel(
Icon( Icon(
painter = painterResource(Res.drawable.ic_mixer_satb), painter = painterResource(Res.drawable.ic_mixer_satb),
contentDescription = "SATB", contentDescription = "SATB",
tint = if (showSATBTools) Color.White else Color.Black, tint = if (showSATBTools) MaterialTheme.colorScheme.background else MaterialTheme.colorScheme.onSecondary,
modifier = Modifier.size(35.dp) modifier = Modifier.size(35.dp)
) )
} }
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
val playPauseButton = @Composable {
IconButton(
onClick = onPlayPauseClick,
modifier = Modifier
.size(48.dp)
.background(MaterialTheme.colorScheme.primary, CircleShape)
) {
Icon(
imageVector = if (isPause) Icons.Filled.PlayArrow else Icons.Filled.Pause,
contentDescription = "Play/Pause",
tint = Color.White
)
}
}
val instrumentButton = @Composable {
IconButton(
onClick = {
isPianoSelected = !isPianoSelected
mediaPlayer?.changeInstru(if (isPianoSelected) 1 else 20)
}
) {
if (isPianoSelected) {
Icon(
imageVector = Icons.Default.Piano,
contentDescription = "Piano",
tint = MaterialTheme.colorScheme.onSecondary
)
} else {
Icon(
painter = painterResource(Res.drawable.ic_organ),
contentDescription = "Orgue",
tint = MaterialTheme.colorScheme.onSecondary,
modifier = Modifier.size(25.dp)
)
}
}
}
if (platform.startsWith("Android")) { if (platform.startsWith("Android")) {
Column( playPauseButton()
modifier = Modifier.wrapContentWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(
onClick = onPlayPauseClick,
modifier = Modifier.size(48.dp).background(MaterialTheme.colorScheme.primary, CircleShape)
) {
Icon(
imageVector = if (isPause) Icons.Filled.PlayArrow else Icons.Filled.Pause,
contentDescription = "Pla",
tint = Color.White
)
}
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
instrumentButton()
Column {
IconButton(
onClick = {
isPianoSelected = !isPianoSelected
if (isPianoSelected) {
mediaPlayer?.changeInstru(1)
} else { } else {
mediaPlayer?.changeInstru(20) instrumentButton()
}
}
) {
if (isPianoSelected) {
Icon(
Icons.Default.Piano,
contentDescription = "Piano",
tint = Color.Black
)
} else {
Icon(
painter = painterResource(Res.drawable.ic_organ),
contentDescription = "Orgue",
tint = Color.Black,
modifier = Modifier.size(25.dp)
)
}
}
}
} else {
Column {
IconButton(
onClick = {
isPianoSelected = !isPianoSelected
if (isPianoSelected) {
mediaPlayer?.changeInstru(1)
} else {
mediaPlayer?.changeInstru(20)
}
}
) {
if (isPianoSelected) {
Icon(
Icons.Default.Piano,
contentDescription = "Piano",
tint = Color.Black
)
} else {
Icon(
painter = painterResource(Res.drawable.ic_organ),
contentDescription = "Orgue",
tint = Color.Black,
modifier = Modifier.size(25.dp)
)
}
}
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Column( playPauseButton()
modifier = Modifier.wrapContentWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(
onClick = onPlayPauseClick,
modifier = Modifier.size(48.dp).background(MaterialTheme.colorScheme.primary, CircleShape)
) {
Icon(
imageVector = if (isPause) Icons.Filled.PlayArrow else Icons.Filled.Pause,
contentDescription = "Pla",
tint = Color.White
)
}
}
} }
@ -512,7 +492,7 @@ fun MidiControlPanel(
Icon( Icon(
imageVector = if (expandedCtl) Icons.Default.MoreHoriz else Icons.Default.MoreVert, imageVector = if (expandedCtl) Icons.Default.MoreHoriz else Icons.Default.MoreVert,
contentDescription = "More", contentDescription = "More",
tint = Color.White tint = MaterialTheme.colorScheme.onSecondary
) )
} }
} }

View file

@ -10,6 +10,7 @@ import androidx.compose.material.icons.automirrored.filled.VolumeOff
import androidx.compose.material.icons.automirrored.filled.VolumeUp import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -34,7 +35,7 @@ fun ModernVolumeSlider(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
.height(42.dp) .height(42.dp)
.background(Color.White.copy(alpha = 0.1f), CircleShape) .background(MaterialTheme.colorScheme.onSecondary.copy(alpha = 0.1f), CircleShape)
.padding(horizontal = 12.dp, vertical = 4.dp) .padding(horizontal = 12.dp, vertical = 4.dp)
) { ) {
Icon( Icon(
@ -44,7 +45,7 @@ fun ModernVolumeSlider(
else -> Icons.AutoMirrored.Filled.VolumeUp else -> Icons.AutoMirrored.Filled.VolumeUp
}, },
contentDescription = null, contentDescription = null,
tint = if (volume > 0) Color.Black else Color.Gray, tint = if (volume > 0) MaterialTheme.colorScheme.onSecondary else Color.Gray,
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)
) )
@ -55,8 +56,8 @@ fun ModernVolumeSlider(
onValueChange = onVolumeChange, onValueChange = onVolumeChange,
modifier = Modifier.fillMaxWidth(0.30f), modifier = Modifier.fillMaxWidth(0.30f),
colors = SliderDefaults.colors( colors = SliderDefaults.colors(
activeTrackColor = Color(0xFFF59E0B), activeTrackColor = MaterialTheme.colorScheme.primary,
inactiveTrackColor = Color.White.copy(alpha = 0.2f), inactiveTrackColor = MaterialTheme.colorScheme.onSecondary.copy(alpha = 0.6f),
thumbColor = Color.Transparent thumbColor = Color.Transparent
), ),
track = { sliderState -> track = { sliderState ->

View file

@ -5,12 +5,16 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
@ -26,6 +30,7 @@ import androidx.compose.ui.unit.sp
import feufaro.composeapp.generated.resources.Res import feufaro.composeapp.generated.resources.Res
import feufaro.composeapp.generated.resources.compose_multiplatform import feufaro.composeapp.generated.resources.compose_multiplatform
import mg.dot.feufaro.FileRepository import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.MGButton
import mg.dot.feufaro.solfa.ColorPrefixA import mg.dot.feufaro.solfa.ColorPrefixA
import mg.dot.feufaro.solfa.ColorPrefixB import mg.dot.feufaro.solfa.ColorPrefixB
import mg.dot.feufaro.solfa.ColorValue import mg.dot.feufaro.solfa.ColorValue
@ -62,48 +67,59 @@ fun QRDisplay(sharedScreenModel: SharedScreenModel, fileRepository: FileReposito
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(Color.Black.copy(alpha = 0.6f)) // Assombrissement moderne .background(Color.Black.copy(alpha = 0.4f))
.clickable { sharedScreenModel.toggleQRCodeVisibility() } // Fermer au clic .clickable { sharedScreenModel.toggleQRCodeVisibility() },
.imePadding() contentAlignment = Alignment.BottomCenter
.systemBarsPadding(),
contentAlignment = Alignment.Center
) { ) {
BoxWithConstraints(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter
) {
val screenHeight = maxHeight
Card( Card(
modifier = Modifier modifier = Modifier
.width(400.dp) .fillMaxWidth()
.wrapContentHeight() .widthIn(max = 450.dp)
.shadow(24.dp, shape = RoundedCornerShape(28.dp)), .height(screenHeight * 0.85f)
shape = RoundedCornerShape(28.dp), .clickable(enabled = false) {}
.shadow(16.dp, shape = RoundedCornerShape(topStart = 32.dp, topEnd = 32.dp)),
shape = RoundedCornerShape(topStart = 32.dp, topEnd = 32.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
) { ) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(24.dp) modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp)
.padding(top = 12.dp, bottom = 32.dp)
) { ) {
Box(
modifier = Modifier
.size(width = 40.dp, height = 4.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f))
)
Spacer(modifier = Modifier.height(12.dp))
Text( Text(
text = "Partager la partition", text = "Scanner ce QR Code",
style = MaterialTheme.typography.titleLarge.copy( style = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
letterSpacing = 0.5.sp fontSize = 22.sp
) )
) )
Text( Spacer(modifier = Modifier.height(5.dp))
text = "Scannez pour ouvrir instantanément",
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant
),
modifier = Modifier.padding(top = 4.dp, bottom = 24.dp)
)
if (qrCodeImage != null) { if (qrCodeImage != null) {
Box( Box(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
modifier = Modifier modifier = Modifier
.size(350.dp) .weight(10f)
.clip(RoundedCornerShape(16.dp)) .fillMaxHeight(1f)
.background(Color.White) .aspectRatio(1f)
.padding(5.dp) .clip(RoundedCornerShape(24.dp))
.padding(12.dp)
) { ) {
Image( Image(
bitmap = qrCodeImage!!, bitmap = qrCodeImage!!,
@ -113,7 +129,8 @@ fun QRDisplay(sharedScreenModel: SharedScreenModel, fileRepository: FileReposito
Box( Box(
modifier = Modifier modifier = Modifier
.size(54.dp) .fillMaxHeight(0.18f)
.aspectRatio(1f)
.background(Color.White, shape = RoundedCornerShape(12.dp)) .background(Color.White, shape = RoundedCornerShape(12.dp))
.padding(4.dp), .padding(4.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
@ -143,15 +160,36 @@ fun QRDisplay(sharedScreenModel: SharedScreenModel, fileRepository: FileReposito
Text( Text(
text = "Génération du QR Code...", text = "Génération du QR Code...",
style = MaterialTheme.typography.bodyMedium.copy( style = MaterialTheme.typography.bodyMedium.copy(
color = ColorValue, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
fontWeight = FontWeight.Medium
) )
) )
} }
} }
} }
Spacer(modifier = Modifier.height(2.dp))
Spacer(modifier = Modifier.height(12.dp)) Text(
text = "Partager et charger rapidement cette partition",
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
letterSpacing = 0.2.sp
)
)
Spacer(modifier = Modifier.height(2.dp))
Button(
onClick = { sharedScreenModel.toggleQRCodeVisibility() },
modifier = Modifier
.width(450.dp),
) {
Text(
text = "Fermer",
fontWeight = FontWeight.Bold,
fontSize = 16.sp
)
}
}
} }
} }
} }

View file

@ -1,14 +1,19 @@
package mg.dot.feufaro.ui package mg.dot.feufaro.ui
import SharedScreenModel
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
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.*
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.Undo
import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.LightMode
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
@ -20,10 +25,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import SharedScreenModel import kotlinx.coroutines.CoroutineScope
import androidx.compose.material.icons.automirrored.filled.Undo import kotlinx.coroutines.Dispatchers
import mg.dot.feufaro.DisplayConfigManager
import mg.dot.feufaro.getPlatform import mg.dot.feufaro.getPlatform
import mg.dot.feufaro.midi.Dynamic import mg.dot.feufaro.midi.Dynamic
import org.koin.compose.koinInject
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -39,6 +46,22 @@ fun Settings(
val isFullScreenEnabled by sharedScreenModel.isFullScreen.collectAsState() val isFullScreenEnabled by sharedScreenModel.isFullScreen.collectAsState()
val platform = getPlatform() val platform = getPlatform()
val isAndroid = platform.name.startsWith("Android") val isAndroid = platform.name.startsWith("Android")
// Load Configurations
val displayConfigManager = koinInject<DisplayConfigManager>()
val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState()
val currentTheme = currentDisplayConfig.themeMode
val isDarkModeActive = currentTheme == "DARK"
val configScope = CoroutineScope(Dispatchers.Default)
var tempDarkMode by remember(currentDisplayConfig.themeMode) {
mutableStateOf(currentDisplayConfig.themeMode == "DARK")
}
var tempFontSize by remember(currentDisplayConfig.fontSize) {
mutableStateOf(currentDisplayConfig.fontSize)
}
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = { title = {
@ -63,8 +86,8 @@ fun Settings(
onToggle = { expandedGeneral = !expandedGeneral } onToggle = { expandedGeneral = !expandedGeneral }
) { ) {
Column( Column(
modifier = Modifier.padding(8.dp), modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
if(isAndroid) { if(isAndroid) {
Row( Row(
@ -81,6 +104,109 @@ fun Settings(
) )
} }
} }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Thème Sombre",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface
)
Switch(
checked = tempDarkMode,
onCheckedChange = { checked ->
val newMode = if (checked) "DARK" else "LIGHT"
displayConfigManager.updateThemeMode(newMode)
},
thumbContent = {
Icon(
imageVector = if (tempDarkMode) Icons.Default.DarkMode else Icons.Default.LightMode,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSecondary
)
},
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colorScheme.primary,
checkedTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.4f),
uncheckedThumbColor = MaterialTheme.colorScheme.error,
uncheckedTrackColor = MaterialTheme.colorScheme.error.copy(alpha = 0.2f),
checkedBorderColor = Color.Transparent,
uncheckedBorderColor = Color.Transparent,
checkedIconColor = Color.White,
uncheckedIconColor = MaterialTheme.colorScheme.onTertiary,
)
)
}
HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f))
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Taille de la police",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(15.dp)
) {
IconButton(
onClick = {
if (currentDisplayConfig.fontSize > 8f) {
displayConfigManager.updateFontSize(currentDisplayConfig.fontSize - 0.5f)
}
},
modifier = Modifier.size(25.dp).background(MaterialTheme.colorScheme.primary, CircleShape)
) {
Text("-", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = Color.White)
}
OutlinedTextField(
value = tempFontSize.toString(),
onValueChange = {},
readOnly = true,
textStyle = MaterialTheme.typography.titleMedium.copy(textAlign = TextAlign.Center),
modifier = Modifier.width(80.dp),
shape = RoundedCornerShape(12.dp)
)
IconButton(
onClick = {
if (currentDisplayConfig.fontSize < 30f) {
displayConfigManager.updateFontSize(currentDisplayConfig.fontSize + 0.5f)
}
},
modifier = Modifier.size(25.dp).background(MaterialTheme.colorScheme.primary, CircleShape)
) {
Text("+", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = Color.White)
}
}
}
Spacer(modifier = Modifier.weight(1f))
OutlinedButton(
onClick = { displayConfigManager.resetToDefault() },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.error
)
) {
Text("Réinitialiser", style = MaterialTheme.typography.labelLarge)
}
} }
} }
@ -145,7 +271,7 @@ fun Settings(
Icon( Icon(
imageVector = Icons.AutoMirrored.Default.Undo, imageVector = Icons.AutoMirrored.Default.Undo,
contentDescription = null, contentDescription = null,
tint = Color.Red tint = MaterialTheme.colorScheme.error
) )
} }
} }

View file

@ -2,7 +2,6 @@ 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.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@ -13,9 +12,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape 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.EventNote import androidx.compose.material.icons.automirrored.filled.ListAlt
import androidx.compose.material.icons.automirrored.filled.Note import androidx.compose.material.icons.automirrored.filled.PlaylistPlay
import androidx.compose.material.icons.automirrored.filled.StarHalf
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.*
@ -25,7 +23,6 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
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.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -87,23 +84,28 @@ fun SimpleDrawerContent(
onCheckedChange = { newState -> onCheckedChange = { newState ->
sharedScreenModel.toggleEditorMode(newState) sharedScreenModel.toggleEditorMode(newState)
}, },
label = "Mode Edit", thumbIcon = Icons.Default.Edit,
color = MaterialTheme.colorScheme.primary label = "Mode Edit"
) )
CustomSwitchItem( CustomSwitchItem(
checked = state1, checked = state1,
onCheckedChange = { state1 = it }, onCheckedChange = { state1 = it },
thumbIcon = Icons.Default.MusicNote,
label = "Analyse chords", label = "Analyse chords",
color = Color(0xFFE57373)
)
CustomSwitchItem(
checked = state2,
onCheckedChange = { state2 = it },
label = "Titre 3",
color = Color(0xFFBFBF11)
) )
Column(
modifier = Modifier.padding(5.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(
onClick = { },
modifier = Modifier.background(Color(0xFF81C784), shape = CircleShape).size(40.dp)
) {
Icon(Icons.Default.Add, contentDescription = null, tint = Color.White)
}
Text("Ajouter", style = MaterialTheme.typography.labelSmall)
}
} }
} }
@ -123,8 +125,7 @@ fun SimpleDrawerContent(
stickyHeader { stickyHeader {
DrawerHeaderSticky( DrawerHeaderSticky(
title = "Solfa disponibles", title = "Solfa disponibles",
icon = Icons.AutoMirrored.Filled.Note, icon = Icons.AutoMirrored.Filled.ListAlt,
color = MaterialTheme.colorScheme.primary,
isExpanded = internalExpanded, isExpanded = internalExpanded,
onToggle = { internalExpanded = !internalExpanded }, onToggle = { internalExpanded = !internalExpanded },
count = internalList.size count = internalList.size
@ -134,11 +135,29 @@ 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)
NavigationDrawerItem( NavigationDrawerItem(
icon = { icon = {
Text("\uD834\uDD1E", fontSize = 18.sp) Text("\uD834\uDD1E", fontSize = 22.sp)
},
label = { DrawerItemLabel(item) },
badge = {
IconButton(
onClick = {
sharedScreenModel.toggleFavorite(item.path)
},
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Default.PlaylistAddCircle,
contentDescription = null,
tint = if (!isFavorite) Color.LightGray else Color(0xFFFFD700),
modifier = Modifier.size(18.dp)
)
}
}, },
label = { DrawerItemLabel(item, sharedScreenModel) },
selected = isSelected, selected = isSelected,
onClick = { onClick = {
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
@ -149,8 +168,8 @@ fun SimpleDrawerContent(
shape = RoundedCornerShape(5.dp), shape = RoundedCornerShape(5.dp),
modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp), modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
colors = NavigationDrawerItemDefaults.colors( colors = NavigationDrawerItemDefaults.colors(
selectedContainerColor = Color(0, 157, 255).copy(alpha = 0.1f), selectedContainerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f),
selectedTextColor = Color(0, 157, 255) selectedTextColor = MaterialTheme.colorScheme.primary
) )
) )
} }
@ -159,8 +178,7 @@ fun SimpleDrawerContent(
stickyHeader { stickyHeader {
DrawerHeaderSticky( DrawerHeaderSticky(
title = "Personnel", title = "Personnel",
icon = Icons.AutoMirrored.Filled.EventNote, icon = Icons.Default.LibraryMusic,
color = MaterialTheme.colorScheme.tertiary,
isExpanded = externalExpanded, isExpanded = externalExpanded,
onToggle = { externalExpanded = !externalExpanded }, onToggle = { externalExpanded = !externalExpanded },
count = externalList.size count = externalList.size
@ -170,12 +188,30 @@ 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)
if (item.path != "") { if (item.path != "") {
NavigationDrawerItem( NavigationDrawerItem(
icon = { icon = {
Text("", fontSize = 18.sp) Text("", fontSize = 20.sp)
},
label = { DrawerItemLabel(item) },
badge = {
IconButton(
onClick = {
sharedScreenModel.toggleFavorite(item.path)
},
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Default.PlaylistAddCircle,
contentDescription = null,
tint = if (!isFavorite) Color.LightGray else Color(0xFFFFD700),
modifier = Modifier.size(18.dp)
)
}
}, },
label = { DrawerItemLabel(item, sharedScreenModel) },
selected = isSelected, selected = isSelected,
onClick = { onClick = {
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
@ -186,8 +222,8 @@ fun SimpleDrawerContent(
shape = RoundedCornerShape(5.dp), shape = RoundedCornerShape(5.dp),
modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp), modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
colors = NavigationDrawerItemDefaults.colors( colors = NavigationDrawerItemDefaults.colors(
selectedContainerColor = Color.Blue.copy(alpha = 0.1f), selectedContainerColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.5f),
selectedTextColor = Color.Blue selectedTextColor = MaterialTheme.colorScheme.onSecondary
) )
) )
} }
@ -197,8 +233,7 @@ fun SimpleDrawerContent(
stickyHeader { stickyHeader {
DrawerHeaderSticky( DrawerHeaderSticky(
title = "Playlist", title = "Playlist",
icon = Icons.AutoMirrored.Filled.StarHalf, icon = Icons.AutoMirrored.Filled.PlaylistPlay,
color = MaterialTheme.colorScheme.tertiary,
isExpanded = playListExpanded, isExpanded = playListExpanded,
onToggle = { playListExpanded = !playListExpanded }, onToggle = { playListExpanded = !playListExpanded },
count = playList.size count = playList.size
@ -211,8 +246,36 @@ fun SimpleDrawerContent(
val isSelected = item.path == activePath val isSelected = item.path == activePath
NavigationDrawerItem( NavigationDrawerItem(
icon = {
Text("", fontSize = 22.sp)
},
label = { label = {
DrawerFavorisItemLabel(item, index, isSelected, sharedScreenModel) DrawerItemLabel(item)
},
badge = {
if (index > 0) {
IconButton(
onClick = { sharedScreenModel.moveToTop(index) },
modifier = Modifier.size(30.dp)
) {
Icon(
imageVector = Icons.Default.VerticalAlignTop,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.onSecondary
)
}
}
IconButton(
onClick = { sharedScreenModel.toggleFavorite(item.path) },
modifier = Modifier.size(30.dp)
) {
Icon(
imageVector = Icons.Default.Star,
contentDescription = null,
tint = if (isSelected) MaterialTheme.colorScheme.background else MaterialTheme.colorScheme.tertiary
)
}
}, },
selected = isSelected, selected = isSelected,
onClick = { onClick = {
@ -221,11 +284,11 @@ fun SimpleDrawerContent(
solfaScreenModel.loadFromFile(item.path) solfaScreenModel.loadFromFile(item.path)
onSongSelected(midi) onSongSelected(midi)
}, },
shape = RoundedCornerShape(5.dp),
modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp), modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
colors = NavigationDrawerItemDefaults.colors( colors = NavigationDrawerItemDefaults.colors(
selectedContainerColor = Color(0xFFFFD700).copy(alpha = 0.3f), selectedContainerColor = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.5f),
selectedTextColor = Color(0xFF665500), selectedTextColor = MaterialTheme.colorScheme.onTertiary,
selectedIconColor = Color(0xFF665500)
) )
) )
} }
@ -250,7 +313,7 @@ fun SimpleDrawerContent(
IconButton(onClick = { IconButton(onClick = {
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
}) { }) {
Icon(Icons.Default.GraphicEq, contentDescription = null, tint = MaterialTheme.colorScheme.primary) Icon(Icons.Default.GraphicEq, contentDescription = null, tint = MaterialTheme.colorScheme.onSecondary)
} }
} }
@ -263,7 +326,7 @@ fun SimpleDrawerContent(
solfaScreenModel.loadCustomFile() solfaScreenModel.loadCustomFile()
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
}) { }) {
Icon(Icons.Default.FolderOpen, contentDescription = null) Icon(Icons.Default.FolderOpen, contentDescription = null, tint = MaterialTheme.colorScheme.onSecondary)
} }
} }
@ -276,7 +339,7 @@ fun SimpleDrawerContent(
onSettingsCheck() onSettingsCheck()
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
}) { }) {
Icon(Icons.Default.Settings, contentDescription = null) Icon(Icons.Default.Settings, contentDescription = null, tint = MaterialTheme.colorScheme.onSecondary)
} }
} }
@ -288,7 +351,7 @@ fun SimpleDrawerContent(
IconButton(onClick = { IconButton(onClick = {
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
}) { }) {
Icon(Icons.Default.Info, contentDescription = null) Icon(Icons.Default.Info, contentDescription = null, tint = MaterialTheme.colorScheme.onSecondary)
} }
} }
} }
@ -300,7 +363,6 @@ fun SimpleDrawerContent(
fun DrawerHeaderSticky( fun DrawerHeaderSticky(
title: String, title: String,
icon: ImageVector, icon: ImageVector,
color: Color,
isExpanded: Boolean, isExpanded: Boolean,
onToggle: () -> Unit, onToggle: () -> Unit,
count: Int count: Int
@ -315,23 +377,23 @@ fun DrawerHeaderSticky(
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(icon, contentDescription = null, tint = color, modifier = Modifier.size(20.dp)) Icon(icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp))
Spacer(Modifier.width(12.dp)) Spacer(Modifier.width(12.dp))
Text( Text(
text = "$title".uppercase(), text = title.uppercase(),
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Bold), style = MaterialTheme.typography.titleSmall.copy(fontWeight = FontWeight.Bold),
color = color color = MaterialTheme.colorScheme.primary
) )
Surface( Surface(
color = Color.Green.copy(alpha = 0.1f), color = MaterialTheme.colorScheme.error.copy(alpha = 0.1f),
shape = MaterialTheme.shapes.extraSmall shape = MaterialTheme.shapes.extraSmall
) { ) {
Text( Text(
text = "${count}", text = count.toString() ,
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp), modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
color = Color.Green color = MaterialTheme.colorScheme.error
) )
} }
Icon( Icon(
@ -344,94 +406,25 @@ fun DrawerHeaderSticky(
} }
@Composable @Composable
fun DrawerItemLabel(item: DrawerItem, sharedScreenModel: SharedScreenModel) { fun DrawerItemLabel(item: DrawerItem) {
val favoriteLists by sharedScreenModel.playlistItems.collectAsState()
val isFavorite = favoriteLists.contains(item)
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text(text = item.title, fontWeight = FontWeight.SemiBold, style = MaterialTheme.typography.bodyMedium) Text(text = item.title, fontWeight = FontWeight.SemiBold, style = MaterialTheme.typography.titleSmall)
Text(text = item.contentTitle, style = MaterialTheme.typography.labelSmall, maxLines = 1) Text(text = item.contentTitle, style = MaterialTheme.typography.labelSmall, maxLines = 1)
} }
Row {
IconButton(
onClick = {
sharedScreenModel.toggleFavorite(item.path)
},
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Default.PlaylistAddCircle,
contentDescription = null,
tint = if (!isFavorite) Color.LightGray else Color(0xFFFFD700),
modifier = Modifier.size(18.dp)
)
}
}
} }
} }
@Composable
fun DrawerFavorisItemLabel(item: DrawerItem, index: Int, isSelected: Boolean, sharedScreenModel: SharedScreenModel) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Reorder,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp).size(18.dp),
tint = Color.Gray.copy(alpha = 0.5f)
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = item.title,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.bodyMedium
)
Text(
text = item.contentTitle,
style = MaterialTheme.typography.labelSmall,
maxLines = 1
)
}
if (index > 0) {
IconButton(
onClick = { sharedScreenModel.moveToTop(index) },
modifier = Modifier.size(30.dp)
) {
Icon(
imageVector = Icons.Default.VerticalAlignTop,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = if (isSelected) Color.White else Color.Gray
)
}
}
IconButton(
onClick = { sharedScreenModel.toggleFavorite(item.path) },
modifier = Modifier.size(30.dp)
) {
Icon(
imageVector = Icons.Default.Star,
contentDescription = null,
tint = if (isSelected) Color.White else Color(0xFFFFD700)
)
}
}
}
@Composable @Composable
fun CustomSwitchItem( fun CustomSwitchItem(
checked: Boolean, checked: Boolean,
onCheckedChange: (Boolean) -> Unit, onCheckedChange: (Boolean) -> Unit,
label: String, label: String,
color: Color thumbIcon: ImageVector
) { ) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
@ -440,11 +433,20 @@ fun CustomSwitchItem(
checked = checked, checked = checked,
onCheckedChange = onCheckedChange, onCheckedChange = onCheckedChange,
colors = SwitchDefaults.colors( colors = SwitchDefaults.colors(
checkedThumbColor = Color.White, checkedThumbColor = MaterialTheme.colorScheme.error,
checkedTrackColor = color, checkedTrackColor = MaterialTheme.colorScheme.error.copy(alpha = 0.5f),
uncheckedTrackColor = color.copy(alpha = 0.3f) uncheckedThumbColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.4f),
uncheckedTrackColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.1f),
uncheckedBorderColor = MaterialTheme.colorScheme.error.copy(alpha = 0.2f),
), ),
modifier = Modifier.scale(0.7f) modifier = Modifier.scale(0.8f),
thumbContent = {
Icon(
imageVector = if (checked) thumbIcon else Icons.Default.Visibility,
contentDescription = null,
modifier = Modifier.size(SwitchDefaults.IconSize)
)
}
) )
Text( Text(
text = label, text = label,

View file

@ -0,0 +1,161 @@
package mg.dot.feufaro.ui
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
object ThemeDefaults {
val PrimaryBlue = Color(0xFF5061FF)
val AccentYellow = Color(0xFFFFFA54)
val AccentGreen = Color(0xFF3EFF96)
val AccentPink = Color(0xFFFF4FA8)
val LightBackground = Color(0xFFF9F9FB)
val DarkBackground = Color(0xFF121214)
fun getSelectedThemeColors(themeModeStr: String): Pair<ColorScheme, ColorScheme> {
return when (themeModeStr.uppercase()) {
"SYSTEM", "DARK", "LIGHT" -> Pair(
lightColorScheme(
primary = PrimaryBlue.copy(alpha = 0.85f),
onPrimary = LightBackground,
secondary = AccentGreen.copy(alpha = 0.85f),
onSecondary = DarkBackground,
secondaryContainer = AccentGreen.copy(alpha = 0.85f),
onSecondaryContainer = DarkBackground,
tertiary = AccentYellow.copy(alpha = 0.85f),
onTertiary = DarkBackground,
error = AccentPink.copy(alpha = 0.85f),
onError = LightBackground,
background = LightBackground,
surface = LightBackground
),
darkColorScheme(
primary = PrimaryBlue,
onPrimary = LightBackground,
secondary = AccentGreen,
onSecondary = LightBackground,
secondaryContainer = AccentGreen,
onSecondaryContainer = DarkBackground,
tertiary = AccentYellow,
onTertiary = DarkBackground,
error = AccentPink,
onError = LightBackground,
background = DarkBackground,
surface = Color(0xFF1E1E24),
onBackground = LightBackground,
onSurface = LightBackground
)
)
else -> Pair(
lightColorScheme(primary = PrimaryBlue, background = LightBackground),
darkColorScheme(primary = PrimaryBlue, background = DarkBackground)
)
}
}
}
@Composable
fun FeufaroTheme(
themeModeSelected: String,
userFontSize: Float,
content: @Composable () -> Unit
) {
val (lightScheme, darkScheme) = ThemeDefaults.getSelectedThemeColors(themeModeSelected)
val useDarkTheme = when (themeModeSelected.uppercase()) {
"DARK" -> true
"LIGHT" -> false
else -> isSystemInDarkTheme()
}
val colorScheme = if (useDarkTheme) darkScheme else lightScheme
val baseSize = userFontSize
val customTypography = Typography(
displayLarge = TextStyle(
fontSize = (baseSize * 2.4f).sp,
letterSpacing = (-1).sp
),
displayMedium = TextStyle(
fontSize = (baseSize * 2.0f).sp,
letterSpacing = (-0.5).sp
),
displaySmall = TextStyle(
fontSize = (baseSize * 1.7f).sp
),
headlineLarge = TextStyle(
fontSize = (baseSize * 1.5f).sp,
letterSpacing = 0.sp
),
headlineMedium = TextStyle(
fontSize = (baseSize * 1.35f).sp
),
headlineSmall = TextStyle(
fontSize = (baseSize * 1.2f).sp
),
titleLarge = TextStyle(
fontSize = (baseSize + 4f).sp,
letterSpacing = 0.15.sp
),
titleMedium = TextStyle(
fontSize = (baseSize + 2f).sp,
letterSpacing = 0.1.sp
),
titleSmall = TextStyle(
fontSize = baseSize.sp,
letterSpacing = 0.1.sp
),
bodyLarge = TextStyle(
fontSize = baseSize.sp,
letterSpacing = 0.25.sp
),
bodyMedium = TextStyle(
fontSize = (baseSize - 1.5f).sp,
letterSpacing = 0.25.sp
),
bodySmall = TextStyle(
fontSize = (baseSize - 3f).sp,
letterSpacing = 0.4.sp
),
labelLarge = TextStyle(
fontSize = (baseSize - 0.5f).sp,
letterSpacing = 0.1.sp
),
labelMedium = TextStyle(
fontSize = (baseSize - 2.5f).sp,
letterSpacing = 0.5.sp
),
labelSmall = TextStyle(
fontSize = (baseSize - 4f).sp,
letterSpacing = 0.5.sp
)
)
MaterialTheme(
colorScheme = colorScheme,
typography = customTypography,
content = content
)
}

View file

@ -1,17 +1,19 @@
package mg.dot.feufaro.ui package mg.dot.feufaro.ui
import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.ScrollbarStyle
import androidx.compose.foundation.VerticalScrollbar import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp
@Composable @Composable
actual fun MyVerticalScrollbar ( actual fun MyVerticalScrollbar (
@ -29,10 +31,22 @@ actual fun MyVerticalScrollbar (
else -> null else -> null
} }
val scrollbarUnhoverColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.85f)
val scrollbarHoverColor = MaterialTheme.colorScheme.secondary
val scrollbarBackground = MaterialTheme.colorScheme.onSecondary.copy(alpha = 0.25f)
if (adapter != null) { if (adapter != null) {
VerticalScrollbar( VerticalScrollbar(
adapter = adapter, adapter = adapter,
modifier = modifier.fillMaxHeight(0.5f).background(Color.Green.copy(0.75f)) modifier = modifier.fillMaxHeight(0.5f).background(scrollbarBackground),
style = ScrollbarStyle(
minimalHeight = 16.dp,
thickness = 8.dp,
shape = RoundedCornerShape(4.dp),
hoverDurationMillis = 250,
unhoverColor = scrollbarUnhoverColor,
hoverColor = scrollbarHoverColor
)
) )
//content() //content()
} }