diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 6d0ee1c..d4b7acc 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 74dd639..b2c751a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bf4b227..637db1f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -55,6 +55,6 @@ litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = " [plugins] 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" } diff --git a/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt b/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt index c6f1f2c..73f2d4a 100644 --- a/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt +++ b/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt @@ -359,7 +359,8 @@ class SoundClassifier( return } 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() butterworth.highPass(6, 48000.0, highPass.toDouble()) diff --git a/wear/TileService.kt b/wear/TileService.kt new file mode 100644 index 0000000..c11fe49 --- /dev/null +++ b/wear/TileService.kt @@ -0,0 +1 @@ +class TileService {} \ No newline at end of file diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/ControlDashboard.kt b/wear/src/main/java/com/birdsounds/identify/presentation/ControlDashboard.kt index 925b096..afc61c6 100644 --- a/wear/src/main/java/com/birdsounds/identify/presentation/ControlDashboard.kt +++ b/wear/src/main/java/com/birdsounds/identify/presentation/ControlDashboard.kt @@ -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) diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/MainActivity.kt b/wear/src/main/java/com/birdsounds/identify/presentation/MainActivity.kt index 71f40af..8d50df8 100644 --- a/wear/src/main/java/com/birdsounds/identify/presentation/MainActivity.kt +++ b/wear/src/main/java/com/birdsounds/identify/presentation/MainActivity.kt @@ -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 = {}) +} \ No newline at end of file diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/MainState.kt b/wear/src/main/java/com/birdsounds/identify/presentation/MainState.kt index 135d40d..f9c1253 100644 --- a/wear/src/main/java/com/birdsounds/identify/presentation/MainState.kt +++ b/wear/src/main/java/com/birdsounds/identify/presentation/MainState.kt @@ -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 }) } diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/MessageSender.kt b/wear/src/main/java/com/birdsounds/identify/presentation/MessageSender.kt index 98025b3..d22165d 100644 --- a/wear/src/main/java/com/birdsounds/identify/presentation/MessageSender.kt +++ b/wear/src/main/java/com/birdsounds/identify/presentation/MessageSender.kt @@ -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 diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/SpeciesList.kt b/wear/src/main/java/com/birdsounds/identify/presentation/SpeciesList.kt index b5f6767..8e1c589 100644 --- a/wear/src/main/java/com/birdsounds/identify/presentation/SpeciesList.kt +++ b/wear/src/main/java/com/birdsounds/identify/presentation/SpeciesList.kt @@ -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() var do_add_observation = false + var _list_on_ui: SnapshotStateList>? = null + fun setSpeciecsShow_list(list_in: SnapshotStateList>) { + _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) } } \ No newline at end of file diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/SpeciesListView.kt b/wear/src/main/java/com/birdsounds/identify/presentation/SpeciesListView.kt new file mode 100644 index 0000000..081f75d --- /dev/null +++ b/wear/src/main/java/com/birdsounds/identify/presentation/SpeciesListView.kt @@ -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 = mutableStateOf("text") +//text.toString() + val species_list_show = mutableStateListOf>() + for (i in 1..10) + { + val hi = mutableStateOf("hi") + species_list_show.add(hi); + } + SpeciesList.setSpeciecsShow_list(species_list_show) + val species_show: SnapshotStateList> = 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 + + } + } + +} \ No newline at end of file diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/StartRecordingScreen.kt b/wear/src/main/java/com/birdsounds/identify/presentation/StartRecordingScreen.kt index 17198e4..2d753e6 100644 --- a/wear/src/main/java/com/birdsounds/identify/presentation/StartRecordingScreen.kt +++ b/wear/src/main/java/com/birdsounds/identify/presentation/StartRecordingScreen.kt @@ -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), ) }