This commit is contained in:
2025-03-08 13:02:11 -05:00
parent bef71f8d00
commit 7ff235fb3f
6 changed files with 176 additions and 167 deletions

View File

@@ -7,10 +7,10 @@
</SelectionState> </SelectionState>
<SelectionState runConfigName="wear"> <SelectionState runConfigName="wear">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-03-08T02:48:22.663246800Z"> <DropdownSelection timestamp="2025-03-08T17:32:57.542834800Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="Default" identifier="serial=192.168.1.195:42789;connection=6910cb2b" /> <DeviceId pluginId="Default" identifier="serial=192.168.1.195:35507;connection=7a90992e" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@@ -17,10 +17,10 @@ import androidx.wear.compose.material.Text
@Composable @Composable
fun ControlDashboard(controlDashboardUiState: ControlDashboardUiState, fun ControlDashboard(controlDashboardUiState: ControlDashboardUiState,
onMicClicked: () -> Unit, onMicClicked: () -> Unit,
onNavClicked: () -> Unit,
modifier: Modifier = Modifier) { modifier: Modifier = Modifier) {
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)) {
ControlDashboardButton(buttonState = controlDashboardUiState.micState, ControlDashboardButton(buttonState = controlDashboardUiState.micState,
onClick = onMicClicked, onClick = onMicClicked,
labelText = if (controlDashboardUiState.micState.expanded) { labelText = if (controlDashboardUiState.micState.expanded) {
@@ -29,6 +29,8 @@ fun ControlDashboard(controlDashboardUiState: ControlDashboardUiState,
"Start" "Start"
}) })
} }
} }
} }

View File

@@ -17,6 +17,8 @@ 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.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@@ -28,9 +30,12 @@ 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.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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.navigation.NavHostController
import androidx.wear.compose.material.CompactChip
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text 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
@@ -103,7 +108,7 @@ fun WearApp() {
val navController: NavHostController = rememberSwipeDismissableNavController() val navController: NavHostController = rememberSwipeDismissableNavController()
mainState.setNavController(navController); mainState.setNavController(navController);
SwipeDismissableNavHost(navController = navController, startDestination = "speaker") { SwipeDismissableNavHost(navController = navController, userSwipeEnabled = true, startDestination = "speaker") {
composable("species_list") { composable("species_list") {
ScreenScaffold { ScreenScaffold {
@@ -112,6 +117,16 @@ fun WearApp() {
} }
composable("speaker") { composable("speaker") {
ScreenScaffold {
CompactChip(modifier = Modifier,
onClick = { navController.navigate("species_list") },
enabled = true,
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
shape = MaterialTheme.shapes.small,
label = {
Text(text = "View>")
})
StartRecordingScreen( StartRecordingScreen(
appState = mainState.appState, appState = mainState.appState,
onMicClicked = { onMicClicked = {
@@ -119,28 +134,18 @@ fun WearApp() {
mainState.onMicClicked() mainState.onMicClicked()
} }
}, },
onNavClicked = {
navController.navigate("species_list")
}
) )
} }
}
} }
// var sdnv = SwipeDismissableNavHost(navController = navController, startDestination = "list") {
// composable("speaker") {
// StartRecordingScreen(
// context = context,
// appState = mainState.appState,
// isPermissionDenied = mainState.isPermissionDenied,
// onMicClicked = {
// scope.launch {
// mainState.onMicClicked()
// }
// },
// )
// }
} }
} }
@@ -151,85 +156,3 @@ tailrec fun Context.findActivity(): Activity = when (this) {
else -> throw IllegalStateException("findActivity should be called in the context of an Activity") 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())
}
}
}
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun MessageList(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 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

@@ -2,6 +2,7 @@ package com.birdsounds.identify.presentation
import android.util.Log import android.util.Log
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import java.time.Instant import java.time.Instant
@@ -11,12 +12,13 @@ class AScore(
_species: String, _species: String,
_score: Float, _score: Float,
_timestamp: Long, _timestamp: Long,
_trigger: Boolean = false
) { ) {
val split_stuff: List<String> = _species.split("_"); val split_stuff: List<String> = _species.split("_");
val species = split_stuff[0]; val species = split_stuff[0];
val score = _score; val score = _score;
var trigger = _trigger;
// var common_name = split_stuff[1]; // var common_name = split_stuff[1];
val common_name = if (split_stuff.size > 1) split_stuff[1] else ""; val common_name = if (split_stuff.size > 1) split_stuff[1] else "";
val timestamp = _timestamp val timestamp = _timestamp
@@ -34,56 +36,42 @@ class AScore(
object SpeciesList { object SpeciesList {
var internal_list = mutableListOf<MutableState<AScore>>() var internal_list = mutableListOf<AScore>()
var do_add_observation = false var do_add_observation = false
var _list_on_ui: SnapshotStateList<MutableState<AScore>>? = null var _list_on_ui: SnapshotStateList<MutableState<AScore>>? = null
fun setSpeciecsShow_list(list_in: SnapshotStateList<MutableState<AScore>>) { fun setSpeciecsShow_list(list_in: SnapshotStateList<MutableState<AScore>>) {
_list_on_ui = list_in; _list_on_ui = list_in;
} }
fun add_observation(species_in: AScore) { fun add_observation(species_in: AScore) {
Log.w(TAG, "In add obervation") Log.w(TAG, "In add observation")
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.value.species == species_in.species) { if (i.species == species_in.species) {
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].value = internal_list[idx_replace] = species_in
species_in // _list_on_ui?.removeAt(idx_replace) internal_list[idx_replace].trigger = true;
// _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(mutableStateOf(species_in)) internal_list.add(species_in)
} }
internal_list = internal_list.sortedBy({ (it.value.age()) }).toMutableList() // internal_list = internal_list.sortedBy({ (it.age()) }).toMutableList()
internal_list = internal_list.sortedWith(compareBy({ it.age() }, { it.score})).toMutableList()
while (_list_on_ui!!.size < internal_list.size) { while (_list_on_ui!!.size < internal_list.size) {
_list_on_ui!!.add(mutableStateOf(AScore("", 0F, 0L))) _list_on_ui!!.add(mutableStateOf(AScore("",0.0F,0L)))
Log.w(TAG, "Adding stuff to UI list");
} }
Log.w(TAG, "Internal list " + internal_list.toString())
for ((index, value) in internal_list.withIndex()) { for ((index, value) in internal_list.withIndex()) {
Log.w(TAG, "Adding ${index}:${value} to list ${_list_on_ui!!.size}") _list_on_ui?.get(index)?.value = value;
if (_list_on_ui!!.size > index)
{
Log.w(TAG,"Updating ${value}")
_list_on_ui!![index].value = value.value
}
} }
Log.w(TAG, internal_list.size.toString()) Log.w(TAG, internal_list.size.toString())

View File

@@ -1,59 +1,154 @@
package com.birdsounds.identify.presentation package com.birdsounds.identify.presentation
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.util.Log
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.lerp
import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults.scalingParams
import androidx.wear.compose.foundation.lazy.ScalingParams
import androidx.wear.compose.foundation.lazy.items
import androidx.wear.compose.material.Text import androidx.wear.compose.material.Text
import com.google.android.horologist.annotations.ExperimentalHorologistApi 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.ScalingLazyColumnState
import com.google.android.horologist.compose.layout.ScreenScaffold
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
import kotlinx.coroutines.delay
fun interpolateColor(value: Float): Color {
// Ensure that the input value is clamped between 0 and 1
val clampedValue = value.coerceIn(0f, 1f)
// Red component: starts at 255 (white) and decreases to 0
val red = (255 * (1 - clampedValue)).toInt()
// Green component: stays at 255 (since both white and green have full green)
val green = 255
// Blue component: starts at 255 (white) and decreases to 0
val blue = (255 * (1 - clampedValue)).toInt()
// Construct the color
return Color(red.toInt(), green.toInt(), blue.toInt())
}
@Composable
fun FlashingText(
text: String,
color: Color,
modifier: Modifier = Modifier
) {
// Set up the flashing state
var isFlashing by remember { mutableStateOf(false) }
// Create animation for the background color
val backgroundColor by animateColorAsState(
targetValue = if (isFlashing) Color.Cyan else Color.Transparent,
animationSpec = tween(durationMillis = 500)
)
// Trigger the flash effect once
LaunchedEffect(key1 = true) {
isFlashing = true
delay(300)
isFlashing = false
}
// Display the text with animated background
Text(
text = text,
color = color,
modifier = modifier
.background(color = backgroundColor)
)
}
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalHorologistApi::class) @OptIn(ExperimentalHorologistApi::class)
@Composable @Composable
fun SpeciesListView( fun SpeciesListView(context: Context,
context: Context,
) { ) {
val species_list_show = mutableStateListOf<MutableState<AScore>>() val species_list_show = mutableStateListOf<MutableState<AScore>>()
for (i in 1..10) { for (i in 1..3)
species_list_show.add(mutableStateOf(AScore(i.toString()+"_"+i.toString(), 0.00F, 0L))); {
val hi = mutableStateOf(AScore("",0.0F,0L))
species_list_show.add(hi);
} }
SpeciesList.setSpeciecsShow_list(species_list_show) SpeciesList.setSpeciecsShow_list(species_list_show)
val species_show: SnapshotStateList<MutableState<AScore>> = remember { species_list_show } val species_show: SnapshotStateList<MutableState<AScore>> = remember { species_list_show }
Column( // var sP = scalingParams( maxTransitionArea= 0.25f, minTransitionArea = 0.05f)
horizontalAlignment = Alignment.CenterHorizontally, // val columnState = ScalingLazyColumnState(scalingParams = sP);
modifier = Modifier var columnState = rememberResponsiveColumnState(contentPadding = ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text,
.verticalScroll(rememberScrollState()) last = ScalingLazyColumnDefaults.ItemType.Chip), verticalArrangement= Arrangement.spacedBy(
.fillMaxWidth() space = 1.dp,
alignment = Alignment.Top,
),)
ScreenScaffold(scrollState = columnState) {
ScalingLazyColumn(
columnState = columnState,
modifier = Modifier.fillMaxWidth()
) { ) {
species_show.forEach { aSpec -> Text(text = aSpec.value.common_name) } items(species_show) { aSpec ->
if (aSpec.value.trigger)
{
Log.w(TAG,"Trigger "+aSpec.toString())
FlashingText(text = aSpec.value.common_name, color = interpolateColor(aSpec.value.score))
aSpec.value.trigger = false;
} else
{
Text(text = aSpec.value.common_name, color = interpolateColor(aSpec.value.score))
} }
}
} // Dynamically display the chips
} }
}
// val columnState =
// rememberResponsiveColumnState(contentPadding = ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text,
// last = ScalingLazyColumnDefaults.ItemType.Chip))
// ScreenScaffold(scrollState = columnState) { // ScreenScaffold(scrollState = columnState) {
// ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) { // ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
// items(species_show) { aSpec -> Text(text = aSpec.value) // items(species_show) { aSpec -> Text(text = aSpec.value.common_name)
// } // Dynamically display the chips // } // Dynamically display the chips
// //
// } // }
// } // }

View File

@@ -10,7 +10,8 @@ import com.google.android.horologist.compose.layout.ScreenScaffold
@Composable @Composable
fun StartRecordingScreen( fun StartRecordingScreen(
appState: AppState, appState: AppState,
onMicClicked: () -> Unit onMicClicked: () -> Unit,
onNavClicked: () -> Unit
) { ) {
ScreenScaffold { ScreenScaffold {
val controlDashboardUiState = computeControlDashboardUiState( val controlDashboardUiState = computeControlDashboardUiState(
@@ -18,8 +19,8 @@ fun StartRecordingScreen(
) )
ControlDashboard( ControlDashboard(
controlDashboardUiState = controlDashboardUiState, controlDashboardUiState = controlDashboardUiState,
onMicClicked = onMicClicked onMicClicked = onMicClicked,
onNavClicked = onNavClicked
) )
} }
} }