Add Settings Page on Drawer & Controls Dynamic velocity
This commit is contained in:
parent
89d0b850f8
commit
e2a5d18a20
4 changed files with 481 additions and 148 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
package mg.dot.feufaro.midi
|
package mg.dot.feufaro.midi
|
||||||
|
|
||||||
enum class Dynamic(val velocity: Int, val label: String, val factor: Float) {
|
import java.util.prefs.Preferences
|
||||||
|
|
||||||
|
enum class Dynamic(val velocity: Int, val label: String, val defaultFactor: Float) {
|
||||||
PPP(16, "ppp", 0.55f),
|
PPP(16, "ppp", 0.55f),
|
||||||
PP (33, "pp", 0.66f),
|
PP (33, "pp", 0.66f),
|
||||||
P (49, "p", 0.77f),
|
P (49, "p", 0.77f),
|
||||||
|
|
@ -9,9 +11,29 @@ enum class Dynamic(val velocity: Int, val label: String, val factor: Float) {
|
||||||
F (96, "f", 1.15f),
|
F (96, "f", 1.15f),
|
||||||
FF (112, "ff", 1.35f),
|
FF (112, "ff", 1.35f),
|
||||||
FFF(126, "fff", 1.60f);
|
FFF(126, "fff", 1.60f);
|
||||||
|
private val prefs = Preferences.userRoot().node("mg.dot.feufaro")
|
||||||
|
var savedFactor: Float = prefs.getFloat("dynamic_${name.lowercase()}", defaultFactor)
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
prefs.putFloat("dynamic_${name.lowercase()}", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
val factor: Float
|
||||||
|
get() = if (isGloballyEnabled) savedFactor else 1.00f
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val globalPrefs = Preferences.userRoot().node("mg.dot.feufaro")
|
||||||
|
var isGloballyEnabled: Boolean = globalPrefs.getBoolean("dynamics_enabled", true)
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
globalPrefs.putBoolean("dynamics_enabled", value)
|
||||||
|
}
|
||||||
|
|
||||||
fun fromVelocity(v: Int): Dynamic =
|
fun fromVelocity(v: Int): Dynamic =
|
||||||
entries.minByOrNull { kotlin.math.abs(it.velocity - v) } ?: MF
|
entries.minByOrNull { kotlin.math.abs(it.velocity - v) } ?: MF
|
||||||
|
|
||||||
|
fun resetToDefaults() {
|
||||||
|
entries.forEach { it.savedFactor = it.defaultFactor }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +50,7 @@ fun MainScreenWithDrawer(
|
||||||
content: @Composable (PaddingValues) -> Unit,
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
) {
|
) {
|
||||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
|
var showSettingsDialog by remember { mutableStateOf(false) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val items by sharedScreenModel.drawerItems.collectAsState()
|
val items by sharedScreenModel.drawerItems.collectAsState()
|
||||||
|
|
@ -175,6 +176,9 @@ fun MainScreenWithDrawer(
|
||||||
},
|
},
|
||||||
onSongSelected = { newSong ->
|
onSongSelected = { newSong ->
|
||||||
sharedScreenModel.loadNewSong("$midiFile")
|
sharedScreenModel.loadNewSong("$midiFile")
|
||||||
|
},
|
||||||
|
onSettingsCheck = {
|
||||||
|
showSettingsDialog = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}, content = {
|
}, content = {
|
||||||
|
|
@ -207,6 +211,13 @@ fun MainScreenWithDrawer(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (showSettingsDialog) {
|
||||||
|
Settings(
|
||||||
|
onDismissRequest = {
|
||||||
|
showSettingsDialog = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
val favoritePaths by sharedScreenModel.playlistItems.collectAsState()
|
val favoritePaths by sharedScreenModel.playlistItems.collectAsState()
|
||||||
Scaffold(contentWindowInsets = WindowInsets.safeDrawing, topBar = {
|
Scaffold(contentWindowInsets = WindowInsets.safeDrawing, topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
|
|
|
||||||
393
composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Settings.kt
Normal file
393
composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Settings.kt
Normal file
|
|
@ -0,0 +1,393 @@
|
||||||
|
package mg.dot.feufaro.ui
|
||||||
|
|
||||||
|
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.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
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 mg.dot.feufaro.midi.Dynamic
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun Settings(
|
||||||
|
onDismissRequest: () -> Unit
|
||||||
|
) {
|
||||||
|
var isDynamicEnabled by remember { mutableStateOf(Dynamic.isGloballyEnabled) }
|
||||||
|
var refreshTrigger by remember { mutableStateOf(0) }
|
||||||
|
var expandedGeneral by remember { mutableStateOf(false) }
|
||||||
|
var expandedPrint by remember { mutableStateOf(false) }
|
||||||
|
var expandedAudio by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = "Paramètres",
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
fontSize = 22.sp
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||||
|
|
||||||
|
SettingsAccordion(
|
||||||
|
title = "Paramètres Général",
|
||||||
|
isExpanded = expandedGeneral,
|
||||||
|
onToggle = { expandedGeneral = !expandedGeneral }
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsAccordion(
|
||||||
|
title = "Paramètres Impression",
|
||||||
|
isExpanded = expandedPrint,
|
||||||
|
onToggle = { expandedPrint = !expandedPrint }
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsAccordion(
|
||||||
|
title = "Paramètres Audio & Nuances",
|
||||||
|
isExpanded = expandedAudio,
|
||||||
|
onToggle = { expandedAudio = !expandedAudio }
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(top = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
|
Text(if(isDynamicEnabled) "Désactiver" else "Activer", fontWeight = FontWeight.Bold, fontSize = 15.sp)
|
||||||
|
Switch(
|
||||||
|
checked = isDynamicEnabled,
|
||||||
|
onCheckedChange = { enabled ->
|
||||||
|
isDynamicEnabled = enabled
|
||||||
|
Dynamic.isGloballyEnabled = enabled
|
||||||
|
refreshTrigger++
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = {
|
||||||
|
Dynamic.resetToDefaults()
|
||||||
|
isDynamicEnabled = true
|
||||||
|
refreshTrigger++
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
|
contentColor = MaterialTheme.colorScheme.error
|
||||||
|
),
|
||||||
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
Text("Réinitialiser", fontSize = 14.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
key(refreshTrigger) {
|
||||||
|
val dynamicEntries = Dynamic.entries
|
||||||
|
val minF = 0.22f
|
||||||
|
val maxF = 2.33f
|
||||||
|
val step = 0.05f
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(Color(0xFF121212).copy(alpha = 0.6f), RoundedCornerShape(16.dp))
|
||||||
|
.padding(8.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(0.dp)
|
||||||
|
) {
|
||||||
|
dynamicEntries.forEachIndexed { index, dynamicItem ->
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(1.dp),
|
||||||
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
|
) {
|
||||||
|
// (-)
|
||||||
|
if(index == 0) {
|
||||||
|
FilledIconButton(
|
||||||
|
onClick = {
|
||||||
|
if (isDynamicEnabled) {
|
||||||
|
val delta = - step
|
||||||
|
var insideBounds = true
|
||||||
|
dynamicEntries.forEach { item ->
|
||||||
|
val projectedValue = item.savedFactor + delta
|
||||||
|
if (projectedValue < minF || projectedValue > maxF) {
|
||||||
|
insideBounds = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (insideBounds) {
|
||||||
|
dynamicEntries.forEach { item ->
|
||||||
|
item.savedFactor = (item.savedFactor + delta).coerceIn(minF, maxF)
|
||||||
|
}
|
||||||
|
refreshTrigger++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = isDynamicEnabled,
|
||||||
|
colors = IconButtonDefaults.filledIconButtonColors(
|
||||||
|
containerColor = Color(0xFF2C2C2C),
|
||||||
|
contentColor = Color.White
|
||||||
|
),
|
||||||
|
modifier = Modifier.size(32.dp)
|
||||||
|
) {
|
||||||
|
Text("-", fontWeight = FontWeight.Bold, fontSize = 16.sp)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.width(32.dp))
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = dynamicItem.label.lowercase(),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = if (isDynamicEnabled) Color.White else Color.Gray,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.width(30.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Slider(
|
||||||
|
value = dynamicItem.savedFactor,
|
||||||
|
onValueChange = { newValue ->
|
||||||
|
if (isDynamicEnabled) {
|
||||||
|
val oldValue = dynamicItem.savedFactor
|
||||||
|
val delta = newValue - oldValue
|
||||||
|
var insideBounds = true
|
||||||
|
|
||||||
|
dynamicEntries.forEach { item ->
|
||||||
|
val projectedValue = item.savedFactor + delta
|
||||||
|
if (projectedValue < minF || projectedValue > maxF) {
|
||||||
|
insideBounds = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insideBounds) {
|
||||||
|
dynamicEntries.forEach { item ->
|
||||||
|
item.savedFactor = (item.savedFactor + delta).coerceIn(minF, maxF)
|
||||||
|
}
|
||||||
|
refreshTrigger++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
valueRange = minF..maxF,
|
||||||
|
enabled = isDynamicEnabled,
|
||||||
|
track = { sliderState ->
|
||||||
|
SliderDefaults.Track(
|
||||||
|
sliderState = sliderState,
|
||||||
|
enabled = isDynamicEnabled,
|
||||||
|
colors = SliderDefaults.colors(
|
||||||
|
activeTrackColor = if (isDynamicEnabled) Color.Blue else Color.Gray,
|
||||||
|
inactiveTrackColor = Color.LightGray
|
||||||
|
),
|
||||||
|
modifier = Modifier.height(6.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
thumb = {
|
||||||
|
SliderDefaults.Thumb(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
colors = SliderDefaults.colors(thumbColor = if(isDynamicEnabled) Color(0xFFFF4081) else Color.LightGray),
|
||||||
|
modifier = Modifier.size(12.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors = SliderDefaults.colors(
|
||||||
|
activeTrackColor = Color(0xFF7C4DFF),
|
||||||
|
inactiveTrackColor = Color(0xFF2C2C2C)
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.height(16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = String.format("%.2f", if (isDynamicEnabled) dynamicItem.savedFactor else 1.00f),
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = if (isDynamicEnabled) Color(0xFFDEFF9A) else Color.DarkGray,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.width(30.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// (+)
|
||||||
|
if(index == 0) {
|
||||||
|
FilledIconButton(
|
||||||
|
onClick = {
|
||||||
|
if (isDynamicEnabled) {
|
||||||
|
val delta = step
|
||||||
|
var insideBounds = true
|
||||||
|
dynamicEntries.forEach { item ->
|
||||||
|
val projectedValue = item.savedFactor + delta
|
||||||
|
if (projectedValue < minF || projectedValue > maxF) {
|
||||||
|
insideBounds = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (insideBounds) {
|
||||||
|
dynamicEntries.forEach { item ->
|
||||||
|
item.savedFactor =
|
||||||
|
(item.savedFactor + delta).coerceIn(minF, maxF)
|
||||||
|
}
|
||||||
|
refreshTrigger++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = isDynamicEnabled,
|
||||||
|
colors = IconButtonDefaults.filledIconButtonColors(
|
||||||
|
containerColor = Color(0xFF2C2C2C),
|
||||||
|
contentColor = Color.White
|
||||||
|
),
|
||||||
|
modifier = Modifier.size(32.dp)
|
||||||
|
) {
|
||||||
|
Text("+", fontWeight = FontWeight.Bold, fontSize = 16.sp)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.width(32.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text("Enregistrer", fontWeight = FontWeight.Bold)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text("Annuler")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsAccordion(
|
||||||
|
title: String,
|
||||||
|
isExpanded: Boolean,
|
||||||
|
onToggle: () -> Unit,
|
||||||
|
content: @Composable ColumnScope.() -> Unit
|
||||||
|
) {
|
||||||
|
val arrowRotationDegree by animateFloatAsState(
|
||||||
|
targetValue = if (isExpanded) 180f else 0f,
|
||||||
|
animationSpec = tween(durationMillis = 250),
|
||||||
|
label = "ArrowRotation"
|
||||||
|
)
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(16.dp)),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = if (isExpanded) {
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.25f)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
border = BorderStroke(
|
||||||
|
width = 1.dp,
|
||||||
|
color = if (isExpanded) MaterialTheme.colorScheme.primary.copy(alpha = 0.3f) else Color.Transparent
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onToggle() }
|
||||||
|
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
fontWeight = if (isExpanded) FontWeight.ExtraBold else FontWeight.SemiBold,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
color = if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.KeyboardArrowDown,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.rotate(arrowRotationDegree)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(visible = isExpanded) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
HorizontalDivider(
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.1f),
|
||||||
|
modifier = Modifier.padding(bottom = 12.dp)
|
||||||
|
)
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
package mg.dot.feufaro.ui
|
package mg.dot.feufaro.ui
|
||||||
|
|
||||||
import SharedScreenModel
|
import SharedScreenModel
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.EventNote
|
import androidx.compose.material.icons.automirrored.filled.EventNote
|
||||||
|
|
@ -26,7 +27,6 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import mg.dot.feufaro.data.DrawerItem
|
import mg.dot.feufaro.data.DrawerItem
|
||||||
|
|
@ -34,7 +34,7 @@ import mg.dot.feufaro.getPlatform
|
||||||
import mg.dot.feufaro.solfa.Solfa
|
import mg.dot.feufaro.solfa.Solfa
|
||||||
import mg.dot.feufaro.viewmodel.SolfaScreenModel
|
import mg.dot.feufaro.viewmodel.SolfaScreenModel
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SimpleDrawerContent(
|
fun SimpleDrawerContent(
|
||||||
items: List<DrawerItem>,
|
items: List<DrawerItem>,
|
||||||
|
|
@ -45,6 +45,7 @@ fun SimpleDrawerContent(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
onScannerButtonClick: () -> Unit,
|
onScannerButtonClick: () -> Unit,
|
||||||
onSongSelected: (String) -> Unit,
|
onSongSelected: (String) -> Unit,
|
||||||
|
onSettingsCheck: () -> Unit
|
||||||
) {
|
) {
|
||||||
var searchTxt by remember { mutableStateOf("") }
|
var searchTxt by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
|
@ -76,7 +77,8 @@ fun SimpleDrawerContent(
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
CustomSwitchItem(
|
CustomSwitchItem(
|
||||||
checked = editMode,
|
checked = editMode,
|
||||||
|
|
@ -228,152 +230,57 @@ fun SimpleDrawerContent(
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Button(onClick = {
|
TooltipBox(
|
||||||
scope.launch { drawerState.close() }
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
}) {
|
tooltip = { PlainTooltip { Text("") } },
|
||||||
Text("Scanner")
|
state = rememberTooltipState()
|
||||||
}
|
|
||||||
Button(onClick = {
|
|
||||||
solfaScreenModel.loadCustomFile()
|
|
||||||
scope.launch { drawerState.close() }
|
|
||||||
}) {
|
|
||||||
Text("Importer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DrawerAccordionSection(
|
|
||||||
title: String,
|
|
||||||
items: List<DrawerItem>,
|
|
||||||
icon: ImageVector,
|
|
||||||
iconColor: Color,
|
|
||||||
activePath: String,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
solfaScreenModel: SolfaScreenModel,
|
|
||||||
onItemClick: (DrawerItem) -> Unit
|
|
||||||
) {
|
|
||||||
var expanded by remember { mutableStateOf(true) }
|
|
||||||
|
|
||||||
val rotationState by animateFloatAsState(targetValue = if (expanded) 180f else 0f)
|
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { expanded = !expanded }
|
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = icon,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = iconColor,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(12.dp))
|
|
||||||
Text(
|
|
||||||
text = title.uppercase(),
|
|
||||||
style = MaterialTheme.typography.labelLarge.copy(
|
|
||||||
letterSpacing = 1.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
Surface(
|
|
||||||
color = iconColor.copy(alpha = 0.1f),
|
|
||||||
shape = MaterialTheme.shapes.extraSmall
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "${items.size}",
|
|
||||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = iconColor
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(Modifier.width(8.dp))
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.KeyboardArrowDown,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.rotate(rotationState).size(20.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedVisibility(visible = expanded) {
|
|
||||||
Column {
|
|
||||||
items.forEach { item ->
|
|
||||||
val isSelected = item.path == activePath
|
|
||||||
NavigationDrawerItem(
|
|
||||||
label = {
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = item.title,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = item.contentTitle,
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
maxLines = 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selected = isSelected,
|
|
||||||
onClick = { onItemClick(item) },
|
|
||||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
colors = NavigationDrawerItemDefaults.colors(
|
|
||||||
unselectedContainerColor = Color.Transparent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier.padding(top = 8.dp),
|
|
||||||
thickness = 0.5.dp,
|
|
||||||
color = MaterialTheme.colorScheme.outlineVariant
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.padding(16.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Row (
|
|
||||||
|
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
IconButton(onClick = {
|
||||||
Button(
|
scope.launch { drawerState.close() }
|
||||||
onClick = {
|
}) {
|
||||||
|
Icon(Icons.Default.GraphicEq, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text("Scanner")
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
}
|
||||||
Button(
|
|
||||||
onClick = {
|
TooltipBox(
|
||||||
scope.launch {
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
//drawerState.close()
|
tooltip = { PlainTooltip { Text("Importer un fichier") } },
|
||||||
}
|
state = rememberTooltipState()
|
||||||
solfaScreenModel.loadCustomFile()
|
) {
|
||||||
}
|
IconButton(onClick = {
|
||||||
) {
|
solfaScreenModel.loadCustomFile()
|
||||||
Text("Importer")
|
scope.launch { drawerState.close() }
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.FolderOpen, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TooltipBox(
|
||||||
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
tooltip = { PlainTooltip { Text("Paramètres") } },
|
||||||
|
state = rememberTooltipState()
|
||||||
|
) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
onSettingsCheck()
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.Settings, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TooltipBox(
|
||||||
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
tooltip = { PlainTooltip { Text("À propos") } },
|
||||||
|
state = rememberTooltipState()
|
||||||
|
) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.Info, contentDescription = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue