diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..7643783
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..6e6eec1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index d62d75e..2a31d53 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,14 @@
+
+
+
+
+
+
+
+
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 e16b375..6452160 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.5.2"
+agp = "8.6.0"
kotlin = "2.0.0"
coreKtx = "1.13.1"
junit = "4.13.2"
@@ -18,6 +18,8 @@ activityCompose = "1.9.1"
coreSplashscreen = "1.0.1"
composeNavigation = "1.4.0-rc01"
media3Common = "1.4.0"
+composeMaterial3 = "1.0.0-alpha23"
+workRuntimeKtx = "2.9.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -43,6 +45,8 @@ androidx-activity-compose = { group = "androidx.activity", name = "activity-comp
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" }
androidx-compose-navigation = { group = "androidx.wear.compose", name = "compose-navigation", version.ref = "composeNavigation" }
androidx-media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3Common" }
+androidx-compose-material3 = { group = "androidx.wear.compose", name = "compose-material3", version.ref = "composeMaterial3" }
+androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts
index 5ec38e9..29dcf5f 100644
--- a/wear/build.gradle.kts
+++ b/wear/build.gradle.kts
@@ -53,7 +53,7 @@ dependencies {
implementation("com.google.android.horologist:horologist-compose-material:0.6.8")
implementation("com.google.android.horologist:horologist-media-ui:0.6.8")
implementation("com.google.android.horologist:horologist-media-data:0.6.8")
-
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.8.1")
implementation("androidx.media3:media3-exoplayer:1.4.0")
implementation(libs.play.services.wearable)
implementation(platform(libs.androidx.compose.bom))
@@ -67,7 +67,8 @@ dependencies {
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.compose.navigation)
implementation(libs.androidx.media3.common)
-// androidTestImplementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.compose.material3)
+ implementation(libs.androidx.work.runtime.ktx) // androidTestImplementation(platform(libs.androidx.compose.bom))
// androidTestImplementation(libs.androidx.ui.test.junit4)
// debugImplementation(libs.androidx.ui.tooling)
// debugImplementation(libs.androidx.ui.test.manifest)
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 89adecc..925b096 100644
--- a/wear/src/main/java/com/birdsounds/identify/presentation/ControlDashboard.kt
+++ b/wear/src/main/java/com/birdsounds/identify/presentation/ControlDashboard.kt
@@ -1,110 +1,68 @@
package com.birdsounds.identify.presentation
-
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Mic
-import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
-import com.birdsounds.identify.R
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.Button
-import androidx.wear.compose.material.CircularProgressIndicator
-import androidx.wear.compose.material.Icon
-import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import androidx.wear.compose.material.CompactChip
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Text
-/**
- * The component responsible for drawing the main 3 controls, with their expanded and minimized
- * states.
- *
- * The state for this class is driven by a [ControlDashboardUiState], which contains a
- * [ControlDashboardButtonUiState] for each of the three buttons.
- */
@Composable
-fun ControlDashboard(
- controlDashboardUiState: ControlDashboardUiState,
- onMicClicked: () -> Unit,
- recordingProgress: Float,
- modifier: Modifier = Modifier
-) {
- Box(
- contentAlignment = Alignment.Center,
- modifier = modifier.fillMaxSize()
- ) {
+fun ControlDashboard(controlDashboardUiState: ControlDashboardUiState, onMicClicked: () -> Unit, modifier: Modifier = Modifier) {
+ 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,
- imageVector = Icons.Filled.Mic,
- contentDescription = if (controlDashboardUiState.micState.expanded) {
- stringResource(id = R.string.stop_recording)
+ labelText = if (controlDashboardUiState.micState.expanded) {
+ "Stop"
} else {
- stringResource(id = R.string.record)
- }
- )
-
-
+ "Start"
+ })
}
}
}
-/**
- * A single control dashboard button
- */
+
@Composable
-private fun ControlDashboardButton(
- buttonState: ControlDashboardButtonUiState,
- onClick: () -> Unit,
- imageVector: ImageVector,
- contentDescription: String,
- modifier: Modifier = Modifier
-) {
- Button(
- modifier = modifier,
- enabled = buttonState.enabled && buttonState.visible,
- onClick = onClick
- ) {
- Icon(
- imageVector = imageVector,
- contentDescription = contentDescription
- )
- }
+private fun ControlDashboardButton(buttonState: ControlDashboardButtonUiState,
+ onClick: () -> Unit,
+ labelText: String,
+ modifier: Modifier = Modifier) {
+ 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
+ )
+ }
+ )
}
-/**
- * The state for a single [ControlDashboardButton].
- */
-data class ControlDashboardButtonUiState(
- val expanded: Boolean,
- val enabled: Boolean,
- val visible: Boolean
-)
-/**
- * The state for a [ControlDashboard].
- */
-data class ControlDashboardUiState(
- val micState: ControlDashboardButtonUiState,
- val playState: ControlDashboardButtonUiState
-) {
- init {
- // Check that at most one of the buttons is expanded
- require(
- listOf(
- micState.expanded,
- playState.expanded
- ).count { it } <= 1
- )
+
+// Button(modifier = modifier, enabled = buttonState.enabled && buttonState.visible, onClick = onClick) {
+// Text(contentDescription);
+//// Icon(imageVector = imageVector, contentDescription = contentDescription)
+// }
+//}
+
+
+data class ControlDashboardButtonUiState(val expanded: Boolean, val enabled: Boolean, val visible: Boolean)
+
+
+data class ControlDashboardUiState(val micState: ControlDashboardButtonUiState) {
+ init { // Check that at most one of the buttons is expanded
+
}
}
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 15de297..79caf7c 100644
--- a/wear/src/main/java/com/birdsounds/identify/presentation/MainActivity.kt
+++ b/wear/src/main/java/com/birdsounds/identify/presentation/MainActivity.kt
@@ -13,74 +13,69 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
-import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.lifecycle.viewmodel.compose.viewModel
-import com.google.android.horologist.audio.ui.VolumeViewModel
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Alignment
-import com.google.android.horologist.compose.layout.AppScaffold
-import com.google.android.horologist.compose.layout.ScreenScaffold
-import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
-import androidx.wear.compose.material.MaterialTheme
-import androidx.wear.compose.material.Text
-import androidx.wear.compose.material.TimeText
-import androidx.wear.compose.material.dialog.Confirmation
+import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.wear.compose.navigation.SwipeDismissableNavHost
import androidx.wear.compose.navigation.composable
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
-import androidx.wear.tooling.preview.devices.WearDevices
-import com.birdsounds.identify.R
import com.birdsounds.identify.presentation.theme.IdentifyTheme
import com.google.android.horologist.annotations.ExperimentalHorologistApi
-import com.google.android.horologist.audio.ui.VolumeScreen
-import com.google.android.horologist.compose.material.AlertContent
+import com.google.android.horologist.audio.ui.VolumeViewModel
+import com.google.android.horologist.compose.layout.AppScaffold
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+val flow_stream = MutableStateFlow("")
+
+val Any.TAG: String
+ get() {
+ val tag = javaClass.simpleName
+ return if (tag.length <= 23) tag else tag.substring(0, 23)
+ }
+
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
-
super.onCreate(savedInstanceState)
-
setTheme(android.R.style.Theme_DeviceDefault)
-
setContent {
- WearApp("Android")
+ WearApp()
}
}
}
@OptIn(ExperimentalHorologistApi::class)
@Composable
-fun WearApp(greetingName: String) {
+fun WearApp() {
+
IdentifyTheme {
lateinit var requestPermissionLauncher: ManagedActivityResultLauncher
+// val job = launch {
+// flow_stream.collect {
+// print("$it ")
+// }
+// }
+
val context = LocalContext.current
val activity = context.findActivity()
val scope = rememberCoroutineScope()
val volumeViewModel: VolumeViewModel = viewModel(factory = VolumeViewModel.Factory)
-
val navController = rememberSwipeDismissableNavController()
val mainState = remember(activity) {
@@ -93,25 +88,20 @@ fun WearApp(greetingName: String) {
}
requestPermissionLauncher = rememberLauncherForActivityResult(RequestPermission()) {
- // We ignore the direct result here, since we're going to check anyway.
scope.launch {
mainState.permissionResultReturned()
}
-
}
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
AppScaffold {
- // Notify the state holder whenever we become stopped to reset the state
DisposableEffect(mainState, scope, lifecycleOwner) {
val lifecycleObserver = object : DefaultLifecycleObserver {
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
- scope.launch { mainState.onStopped() }
}
}
-
lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
onDispose {
@@ -120,65 +110,21 @@ fun WearApp(greetingName: String) {
}
SwipeDismissableNavHost(navController = navController, startDestination = "speaker") {
composable("speaker") {
- SpeakerRecordingScreen(
- playbackState = mainState.playbackState,
+ StartRecordingScreen(
+ appState = mainState.appState,
isPermissionDenied = mainState.isPermissionDenied,
- recordingProgress = mainState.recordingProgress,
onMicClicked = {
scope.launch {
mainState.onMicClicked()
}
- }
+ },
)
-
- if (mainState.showPermissionRationale) {
- AlertContent(
- onOk = {
- requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
- mainState.showPermissionRationale = false
- },
- onCancel = {
- mainState.showPermissionRationale = false
- },
- title = stringResource(
- id = R.string.rationale_for_microphone_permission
- )
- )
- }
-
- if (mainState.showSpeakerNotSupported) {
- Confirmation(
- onTimeout = { mainState.showSpeakerNotSupported = false }
- ) {
- Text(text = stringResource(id = R.string.no_speaker_supported))
- }
- }
}
-
-
}
}
-
-
}
}
-@Composable
-fun Greeting(greetingName: String) {
- Text(
- modifier = Modifier.fillMaxWidth(),
- textAlign = TextAlign.Center,
- color = MaterialTheme.colors.primary,
- text = stringResource(R.string.hello_world, greetingName)
- )
-}
-
-@Preview(device = WearDevices.SMALL_ROUND, showSystemUi = true)
-@Composable
-fun DefaultPreview() {
- WearApp("Preview Android")
-}
-
tailrec fun Context.findActivity(): Activity =
when (this) {
is Activity -> this
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 da8c62d..957b80a 100644
--- a/wear/src/main/java/com/birdsounds/identify/presentation/MainState.kt
+++ b/wear/src/main/java/com/birdsounds/identify/presentation/MainState.kt
@@ -3,185 +3,105 @@ package com.birdsounds.identify.presentation
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
-import android.media.AudioDeviceInfo
-import android.media.AudioManager
+import android.util.Log
import androidx.annotation.RequiresPermission
import androidx.compose.foundation.MutatorMutex
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.content.ContextCompat
-import androidx.core.content.getSystemService
-import java.time.Duration
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import java.time.Duration
+import java.time.LocalDateTime
-class MainState(
- private val activity: Activity,
- private val requestPermission: () -> Unit
-) {
- /**
- * The [MutatorMutex] that guards the playback state of the app.
- *
- * Due to being a [MutatorMutex], this automatically handles cleanup of any ongoing asynchronous
- * work, like playing music or recording, ensuring that only one operation is occurring at a
- * time.
- *
- * For example, if the user is currently recording, and they hit the mic button again, the
- * second [onMicClicked] will cancel the previous [onMicClicked] that was doing the recording,
- * waiting for everything to be cleaned up, before running its own code.
- */
+class MainState(private val activity: Activity, private val requestPermission: () -> Unit) {
private val playbackStateMutatorMutex = MutatorMutex()
-
- /**
- * The primary playback state.
- */
- var playbackState by mutableStateOf(PlaybackState.Ready)
+ var appState by mutableStateOf(AppState.Ready)
private set
-
- /**
- * The progress of an ongoing recording.
- *
- * Note that this value can be read even when recording is not occurring, in which case it
- * corresponds to the last known value of recording progress (or 0), where that value is useful
- * for animations.
- */
var recordingProgress by mutableStateOf(0f)
private set
-
- /**
- * `true` if we know the user has denied the record audio permission.
- */
var isPermissionDenied by mutableStateOf(false)
private set
-
- /**
- * `true` if we the permission rationale should be shown.
- */
- var showPermissionRationale by mutableStateOf(false)
-
- /**
- * `true` if we the speaker not supported rationale should be shown.
- */
- var showSpeakerNotSupported by mutableStateOf(false)
-
- /**
- * The [SoundRecorder] for recording and playing audio captured on-device.
- */
private val soundRecorder = SoundRecorder(activity, "audiorecord.opus")
- suspend fun onStopped() {
- playbackStateMutatorMutex.mutate {
- playbackState = PlaybackState.Ready
- }
- }
-
suspend fun onMicClicked() {
playbackStateMutatorMutex.mutate {
- when (playbackState) {
- is PlaybackState.Ready,
- PlaybackState.PlayingVoice ->
- // If we weren't recording, check our permission to start recording.
- when {
- ContextCompat.checkSelfPermission(
- activity,
- Manifest.permission.RECORD_AUDIO
- ) == PackageManager.PERMISSION_GRANTED -> {
- // We have the permission, we can start recording now
- playbackState = PlaybackState.Recording
- record(
- soundRecorder = soundRecorder,
- setProgress = { progress ->
- recordingProgress = progress
- }
- )
- playbackState = PlaybackState.Ready
- }
- activity.shouldShowRequestPermissionRationale(
- Manifest.permission.RECORD_AUDIO
- ) -> {
- // If we should show the rationale prior to requesting the permission,
- // send that event
- showPermissionRationale = true
- playbackState = PlaybackState.Ready
- }
- else -> {
- // Request the permission
- requestPermission()
- playbackState = PlaybackState.Ready
- }
+ when (appState) {
+ is AppState.Ready -> when {
+ (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) -> {
+ Log.e(TAG, "Permissions granted, continuing to record");
+ appState = AppState.Recording
+ record(soundRecorder = soundRecorder, setProgress = { progress -> recordingProgress = progress })
+ }
+
+ else -> {
+ Log.e(TAG, "Requesting permissions");
+ requestPermission()
}
- // If we were already recording, transition back to ready
- PlaybackState.Recording -> {
- playbackState = PlaybackState.Ready
}
+
+ AppState.Recording -> {
+ appState = AppState.Ready
+ }
+
}
}
}
suspend fun permissionResultReturned() {
playbackStateMutatorMutex.mutate {
- // Check if the user granted the permission
- if (
- ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) ==
- PackageManager.PERMISSION_GRANTED
- ) {
- // The user granted the permission, continue on to start recording
- playbackState = PlaybackState.Recording
- record(
- soundRecorder = soundRecorder,
- setProgress = { progress ->
- recordingProgress = progress
- }
- )
- playbackState = PlaybackState.Ready
+ if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
+ appState = AppState.Recording
+ record(soundRecorder = soundRecorder, setProgress = { progress ->
+ recordingProgress = progress
+ })
+ appState = AppState.Ready
} else {
- // We have confirmation now that the user denied the permission
isPermissionDenied = true
- playbackState = PlaybackState.Ready
+ appState = AppState.Ready
}
}
}
}
-/**
- * The three playback states of the application.
- */
-sealed class PlaybackState {
- object Ready : PlaybackState()
- object PlayingVoice : PlaybackState()
- object Recording : PlaybackState()
+sealed class AppState {
+ object Ready : AppState()
+ object Recording : AppState()
}
-/**
- * A helper function to record, updating the progress state while recording.
- *
- * This requires the [Manifest.permission.RECORD_AUDIO] permission to run.
- */
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
-private suspend fun record(
- soundRecorder: SoundRecorder,
- setProgress: (progress: Float) -> Unit,
- maxRecordingDuration: Duration = Duration.ofSeconds(10),
- numberTicks: Int = 10
-) {
- coroutineScope {
- // Kick off a parallel job to record
- val recordingJob = launch { soundRecorder.record() }
+private suspend fun record(soundRecorder: SoundRecorder,
+ setProgress: (progress: Float) -> Unit,
+ maxRecordingDuration: Duration = Duration.ofSeconds(10),
+ numberTicks: Int = 10) {
- val delayPerTickMs = maxRecordingDuration.toMillis() / numberTicks
- val startTime = System.currentTimeMillis()
+ coroutineScope { // Kick off a parallel job to
- repeat(numberTicks) { index ->
- setProgress(index.toFloat() / numberTicks)
- delay(startTime + delayPerTickMs * (index + 1) - System.currentTimeMillis())
+ Log.e(TAG, "Mock recording"); // val recordingJob = launch { soundRecorder.record() }
+ val ByteFlow: Flow = flow{
+ for (i in 1..3) {
+ var string_send = LocalDateTime.now().toString()
+ emit(string_send);
+ delay(250);
+ Log.e(TAG, "Emitting " + string_send)
+ }
}
- // Update the progress to be complete
- setProgress(1f)
- // Stop recording
- recordingJob.cancel()
+ //
+ // val delayPerTickMs = maxRecordingDuration.toMillis() / numberTicks
+ // val startTime = System.currentTimeMillis()
+ //
+ // repeat(numberTicks) { index ->
+ // setProgress(index.toFloat() / numberTicks)
+ // delay(startTime + delayPerTickMs * (index + 1) - System.currentTimeMillis())
+ // } // Update the progress to be complete
+ // setProgress(1f)
+ //
+ // // Stop recording
+ // recordingJob.cancel()
}
}
diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/MessageSendRecv.kt b/wear/src/main/java/com/birdsounds/identify/presentation/MessageSendRecv.kt
new file mode 100644
index 0000000..24ac43a
--- /dev/null
+++ b/wear/src/main/java/com/birdsounds/identify/presentation/MessageSendRecv.kt
@@ -0,0 +1,11 @@
+package com.birdsounds.identify.presentation
+
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import com.google.android.gms.wearable.Wearable
+
+class MessageSendRecv(private val context: Context)
+{
+//MainState.
+}
\ No newline at end of file
diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/SoundRecorder.kt b/wear/src/main/java/com/birdsounds/identify/presentation/SoundRecorder.kt
index f6c824d..5afdcd8 100644
--- a/wear/src/main/java/com/birdsounds/identify/presentation/SoundRecorder.kt
+++ b/wear/src/main/java/com/birdsounds/identify/presentation/SoundRecorder.kt
@@ -3,11 +3,17 @@ package com.birdsounds.identify.presentation
import android.Manifest
import android.content.Context
+import android.media.AudioRecord
import android.media.MediaRecorder
import android.util.Log
import androidx.annotation.RequiresPermission
+import com.google.android.gms.wearable.Wearable
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
import java.io.File
import kotlinx.coroutines.suspendCancellableCoroutine
+import java.time.LocalDateTime
/**
* A helper class to provide methods to record audio input from the MIC to the internal storage.
@@ -17,27 +23,25 @@ class SoundRecorder(
outputFileName: String
) {
private val audioFile = File(context.filesDir, outputFileName)
-
private var state = State.IDLE
private enum class State {
IDLE, RECORDING
}
- /**
- * Records from the microphone.
- *
- * This method is cancellable, and cancelling it will stop recording.
- */
+
+
+
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
suspend fun record() {
- if (state != State.IDLE) {
- Log.w(TAG, "Requesting to start recording while state was not IDLE")
- return
- }
+
+
+
+
suspendCancellableCoroutine { cont ->
@Suppress("DEPRECATION")
+
val mediaRecorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(MediaRecorder.OutputFormat.OGG)
@@ -58,7 +62,7 @@ class SoundRecorder(
mediaRecorder.prepare()
mediaRecorder.start()
-
+ Log.e("com.birdsounds.identify","Hey I'm recording")
state = State.RECORDING
}
}
diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/SpeakerPlayerScreen.kt b/wear/src/main/java/com/birdsounds/identify/presentation/SpeakerPlayerScreen.kt
deleted file mode 100644
index 7905d13..0000000
--- a/wear/src/main/java/com/birdsounds/identify/presentation/SpeakerPlayerScreen.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.birdsounds.identify.presentation
-
-import androidx.activity.compose.ReportDrawnAfter
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.media3.common.util.UnstableApi
-import com.google.android.horologist.annotations.ExperimentalHorologistApi
-import com.google.android.horologist.audio.ui.VolumeViewModel
-import com.google.android.horologist.audio.ui.components.actions.SetVolumeButton
-import com.google.android.horologist.media.ui.components.PodcastControlButtons
-import com.google.android.horologist.media.ui.screens.player.DefaultMediaInfoDisplay
-import com.google.android.horologist.media.ui.screens.player.PlayerScreen
-import com.google.android.horologist.media.ui.state.PlayerUiController
-import com.google.android.horologist.media.ui.state.PlayerUiState
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.first
-
-@androidx.annotation.OptIn(UnstableApi::class)
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SpeakerPlayerScreen(
- onVolumeClick: () -> Unit,
- volumeViewModel: VolumeViewModel = viewModel(factory = VolumeViewModel.Factory),
- playerViewModel: SpeakerPlayerViewModel = viewModel(factory = SpeakerPlayerViewModel.Factory),
- modifier: Modifier = Modifier
-) {
- val volumeUiState by volumeViewModel.volumeUiState.collectAsStateWithLifecycle()
-
- PlayerScreen(
- modifier = modifier,
- background = {},
- playerViewModel = playerViewModel,
- volumeViewModel = volumeViewModel,
- mediaDisplay = { playerUiState ->
- DefaultMediaInfoDisplay(playerUiState)
- },
- buttons = { state ->
- SetVolumeButton(
- volumeUiState = volumeUiState,
- onVolumeClick = onVolumeClick,
- enabled = state.connected && state.media != null
- )
- },
- controlButtons = { playerUiController, playerUiState ->
- PlayerScreenPodcastControlButtons(playerUiController, playerUiState)
- }
- )
-
- ReportDrawnAfter {
- playerViewModel.playerState.filterNotNull().first()
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun PlayerScreenPodcastControlButtons(
- playerUiController: PlayerUiController,
- playerUiState: PlayerUiState,
- modifier: Modifier = Modifier
-) {
- PodcastControlButtons(
- modifier = modifier,
- playerController = playerUiController,
- playerUiState = playerUiState
- )
-}
diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/SpeakerPlayerViewModel.kt b/wear/src/main/java/com/birdsounds/identify/presentation/SpeakerPlayerViewModel.kt
deleted file mode 100644
index ee5e19c..0000000
--- a/wear/src/main/java/com/birdsounds/identify/presentation/SpeakerPlayerViewModel.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.birdsounds.identify.presentation
-
-
-import androidx.compose.runtime.getValue
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
-import androidx.lifecycle.viewModelScope
-import androidx.lifecycle.viewmodel.initializer
-import androidx.lifecycle.viewmodel.viewModelFactory
-import androidx.media3.common.Player
-import androidx.media3.common.util.UnstableApi
-import androidx.media3.exoplayer.ExoPlayer
-import com.google.android.horologist.annotations.ExperimentalHorologistApi
-import com.google.android.horologist.media.data.repository.PlayerRepositoryImpl
-import com.google.android.horologist.media.model.Media
-import com.google.android.horologist.media.ui.state.PlayerViewModel
-import java.io.File
-import kotlinx.coroutines.launch
-
-@UnstableApi
-@OptIn(ExperimentalHorologistApi::class)
-class SpeakerPlayerViewModel(
- playerRepository: PlayerRepositoryImpl,
- player: Player,
- audioFile: File,
- audioFileUri: String
-) : PlayerViewModel(playerRepository) {
-
- init {
- viewModelScope.launch {
- playerRepository.connect(player) {}
- if (audioFile.exists()) {
- val media = Media(
- id = "",
- uri = audioFileUri,
- title = "Recorded audio",
- artist = ""
- )
- playerRepository.setMedia(media)
- }
- }
- }
-
- val playerState = playerRepository.player
-
- @ExperimentalHorologistApi
- public companion object {
- private const val TAG = "SpeakerPlayerViewModel"
-
- public val Factory: ViewModelProvider.Factory = viewModelFactory {
- initializer {
- val application = this[APPLICATION_KEY]!!
-
- val outputFileName = "audiorecord.opus"
- val audioFile = File(application.filesDir, outputFileName)
- val audioFileUri = application.filesDir.path + "/" + outputFileName
-
- val player = ExoPlayer.Builder(application)
- .setSeekForwardIncrementMs(5000L)
- .setSeekBackIncrementMs(5000L)
- .build()
-
- SpeakerPlayerViewModel(
- PlayerRepositoryImpl(),
- player,
- audioFile,
- audioFileUri
- )
- }
- }
- }
-}
diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/SpeakerRecordingScreen.kt b/wear/src/main/java/com/birdsounds/identify/presentation/SpeakerRecordingScreen.kt
deleted file mode 100644
index 702fd4e..0000000
--- a/wear/src/main/java/com/birdsounds/identify/presentation/SpeakerRecordingScreen.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.birdsounds.identify.presentation
-
-
-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 com.google.android.horologist.compose.layout.ScreenScaffold
-
-/**
- * The composable responsible for displaying the main UI.
- *
- * This composable is stateless, and simply displays the state given to it.
- */
-@Composable
-fun SpeakerRecordingScreen(
- playbackState: PlaybackState,
- isPermissionDenied: Boolean,
- recordingProgress: Float,
- onMicClicked: () -> Unit
-) {
- ScreenScaffold {
- // Determine the control dashboard state.
- // This converts the main app state into a control dashboard state for rendering
- val controlDashboardUiState = computeControlDashboardUiState(
- playbackState = playbackState,
- isPermissionDenied = isPermissionDenied
- )
- ControlDashboard(
- controlDashboardUiState = controlDashboardUiState,
- onMicClicked = onMicClicked,
- recordingProgress = recordingProgress
- )
- }
-}
-
-private fun computeControlDashboardUiState(
- playbackState: PlaybackState,
- isPermissionDenied: Boolean
-): ControlDashboardUiState =
- when (playbackState) {
- PlaybackState.PlayingVoice -> ControlDashboardUiState(
- micState = ControlDashboardButtonUiState(
- expanded = false,
- enabled = false,
- visible = false
- ),
- playState = ControlDashboardButtonUiState(
- expanded = true,
- enabled = true,
- visible = true
- )
- )
- PlaybackState.Ready -> ControlDashboardUiState(
- micState = ControlDashboardButtonUiState(
- expanded = false,
- enabled = !isPermissionDenied,
- visible = true
- ),
- playState = ControlDashboardButtonUiState(
- expanded = false,
- enabled = true,
- visible = true
- )
- )
- PlaybackState.Recording -> ControlDashboardUiState(
- micState = ControlDashboardButtonUiState(
- expanded = true,
- enabled = true,
- visible = true
- ),
- playState = ControlDashboardButtonUiState(
- expanded = false,
- enabled = false,
- visible = false
- )
- )
- }
-
-private class PlaybackStatePreviewProvider : CollectionPreviewParameterProvider(
- listOf(
- PlaybackState.Ready,
- PlaybackState.Recording,
- PlaybackState.PlayingVoice
- )
-)
-
-@WearPreviewDevices
-@WearPreviewFontScales
-@Composable
-fun SpeakerScreenPreview(
- @PreviewParameter(PlaybackStatePreviewProvider::class) playbackState: PlaybackState
-) {
- SpeakerRecordingScreen(
- playbackState = playbackState,
- isPermissionDenied = true,
- recordingProgress = 0.25f,
- onMicClicked = {}
- )
-}
diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/StartRecordingScreen.kt b/wear/src/main/java/com/birdsounds/identify/presentation/StartRecordingScreen.kt
new file mode 100644
index 0000000..c010bd4
--- /dev/null
+++ b/wear/src/main/java/com/birdsounds/identify/presentation/StartRecordingScreen.kt
@@ -0,0 +1,68 @@
+package com.birdsounds.identify.presentation
+
+
+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 com.google.android.horologist.compose.layout.ScreenScaffold
+
+@Composable
+fun StartRecordingScreen(
+ appState: AppState,
+ isPermissionDenied: Boolean,
+ onMicClicked: () -> Unit
+) {
+ ScreenScaffold {
+ val controlDashboardUiState = computeControlDashboardUiState(
+ appState = appState,
+ isPermissionDenied = isPermissionDenied
+ )
+ ControlDashboard(
+ controlDashboardUiState = controlDashboardUiState,
+ onMicClicked = onMicClicked
+ )
+ }
+}
+
+private fun computeControlDashboardUiState(
+ appState: AppState,
+ isPermissionDenied: Boolean
+): ControlDashboardUiState =
+ when (appState) {
+ AppState.Ready -> ControlDashboardUiState(
+ micState = ControlDashboardButtonUiState(
+ expanded = false,
+ enabled = !isPermissionDenied,
+ visible = true
+ ),
+ )
+ AppState.Recording -> ControlDashboardUiState(
+ micState = ControlDashboardButtonUiState(
+ expanded = true,
+ enabled = true,
+ visible = true
+ ),
+ )
+ }
+
+private class PlaybackStatePreviewProvider : CollectionPreviewParameterProvider(
+ listOf(
+ AppState.Recording,
+ AppState.Ready
+ )
+)
+
+@WearPreviewDevices
+@WearPreviewFontScales
+@Composable
+fun SpeakerScreenPreview(
+ @PreviewParameter(PlaybackStatePreviewProvider::class) appState: AppState
+) {
+ StartRecordingScreen(
+ appState = appState,
+ isPermissionDenied = true,
+ onMicClicked = {}
+ )
+}