YACWC
This commit is contained in:
@@ -44,21 +44,20 @@ dependencies {
|
||||
include("*.jar")
|
||||
})
|
||||
api(files("libs/opus.aar"))
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4")
|
||||
implementation("androidx.compose.ui:ui-tooling:1.3.1")
|
||||
implementation("androidx.navigation:navigation-compose:2.8.0-rc01")
|
||||
implementation("androidx.wear.compose:compose-navigation:1.3.1")
|
||||
implementation("com.google.android.horologist:horologist-audio-ui:0.6.18")
|
||||
implementation("com.google.android.horologist:horologist-audio:0.6.18")
|
||||
implementation("com.google.android.horologist:horologist-compose-tools:0.6.18")
|
||||
implementation("com.google.android.horologist:horologist-compose-tools:0.6.18")
|
||||
implementation("androidx.compose.material:material-icons-core:1.6.8")
|
||||
implementation("androidx.compose.material:material-icons-extended:1.6.8")
|
||||
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.androidx.lifecycle.viewmodel.compose)
|
||||
implementation(libs.ui.tooling)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.compose.navigation.v131)
|
||||
implementation(libs.horologist.audio.ui)
|
||||
implementation(libs.horologist.audio)
|
||||
implementation(libs.horologist.compose.tools)
|
||||
implementation(libs.androidx.material.icons.core)
|
||||
implementation(libs.androidx.material.icons.extended)
|
||||
implementation(libs.horologist.compose.material)
|
||||
implementation(libs.horologist.media.ui)
|
||||
implementation(libs.horologist.media.data)
|
||||
implementation(libs.kotlinx.coroutines.play.services)
|
||||
implementation(libs.androidx.media3.exoplayer)
|
||||
implementation(libs.play.services.wearable)
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.ui)
|
||||
@@ -66,15 +65,20 @@ dependencies {
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.compose.material)
|
||||
implementation(libs.androidx.compose.foundation)
|
||||
// implementation("androidx.compose.foundation:foundation:1.8.0-rc01")
|
||||
implementation(libs.androidx.wear.tooling.preview)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.core.splashscreen)
|
||||
implementation(libs.androidx.compose.navigation)
|
||||
implementation(libs.androidx.media3.common)
|
||||
implementation("co.touchlab:stately-concurrent-collections:2.0.0")
|
||||
implementation(libs.androidx.wear.ongoing)
|
||||
implementation(libs.accompanist.flowlayout)
|
||||
implementation(libs.stately.concurrent.collections)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.androidx.runtime.android) // androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.runtime.android)
|
||||
implementation(libs.androidx.animation.core.android)
|
||||
// androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
// androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||
// debugImplementation(libs.androidx.ui.tooling)
|
||||
// debugImplementation(libs.androidx.ui.test.manifest)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
|
||||
@@ -26,6 +28,8 @@
|
||||
<activity
|
||||
android:name=".presentation.MainActivity"
|
||||
android:exported="true"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:immersive="true"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/MainActivityTheme.Starting">
|
||||
<intent-filter>
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.birdsounds.identify.presentation
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.wear.compose.material.Text
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
|
||||
@Composable
|
||||
fun FlashingText(
|
||||
text: String,
|
||||
color: Color,
|
||||
textDecor: TextDecoration = TextDecoration.None,
|
||||
modifier: Modifier = Modifier,
|
||||
key: Long,
|
||||
fontStyle: FontStyle = FontStyle.Normal
|
||||
) {
|
||||
// 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.DarkGray else Color.Transparent,
|
||||
animationSpec = tween(durationMillis = 1000)
|
||||
)
|
||||
|
||||
// Trigger the flash effect once
|
||||
LaunchedEffect(key1 = key) {
|
||||
isFlashing = true
|
||||
delay(1500)
|
||||
isFlashing = false
|
||||
}
|
||||
|
||||
// Display the text with animated background
|
||||
AutoSizedText(
|
||||
text = text,
|
||||
color = color,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = modifier
|
||||
.background(color = backgroundColor),
|
||||
textDecor = textDecor,
|
||||
fontStyle = fontStyle
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AutoSizedText(
|
||||
text: String,
|
||||
color: Color = Color.White,
|
||||
textAlign: TextAlign = TextAlign.Center,
|
||||
modifier: Modifier = Modifier,
|
||||
textDecor: TextDecoration = TextDecoration.None,
|
||||
minFontSize: TextUnit = 5.sp,
|
||||
targetFontSize: TextUnit = 24.sp,
|
||||
fontWeight: FontWeight = FontWeight.Normal,
|
||||
fontStyle: FontStyle = FontStyle.Normal,
|
||||
) {
|
||||
var display_text = text;
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
BoxWithConstraints(modifier) {
|
||||
val density = LocalDensity.current
|
||||
// val minFontSizePx = with(density) { minFontSize.toPx() }
|
||||
// val maxFontSizePx = with(density) { targetFontSize.toPx() }
|
||||
val availableWidthPx = with(density) { maxWidth.toPx() }
|
||||
|
||||
// Binary search to find the appropriate font size that fits the available width
|
||||
var lowPx: TextUnit = minFontSize
|
||||
var highPx: TextUnit = targetFontSize
|
||||
var bestFontSizePx = targetFontSize;
|
||||
val textStyle = TextStyle(fontWeight = fontWeight, color = color, fontSize=bestFontSizePx)
|
||||
while (lowPx <= highPx) {
|
||||
val midPx: TextUnit = TextUnit(value=lowPx.value/2 + highPx.value/2 , type= TextUnitType.Sp)
|
||||
val style = textStyle.copy(fontSize = midPx)
|
||||
|
||||
val textLayoutResult = textMeasurer.measure(
|
||||
text = display_text,
|
||||
style = style,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
density = density,
|
||||
)
|
||||
|
||||
if (textLayoutResult.size.width <= availableWidthPx) {
|
||||
// // This font size fits, try a larger oneb
|
||||
bestFontSizePx = midPx;
|
||||
break;
|
||||
} else {
|
||||
// // This font size is too large, try a smaller one
|
||||
// highPx = (midPx - 1).toFloat()
|
||||
highPx = TextUnit(value=midPx.value - 1, type=TextUnitType.Sp)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the best font size back to sp and use it
|
||||
// val bestFontSize = with(density) { bestFontSizePx.toSp() }
|
||||
|
||||
|
||||
Text(
|
||||
text = display_text,
|
||||
color = color,
|
||||
fontSize = bestFontSizePx,
|
||||
fontStyle = fontStyle,
|
||||
fontWeight = fontWeight,
|
||||
maxLines = 1,
|
||||
textDecoration = textDecor,
|
||||
overflow = TextOverflow.Clip,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = textAlign
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
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.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,
|
||||
onNavClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier) {
|
||||
Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxSize()) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
ControlDashboardButton(buttonState = controlDashboardUiState.micState,
|
||||
onClick = onMicClicked,
|
||||
labelText = if (controlDashboardUiState.micState.expanded) {
|
||||
"Stop"
|
||||
} else {
|
||||
"Start"
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,56 +6,62 @@
|
||||
package com.birdsounds.identify.presentation
|
||||
|
||||
import android.Manifest
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
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.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.ServiceCompat.startForeground
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.wear.ambient.AmbientLifecycleObserver
|
||||
import androidx.wear.compose.material.CompactChip
|
||||
import androidx.wear.compose.material.MaterialTheme
|
||||
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 androidx.wear.ongoing.OngoingActivity
|
||||
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.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
|
||||
|
||||
|
||||
val flow_stream = MutableStateFlow<String>("")
|
||||
|
||||
val Any.TAG: String
|
||||
@@ -64,26 +70,30 @@ val Any.TAG: String
|
||||
return if (tag.length <= 23) tag else tag.substring(0, 23)
|
||||
}
|
||||
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
|
||||
lateinit var ambientObserver:AmbientLifecycleObserver
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
|
||||
ambientObserver= AmbientLifecycleObserver(this.findActivity(), ambientCallback)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
lifecycle.addObserver(ambientObserver)
|
||||
setTheme(android.R.style.Theme_DeviceDefault)
|
||||
setContent {
|
||||
WearApp()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ForegroundServiceType")
|
||||
@OptIn(ExperimentalHorologistApi::class)
|
||||
@Composable
|
||||
@Preview
|
||||
fun WearApp() {
|
||||
|
||||
IdentifyTheme {
|
||||
lateinit var requestPermissionLauncher: ManagedActivityResultLauncher<String, Boolean>
|
||||
val context = LocalContext.current
|
||||
@@ -91,6 +101,48 @@ fun WearApp() {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
||||
val touchIntent =
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
Intent(context, MainActivity::class.java),
|
||||
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
|
||||
val notificationBuilder =
|
||||
NotificationCompat.Builder(context, "Identify Watch App")
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(com.birdsounds.identify.R.mipmap.ic_launcher)
|
||||
|
||||
|
||||
val ongoingActivity =
|
||||
OngoingActivity.Builder(
|
||||
context, 4, notificationBuilder
|
||||
).setTouchIntent(touchIntent)
|
||||
.setStaticIcon(
|
||||
com.birdsounds.identify.R.mipmap.ic_launcher)
|
||||
.build()
|
||||
|
||||
|
||||
ongoingActivity.apply(context)
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.notify(
|
||||
4,
|
||||
notificationBuilder.build()
|
||||
)
|
||||
|
||||
val notification: Notification = NotificationCompat.Builder(
|
||||
context, "Identify Watch App"
|
||||
)
|
||||
.setContentTitle("Always On App")
|
||||
.setContentText("Running in foreground")
|
||||
.setSmallIcon(com.birdsounds.identify.R.mipmap.ic_launcher)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
|
||||
|
||||
|
||||
val volumeViewModel: VolumeViewModel = viewModel(factory = VolumeViewModel.Factory)
|
||||
|
||||
val mainState = remember(activity) {
|
||||
@@ -99,7 +151,6 @@ fun WearApp() {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
requestPermissionLauncher = rememberLauncherForActivityResult(RequestPermission()) {
|
||||
scope.launch {
|
||||
mainState.permissionResultReturned()
|
||||
@@ -108,42 +159,111 @@ fun WearApp() {
|
||||
|
||||
val navController: NavHostController = rememberSwipeDismissableNavController()
|
||||
mainState.setNavController(navController);
|
||||
SwipeDismissableNavHost(navController = navController, userSwipeEnabled = true, startDestination = "speaker") {
|
||||
|
||||
SwipeDismissableNavHost(
|
||||
navController = navController,
|
||||
userSwipeEnabled = true,
|
||||
startDestination = "speaker"
|
||||
) {
|
||||
composable("species_list") {
|
||||
ScreenScaffold {
|
||||
SpeciesListView(context = context)
|
||||
SpeciesListView(context = context, appState = mainState.appState)
|
||||
}
|
||||
}
|
||||
|
||||
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>")
|
||||
})
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Row() {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isPressed by interactionSource.collectIsPressedAsState()
|
||||
|
||||
// Define the animation for the scale
|
||||
val scale by animateFloatAsState(
|
||||
targetValue = if (isPressed) 2.0f else 1.0f
|
||||
)
|
||||
|
||||
CompactChip(
|
||||
onClick = {
|
||||
vibrate(
|
||||
context,
|
||||
250
|
||||
);navController.navigate("species_list")
|
||||
},
|
||||
enabled = true,
|
||||
modifier = Modifier.scale(scale),
|
||||
interactionSource = interactionSource,
|
||||
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
label = {
|
||||
Text(text = "Show List")
|
||||
},
|
||||
|
||||
)
|
||||
|
||||
|
||||
StartRecordingScreen(
|
||||
appState = mainState.appState,
|
||||
onMicClicked = {
|
||||
scope.launch {
|
||||
mainState.onMicClicked()
|
||||
}
|
||||
},
|
||||
onNavClicked = {
|
||||
navController.navigate("species_list")
|
||||
}
|
||||
Row() {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isPressed by interactionSource.collectIsPressedAsState()
|
||||
|
||||
)
|
||||
// Define the animation for the scale
|
||||
val scale by animateFloatAsState(
|
||||
targetValue = if (isPressed) 2.0f else 1.0f
|
||||
)
|
||||
CompactChip(
|
||||
onClick = {
|
||||
vibrate(context, 250);
|
||||
scope.launch {
|
||||
mainState.onMicClicked()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.scale(scale),
|
||||
interactionSource = interactionSource,
|
||||
enabled = true,
|
||||
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
label = {
|
||||
Text(text = if (mainState.appState == AppState.Recording) "Stop Recording" else "Start Recording")
|
||||
})
|
||||
}
|
||||
Row() {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isPressed by interactionSource.collectIsPressedAsState()
|
||||
|
||||
// Define the animation for the scale
|
||||
val scale by animateFloatAsState(
|
||||
targetValue = if (isPressed) 2.0f else 1.0f
|
||||
)
|
||||
CompactChip(
|
||||
onClick = {
|
||||
vibrate(context, 250);
|
||||
species_list_show.clear();
|
||||
internal_list.clear();
|
||||
},
|
||||
enabled = true,
|
||||
modifier = Modifier.scale(scale),
|
||||
interactionSource = interactionSource,
|
||||
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
label = {
|
||||
Text(text = "Clear List")
|
||||
},
|
||||
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ class MainState(private val activity: Activity, private val requestPermission: (
|
||||
public fun setNavController(_navController: NavHostController) {
|
||||
navController = _navController
|
||||
}
|
||||
|
||||
|
||||
|
||||
suspend fun onMicClicked() {
|
||||
playbackStateMutatorMutex.mutate {
|
||||
when (appState) {
|
||||
|
||||
@@ -1,28 +1,58 @@
|
||||
package com.birdsounds.identify.presentation
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.google.android.gms.wearable.MessageEvent
|
||||
import com.google.android.gms.wearable.WearableListenerService
|
||||
import kotlinx.coroutines.delay
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
var last_message_recv_tstamp: Long = 0L;
|
||||
|
||||
class MessageListenerService : WearableListenerService() {
|
||||
private val tag = "MessageListenerService"
|
||||
|
||||
lateinit var this_context: Context;
|
||||
fun set_context(context: Context)
|
||||
{
|
||||
this_context = context;
|
||||
}
|
||||
override fun onMessageReceived(p0: MessageEvent) {
|
||||
super.onMessageReceived(p0)
|
||||
val t_scored = ByteBuffer.wrap(p0.data).getLong()
|
||||
last_message_recv_tstamp = t_scored;
|
||||
|
||||
var byte_strings: ByteArray = p0.data.copyOfRange(8, p0.data.size)
|
||||
var score_species_string = byte_strings.decodeToString()
|
||||
var list_strings: List<String> = score_species_string.split(';')
|
||||
var do_vibrate = false;
|
||||
var did_add = false;
|
||||
var new_entries = 0;
|
||||
SpeciesList.clear_new_flags();
|
||||
list_strings.map({
|
||||
var split_str = it.split(',')
|
||||
if (split_str.size == 2) {
|
||||
var out = AScore(split_str[0], split_str[1].toFloat(), t_scored)
|
||||
if (out.score > 0.005) {
|
||||
SpeciesList.add_observation(out)
|
||||
new_entries+=1;
|
||||
var out = AScore(split_str[0], split_str[1].toFloat(), t_scored);
|
||||
out.new_entry = true;
|
||||
|
||||
if (SpeciesList.add_observation(out))
|
||||
{
|
||||
did_add = true;
|
||||
}
|
||||
do_vibrate = true;
|
||||
}
|
||||
})
|
||||
// SpeciesList.set_num_new_entries(new_entries);
|
||||
// SpeciesList.insert_new_entry_spacer(new_entries);
|
||||
if (did_add) {
|
||||
vibrateDouble(this, 100, 250, 100);
|
||||
} else if (do_vibrate) {
|
||||
vibrate(this,100)
|
||||
}
|
||||
// if (new_entries > 0) {
|
||||
SpeciesList.update_list_on_ui_thread();
|
||||
// }
|
||||
|
||||
MessageSender.messageLog.add(t_scored)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.birdsounds.identify.presentation
|
||||
|
||||
import androidx.wear.ambient.AmbientLifecycleObserver
|
||||
|
||||
val ambientCallback = object : AmbientLifecycleObserver.AmbientLifecycleCallback {
|
||||
override fun onEnterAmbient(ambientDetails: AmbientLifecycleObserver.AmbientDetails) {
|
||||
// ... Called when moving from interactive mode into ambient mode.
|
||||
}
|
||||
|
||||
override fun onExitAmbient() {
|
||||
// ... Called when leaving ambient mode, back into interactive mode.
|
||||
}
|
||||
|
||||
override fun onUpdateAmbient() {
|
||||
// ... Called by the system in order to allow the app to periodically
|
||||
// update the display while in ambient mode. Typically the system will
|
||||
// call this every 60 seconds.
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,13 @@ import com.theeasiestway.opus.Opus
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
|
||||
import android.media.AudioFormat
|
||||
import android.media.AudioRecord
|
||||
import android.media.MediaRecorder
|
||||
import android.media.audiofx.AutomaticGainControl
|
||||
import android.media.audiofx.NoiseSuppressor
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresPermission
|
||||
import encodePcmToAac
|
||||
@@ -21,6 +23,66 @@ import java.time.Instant
|
||||
/**
|
||||
* A helper class to provide methods to record audio input from the MIC to the internal storage.
|
||||
*/
|
||||
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
fun vibrateDouble(
|
||||
context: Context,
|
||||
firstDuration: Long = 100,
|
||||
pauseDuration: Long = 200,
|
||||
secondDuration: Long = 100
|
||||
) {
|
||||
// Get the vibrator service
|
||||
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||
vibratorManager.defaultVibrator
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
}
|
||||
|
||||
// Create a pattern for double vibration: 0ms delay, firstDuration vibrate, pauseDuration pause, secondDuration vibrate
|
||||
val vibrationPattern = longArrayOf(0, firstDuration, pauseDuration, secondDuration)
|
||||
|
||||
// The -1 parameter means don't repeat the pattern
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val vibrationEffect = VibrationEffect.createWaveform(vibrationPattern, -1)
|
||||
vibrator.vibrate(vibrationEffect)
|
||||
} else {
|
||||
// Deprecated method for older API levels
|
||||
@Suppress("DEPRECATION")
|
||||
vibrator.vibrate(vibrationPattern, -1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun vibrate(context: Context, duration: Long = 500) {
|
||||
val vibrator = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||
vibratorManager.defaultVibrator
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
val vibrationEffect = VibrationEffect.createOneShot(
|
||||
duration, // duration in milliseconds
|
||||
VibrationEffect.DEFAULT_AMPLITUDE // strength of vibration
|
||||
)
|
||||
vibrator.vibrate(vibrationEffect)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
vibrator.vibrate(duration) // Vibration for 500ms
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class SoundRecorder(
|
||||
context_in: Context,
|
||||
@@ -44,7 +106,7 @@ class SoundRecorder(
|
||||
var noiseSuppressor: NoiseSuppressor? = null
|
||||
var automaticGainControl: AutomaticGainControl? = null
|
||||
var chunk_index: Int = 0
|
||||
val audioSource = MediaRecorder.AudioSource.DEFAULT
|
||||
val audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION
|
||||
val sampleRateInHz = 48000
|
||||
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
||||
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.birdsounds.identify.presentation
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import java.time.Instant
|
||||
|
||||
@@ -12,16 +15,24 @@ class AScore(
|
||||
_species: String,
|
||||
_score: Float,
|
||||
_timestamp: Long,
|
||||
_trigger: Boolean = false
|
||||
) {
|
||||
|
||||
_trigger: Boolean = false,
|
||||
_new_entry: Boolean = false,
|
||||
_redraw_number: Long = 0,
|
||||
) {
|
||||
var redraw_number = _redraw_number;
|
||||
val split_stuff: List<String> = _species.split("_");
|
||||
val species = split_stuff[0];
|
||||
val score = _score;
|
||||
var trigger = _trigger;
|
||||
var new_entry = _new_entry;
|
||||
|
||||
// var common_name = split_stuff[1];
|
||||
val common_name = if (split_stuff.size > 1) split_stuff[1] else "";
|
||||
val timestamp = _timestamp
|
||||
fun set_redraw_number(redraw_num: Long)
|
||||
{
|
||||
redraw_number = redraw_num
|
||||
}
|
||||
fun age(): Long {
|
||||
var tstamp: Long = Instant.now().toEpochMilli()
|
||||
return (tstamp - timestamp)
|
||||
@@ -34,20 +45,28 @@ class AScore(
|
||||
}
|
||||
}
|
||||
|
||||
var internal_list = mutableListOf<AScore>()
|
||||
|
||||
object SpeciesList {
|
||||
var internal_list = mutableListOf<AScore>()
|
||||
var trigger_redraw = 0;
|
||||
var num_new_entries = 0;
|
||||
var do_add_observation = false
|
||||
var _list_on_ui: SnapshotStateList<MutableState<AScore>>? = null
|
||||
fun setSpeciecsShow_list(list_in: SnapshotStateList<MutableState<AScore>>) {
|
||||
_list_on_ui = list_in;
|
||||
}
|
||||
|
||||
fun clear_new_flags() {
|
||||
for (i in internal_list) {
|
||||
i.new_entry = false;
|
||||
}
|
||||
}
|
||||
|
||||
fun add_observation(species_in: AScore) {
|
||||
fun add_observation(species_in: AScore): Boolean {
|
||||
Log.w(TAG, "In add observation")
|
||||
var idx = 0
|
||||
var idx_replace = -1
|
||||
var added_new = false;
|
||||
for (i in internal_list) {
|
||||
if (i.species == species_in.species) {
|
||||
idx_replace = idx
|
||||
@@ -60,22 +79,31 @@ object SpeciesList {
|
||||
internal_list[idx_replace].trigger = true;
|
||||
} else {
|
||||
internal_list.add(species_in)
|
||||
added_new = true;
|
||||
}
|
||||
|
||||
// internal_list = internal_list.sortedBy({ (it.age()) }).toMutableList()
|
||||
internal_list = internal_list.sortedWith(compareBy({ it.age() }, { it.score})).toMutableList()
|
||||
internal_list =
|
||||
internal_list.sortedWith(compareBy({ it.age() }, { it.score })).toMutableList()
|
||||
|
||||
return added_new
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun update_list_on_ui_thread(
|
||||
) {
|
||||
while (_list_on_ui!!.size < internal_list.size) {
|
||||
_list_on_ui!!.add(mutableStateOf(AScore("",0.0F,0L)))
|
||||
_list_on_ui!!.add(mutableStateOf(AScore("", 0.0F, 0L)))
|
||||
}
|
||||
val time_stamp = System.currentTimeMillis()
|
||||
for ((index, value) in internal_list.withIndex()) {
|
||||
_list_on_ui?.get(index)?.value = value;
|
||||
}
|
||||
|
||||
Log.w(TAG, internal_list.size.toString())
|
||||
Log.w(TAG, _list_on_ui?.size.toString()) // internal_list.add(species_in)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,23 +2,22 @@ package com.birdsounds.identify.presentation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.text.format.DateFormat
|
||||
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.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Mic
|
||||
import androidx.compose.material.icons.rounded.MicOff
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -27,124 +26,228 @@ import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
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.curvedComposable
|
||||
import androidx.wear.compose.foundation.lazy.items
|
||||
import androidx.wear.compose.material.Text
|
||||
import androidx.wear.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.wear.compose.material.Icon
|
||||
import androidx.wear.compose.material.TimeTextDefaults
|
||||
import androidx.wear.compose.material.TimeText
|
||||
import androidx.wear.compose.material.curvedText
|
||||
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
|
||||
import java.time.Instant
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
fun interpolateColor(value: Float): Color {
|
||||
// Ensure that the input value is clamped between 0 and 1
|
||||
val clampedValue = value.coerceIn(0f, 1f)
|
||||
// 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()
|
||||
// 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
|
||||
// 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()
|
||||
// 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)
|
||||
)
|
||||
// Construct the color
|
||||
return Color(red.toInt(), green.toInt(), blue.toInt())
|
||||
}
|
||||
|
||||
|
||||
|
||||
val species_list_show = mutableStateListOf<MutableState<AScore>>()
|
||||
|
||||
@SuppressLint("UnrememberedMutableState")
|
||||
@OptIn(ExperimentalHorologistApi::class)
|
||||
@Composable
|
||||
fun SpeciesListView(context: Context,
|
||||
fun SpeciesListView(
|
||||
context: Context, appState: AppState,
|
||||
) {
|
||||
|
||||
val species_list_show = mutableStateListOf<MutableState<AScore>>()
|
||||
for (i in 1..3)
|
||||
{
|
||||
val hi = mutableStateOf(AScore("",0.0F,0L))
|
||||
|
||||
for (i in 1..3) {
|
||||
val hi = mutableStateOf(AScore("", 0.0F, 0L))
|
||||
species_list_show.add(hi);
|
||||
}
|
||||
SpeciesList.setSpeciecsShow_list(species_list_show)
|
||||
|
||||
val species_show: SnapshotStateList<MutableState<AScore>> = remember { species_list_show }
|
||||
|
||||
// var sP = scalingParams( maxTransitionArea= 0.25f, minTransitionArea = 0.05f)
|
||||
// val columnState = ScalingLazyColumnState(scalingParams = sP);
|
||||
var columnState = rememberResponsiveColumnState(contentPadding = ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text,
|
||||
last = ScalingLazyColumnDefaults.ItemType.Chip), verticalArrangement= Arrangement.spacedBy(
|
||||
space = 1.dp,
|
||||
alignment = Alignment.Top,
|
||||
),)
|
||||
var columnState = rememberResponsiveColumnState(
|
||||
contentPadding = ScalingLazyColumnDefaults.padding(
|
||||
first = ScalingLazyColumnDefaults.ItemType.Text,
|
||||
last = ScalingLazyColumnDefaults.ItemType.Chip
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(
|
||||
space = 1.dp,
|
||||
alignment = Alignment.Top,
|
||||
),
|
||||
)
|
||||
|
||||
val text_counter = remember { mutableStateOf("Initial text") }
|
||||
LaunchedEffect(key1 = true) {
|
||||
while (true) {
|
||||
// Update the text - you can put your custom logic here
|
||||
// For example, showing current time:
|
||||
var lag = ((Instant.now().toEpochMilli() - last_message_recv_tstamp) / 1000F).toInt();
|
||||
if (last_message_recv_tstamp == 0L) {
|
||||
text_counter.value = "No msg recv"
|
||||
} else {
|
||||
text_counter.value = "${lag}s"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
ScreenScaffold(scrollState = columnState) {
|
||||
ScalingLazyColumn(
|
||||
columnState = columnState,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
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
|
||||
|
||||
// Delay for 1 second
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
ScreenScaffold(
|
||||
scrollState = columnState
|
||||
) {
|
||||
TimeText(
|
||||
timeSource =
|
||||
TimeTextDefaults.timeSource(
|
||||
DateFormat.getBestDateTimePattern(Locale.getDefault(), "hh:mm")
|
||||
),
|
||||
startCurvedContent = {
|
||||
curvedComposable {
|
||||
Icon(
|
||||
if (appState == AppState.Recording) Icons.Rounded.Mic else Icons.Rounded.MicOff,
|
||||
contentDescription = "",
|
||||
modifier = Modifier.size(16.dp)
|
||||
// tint = androidx.compose.ui.graphics.Color.Red
|
||||
)
|
||||
}
|
||||
|
||||
// curvedText(
|
||||
// text = if (appState == AppState.Recording) "" else "Inactive0",
|
||||
//// textAlign = TextAlign.Center,
|
||||
// )
|
||||
},
|
||||
endCurvedContent = {
|
||||
|
||||
curvedText(text = text_counter.value)//, textAlign = TextAlign.Center)
|
||||
|
||||
}
|
||||
|
||||
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
|
||||
ScalingLazyColumn(
|
||||
columnState = columnState,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
itemsIndexed(
|
||||
species_show,
|
||||
) { index, aSpec ->
|
||||
var text_decor = TextDecoration.None
|
||||
var text_style = FontStyle.Normal;
|
||||
|
||||
val currentName = aSpec.value.common_name
|
||||
val currentScore = aSpec.value.score
|
||||
val isNewEntry = aSpec.value.new_entry
|
||||
val shouldTrigger = aSpec.value.trigger
|
||||
|
||||
var add_sep = false;
|
||||
if (aSpec.value.new_entry) {
|
||||
// add_sep = true;
|
||||
text_style = FontStyle.Italic;
|
||||
// aSpec.value.new_entry = false
|
||||
}
|
||||
Log.w(TAG, "Species " + currentName + " " + aSpec.value.new_entry.toString())
|
||||
LaunchedEffect(Unit) {
|
||||
delay(100);
|
||||
aSpec.value.new_entry = false
|
||||
}
|
||||
// if (index == (SpeciesList.num_new_entries-1)) {
|
||||
// add_sep = true;"
|
||||
// }
|
||||
Log.w(
|
||||
TAG,
|
||||
"Adding Separator: " + index.toString() + " " + SpeciesList.num_new_entries + " " + add_sep.toString()
|
||||
)
|
||||
aSpec.value.new_entry = false;
|
||||
|
||||
// var composables: List<@Composable () -> Unit> = listOf();
|
||||
|
||||
key(currentName, isNewEntry, shouldTrigger) {
|
||||
if (shouldTrigger) {
|
||||
Log.w(TAG, "Trigger " + aSpec.toString())
|
||||
FlashingText(
|
||||
text = aSpec.value.common_name,
|
||||
fontStyle = text_style,
|
||||
color = interpolateColor(aSpec.value.score),
|
||||
key = Instant.now().toEpochMilli(),
|
||||
textDecor = text_decor,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
delay(100);
|
||||
aSpec.value.trigger = false
|
||||
}
|
||||
Log.e("TAG", "BLINKING")
|
||||
} else {
|
||||
|
||||
AutoSizedText(
|
||||
text = aSpec.value.common_name,
|
||||
color = interpolateColor(aSpec.value.score),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
textAlign = TextAlign.Center,
|
||||
textDecor = text_decor,
|
||||
fontStyle = text_style,
|
||||
)
|
||||
|
||||
Log.e("TAG", "Not blinking")
|
||||
}
|
||||
}
|
||||
|
||||
if (add_sep) {
|
||||
// composables += {
|
||||
AutoSizedText(
|
||||
text = "-------------------",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
// }
|
||||
}
|
||||
|
||||
// Column {
|
||||
// composables.forEach { composable ->
|
||||
// Row {
|
||||
// composable()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
}// Dynamically display the chips
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// ScreenScaffold(scrollState = columnState) {
|
||||
// ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
|
||||
// items(species_show) { aSpec -> Text(text = aSpec.value.common_name)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.birdsounds.identify.presentation
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.navigation.NavHostController
|
||||
import com.google.android.horologist.compose.layout.ScreenScaffold
|
||||
|
||||
@Composable
|
||||
fun StartRecordingScreen(
|
||||
appState: AppState,
|
||||
onMicClicked: () -> Unit,
|
||||
onNavClicked: () -> Unit
|
||||
) {
|
||||
ScreenScaffold {
|
||||
val controlDashboardUiState = computeControlDashboardUiState(
|
||||
appState = appState,
|
||||
)
|
||||
ControlDashboard(
|
||||
controlDashboardUiState = controlDashboardUiState,
|
||||
onMicClicked = onMicClicked,
|
||||
onNavClicked = onNavClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeControlDashboardUiState(
|
||||
appState: AppState,
|
||||
): ControlDashboardUiState =
|
||||
when (appState) {
|
||||
AppState.Ready -> ControlDashboardUiState(
|
||||
micState = ControlDashboardButtonUiState(expanded = false, visible = true, enabled = true),
|
||||
)
|
||||
AppState.Recording -> ControlDashboardUiState(
|
||||
micState = ControlDashboardButtonUiState(expanded = true, visible = true, enabled = true),
|
||||
)
|
||||
}
|
||||
|
||||
private class PlaybackStatePreviewProvider : CollectionPreviewParameterProvider<AppState>(
|
||||
listOf(
|
||||
AppState.Recording,
|
||||
AppState.Ready
|
||||
)
|
||||
)
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<style name="MainActivityTheme.Starting" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@android:color/black</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
|
||||
<item name="postSplashScreenTheme">@android:style/Theme.DeviceDefault</item>
|
||||
</style>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user