This commit is contained in:
2025-02-11 09:41:09 -05:00
parent c5824c9439
commit 1113c5369d
12 changed files with 254 additions and 105 deletions

2
.idea/kotlinc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="KotlinJpsPluginSettings"> <component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.0" /> <option name="version" value="2.0.20" />
</component> </component>
</project> </project>

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">

View File

@@ -55,6 +55,6 @@ litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.0.20" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

View File

@@ -359,7 +359,8 @@ class SoundClassifier(
return return
} }
val sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext) val sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext)
val highPass = sharedPref.getInt("high_pass", 0) // val highPass = sharedPref.getInt("high_pass", 0)
val highPass = 300;
val butterworth = Butterworth() val butterworth = Butterworth()
butterworth.highPass(6, 48000.0, highPass.toDouble()) butterworth.highPass(6, 48000.0, highPass.toDouble())

1
wear/TileService.kt Normal file
View File

@@ -0,0 +1 @@
class TileService {}

View File

@@ -9,12 +9,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.wear.compose.material.CompactChip import androidx.wear.compose.material.CompactChip
import androidx.wear.compose.material.MaterialTheme import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text import androidx.wear.compose.material.Text
@Composable @Composable
fun ControlDashboard(controlDashboardUiState: ControlDashboardUiState, onMicClicked: () -> Unit, modifier: Modifier = Modifier) { fun ControlDashboard(controlDashboardUiState: ControlDashboardUiState,
onMicClicked: () -> Unit,
modifier: Modifier = Modifier,
navController: NavHostController) {
Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxSize()) { Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxSize()) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
@@ -35,22 +39,17 @@ private fun ControlDashboardButton(buttonState: ControlDashboardButtonUiState,
onClick: () -> Unit, onClick: () -> Unit,
labelText: String, labelText: String,
modifier: Modifier = Modifier) { modifier: Modifier = Modifier) {
CompactChip( CompactChip(modifier = Modifier,
modifier = Modifier, onClick = onClick,
onClick = onClick ,
enabled = true, enabled = true,
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp), contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
shape = MaterialTheme.shapes.small, shape = MaterialTheme.shapes.small,
label = { label = {
Text( Text(text = labelText)
text = labelText })
)
}
)
} }
// Button(modifier = modifier, enabled = buttonState.enabled && buttonState.visible, onClick = onClick) { // Button(modifier = modifier, enabled = buttonState.enabled && buttonState.visible, onClick = onClick) {
// Text(contentDescription); // Text(contentDescription);
//// Icon(imageVector = imageVector, contentDescription = contentDescription) //// Icon(imageVector = imageVector, contentDescription = contentDescription)

View File

@@ -15,19 +15,39 @@ import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.wear.compose.material.Text
import androidx.wear.compose.navigation.SwipeDismissableNavHost import androidx.wear.compose.navigation.SwipeDismissableNavHost
import androidx.wear.compose.navigation.composable import androidx.wear.compose.navigation.composable
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices
import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales
import com.birdsounds.identify.presentation.theme.IdentifyTheme import com.birdsounds.identify.presentation.theme.IdentifyTheme
import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.audio.ui.VolumeViewModel import com.google.android.horologist.audio.ui.VolumeViewModel
import com.google.android.horologist.compose.layout.ScalingLazyColumn
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
import com.google.android.horologist.compose.layout.ScreenScaffold
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
import com.google.android.horologist.compose.material.Chip
import com.google.android.horologist.compose.material.ListHeaderDefaults.firstItemPadding
import com.google.android.horologist.compose.material.ResponsiveListHeader
import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -40,11 +60,9 @@ val Any.TAG: String
} }
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen() installSplashScreen()
@@ -69,61 +87,148 @@ fun WearApp() {
val volumeViewModel: VolumeViewModel = viewModel(factory = VolumeViewModel.Factory) val volumeViewModel: VolumeViewModel = viewModel(factory = VolumeViewModel.Factory)
val navController = rememberSwipeDismissableNavController()
val mainState = remember(activity) { val mainState = remember(activity) {
MainState( MainState(activity = activity, requestPermission = {
activity = activity, requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
requestPermission = { })
requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
}
)
} }
requestPermissionLauncher = rememberLauncherForActivityResult(RequestPermission()) { requestPermissionLauncher = rememberLauncherForActivityResult(RequestPermission()) {
scope.launch { scope.launch {
mainState.permissionResultReturned() mainState.permissionResultReturned()
} }
} }
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current val navController: NavHostController = rememberSwipeDismissableNavController()
mainState.setNavController(navController);
SwipeDismissableNavHost(navController = navController, startDestination = "speaker") {
// AppScaffold { composable("speaker") {
// DisposableEffect(mainState, scope, lifecycleOwner) { StartRecordingScreen(
// val lifecycleObserver = object : DefaultLifecycleObserver { context = context,
// override fun onStop(owner: LifecycleOwner) { navController = navController,
// super.onStop(owner) appState = mainState.appState,
// } isPermissionDenied = mainState.isPermissionDenied,
// } onMicClicked = {
// lifecycleOwner.lifecycle.addObserver(lifecycleObserver) scope.launch {
// mainState.onMicClicked()
// onDispose { }
// lifecycleOwner.lifecycle.removeObserver(lifecycleObserver) },
// } )
// } }
var sdnv = SwipeDismissableNavHost(navController = navController, startDestination = "speaker") {
composable("speaker") { composable("species_list") {
StartRecordingScreen( SpeciesListView(context = context)
context = context, }
appState = mainState.appState,
isPermissionDenied = mainState.isPermissionDenied, }
onMicClicked = {
scope.launch { // var sdnv = SwipeDismissableNavHost(navController = navController, startDestination = "list") {
mainState.onMicClicked() // composable("speaker") {
} // StartRecordingScreen(
}, // context = context,
) // appState = mainState.appState,
} // isPermissionDenied = mainState.isPermissionDenied,
// } // onMicClicked = {
// scope.launch {
// mainState.onMicClicked()
// }
// },
// )
// }
}
}
tailrec fun Context.findActivity(): Activity = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> throw IllegalStateException("findActivity should be called in the context of an Activity")
}
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun MessageDetail(id: String) {
val scrollState = rememberScrollState()
ScreenScaffold(scrollState = scrollState) {
val padding =
ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text, last = ScalingLazyColumnDefaults.ItemType.Text)()
Column(modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState)
.rotaryWithScroll(scrollState)
.padding(padding),
verticalArrangement = Arrangement.Center) {
Text(text = id, textAlign = TextAlign.Center, modifier = Modifier.fillMaxSize())
} }
} }
} }
tailrec fun Context.findActivity(): Activity = @OptIn(ExperimentalHorologistApi::class)
when (this) { @Composable
is Activity -> this fun MessageList(onMessageClick: (String) -> Unit) {
is ContextWrapper -> baseContext.findActivity() val columnState =
else -> throw IllegalStateException( rememberResponsiveColumnState(contentPadding = ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text,
"findActivity should be called in the context of an Activity" last = ScalingLazyColumnDefaults.ItemType.Chip))
)
ScreenScaffold(scrollState = columnState) {
ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
item {
ResponsiveListHeader(contentPadding = firstItemPadding()) {
Text(text = "Hey hey hey")
}
}
item {
Chip(label = "Message 1", onClick = { onMessageClick("message1") })
}
item {
Chip(label = "Message 2", onClick = { onMessageClick("message2") })
}
}
} }
}
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun MessageList2(onMessageClick: (String) -> Unit) {
val columnState =
rememberResponsiveColumnState(contentPadding = ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text,
last = ScalingLazyColumnDefaults.ItemType.Chip))
ScreenScaffold(scrollState = columnState) {
ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
item {
ResponsiveListHeader(contentPadding = firstItemPadding()) {
Text(text = "Hey hey hey")
}
}
item {
Chip(label = "Message 3", onClick = { onMessageClick("message3") })
}
item {
Chip(label = "Message 4", onClick = { onMessageClick("message4") })
}
}
}
}
@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun MessageDetailPreview() {
MessageDetail("test")
}
@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun MessageListPreview() {
MessageList(onMessageClick = {})
}

View File

@@ -1,9 +1,7 @@
package com.birdsounds.identify.presentation package com.birdsounds.identify.presentation
import android.Manifest import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.util.Log import android.util.Log
import androidx.annotation.RequiresPermission import androidx.annotation.RequiresPermission
@@ -11,24 +9,13 @@ import androidx.compose.foundation.MutatorMutex
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import co.touchlab.stately.collections.ConcurrentMutableCollection import androidx.navigation.NavHostController
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.ChannelClient import com.google.android.gms.wearable.ChannelClient
import com.google.android.gms.wearable.ChannelClient.ChannelCallback import com.google.android.gms.wearable.ChannelClient.ChannelCallback
import com.google.android.gms.wearable.Wearable
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
import java.time.Duration import java.time.Duration
import java.time.LocalDateTime
import java.util.concurrent.ExecutionException
class MainState(private val activity: Activity, private val requestPermission: () -> Unit) { class MainState(private val activity: Activity, private val requestPermission: () -> Unit) {
@@ -40,7 +27,10 @@ class MainState(private val activity: Activity, private val requestPermission: (
var isPermissionDenied by mutableStateOf(false) var isPermissionDenied by mutableStateOf(false)
private set private set
private val soundRecorder = SoundRecorder(activity, "audiorecord.opus") private val soundRecorder = SoundRecorder(activity, "audiorecord.opus")
private var navController: NavHostController? = null
public fun setNavController(_navController: NavHostController) {
navController = _navController
}
suspend fun onMicClicked() { suspend fun onMicClicked() {
playbackStateMutatorMutex.mutate { playbackStateMutatorMutex.mutate {
when (appState) { when (appState) {
@@ -48,8 +38,7 @@ class MainState(private val activity: Activity, private val requestPermission: (
(ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) -> { (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) -> {
Log.e(TAG, "Permissions granted, continuing to record") Log.e(TAG, "Permissions granted, continuing to record")
appState = AppState.Recording appState = AppState.Recording
navController?.navigate("species_list")
record(activity = activity, soundRecorder = soundRecorder, setProgress = { progress -> recordingProgress = progress }) record(activity = activity, soundRecorder = soundRecorder, setProgress = { progress -> recordingProgress = progress })
} }

View File

@@ -4,7 +4,7 @@ package com.birdsounds.identify.presentation
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import co.touchlab.stately.collections.ConcurrentMutableSet import co.touchlab.stately.collections.ConcurrentMutableSet
import com.google.android.gms.tasks.Tasksx import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.Wearable import com.google.android.gms.wearable.Wearable
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View File

@@ -1,15 +1,17 @@
package com.birdsounds.identify.presentation package com.birdsounds.identify.presentation
import android.util.Log import android.util.Log
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.snapshots.SnapshotStateList
import java.time.Instant import java.time.Instant
class AScore( class AScore(
_species: String, _species: String,
_score: Float, _score: Float,
_timestamp: Long _timestamp: Long,
) { ) {
var split_stuff = _species.split("_") var split_stuff = _species.split("_")
val species = split_stuff[0] val species = split_stuff[0]
@@ -31,32 +33,46 @@ class AScore(
object SpeciesList { object SpeciesList {
var internal_list = mutableListOf<AScore>() var internal_list = mutableListOf<AScore>()
var do_add_observation = false var do_add_observation = false
var _list_on_ui: SnapshotStateList<MutableState<String>>? = null
fun setSpeciecsShow_list(list_in: SnapshotStateList<MutableState<String>>) {
_list_on_ui = list_in;
}
fun add_observation(species_in: AScore) { fun add_observation(species_in: AScore) {
Log.w(TAG,"In add obsergation")
do_add_observation = false do_add_observation = false
var idx = 0 var idx = 0
var idx_replace = -1 var idx_replace = -1
for (i in internal_list) for (i in internal_list) {
{ if (i.species == species_in.species) {
if (i.species == species_in.species)
{
do_add_observation = false do_add_observation = false
idx_replace = idx idx_replace = idx
} }
idx+=1 idx += 1
} }
if (idx_replace >= 0) if (idx_replace >= 0) {
{
Log.w(TAG, "Replacing") Log.w(TAG, "Replacing")
internal_list[idx_replace] = species_in internal_list[idx_replace] = species_in // _list_on_ui?.removeAt(idx_replace)
// _list_on_ui?.add(species_in);
// if (_list_on_ui != null) {
// _list_on_ui?.removeAt(idx_replace)
// _list_on_ui.add(idx_replace, species_in);
// }
} else { } else {
internal_list.add(species_in) internal_list.add(species_in)
} }
// val list_reranked = internal_list.withIndex().sortedBy { it -> it.value.age() }
internal_list = internal_list.sortedBy({ (it.age()) }).toMutableList() internal_list = internal_list.sortedBy({ (it.age()) }).toMutableList()
for ((index, value) in internal_list.withIndex()) {
_list_on_ui?.get(index)?.value = value.common_name;
}
Log.w(TAG, internal_list.toString()) Log.w(TAG, internal_list.size.toString())
// internal_list.add(species_in) Log.w(TAG, _list_on_ui?.size.toString()) // internal_list.add(species_in)
} }
} }

View File

@@ -0,0 +1,48 @@
package com.birdsounds.identify.presentation
import android.content.Context
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Modifier
import androidx.wear.compose.foundation.lazy.items
import androidx.wear.compose.material.Text
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumn
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
import com.google.android.horologist.compose.layout.ScreenScaffold
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SpeciesListView(context: Context,
) {
val text: MutableState<String> = mutableStateOf("text")
//text.toString()
val species_list_show = mutableStateListOf<MutableState<String>>()
for (i in 1..10)
{
val hi = mutableStateOf("hi")
species_list_show.add(hi);
}
SpeciesList.setSpeciecsShow_list(species_list_show)
val species_show: SnapshotStateList<MutableState<String>> = remember { species_list_show }
val columnState =
rememberResponsiveColumnState(contentPadding = ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text,
last = ScalingLazyColumnDefaults.ItemType.Chip))
ScreenScaffold(scrollState = columnState) {
ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
items(species_show) { aSpec -> Text(text = aSpec.value)
} // Dynamically display the chips
}
}
}

View File

@@ -3,49 +3,40 @@ package com.birdsounds.identify.presentation
import android.content.Context import android.content.Context
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices import androidx.navigation.NavHostController
import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales
import com.google.android.horologist.compose.layout.ScreenScaffold import com.google.android.horologist.compose.layout.ScreenScaffold
@Composable @Composable
fun StartRecordingScreen( fun StartRecordingScreen(
context: Context, context: Context,
appState: AppState, appState: AppState,
navController: NavHostController,
isPermissionDenied: Boolean, isPermissionDenied: Boolean,
onMicClicked: () -> Unit onMicClicked: () -> Unit
) { ) {
ScreenScaffold { ScreenScaffold {
val controlDashboardUiState = computeControlDashboardUiState( val controlDashboardUiState = computeControlDashboardUiState(
appState = appState, appState = appState,
isPermissionDenied = isPermissionDenied
) )
ControlDashboard( ControlDashboard(
controlDashboardUiState = controlDashboardUiState, controlDashboardUiState = controlDashboardUiState,
onMicClicked = onMicClicked onMicClicked = onMicClicked,
navController = navController
) )
} }
} }
private fun computeControlDashboardUiState( private fun computeControlDashboardUiState(
appState: AppState, appState: AppState,
isPermissionDenied: Boolean
): ControlDashboardUiState = ): ControlDashboardUiState =
when (appState) { when (appState) {
AppState.Ready -> ControlDashboardUiState( AppState.Ready -> ControlDashboardUiState(
micState = ControlDashboardButtonUiState( micState = ControlDashboardButtonUiState(expanded = false, visible = true, enabled = true),
expanded = false,
enabled = !isPermissionDenied,
visible = true
),
) )
AppState.Recording -> ControlDashboardUiState( AppState.Recording -> ControlDashboardUiState(
micState = ControlDashboardButtonUiState( micState = ControlDashboardButtonUiState(expanded = true, visible = true, enabled = true),
expanded = true,
enabled = true,
visible = true
),
) )
} }