Restore latest local work after Seafile git corruption repair

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TvDdasWXdhrvQriBCPfFn1
This commit is contained in:
2026-08-30 22:19:01 -04:00
parent e73d52d221
commit a70b507c18
29 changed files with 1089 additions and 1163 deletions
@@ -61,7 +61,8 @@ fun FlashingText(
modifier = modifier
.background(color = backgroundColor),
textDecor = textDecor,
fontStyle = fontStyle
fontStyle = fontStyle,
key = key
)
}
@@ -72,6 +73,7 @@ fun AutoSizedText(
textAlign: TextAlign = TextAlign.Center,
modifier: Modifier = Modifier,
textDecor: TextDecoration = TextDecoration.None,
key: Long = 0,
minFontSize: TextUnit = 5.sp,
targetFontSize: TextUnit = 24.sp,
fontWeight: FontWeight = FontWeight.Normal,
@@ -115,7 +117,9 @@ fun AutoSizedText(
// Convert the best font size back to sp and use it
// val bestFontSize = with(density) { bestFontSizePx.toSp() }
LaunchedEffect(key1 = key) {
}
Text(
text = display_text,
@@ -12,7 +12,6 @@ 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
@@ -41,7 +40,6 @@ import androidx.compose.ui.platform.LocalContext
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
@@ -53,7 +51,6 @@ import androidx.wear.compose.navigation.SwipeDismissableNavHost
import androidx.wear.compose.navigation.composable
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
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
@@ -166,7 +163,7 @@ fun WearApp() {
) {
composable("species_list") {
ScreenScaffold {
SpeciesListView(context = context, appState = mainState.appState)
SpeciesListView(context = context, appState = mainState.appState, birdHeardState = mainState.birdHeardState, mainState = mainState )
}
}
@@ -20,8 +20,14 @@ import java.time.Duration
class MainState(private val activity: Activity, private val requestPermission: () -> Unit) {
private val playbackStateMutatorMutex = MutatorMutex()
var appState by mutableStateOf<AppState>(AppState.Ready)
private set
var birdHeardState by mutableStateOf<BirdHeardState>(BirdHeardState.NotHearing)
var recordingProgress by mutableStateOf(0f)
private set
var isPermissionDenied by mutableStateOf(false)
@@ -82,6 +88,13 @@ sealed class AppState {
object Recording : AppState()
}
sealed class BirdHeardState {
object Hearing : BirdHeardState()
object NotHearing : BirdHeardState()
}
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
private suspend fun record(activity: (Activity),soundRecorder: SoundRecorder,
setProgress: (progress: Float) -> Unit,
@@ -1,6 +1,7 @@
package com.birdsounds.identify.presentation
import android.content.Context
import android.util.Log
import androidx.compose.ui.platform.LocalContext
import com.google.android.gms.wearable.MessageEvent
import com.google.android.gms.wearable.WearableListenerService
@@ -16,6 +17,7 @@ class MessageListenerService : WearableListenerService() {
{
this_context = context;
}
override fun onMessageReceived(p0: MessageEvent) {
super.onMessageReceived(p0)
val t_scored = ByteBuffer.wrap(p0.data).getLong()
@@ -27,12 +29,14 @@ class MessageListenerService : WearableListenerService() {
var do_vibrate = false;
var did_add = false;
var new_entries = 0;
SpeciesList.clear_new_flags();
// bird_heard_state = BirdHeardState.NotHearing
SpeciesList.bird_not_heard();
list_strings.map({
var split_str = it.split(',')
if (split_str.size == 2) {
if ((split_str.size == 2) && !(split_str[0]=="bird1_bird1")) {
Log.d(TAG, split_str[0] + "${split_str[0] == "bird1"}")
new_entries+=1;
var out = AScore(split_str[0], split_str[1].toFloat(), t_scored);
var out = AScore(split_str[0], split_str[1].toFloat(), t_scored, true);
out.new_entry = true;
if (SpeciesList.add_observation(out))
@@ -41,8 +45,16 @@ class MessageListenerService : WearableListenerService() {
}
do_vibrate = true;
}
else if (split_str[0]=="bird1_bird1") {
Log.d(TAG, "I AM HEARING A BIRD")
SpeciesList.bird_heard();
// bird_heard_state = BirdHeardState.Hearing
}
})
// SpeciesList.set_num_new_entries(new_entries);
SpeciesList.num_new_entries = new_entries;
// SpeciesList.insert_new_entry_spacer(new_entries);
if (did_add) {
vibrateDouble(this, 100, 250, 100);
@@ -31,6 +31,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import java.nio.ByteOrder
fun vibrateDouble(
context: Context,
@@ -40,7 +41,8 @@ fun vibrateDouble(
) {
// 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
val vibratorManager =
context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
@Suppress("DEPRECATION")
@@ -64,7 +66,8 @@ fun vibrateDouble(
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
val vibratorManager =
context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
@Suppress("DEPRECATION")
@@ -113,7 +116,8 @@ class SoundRecorder(
val frameSize = Constants.FrameSize._120();
val chunkSize = frameSize.v * 2; // Mono * 2 bytes per sample
val counts_until_reset = 3 * sampleRateInHz / frameSize.v;
val seconds_per_message = 3
val counts_until_reset = seconds_per_message * sampleRateInHz / frameSize.v;
val bufferSize =
AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
@@ -126,15 +130,14 @@ class SoundRecorder(
)
val thread = Thread {
// var sent_first: Boolean = false
val outputByteStream = ByteBuffer.allocate(1000000)
audioRecord.startRecording()
var last_tstamp: Long = Instant.now().toEpochMilli();
var count: Int = 0;
var min_val = 1000000f
var max_val = -1000000f
while (true) /**/ {
if (Thread.interrupted()) {
audioRecord.release();
@@ -155,10 +158,16 @@ class SoundRecorder(
var offset = 0
var remained = frame.size
while (remained > 0) {
val read = audioRecord.read(frame, offset, remained)
val read: Int = audioRecord.read(frame, offset, remained)
offset += read
remained -= read
}
val s_arry = ShortArray(frame.size / 2)
ByteBuffer.wrap(frame).order(
ByteOrder.LITTLE_ENDIAN
).asShortBuffer().get(s_arry);
min_val = Math.min(s_arry.min().toFloat(), min_val)
max_val = Math.max(s_arry.max().toFloat(), max_val);
val encoded: ByteArray? =
@@ -174,7 +183,7 @@ class SoundRecorder(
codec_opus.encoderRelease();
count = 0;
val writtenBytes = outputByteStream.position()
Log.e(TAG,"Wrote ${writtenBytes} bytes!")
Log.e(TAG, "Wrote ${writtenBytes} bytes!")
val duplicate = outputByteStream.duplicate()
duplicate.flip() // Prepare for reading
// outputByteStream.flip()
@@ -185,8 +194,14 @@ class SoundRecorder(
val tstamp_buffer = ByteBuffer.allocate(Long.SIZE_BYTES)
val tstamp_bytes = tstamp_buffer.putLong(tstamp).array()
var byte_send: ByteArray = tstamp_bytes + bytes;
Log.e(TAG, "Sending message of 3s with size ${byte_send.size} + ${byte_send[0].toString()}")
Log.e(
TAG,
"Sending message of 3s with size ${byte_send.size} + ${byte_send[0].toString()}"
)
MessageSender.sendMessage("/audio", byte_send, context)
Log.d(TAG, "${min_val} ${max_val} ")
min_val = 1000000f
max_val = -1000000f
}
@@ -12,27 +12,22 @@ import java.time.Instant
class AScore(
_species: String,
_score: Float,
_timestamp: Long,
_trigger: Boolean = false,
_new_entry: Boolean = false,
_redraw_number: Long = 0,
val _species: String,
val _score: Float,
val _timestamp: Long,
val _new_entry: Boolean = false,
val _trigger: Boolean = false,
val _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)
@@ -48,10 +43,10 @@ class AScore(
var internal_list = mutableListOf<AScore>()
object SpeciesList {
var trigger_redraw = 0;
lateinit var _mainState: MainState
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;
}
@@ -61,9 +56,9 @@ object SpeciesList {
i.new_entry = false;
}
}
fun add_observation(species_in: AScore): Boolean {
Log.w(TAG, "In add observation")
var idx = 0
var idx_replace = -1
var added_new = false;
@@ -74,7 +69,6 @@ object SpeciesList {
idx += 1
}
if (idx_replace >= 0) {
Log.w(TAG, "Replacing")
internal_list[idx_replace] = species_in
internal_list[idx_replace].trigger = true;
} else {
@@ -94,6 +88,7 @@ object SpeciesList {
fun update_list_on_ui_thread(
) {
// _list_on_ui!!.clear();
while (_list_on_ui!!.size < internal_list.size) {
_list_on_ui!!.add(mutableStateOf(AScore("", 0.0F, 0L)))
}
@@ -101,9 +96,24 @@ object SpeciesList {
for ((index, value) in internal_list.withIndex()) {
_list_on_ui?.get(index)?.value = value;
}
}
fun bird_not_heard() {
_mainState.birdHeardState= BirdHeardState.NotHearing;
Log.d(TAG, "I set the state to not hearing")
}
fun bird_heard() {
_mainState.birdHeardState = BirdHeardState.Hearing;
Log.d(TAG, "I set the state to hearing")
}
fun setBirdHeardVar(birdHeardState: BirdHeardState) {
}
fun setMainState(mainState: MainState) {
_mainState = mainState;
Log.d(TAG, "I set the internal app main state variable")
}
}
@@ -2,6 +2,9 @@ package com.birdsounds.identify.presentation
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.text.format.DateFormat
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
@@ -11,17 +14,18 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Face
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.Mic
import androidx.compose.material.icons.rounded.MicOff
import androidx.compose.material.icons.rounded.MusicNote
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
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -29,11 +33,13 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.curvedComposable
import androidx.wear.compose.foundation.lazy.items
import androidx.wear.compose.foundation.lazy.itemsIndexed
import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.LocalContentColor
import androidx.wear.compose.material.TimeTextDefaults
import androidx.wear.compose.material.TimeText
import androidx.wear.compose.material.curvedText
@@ -67,11 +73,13 @@ fun interpolateColor(value: Float): Color {
val species_list_show = mutableStateListOf<MutableState<AScore>>()
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SpeciesListView(
context: Context, appState: AppState,
context: Context, appState: AppState, birdHeardState: BirdHeardState, mainState: MainState,
) {
@@ -80,7 +88,7 @@ fun SpeciesListView(
species_list_show.add(hi);
}
SpeciesList.setSpeciecsShow_list(species_list_show)
SpeciesList.setMainState(mainState);
val species_show: SnapshotStateList<MutableState<AScore>> = remember { species_list_show }
// var sP = scalingParams( maxTransitionArea= 0.25f, minTransitionArea = 0.05f)
@@ -96,6 +104,16 @@ fun SpeciesListView(
),
)
var battery_level = remember { mutableStateOf(getBatteryLevel(context)) }
LaunchedEffect(key1 = Unit) {
while (true) {
battery_level.value = getBatteryLevel(context)
delay(60000) // Update every minute
}
}
val text_counter = remember { mutableStateOf("Initial text") }
LaunchedEffect(key1 = true) {
while (true) {
@@ -115,7 +133,6 @@ fun SpeciesListView(
ScreenScaffold(
scrollState = columnState
) {
@@ -125,23 +142,34 @@ fun SpeciesListView(
DateFormat.getBestDateTimePattern(Locale.getDefault(), "hh:mm")
),
startCurvedContent = {
curvedComposable {
Icon(
Icons.Rounded.MusicNote,
contentDescription = "",
modifier = Modifier.size(14.dp),
tint = if (mainState.birdHeardState == BirdHeardState.Hearing
) Color.Green else Color.Black
)
}
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
modifier = Modifier.size(14.dp)
)
}
// curvedText(
// text = if (appState == AppState.Recording) "" else "Inactive0",
//// textAlign = TextAlign.Center,
// )
},
endCurvedContent = {
curvedText(text = text_counter.value)//, textAlign = TextAlign.Center)
curvedText(
text = text_counter.value + " · " + battery_level.value+"%",
fontSize = TextUnit(value = 14F, type = TextUnitType.Sp)
)
//, textAlign = TextAlign.Center)`
}
@@ -156,102 +184,77 @@ fun SpeciesListView(
) {
ScalingLazyColumn(
columnState = columnState,
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
columnState = columnState
) {
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;"
// if (isNewEntry) {
// text_style = FontStyle.Italic;
// aSpec.value.new_entry = false;
// }
Log.w(
TAG,
"Adding Separator: " + index.toString() + " " + SpeciesList.num_new_entries + " " + add_sep.toString()
index.toString() + " " + currentName + " " + isNewEntry.toString() + " " + shouldTrigger.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
Row {
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 {
} else {
Row {
AutoSizedText(
text = aSpec.value.common_name,
color = interpolateColor(aSpec.value.score),
modifier = Modifier.fillMaxSize(),
textAlign = TextAlign.Center,
key = Instant.now().toEpochMilli(),
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)
// } // Dynamically display the chips
//
// }
// }
// Get current battery level
private fun getBatteryLevel(context: Context): Int {
val batteryStatus: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { iFilter ->
context.registerReceiver(null, iFilter)
}
val level = batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
val scale = batteryStatus?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
return if (level != -1 && scale != -1) {
level * 100 / scale
} else {
-1
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 0 B