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

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.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.wear.compose.material.CompactChip
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
@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()) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
@@ -35,22 +39,17 @@ private fun ControlDashboardButton(buttonState: ControlDashboardButtonUiState,
onClick: () -> Unit,
labelText: String,
modifier: Modifier = Modifier) {
CompactChip(
modifier = Modifier,
onClick = onClick ,
CompactChip(modifier = Modifier,
onClick = onClick,
enabled = true,
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
shape = MaterialTheme.shapes.small,
label = {
Text(
text = labelText
)
}
)
Text(text = labelText)
})
}
// Button(modifier = modifier, enabled = buttonState.enabled && buttonState.visible, onClick = onClick) {
// Text(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.setContent
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.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
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.composable
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.google.android.horologist.annotations.ExperimentalHorologistApi
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.launch
@@ -40,11 +60,9 @@ val Any.TAG: String
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
@@ -69,61 +87,148 @@ fun WearApp() {
val volumeViewModel: VolumeViewModel = viewModel(factory = VolumeViewModel.Factory)
val navController = rememberSwipeDismissableNavController()
val mainState = remember(activity) {
MainState(
activity = activity,
requestPermission = {
requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
}
)
MainState(activity = activity, requestPermission = {
requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
})
}
requestPermissionLauncher = rememberLauncherForActivityResult(RequestPermission()) {
scope.launch {
mainState.permissionResultReturned()
}
}
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
val navController: NavHostController = rememberSwipeDismissableNavController()
mainState.setNavController(navController);
SwipeDismissableNavHost(navController = navController, startDestination = "speaker") {
// AppScaffold {
// DisposableEffect(mainState, scope, lifecycleOwner) {
// val lifecycleObserver = object : DefaultLifecycleObserver {
// override fun onStop(owner: LifecycleOwner) {
// super.onStop(owner)
// }
// }
// lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
//
// onDispose {
// lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
// }
// }
var sdnv = SwipeDismissableNavHost(navController = navController, startDestination = "speaker") {
composable("speaker") {
StartRecordingScreen(
context = context,
appState = mainState.appState,
isPermissionDenied = mainState.isPermissionDenied,
onMicClicked = {
scope.launch {
mainState.onMicClicked()
}
},
)
}
// }
composable("speaker") {
StartRecordingScreen(
context = context,
navController = navController,
appState = mainState.appState,
isPermissionDenied = mainState.isPermissionDenied,
onMicClicked = {
scope.launch {
mainState.onMicClicked()
}
},
)
}
composable("species_list") {
SpeciesListView(context = context)
}
}
// var sdnv = SwipeDismissableNavHost(navController = navController, startDestination = "list") {
// 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 =
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 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

@@ -1,9 +1,7 @@
package com.birdsounds.identify.presentation
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.util.Log
import androidx.annotation.RequiresPermission
@@ -11,24 +9,13 @@ import androidx.compose.foundation.MutatorMutex
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import co.touchlab.stately.collections.ConcurrentMutableCollection
import com.google.android.gms.tasks.Tasks
import androidx.navigation.NavHostController
import com.google.android.gms.wearable.ChannelClient
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.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
import java.time.Duration
import java.time.LocalDateTime
import java.util.concurrent.ExecutionException
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)
private set
private val soundRecorder = SoundRecorder(activity, "audiorecord.opus")
private var navController: NavHostController? = null
public fun setNavController(_navController: NavHostController) {
navController = _navController
}
suspend fun onMicClicked() {
playbackStateMutatorMutex.mutate {
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) -> {
Log.e(TAG, "Permissions granted, continuing to record")
appState = AppState.Recording
navController?.navigate("species_list")
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.util.Log
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers

View File

@@ -1,15 +1,17 @@
package com.birdsounds.identify.presentation
import android.util.Log
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.snapshots.SnapshotStateList
import java.time.Instant
class AScore(
_species: String,
_score: Float,
_timestamp: Long
_timestamp: Long,
) {
) {
var split_stuff = _species.split("_")
val species = split_stuff[0]
@@ -31,32 +33,46 @@ class AScore(
object SpeciesList {
var internal_list = mutableListOf<AScore>()
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) {
Log.w(TAG,"In add obsergation")
do_add_observation = false
var idx = 0
var idx_replace = -1
for (i in internal_list)
{
if (i.species == species_in.species)
{
for (i in internal_list) {
if (i.species == species_in.species) {
do_add_observation = false
idx_replace = idx
}
idx+=1
idx += 1
}
if (idx_replace >= 0)
{
if (idx_replace >= 0) {
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 {
internal_list.add(species_in)
}
// val list_reranked = internal_list.withIndex().sortedBy { it -> it.value.age() }
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())
// internal_list.add(species_in)
Log.w(TAG, internal_list.size.toString())
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 androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices
import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales
import androidx.navigation.NavHostController
import com.google.android.horologist.compose.layout.ScreenScaffold
@Composable
fun StartRecordingScreen(
context: Context,
appState: AppState,
navController: NavHostController,
isPermissionDenied: Boolean,
onMicClicked: () -> Unit
) {
ScreenScaffold {
val controlDashboardUiState = computeControlDashboardUiState(
appState = appState,
isPermissionDenied = isPermissionDenied
)
ControlDashboard(
controlDashboardUiState = controlDashboardUiState,
onMicClicked = onMicClicked
onMicClicked = onMicClicked,
navController = navController
)
}
}
private fun computeControlDashboardUiState(
appState: AppState,
isPermissionDenied: Boolean
): ControlDashboardUiState =
when (appState) {
AppState.Ready -> ControlDashboardUiState(
micState = ControlDashboardButtonUiState(
expanded = false,
enabled = !isPermissionDenied,
visible = true
),
micState = ControlDashboardButtonUiState(expanded = false, visible = true, enabled = true),
)
AppState.Recording -> ControlDashboardUiState(
micState = ControlDashboardButtonUiState(
expanded = true,
enabled = true,
visible = true
),
micState = ControlDashboardButtonUiState(expanded = true, visible = true, enabled = true),
)
}