diff --git a/composeApp/src/commonMain/composeResources/files/config.json b/composeApp/src/commonMain/composeResources/files/config.json index 8bfa415..502f603 100644 --- a/composeApp/src/commonMain/composeResources/files/config.json +++ b/composeApp/src/commonMain/composeResources/files/config.json @@ -1,6 +1,6 @@ { - "themeMode": "DARK", - "fontSize": 18.5, + "themeMode": "LIGHT", + "fontSize": 14, "playlist": [ "assets://a.txt", "assets://ffpm-617.txt", diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt index d6acdf2..bacd134 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt @@ -4,6 +4,7 @@ import SharedScreenModel import androidx.compose.foundation.layout.RowScope import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -14,33 +15,26 @@ import kotlinx.coroutines.launch import org.koin.compose.koinInject import cafe.adriel.voyager.navigator.CurrentScreen import cafe.adriel.voyager.navigator.Navigator +import mg.dot.feufaro.ui.FeufaroTheme import mg.dot.feufaro.viewmodel.SolfaScreenModel @Composable fun App() { - Navigator(screen = ScreenSolfa) { - CurrentScreen() + val displayConfigManager = koinInject() + val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState() + + FeufaroTheme(themeModeSelected = currentDisplayConfig.themeMode, userFontSize = currentDisplayConfig.fontSize) { + Navigator(screen = ScreenSolfa) { + CurrentScreen() + } } val fileRepository = koinInject() - val displayConfigManager = koinInject() - val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState() - // Load Configurations val configScope = CoroutineScope(Dispatchers.Default) val sharedScreenModel = koinInject() val solfaScreenModel = koinInject() - LaunchedEffect(Unit) { - configScope.launch { - try { - displayConfigManager.loadConfigFromFile("assets://config.json") - } catch (e: Exception) { - e.printStackTrace() - } - } - } - LaunchedEffect(currentDisplayConfig) { if (currentDisplayConfig.playlist.isNotEmpty()) { if (DeepLinkHandler.hasConsumedDeepLink) { @@ -94,10 +88,10 @@ fun MGButton( val displayConfigManager = koinInject() 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() + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError, + disabledContainerColor = MaterialTheme.colorScheme.error.copy(alpha = 0.6f), + disabledContentColor = MaterialTheme.colorScheme.onError.copy(alpha = 0.7f) ) Button( onClick = onClick, diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/DisplayConfigManager.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/DisplayConfigManager.kt index 54ef525..60dc138 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/DisplayConfigManager.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/DisplayConfigManager.kt @@ -1,14 +1,16 @@ package mg.dot.feufaro +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow 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.DisplayConfig -import kotlinx.serialization.json.Json -import kotlin.String - -import kotlinx.serialization.Serializable +import java.util.prefs.Preferences @Serializable data class AppConfigJson( @@ -36,20 +38,29 @@ class DisplayConfigManager( private val fileRepository: FileRepository ) { - private val _displayConfig = MutableStateFlow( - DisplayConfig( - themeMode = "SYSTEM", - fontSize = 16f, - playlist = listOf(), - buttonContainerColorHex = "#FF000000", - buttonContentColorHex = "#FFFFFFFF", - buttonDisabledContainerColorHex = "#FF888888", - buttonDisabledContentColorHex = "#FFAA6666" - - ), - ) + private val prefs = Preferences.userRoot().node("mg.dot.feufaro") + private val _displayConfig = MutableStateFlow(DisplayConfig()) val displayConfig: StateFlow = _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) { try { val jsonString = fileRepository.readFileContent(filePath) @@ -61,5 +72,29 @@ class DisplayConfigManager( 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 } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt index 786695d..7cb0c5d 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt @@ -11,6 +11,7 @@ import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Icon import androidx.compose.material3.IconToggleButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -151,14 +152,15 @@ object ScreenSolfa : Screen { val measureString: String by sharedScreenModel.measure.collectAsState() val songTitle: String by sharedScreenModel.songTitle.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 = 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) ) - Text(text = measureString) - Text(text = "Stanza: $stanza") + Text(text = measureString, style = MaterialTheme.typography.bodyLarge) + Text(text = "Stanza: $stanza", style = MaterialTheme.typography.bodyLarge) //ScreenTranspose.Content() } LazyVerticalGridTUO( diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/config/DisplayConfig.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/config/DisplayConfig.kt index 30eb6a5..b36558b 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/config/DisplayConfig.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/config/DisplayConfig.kt @@ -4,12 +4,12 @@ import kotlinx.serialization.Serializable @Serializable data class DisplayConfig ( - val themeMode: String, - val fontSize: Float, - val playlist: List, - val buttonContainerColorHex: String, - val buttonContentColorHex: String , - val buttonDisabledContainerColorHex: String, - val buttonDisabledContentColorHex: String , + val themeMode: String = "LIGHT", + val fontSize: Float = 14f, + val playlist: List = emptyList(), + val buttonContainerColorHex: String = "#737EFC", + val buttonContentColorHex: String = "#FFFFFF", + val buttonDisabledContainerColorHex: String = "#CCCCCC", + val buttonDisabledContentColorHex: String = "#888888" ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUODetailDialog.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUODetailDialog.kt index e4ebfcb..a8e9f78 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUODetailDialog.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUODetailDialog.kt @@ -91,10 +91,16 @@ fun TUODetailDialog( if (existing.isEmpty() && canAdd) add("_") else add(existing) } } - val lyricsLines = result - .flatMap { it.split("\n") } - .map { it.trim() } - .toMutableList() + val lyricsLines = remember(editState.lyricsByStanza) { + mutableStateListOf().apply { + addAll(editState.lyricsByStanza.values.toList()) + } + } + val editedLyricsMap = remember { + mutableStateMapOf().apply { + putAll(editState.lyricsByStanza) + } + } var canAddMark = mutableStateOf(false) Popup( @@ -111,7 +117,7 @@ fun TUODetailDialog( .widthIn(max=125.dp) .heightIn(max = 400.dp), 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()) { Row ( @@ -269,7 +275,7 @@ fun TUODetailDialog( }) { Text( text = "/", - color = Color.Cyan, + color = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f), fontSize = 18.sp, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center @@ -283,7 +289,7 @@ fun TUODetailDialog( }) { Icon( imageVector = Icons.Default.Add, - tint = Color.Cyan, + tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f), contentDescription = null ) } @@ -297,7 +303,7 @@ fun TUODetailDialog( MyTextEditField( value = templateFragment, customFontSize = 14.sp, - color = Color.Yellow, + color = MaterialTheme.colorScheme.tertiary.copy(alpha = 1.5f), customPadding = 8.dp, customBrush = SolidColor(Color.White), isEditable = false, @@ -313,7 +319,7 @@ fun TUODetailDialog( MyTextEditField( value = marker, customFontSize = 14.sp, - color = Color.Yellow, + color = MaterialTheme.colorScheme.tertiary.copy(alpha = 1.5f), customPadding = 8.dp, customBrush = SolidColor(Color.White), isEditable = isEditable, @@ -342,7 +348,7 @@ fun TUODetailDialog( }) { Icon( imageVector = Icons.Default.Add, - tint = Color.Green, + tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f), contentDescription = null ) } @@ -366,7 +372,7 @@ fun TUODetailDialog( .fillMaxWidth() .padding(bottom = 12.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 ) { Row( @@ -414,6 +420,14 @@ fun TUODetailDialog( // --- SECTION LYRICS --- 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( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 2.dp) @@ -421,18 +435,47 @@ fun TUODetailDialog( Column( modifier = Modifier.fillMaxWidth(0.8f) ) { - MyTextEditField( - value = line, - customFontSize = 13.sp, - color = Color.White, - customPadding = 8.dp, - customBrush = SolidColor(Color.White), - isEditable = isEditable, - isAddable = canAdd, - onValueChng = { newValue -> - lyricsLines[index] = newValue + + val isLyricsValid = validateLyricsInput(line, templateFragment) + val tooltipState2 = rememberTooltipState(isPersistent = false) + Row( + ) { + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + if (!isLyricsValid) { + PlainTooltip( + containerColor = Color(0XFF34EB71), + contentColor = Color.White + ) { + Text("Veuillez suivre ce format: $templateFragment") + } + } + }, + state = tooltipState2 + ) { + MyTextEditField( + value = displayedLine, + customFontSize = 13.sp, + color = Color.White, + customPadding = 8.dp, + customBrush = SolidColor(Color.White), + isEditable = isEditable, + isAddable = canAdd, + isWarn = !isLyricsValid, + funTransform = { input -> transformLyricsInput(input, templateFragment) }, + onValueChng = { newValue -> + val trimmedValue = newValue.trimStart() + val dataToSave = if (trimmedValue.endsWith(" ")) trimmedValue else "$trimmedValue " + lyricsLines[index] = trimmedValue + val updatedLyricsMap = editState.lyricsByStanza.toMutableMap() + editedLyricsMap[index + 1] = dataToSave + + val updatedState = editState.copy(lyricsByStanza = updatedLyricsMap) + } + ) } - ) + } } if (isEditable || canAdd) { IconButton( @@ -440,10 +483,10 @@ fun TUODetailDialog( if (index >= 0) { if (index == 0) lyricsLines.add("_") else lyricsLines.removeAt(index) } - }) { + }) { Icon( imageVector = if (index == 0) Icons.Default.Add else Icons.Default.Clear, - tint = if (index == 0) 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 ) } @@ -467,12 +510,17 @@ fun TUODetailDialog( if (isEditable || canAdd) { IconButton( onClick = { + val finalLyricsMap = editState.lyricsByStanza.toMutableMap() + finalLyricsMap.putAll(editedLyricsMap) + if (!finalLyricsMap.containsKey(currentStanza)) { + finalLyricsMap[currentStanza] = lyricsLines.getOrNull(0)?.trim() ?: "" + } val state = TUOEditState( tuoIndex = globalIndex, notesByVoice = notes.toMap(), originalNotes = originalNotes.toMap(), originalLyricsByStanza = originalLyricsByStz.toMutableMap(), - lyricsByStanza = mutableMapOf(currentStanza to lyricsLines.joinToString(" ")), + lyricsByStanza = finalLyricsMap, templateFragment = templateFragment, marker = if(marker.isEmpty()) "_" else marker, originalSep = initialSep, @@ -485,7 +533,7 @@ fun TUODetailDialog( Icon( Icons.Default.Build, 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("(? + 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 { return input.lowercase() .replace(";", "• ,") @@ -585,7 +729,7 @@ fun MyTextEditField( .background(Color.Black.copy(alpha = 0.3f), RoundedCornerShape(4.dp)) .border( 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) ) .padding(customPadding), diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt index 2f14a68..88fb595 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt @@ -268,11 +268,11 @@ fun TimeUnitComposable( parentFocusRequester: FocusRequester? = null, 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 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 ) val focusRequesters = remember { List(4) { FocusRequester() } } @@ -449,7 +449,7 @@ fun TimeUnitComposable( fontSize = 10.sp, baselineShift = BaselineShift.Superscript, //fontStyle = FontStyle.Italic, - color = FEUFAROO_KEY_CHANGE_COLOR + color = MaterialTheme.colorScheme.primary )) { append(text+" ") } @@ -459,6 +459,7 @@ fun TimeUnitComposable( } Text( text = annotatedText, + style = MaterialTheme.typography.bodyLarge, onTextLayout = { result -> textLayoutResult = result } @@ -768,8 +769,8 @@ fun LazyVerticalGridTUO( fontStyle = fontStyle, fontWeight = fontWeight, style = TextStyle( - color = Color.Black, - fontSize = 17.sp + color = MaterialTheme.colorScheme.onSecondary, + fontSize = MaterialTheme.typography.titleMedium.fontSize ) ) } @@ -792,11 +793,11 @@ fun LazyVerticalGridTUO( .width(gridWidthDp / gridColumnCount) .border( 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) ) .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) ) .combinedClickable( @@ -994,18 +995,18 @@ fun LazyVerticalGridTUO( text = dynamicSpaceSyl, modifier = Modifier .fillMaxWidth() - .padding(end = 4.dp)/*.border(1.dp, Color.Yellow, RectangleShape)*/ + .padding(end = 4.dp) .wrapContentSize(unbounded = true, align = alignmentText), softWrap = false, maxLines = 1, overflow = TextOverflow.Visible, style = TextStyle( color = when { - (isTooLong) -> Color(0XFF121212) - (index == 0) -> Color.Black - else -> Color(0XFF3B3A39) + (isTooLong) -> MaterialTheme.colorScheme.onBackground + (index == 0) -> MaterialTheme.colorScheme.onBackground + else -> MaterialTheme.colorScheme.onBackground }, - fontSize = 16.sp, + fontSize = MaterialTheme.typography.titleMedium.fontSize, ), onTextLayout = { txtLayoutRes -> textWidth = with(density) { txtLayoutRes.size.width.toDp() } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt index f287392..4de5e70 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt @@ -2,6 +2,8 @@ package mg.dot.feufaro.ui import SharedScreenModel import androidx.compose.animation.* +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource 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.graphics.Color import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.graphicsLayer 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.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign @@ -169,6 +174,8 @@ fun MainScreenWithDrawer( } } + val isQrVisible = sharedScreenModel.isQRCodeVisible.value + ModalNavigationDrawer(drawerState = drawerState, drawerContent = { SimpleDrawerContent( items, @@ -350,16 +357,19 @@ fun MainScreenWithDrawer( maxLines = 1, softWrap = false, overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.headlineLarge ) } } } } }, navigationIcon = { - IconButton(onClick = { - scope.launch { drawerState.open() } - }) { - Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu") + if(!isQrVisible) { + IconButton(onClick = { + scope.launch { drawerState.open() } + }) { + Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu") + } } }, actions = { var tempInterval by remember(fileContent) { mutableStateOf(0) } @@ -382,7 +392,7 @@ fun MainScreenWithDrawer( ) { Text( text = tempUiKey, - fontSize = 25.sp, + style = MaterialTheme.typography.displaySmall, fontWeight = FontWeight.Black, textAlign = TextAlign.Center, 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 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" TooltipBox( @@ -522,11 +532,9 @@ fun MainScreenWithDrawer( }, modifier = Modifier.fillMaxWidth().height(36.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( - contentColor = Color( - 0xFFFFD700 - ) + contentColor = MaterialTheme.colorScheme.tertiary ) ) { Text( @@ -571,7 +579,6 @@ fun MainScreenWithDrawer( } } }, - containerColor = Color(0x20000000), icon = Icons.AutoMirrored.Default.Undo ) } @@ -593,7 +600,6 @@ fun MainScreenWithDrawer( } } }, - containerColor = Color(0x20000000), icon = Icons.Filled.SaveAs ) } @@ -607,11 +613,10 @@ fun MainScreenWithDrawer( onClick = { sharedScreenModel.toggleEditorMode(false) }, - containerColor = Color(0x20000000), icon = Icons.Default.Close ) } - } else { + } else if(!isQrVisible) { AnimatedVisibility( visible = isExpanded and !showMidiCtrl, enter = fadeIn() + scaleIn() + slideInVertically { it / 2 }, @@ -623,7 +628,6 @@ fun MainScreenWithDrawer( onClick = { sharedScreenModel.descGridCount(1) }, - containerColor = Color(0x15000000), size = 30.dp, icon = Icons.Default.Remove ) @@ -633,7 +637,6 @@ fun MainScreenWithDrawer( onClick = { sharedScreenModel.addGridCount(1) }, - containerColor = Color(0x15000000), size = 30.dp, icon = Icons.Default.Add ) @@ -650,7 +653,6 @@ fun MainScreenWithDrawer( sharedScreenModel.toggleQRCodeVisibility() sharedScreenModel.setExpandedFAB(false) }, - containerColor = Color(0x15000000), icon = Icons.Filled.QrCode ) } @@ -665,7 +667,6 @@ fun MainScreenWithDrawer( onClick = { showPrintSettings = !showPrintSettings }, - containerColor = Color(0x15000000), icon = Icons.Filled.Print ) } @@ -679,7 +680,6 @@ fun MainScreenWithDrawer( onClick = { sharedScreenModel.setMidiCtrl(!showMidiCtrl) }, - containerColor = Color(0x15000000), icon = if (showMidiCtrl) Icons.Filled.StopCircle else Icons.Filled.PlayCircle ) } @@ -690,7 +690,6 @@ fun MainScreenWithDrawer( refreshTrigeer++ sharedScreenModel.loadNewSong("$midiFile") }, - containerColor = if (isExpanded) Color(0x25000000) else Color.Transparent, icon = if (isExpanded) Icons.Filled.Close else Icons.Filled.Menu ) } @@ -748,17 +747,42 @@ fun MainScreenWithDrawer( /*.windowInsetsPadding(currentInsets.union(WindowInsets.ime))*/ ) { content(PaddingValues(0.dp)) - if (sharedScreenModel.isQRCodeVisible.value) { + + AnimatedVisibility( + visible = isQrVisible, + enter = slideInVertically( + initialOffsetY = { fullHeight -> fullHeight } + ) + fadeIn(), + exit = slideOutVertically( + targetOffsetY = { fullHeight -> fullHeight } + ) + fadeOut() + ) { QRDisplay( sharedScreenModel = sharedScreenModel, fileRepository = solfaScreenModel.fileRepository ) } + + var isHovered by remember { mutableStateOf(false) } + val offsetX by animateDpAsState(targetValue = if (isHovered || isSearchActive) 0.dp else 15.dp) + AnimatedVisibility( - visible = !isEditMode, + visible = !isEditMode && !isQrVisible, modifier = Modifier .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( onClick = { @@ -778,14 +802,14 @@ fun MainScreenWithDrawer( .size(55.dp) .alpha(0.6f) .background( - color = Color.Blue, + color = MaterialTheme.colorScheme.tertiary, shape = CircleShape ) ) { Icon( imageVector = if (isSearchActive) Icons.Default.Close else Icons.Default.Search, contentDescription = null, - tint = Color.White + tint = MaterialTheme.colorScheme.onTertiary ) } } @@ -884,20 +908,19 @@ fun MainScreenWithDrawer( @Composable private fun MyFAB( onClick: () -> Unit, - containerColor: Color, size: Dp ?= 55.dp, icon: ImageVector ) { FloatingActionButton( onClick = onClick, 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) ) { Icon( imageVector = icon, contentDescription = null, - tint = Color.Blue.copy(alpha = 0.75f) + tint = MaterialTheme.colorScheme.primary ) } } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/MidiControlPanel.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/MidiControlPanel.kt index 0cc0548..ec24e3a 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/MidiControlPanel.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/MidiControlPanel.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.paint +import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.TransformOrigin @@ -121,8 +122,25 @@ fun MidiControlPanel( ) { Column( modifier = modifier + .padding(24.dp) .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 ) { AnimatedVisibility( @@ -130,40 +148,43 @@ fun MidiControlPanel( ) { Row( 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( value = currentPos, onValueChange = onSeek, valueRange = 0f..(if (duration > 0) duration else 1f), modifier = Modifier.weight(1f), colors = SliderDefaults.colors( - thumbColor = Color.Red, - activeTrackColor = Color.Green, - inactiveTrackColor = Color.Gray, - inactiveTickColor = Color(0xffb0BEC5), - disabledThumbColor = Color(0xff78909C), - disabledActiveTickColor = Color(0xff757575), - disabledActiveTrackColor = Color(0xffBDBDBD), - disabledInactiveTickColor = Color(0xff616161), - disabledInactiveTrackColor = Color(0xffBCAAA4), + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = Color.White.copy(alpha = 0.2f), + thumbColor = MaterialTheme.colorScheme.secondary, + disabledThumbColor = Color.Gray, + disabledActiveTrackColor = Color.Gray.copy(alpha = 0.5f) ), thumb = { Box( modifier = Modifier - .size(15.dp) - .background(Color.Gray, CircleShape) + .size(12.dp) + .background(MaterialTheme.colorScheme.secondary, CircleShape) + .shadow(4.dp, CircleShape) ) }, track = { sliderState -> SliderDefaults.Track( 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) ) { displayedBpm -> 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 = "$displayedBpm", + text = displayedBpm.toString(), color = if (isCenter) Color.White else Color.Gray.copy(alpha = 0.4f), - fontSize = if (isCenter) 22.sp else 14.sp, - fontWeight = if (isCenter) FontWeight.Bold else FontWeight.Normal, + style = bpmTextStyle, modifier = Modifier .clip(CircleShape) .clickable(enabled = !isCenter) { updateTempoToBpm(displayedBpm) } @@ -308,7 +334,7 @@ fun MidiControlPanel( Text( text = "bpm", color = Color.White.copy(0.6f), - fontSize = 12.sp, + fontSize = MaterialTheme.typography.bodyMedium.fontSize, modifier = Modifier.padding(end = 8.dp) ) } @@ -340,8 +366,8 @@ fun MidiControlPanel( }, colors = ButtonDefaults.buttonColors( containerColor = when { - (isWaitingForB || isLooping) -> Color.Red - else -> Color.LightGray + (isWaitingForB || isLooping) -> MaterialTheme.colorScheme.error + else -> MaterialTheme.colorScheme.onError } ), contentPadding = PaddingValues(horizontal = 2.dp, vertical = 0.dp), @@ -356,7 +382,7 @@ fun MidiControlPanel( Text( text = "A", color = if (isWaitingForB || isLooping) Color.White else Color.Black, - fontSize = 16.sp + fontSize = MaterialTheme.typography.titleLarge.fontSize ) Icon( @@ -369,7 +395,7 @@ fun MidiControlPanel( Text( text = "B", color = if (isLooping) Color.White else Color.Black, - fontSize = 16.sp + fontSize = MaterialTheme.typography.titleLarge.fontSize ) } } @@ -388,107 +414,61 @@ fun MidiControlPanel( Icon( painter = painterResource(Res.drawable.ic_mixer_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) ) } } Spacer(modifier = Modifier.weight(1f)) - if(platform.startsWith("Android")) { - Column( - 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 - ) - } + 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 + ) } + } - Spacer(modifier = Modifier.weight(1f)) - - Column { + val instrumentButton = @Composable { IconButton( onClick = { isPianoSelected = !isPianoSelected - if (isPianoSelected) { - mediaPlayer?.changeInstru(1) - } else { - mediaPlayer?.changeInstru(20) - } - + mediaPlayer?.changeInstru(if (isPianoSelected) 1 else 20) } ) { if (isPianoSelected) { Icon( - Icons.Default.Piano, + imageVector = Icons.Default.Piano, contentDescription = "Piano", - tint = Color.Black + tint = MaterialTheme.colorScheme.onSecondary ) } else { Icon( painter = painterResource(Res.drawable.ic_organ), contentDescription = "Orgue", - tint = Color.Black, + tint = MaterialTheme.colorScheme.onSecondary, 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) - ) - } - } - } + if (platform.startsWith("Android")) { + playPauseButton() Spacer(modifier = Modifier.weight(1f)) - Column( - 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 - ) - } - } - } + instrumentButton() + } else { + instrumentButton() + Spacer(modifier = Modifier.weight(1f)) + playPauseButton() + } Spacer(modifier = Modifier.weight(1f)) @@ -512,7 +492,7 @@ fun MidiControlPanel( Icon( imageVector = if (expandedCtl) Icons.Default.MoreHoriz else Icons.Default.MoreVert, contentDescription = "More", - tint = Color.White + tint = MaterialTheme.colorScheme.onSecondary ) } } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/ModernVolumeSlider.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/ModernVolumeSlider.kt index 7576eff..cbe7d24 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/ModernVolumeSlider.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/ModernVolumeSlider.kt @@ -10,6 +10,7 @@ import androidx.compose.material.icons.automirrored.filled.VolumeOff import androidx.compose.material.icons.automirrored.filled.VolumeUp import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.runtime.Composable @@ -34,7 +35,7 @@ fun ModernVolumeSlider( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .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) ) { Icon( @@ -44,7 +45,7 @@ fun ModernVolumeSlider( else -> Icons.AutoMirrored.Filled.VolumeUp }, 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) ) @@ -55,8 +56,8 @@ fun ModernVolumeSlider( onValueChange = onVolumeChange, modifier = Modifier.fillMaxWidth(0.30f), colors = SliderDefaults.colors( - activeTrackColor = Color(0xFFF59E0B), - inactiveTrackColor = Color.White.copy(alpha = 0.2f), + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = MaterialTheme.colorScheme.onSecondary.copy(alpha = 0.6f), thumbColor = Color.Transparent ), track = { sliderState -> diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/QRDisplay.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/QRDisplay.kt index 28f2ce6..14607da 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/QRDisplay.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/QRDisplay.kt @@ -5,12 +5,16 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue 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.compose_multiplatform import mg.dot.feufaro.FileRepository +import mg.dot.feufaro.MGButton import mg.dot.feufaro.solfa.ColorPrefixA import mg.dot.feufaro.solfa.ColorPrefixB import mg.dot.feufaro.solfa.ColorValue @@ -62,96 +67,129 @@ fun QRDisplay(sharedScreenModel: SharedScreenModel, fileRepository: FileReposito Box( modifier = Modifier .fillMaxSize() - .background(Color.Black.copy(alpha = 0.6f)) // Assombrissement moderne - .clickable { sharedScreenModel.toggleQRCodeVisibility() } // Fermer au clic - .imePadding() - .systemBarsPadding(), - contentAlignment = Alignment.Center + .background(Color.Black.copy(alpha = 0.4f)) + .clickable { sharedScreenModel.toggleQRCodeVisibility() }, + contentAlignment = Alignment.BottomCenter ) { - Card( - modifier = Modifier - .width(400.dp) - .wrapContentHeight() - .shadow(24.dp, shape = RoundedCornerShape(28.dp)), - shape = RoundedCornerShape(28.dp), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + BoxWithConstraints( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.BottomCenter ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(24.dp) + val screenHeight = maxHeight + Card( + modifier = Modifier + .fillMaxWidth() + .widthIn(max = 450.dp) + .height(screenHeight * 0.85f) + .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) ) { - Text( - text = "Partager la partition", - style = MaterialTheme.typography.titleLarge.copy( - fontWeight = FontWeight.Bold, - letterSpacing = 0.5.sp - ) - ) - - Text( - 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) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(top = 12.dp, bottom = 32.dp) + ) { Box( - contentAlignment = Alignment.Center, modifier = Modifier - .size(350.dp) - .clip(RoundedCornerShape(16.dp)) - .background(Color.White) - .padding(5.dp) - ) { - Image( - bitmap = qrCodeImage!!, - contentDescription = "Code QR du fichier actif", - modifier = Modifier.fillMaxSize() - ) + .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 = "Scanner ce QR Code", + style = MaterialTheme.typography.titleLarge.copy( + fontWeight = FontWeight.Bold, + fontSize = 22.sp + ) + ) + + Spacer(modifier = Modifier.height(5.dp)) + + if (qrCodeImage != null) { Box( + contentAlignment = Alignment.Center, modifier = Modifier - .size(54.dp) - .background(Color.White, shape = RoundedCornerShape(12.dp)) - .padding(4.dp), - contentAlignment = Alignment.Center + .weight(10f) + .fillMaxHeight(1f) + .aspectRatio(1f) + .clip(RoundedCornerShape(24.dp)) + .padding(12.dp) ) { Image( - painter = painterResource(Res.drawable.compose_multiplatform), - contentDescription = "App Logo", - modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(8.dp)) + bitmap = qrCodeImage!!, + contentDescription = "Code QR du fichier actif", + modifier = Modifier.fillMaxSize() ) + + Box( + modifier = Modifier + .fillMaxHeight(0.18f) + .aspectRatio(1f) + .background(Color.White, shape = RoundedCornerShape(12.dp)) + .padding(4.dp), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(Res.drawable.compose_multiplatform), + contentDescription = "App Logo", + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(8.dp)) + ) + } + } + } else { + Box( + modifier = Modifier.size(280.dp), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator( + modifier = Modifier.size(48.dp), + color = ColorPrefixA, + strokeWidth = 4.dp, + trackColor = ColorPrefixB.copy(alpha = 0.2f) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "Génération du QR Code...", + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f) + ) + ) + } } } - } else { - Box( - modifier = Modifier.size(280.dp), - contentAlignment = Alignment.Center + Spacer(modifier = Modifier.height(2.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), + ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CircularProgressIndicator( - modifier = Modifier.size(48.dp), - color = ColorPrefixA, - strokeWidth = 4.dp, - trackColor = ColorPrefixB.copy(alpha = 0.2f) - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = "Génération du QR Code...", - style = MaterialTheme.typography.bodyMedium.copy( - color = ColorValue, - fontWeight = FontWeight.Medium - ) - ) - } + Text( + text = "Fermer", + fontWeight = FontWeight.Bold, + fontSize = 16.sp + ) } } - - Spacer(modifier = Modifier.height(12.dp)) } } } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Settings.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Settings.kt index 799cb96..c9f78ef 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Settings.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Settings.kt @@ -1,14 +1,19 @@ package mg.dot.feufaro.ui +import SharedScreenModel import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape 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.LightMode import androidx.compose.material3.* import androidx.compose.runtime.* 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.unit.dp import androidx.compose.ui.unit.sp -import SharedScreenModel -import androidx.compose.material.icons.automirrored.filled.Undo +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import mg.dot.feufaro.DisplayConfigManager import mg.dot.feufaro.getPlatform import mg.dot.feufaro.midi.Dynamic +import org.koin.compose.koinInject @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -39,6 +46,22 @@ fun Settings( val isFullScreenEnabled by sharedScreenModel.isFullScreen.collectAsState() val platform = getPlatform() val isAndroid = platform.name.startsWith("Android") + + // Load Configurations + val displayConfigManager = koinInject() + 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( onDismissRequest = onDismissRequest, title = { @@ -63,8 +86,8 @@ fun Settings( onToggle = { expandedGeneral = !expandedGeneral } ) { Column( - modifier = Modifier.padding(8.dp), - verticalArrangement = Arrangement.spacedBy(10.dp) + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) ) { if(isAndroid) { 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( imageVector = Icons.AutoMirrored.Default.Undo, contentDescription = null, - tint = Color.Red + tint = MaterialTheme.colorScheme.error ) } } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/SimpleDrawerContent.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/SimpleDrawerContent.kt index 29cb813..905df89 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/SimpleDrawerContent.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/SimpleDrawerContent.kt @@ -2,7 +2,6 @@ package mg.dot.feufaro.ui import SharedScreenModel import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable 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.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.EventNote -import androidx.compose.material.icons.automirrored.filled.Note -import androidx.compose.material.icons.automirrored.filled.StarHalf +import androidx.compose.material.icons.automirrored.filled.ListAlt +import androidx.compose.material.icons.automirrored.filled.PlaylistPlay import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.runtime.* @@ -25,7 +23,6 @@ import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -87,23 +84,28 @@ fun SimpleDrawerContent( onCheckedChange = { newState -> sharedScreenModel.toggleEditorMode(newState) }, - label = "Mode Edit", - color = MaterialTheme.colorScheme.primary + thumbIcon = Icons.Default.Edit, + label = "Mode Edit" ) CustomSwitchItem( checked = state1, onCheckedChange = { state1 = it }, + thumbIcon = Icons.Default.MusicNote, 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 { DrawerHeaderSticky( title = "Solfa disponibles", - icon = Icons.AutoMirrored.Filled.Note, - color = MaterialTheme.colorScheme.primary, + icon = Icons.AutoMirrored.Filled.ListAlt, isExpanded = internalExpanded, onToggle = { internalExpanded = !internalExpanded }, count = internalList.size @@ -134,11 +135,29 @@ fun SimpleDrawerContent( if (internalExpanded) { items(internalList) { item -> val isSelected = item.path == activePath + val favoriteLists by sharedScreenModel.playlistItems.collectAsState() + val isFavorite = favoriteLists.contains(item) + NavigationDrawerItem( 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, onClick = { scope.launch { drawerState.close() } @@ -149,8 +168,8 @@ fun SimpleDrawerContent( shape = RoundedCornerShape(5.dp), modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp), colors = NavigationDrawerItemDefaults.colors( - selectedContainerColor = Color(0, 157, 255).copy(alpha = 0.1f), - selectedTextColor = Color(0, 157, 255) + selectedContainerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f), + selectedTextColor = MaterialTheme.colorScheme.primary ) ) } @@ -159,8 +178,7 @@ fun SimpleDrawerContent( stickyHeader { DrawerHeaderSticky( title = "Personnel", - icon = Icons.AutoMirrored.Filled.EventNote, - color = MaterialTheme.colorScheme.tertiary, + icon = Icons.Default.LibraryMusic, isExpanded = externalExpanded, onToggle = { externalExpanded = !externalExpanded }, count = externalList.size @@ -170,12 +188,30 @@ fun SimpleDrawerContent( if (externalExpanded) { items(externalList) { item -> val isSelected = item.path == activePath + val favoriteLists by sharedScreenModel.playlistItems.collectAsState() + val isFavorite = favoriteLists.contains(item) + if (item.path != "") { NavigationDrawerItem( 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, onClick = { scope.launch { drawerState.close() } @@ -186,8 +222,8 @@ fun SimpleDrawerContent( shape = RoundedCornerShape(5.dp), modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp), colors = NavigationDrawerItemDefaults.colors( - selectedContainerColor = Color.Blue.copy(alpha = 0.1f), - selectedTextColor = Color.Blue + selectedContainerColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.5f), + selectedTextColor = MaterialTheme.colorScheme.onSecondary ) ) } @@ -197,8 +233,7 @@ fun SimpleDrawerContent( stickyHeader { DrawerHeaderSticky( title = "Playlist", - icon = Icons.AutoMirrored.Filled.StarHalf, - color = MaterialTheme.colorScheme.tertiary, + icon = Icons.AutoMirrored.Filled.PlaylistPlay, isExpanded = playListExpanded, onToggle = { playListExpanded = !playListExpanded }, count = playList.size @@ -211,8 +246,36 @@ fun SimpleDrawerContent( val isSelected = item.path == activePath NavigationDrawerItem( + icon = { + Text("♯", fontSize = 22.sp) + }, 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, onClick = { @@ -221,11 +284,11 @@ fun SimpleDrawerContent( solfaScreenModel.loadFromFile(item.path) onSongSelected(midi) }, + shape = RoundedCornerShape(5.dp), modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp), colors = NavigationDrawerItemDefaults.colors( - selectedContainerColor = Color(0xFFFFD700).copy(alpha = 0.3f), - selectedTextColor = Color(0xFF665500), - selectedIconColor = Color(0xFF665500) + selectedContainerColor = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.5f), + selectedTextColor = MaterialTheme.colorScheme.onTertiary, ) ) } @@ -250,7 +313,7 @@ fun SimpleDrawerContent( IconButton(onClick = { 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() 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() 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 = { 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( title: String, icon: ImageVector, - color: Color, isExpanded: Boolean, onToggle: () -> Unit, count: Int @@ -315,23 +377,23 @@ fun DrawerHeaderSticky( modifier = Modifier.padding(16.dp), 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)) Text( - text = "$title".uppercase(), + text = title.uppercase(), modifier = Modifier.weight(1f), - style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Bold), - color = color + style = MaterialTheme.typography.titleSmall.copy(fontWeight = FontWeight.Bold), + color = MaterialTheme.colorScheme.primary ) Surface( - color = Color.Green.copy(alpha = 0.1f), + color = MaterialTheme.colorScheme.error.copy(alpha = 0.1f), shape = MaterialTheme.shapes.extraSmall ) { Text( - text = "${count}", + text = count.toString() , modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp), style = MaterialTheme.typography.labelSmall, - color = Color.Green + color = MaterialTheme.colorScheme.error ) } Icon( @@ -344,94 +406,25 @@ fun DrawerHeaderSticky( } @Composable -fun DrawerItemLabel(item: DrawerItem, sharedScreenModel: SharedScreenModel) { - val favoriteLists by sharedScreenModel.playlistItems.collectAsState() - val isFavorite = favoriteLists.contains(item) +fun DrawerItemLabel(item: DrawerItem) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { 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) } - - 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 fun CustomSwitchItem( checked: Boolean, onCheckedChange: (Boolean) -> Unit, label: String, - color: Color + thumbIcon: ImageVector ) { Column( horizontalAlignment = Alignment.CenterHorizontally @@ -440,11 +433,20 @@ fun CustomSwitchItem( checked = checked, onCheckedChange = onCheckedChange, colors = SwitchDefaults.colors( - checkedThumbColor = Color.White, - checkedTrackColor = color, - uncheckedTrackColor = color.copy(alpha = 0.3f) + checkedThumbColor = MaterialTheme.colorScheme.error, + checkedTrackColor = MaterialTheme.colorScheme.error.copy(alpha = 0.5f), + 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 = label, diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Theme.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Theme.kt new file mode 100644 index 0000000..eac852b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Theme.kt @@ -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 { + 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 + ) +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/ui/MyVerticalScrollbar.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/ui/MyVerticalScrollbar.kt index 45b25be..e713220 100644 --- a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/ui/MyVerticalScrollbar.kt +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/ui/MyVerticalScrollbar.kt @@ -1,17 +1,19 @@ package mg.dot.feufaro.ui import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.ScrollbarStyle import androidx.compose.foundation.VerticalScrollbar import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.rememberScrollbarAdapter 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.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp @Composable actual fun MyVerticalScrollbar ( @@ -29,10 +31,22 @@ actual fun MyVerticalScrollbar ( 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) { VerticalScrollbar( 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() }