commit e3f7c2d5ba5e1016185c1bc8d44e0fa0e1df8b59
parent c5de5e146a35881a1a810c5f0783d2c98680f542
Author: massi <mdsiboldi@gmail.com>
Date: Wed, 3 Jul 2024 02:24:39 -0700
persist settings
Diffstat:
5 files changed, 99 insertions(+), 13 deletions(-)
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
@@ -4,7 +4,7 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
- <DropdownSelection timestamp="2024-07-01T20:44:48.239762038Z">
+ <DropdownSelection timestamp="2024-07-03T09:18:11.113954846Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=1C161FDF600FF6" />
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
@@ -59,6 +59,7 @@ dependencies {
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.lifecycle.viewmodel.compose)
+ implementation(libs.androidx.datastore.preferences)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
diff --git a/app/src/main/java/com/example/fretboardtrainer/MainActivity.kt b/app/src/main/java/com/example/fretboardtrainer/MainActivity.kt
@@ -1,5 +1,7 @@
package com.example.fretboardtrainer
+import android.app.Application
+import android.content.Context
import android.content.pm.ActivityInfo
import android.os.Bundle
import androidx.activity.ComponentActivity
@@ -40,13 +42,51 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
-import androidx.lifecycle.ViewModel
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.fretboardtrainer.ui.theme.FretboardTrainerTheme
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+val FRET_COUNT = 13; // open + 12 frets
+val STRING_COUNT = 6;
+
+val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
+
+fun serializeBoolList(l: List<Boolean>): Int {
+ var num: Int = 0;
+ for (b in l) {
+ num = num shl 1
+ if (b) {
+ num = num or 1
+ }
+ }
+ return num
+}
+
+fun deserializeBoolList(num: Int, len: Int): List<Boolean> {
+ var l = len - 1
+ var n = num
+ val list = MutableList<Boolean>(len) { false }
+ while (l >= 0) {
+ if ((n and 1) == 1) {
+ list[l] = true
+ }
+ n = n shr 1
+ l--
+ }
+ return list
+}
fun weightForFret(fretIdx: Int): Float {
var weight = 24 - fretIdx * 1.0f
@@ -78,18 +118,43 @@ fun noteName(note: List<Int>): String {
return NOTES[(startIdx + note[0]) % NOTES.size]
}
+// TODO: persist string and fret settings across app launches.
data class FretfretState(
val showSettings: Boolean = false,
- val stringSettings: List<Boolean> = List(6) { true },
- val fretSettings: List<Boolean> = List(13) { true },
+ val stringSettings: List<Boolean> = List(STRING_COUNT) { true },
+ val fretSettings: List<Boolean> = List(FRET_COUNT) { true },
val note: List<Int>? = null,
val lastNote: List<Int>? = null,
)
-class FretfretViewModel : ViewModel() {
+val STRING_KEY = intPreferencesKey("string_key")
+val FRET_KEY = intPreferencesKey("fret_key")
+
+class FretfretViewModel(application: Application) : AndroidViewModel(application) {
private val _uiState = MutableStateFlow(FretfretState())
val uiState = _uiState.asStateFlow()
+ init {
+ viewModelScope.launch {
+ _uiState.update { currentState ->
+ currentState.copy(
+ stringSettings = application.dataStore.data.map { prefs ->
+ when (prefs[STRING_KEY]) {
+ is Int -> deserializeBoolList(prefs[STRING_KEY]!!, STRING_COUNT)
+ else -> List(STRING_COUNT) { true }
+ }
+ }.first(),
+ fretSettings = application.dataStore.data.map { prefs ->
+ when (prefs[FRET_KEY]) {
+ is Int -> deserializeBoolList(prefs[FRET_KEY]!!, FRET_COUNT)
+ else -> List(FRET_COUNT) { true }
+ }
+ }.first(),
+ )
+ }
+ }
+ }
+
fun toggleString(idx: Int) {
_uiState.update { currentState ->
val res = currentState.stringSettings.toMutableList()
@@ -98,6 +163,7 @@ class FretfretViewModel : ViewModel() {
stringSettings = res.toList()
)
}
+ viewModelScope.launch { updateSettings() }
}
fun toggleFret(idx: Int) {
@@ -108,6 +174,7 @@ class FretfretViewModel : ViewModel() {
fretSettings = res.toList()
)
}
+ viewModelScope.launch { updateSettings() }
}
private fun pickNote(): List<Int> {
@@ -139,6 +206,13 @@ class FretfretViewModel : ViewModel() {
}
}
+ private suspend fun updateSettings() {
+ getApplication<Application>().dataStore.edit { settings ->
+ settings[STRING_KEY] = serializeBoolList(uiState.value.stringSettings);
+ settings[FRET_KEY] = serializeBoolList(uiState.value.fretSettings);
+ }
+ }
+
fun toggleSettings() {
val show = !uiState.value.showSettings
_uiState.update { currentState ->
@@ -165,7 +239,7 @@ class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun Fretfret(viewModel: FretfretViewModel = viewModel() ) {
+fun Fretfret(viewModel: FretfretViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsState()
FretboardTrainerTheme {
Scaffold(
@@ -205,7 +279,7 @@ fun Fretfret(viewModel: FretfretViewModel = viewModel() ) {
)
if (uiState.showSettings) {
Row(horizontalArrangement = Arrangement.SpaceBetween) {
- repeat(13) { n ->
+ repeat(FRET_COUNT) { n ->
Column(
modifier = Modifier
.weight(weightForFret(n)),
@@ -246,16 +320,13 @@ fun Fretfret(viewModel: FretfretViewModel = viewModel() ) {
}
}
-val FRET_COUNT = 13; // open + 12 frets
-val STRING_COUNT = 6;
-
@Composable
fun StringSettings(settings: List<Boolean>, toggle: (n: Int) -> Unit) {
Column {
- repeat(6) { n ->
+ repeat(STRING_COUNT) { n ->
Row(verticalAlignment = Alignment.CenterVertically) {
Text(STRINGS[n])
- Checkbox(modifier = Modifier.height((200 / 6).dp),
+ Checkbox(modifier = Modifier.height((200 / STRING_COUNT).dp),
checked = settings[n],
onCheckedChange = { toggle(n) })
}
@@ -318,7 +389,7 @@ fun TopBar() {
@Composable
fun Strings(fretIdx: Int, noteAt: Int?, color: Color, openStringColor: Color) {
Column(modifier = Modifier.fillMaxSize()) {
- repeat(6) { n: Int ->
+ repeat(STRING_COUNT) { n: Int ->
Box(
modifier = Modifier
.fillMaxWidth()
diff --git a/app/src/test/java/com/example/fretboardtrainer/ExampleUnitTest.kt b/app/src/test/java/com/example/fretboardtrainer/ExampleUnitTest.kt
@@ -14,4 +14,14 @@ class ExampleUnitTest {
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
+
+ @Test
+ fun serializes_settings() {
+ assertEquals(0b1010, serializeBoolList(listOf(true, false, true, false)))
+ }
+
+ @Test
+ fun deserializes_settings() {
+ assertEquals(listOf(true, false, true, false), deserializeBoolList(0b1010, 4))
+ }
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
@@ -8,6 +8,8 @@ espressoCore = "3.5.1"
lifecycle = "2.8.3"
activityCompose = "1.9.0"
composeBom = "2023.08.00"
+datastorePreferencesCore = "1.1.1"
+datastorePreferences = "1.1.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -25,6 +27,8 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-datastore-preferences-core = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "datastorePreferencesCore" }
+androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }