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 e55e2172c1
15 changed files with 942 additions and 421 deletions

View file

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

View file

@ -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<DisplayConfigManager>()
val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState()
FeufaroTheme(themeModeSelected = currentDisplayConfig.themeMode, userFontSize = currentDisplayConfig.fontSize) {
Navigator(screen = ScreenSolfa) {
CurrentScreen()
}
}
val fileRepository = koinInject<FileRepository>()
val displayConfigManager = koinInject<DisplayConfigManager>()
val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState()
// Load Configurations
val configScope = CoroutineScope(Dispatchers.Default)
val sharedScreenModel = koinInject<SharedScreenModel>()
val solfaScreenModel = koinInject<SolfaScreenModel>()
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<DisplayConfigManager>()
val currentDisplayConfig by displayConfigManager.displayConfig.collectAsState()
val buttonColors = ButtonDefaults.buttonColors(
containerColor = currentDisplayConfig.buttonContainerColorHex.toColor(),
disabledContainerColor = currentDisplayConfig.buttonDisabledContainerColorHex.toColor(),
contentColor = currentDisplayConfig.buttonDisabledContentColorHex.toColor(),
disabledContentColor = currentDisplayConfig.buttonDisabledContentColorHex.toColor()
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,

View file

@ -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> = _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
}

View file

@ -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(

View file

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

View file

@ -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<String>().apply {
addAll(editState.lyricsByStanza.values.toList())
}
}
val editedLyricsMap = remember {
mutableStateMapOf<Int, String>().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("(?<![\\?:,\\.; ])_"), "-_")
.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 {
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),

View file

@ -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() }

View file

@ -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
)
}
}

View file

@ -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
)
}
}

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.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 ->

View file

@ -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))
}
}
}

View file

@ -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<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(
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
)
}
}

View file

@ -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,

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
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()
}